Import/Export of Key Shares

Import/Export of Key Shares


There are different scenarios where import and export are useful to enable access to the same key on different


When running in a high availability system, it may be important to have a fail-over site that can be switched to in
case of failure in the primary site. Import and export allows moving key shares from one site to another and keep the same keyID on the new site such that the user does not notice that load has been shifted to an alternate site.

Note that key shares cannot be used across the two sites, so a signature cannot be made using node 1 from the main site and node 2 from the fail-over site. From the TSM perspective it is two separate keys. This is done to ensure that shares are kept secure as long as a single site is not compromised in excess of the security parameters.

Multiple control

Some keys may require that multiple people are in control of them and can use them to sign data independently of each other. In this scenario one share may be stored on an embedded client on a mobile phone while the rest are stored on some shared servers. In this case each instance of the key will have its own keyID as they have to coexist on some servers.

Security notes

When exporting and importing keys, the key shares are re-shared while in transport. This is done such that transport shares that are compromised in transport will not compromise the shares at either the exporting or importing site. This also ensures that the importing and export site are independent.


To enable export a couple of configuration entries need to be configured

  ExportWhiteList = ["BA3E64=="]


EnableExport will enable endpoints so export can be accessed.

ExportWhiteList is used to control under which public keys the keyShares can be exported under. This is a list of BASE64 encoded RSA public keys. Only keys on the list will be allowed when performing an export operation.

Configuring the WhiteList

If possible, the white list should only contain public keys of trusted servers. For testing, or in the case with very dynamic client lists, a wildcard can be used:

  ExportWhiteList = ["*"]

This will accept any keys for export, and should be used only for testing or when other security checks are used to ensure no unauthorized access is allowed.

Fail-over setting

In the fail-over setting, the best way to configure the fail-over sites, is that the WhiteList of the main site only contains the exported wrapping key from the corresponding fail-over site. So the WhiteList of node1 in the main site will contain the wrapping key from node1 of the fail-over site and only this key, and likewise for node2, etc. This will ensure that key shares can only be moved between the two corresponding nodes in the main and fail-over site and nowhere else.

Code examples

There are two types of functions for export and import. With or without session ID. Without session ID is for when all nodes are controlled by the same entity, and with session ID is for when different nodes are run from different places, e.g. when using embedded nodes on mobile phones or when different people are in control of different nodes.

Without session ID

This example exports and imports on the same client. In a fail-over setup the exported shares need to be moved from the main nodes to the fail-over nodes.

// Error checking have been omitted for simplicity
// Input: client and keyId
ecdsaClient := tsm.NewECDSAClient(client)
keyID, err := ecdsaClient.Keygen(curveName)

// Performed on the client at the importing side, wrapKeys are transported to the exporting nodes
wrapKeys, _, err := ecdsaClient.WrappingKeys()

// Performed on the client at the exporting side, return values are transported to the importing nodes
keyShares, chainCodes, curve, derPublicKey, err := ecdsaClient.ExportWrappedKeyShares(keyID, wrapKeys)

// Performed on the client at the importing side. keyID can be replaced with the empty string in which case a new keyID
// will be generated
importedKeyId, err := ecdsaClient.ImportWrappedKeyShares(curve, keyShares, chainCodes, derPublicKey, keyID)

With session ID

This example is pseudocode written using GO against the mobile interfaces. The mobile interfaces are released in swift and java for iOS and Android respectively.

// Error checking have been omitted for simplicity
// Input: config and keyId

// Importing Client: Prepare wrapping key
importClient, _ := NewEmbeddedTenantClient(config)
wrappingClient := NewWrappingClient(importClient)
importECDSAClient := NewECDSAClient(importClient)

wrappingKey, _ := wrappingClient.WrappingKeys()
encodedKey := base64.StdEncoding.EncodeToString(wrappingKey.SubjectPublicKeyInfo)

