reconcileKeyshare

abstract suspend fun reconcileKeyshare(keyId: String): Result<ByteArray>

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

Note: Operations like keygen, keyRefresh, and import already perform reconciliation internally as part of their execution. This standalone method exists for retry scenarios when the internal reconciliation fails.

When to use: If an operation completes its expensive MPC computation but the after-part (reconciliation) fails due to network interruption or crash, the keyshare remains in "staged" state. You can then retry reconciliation using this method without redoing the entire expensive operation.

Flow:

  1. Operation (keygen/refresh/import) does main MPC computation

  2. Operation internally attempts reconciliation (staged → current)

  3. If step 2 fails, keyshare stays "staged"

  4. Use this method to retry step 2 later

Example: Retry Failed Reconciliation from Keygen

// User provides keyId of their incomplete operation
val keyId = "user_provided_key_id"

// Check storage for staged keyshare
val dao = storageClient.read(keyId)

if (dao?.stagedKeyshare != null && dao.currentKeyshare == null) {
// Reconciliation was incomplete - retry it
val reconciledKeyshare = session.reconcileKeyshare(keyId).getOrThrow()

// Verify it's now in storage as currentKeyshare
val updatedDao = storageClient.read(keyId)
println("Current keyshare available: ${updatedDao?.currentKeyshare != null}")
}

Example: Retry Logic for Failed Reconciliation

suspend fun reconcileWithRetry(
session: TrioSession,
storageClient: StorageClient<ReconcileStoreDao>,
keyId: String,
maxRetries: Int = 3
): Result<ByteArray> {
// First check if reconciliation is needed
val dao = storageClient.read(keyId)
if (dao?.currentKeyshare != null) {
return Result.success(dao.currentKeyshare)
}

if (dao?.stagedKeyshare == null) {
return Result.failure(Exception("No staged keyshare found for $keyId"))
}

// Retry reconciliation
repeat(maxRetries) { attempt ->
try {
println("Reconciliation attempt ${attempt + 1}/$maxRetries")
return session.reconcileKeyshare(keyId)
} catch (e: Exception) {
if (attempt < maxRetries - 1) {
println("Attempt failed, retrying in 2s...")
delay(2000)
}
}
}
return Result.failure(Exception("Reconciliation failed after $maxRetries attempts"))
}

Example: After Key Refresh (Recovery Scenario)

// Normal key refresh (internally reconciled)
val refreshedKeyshare = session.keyRefresh(oldKeyshare).getOrThrow()

// If app crashes before completion, on restart:
suspend fun checkForIncompleteRefresh(
storageClient: StorageClient<ReconcileStoreDao>,
keyId: String
) {
val dao = storageClient.read(keyId)

if (dao?.stagedKeyshare != null && dao.currentKeyshare == null) {
// Refresh completed but reconciliation failed - retry
println("Retrying reconciliation after refresh...")
val reconciledKeyshare = session.reconcileKeyshare(keyId).getOrThrow()
println("Key refresh complete!")
}
}

Return

Result containing the reconciled (active) keyshare as ByteArray

Parameters

keyId

The unique identifier of the keyshare to reconcile (hex format)

See also