README.md
September 23, 2025 ยท View on GitHub
Monoid JavaCard
0xF06D6F6E6F6964
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.
Requirements
JavaCard 3.0.5 with ECC support is required.
See cards for links to vendors.
Build & Install
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-11
For other editors or terminals, please ensure related configurations accordingly.
Now we can build the project with Gradle:
./gradlew buildJavaCard
Then install applets to a card with gp command:
gp --install build/javacard/monoidsafe.cap
gp --install build/javacard/monoid.cap
The 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.
Development
The development workflow requires Node.js for source code housekeeping and some test case validation.
npm install
Run tests with simulator:
./gradlew test --info --rerun-tasks
Run 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.cap
CARD_TYPE=physical ./gradlew test --info --rerun-tasks
Commands to run:
# sync some constants like AIDs (and more) across files.
npx inplate --update
# format code with prettier. YES!!!
npx prettier --write .
Architecture
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.
Roadmap
Core features
- 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
Additional mobile app features
- Keys and data sync
- Applet gallery
Applets for typical use cases
- FIDO2
- One-time password (OTP)
- Tesla key
Cryptography
- Elliptic curves:
-
"secp256k1" -
"ed25519"
-
- Ciphers:
-
"ecdsa"
-
ISO-7816 Commands
Monoid Applet uses a minimal CBOR-based RPC protocol.
Command authentication
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.
0x20 ~ 0x2F System management
0x20 Hello
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;
};
};
0x21 Authenticate
type Request = {
pin: string;
safe?: true;
};
type Response = {};
0x22 Set PIN
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).
0x2F System information
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[];
};
};
0x30 ~ 0x3F Safe management
type SafeItemKeyType = 'entropy' | 'seed' | 'master' | 'secp256k1';
type SafeItemType = SafeItemKeyType | byte;
0x30 List items
Requires authentication.
type Request = {
type?: SafeItemType;
};
type Response = {
items: {
index: byte[];
type: SafeItemType;
alias?: string;
}[];
};
0x31 View
Requires authentication.
type Request = {
index: byte[];
};
type Response = {
alias?: string;
};
0x32 Get item
Requires authentication with safe PIN.
type Request = {
index: byte[];
};
type Response = {
alias?: string;
data: byte[];
};
0x33 Set item
Requires authentication.
type Request = {
index: byte[];
alias?: byte[];
data?: byte[];
};
type Response = {};
0x34 Create item
Requires authentication.
type Request = {
(
| {
type: SafeItemType;
}
| {
index: byte[];
}
) & {
alias?: byte[];
data: byte[];
};
type Response = {
index: byte[];
};
0x35 Remove item
Requires authentication.
type Request = {
index: byte[];
};
type Response = {};
0x38 Create random key
Requires authentication.
type Request = {
type: SafeItemKeyType;
};
type Response = {
index: byte[];
};
0x40 ~ 0x4F Key usage
type Key = {
index: byte[];
} & (
| {
// seed
seed: string;
curve: string;
path: byte[];
}
| {
// master
curve: string;
path: byte[];
}
| {
// key
}
);
0x40 View key
Requires authentication.
type Request = Key;
type Response = {
publicKey: byte[];
} & (
| {
// seed / master
chainCode: byte[];
}
| {
// key
}
);
0x41 Sign
Requires authentication.
type Request = Key & {
cipher: string;
digest: byte[];
};
type Response = {
signature: byte[];
};
Index
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
Credits
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. ๐ซก
License
MIT License.