TrioSession

interface 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 reconciled

  • currentKeyshare: 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

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
abstract val verifyingKey: String

The public verifying key of this party's master signing key (hex format). Share with other parties to establish secure communication.

Functions

Link copied to clipboard
abstract suspend fun export(keyshare: ByteArray): Result<ByteArray>

Exports keyshare data for secure backup.

Link copied to clipboard
abstract suspend fun import(keysharePrivateKey: ByteArray): Result<ByteArray>

Imports a keyshare from backup data or existing private key.

Link copied to clipboard
abstract suspend fun keygen(): Result<ByteArray>

Generates a new distributed keyshare through three-party MPC.

Link copied to clipboard
abstract suspend fun keyRefresh(keyshare: ByteArray): Result<ByteArray>

Refreshes an existing keyshare without changing its public key.

Link copied to clipboard
abstract suspend fun preSignature(keyshare: ByteArray): Result<ByteArray>

Generates a pre-signature for instant signing later with preSignatureFinal.

Link copied to clipboard
abstract suspend fun preSignatureFinal(preSignature: ByteArray, message: String, derivationPath: String = "m"): Result<ByteArray>

Finalizes a pre-signature with the actual message and derivation path.

Link copied to clipboard
abstract suspend fun reconcileKeyshare(keyId: String): Result<ByteArray>

Reconciles a staged keyshare to complete the after-part and make it active for signing.

Link copied to clipboard
abstract suspend fun recovery(keysharePublicKey: ByteArray): Result<ByteArray>

Recovers a keyshare using its public key, for advanced recovery scenarios.

Link copied to clipboard
abstract suspend fun signature(keyshare: ByteArray, message: String, derivationPath: String = "m"): Result<ByteArray>

Creates a cryptographic signature using three-party threshold signing.