Signing is the main goal of the Keycard and indeed any hardware wallet. The Keycard signs with the key at the path you give it, and since applet 4.0 that is the only way to ask: the path travels with the command.
The Keycard is a BIP-32 compatible wallet, so it derives keys as defined by the BIP-32 specification to create a hierarchical deterministic wallet. The maximum depth of a key path is 10 components, excluding the master key.
On 4.0 there is no "current key" on the card at all, so there is nothing to derive ahead of time — pass the path to the command that needs it:
deriveKey() and getStatus(GET_STATUS_P1_KEY_PATH) exist for applet 3.x
cards, which keep a current path between commands. If you still support them,
call deriveKey before sign when the card reports a version below 0x0400;
on 4.0 the path in the signing call is what counts and the card has no path to
report. Prefer signWithPath/exportKey everywhere — they work on both.Since the Keycard has no user input/output capabilities, it would be useless to transfer an entire transaction to the card for signing. You should instead calculate the transaction hash, according to the rules of the cryptocurrency you are handling, and send that for signature. This also means you can handle anything which requires ECDSA signatures over the secp256k1 curve, regardless of the hashing algorithm used, as long as it outputs a 256-bit hash. This opens the door to signing transactions for the most common cryptocurrencies, but also makes it usable outside the realm of crypto transactions.
Signing is done by naming the key to sign with:
Signing requires user authentication. Since 4.0 that is unconditionally true — there is no pinless path.
cmdSet.sign(hash), with no path, sends the old "sign with the current key"
instruction. A 4.0 card rejects it with SW 0x6A86. Use signWithPath in code
that has to work with current cards — it works on 3.x too.Introduced in 4.0, P2 = 0x03 selects BIP-340 Schnorr, as used by Bitcoin Taproot. The 32-byte key tweak is part of the call; pass null for an untweaked key.
The tweak is added to the derived private key before signing, which is exactly what Taproot key-path spending requires. The public key returned alongside the signature is the tweaked one, so verify against that rather than the plain derived key.
This has always been the recommended form, and since 4.0 it is the only form:
Because the command carries everything it needs, signing does not depend on what the last command happened to leave on the card.