BIP32 Mechanism Support and Implementation
This section describes the BIP32 functions, key attributes, error codes, and mechanisms supported for BIP32 with the HSM.
NOTE This feature requires minimum Luna HSM Firmware 7.3.0 and Luna HSM Client 7.3.0.
Curve Support
Only curve secp256k1 is supported. The BIP32 derivation mechanisms fail with CKR_TEMPLATE_INCONSISTENT if you attempt to specify a curve with CKA_ECDSA_PARAMS.
Key Type and Form
The key type CKK_BIP32 is used to distinguish keys that can be used for BIP32 from all the existing ECDSA keys. Existing ECDSA keys cannot be used with any of the BIP32 mechanisms because they lack a chain code. The serialization format when importing, exporting, wrapping and unwrapping keys is also different from ECDSA keys. All mechanisms supported by ECDSA keys are supported for BIP32 keys.
Extended Keys and Hardened Keys
BIP32 includes hardened and non-hardened (normal) child keys. Each has a 32-bit index. Child keys are considered hardened if the most significant bit of their index is set. This bit is defined as CKF_BIP32_HARDENED. This allows 2^31 hardened keys and 2^31 non-hardened keys per parent.
Hardened private keys create a firewall through which multi-level key derivation compromises cannot happen. For normal (non-hardened) keys one can derive child public keys of a given parent key without knowing any private key. So if an attacker gets a normal parent chain code and parent public key, he can brute-force all chain codes deriving from it. If the attacker also obtains a child, grandchild, or further-descended private key, he can use the chain code to generate all of the extended private keys descending from that private key. The formula for creating hardened keys makes it impossible to create child public keys without knowing the parent private key.
Key Derivation
Two new mechanisms are added to support all the key derivations in BIP32.
CKM_BIP32_MASTER_DERIVE
This mechanism derives the master key pair from a seed. The input key must have the type CKK_GENERIC_SECRET
(size between 128 and 512 bits). This mechanism is unique in that it derives two keys from one. This requires us to accept two templates as input, and to output the two derived key handles. In order to avoid confusion, the three last arguments of C_DeriveKey()(pTemplate
, ulAttributeCount
and phKey
) must be null or zero. CKR_ARGUMENTS_BAD
is returned if any of those parameters is non-NULL. The templates and returned handles are instead passed in through the mechanism parameters, which are clearly labeled public and private. Choose to not generate the public or private key by leaving those parameters as zero or null.
typedef struct CK_BIP32_MASTER_DERIVE_PARAMS { CK_ATTRIBUTE_PTR pPublicKeyTemplate; CK_ULONG ulPublicKeyAttributeCount; CK_ATTRIBUTE_PTR pPrivateKeyTemplate; CK_ULONG ulPrivateKeyAttributeCount; CK_OBJECT_HANDLE hPublicKey; // output parameter CK_OBJECT_HANDLE hPrivateKey; // output parameter } CK_BIP32_MASTER_DERIVE_PARAMS;
See Code Samples for a code example.
CKM_BIP32_CHILD_DERIVE
This mechanism derives child keys from a parent key. The mechanism can generate both the private and public part of the key pair, and can accept a BIP32 public or private key as input. An error is returned if a public to private derivation is attempted. Like the master key derivation, the templates and key handle outputs are passed through the mechanism parameters. Choose to not generate the public or private key by leaving those parameters as zero or null.
The BIP32 and BIP44 specifications recommend wallet structures and use cases. The specifications provide a good reference for deciding how a key tree should be organized and if a particular key should be hardened or not. Follow the specifications to avoid potential security holes.
This mechanism can be used to generate keys that are several levels deep in the key hierarchy. The path of the key is specified with pulPath
and ulPathLen
. The path is an array of 32-bit unsigned integers (key indices).
The highest bit (0x80000000) is used to indicate a hardened key. So a path value for a normal key must be <= 0x7FFFFFFF (decimal 2147483647). For a hardened key, 0x80000000 <= path value <= 0xFFFFFFFF. The path is relative to the input key. For example, if the path is [5, 1, 4] and the path of the input key is m/0 then the resulting path is m/0/5/1/4.
The max number of levels (path length) is 255 for BIP32 in the HSM firmware. This is expected to be generally adequate.
typedef struct CK_BIP32_CHILD_DERIVE_PARAMS { CK_ATTRIBUTE_PTR pPublicKeyTemplate; CK_ULONG ulPublicKeyAttributeCount; CK_ATTRIBUTE_PTR pPrivateKeyTemplate; CK_ULONG ulPrivateKeyAttributeCount; CK_ULONG_PTR pulPath; CK_ULONG ulPathLen; CK_OBJECT_HANDLE hPublicKey; // output parameter CK_OBJECT_HANDLE hPrivateKey; // output parameter CK_ULONG ulPathErrorIndex; // output parameter } CK_BIP32_CHILD_DERIVE_PARAMS;
See Code Samples for a code example.
Error Codes
These mechanisms can fail in ways not applicable to other mechanisms.
CKR_BIP32_CHILD_INDEX_INVALID: This error is returned on the rare occurrence (1 / 2^127) that a child derivation returns an all-zero private key, a private key bigger than or equal to the curve order parameter n
, or a point at infinity. This error signifies that the child key index cannot be used to derive keys. Choose a different index and try the derivation again. The problematic child index is indicated by ulPathErrorIndex
.
PCKS#11 does not have fixed width integers. This error can also be returned on platforms where CK_ULONG
is bigger than 32 bits and a child index is bigger than 2^32 – 1.
CKR_BIP32_INVALID_HARDENED_DERIVATION: This error is returned from an attempt to derive a hardened key from a public key. The BIP32 specification does not support such a derivation.
CKR_BIP32_MASTER_SEED_LEN_INVALID: The BIP32 specification recommends deriving the master key from a seed that is between 128 and 512 bits long. This error is returned if the seed length is outside of that range.
CKR_BIP32_MASTER_SEED_INVALID: This error is returned on the rare occurrence (1 / 2^127) that the master derivation returns an all zero private key, a private key bigger than or equal to the curve order parameter n
, or a point at infinity. This error signifies that the master seed cannot be used for BIP32. Generate a new master seed and retry the derivation.
CKR_BIP32_INVALID_KEY_PATH_LEN: This error is returned when ulPathLen
is 0 or greater than 255. The BIP44 standard only requires paths of length 5 so this limit should be acceptable for all customers.
Key Attributes
The following attributes will exist on all keys created with one of the above derivation mechanisms.
CKA_BIP32_CHAIN_CODE: The chain code is essential for BIP32 keys and is used to derive future keys. The public and private key share this value. Read only.
CKA_BIP32_VERSION_BYTES: Version bytes are used to further identify BIP32 keys. The version bytes help determine if a key is used on the main bitcoin network or the test network. This attribute defaults to CKG_BIP32_VERSION_MAINNET_PUB/PRIV
if it was not specified at key creation time. You can set this value to CKG_BIP32_VERSION_TESTNET_PUB/PRIV
if applicable.
CKA_BIP32_CHILD_INDEX: The child index stores which index was used to derive this key. An index with the CKF_BIP32_HARDENED
bit set is considered a hardened child. The child index is 0 for the master key. The public and private key share this value. Read only.
CKA_BIP32_CHILD_DEPTH: The depth of the child key in the key tree. The master key has a depth of 0. The public and private key share this value. Read only.
CKA_BIP32_ID: The unique identifier for the key. This value is derived from the HASH160 of the compressed public key. The first 32 bits of this value is known as the fingerprint. (CKA_ID
is not used for this purpose because it is writable by the user.) The public and private key share this value. Read only.
NOTE No attribute is included for the parent ID because it should not be required. The anticipated use-case is to derive a key, use it and then delete it. In general, there should not be a need to discover how keys are organized based on the fingerprints or IDs. The parent fingerprint is available in case there is need to rediscover a key tree, but the wallet software must deal with any collisions. The BIP32 designers considered the parent ID not sufficiently important to include in serialized keys; therefore we exclude it as well.
CKA_BIP32_FINGERPRINT and CKA_BIP32_PARENT_FINGERPRINT:
The fingerprints for the key and parent key are the first 32 bits of the BIP32 key identifier. These can be used to identify keys but the wallet software must handle any collisions. For identifying keys, it is better to use CKA_BIP32_ID because it is long enough that collisions should not be an issue. The public and private key share this value. The master key has a parent fingerprint of 0. Read only.
Public Key Import/Export
To support importing existing BIP32 keys, we support their serialization format. For public keys, we will have functions in our library to facilitate importing and exporting.
CK_RV CA_Bip32ImportPubKey( CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, const CK_CHAR_PTR pKey, //in BIP32 serialization format CK_OBJECT_HANDLE_PTR phObject ); CK_RV CA_Bip32ExportPubKey( CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_CHAR_PTR pKey, //in BIP32 serialization format CK_ULONG_PTR pulKeyLen //on input contains max. buffer size, returns // actual size );
Importing is done with CA_Bip32ImportPubKey()
. The function is similar to C_CreateObject()
but it takes an additional parameter for the serialized public key. The template passed in should contain all the desired non-BIP32 attributes like CKA_TOKEN
, CKA_PRIVATE
, CKA_DERIVE
, etc. The function decodes the public key to get all the BIP32 attributes. Both sets of attributes are then used to create the public key on the HSM.
NOTE When importing a serialized extended public key, implementations must verify whether the X coordinate in the public key data corresponds to a point on the curve. If not, the extended public key is invalid.
Exporting is done with CA_Bip32ExportPubKey()
. The specified object is extracted from the HSM and encoded in the BIP32 format. The result is a NULL-terminated string and is placed in the pKey
parameter. The length of pKey
has a maximum of 112 characters. This constant is defined as CKG_BIP32_MAX_SERIALIZED_LEN
. It’s possible that not all characters are needed to serialize the key. Any unused characters are set to 0.
See Code Samples for code examples.
BIP32 Serialization Format
Extended public and private keys are serialized as follows:
>4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
>1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
>4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
>4 bytes: child number (index) – 32-bit unsigned integer with most significant byte first (0x00000000 if master key)
>32 bytes: the chain code
>33 bytes: the public key or private key data
This 78 byte structure is encoded like other Bitcoin data in Base58, by first adding 32 checksum bits (derived from the double SHA-256 checksum), and then converting to the Base58 representation. This results in a Base58-encoded string of up to CKG_BIP32_MAX_SERIALIZED_LEN
characters. Because of the choice of the version bytes, the Base58 representation will start with "xprv" or "xpub" on mainnet, "tprv" or "tpub" on testnet.
Private Key Import/Export
Private keys can be imported and exported with existing PKCS#11 functions. They can be imported and exported only if the HSM uses the key wrap model instead of cloning. Import a key by calling C_Encrypt*()
on the serialized key followed by C_UnwrapKey()
. Exporting keys by calling C_WrapKey()
followed by C_Decrypt*()
. Use C_WrapKey()
and C_UnwrapKey()
to store keys off the HSM, or to move them between HSMs.
See Code Samples for code examples.
Key Backup and Cloning
Backups and cloning of BIP32 keys are supported only between version 7.x Luna HSMs. Further, cloning of BIP32 keys is supported only in firmware versions that have BIP32 support. BIP32 keys cannot be cloned to older firmware versions made before BIP32 support was added.
Non-FIPS Algorithm
The BIP32 mechanisms are available only if non-FIPS algorithms are allowed.
Host Tools
Multitoken and Ckdemo support BIP32.
Code Samples
Deriving the master key pair
We highly recommend setting CKA_PRIVATE
on the master public and private keys to TRUE to prevent the chain code from being seen by unauthorized users. The master key should be used only for derivations so it is the only operation allowed. The version bytes default to 0x0488B21E/0x0488ADE4 for the public/private keys if the attribute is missing in the template. Those are the values specified in BIP32 for keys on the main bitcoin network.
CK_ATTRIBUTE pubTemplate[] = { {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_DERIVE, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, }; CK_ATTRIBUTE privTemplate[] = { {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_SENSITIVE, &bTrue, sizeof(bTrue)}, {CKA_DERIVE, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, }; CK_BIP32_MASTER_DERIVE_PARAMS mechParams; mechParams.pPublicKeyTemplate = pubTemplate; mechParams.ulPublicKeyAttributeCount = ARRAY_SIZE(pubTemplate); mechParams.pPrivateKeyTemplate = privTemplate; mechParams.ulPrivateKeyAttributeCount = ARRAY_SIZE(privTemplate); CK_MECHANISM mechanism = {CKM_BIP32_MASTER_DERIVE, &mechParams, sizeof(mechParams)}; CK_RV rv = C_DeriveKey(hSession, &mechanism, hSeedKey, NULL, 0, NULL); // fail if rv != CKR_OK CK_OBJECT_HANDLE pubKey = mechanism.mechParams->hPublicKey; CK_OBJECT_HANDLE privKey = mechanism.mechParams->hPrivateKey;
The new key handles will be stored in pubKey
and privKey
if the derivation was successful.
Deriving a child leaf key
We highly recommend setting CKA_PRIVATE
on the child public and private keys to TRUE to prevent the chain code from being seen by unauthorized users. A child leaf key (the bottom key in the tree) should not be used for derivation, and is meant for signing, verifying, encrypting and decrypting. Parent child keys need the derive attribute turned on. The version bytes default to 0x0488B21E/0x0488ADE4 for the public/private keys if the attribute is missing. Those are the values specified in BIP32 for keys on the main bitcoin network.
CK_ATTRIBUTE pubTemplate[] = { {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_ENCRYPT, &bTrue, sizeof(bTrue)}, {CKA_VERIFY, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, }; CK_ATTRIBUTE privTemplate[] = { {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_SENSITIVE, &bTrue, sizeof(bTrue)}, {CKA_SIGN, &bTrue, sizeof(bTrue)}, {CKA_DECRYPT, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, }; CK_ULONG path[] = { CKF_BIP32_HARDENED | CKG_BIP44_PURPOSE, CKF_BIP32_HARDENED | CKG_BIP44_COIN_TYPE_BTC, CKF_BIP32_HARDENED | 1, CKG_BIP32_EXTERNAL_CHAIN, 0 }; CK_BIP32_MASTER_DERIVE_PARAMS mechParams; mechParams.pPublicKeyTemplate = pubTemplate; mechParams.ulPublicKeyAttributeCount = ARRAY_SIZE(pubTemplate); mechParams.pPrivateKeyTemplate = privTemplate; mechParams.ulPrivateKeyAttributeCount = ARRAY_SIZE(privTemplate); mechParams.pulPath = path; mechParams.ulPathLen = ARRAY_SIZE(path); CK_MECHANISM mechanism = {CKM_BIP32_CHILD_DERIVE, &mechParams, sizeof(mechParams)}; CK_RV rv = C_DeriveKey(hSession, &mechanism, hMasterPrivKey, NULL, 0, NULL); // fail if rv != CKR_OK CK_OBJECT_HANDLE pubKey = mechanism.mechParams->hPublicKey; CK_OBJECT_HANDLE privKey = mechanism.mechParams->hPrivateKey;
The new key handles are stored in pubKey
and privKey
if the derivation was successful. The path generates a key pair that follows the BIP44 convention and can be used to receive BTC.
Importing a public extended key
CK_ATTRIBUTE template[] = { {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_DERIVE, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, }; CK_CHAR_PTR encodedKey = “xpub661MyMwAqRbcFtXgS5…”; //BIP32 serialization format CK_OBJECT_HANDLE pubKey; CK_RV rv = CA_Bip32ImportKey(hSession, template, ARRAY_SIZE(template), encodedKey, &pubKey);
The handle for the newly create key is stored in pubKey
if the import was successful.
Exporting a public extended key
CK_CHAR encodedKey[CKG_BIP32_MAX_SERIALIZED_LEN+1]; CK_ULONG ulEncodedKeySize = sizeof(encodedKey); CK_RV rv = CA_Bip32ExportPubKey(hSession, hObject, encodedKey, &ulEncodedKeySize );
The encoded key is stored in encodedKey
(BIP32 serialization format) if there were no errors.
Importing a private extended key
CK_ATTRIBUTE template[] = { {CKA_CLASS &keyClass, sizeof(keyClass)}, {CKA_TOKEN, &bToken, sizeof(bToken)}, {CKA_KEY_TYPE &keyType, sizeof(keyType)}, {CKA_PRIVATE, &bTrue, sizeof(bTrue)}, {CKA_DERIVE, &bTrue, sizeof(bTrue)}, {CKA_MODIFIABLE, &bTrue, sizeof(bTrue)}, {CKA_LABEL, pbLabel, strlen(pbLabel)}, {CKA_SENSITIVE &bTrue, sizeof(bTrue)}, }; CK_CHAR_PTR encodedKey = “xprv9s21ZrQH143K3QTDL4LXw2F…”; CK_MECHANISM mechanism = {CKM_AES_KWP, NULL, 0}; CK_BYTE wrappedKey[256]; CK_ULONG wrappedKeyLen = sizeof(wrappedKey); CK_OBJECT_HANDLE hUnwrappedKey; CK_RV rv = C_EncryptInit(hSession, &mechanism, hWrappingKey); // fail if rv != CKR_OK rv = C_Encrypt(hSession, encodedKey, sizeof(encodedKey), wrappedKey, &wrappedKeyLen); // fail if rv != CKR_OK rv = C_UnwrapKey(hSession, &mechanism, hWrappingKey, wrappedKey, wrappedKeyLen, template, ARRAY_SIZE(template), &hUnwrappedKey);
After unwrapping, the encoded key's BIP32 serialization format is decoded (the template key type is checked for BIP32). The handle of the unwrapped key is stored in hUnwrappedKey
if there were no errors.
Exporting a private extended key
CK_MECHANISM mechanism = {CKM_AES_KWP, NULL, 0}; CK_BYTE key[256]; CK_ULONG keyLen = sizeof(key); CK_RV rv = C_WrapKey(hSession, &mechanism, hWrappingKey, hKeyToWrap, key, &keyLen); // fail if rv != CKR_OK rv = C_DecryptInit(hSession, &mechanism, hWrappingKey); // fail if rv != CKR_OK rv = C_Decrypt(hSession, key, keyLen, key, &keyLen); // fail if rv != CKR_OK key[keyLen] = 0 // The key isn’t NULL terminated after C_Decrypt().
C_WrapKey() must convert the BIP32 key to the BIP32 serialization format before wrapping.
The serialized key is stored in key
if there were no errors.
PKCS#11 Definitions
#define CKK_BIP32 (CKK_VENDOR_DEFINED | 0x14) #define CKM_BIP32_MASTER_DERIVE (CKM_VENDOR_DEFINED | 0xE00) #define CKM_BIP32_CHILD_DERIVE (CKM_VENDOR_DEFINED | 0xE01) #define CKR_BIP32_CHILD_INDEX_INVALID (CKR_VENDOR_DEFINED | 0x83) #define CKR_BIP32_INVALID_HARDENED_DERIVATION (CKR_VENDOR_DEFINED | 0x84) #define CKR_BIP32_MASTER_SEED_LEN_INVALID (CKR_VENDOR_DEFINED | 0x85) #define CKR_BIP32_MASTER_SEED_INVALID (CKR_VENDOR_DEFINED | 0x86) #define CKR_BIP32_INVALID_KEY_PATH_LEN (CKR_VENDOR_DEFINED | 0x87) #define CKA_BIP32_CHAIN_CODE (CKA_VENDOR_DEFINED | 0x1100) #define CKA_BIP32_VERSION_BYTES (CKA_VENDOR_DEFINED | 0x1101) #define CKA_BIP32_CHILD_INDEX (CKA_VENDOR_DEFINED | 0x1102) #define CKA_BIP32_CHILD_DEPTH (CKA_VENDOR_DEFINED | 0x1103) #define CKA_BIP32_ID (CKA_VENDOR_DEFINED | 0x1104) #define CKA_BIP32_FINGERPRINT (CKA_VENDOR_DEFINED | 0x1105) #define CKA_BIP32_PARENT_FINGERPRINT (CKA_VENDOR_DEFINED | 0x1106) #define CKG_BIP32_VERSION_MAINNET_PUB (0x0488B21E) #define CKG_BIP32_VERSION_MAINNET_PRIV (0x0488ADE4) #define CKG_BIP32_VERSION_TESTNET_PUB (0x043587CF) #define CKG_BIP32_VERSION_TESTNET_PRIV (0x04358394) #define CKG_BIP44_PURPOSE (0x0000002C) #define CKG_BIP44_COIN_TYPE_BTC (0x00000000) #define CKG_BIP44_COIN_TYPE_BTC_TESTNET (0x00000001) #define CKG_BIP32_EXTERNAL_CHAIN (0x00000000) #define CKG_BIP32_INTERNAL_CHAIN (0x00000001) #define CKG_BIP32_MAX_SERIALIZED_LEN (112) #define CKF_BIP32_HARDENED (0x80000000) #define CKF_BIP32_MAX_PATH_LEN (255)