Cryptographic commands
VERIFY PIN
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
var |
MAC | Encrypted PIN (or empty) |
Preconditions: Secure Channel must be opened.
The PIN verification is done in a standard way through the secure channel. The PIN chosen during the initialization needs to be provided after each EC signature, because a signature resets the PIN validity.
If the PIN entry is correct, the card returns 0x9000 and the PIN is marked as authenticated
for the entire session (until the application is deselected, the card is reset/teared, or a
signature is performed).
When there is no data in the command, the card returns 1 byte indicating the number of PIN retries left. This allows the client to check the PIN status without attempting a verification.
The PIN must be between 4 and 9 figures long.
Request Data — Verify mode (plaintext)
Field |
Size |
Description |
|---|---|---|
PIN |
4-9B |
Digit characters |
Request Data — Query mode: Send with empty data.
Response Data (query mode) — 1 byte
Field |
Size |
Description |
|---|---|---|
Retries remaining |
1B |
Number of PIN attempts left |
Retry behavior: The PIN can be tested 3 times before a disconnection of the card is
required. Then it can be tested 3 more times before the PIN state is locked, requiring
unlocking with PUK. When the number of tries reaches 3 (per session), the card needs a power
cycle before accepting new PIN verification, and it returns 0x63C0. After a power cycle,
if the card still throws 0x63C0, that means the PIN is blocked and the UNBLOCK PIN
command must be used.
Status Words
SW |
Description |
|---|---|
|
PIN correct (verified for session) |
|
Wrong PIN. x = remaining attempts |
|
Session tries exhausted (power cycle required) or PIN blocked |
|
Incorrect data length |
|
Card not initialized or PIN auth disabled |
CHANGE PIN
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Selector |
|
var |
MAC | Encrypted data |
Preconditions: Secure Channel must be opened. To change the PIN: user PIN must be verified (else PUK must be provided in data).
Change the PIN or PUK. In case of invalid format, the code 0x6A80 is returned. If the
conditions match, the PIN or PUK is updated and 0x9000 is returned.
The new PIN must be 9 chars long, must start with at least 4 figure characters ('0'-'9').
If the PIN is less than 9 numbers, it must be filled with 0x00 until 9 chars long. The
PUK must be 12 bytes long.
P1=0x00 — Change PIN (PIN already verified):
Field |
Size |
Description |
|---|---|---|
New PIN |
9B |
4-9 digits, right-padded with |
The current PUK added for auth in data is mandatory if PinAuth is disabled, as the PIN can’t be verified. The new PIN is verified for the session (until card power cycle or EC signature), except if PinAuth is disabled.
P1=0x00 — Change PIN (PIN not verified, use PUK instead):
Field |
Size |
Description |
|---|---|---|
New PIN |
9B |
4-9 digits, right-padded with |
PUK |
12B |
Current PUK for authorization |
P1=0x01 — Change PUK:
Field |
Size |
Description |
|---|---|---|
New PUK |
12B |
New PUK code |
Current PUK |
12B |
Current PUK for authorization |
Total plaintext: 24 bytes.
P1=other — Returns remaining PIN tries with 0x63Cx. Resets any user auth.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Invalid PIN/PUK format |
|
PIN not validated or card not initialized. Also returned when bad PUK provided to change PIN (this is considered as PIN not validated). |
|
PUK given is invalid (when P1=0x01) |
|
Remaining PIN tries (when P1=other) |
UNBLOCK PIN
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
var |
MAC | Encrypted data |
Preconditions: Secure Channel opened, PIN must be blocked, PinAuth enabled.
Unblock the user PIN. The data field must contain exactly 21 chars, otherwise SW 0x6A80
is returned. The first 12 bytes are the PUK and the last 9 digits are the new PIN. If the
PUK is correct the PIN is changed to the given one, PIN is unblocked and authenticated for
the rest of the session. The status code 0x9000 is returned.
The PIN must be 9 chars long, must start with 4 figure characters ('0'-'9'). If the
PIN is less than 9 numbers, it must be filled with 0x00 until 9 chars long.
Request Data (plaintext) — 21 bytes
Field |
Size |
Description |
|---|---|---|
PUK |
12B |
PUK code |
New PIN |
9B |
4-9 digits, right-padded with |
If the PUK is wrong, the SW 0x63Cx is sent back; x is the remaining tries left before
a power cycle is required. PUK tries are unlimited, but only 12 tries can be performed on each
power cycle. The card needs to be powered off (and then on) before sending new PUK. Else it
throws 0x63C0 without checking the PUK.
Status Words
SW |
Description |
|---|---|
|
Success (PIN unblocked and authenticated) |
|
Format invalid (not 21 bytes or non-digit PIN) |
|
PIN is not blocked or PinAuth disabled |
|
Wrong PUK. x = remaining tries before power cycle |
LOAD KEY
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Key type |
|
var |
MAC | Encrypted data |
Preconditions: Secure Channel opened, PIN or challenge-response validated.
Load a seed or a key pair. Can also be used for dual generation of the seed (shared with 2 cards paired beforehand). Once a seed is loaded in the card, this card now behaves like the one it was backup from. Be aware that key derivation paths are not backed up and must be identical to retrieve the same key pairs.
The PIN must be at the end of the data, unless a user key auth was performed. PIN when
provided is always right-filled with 0x00 up to 9 bytes. Load key checks the last 9 bytes
as the PIN in case no user auth by key is valid.
P1 values
P1 |
Description |
|---|---|
|
ECC SECP256k1 keypair (k1 only). Key source set to |
|
ECC SECP256k1 extended keypair with chain code (k1 only). Key source set to |
|
Binary seed (BIP39, 16-64 bytes). Master key nodes computed following SLIP10,
compatible with BIP32. One master key for k1, one for r1. Key source set to |
|
Dual Gen part 1: initializes the dual generation seed mode and sends back a public key signed with the Basic Group key. Nothing is done about any key or seed loading at this stage. |
|
Dual Gen part 2: receives the public key from the other card (plus signature). After
this, both cards have a common seed. Key source set to |
P1=0x01 — EC keypair (plaintext)
Field |
Size |
Description |
|---|---|---|
Tag |
1B |
Keypair template tag |
Template length |
1-2B |
BER-TLV length |
Tag |
1B |
Public key tag (optional, can be omitted) |
Public key length |
1-2B |
BER-TLV length |
Public key |
65B |
EC uncompressed point (optional) |
Tag |
1B |
Private key tag |
Private key length |
1-2B |
BER-TLV length |
Private key |
32B |
EC private key scalar |
PIN |
9B |
Right-padded with |
P1=0x02 — Extended keypair: Same as P1=0x01 with additional Tag 0x82 = chain code (32B).
P1=0x03 — Binary seed (plaintext)
Field |
Size |
Description |
|---|---|---|
Seed |
16-64B |
BIP39 binary seed |
PIN |
9B |
Right-padded with |
Dual generation protocol (P1=0x04 and P1=0x05):
card 1 : 80D00400 -> PublicKeySigned of the 1st card
card 2 : 80D00400 -> PublicKeySigned of the 2nd card
card 1 : 80D00500 PK2 -> Send 2nd card's signed key to 1st card
card 2 : 80D00500 PK1 -> Send 1st card's signed key to 2nd card
After this, the 2 cards have a common seed SHA256(ECDH). One card is a backup of the
other, even if no secrets were leaked externally. Even the computer doing the transfer
exchange can’t compute the seed (protected by ECDH). The computer can’t be part of the key
exchange, as this requires a signature with a secret key only located within the cards.
The common seed can be checked by making a derivation path and reading the blockchain public keys of the cards, which should match.
Status Words
SW |
Description |
|---|---|
|
Success. PIN is validated for the session. |
|
Wrong PIN |
|
Invalid format, invalid dual signature, or PIN expected (data len < 9B) |
|
Invalid P1 |
|
Key/seed already loaded (requires reset) |
Note
This command can only be performed once. There’s no way to erase the seed except with a
full reset using the PUK. After successful loading with PIN, the PIN is validated for the
session. The PIN-less path is reset. Unless a DERIVE KEY is sent, a subsequent SIGN
command will use the master keypair for signature.
DERIVE KEY
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Options |
|
var |
MAC | Encrypted path |
Preconditions: Secure Channel opened, PIN or challenge-response validated, seed loaded.
This command is used before a signing session to generate a private key according to the
BIP32 and SLIP10 specifications. The generated key is used for all subsequent SIGN
sessions. The maximum depth of derivation from the master key is 8. Any attempt to get
deeper results in 0x6A80 being returned.
The BIP32 specifications define a few checks which must be performed on the derived keys. If these fail (mostly using r1 curve), there is a looping mechanism described by the SLIP10 proposal, enforced by the card.
The ability to start derivation from the parent keys allows to more efficiently switch between
children of the same key. Note however that only the immediate parent of the current key is
cached so you cannot use this to go back in the hierarchy. If no valid parent key is available
(e.g. when the current key is the master root), the status code 0x6B00 is returned.
P1 bit fields
Bits |
Description |
|---|---|
bit 4 |
|
bit 5 |
|
bits 7-6 |
|
Request Data (plaintext)
Field |
Size |
Description |
|---|---|---|
Path elements |
n × 4B |
Sequence of 32-bit big-endian integers (0 to 8 levels). Empty = set master key as current. |
Each path element is 4 bytes. For hardened derivation, set bit 31 (OR with 0x80000000).
A client can perform a GET PUBKEY command to get the actual current key path and resume
derivation using a different path.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Invalid format or depth > 8 |
|
Derivation from parent but no valid parent cached (e.g. master is current) |
|
No seed loaded or PIN not verified |
GENERATE TRNG RANDOM
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Size |
|
— |
None |
Preconditions: Secure Channel must be opened.
Used to generate some random data from the chip internal TRNG (AIS 20 class DRG.3). The given size with P1 shall be between 16 and 64 bytes and must be a multiple of 4 (16, 20, 24, …, 64).
This command is only an output, to use the chip secure random number generator. For example to generate a mnemonic in the host machine from a confident random source. It doesn’t change any state in the card.
Response Data
Field |
Size |
Description |
|---|---|---|
Random data |
P1 bytes |
Raw TRNG output from the chip |
Status Words
SW |
Description |
|---|---|
|
Success |
|
Invalid P1 (not 16-64 or not multiple of 4) |
GENERATE KEY
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
var |
MAC | Encrypted PIN |
Preconditions: Secure Channel opened.
Generates a new seed and stores keys completely on card. The state of the card after execution
is the same as if a LOAD KEY command had been performed. With the use of this command, the
seed secret never escapes the card protection. If performed the simple way, there’s no way to
get a backup of this seed.
The key source is set to 'S'.
Request Data (plaintext)
Field |
Size |
Description |
|---|---|---|
PIN |
9B |
Right-padded with |
Status Words
SW |
Description |
|---|---|
|
Success. PIN is validated for the session. |
|
Wrong PIN |
|
Seed/key already loaded (requires reset) |
|
PIN expected or bad data |
Note
This command can be performed only once after a card reset, as there’s no way to erase nor change the current wallet seed key, except with a reset with the PUK. After successful generation with PIN, the PIN is validated for the session.
SIGN
Request APDU (encrypted, except pinless mode)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Key/derive |
Sig type |
var |
MAC | Encrypted data (or plaintext for pinless) |
Preconditions: Secure Channel opened (except pinless), PIN or user auth or pinless, key loaded.
The card applet can sign any 256-bit hash provided, using ECDSA with 256k1 or 256r1 EC parameters. It also supports EdDSA (Ed25519) signatures on raw data, and BIP340 Schnorr signatures. Most blockchain systems use SHA2-256 to hash the message, but this card applet is agnostic from this for ECDSA, since the signature is performed on a hash provided by the user. For EdDSA, the card signs raw data directly (the EdDSA algorithm performs its own internal hashing per RFC 8032).
The ephemeral k used in the ECDSA and Schnorr is random and different for each signature.
For ECDSA, this is automatically performed by the Signature function of the underlying JCOP4
platform. This applet does not use RFC6979 deterministic digital signature generation.
The derivation of the key pair node can also be done using the signature command (relative or absolute). The card derives just before signing. This can’t be used to sign with a different key, and this cannot change the current stored key.
Additionally, to be compatible with blockchain specific signatures, the S part of the signature is always output as “canonical”, changed for S to be on the “lower” side (S lower than n/2).
P1 values
P1 |
Description |
|---|---|
|
Current key (k1) |
|
Current key (r1) |
|
Current key (Ed25519) |
|
Derive + sign with k1 (OR with bits 7-6 for source) |
|
Derive + sign with r1 (OR with bits 7-6 for source) |
|
Derive + sign with Ed25519 (OR with bits 7-6 for source) |
|
Pinless path (k1 only, no SC required) |
P1=0x03 is specifically thought for payment transactions. It can be executed without
Secure Channel (since no sensitive info is transmitted) and does not require PIN
authentication. The current derivation path on the card remains unchanged, but the signing
process is performed using the PIN-less derivation path previously defined using the
SET PINLESS PATH command.
P2 values
P2 |
Description |
|---|---|
|
ECDSA with canonical low S |
|
ECDSA with EOSIO canonical filter |
|
Bitcoin Schnorr BIP340 (k1 only) |
|
EdDSA (Ed25519, RFC 8032) |
Request Data — P1=0x00/0x10 (current key, plaintext)
Field |
Size |
Description |
|---|---|---|
Hash |
32B |
256-bit hash to sign |
PIN |
9B |
Right-padded with |
Request Data — P1=0x01/0x11 (derive + sign, plaintext)
Field |
Size |
Description |
|---|---|---|
Hash |
32B |
256-bit hash to sign |
Path elements |
n × 4B |
Derivation path (32-bit big-endian integers) |
PIN |
9B |
Right-padded with |
Request Data — P1=0x20 (current Ed key, plaintext)
Field |
Size |
Description |
|---|---|---|
Data length |
2B |
Big-endian length of the raw data to sign |
Data |
1-1200B |
Raw data to sign (max 1200 bytes, not a hash) |
PIN |
9B |
Right-padded with |
Request Data — P1=0x21 (derive + sign Ed, plaintext)
Field |
Size |
Description |
|---|---|---|
Data length |
2B |
Big-endian length of the raw data to sign |
Data |
1-1200B |
Raw data to sign (max 1200 bytes, not a hash) |
Path elements |
n × 4B |
Derivation path (32-bit big-endian integers) |
PIN |
9B |
Right-padded with |
Request Data — P1=0x03 (pinless, plaintext, no SC)
Field |
Size |
Description |
|---|---|---|
Hash |
32B |
256-bit hash to sign |
Response Data — ECDSA (P2=0x00 or 0x01)
The signature is encoded as an X9.62 ASN1 DER sequence: SEQUENCE { r INTEGER, s INTEGER }.
For usage on some blockchains, one needs to calculate the recovery ID in addition to the
signature. To calculate the recovery ID, apply the same algorithm used for public key recovery
from a transaction starting with a recovery ID of 0. If the public key matches, you have found
the recovery ID, otherwise increment and try again.
Field |
Size |
Description |
|---|---|---|
Signature |
70-72B |
ASN.1 DER X9.62 format |
Response Data — BIP340 Schnorr (P2=0x02)
Returns 64 bytes R|S = 2x256 bits MSB first, as per BIP340 standard. The 32-byte nonce
is provided by a random source in the JCOP4 platform. Works only with k1 keys.
Field |
Size |
Description |
|---|---|---|
R |
32B |
Schnorr R component (MSB first) |
S |
32B |
Schnorr S component (MSB first) |
Response Data — EdDSA (P2=0x03)
Returns 64 bytes R|S as per RFC 8032 (Ed25519). The signature is computed over the raw
data provided in the request (not a hash). The EdDSA algorithm performs its own internal
SHA-512 hashing as specified by the standard.
Field |
Size |
Description |
|---|---|---|
R |
32B |
EdDSA R component per RFC 8032 |
S |
32B |
EdDSA S component per RFC 8032 |
PIN behavior: The PIN must be with a 9 numbers fixed length, right-filled with 0x00.
If there was no valid user auth or not in pinless mode, the data length expected is counted
with the PIN included. A PIN try is always counted when calling this command with a path data
longer than a 9 bytes PIN.
An EC signature resets the PIN or user key auth. That means that a PIN verification (or user sign auth) must be performed afterwards and before calling any commands which require a user auth. If several hashes are granted by user-auth-for-sign, the card expects the signatures to be done in the exact same order as the authentication. The reset of the auth occurs when all signatures are done (up to 4).
After a successful signature session, the user auth is left opened: if a PIN was provided, the PIN stays verified for other commands. If a user key auth was used, the EC auth is opened for other commands. In case of bad PIN provided, the PIN auth is disengaged.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Data too short or path not multiple of 4 |
|
Pinless path not defined (P1=0x03) |
|
No key loaded |
|
Wrong PIN |
|
Data length mismatch with expected PIN |
|
Unknown P1 or P2, or P2=2 with P1 not k1 |
DECRYPT
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Mode |
|
var |
MAC | Encrypted data |
Preconditions: Secure Channel opened, PIN or user auth, seed loaded.
Generates a symmetric secret for simplified ECIES using an EC key in the BIP32 tree. This command is inspired by DECipher in OpenPGP smartcards. This allows an asymmetric encryption using a key in the card seed tree. Anyone can encrypt with a public key from the card, and only the (private) key in the card can decrypt.
During the seed loading, the card saves in a dedicated key slot the result of a fixed derivation path. The child EC key used for this command is fixed (relative to a given seed). The path is computed with SLIP17 for the URI “openpgp://cryptnox” with index=0:
m/17'/910196630'/2006168372'/332516148'/580566270'
The symmetric key is computed as SHA256(k . PubKey), where k is the private ECP256r1
key in the “decrypt” key slot.
P1 values
P1 |
Description |
|---|---|
|
Output symmetric key |
|
Decrypt data in card using the derived AES key |
P1=0x00 — Output symmetric key (plaintext)
Field |
Size |
Description |
|---|---|---|
PIN |
9B |
Right-padded with |
PubKey |
65B |
Third-party EC R1 uncompressed point ( |
Total: 74B (with PIN) or 65B (user auth).
Response Data (P1=0x00) — 32 bytes
Field |
Size |
Description |
|---|---|---|
Symmetric key |
32B |
|
P1=0x01 — Decrypt in card (plaintext)
When P1 is not 0, the hashed ECDH is used internally as an AES128 key: IV | Key, with
CBC. The padding is left to the outer system, so data must be padded to 16 bytes alignment
(and un-padded by the caller).
Field |
Size |
Description |
|---|---|---|
PIN |
9B |
Right-padded with |
PubKey |
65B |
Third-party EC R1 uncompressed point |
Encrypted data |
n × 16B |
AES-CBC encrypted data (must be 16-byte aligned) |
Response Data (P1=0x01)
Field |
Size |
Description |
|---|---|---|
Decrypted data |
var |
Plaintext output |
Example usage: Let’s call the user Alice and her friend Bob. Alice reads the “encrypt”
public key by calling GET PUBKEY with derive-from-master and derive-r1 for the SLIP17
path, getting PKA. Alice shares this public key with Bob. Bob generates an ephemeral
ECr1 key pair kb/PKB, computes key = SHA256(kb . PKA) to get IV | AESkey, encrypts
the message with AES-CBC, and shares the ciphertext with Alice along with PKB. Alice calls
DECRYPT with PIN | PKB | ciphertext and the card gives out the decrypted data.
The PIN/noPIN case in the data is inferred from the current user key auth status. If a user auth was performed with a user signature (CheckUserKey), this command expects no PIN. If no user auth was validated, this command expects a PIN prepending the data.
The PIN state is changed by this command: if wrong PIN, the auth is reset. If correct, the PIN is validated.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Incorrect data length |
|
No key/seed loaded |
|
Wrong PIN |
|
Data too large (outside secure channel) |
GENERATE RSA SEED WRAP KEY
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
var |
MAC | Encrypted data |
Preconditions: Secure Channel must be opened.
This command generates an RSA key pair on the card that can be used to wrap (encrypt) the card’s seed for secure export. The RSA key size is specified in the request data.
Request Data (plaintext)
Field |
Size |
Description |
|---|---|---|
RSA key size |
2B |
Big-endian RSA key size in bits (e.g. |
Status Words
SW |
Description |
|---|---|
|
Success |
|
Invalid key size |
SIGN PUBLIC
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
Key type |
|
— |
None (or MAC only) |
Preconditions: Secure Channel opened, PIN or challenge-response validated, seed loaded.
This command certifies (signs) the current blockchain public key using the card’s static private key. This allows external parties to verify that a given blockchain public key genuinely belongs to this card, by checking the signature against the card’s certificate.
P1 values
P1 |
Description |
|---|---|
|
Current key (k1) |
|
Current key (r1) |
|
Current key (Ed25519) |
Response Data
Field |
Size |
Description |
|---|---|---|
Signature |
70-72B |
ASN.1 DER ECDSA signature over the current blockchain public key, signed with the card’s static private key |
Status Words
SW |
Description |
|---|---|
|
Success |
|
No key loaded or PIN not verified |
|
Invalid P1 |
RESET
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
var |
MAC | Encrypted PUK |
Preconditions: Secure Channel must be opened.
When the PUK code is sent into the RESET command, the card is reset. The reset PUK code is chosen during the initialization stage. The PUK reset code is a 12 characters long string, which allows a full reset of the card. On top of this 12 chars secret, the reset is also secured by the user pairing key mechanism (if secret). That means that resetting a card requires, on top of the PUK, to know the PairingKey to send the command encrypted. Still, the PUK can act as a PairingKey on its own.
The reset command erases everything in the card, and makes the card as it was before the initialization. All data in the card will be reset, with no way to recover (except from seed/mnemonic recovery).
Request Data (plaintext) — 12 bytes
Field |
Size |
Description |
|---|---|---|
PUK |
12B |
PUK code |
Note
The PUK tries are unlimited, but there’s an internal loop hash that takes time inside the card for each try. Every 12 tries, a power cycle is required to perform new tries.
In the case of a power loss during reset, the card can only have its blockchain keys deleted. The card would have the flag “seeded” false and “initialized” true. This would need another reset for a full reset, or the keys need to be generated/loaded again.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Wrong PUK (x = remaining tries before power cycle) |
|
Power cycle required before new PUK attempts |
|
Data length is not 12 bytes |