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/export-key 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/export-key endpoint.
The /v3/export-key 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
- Create DuoSession if you haven't already.
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()
orSilentShard.EdDSA.generateEncryptionDecryptionKeyPair()
which returnsResult
ofSuccess
withTuple
of(Data,Data)
(private-public) orFailure
witherror
.// generate the keyshare encryption key-pair
//ECDSA
let hostPrivateKeyPublicKeyPair = await SilentShardDuo.ECDSA.generateEncryptionDecryptionKeyPair()
// or EdDSA
let hostPrivateKeyPublicKeyPair = await SilentShardDuo.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
- Get the encrypted share of the cloud node by making a POST request to the
/v3/export-key
endpoint with :
-
key_id
: You can get UrlSafeBase64 encoded key_id string by using :let keyshareKeyIdResult = await SilentShardDuo.ECDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare)
//or
let keyshareKeyIdResult = await SilentShardDuo.EdDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare) -
public_key
: second element from the pair generated instep 2
Step 4 : Collect all the parameters
We need four things to perform export. Now, let's collect them all.
hostKeyshare: Data
: Keyshare to export.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 toData
let encryptedServerKeyshare = Data(base64Encoded: exportRequestResponse.encServerShare)
hostEncryptionKey: Data
: first element from the pair generated instep 2
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 toData
let serverKeyshareDecryptionPublicKey = Data(exportRequestResponse.serverPublicKey)
Step 5 : Perform Export
- Call
duoSession.export()
with all the required params (as explained above and in the following example) which returnsResult
ofSuccess
with Keyshare bytes asData
orFailure
witherror
.
Example
- ECDSA
- EdDSA
private func performKeyExport(keyshare: Data, duoSession: DuoSession) async -> Data?
{
// MARK: generate the keyshare encryption key-pair
let encryptionKeyPairResult = await SilentShardDuo.ECDSA.generateEncryptionDecryptionKeyPair()
let encryptionKeyPair : (Data,Data)
switch encryptionKeyPairResult {
case .success(let encryptionKeyPairSuccess):
do {
// do something with encryptionKeyPair bytes-bytes
encryptionKeyPair = encryptionKeyPairSuccess
// private-key
Swift.print(encryptionKeyPair.0)
// public-key
Swift.print(encryptionKeyPair.1)
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return nil
}
}
// MARK: get key-id for keyshare to export
let keyshareKeyIdResult = await SilentShardDuo.ECDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare)
let keyshareKeyId : String
switch keyshareKeyIdResult {
case .success(let keyshareKeyIdSuccess):
do {
// do something with key-id string
keyshareKeyId = keyshareKeyIdSuccess
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return nil
}
}
// MARK: make export network request : get the server's encrypted keyshare
let exportRequestResponse = try? await getExportData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/export-key",
keyId: keyshareKeyId,
publicKey: encryptionKeyPair.1
)
// MARK: perform keyshare export will all the params we collected so far
let exportResult = await duoSession.export(
hostKeyshare: keyshare,
otherEncryptedKeyshare: Data(
base64Encoded: exportRequestResponse!.encServerShare)!,
hostEncryptionKey: encryptionKeyPair.0,
otherDecryptionKey: Data(exportRequestResponse!.serverPublicKey))
// returns nil if operation fails
switch exportResult {
case .success(let dataBytes):
do {
// do something with exported (encrypted) keyshare bytes
Swift.print(dataBytes)
return dataBytes
}
case .failure(let error):
do {
// 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 clientEncPublicKey = publicKey.base64EncodedString()
print(clientEncPublicKey)
let request = ExportRequest(
keyId: keyId,
clientEncPubkey: clientEncPublicKey
)
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: [UInt8]
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
}
private func performKeyExport(keyshare: Data, duoSession: DuoSession) async -> Data?
{
// MARK: generate the keyshare encryption key-pair
let encryptionKeyPairResult = await SilentShardDuo.EdDSA.generateEncryptionDecryptionKeyPair()
let encryptionKeyPair : (Data,Data)
switch encryptionKeyPairResult {
case .success(let encryptionKeyPairSuccess):
do {
// do something with encryptionKeyPair bytes-bytes
encryptionKeyPair = encryptionKeyPairSuccess
// private-key
Swift.print(encryptionKeyPair.0)
// public-key
Swift.print(encryptionKeyPair.1)
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return nil
}
}
// MARK: get key-id for keyshare to export
let keyshareKeyIdResult = await SilentShardDuo.EdDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare)
let keyshareKeyId : String
switch keyshareKeyIdResult {
case .success(let keyshareKeyIdSuccess):
do {
// do something with key-id string
keyshareKeyId = keyshareKeyIdSuccess
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return nil
}
}
// MARK: make export network request : get the server's encrypted keyshare
let exportRequestResponse = try? await getExportData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/export-key",
keyId: keyshareKeyId,
publicKey: encryptionKeyPair.1
)
// MARK: perform keyshare export will all the params we collected so far
let exportResult = await duoSession.export(
hostKeyshare: keyshare,
otherEncryptedKeyshare: Data(
base64Encoded: exportRequestResponse!.encServerShare)!,
hostEncryptionKey: encryptionKeyPair.0,
otherDecryptionKey: Data(exportRequestResponse!.serverPublicKey))
// returns nil if operation fails
switch exportResult {
case .success(let dataBytes):
do {
// do something with exported (encrypted) keyshare bytes
Swift.print(dataBytes)
return dataBytes
}
case .failure(let error):
do {
// 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 clientEncPublicKey = publicKey.base64EncodedString()
print(clientEncPublicKey)
let request = ExportRequest(
keyId: keyId,
clientEncPubkey: clientEncPublicKey
)
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: [UInt8]
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
}
- The request body format is documented in the export-key endpoint.
key_id
: The key ID of the keyshare. Unique identifier for a wallet, 32 byte base64Url encoded string.client_enc_pubkey
: The encryption public key of the client. Base64 encoded string.ExportRequest
: The request params for the export-key endpoint.ExportResponse
: The response from the export-key endpoint. It contains the encrypted share of the cloud node.
hostKeyshare
: The client's share of the MPC wallet.hostEncryptionKey
: The client's encryption private key.otherDecryptionKey
: The server's encryption public key. (This is included in the response from theexport-key
endpoint)otherEncryptedKeyshare
: The encrypted server share. (This is included in the response from theexport-key
endpoint)