Emergency Recovery (ECDSA)
This article will show you how to create an ERS backup of an ECDSA key in the TSM and how the key can later be recovered from the backup. In this example, we will use the TSM SDK to do the recovery, but on request, we can also provide an open-source Go reference implementation of the recovery and validation.
The example in this section assumes that you have access to a TSM and that you have access to the TSM SDK. Our Getting Started tutorials provide more information about how to get to that point.
Creating Recovery Data
Creating recovery data requires that the MPC nodes in the TSM are configured to allow this. See this section for more about how to configure this in the TSM.
We first need to generate a key in the TSM so we have something to recover. For this we run an MPC key generation session as follows:
sessionID := tsm.GenerateSessionID()
players := []int{0, 1, 2} // We want to generate the key as a sharing among these players
threshold := 1 // The security threhsold of the key
sessionConfig := tsm.NewSessionConfig(sessionID, threshold, players, nil)
ctx := context.Background()
keyID, err = client.ECDSA().GenerateKey(ctx, sessionConfig, threshold, "secp256k1", "")
For the MPC session to start, you must make this call using each of the three MPC nodes, and they must all agree on the session parameters (sessionID
, players
, threshold
). A complete, running example of this can be found below.
Once the key is generated, we can now continue to create ERS recovery data for the key.
We first choose an ERS label and generate an ERS key:
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
)
ersLabel := []byte("exampleLabel")
ersPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
ersPublicKey, err := x509.MarshalPKIXPublicKey(&ersPrivateKey.PublicKey)
In this example, we generated the private ERS key in the clear. In a production setting, the ERS key will instead often be generated inside an HSM, and only the public ERS key will be exported from the HSM.
We can now run an MPC session that generates recovery data for the key as follows:
sessionID := tsm.GenerateSessionID()
sessionConfig := tsm.NewSessionConfig(sessionID, threshold, players, nil)
partialRecoveryData, err := client.ECDSA().GenerateRecoveryData(ctx, sessionConfig, keyID, ersPublicKey, ersLabel)
As with key generation, this call has to be done for each MPC node.
Ensuring Validity of the ERS Public KeyThe returned recovery data essentially consists of the private key share, encrypted under the provided ERS public key. For security, it’s important to enforce that the correct ERS public key is used, and not just some rogue public key. As part of the generation of the recovery data, the MPC nodes will compare the ERS public key that was provided by the SDK, and the MPC session will abort if they are not equal. So the way to ensure that recovery data uses the correct ERS public key is to let each SDK (or at least some of the SDKs) validate the ERS public key before the call to
GenerateRecoveryData
is made.
If the MPC session was successful, each SDK will receive partial recovery data. The partial data must be collected, and combined into the final recovery data:
recoveryData, err := tsm.ECDSAFinalizeRecoveryData(recoveryDataArray, &ersPrivateKey.PublicKey, ersLabel)
The recoveryData
now contains all the key shares of the key, each encrypted under the ERS public key, and the recovery data can be stored and later used for recovery. The recovery data is not sensitive since the secret key shares are encrypted under the ERS public key.
Validating Recovery Data
The recovery data will often be stored for a while, maybe by some 3rd party. It contains a zero-knowledge proof that allows anyone to validate it, without being able to decrypt it.
Given the public ERS key, the ERS label, and the public ECDSA key, the recovery data can be validated, using the zero-knowledge proof, with this:
publicKey, err := ecdsaClient.PublicKey(keyID, nil) // Get the public key from the TSM
err = tsm.ECDSAValidateRecoveryData(recoveryData, publicKey, ersPublicKey, ersLabel)
We know the recovery data is valid if the call to RecoveryInfoValidate
does not return an error. This means it contains key shares that can be decrypted with the provided ERS label and the private ERS key corresponding to the provided ERS public key, and that the decrypted key shares are indeed shares of the private key corresponding to the provided public key.
Anyone can validate the recovery data. It does not require the ERS private key to validate, and the key shares in the recovery data remain encrypted under the ERS public key.
Validation also does not require access to any MPC nodes. The ECDSAValidateRecoveryData()
method in the TSM SDK is a static method. If you don't have access to a TSM SDK, you can validate the recovery data using our open-source ERS reference implementation or implement the validation method yourself.
Validating the recovery data regularly lets you detect if someone has corrupted or tampered with it. Also, if transferring the recovery data from one person to another, the receiving person may want to re-validate the recovery data if he doesn't trust the sender.
The only important thing to remember when validating, is to use the correct ERS public key, ERS label, and ECDSA public key, since the validity is relative to these keys.
Key Recovery
Given the ERS label and the private ERS key, the recovery data can later be used to recover the private ECDSA master key and master chain code from the recovery data. This can be done like this:
recoveredData, err := tsm.ECDSARecoverPrivateKey(recoveryData, ersPrivateKey, ersLabel)
masterKey := recoveredData.PrivateKey
masterChainCode := recoveredData.MasterChainCode
Here we used a static helper method in the TSM SDK for convenience. You can also recover the ECDSA key using our open-source reference implementation or implement the recovery code yourself.
Once you have recovered the master key and master chain code, you can use these to compute derived public and private keys as described in the BIP32 specification. Alternatively, the Builder Vault SDK has a method that can do this:
chainPath := []uint32{44, 0, 1}
derivedPrivateKey, err := tsm.ECDSADerivePrivateKey(masterKey, masterChainCode, chainPath)
Key Recovery with Black Box DecryptionIn this example we used the private ERS key directly for recovery. In a production setting, the private ERS may exist only inside an HSM, and we may want to recover using only the RSA decryption function of the HSM, that is, without exporting the private ERS key from the HSM. In our open-source ERS library, you can see how recovery can be done using only "black-box" calls to RSA decryptions using the private ERS key. That is, we can recover using only something that implements this interface:
Decrypt(ciphertext, label []byte) (plaintext []byte, err error)
A Complete Example
Running code examples combining the steps described above, can be found in our demo repository: Go, Java, node.js.
Updated 19 days ago