Refresh with reconcile protocol and storage client
Overview
The reconcile protocol ensures consistency between client and server keyshares.
- The server has properly stored its keyshare
- The client's pending keyshare matches the server's expectations
- Both parties agree on key metadata (keyId, algorithm, etc.)
Key Components
Storage Domains
Overview
The reconcile protocol ensures consistency between client and server keyshares.
- The server has properly stored its keyshare
- The client's pending keyshare matches the server's expectations
- Both parties agree on key metadata (keyId, algorithm, etc.)
Key Components
Storage Domains
Overview
refresh rotates the keyshares of an existing key without changing its public key. Like keygen, it does not overwrite your active share directly: the newly rotated share is written to the staged slot, and a reconcile exchange with the cloud settles it before it replaces the current share.
The difference from keygen is that a refresh has a share to fall back to. The ReconcileStoreDao keeps the previous share in current while the new one sits in staged, so if the reconcile is interrupted or rejected, the key remains fully usable on the old share, and a share captured before the refresh is invalidated once the rotation commits. Your StorageClient is the source of truth the SDK reads back to decide how to finish.
Reconcile runs automatically as the final step of refresh, keygen, import, recover, and hardDerivation (hardDerivation is ECDSA-only) — in the normal flow you never call it yourself. reconcile(keyId) (returning Result<ByteArray>) is exposed only as a manual retry: call it if a previous operation crashed mid-reconcile and left a staged keyshare that was never promoted to current. The sequence below shows what the SDK does internally, including the rollback path when reconcile does not complete.
Outcomes
A refresh reconcile settles into one of three results:
- Complete — the staged share landed; the SDK promotes it to
currentand clearsstaged. - Already applied — a prior interrupted attempt had already committed; the SDK confirms storage matches.
- Rollback — the staged share is discarded and the previous
currentshare is kept, so the key stays usable exactly as it was before the refresh.
Key Components
Retrying after a crash
refresh, keygen, import, recover, and hardDerivation reconcile internally, so you normally never call reconcile yourself. The exception is recovery after a crash: if the app is killed mid-reconcile, the rotated share stays in the staged slot and was never promoted (the key is still fully usable on the current share). Calling reconcile(keyId) on next launch settles it — promoting or rolling back — without re-running the refresh.
Reconciling every stored key on startup closes this gap:
// Run once on app launch, off the main thread.
suspend fun reconcileOnStartup(trioSession: TrioSession, storedKeyIds: List<String>) {
for (keyId in storedKeyIds) {
trioSession.reconcile(keyId)
.onFailure { Log.e("MPC", "reconcile failed for $keyId: ${it.message}") }
}
}
Overview
refresh rotates the keyshares of an existing key without changing its public key. Like keygen, it does not overwrite your active share directly: the newly rotated share is written to the staged slot, and a reconcile exchange with the cloud settles it before it replaces the current share.
The difference from keygen is that a refresh has a share to fall back to. The ReconcileStoreDao keeps the previous share in currentKeyshare while the new one sits in stagedKeyshare, so if the reconcile is interrupted or rejected, the key remains fully usable on the old share, and a share captured before the refresh is invalidated once the rotation commits. Your StorageClient is the source of truth the SDK reads back to decide how to finish.
Reconcile runs automatically as the final step of keygen, refresh, import, recover, and hardDerivation (hardDerivation is ECDSA-only) — you normally never call it yourself. TrioSession exposes reconcile(keyId:) async -> Result<Data, Error> only as a manual retry: if a prior operation crashed mid-reconcile, call it with the affected keyId to complete the protocol. The diagram below shows what the SDK does internally, including the rollback path when reconcile does not complete.
Outcomes
A refresh reconcile settles into one of three results:
- Complete — the staged share landed; the SDK promotes it to
currentand clearsstaged. - Already applied — a prior interrupted attempt had already committed; the SDK confirms storage matches.
- Rollback — the staged share is discarded and the previous
currentshare is kept, so the key stays usable exactly as it was before the refresh.
Key Components
Retrying after a crash
refresh, keygen, import, recover, and hardDerivation reconcile internally, so you normally never call reconcile yourself. The exception is recovery after a crash: if the app is killed mid-reconcile, the rotated share stays in the staged slot and was never promoted (the key is still fully usable on the current share). Calling reconcile(keyId:) on next launch settles it — promoting or rolling back — without re-running the refresh.
Reconciling every stored key on startup closes this gap:
// Run once on app launch.
func reconcileOnStartup(trioSession: TrioSession, storedKeyIds: [String]) async {
for keyId in storedKeyIds {
if case .failure(let error) = await trioSession.reconcile(keyId: keyId) {
Swift.print("reconcile failed for \(keyId): \(error)")
}
}
}