// Exporting client: Export key share
exportClient, _ := NewEmbeddedTenantClient(fmt.Sprintf(
	`# Normal configuration plus:\n" +
	"[Player]\n" +
	"ExportWhiteList = ["%s"]\n`, encodedKey))
exportECDSAClient := NewECDSAClient(exportClient)
exportSessionID := GenerateSessionID()
exportedShare, _ := exportECDSAClient.ExportWrappedKeySharesWithSessionID(exportSessionID, keyID, wrappingKey.SubjectPublicKeyInfo)
// Simultaneous to this, the export need to be started on the all the server nodes with the same exportSessionID

// Importing Client: Import key share. Note an empty string is used for keyID which will result in a new keyID being generated.
importSessionID := GenerateSessionID()
importECDSAClient.ImportWrappedKeySharesWithSessionID(importSessionID, exportedShare.Curve, exportedShare.KeyShare, exportedShare.ChainCode, exportedShare.PublicKey, "")
// Simultaneous to this, the import should be started on the server node with the same importSessionID

Import of an external key

Sometimes a key exist outside the TSM and need to be imported into the TSM. This can be done both with and without session ID and the below example is done without session ID for simplicity, but could easily be modified to using session ID by adapting the changes in the with session ID section.

// Error checking have been omitted for simplicity
// Input:
//   client
//   n, t - Number of MPC nodes and security threshold in the TSM 
//   curveName, privateKey, publicKey (ASN.1 DER encoded)
ecdsaClient := tsm.NewECDSAClient(client)

keyShares, _ := tsm.ECDSASecretShare(n, t, curveName, privateKey)

publicKeys, _, _ := ecdsaClient.WrappingKeys()
wrappedKeyShares := wrapKeyShares(publicKeys, keyShares)

// Wrapped chain codes can be encrypted like the key shares
ecdsaClient.ImportWrappedKeyShares(curveName, wrappedKeyShares, nil, publicKey, "")

The code uses the following two utility methods:

// Error checking have been omitted for simplicity
func wrapKeyShares(keys [][]byte, blobs [][]byte) [][]byte {
    length := len(keys)

    encryptions := make([][]byte, length)
    for i := 0; i < length; i++ {
         encryptions[i] = oaepEncrypt(keys[i], blobs[i])

    return encryptions

func oaepEncrypt(key []byte, data []byte) []byte {
    pkInterface, _ := x509.ParsePKIXPublicKey(key)
    pk, _ := pkInterface.(*rsa.PublicKey)

    cipherText, _ := rsa.EncryptOAEP(crypto.SHA256.New(), rand.Reader, pk, data, nil)

    return cipherText

Encoding of public key

The (DER) public key supplied to the import call needs to be a ASN.1 DER encoded SubjectPublicKeyInfo. This is what is exported from the TSM, but for keys that are imported from an external system it may be required to encode a raw ECC point to an SPKI. A short example showing how to encode a raw secp256k1 ECC point to a SPKI:

// No errors are checked, so add error tests on all err return values.
oidPublicKeyECDSA := asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}

secp256k1OID := asn1.ObjectIdentifier{1, 3, 132, 0, 10}
oidBytes, err := asn1.Marshal(secp256k1OID)

publicKeyBytes, err := hex.DecodeString("045514f5ebcd1850c9e76cd201b385ad81fac5f2a183841cd7adee7c8bfc34bff2c73921fd29d7088bd085df353d65197ec884ab7c168e8cf242c078fcfaac77ba")
publicKeyBitString := asn1.BitString{
	Bytes:     publicKeyBytes,
	BitLength: 8 * len(publicKeyBytes),

var algorithmIdentifier pkix.AlgorithmIdentifier
algorithmIdentifier.Algorithm = oidPublicKeyECDSA
algorithmIdentifier.Parameters.FullBytes = oidBytes

spki := subjectPublicKeyInfo{
	Algorithm: algorithmIdentifier,
	PublicKey: publicKeyBitString,
spkiBytes, err := asn1.Marshal(spki)

spkiStr := hex.EncodeToString(spkiBytes)