Skip to main content

KeyRefresh with Reconcile Protocol

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 — nothing is lost, and a share captured before the refresh is useless afterward. This two-slot design is what makes the rotation crash-safe, and 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, and import — 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 current and clears staged.
  • Already applied — a prior interrupted attempt had already committed; the SDK confirms storage matches.
  • Rollback — the staged share is discarded and the previous current share is kept, so the key stays usable exactly as it was before the refresh.

Key Components

ComponentDescriptionInterface
StorageClientManages keyshare transactions/states and storage operationsStorageClientInterface app supposed to implement
Mobile SDKSilentShard SDK-
CloudCloud node-
Current KeyshareKeyshare went through reconcile protocol successfully-
Staged KeyshareKeyshare which will be subjected to reconcile protocol and could potentially become current keyshare if it goes through reconcile successfully-
Reconcile Outcome : CompleteStaged Keyshare successfully completed the reconcile protocol and could be saved as current keyshare-
Reconcile Outcome : RollbackStaged Keyshare is supposed to be discarded-
Loading Diagram...

Retrying after a crash

refresh, keygen, and import 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(duoSession: DuoSession, storedKeyIds: List<String>) {
for (keyId in storedKeyIds) {
duoSession.reconcile(keyId)
.onFailure { Log.e("MPC", "reconcile failed for $keyId: ${it.message}") }
}
}