BIP32: Non-Hardened Derivation

The TSM supports soft (aka non-hardened) derivation according to the BIP32 standard.

Generating a Master Key

Generating a BIP32 master key can be done like this:

masterKeyID, err := ecdsaClient.Keygen("secp256k1")

This instructs the MPC nodes to engage in a distributed key generation protocol that produces one key share for each MPC node. In addition, a random master chain code is generated and stored at each MPC node.

Signing with a Derived Key

Suppose you want to sign a message msg using the key derived using the chain path m/42/1. Here m stands for the master key and /42/1 is the actual chain path. Then you can do as follows:

chainPath := []uint32{42, 1}
signature, _, err := ecdsaClient.Sign(masterKeyID, chainPath, msg)

This will instruct each of the MPC nodes to derive a share of the derived key m/42/1 and use that key share to sign the message. Note that there is no key ID for the derived key. Instead, you use the master key ID and the chain path each time you want to sign with the derived key.

Signing with a derived key, as shown, is quite efficient. This is because soft 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 sign directly with the master key, you can simply use chainPath=nil, like this:

signature, _, err := ecdsaClient.Sign(masterKeyID, nil, msg)

Getting a Derived Public Key

In addition to signing with a derived key, you can also fetch the public derived key as follows:

chainPath := []uint32{42, 1}
derPublicKey, err := ecdsaClient.PublicKey(masterKeyID, chainPath)

Example Code

The following is a self-contained Go example, showing how to generate ECDSA signatures and public keys for derived keys.

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"gitlab.com/sepior/go-tsm-sdk"
)

// Configure your TSM here
const credentials string = `
{
	"userID": "my-user-id",
	"urls": [ "https://my-tsm-node1.tsm.sepior.net", "https://my-tsm-node2.tsm.sepior.net", "https://my-tsm-node3.tsm.sepior.net" ],
	"passwords": [ "password1", "password2", "password3" ]
}
`

func main() {

	// Create a new client
	tsmClient, err := tsm.NewPasswordClientFromEncoding(credentials)
	if err != nil {
		panic(err)
	}
	ecdsaClient := tsm.NewECDSAClient(tsmClient)
	
	// Generate ECDSA master key and master chain code
	keyID, err := ecdsaClient.Keygen("secp256k1")
	if err != nil {
		panic(err)
	}
	
	// The message to be signed
	message := []byte(`Hello World`)
	msgHash := sha256.Sum256(message)
	
	// Example 1: Sign message using the generated master key
	signature1, _, err := ecdsaClient.Sign(keyID, nil, msgHash[:])
	if err != nil {
		panic(err)
	}
	fmt.Println("Signature:", hex.EncodeToString(signature1))

	// Example 2: Sign message using the non-hardened derived key m/1/2/3
	chainPath2 := []uint32{1, 2, 3}
	signature2, _, err := ecdsaClient.Sign(keyID, chainPath2, msgHash[:])
	if err != nil {
		panic(err)
	}
	fmt.Println("Signature:", hex.EncodeToString(signature2))

}