External Key Import (ECDSA)
Sometimes you may need to import an external key into the TSM, e.g., a key from an external key store or wallet. To do this, you will have to first create a secret sharing of the key, then wrap (encrypt) each share with the wrapping key of the MPC node that should receive the share, and then run an MPC session that imports the key.
The following shows you how to do this for an ECDSA key.
We will assume here that you want to import a Bitcoin or Ethereum private key, i.e., a raw scalar on the secp256k1 curve, and that it has the following hex encoding:
privateKeyHex := "90c3a45df61c8dd3c683a1772657473868c0bb416092d80b6a12d3aa314916d8"
We will also assume that the corresponding chain code has this hex encoding:
chainCodeHex := "7365df71160ca42df2fa3f447fb62f74c90e1996a7cacbd437d41a3638a49809"
Chain Code is OptionalThis example assumes that you are importing an extended key, consisting of the private key itself and a chain code. The extended key could for example be the BIP32 master key, or an extended key derived from a master key.
If the key you want to import is not an extended BIP32 key, you can simply ignore the chain code in the following. The TSM will then generate a random chain code, when you import the private key.
You will also need the public key that corresponds to the private key. If you don’t have the public key already, you can compute it from the private key as follows:
privateKey, err := hex.DecodeString(privateKeyHex)
curve, err := ec.NewCurve(ec.Secp256k1.Name())
x, err := curve.Zn().DecodeScalar(privateKey)
y := curve.G().Multiply(x)
pkixPubKey, err := tsmutils.ECPointToPKIXPublicKey(curve.Name(), y.Encode())
The next step is to create a secret sharing of the private key. In this example we will create a secret sharing for three MPC nodes identified by indices 0, 1, 2, and with a security threshold of 1:
threshold := 1
players := []int{0, 1, 2}
curveName := "secp256k1"
keyShares, err := tsmutils.ShamirSecretShare(threshold, players, curveName, privateKey)
Each of the key shares must now be encrypted using the wrapping key of the MPC node to which it should be sent. For example, if the client is the SDK controlling MPC node 1, this can be done as follows:
ctx := context.Background()
wrappingKey, err := client.WrappingKey().WrappingKey(ctx)
pub, err := x509.ParsePKIXPublicKey(wrappingKey)
rsaWrappingKey := pub.(*rsa.PublicKey)
wrappedShare1, err := tsmutils.Wrap(rsaWrappingKey, keyShares[1])
wrappedChainCode1, err := tsmutils.Wrap(rsaWrappingKey, masterChainCode)
The key import is then completed by requesting a key import MPC session as follows:
essionID := tsm.GenerateSessionID()
players := []int{1,2,3}
sessionConfig := tsm.NewSessionConfig(sessionID, players, nil)
keyID, err = client.ECDSA().ImportKey(ctx, sessionConfig, threshold, wrappedShare1, wrappedChainCode1, pkixPubKey, "")
As usual, the MPC session only starts when all the nodes defined by the configuration have made this call on their SDK, and it only succeeds if they agree on the session meta data (sessionID
, players
) as well as the value of threshold
and pkixPubKey
.
The wrapped chain code is an optional argument. If provided, the MPC nodes will make check that the same chain code was provided to all MPC nodes.
Importing from a BIP32 Master Seed or BIP32 Mnemonic Code
In the previous section we considered import of a regular private ECDSA key or an extended BIP32 key consisting of the private key and a chain code into the TSM.
In some cases, though, you may want to import keys based on a single BIP32 seed, or a BIP39 mnemonic code that encodes a BIP32 seed.
The TSM does not currently support BIP39, so if you hold a BIP39 mnemonic code, you first convert this to the corresponding BIP32 seed.
There are then two options:
- Convert the BIP32 seed to an extended key and import the extended key as explained above. So if the BIP32 seed was used in an external wallet that follows BIP44, this will only work if you derive the extended BIP32 key for each derived BIP44 account before, and then imports these keys separately into the TSM. And these keys will then exist in the TSM as separate keys.
- If the first approach does not fit your use case, the TSM supports importing the BIP32 seed directly, and it lets you do hardened BIP32 derivations of keys, once they are imported. But this is currently only supported for specific threshold parameters, and the initial hardened derivations are quite resource demanding. You can read more about this approach in our section about hardened key derivation.
Code Example
You can find running example code showing how to import an extended BIP32 key (i.e., a raw ECDSA secp256k1 private key and the corresponding chain code) into a Builder Vault instance in our demo repository: Go, Java, node.js.
Updated 19 days ago