Skip to main content

Export Key


Users can at any time request to "export" their keys into a private key (EOA) and stop using an MPC wallet.

Once the private key is exported, the guarantees of MPC are lost.

Since this operation is a sensitive operations, we don't expose it directly in the SDK. Instead we provide an endpoint in the cloud node /v3/ecdsa/export or /v3/eddsa/export that can be used to get the encrypted share of the cloud node. We provide a method in the client SDK to combine the client and server shares to recover the full private key.

It's the responsibility of the company using the SDK to expose this endpoint to the user in a secure way.

For e.g:

  • When the user requests to export the private key. The backend can request additional authentication from the user (e.g: 2FA) before exporting the private key.
  • Once the user is authenticated, the backend can get the encrypted share from the cloud node and pass it to the user.
  • The user can decrypt the share and recover the full private key using duoSession.export().

Get the encrypted share of the cloud node by making a POST request to the /v3/ecdsa/export or /v3/eddsa/export endpoint.

The /v3/ecdsa/export or /v3/eddsa/export endpoint is not exposed directly in the SDK. Here for the sake of simplicity, we will use the getExportData method to make the request from the client. Do NOT expose this endpoint to the user directly in a production application.


Step 1 : Create Session


Step 2 : Generate Encryption key-pair


  • Generate Encryption/Decryption key-pair to export private-key encrypted with generated key-pair by calling SilentShard.ECDSA.generateEncryptionDecryptionKeyPair() or SilentShard.EdDSA.generateEncryptionDecryptionKeyPair() which returns Result of Success with Tuple of (Data,Data) (private-public) or Failure with error.

    // generate the keyshare encryption key-pair
    //ECDSA
    let hostPrivateKeyPublicKeyPair = await SilentShard.ECDSA.generateEncryptionDecryptionKeyPair()
    // or EdDSA
    let hostPrivateKeyPublicKeyPair = await SilentShard.EdDSA.generateEncryptionDecryptionKeyPair()

    switch hostPrivateKeyPublicKeyPair {
    case .success(let privateKeyPublicKeyPair):
    do {
    // do something with hostPrivateKeyPublicKeyPair bytes-bytes

    // private-key
    Swift.print(privateKeyPublicKeyPair.0)
    // public-key
    Swift.print(privateKeyPublicKeyPair.1)
    }
    case .failure(let error):
    do {
    // show error to user or abort process
    Swift.print(error)
    return nil
    }
    }

Step 3 : Perform network request to the server


  1. key_id : The keyId (a String) returned by keygen/import that addresses this keyshare.

  2. public_key : second element from the pair generated in step 2

Step 4 : Collect all the parameters


We need four things to perform export. Now, let's collect them all.

  1. keyId: String : The keyId addressing the keyshare to export (from keygen/import).
  2. otherEncryptedKeyshare: Data : We parse the response from the network request we do in step 3 and get the server's encrypted keyshare(base64 encoded) convert it to Data
    let encryptedServerKeyshare = Data(
    hex: exportRequestResponse!.encServerShare
    )
  3. hostEncryptionKey: Data : first element from the pair generated in step 2
  4. otherDecryptionKey: Data : We parse the response from the network request we do in step 3 and get the server's public key to decrypt the server's encrypted keyshare and then convert it to Data
    let serverKeyshareDecryptionPublicKey = Data(hex: exportRequestResponse!.serverPublicKey)

Step 5 : Perform Export


  • Call duoSession.export() with all the required params (as explained above and in the following example) which returns a Result of Success with the reconstructed private key bytes as Data or Failure with error.

Example


Example.swift
private func performKeyExport(keyId: String, duoSession: DuoSession) async -> Data? {

// MARK: generate the keyshare encryption key-pair
// The pair is (hostEncryptionKey, hostPublicKey): keep the first to decrypt
// the export, and hand the second to the peer so it can encrypt its share.
let encryptionKeyPairResult = await SilentShard.ECDSA.generateEncryptionDecryptionKeyPair()
let encryptionKeyPair: (Data, Data)

switch encryptionKeyPairResult {
case .success(let pair):
encryptionKeyPair = pair
// private-key (hostEncryptionKey)
Swift.print(encryptionKeyPair.0)
// public-key (share this with the peer)
Swift.print(encryptionKeyPair.1)
case .failure(let error):
// show error to user or abort process
Swift.print(error)
return nil
}

// MARK: make export network request : get the server's encrypted keyshare
// Address the key by its keyId (from keygen/import) — no keyshare bytes here.
let exportRequestResponse = try? await getExportData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/ecdsa/export",
keyId: keyId,
publicKey: encryptionKeyPair.1
)

// MARK: perform keyshare export with all the params we collected so far
let exportResult = await duoSession.export(
keyId: keyId,
otherEncryptedKeyshare: Data(hex: exportRequestResponse!.encServerShare),
hostEncryptionKey: encryptionKeyPair.0,
otherDecryptionKey: Data(hex: exportRequestResponse!.serverPublicKey)
)

// returns nil if operation fails
switch exportResult {
case .success(let privateKeyBytes):
// do something with the reconstructed raw private-key bytes
Swift.print(privateKeyBytes)
return privateKeyBytes
case .failure(let error):
// show error to user or abort process
Swift.print(error)
return nil
}
}

/*
Network request to get the server's encrypted keyshare.*/
private func getExportData(url: String, keyId: String, publicKey: Data)
async throws -> ExportResponse
{
let request = ExportRequest(
keyId: keyId,
clientEncPubkey: publicKey.hexString()
)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let requestData = try encoder.encode(request)
let response = try await sendPostRequest(
url: URL(string: url)!,
body: requestData
)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(ExportResponse.self, from: response)
}

/*
Network request params*/
struct ExportRequest: Codable {
let keyId: String
let clientEncPubkey: String
}

/*
Network response*/
struct ExportResponse: Codable {
let keyId: String
let serverPublicKey: String
let encServerShare: String
}

/*
Perform network request to the server*/
private func sendPostRequest(url: URL, body: Data) async throws -> Data
{
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body
let (data, _) = try await URLSession.shared.data(for: request)
return data
}

extension Data {
init?(hex: String) {
let hex = hex.dropFirst(hex.hasPrefix("0x") ? 2 : 0)

guard hex.count % 2 == 0 else { return nil }

self.init(capacity: hex.count / 2)

var index = hex.startIndex
while index < hex.endIndex {
let nextIndex = hex.index(index, offsetBy: 2)
guard let byte = UInt8(hex[index..<nextIndex], radix: 16) else {
return nil
}
self.append(byte)
index = nextIndex
}
}

func hexString() -> String {
return self.map { String(format: "%02x", $0) }.joined()
}
}

  • keyId: Addresses the client's keyshare in your storage client (from keygen/import).
  • hostEncryptionKey: The client's encryption private key.
  • otherDecryptionKey: The server's encryption public key. (This is included in the response from the /v3/ecdsa/export or /v3/eddsa/export endpoint)
  • otherEncryptedKeyshare: The encrypted server share. (This is included in the response from the /v3/ecdsa/export or /v3/eddsa/export endpoint)