DevelopersKeycard API
Changes in 4.0
23 Sept 2026

Applet 4.0 is the largest protocol revision since 2.0. Cards cannot be upgraded from 3.x to 4.0 — the applet is locked on shipped cards — so both versions will be in the field indefinitely and clients should support both. This page is the migration checklist; each command page documents its own version differences.

The applet version is the 0x02 field of the SELECT response. Version 4.0 is 0x0400. Branch on it before doing anything else:

if (appVersion >= 0x0400) use SecureChannel v2 (certificate based)
else                      use SecureChannel v1 (pairing based)

Everything else follows from that choice.

3.x4.0
Channel establishmentPAIR → OPEN SECURE CHANNEL → MUTUALLY AUTHENTICATEOPEN SECURE CHANNEL only
Card authenticationpairing cryptogramsfactory certificate + transcript signature
Client state to persistpairing secret, per cardnothing
Credentials at INITPIN, PUK, pairing passwordPIN, PUK
Command transportonly the data field encrypted, per-command INSwhole APDU encrypted, wrapped in SECURED APDU
Card identity checkIDENT commandcertificate in the SELECT response
Current key pathset by DERIVE KEY, read with GET STATUSno such state; every command carries its path
Pinless signingSET PINLESS PATHremoved
SELECT responseInstance UID, channel public key, pairing slotsstatus byte, identity certificate
Unsigned commandsSELECT, OPEN SC, FACTORY RESET, GET DATA, pinless SIGNSELECT, OPEN SC, FACTORY RESET
PlatformJavaCard 3.0.4+JavaCard 3.0.5 + keycard-math package

Drop pairing entirely for 4.0 cards. There is no pairing password, no pairing index, no pairing slot to store or clean up.

Verify the certificate instead. Read the 98-byte certificate from the SELECT response, recover the CA public key from its r/s/v fields and check it against your anchors — see Card identity.

Always pass a path. There is no card state to remember, which removes an entire category of "signed with the wrong key" bugs. A path of zero length means the master key.

Expect everything to need a secure channel. Notably GET DATA used to work without one. Only SELECT, the handshake and factory reset are still exempt.

Re-key local storage if you used the Instance UID. It is gone. The Key UID identifies the wallet; the certificate public key identifies the card across reinstalls.

  • Schnorr signaturesSIGN with P2 = 0x03, BIP-340/BIP-341 compatible, with optional key tweaking. Enough for Bitcoin Taproot key-path spends.
  • ECDHECDH over NIP-44 and EIP-1581 subtrees, which is what Nostr encrypted DMs need.
  • BIP-85EXPORT BIP85 for deterministic entropy that other BIP-85 tools can reproduce from the same seed.
  • LEELOAD KEY P1 = 0x04 and EXPORT LEE.
  • GET CHALLENGEGET CHALLENGE for card-sourced randomness.
  • Segmented NDEF readsGET DATA with an offset.

  1. 1
    Branch on the applet version after SELECT.
  2. 2
    Add the certificate verification path and the CA anchors; keep IDENT for 3.x.
  3. 3
    Add SecureChannel v2 next to v1, keeping v1 for older cards.
  4. 4
    Make the derivation path mandatory in your signing calls — on 3.x the same call works, it only has to keep setting the current path too.
  5. 5
    Remove any pinless-signing code paths you can, rather than version-gating them.
Last edited
23 Sept 2026