A JavaCard with Monoid applets installed is:
- A hardware crypto wallet.
- A FIDO2 authenticator.
- A One-Time Password (OTP) generator.
- A Tesla key.
The goal is to allow third-party Monoid applets to interact with Monoid Applet Shareable interface for signing/verifying and data access, so that Monoid can manage those keys and data and make it easy for users to backup, restore or even synchronize securely with unified UX.
JavaCard 3.0.5 with ECC support is required.
See cards for links to vendors.
For vscode-based editor, please install the recommended Java extensions pack curated by Microsoft and create symlink /jdks/jdk-11 to your JDK 11 installation, e.g.:
ln -s /path/to/jdk-11 /jdks/jdk-11For other editors or terminals, please ensure related configurations accordingly.
Now we can build the project with Gradle:
./gradlew buildJavaCardThen install applets to a card with gp command:
gp --install build/javacard/monoidsafe.cap
gp --install build/javacard/monoid.capThe gradle plugin task
./gradlew installJavaCardwill try to append multiple--installoptions togpcommand, which is not supported in my case. So we have to do it manually.
The development workflow requires Node.js for source code housekeeping and some test case validation.
npm installRun tests with simulator:
./gradlew test --info --rerun-tasksRun tests on physical cards:
# requires a clean installation:
# gp --uninstall build/javacard/monoid.cap
# gp --uninstall build/javacard/monoidsafe.cap
gp --install build/javacard/monoidsafe.cap
gp --install build/javacard/monoid.capCARD_TYPE=physical ./gradlew test --info --rerun-tasksCommands to run:
# sync some constants like AIDs (and more) across files.
npx inplate --update
# format code with prettier. YES!!!
npx prettier --write .The Monoid Applet stores the safe PIN required by Monoid Safe Applet. When it has the safe PIN, the safe is considered unlocked, meaning:
- Monoid Applet itself can access the safe freely (commands probably have their own authentication requirements though).
- Applets with granted permissions can interact (sign/verify) with keys and access data in Monoid Safe Applet (via Monoid Applet).
See architecture for more details.
- Monoid Applet basic commands
- Monoid Applet third-party management commands
- Monoid Applet
Shareableinterface - Monoid mobile app
- Key management
- Crypto transaction signing
- Third-party applet management
- Keys and data sync
- Applet gallery
- FIDO2
- One-time password (OTP)
- Tesla key
- Elliptic curves:
-
"secp256k1" -
"ed25519"
-
- Ciphers:
-
"ecdsa"
-
Monoid Applet uses a minimal CBOR-based RPC protocol.
The authentication is done within a session, using 0x21 command.
- Access PIN: for commands that would not result in keys/data revealed.
- Safe PIN: for commands that could result in keys/data revealed, or commands that requires access PIN.
type Request = {};type Response = {
version: number;
/** A 4-byte random id generated on _Monoid Applet_ installation. */
id: byte[];
features: {
curves: string[];
ciphers: string[];
};
/** Tries remaining for the PIN, or `false` if the PIN is not set. */
pin: number | false;
safe: {
/** A 4-byte random id generated on _Monoid Safe Applet_ installation. */
id: byte[];
/** Tries remaining for the PIN, or `false` if the PIN is not set. */
pin: number | false;
/** Whether the safe is unlocked (i.e., Monoid Applet stores a validated safe PIN). */
unlocked: boolean;
};
};type Request = {
pin: string;
safe?: true;
};type Response = {};Requires authentication with safe PIN if it is set.
type Request = {
pin: string;
safe?: true;
};type Response = {};In case of Monoid Applet reinstallation resulting in its losing the safe PIN, setting safe PIN again (to either the same or a different PIN) will restore the safe PIN stored in Monoid Applet (thus "unlocking" the safe).
type Request = {};type Response = {
versions: {
monoid: number;
javacard: [number, number];
};
memories: {
persistent: {
available: number;
};
transient: {
reset: {
available: number;
};
deselect: {
available: number;
};
};
};
features: {
curves: string[];
ciphers: string[];
};
};type SafeItemKeyType = 'entropy' | 'seed' | 'master' | 'secp256k1';
type SafeItemType = SafeItemKeyType | byte;Requires authentication.
type Request = {
type?: SafeItemType;
};type Response = {
items: {
index: byte[];
type: SafeItemType;
alias?: string;
}[];
};Requires authentication.
type Request = {
index: byte[];
};type Response = {
alias?: string;
};Requires authentication with safe PIN.
type Request = {
index: byte[];
};type Response = {
alias?: string;
data: byte[];
};Requires authentication.
type Request = {
index: byte[];
alias?: byte[];
data?: byte[];
};type Response = {};Requires authentication.
type Request = {
(
| {
type: SafeItemType;
}
| {
index: byte[];
}
) & {
alias?: byte[];
data: byte[];
};type Response = {
index: byte[];
};Requires authentication.
type Request = {
index: byte[];
};type Response = {};Requires authentication.
type Request = {
type: SafeItemKeyType;
};type Response = {
index: byte[];
};type Key = {
index: byte[];
} & (
| {
// seed
seed: string;
curve: string;
path: byte[];
}
| {
// master
curve: string;
path: byte[];
}
| {
// key
}
);Requires authentication.
type Request = Key;type Response = {
publicKey: byte[];
} & (
| {
// seed / master
chainCode: byte[];
}
| {
// key
}
);Requires authentication.
type Request = Key & {
cipher: string;
digest: byte[];
};type Response = {
signature: byte[];
};A safe item index is a 2 + 8 bytes array.
The first 2 bytes are the type of the item (0x0000 to 0x0FFF and 0xFFFF are reserved), and the remaining 8 bytes are unique per item, typically 8-byte digest of the data if immutable:
0x00Monoid applet data (draft, not implemented yet)0x0001Third-party applet permission data- +8 bytes digest of third-party AID
0x01"entropy"Entropy- +1 byte entropy length
- +8 bytes digest of entropy
0x02"seed"Seed key- +1 byte seed length
- +8 bytes digest of seed
0x03"master"Master key- +1 byte master key length (note data length is master key length * 2)
- +8 bytes digest of master key
0x04EC key0x0401"secp256k1"SECP256K1 key- +8 bytes digest of key
0x0FFFGeneric data (draft, not implemented yet)- +8 bytes unique identifier
Monoid JavaCard is a javacard-gradle-template fork, which uses projects like javacard-gradle-plugin and ant-javacard under the hood.
As this is my first JavaCard project, it would not be possible without great open-source works like these. 🎉
Especially, during prototyping, I was extensively using keycard as a manual for verified technical details, which saved me a lot of time and effort for trial-and-error. Keycard is also the reason that I know JavaCard is the ancient technology behind many card-based hardware crypto wallets. 🫡
MIT License.
