SLIP 10

Of the SatoshiLabs Improvement Proposals (SLIPs), SLIP10 extends the Bitcoin Improvement Proposal 32 (BIP32). SLIP10 support is introduced with Luna HSM firmware version 7.8.7, and also requires Luna HSM Client version 10.7.1 for the matching library updates.

SLIP10 supports curves Ed25519 and secp256k1, as well as NIST P-256 curve. All operations appear as BIP32 operations and one of the 3 SLIP-10 curves must be explicitly specified.

SLIP10 BIP32 Master Key derivation and Child Key derivation

CKM_BIP32_MASTER_DERIVE and CKM_BIP32_CHILD_DERIVE – need to explicitly specify the curve to be used, if curve not specified then BIP32 derivation is performed.

>key type CKK_BIP32

>attributes CKA_BIP32_…

>error codes CKR_BIP32_…

>defines CKG_BIP32_... and CKF_BIP32_...

>public key import/export via CA_Bip32ImportPubKey() and CA_Bip32ExportPubKey()

>when attempted against an HA slot, requires that group members be running BIP32 firmware (7.3.0 onward).

Caveats

The following circumstances are worth noting.

>Cloning SLIP10 ED25519 keys to older firmware ( versions before 7.8.7) will not work. That includes Backup.

>The BIP32 and SLIP10 mechanisms are available only if non-FIPS algorithms are allowed (see Enable non-FIPS algorithms)

>A SLIP10 CKK_BIP32 key for curve secp256k1 stored on the HSM cannot be differentiated from a BIP32 key. For SLIP10 curves NIST P-256 and Ed25519, the curve parameters attribute can be used to indicate it is a SLIP10 key. Therefore, a user could pick a SLIP10 secp256k1 key for BIP32 operations.
The concern is that BIP32 key derivations are sometimes invalid causing the tree to stop at that point, while SLIP10 will retry with a new value and keep going. Thus, some levels on a SLIP10 tree cannot be reached if using BIP32 derivations. But the chance of an invalid BIP32 key is lower than 1 in 2127.

>When importing/exporting a SLIP10 key (uses serialization format to store extra BIP32 attributes) the serialization data does not indicate if SLIP10 or BIP32, nor which curve was used. The user must specify the curve parameters in the key template when importing.

Some of the above issues are reduced/eliminated if you keep track of how the keys were derived (add label?) and only use appropriate keys for BIP32 or SLIP10 operations.

Curve Support

Curve secp256k1 is supported for BIP32. If you attempt to specify a curve with CKA_ECDSA_PARAMS in the derive key templates, the BIP32 derivation mechanisms fail with CKR_TEMPLATE_INCONSISTENT.

For SLIP-10 the ability to specify the curve parameters with the BIP32 mechanisms is allowed. Specify the attribute CKA_ECDSA_PARAMS when using the BIP32 derive mechanisms. If the attribute is not specified, then a BIP32 secp256k1 key is derived (same as pre-7.8.7 LUNA firmware). If this attribute is present in the incoming derive template, then the firmware checks if it specifies one of the three supported curves and if so, it performs SLIP-10 operations (if not then an error is thrown).

Hardened Keys

The specification allows both extended child keys and hardened child keys. For SLIP10 Ed25519 only hardened key generation from private parent key to private child key is supported. For secp256k1 and NIST P-256 curves both hardened and non-hardened keys are allowed.

Key Derivation

This section shows the differences between sample code for BIP32 and SLIP-10. Basically, for SLIP-10 the attribute CKA_ECDSA_PARAMS must be specified in the key templates.

Deriving the master key pair

We strongly recommended to set 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. The desired SLIP-10 curve must be provided in the key templates.

unsigned char ecParams[] =
{0x06,0x05,0x2B,0x81,0x04,0x00,0x0A};    			/* secp256k1 */
//OR	{0x06,0x08,0x2A,0x86,0x48,0xCE,0x3D,0x03,0x01,0x07}; 	/* X9_62_prime256v1 */
//OR	{0x06,0x09,0x2B,0x06,0x01,0x04,0x01,0xDA,0x47,0x0F,0x01}; 	/* Ed25519 */
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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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

It’s highly recommended to set 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. The desired SLIP-10 curve must be given in the key templates.

See previous section for “ecParams[]” definition.

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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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 is 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 (for UC 10.7.1 onwards)

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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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

(Same as BIP32)

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.

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)},
{CKA_ECDSA_PARAMS,      &ecParams,    sizeof(ecParams)},
};
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 its 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

(Same as BIP32)

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.