DuoSession
Two-party MPC (Multi-Party Computation) session for secure distributed cryptographic operations.
DuoSession enables secure threshold signature schemes between two parties without ever reconstructing the complete private key. Both parties must cooperate to sign, providing enhanced security over single-key solutions. Supports ECDSA and EdDSA with operations for key generation, signing, key management, and backup/recovery.
Getting Started
1. Implement Your Key Provider
The key provider manages your device's secure master signing key. This key is used to authenticate MPC operations and should be stored in hardware-backed secure storage.
// Example: Using platform secure storage
class SecureKeyProvider : MasterSigningKeyProvider {
private val secureStore = getPlatformSecureStorage()
private val keyAlias = "mpc_master_key"
override val verifyingKey: ByteArray
get() = secureStore.getPublicKey(keyAlias)
override val masterSigningKeyType = MasterSigningKeyType.ECDSA
override fun sign(data: ByteArray): ByteArray {
return secureStore.sign(keyAlias, data)
}
}2. Implement Storage Client
The storage client manages keyshare persistence and tracks staged/current keyshare states. It uses ReconcileStoreDao which has three important fields:
keyId: Unique identifier for the keysharestagedKeyshare: Keyshare that completed MPC operation but not yet reconciledcurrentKeyshare: Fully reconciled and active keyshare ready for signing
class MyStorageClient : StorageClient<ReconcileStoreDao> {
private val storage = mutableMapOf<String, ReconcileStoreDao>()
override suspend fun write(dao: ReconcileStoreDao) {
storage[dao.keyId] = dao
}
override suspend fun read(key: String): ReconcileStoreDao? {
return storage[key]
}
}3. Create Session with Demo Server
// Demo server configuration
val storageClient = MyStorageClient()
val session = SilentShard.ECDSA.createDuoSession(
keyProvider = SecureKeyProvider(),
cloudVerifyingKey = "01829099948d222f9731323900bc42bd3cc6f4e692f920705a9959b3e52378f188",
websocketConfig = WebsocketConfig(
url = "wss://demo-server.silencelaboratories.com"
),
storageClient = storageClient
)4. Generate and Use Keyshare
// Generate distributed keyshare (internally reconciled)
val keyshare = session.keygen().getOrThrow()
// Get public key for address derivation
val publicKey = SilentShard.ECDSA.getKeysharePublicKey(keyshare).getOrThrow()
val address = deriveAddress(publicKey)
// Sign transaction
val txHash = hashTransaction(transaction)
val signature = session.signature(
keyshare = keyshare,
message = txHash.toHexString(),
derivationPath = "m/44'/X'/0'/0/0"
).getOrThrow()5. Handle Reconciliation Failures (Advanced)
If reconciliation fails, the keyshare stays in stagedKeyshare field. You can retry:
// Check storage for staged keyshare
val keyId = "your_key_id"
val dao = storageClient.read(keyId)
if (dao?.stagedKeyshare != null && dao.currentKeyshare == null) {
// Reconciliation was incomplete - retry it
println("Found staged keyshare, retrying reconciliation...")
val reconciledKeyshare = session.reconcileKeyshare(keyId).getOrThrow()
println("Reconciliation successful!")
}Thread Safety
All operations are suspend functions requiring coroutine context. Do not call from main/UI thread.
Advanced: Custom Network Transport
Option 1: Custom WebSocket Configuration (Web-Based)
If you need custom WebSocket handling (custom headers, authentication, reconnection logic):
class CustomWebSocketClient(
private val url: String,
private val authToken: String
) : NetworkClient<WebSocketAction> {
private var connection: WebSocket? = null
override suspend fun send(action: WebSocketAction): ByteArray = suspendCoroutine { cont ->
// Custom connection with authentication
connection = WebSocket(url).apply {
setHeader("Authorization", "Bearer $authToken")
setHeader("X-Client-Version", "1.0.0")
onOpen {
// Send MPC data
send(action.payload)
}
onMessage { data ->
// Receive MPC response
cont.resume(data)
}
onError { error ->
cont.resumeWithException(error)
}
}
connection?.connect()
}
override suspend fun disconnect() {
connection?.close()
}
}
// Use custom WebSocket client
val session = SilentShard.ECDSA.createDuoSession(
keyProvider = keyProvider,
cloudVerifyingKey = serverPublicKey,
networkClient = CustomWebSocketClient(
url = "wss://your-custom-server.com/mpc",
authToken = getUserAuthToken()
),
storageClient = storage,
actionProvider = CustomWebSocketActionProvider()
)Option 2: BLE Hardware Wallet (Non-Web)
For specialized hardware scenarios like Bluetooth Low Energy:
// BLE hardware wallet transport
class BLEDuoClient(private val device: BluetoothDevice) : NetworkClient<BLEAction> {
override suspend fun send(action: BLEAction): ByteArray {
val characteristic = device.getCharacteristic(action.uuid)
characteristic.writeValue(action.payload)
return characteristic.readValue()
}
}
val session = SilentShard.ECDSA.createDuoSession(
keyProvider = keyProvider,
cloudVerifyingKey = hardwareWalletPublicKey,
networkClient = BLEDuoClient(bleDevice),
storageClient = storage,
actionProvider = BLEDuoActionProvider()
)See also
for keyshare persistence
for understanding staged vs current keyshares