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
- 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() or SilentShard.EdDSA.generateEncryptionDecryptionKeyPair() which returns
ResultofSuccesswithPairofByteArray(private-public) orFailurewith exception.// In case of ECDSA
val privatePublicKeyPair = SilentShard.ECDSA.generateEncryptionDecryptionKeyPair()
// In case of EdDSA
val privatePublicKeyPair = SilentShard.EdDSA.generateEncryptionDecryptionKeyPair()
// Private Key
privatePublicKeyPair.first
// Public Key
privatePublicKeyPair.second
Step 3 : Perform network request to the server
- Get the encrypted share of the cloud node by making a POST request to the
/v3/ecdsa/export or /v3/eddsa/exportendpoint with :
-
key_id: You can get key_id bytes by calling SilentShard.ECDSA.getKeyshareKeyId() or SilentShard.EdDSA.getKeyshareKeyId() :SilentShard.ECDSA.getKeyshareKeyId(keyshare = keyshare)
//or
SilentShard.EdDSA.getKeyshareKeyId(keyshare = 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: ByteArray: Keyshare to export.otherEncryptedKeyshare: ByteArray: We parse the response from the network request we do in step 3 and get the server's encrypted keyshare convert it toByteArrayexportResponse.encServerShare.hexToByteArray()hostEncryptionKey: ByteArray: first element from the pair generated instep 2otherDecryptionKey: ByteArray: 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 toByteArrayexportResponse.serverPublicKey.hexToByteArray()
Step 5 : Perform Export
- Call duoSession.export() with all the required params (as explained above and in the following example) which returns
ResultofSuccesswith KeyshareByteArrayorFailurewith exception.
Example
- ECDSA
- EdDSA
suspend fun exportPrivateKey(keyshare: ByteArray, 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 = getExportData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/${SilentShard.ECDSA.tssImplementation}/export")
},
keyId = SilentShard.ECDSA.getKeyshareKeyId(
keyshare = keyshare).getOrThrow(),
publicKey = hostEncryptionKeyPair.getOrThrow().second
)
val serverEncryptedKeyshare = exportResponse.encServerShare.hexToByteArray()
val serverPublicKey = exportResponse.serverPublicKey.hexToByteArray()
//Perform Private-Key export
duoSession.export(
hostKeyshare = keyshare,
otherEncryptedKeyshare = serverEncryptedKeyshare,
hostEncryptionKey = hostEncryptionKeyPair.getOrThrow().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(keyshare: ByteArray, 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 = getExportData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/${SilentShard.EdDSA.tssImplementation}/export")
},
keyId = SilentShard.EdDSA.getKeyshareKeyId(
keyshare = keyshare).getOrThrow(),
publicKey = hostEncryptionKeyPair.getOrThrow().second
)
val serverEncryptedKeyshare = exportResponse.encServerShare.hexToByteArray()
val serverPublicKey = exportResponse.serverPublicKey.hexToByteArray()
//Perform Private-Key export
duoSession.export(
hostKeyshare = keyshare,
otherEncryptedKeyshare = serverEncryptedKeyshare,
hostEncryptionKey = hostEncryptionKeyPair.getOrThrow().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()
}
}
- The request body format is documented in the /v3/ecdsa/export or /v3/eddsa/export 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 /v3/ecdsa/export or /v3/eddsa/export endpoint.ExportResponse: The response from the /v3/ecdsa/export or /v3/eddsa/export 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 the/v3/ecdsa/export or /v3/eddsa/exportendpoint)otherEncryptedKeyshare: The encrypted server share. (This is included in the response from the/v3/ecdsa/export or /v3/eddsa/exportendpoint)