Export Key
Reconstruct and export the full private key of the MPC wallet by combining both shares. Use this to move a wallet out of MPC, for example into another wallet ecosystem.
Once the private key is exported, the MPC security guarantees are lost.
Export is not exposed directly in the client SDK. Instead, you make a POST request to the cloud node's export endpoint to retrieve the server's encrypted share, then use the SDK's export method to combine both shares and recover the full private key.
It's your responsibility to expose this endpoint securely. For example:
- When a user requests export, your backend authenticates them (2FA, etc.).
- Your backend calls the cloud node's export endpoint and returns the encrypted share to the client.
- The client uses the SDK's export method to decrypt and combine the shares.
Never expose the export endpoint directly to users.
- ECDSA
- EdDSA
import { ecdsaKeyExport, type EcdsaSession } from '@silencelaboratories/silent-shard-sdk/ecdsa';
import * as duo from '@silencelaboratories/dkls-sdk';
import { Platform } from 'react-native';
const CLOUD_NODE_URI = Platform.select({ android: '10.0.2.2', ios: 'localhost' }) + ':8080';
export const exportKey = async (session: EcdsaSession) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
console.log('Keyshare: ', keyshare.keyIdHex);
// Export
const encryptionKey = await duo.EncryptionKey.create();
const exportRes = await fetch(`http://${CLOUD_NODE_URI}/v3/ecdsa/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
key_id: keyshare.keyIdHex, // Use Hex version of keyId for server request
client_enc_pubkey: encryptionKey.publicKeyHex,
}),
});
const exportJson = await exportRes.json();
const exportConfig = {
keyshare: keyshare,
client_secret_key: encryptionKey.privateKeyB64,
server_public_key: exportJson.server_public_key,
encrypted_server_share: exportJson.enc_server_share,
};
const privateKeyHex = await ecdsaKeyExport(exportConfig);
console.log('Exported private key: ', privateKeyHex);
};
import { eddsaKeyExport, type EddsaSession } from '@silencelaboratories/silent-shard-sdk/eddsa';
import { EncryptionKey } from '@silencelaboratories/schnorr-sdk';
import { Platform } from 'react-native';
const CLOUD_NODE_URI = Platform.select({ android: '10.0.2.2', ios: 'localhost' }) + ':8080';
export const exportKey = async (session: EddsaSession) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
console.log('Keyshare: ', keyshare.publicKeyHex);
// Export
const encryptionKey = await EncryptionKey.create();
const exportRes = await fetch(`http://${CLOUD_NODE_URI}/v3/eddsa/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
key_id: keyshare.keyIdHex,
client_enc_pubkey: encryptionKey.publicKeyHex,
}),
});
const exportJson = await exportRes.json();
const exportConfig = {
keyshare: keyshare,
client_encryption_key: encryptionKey,
server_public_key: exportJson.server_public_key,
encrypted_server_share: exportJson.enc_server_share,
};
const privateKeyHex = await eddsaKeyExport(exportConfig);
console.log('Exported private key: ', privateKeyHex);
};
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted share. Make a POST request to the export endpoint (/v3/ecdsa/export or /v3/eddsa/export) with:
key_id: the keyshare's key ID (hex string, from the Keyshare'skeyIdHex)client_enc_pubkey: an encryption public key generated viaEncryptionKey.create()
Step 2: Combine shares. Pass the response and your keyshare to ecdsaKeyExport(), which takes an EcdsaExportConfig with:
keyshare: your client share (a Keyshare)client_secret_key: the private key from your encryption key pairserver_public_key: from the export endpoint responseencrypted_server_share: from the export endpoint response
Returns privateKeyHex: the full private key in hex format.
- ECDSA
- EdDSA
- Taproot
import 'dart:convert';
import 'dart:io';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<String> exportKey(sdk.EcdsaSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.DklsKeyshare keyshare = await session.keygen();
print('Keyshare created, public key: ${keyshare.publicKeyHex}');
// Export
final clientEncKeys = sdk.DklsEncryptionKey();
final exportUrl = 'http://$nodeUri/v3/ecdsa/export';
final response = await http.post(Uri.parse(exportUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
"key_id": keyshare.keyIdHex,
"client_enc_pubkey": clientEncKeys.publicKeyHex,
}));
print('Response: ${response.body}');
final exportResponse = ExportResponse.fromJson(jsonDecode(response.body));
print('Export response: ${exportResponse.encServerShare} ${exportResponse.serverPublicKey} ${exportResponse.keyId} ${clientEncKeys.publicKeyHex}');
final privateKey = sdk.ecdsaDuoKeyExport(
keyshare: keyshare,
serverEncryptedKeyshare: exportResponse.encServerShare,
serverEncryptionPublicKey: exportResponse.serverPublicKey,
clientEncryptionKeys: clientEncKeys,
);
print('Exported private key: $privateKey');
return privateKey;
}
class ExportResponse {
final String serverPublicKey;
final String encServerShare;
final String keyId;
ExportResponse.fromJson(Map<String, dynamic> json)
: keyId = json['key_id'],
encServerShare = json['enc_server_share'],
serverPublicKey = json['server_public_key'];
}
import 'dart:convert';
import 'dart:io';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<void> exportKey(sdk.EddsaSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final keyshare = await session.keygen();
// Export
final clientEncKeys = sdk.SchnorrEncryptionKey.create();
final exportUrl = 'http://$nodeUri/v3/eddsa/export';
final response = await http.post(Uri.parse(exportUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
"key_id": keyshare.keyIdHex,
"client_enc_pubkey": clientEncKeys.publicKeyHex,
}));
final exportResponse = ExportResponse.fromJson(jsonDecode(response.body));
final privateKey = sdk.schnorrDuoKeyExport(
keyshare: keyshare,
serverEncryptedKeyshare: exportResponse.encServerShare,
serverEncryptionPublicKey: exportResponse.serverPublicKey,
clientEncryptionKeys: clientEncKeys,
);
print('Exported private key: $privateKey');
}
class ExportResponse {
final String serverPublicKey;
final String encServerShare;
final String keyId;
ExportResponse.fromJson(Map<String, dynamic> json)
: keyId = json['key_id'],
encServerShare = json['enc_server_share'],
serverPublicKey = json['server_public_key'];
}
import 'dart:convert';
import 'dart:io';
import 'package:convert/convert.dart';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<String> exportKey(sdk.TaprootSession session) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.TaprootKeyshare keyshare = await session.keygen();
print('Keyshare created, public key: ${keyshare.publicKeyHex}');
// Generate a client encryption keypair for the secure exchange.
final clientEncKeys = sdk.TaprootEncryptionKey();
final exportUrl = 'http://$nodeUri/v3/taproot/export';
final response = await http.post(Uri.parse(exportUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'key_id': keyshare.keyIdHex,
'client_enc_pubkey': clientEncKeys.publicKeyHex,
}));
final body = jsonDecode(response.body) as Map<String, dynamic>;
final encServerShare = hex.decode(body['enc_server_share'] as String);
final serverPublicKey = hex.decode(body['server_public_key'] as String);
final privateKeyBytes = await session.export(
keyId: keyshare.keyId,
serverEncryptedKeyshare: encServerShare,
serverPublicKey: serverPublicKey,
clientEncryptionKey: clientEncKeys,
);
final privateKey = hex.encode(privateKeyBytes);
print('Exported private key: $privateKey');
clientEncKeys.free();
return privateKey;
}
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted share. Make a POST request to the export endpoint (/v3/export) with:
key_id: the keyshare's key ID (base64 string)client_enc_pubkey: an encryption public key (base64 string)
Step 2: Combine shares. Call ecdsaDuoKeyExport with:
serverEncrptedKeyshare: from the export endpoint responseserverEncryptionPublicKey: from the export endpoint responseclientEncryptionKeys: your generated DklsEncryptionKey
Returns the full private key in hex format.
- ECDSA
- EdDSA
suspend fun exportPrivateKey(keyId: String, duoSession: DuoSession): ByteArray {
return withContext(Dispatchers.IO) {
//Generate Encryption KeyPair to export private-key encrypted with this pair's private-key.
//It is a pair. The First item of the pair is the private key, and the second element is public key.
val hostEncryptionKeyPair = SilentShard.ECDSA.generateEncryptionDecryptionKeyPair()
//Get Other party data from network
val exportResponse : ExportResponse = getExportData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/ecdsa/export")
},
keyId = keyId,
publicKey = hostEncryptionKeyPair.second
)
val serverEncryptedKeyshare = exportResponse.encServerShare.hexToByteArray()
val serverPublicKey = exportResponse.serverPublicKey.hexToByteArray()
//Perform Private-Key export
duoSession.export(
keyId = keyId,
otherEncryptedKeyshare = serverEncryptedKeyshare,
hostEncryptionKey = hostEncryptionKeyPair.first,
otherDecryptionKey = serverPublicKey
).getOrThrow()
}
}
private fun getExportData(url: String, keyId: String, publicKey: ByteArray) = ExportRequest(
keyId = keyId, clientEncPubKey = publicKey.toHexString()
).let {
json.encodeToString(it)
}.let { request ->
sendPostRequest(
url, null, request
).decodeToString().let { response ->
json.decodeFromString<ExportResponse>(response)
}
}
@Serializable
private data class ExportRequest(
@SerialName("key_id") val keyId: String,
@SerialName("client_enc_pubkey") val clientEncPubKey: String,
)
@Serializable
private data class ExportResponse(
@SerialName("key_id") val keyId: String,
@SerialName("server_public_key") val serverPublicKey: String,
@SerialName("enc_server_share") val encServerShare: String,
)
private fun sendPostRequest(url: String, authHeader: String?, body: Any): ByteArray {
return runBlocking {
val response: HttpResponse = client.post(url) {
headers {
authHeader?.let {
header(HttpHeaders.Authorization, "Bearer $it")
}
}
when (body) {
is String -> {
contentType(ContentType.Application.Json)
}
is ByteArray -> {
contentType(ContentType.Application.OctetStream)
}
}
setBody(body)
}
response.readRawBytes()
}
}
suspend fun exportPrivateKey(keyId: String, duoSession: DuoSession): ByteArray {
return withContext(Dispatchers.IO) {
//Generate Encryption KeyPair to export private-key encrypted with this pair's private-key.
//It is a pair. The First item of the pair is the private key, and the second element is public key.
val hostEncryptionKeyPair = SilentShard.EdDSA.generateEncryptionDecryptionKeyPair()
//Get Other party data from network
val exportResponse : ExportResponse = getExportData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/eddsa/export")
},
keyId = keyId,
publicKey = hostEncryptionKeyPair.second
)
val serverEncryptedKeyshare = exportResponse.encServerShare.hexToByteArray()
val serverPublicKey = exportResponse.serverPublicKey.hexToByteArray()
//Perform Private-Key export
duoSession.export(
keyId = keyId,
otherEncryptedKeyshare = serverEncryptedKeyshare,
hostEncryptionKey = hostEncryptionKeyPair.first,
otherDecryptionKey = serverPublicKey
).getOrThrow()
}
}
private fun getExportData(url: String, keyId: String, publicKey: ByteArray) = ExportRequest(
keyId = keyId, clientEncPubKey = publicKey.toHexString()
).let {
json.encodeToString(it)
}.let { request ->
sendPostRequest(
url, null, request
).decodeToString().let { response ->
json.decodeFromString<ExportResponse>(response)
}
}
@Serializable
private data class ExportRequest(
@SerialName("key_id") val keyId: String,
@SerialName("client_enc_pubkey") val clientEncPubKey: String,
)
@Serializable
private data class ExportResponse(
@SerialName("key_id") val keyId: String,
@SerialName("server_public_key") val serverPublicKey: String,
@SerialName("enc_server_share") val encServerShare: String,
)
private fun sendPostRequest(url: String, authHeader: String?, body: Any): ByteArray {
return runBlocking {
val response: HttpResponse = client.post(url) {
headers {
authHeader?.let {
header(HttpHeaders.Authorization, "Bearer $it")
}
}
when (body) {
is String -> {
contentType(ContentType.Application.Json)
}
is ByteArray -> {
contentType(ContentType.Application.OctetStream)
}
}
setBody(body)
}
response.readRawBytes()
}
}
Please refer to the Session creation guide to learn how to create a session.
Step 1: Generate an encryption key pair. Use SilentShard.ECDSA.generateEncryptionDecryptionKeyPair() (or SilentShard.EdDSA.generateEncryptionDecryptionKeyPair()), which returns a Pair<ByteArray, ByteArray>: the first is the private key, the second is the public key.
Step 2: Fetch the server's encrypted share. Make a POST request to the export endpoint (/v3/ecdsa/export or /v3/eddsa/export) with the key ID (from SilentShard.ECDSA.getKeyshareKeyId() or SilentShard.EdDSA.getKeyshareKeyId()) and your public key.
Step 3: Combine shares. Call duoSession.export() with hostKeyshare, otherEncryptedKeyshare, hostEncryptionKey, and otherDecryptionKey.
The result is a Result type: Success with a ByteArray (the full private key), or Failure with an Exception.
- ECDSA
- EdDSA
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()
}
}
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.EdDSA.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/eddsa/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()
}
}
Please refer to the Session creation guide to learn how to create a session.
Step 1: Generate an encryption key pair. Use SilentShard.ECDSA.generateEncryptionDecryptionKeyPair() (or SilentShard.EdDSA.generateEncryptionDecryptionKeyPair()), which returns a Result with a tuple of (Data, Data): private key and public key.
Step 2: Fetch the server's encrypted share. Make a POST request to the export endpoint (/v3/ecdsa/export or /v3/eddsa/export) with the key ID (from SilentShard.ECDSA.getKeyshareKeyId() or SilentShard.EdDSA.getKeyshareKeyId()) and your public key.
Step 3: Combine shares. Call duoSession.export() with hostKeyshare, otherEncryptedKeyshare, hostEncryptionKey, and otherDecryptionKey.
The result is a Result type: Success with Data (the full private key), or Failure with an error.