Skip to content

vilicvane/monoid-javacard

Repository files navigation

Monoid JavaCard

0xF06D6F6E6F6964

Monoid

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 installJavaCard will try to append multiple --install options to gp command, 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:

  1. Monoid Applet itself can access the safe freely (commands probably have their own authentication requirements though).
  2. 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 Shareable interface
  • 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:

  • 0x00 Monoid applet data (draft, not implemented yet)
    • 0x0001 Third-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
  • 0x04 EC key
    • 0x0401 "secp256k1" SECP256K1 key
      • +8 bytes digest of key
  • 0x0FFF Generic 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.

About

Not only an open-source hardware wallet.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors