BIP32: Non-Hardened Derivation
Generating a Master Key
Generating a BIP32 master key can be done like this:
curveName := "secp256k1"
masterKeyID, err := client.ECDSA().GenerateKey(context, sessionConfig, threshold, curveName, "")
As explained in the previous section, each SDK must be called in order for the MPC key generation session to take place. As a result, each MPC node will hold one key share of the master key. In addition, a random master chain code is generated and stored at each MPC node.
The above call generates an extended key in the TSM, as specified in BIP-32. The extended key consists of the actual key, which is secret shared among the MPC nodes, and a chain code. Each MPC node holds a copy of the chain code.
Instead of generating a master key in the TSM, it is also possible to import a master key.
According to the BIP32 standard, the master key and chain code are both derived from a master seed (or a BIP39 passphrase). When calling
GenerateKey
the master key and the chain code are sampled as two independent values. The master key only exists as a secret sharing and is never assembled at a single MPC node. On the other hand, each of the MPC nodes will hold a copy of the master chain code. This may require special care when importing master keys from external wallets.
Obtaining a Derived Public Key
Once the master key has been generated, you can obtain a derived public key as follows. Suppose you want the public key derived according to the derivation path m/42/2/3
. Here m stands for the master key and /42/2/3
is the actual derivation path, see BIP32 for more on derivation paths. Then you can proceed like this:
derivationPath := []uint32{42,2,3}
pkixPublicKey, err := ecdsaClient.PublicKey(masterKeyID, derivationPath)
The returned encoded public key is a SubjectPublicKeyInfo
structure, see RFC 5280, Section 4.1.
Each MPC node uses its master key share and master chain code and the provided derivation path to compute a share of the derived key. Each MPC node can do this locally, due to the mathematical properties of non-hardened key derivation. The derived key shares are computed on-the-fly and cached in the memory of each MPC node.
When to Trust the Public KeyThe key generation protocol ensures that honest players, whose operation succeeds, will end up with the correct key share and the correct public key. But the protocol does not guarantee that all honest players will succeed. In fact, all but one honest player may experience a session abort.
Since the call to
PublicKey()
simply fetches the public key from a single MPC node, it is important that this public key is only used, e.g., for creating a wallet address, when it is known that all players succeeded the key generation session.In addition, unless you really trust the MPC node to which the SDK is connected, it may be necessary to retrieve the public key from the other MPC nodes and ensure that the public keys returned are all equal, before the public key is used. This prevents a single corrupt MPC node from returning a false public key where only the corrupt MPC node knows the private key.
Signing with a Derived Key
Suppose you want to sign a message using a key derived from a master key according to the derivation path m/42/2/3
. Then you can do as follows:
derivationPath := []uint32{42, 2, 3}
curveName := "secp256k1"
partialSignResult, err := client.ECDSA().Sign(context, sessionConfig, masterKeyID, derivationPath, msgHash)
This will instruct each MPC node to locally derive a share of the derived key m/42/2/3
and use that key share to sign the message via an interactive MPC session with the other MPC nodes.
Note that there is no key ID for the derived key. Instead, you use the master key ID and the derivation path each time you want to sign with the derived key.
Signing with a derived key, as shown, is quite efficient. This is because non-hardened BIP-32 derivation lets each MPC node compute its share of the derived key locally, based only on its share of the master key and the given chain path. The MPC nodes keep their derived key shares safe, just like the master key shares.
If you want to get the master public key, or sign directly with the master key, you can simply use an empty derivation path, like this:
derivationPath := []uint32{} // empty path corresponds to master key
partialSignResult, err := client.ECDSA().Sign(context, sessionConfig, masterKeyID, derivationPath, msgHash)
Code Examples
You can find self-contained example code, showing how to generate signatures and public keys for keys derived from a (normal, non-hardened) BIP32 master key using Builder Vault in our demo repository here: Go, Java, node.js.
Updated 19 days ago