Export
Export produces an encrypted blob containing the user's full reconstructed private key — not just the device's share. Anyone holding the matching decryption key can re-import the wallet on a fresh device. The blob is encrypted to a host-side keypair that the example generates per export.
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 combines both encrypted shares into a single export blob.
- 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)
val keyshare = dao.currentKeyshare ?: throw Exception("No active keyshare")
return sessionFor(dao.keyType).export(
hostKeyshare = keyshare,
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 {
let record = try loadRecord(keyId: keyId)
let keyshare = try requireActiveKeyshare(record)
return try await sessionForKeyType(record.keyType).export(
hostKeyshare: keyshare,
otherEncryptedKeyshare: serverEncShare,
hostEncryptionKey: pendingExports[keyId]!,
otherDecryptionKey: serverPublicKey
).get()
}
What the export blob becomes
The example wraps each wallet's export blob 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.