Export
Export reconstructs the user's full private key — not just the device's share — and returns it as raw bytes; the example saves it hex-encoded in a plaintext JSON file. Anyone holding that file can re-import the wallet on a fresh device. The host-side keypair the example generates per export only protects the server's share as it travels back to the device — the exported key itself is returned in the clear.
For the SDK contract see Export Key (Kotlin) and Export Key (Swift).
Duo's 3-step protocol
Duo export requires coordination with the server over HTTP:
prepareExport(keyId)— generates a fresh host encryption/decryption keypair locally.- HTTP fetch — the example posts the host public key to the server's
/v3/{algorithm}/exportendpoint and gets back the server's encrypted share + the server's public key. exportKeyshare(...)— the SDK decrypts the server's share with the host keypair, combines it with the device's share, and reconstructs the raw private key.
- Android
- iOS / macOS
vault/.../session/VaultSessionManager.kt
suspend fun prepareExport(keyId: String): Triple<ByteArray, String, String> {
val keyType = readDao(keyId).keyType
val (hostEncryptionKey, hostDecryptionKey) =
keyType.silentShard.generateEncryptionDecryptionKeyPair()
return Triple(hostEncryptionKey, hostDecryptionKey.toHex(), keyType.algorithmName)
}
suspend fun exportKeyshare(
keyId: String,
hostEncryptionKey: ByteArray,
serverEncShare: ByteArray,
serverPublicKey: ByteArray,
): ByteArray {
val dao = readDao(keyId)
return sessionFor(dao.keyType).export(
keyId,
otherEncryptedKeyshare = serverEncShare,
hostEncryptionKey = hostEncryptionKey,
otherDecryptionKey = serverPublicKey
).getOrThrow()
}
Vault/Session/VaultSessionManager.swift
func prepareExport(keyId: String) async throws
-> (hostDecryptionKeyHex: String, keyType: KeyType) {
let keyType = try loadRecord(keyId: keyId).keyType
let (hostEncryptionKey, hostDecryptionKey) =
try SilentShard.ECDSA.generateEncryptionDecryptionKeyPair().get()
// ... store hostEncryptionKey for step 3
return (hostDecryptionKey.toHexString(), keyType)
}
func exportKeyshare(keyId: String, serverEncShare: Data, serverPublicKey: Data) async throws -> Data {
guard let hostEncryptionKey = pendingExports.removeValue(forKey: keyId) else {
throw VaultError.noPendingExport(keyId)
}
let record = try loadRecord(keyId: keyId)
return try await sessionForKeyType(record.keyType).export(
keyId: keyId,
otherEncryptedKeyshare: serverEncShare,
hostEncryptionKey: hostEncryptionKey,
otherDecryptionKey: serverPublicKey
).get()
}
What the exported key becomes
The example wraps each wallet's exported key (hex-encoded) into a JSON entry and writes the result to a user-chosen file:
{ "keyType": "ECDSA", "chain": "ETHEREUM_SEPOLIA", "exportDataHex": "abc123..." }
The vault never persists exports — the file is purely for the user to store.
