Skip to content

feat(m6/ec-c7): production Self VK (verified) + staging-proof capture instrumentation#34

Open
santisiri wants to merge 1 commit into
mainfrom
feat/ec-c7-vk-capture-prep
Open

feat(m6/ec-c7): production Self VK (verified) + staging-proof capture instrumentation#34
santisiri wants to merge 1 commit into
mainfrom
feat/ec-c7-vk-capture-prep

Conversation

@santisiri

Copy link
Copy Markdown
Member

EC-C7 — real Self verifier: production VK (verified) + capture instrumentation

EC-C7 flips ubi2 from the devnet MockZkVerifier to the real on-chain Groth16Verifier for Self passport proofs. Two deliverables; this PR lands #1 fully and wires up #2.

✅ Deliverable 1 — the production vc_and_disclose VK (adversarially verified)

Prior blocker: Self's S3 ceremony bucket 403s and the .zkey is uncommitted, so we only had a synthetic layout-lock VK. Resolved by extracting the VK from Self's on-chain Groth16 verifier:

  • crates/zkpoh/fixtures/self_prod_vkey.json — Self's real 21-signal V2 VK (groth16/bn128, nPublic 21, IC 22), cross-checked byte-identical across two independent primary sources: the GitHub-generated Verifier_vc_and_disclose.sol and the Celoscan-verified live Celo-mainnet deployment 0x0A57C317… (reported byte-identical to the Celo-Sepolia staging verifier 0x7C2FBA7F…).
  • Correctness: the snarkjs G2 c0/c1 swap was proven by reducing vk_gamma_2 to the canonical BN254 G2 generator; all 4 fixed points + 22 IC points pass on-curve/subgroup checks; the stale 20-signal legacy VK is rejected.
  • crates/zkpoh/tests/self_prod_vk_derisk.rs proves the real VK loads through our exact pin pipeline at arity-21 (936-byte canonical VK). It does not yet assert a proof verifies — that needs the captured proof.

⏳ Deliverable 2 — capture a genuine staging proof (instrumented; needs one human scan)

Self mock-passport registration runs in a phone TEE and can't be scripted headlessly. This PR wires the capture so a single scan yields a reusable fixture:

  • apps/wallet/app/api/self-verify/capture.ts (+ route wiring) — a dev-only side-channel, a no-op unless UBI2_SELF_CAPTURE_DIR is set, that never touches the relay's trust boundary. On a genuine staging scan it persists {proof, publicSignals} + a decoded meta file (each slot as decimal and 0x-padded 32-byte scalar) for reconciling UBI2_SELF_SCOPE + the accepted roots.
  • docs/ec-c7-capture-runbook.md — the exact human steps, the off-chain snarkjs GO/NO-GO gate, and the four mechanical pin+flip edits.

Safety

No consensus/runtime code changed — MockZkVerifier remains the devnet default; the real flip stays gated on the captured proof. zkpoh suite green (5 tests incl. the new de-risk); wallet typecheck green; capture logic verified (32-byte BE hex, no-op when disabled).

🤖 Generated with Claude Code

… + staging-proof capture instrumentation

EC-C7 = flip the real Groth16 verifier on for Self proofs. Two deliverables; this lands #1 fully and
wires up #2 (which needs a one-time human mock-passport scan).

DELIVERABLE 1 — the production vc_and_disclose VK (DONE, adversarially verified):
- crates/zkpoh/fixtures/self_prod_vkey.json: Self's real 21-signal vc_and_disclose V2 verifying key
  (protocol groth16, curve bn128, nPublic 21, IC length 22). Reconstructed from Self's on-chain Groth16
  verifier and cross-checked BYTE-IDENTICAL across two independent primary sources — the GitHub-generated
  Verifier_vc_and_disclose.sol and the Celoscan-verified live Celo-mainnet deployment
  0x0A57C317800865194496763377d25CA2082DB649 (reported byte-identical to the Celo-Sepolia staging verifier
  0x7C2FBA7F...). The snarkjs G2 c0/c1 swap was PROVEN correct by reducing vk_gamma_2 to the canonical
  BN254 G2 generator; all 4 fixed points + 22 IC points pass on-curve + prime-order-subgroup checks. This
  supersedes the prior blocker (Self's S3 ceremony bucket 403s; the stale 20-signal legacy VK is rejected).
- crates/zkpoh/tests/self_prod_vk_derisk.rs: proves the real VK loads through our exact pin pipeline —
  SnarkjsVk::to_pinned -> to_canonical_bytes -> from_canonical_bytes(Validate::Yes) ->
  Groth16Verifier::from_vk_bytes, reporting the confirmed arity (nPublic 21, 936-byte canonical VK).
  (Does NOT yet assert a proof verifies — that needs the captured staging proof.)

DELIVERABLE 2 — capture instrumentation (ready; awaits one scan):
- apps/wallet/app/api/self-verify/capture.ts + route.ts wiring: a dev-only capture side-channel, a NO-OP
  unless UBI2_SELF_CAPTURE_DIR is set (never touches the relay's trust boundary or response). On a genuine
  Self STAGING scan it persists the verbatim {proof, publicSignals} plus a decoded meta file (each public
  slot as decimal AND 0x-padded 32-byte scalar) for reconciling UBI2_SELF_SCOPE + the accepted roots.

- docs/ec-c7-capture-runbook.md: the exact human steps (Self mock passport, tunnel, scan), the off-chain
  snarkjs GO/NO-GO gate, and the four mechanical edits to pin+flip once a proof is captured.

zkpoh suite green (5 tests incl. the new de-risk); wallet typecheck green; capture logic verified
(32-byte BE hex correct, no-op when disabled). No consensus/runtime code changed — MockZkVerifier remains
the devnet default; the real flip is still gated on the captured proof.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant