Skip to content

feat(holder): cache and reuse proofs for unchanged credentials (#426) - #472

Merged
Psalmuel01 merged 5 commits into
ToluLabs:mainfrom
CodingBabe-1:feat/426-proof-caching
Sep 2, 2026
Merged

feat(holder): cache and reuse proofs for unchanged credentials (#426)#472
Psalmuel01 merged 5 commits into
ToluLabs:mainfrom
CodingBabe-1:feat/426-proof-caching

Conversation

@CodingBabe-1

@CodingBabe-1 CodingBabe-1 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #426: a holder who already generated a valid proof for a credential no longer re-proves it needlessly when the underlying credential, its parameters, and the circuit are unchanged.

Proof generation is the most expensive step in the holder flow (server-side Noir witness + browser WASM UltraHonk). This PR caches generated proofs locally, keyed by the exact inputs that determine a proof — commitment, claim parameters, and circuit/VK version — and reuses a valid, matching prior proof instead of regenerating it. Reuse is surfaced in the UI with a clear "using existing proof" banner so it's visible rather than a silent optimisation.

How the cache works

Every proof is a pure function of:

  • the credential commitment (derived from type, value, salt, issuer),
  • the claim parameters baked into the credential (thresholds / restricted list),
  • the circuit / VK version it was generated with.

lib/proof-cache.ts derives a deterministic buildProofKey from exactly these three, stores proofs locally (JSON-safe byte arrays), and:

  • reuses a cached proof only when the key matches and the on-chain record is still valid,
  • invalidates on expiry (local provedAt + TTL window, and a live is_verified read),
  • invalidates on revocation (the on-chain record no longer validates),
  • invalidates on VK version change — the VK version is itself derived from the actual compiled circuit bytecode (resolveVkVersion), so bumping a circuit or toolchain automatically invalidates every cached proof,
  • invalidates on param change (any threshold / restricted change produces a new key).

Holder page integration

  • ProofFlow checks the cache before doing any expensive work; on a hit it jumps straight to the generated state with a "Using existing proof — params unchanged" banner. Fresh proofs are saved to the cache, and a successful on-chain submission marks the entry as proved (with its TTL) so it stays reusable until it expires.
  • BatchProofFlow does the same per credential and shows a per-row "using existing proof" badge; successful batch submission records every reused/fresh proof's cache entry.
  • Removing a credential also clears its cached proofs.

Acceptance checklist

  • A valid, matching prior proof is reused instead of regenerated (unit-tested).
  • Correctly invalidated on expiry, revocation, VK version change, or param change (unit-tested).
  • Reuse is visible in the UI ("using existing proof").
  • Existing self-service flows unchanged; proving still works exactly as before when no cached proof applies.

Type of change

  • Bug fix
  • New feature / credential type
  • Refactor / cleanup
  • Docs
  • CI / tooling

Checklist

  • cargo test passes (contracts) — no contract changes
  • pnpm tsc --noEmit passes (frontend)
  • pnpm build passes (frontend)
  • pnpm test passes (frontend – includes issuer suite)
  • Circuit changes: none (VK-version handling reads the existing shipped circuit bytecode)
  • No NEXT_PUBLIC_ prefix on server-only env vars
  • No identity fields stored or logged after KYC provider call

Notes for reviewers

  • Reuse is keyed on the credential as stored in the local wallet, so an on-chain is_verified read is only attempted when the local record already says the proof is currently valid; if contracts aren't reachable it falls back to the local expiry check. This means the cache is never trusted blindly — a mismatch in any dimension triggers a fresh proof.
  • The circuit bytecode hash is computed with crypto.subtle, which is available in the app's secure-context browsers.
  • New test tooling (vitest config / pnpm test) is shared across the frontend workspace; the standalone @stellarcred/issuer suite keeps its own node-environment runner.

closes #426

CodingBabe-1 and others added 2 commits August 31, 2026 09:11
Re-proving a credential whose on-chain record is still valid wasted time and
compute (server-side witness + browser WASM UltraHonk). Add local proof
caching keyed by the exact inputs that determine a proof — commitment, claim
params, and circuit/VK version — so a valid, matching proof is reused instead
of regenerated.

New lib/proof-cache.ts:
- buildProofKey derives a deterministic key from (type, commitment,
  canonicalised claim params, VK version).
- Proofs are stored locally and reused only when the entry matches AND the
  on-chain record is still valid (local proofStatus expiry check plus a live
  is_verified read to catch revocation).
- Correctly invalidated on expiry, revocation, VK version change, or any
  change to the claim params; VK version is itself derived from the actual
  circuit bytecode, so bumping the circuit invalidates every old proof.

Holder page (ProofFlow + BatchProofFlow) now checks the cache before doing any
expensive work and, when reusing, surfaces a clear "using existing proof"
banner so the behaviour is visible. Reused proofs are still submittable, and a
successful submission records the cache entry as proved (with its TTL) so it
stays reusable until it expires. Removing a credential also clears its cached
proofs.

Coverage: unit tests cover reuse, param-change / VK-version / expiry /
revocation invalidation, and independent handling across multiple
credentials.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@CodingBabe-1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…ToluLabs#426)

The "Merge branch 'main'" on this branch left the frontend build broken:
- pnpm-workspace.yaml had a duplicated `esbuild: true` under allowBuilds,
  which is invalid YAML and made every `pnpm install --frozen-lockfile`
  (frontend, contracts, and a11y CI jobs) fail.
- pnpm-lock.yaml was left with duplicated mapping keys from the merge;
  regenerated it consistently with pnpm 9 (the CI version).
- proof-cache.ts imported a `CircuitArtifact` type that proof.ts never
  exported; added the export and typed the circuit fetch with it.
- The merge dropped the cache-reuse path and the mark-proved / purge
  call sites from holder/page.tsx; restored them so proof caching works
  in both the single and batch flows.
- Raised the holder route JS budget (16 kB -> 17 kB) for the new local
  proof-cache module, which pushed the route past its previous limit.
# Conflicts:
#	frontend/app/holder/page.tsx
@CodingBabe-1
CodingBabe-1 force-pushed the feat/426-proof-caching branch from 16be835 to 00dc5dc Compare September 1, 2026 12:19
@Psalmuel01
Psalmuel01 merged commit 7411b1e into ToluLabs:main Sep 2, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: proof caching / reuse so an unchanged credential is not re-proven needlessly

2 participants