MuSig2 commands (BIP-327)
The card implements the on-card half of the BIP-327 MuSig2 multi-signature protocol. MuSig2 produces a single Schnorr (BIP-340) signature that is indistinguishable from a non-MuSig signature on-chain, while requiring two communication rounds between cosigners. The card acts as one of the cosigners holding its share of the aggregate private key; key aggregation, nonce aggregation and the host-side Taproot tweak are performed off-card.
The protocol uses three commands run in sequence:
NONCE GEN (INS 0xC7) — the card generates its own pair of secret nonces
(k1, k2)and returns the matching public pair(R1, R2).LOAD COSIGNER KEY (INS 0xC8 P1=01) — the host sends each cosigner public key (including the card’s own) so the card can build the KeyAgg list hash
Lincrementally.PARTIAL SIGN (INS 0xC8 P1=02) — the host sends the aggregate nonce
(aggR1, aggR2), the aggregate public keyQ, and the message hash. The card returns its 32-byte partial signature.
Note
The card is curve-fixed on secp256k1 for MuSig2. Only the wallet’s k1 key (or the pinless k1 key if applicable) is usable as the signing key. The card never aggregates the partial signatures itself — aggregation is the host’s responsibility.
Protocol state machine
The card enforces the protocol order with an internal state machine. Calls outside the
expected sequence are rejected with 0x6985.
State |
Set by |
Accepts |
|---|---|---|
IDLE |
Card reset, after PARTIAL SIGN, after rejected PARTIAL SIGN |
NONCE GEN |
NONCE_GENERATED |
Successful NONCE GEN |
LOAD COSIGNER KEY, NONCE GEN (restarts) |
WAIT_COSIGNERS |
First successful LOAD COSIGNER KEY |
LOAD COSIGNER KEY (more), PARTIAL SIGN |
After a card disconnect between LOAD COSIGNER KEY and PARTIAL SIGN, the card auto-demotes
back to NONCE_GENERATED so the host can resend the cosigner list. The active nonce pair
(k1, k2) and the binding to the card’s signing key remain available across a disconnect,
so NONCE GEN does not need to be re-issued.
NONCE GEN
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
|
MAC |
Preconditions: Secure Channel opened, seed loaded, PIN or user-key authentication performed (or pinless path active).
Generates the card’s MuSig2 secret nonce pair (k1, k2) and returns the corresponding
public points (R1, R2). The returned nonces are bound to the card’s signing key
pk = sk*G: LOAD COSIGNER KEY uses this binding to detect pk in the cosigner
list, and PARTIAL SIGN verifies the signing key has not changed in between (BIP-327
requires pk == secnonce[64:97]).
The secret nonces are derived per BIP-327 NonceGen. Two successive NONCE GEN calls
always produce different nonce pairs.
Response Data — 66 bytes
Field |
Size |
Description |
|---|---|---|
R1 |
33B |
Compressed EC point |
R2 |
33B |
Compressed EC point |
Note
An active nonce pair (k1, k2) remains available across a card disconnect. It is
destroyed after PARTIAL SIGN (successful or rejected), after a full reset, and at
the start of any new NONCE GEN call. The host is expected to use each nonce pair
exactly once; reusing nonces across two different messages leaks the private key by
linear algebra. The card enforces this by destroying the nonces after each sign.
Status Words
SW |
Description |
|---|---|
|
Success |
|
PIN or user-key auth not performed, or seed not loaded |
LOAD COSIGNER KEY
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
|
MAC | Encrypted data |
Preconditions: Secure Channel opened, PIN or user-key authentication performed (or pinless path active), state is NONCE_GENERATED or WAIT_COSIGNERS.
Feeds one cosigner public key (33-byte compressed) into the incremental KeyAgg list hash
L = sha256("KeyAgg list" || pk1 || pk2 || ... || pkN). Must be called once per cosigner,
including the card’s own key. The order must match the order the host uses to compute the
aggregate public key Q.
A maximum of 32 cosigners can be loaded in a single session; the 33rd call is rejected with
0x6985.
While processing each key, the card compares it against its own signing key pk. If a
match is found, PARTIAL SIGN will be allowed to proceed, mirroring the BIP-327 Sign
algorithm’s check pk in pubkeys.
Request Data — 33 bytes
Field |
Size |
Description |
|---|---|---|
Cosigner pubkey |
33B |
Compressed EC point ( |
Status Words
SW |
Description |
|---|---|
|
Success |
|
Data length is not 33 bytes |
|
State is not NONCE_GENERATED or WAIT_COSIGNERS, PIN / user-auth not performed, or cosigner-count limit (32) reached |
PARTIAL SIGN
Request APDU (encrypted)
CLA |
INS |
P1 |
P2 |
LC |
Data |
|---|---|---|---|---|---|
|
|
|
|
|
MAC | Encrypted data |
Preconditions: Secure Channel opened, state is WAIT_COSIGNERS, PIN or user-key auth
performed (or pinless path active), card’s own key seen via LOAD COSIGNER KEY.
Computes and returns the card’s partial signature s_i per BIP-327 Sign:
b = tagged_hash("MuSig/noncecoef", aggR1 || aggR2 || Q.x || msg) mod n
R = aggR1 + b * aggR2
e = tagged_hash("BIP0340/challenge", R.x || Q.x || msg) mod n
a_i = tagged_hash("KeyAgg coefficient", L || pk_i) mod n
s_i = (k1 + b * k2 + e * a_i * sk) mod n
The card never returns R, e, a_i or any other intermediate; only s_i. After
the call, the active nonce pair (k1, k2) is destroyed and the state machine returns to
IDLE regardless of the outcome; a new NONCE GEN is required for the next signature.
Before returning s_i, the card verifies the BIP-327 PartialSigVerifyInternal equation
s_i * G == Re* + e * a_i * g' * P on its own partial signature. If the equation does not
hold, the call is rejected with 0x6985 and the signing session is terminated; the host
must restart from a new NONCE GEN.
Request Data — 195 bytes
Field |
Size |
Description |
|---|---|---|
aggR1 |
65B |
Uncompressed EC point ( |
aggR2 |
65B |
Uncompressed EC point ( |
aggPubKey Q |
33B |
Compressed aggregate public key (host-computed, must already be tweaked if Taproot) |
Message |
32B |
Message hash (typically a Taproot |
Response Data — 32 bytes
Field |
Size |
Description |
|---|---|---|
Partial signature s_i |
32B |
Scalar |
Note
The aggregate public key Q provided in the request must already include the host-side
Taproot tweak when applicable. The card does not apply BIP-341 tweaks. Sending an
un-tweaked Q for a Taproot output yields a signature that will not verify under the
on-chain key.
Important
PARTIAL SIGN is single-use per NONCE GEN call. The nonces are destroyed before
the function returns and the state goes back to IDLE, so a second PARTIAL SIGN
against the same aggR1, aggR2 is rejected. To sign another message, run a full
NONCE GEN → LOAD COSIGNER KEY (× N) → PARTIAL SIGN cycle.
Status Words
SW |
Description |
|---|---|
|
Success |
|
Data length is not 195 bytes |
|
State is not WAIT_COSIGNERS, card’s own key not in cosigner list, PIN/user-auth
not performed, or BIP-327 internal check failed ( |
|
Internal computation failure (operands out of range); the signing session is terminated on this path |
References
BIP-327 — MuSig2 for BIP340-compatible Multi-Signatures
BIP-340 — Schnorr Signatures for secp256k1
BIP-341 — Taproot: SegWit version 1 spending rules (host-side tweak only)