TrioSession
Three-party MPC (Multi-Party Computation) session for secure distributed cryptographic operations.
TrioSession enables secure threshold signature schemes between three parties, ensuring that no single party ever reconstructs the complete private key. All parties must cooperate to sign, providing enhanced security over single-key or two-party solutions. Supports key generation, signing, key management, backup/recovery, and advanced pre-signature flows for high-throughput use cases.
Key Advantages Over Two-Party (Duo)
Pre-Signatures: Generate signatures in advance for instant transaction signing (~50ms)
Recovery: Recover lost keyshares using other two parties (2-of-3 threshold)
Enhanced Security: No single point of failure - requires 2 of 3 parties
Flexibility: One party can be temporarily offline
Storage and Key Provider
StorageClient: Stores keyshares and tracks reconciliation state (staged → current).
KeyProvider: Provides secure access to your master signing key. Private key must stay in hardware (Secure Enclave, Android Keystore, HSM). Never load it into app memory.
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, secure enclave, or similar robust mechanism.
class MyKeyProvider : MasterSigningKeyProvider {
// ...implement your secure key storage and signing logic...
// This could use hardware security module, secure enclave, or platform secure storage
override val verifyingKey: ByteArray = getPublicKeyFromSecureStorage()
override val masterSigningKeyType = MasterSigningKeyType.ECDSA
override fun sign(data: ByteArray): ByteArray = signWithSecureKey(data)
}2. Implement Storage Client
The storage client manages keyshare persistence and tracks staged/current keyshare states. It uses ReconcileStoreDao with fields for keyId, stagedKeyshare, and currentKeyshare. This can be implemented using secure DB, hardware module, or platform secure storage.
class MyStorageClient : StorageClient<ReconcileStoreDao> {
// ...implement your storage logic...
// This could use secure database, hardware-backed storage, or platform secure storage
private val storage = mutableMapOf<String, ReconcileStoreDao>()
override suspend fun write(dao: ReconcileStoreDao) { storage[dao.keyId] = dao }
override suspend fun read(key: String): ReconcileStoreDao? = storage[key]
}3. Create Session with Demo Server
val session = SilentShard.ECDSA.createTrioSession(
keyProvider = MyKeyProvider(),
cloudVerifyingKey = "019c4c79e942bbc3ff1d6ace7256404d701498056978cc4638c35832acdf821b1e",
websocketConfig = WebsocketConfig(url = "wss://trio-server.demo.silencelaboratories.com"),
storageClient = MyStorageClient()
)4. Generate and Use Keyshare
// Generate distributed keyshare (internally reconciled)
val keyshare = session.keygen().getOrThrow()
// Get public key for address derivation
val publicKey = getKeysharePublicKey(keyshare)
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)
Keygen internally performs reconciliation automatically. If keygen completes but the internal reconciliation step fails (due to network issues or crashes), the keyshare stays in stagedKeyshare field. You can retry reconciliation manually:
// 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!")
}Keyshare Lifecycle
stagedKeyshare: Keyshare that completed MPC operation but not yet reconciledcurrentKeyshare: Fully reconciled and active keyshare ready for signing
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.createTrioSession(
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 BLETrioClient(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.createTrioSession(
keyProvider = keyProvider,
cloudVerifyingKey = hardwareWalletPublicKey,
networkClient = BLETrioClient(bleDevice),
storageClient = storage,
actionProvider = BLETrioActionProvider()
)See also
for keyshare persistence
for understanding staged vs current keyshares
Properties
Functions
Refreshes an existing keyshare without changing its public key.
Generates a pre-signature for instant signing later with preSignatureFinal.
Reconciles a staged keyshare to complete the after-part and make it active for signing.