Skip to main content

Wallet flows

Today (SDK 0.1.0)

The wallet has no revocation responsibilities beyond hygiene:

  1. After issuance, call openac.precompute(...) once and persist the resulting PrecomputedCredential (its serialized form is safe to store at rest; treat it like an OAuth refresh token).
  2. For every verifier session: request a fresh verifierNonce, sign it with the device key, call openac.present(...), send the bundle.
  3. Keep the device private key in the OS secure element; never persist Show witnesses across sessions.

No witness fetches, no status checks, no extra metadata travel with the proof.

Future flows under in_proof_future

When the non-membership scheme lands, the wallet adds three new responsibilities:

Periodic witness refresh

The wallet schedules refresh either by issuer push (long-poll, push notification) or by background poll. Two constraints:

  • Refresh independently of presentations. A refresh triggered by an imminent presentation correlates the request with the verifier; refresh on its own cadence.
  • Refresh for all credentials in the wallet, not only the one about to be used. Fetching a slice for "only the credential I'm about to use" leaks intent.

Presentation with revocation sub-proof

// Sketch; the API does not yet exist.
const proof = await openac.present({
precomputed,
verifierNonce,
devicePrivateKey,
keys,
revocation: {
epoch,
root,
rootSignature,
witness: nonMembershipWitness,
},
});

The wallet must:

  1. Pick the latest (root, epoch) that is still inside the verifier's freshness window. Older roots are fine if the window allows; using a too-recent root that the verifier hasn't seen yet will be rejected.
  2. Include the issuer's signature over (root, epoch) so the verifier can audit freshness without contacting the issuer at presentation time.
  3. Treat the witness as private — it must never appear in logs or telemetry.

Recovery from a stale or revoked state

Verifier responseWallet action
"epoch too old"Trigger an immediate witness refresh; do not retry with the same witness.
"non-membership proof invalid"Credential is revoked. Mark it as invalid locally; do not retry. Surface to the user with the issuer's remediation channel.
"root signature invalid"Refuse to retry against this verifier; this is a verifier configuration error or an attack.

Freshness and unlinkability

The unlinkability guarantee in the paper relies on three randomizations: a fresh verifierNonce, fresh blinds inside the WASM present call, and a fresh device signature. Revocation does not add a new randomization — it only adds a public binding to (root, epoch). That public value is shared across every wallet that proved against the same epoch, so it does not in itself link presentations. Wallets must still avoid attaching wallet-specific metadata (User-Agent strings, headers, request ordering) when fetching witnesses or presenting proofs.