Skip to content

feat(proofs): on-chain Groth16 verification of commitment openings#46

Merged
0xErgod merged 1 commit into
mainfrom
feat/39-on-chain-groth16-proof-flow
May 28, 2026
Merged

feat(proofs): on-chain Groth16 verification of commitment openings#46
0xErgod merged 1 commit into
mainfrom
feat/39-on-chain-groth16-proof-flow

Conversation

@0xErgod

@0xErgod 0xErgod commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 5 of the ZK-friendly stack migration: the first end-to-end ZK proof through the whole protocol. TS commits → the Rust prover-server proves → Move verifies on chain. This closes the unshipped Move-side verification criterion that the original (closed) issues #31/#32 never delivered.

Linked Issue

Closes #39

Design decisions (recorded on #39)

  • open_with_proof(commitment: &SecretCommitment, claimed_first_value, proof_bytes, clock)immutable ref: a partial-open doesn't consume the commitment or flip its opened flag. Repeatable, orthogonal to the full open_secret reveal.
  • Public inputs reconstructed from the commitment's own on-chain fields (commitment_x/y, encoding_id) + the caller's claimed value. This binds the proof to this commitment — a proof for a different commitment fails verification because the public inputs won't match.
  • Auth: the proof is cryptographically self-authorizing, but commitments are owned objects, so Sui's object model gates submission to the owner. Kept the owned-object lifecycle unchanged (no shared/frozen migration).
  • Only claimed_first_value (stream[0]) is revealed; the rest of the opening + blinding stay private.

Changes

Move (contracts/sources/commitments.move): open_with_proof entry + SecretPartialOpened event + open_with_proof_rejects_garbage_proof test.

SDK (packages/sdk/src/proofs.ts, new): proveCommitmentOpening (HTTP → prover-server, returns arkworks-compressed proof bytes — Sui-compatible, no re-encoding) + buildOpenWithProofTx PTB builder + DEFAULT_PROVER_URL (127.0.0.1:3001). Exported from index.

dApp (Commit.tsx): "open with proof (ZK)" button with progress phases (proving → verifying on chain → verified) + proven-value receipt row + structured logging. VITE_PROVER_URL override.

Redeploy: the new entry required a fresh publish. whisper_protocol republished to the dev devnet (package 0xad371607…, registry 0x65a41743…); Published.toml + networks.json + networks.ts updated.

Requirements checklist (from #39)

  • Rust prover-server answers POST /prove/pedersen_opens_to with a valid proof — verified live
  • Move open_with_proof calls verify_groth16_proof (via proofs::verify_pedersen_opens_to), accepts a valid proof, emits the partial-open event; rejects a proof for garbage (Move test)
  • SDK exposes proveCommitmentOpening + the open_with_proof PTB builder
  • End-to-end on devnet: TS commits → Rust proves → Move verifies → event emitted
  • VK drift guard: server's VK matches the bytes embedded in proofs.move byte-for-byte (re-verified this PR)

Live e2e (Playwright + locally-booted prover-server)

Booted crates/prover-server (release; loaded its deterministic-seed keys), pointed the dApp at the devnet + prover, drove the flow:

[commit] posted            commitment 0xfcff9569…  tx A8b8Zu79…
[commit] proof produced    proofBytes: 128          ← Groth16 proof over HTTP
[commit] proof verified on chain  tx 2UM9ztiU…      ← Sui native verifier ACCEPTED

Zero console errors. The 128-byte arkworks-compressed proof is accepted by Sui's BN254 verifier against the VK embedded in proofs.move — exactly as the VK-match check predicted. UI showed "zk partial open · verified · stream[0] = …".

Before the redeploy, the proof-open correctly failed with "No function open_with_proof" against the Phase-2 package — confirming the binding is real (the function genuinely has to be on chain).

Testing

  • sui move test — 15/15 (was 14; +open_with_proof_rejects_garbage_proof)
  • pnpm typecheck (4 projects), SDK + dApp build, networks.ts drift check — all green
  • Live e2e as above

Notes for the reviewer

  • Redeploy artifacts in this PR (Published.toml/networks.json/networks.ts): unavoidable — a new entry function requires republishing, and the e2e needs the dApp/SDK pointed at the package that actually has open_with_proof. Same fresh-publish pattern as chore(devnet): publish whisper_protocol to the dev devnet #44 (the old Phase-2 package is orphaned on the shared devnet, harmless).
  • No committed Playwright spec — same rationale as Phase 4 (MCP-driven live e2e; a committed spec against a shared devnet + a prover-server dependency would be flaky in CI).
  • The garbage-proof Move test aborts with proofs::E_INVALID_PROOF (cross-module const ref works in test attributes). The accept path can't be a Move unit test (needs a real proof from the off-chain prover) — covered by the live e2e per the behavior-only Move-test discipline.

[agent PR]

Phase 5 of the ZK-friendly stack migration — the first end-to-end ZK
proof through the protocol: TS commits, the Rust prover-server proves,
Move verifies on chain.

Move (commitments.move):
- New `open_with_proof(commitment: &SecretCommitment, claimed_first_value,
  proof_bytes, clock)` entry. Reconstructs the proof's public inputs from
  the commitment's own on-chain fields (commitment_x/y, encoding_id) +
  the caller's claimed value, then calls proofs::verify_pedersen_opens_to
  (aborts E_INVALID_PROOF on failure). Emits SecretPartialOpened — only
  stream[0] (the proven public value) is revealed; the rest of the
  opening + the blinding stay private.
- `&SecretCommitment` immutable: a partial-open doesn't consume the
  commitment or flip its `opened` flag, so it's repeatable and orthogonal
  to the full open_secret reveal. The proof is cryptographically self-
  authorizing; because commitments are owned objects, Sui's object model
  gates submission to the owner.
- Test: open_with_proof_rejects_garbage_proof (128 zero bytes -> abort).
  The accept path needs a real proof and is covered by the live e2e.

SDK (proofs.ts):
- proveCommitmentOpening: shapes the PedersenOpensToInputs body, POSTs to
  the prover-server's /prove/pedersen_opens_to, returns the arkworks-
  compressed proof bytes (Sui-compatible, no re-encoding).
- buildOpenWithProofTx: the open_with_proof PTB. Passes only the object +
  claimed value + proof; the Move entry reconstructs point coords +
  encoding_id from the object.
- DEFAULT_PROVER_URL = 127.0.0.1:3001 (matches prover-server's bind).

dApp (Commit.tsx):
- "open with proof (ZK)" button alongside "reveal (full open)", with
  progress phases (proving -> verifying on chain -> verified) and a
  proven-value receipt row. Structured console logging at each boundary.
- VITE_PROVER_URL env override.

Redeploy: the new open_with_proof entry means a fresh publish was needed.
whisper_protocol republished to the dev devnet (package
0xad371607..., registry 0x65a41743...); Published.toml + networks.json +
networks.ts updated. VK drift guard passed (server VK == proofs.move VK,
byte-for-byte) so server-generated proofs verify on chain.

Verified end-to-end on the live devnet via Playwright + a locally-booted
prover-server: commit -> "open with proof" -> prover produces a 128-byte
Groth16 proof over HTTP -> open_with_proof posts it -> Sui's native
verifier accepts against the embedded VK -> SecretPartialOpened emitted.
Zero console errors. The console trace shows:
  [commit] posted -> [commit] proof produced (128 bytes) ->
  [commit] proof verified on chain

This closes the unshipped Move-side verification criterion that the
original issues #31/#32 never delivered.

Verification: sui move test 15/15, pnpm typecheck (4 projects), SDK +
dApp build, networks.ts drift check — all green.

Closes #39

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@0xErgod 0xErgod added this to the ZK-friendly stack migration milestone May 28, 2026
@0xErgod 0xErgod added zk-migration Atomic ZK-friendly stack migration (kills X25519/Blake2b/HKDF) move-contracts Touches contracts/sources/*.move sdk Touches packages/sdk dapp Touches apps/protocol in-progress Work is actively being done on this issue labels May 28, 2026
@0xErgod 0xErgod self-assigned this May 28, 2026
@0xErgod

0xErgod commented May 28, 2026

Copy link
Copy Markdown
Owner Author

Review pass: APPROVE — no critical, important, or nits. The reviewer validated the security crux: open_with_proof reconstructs all three point-bound public inputs from the commitment object's own fields (not caller args), in the order matching the circuit's new_input allocation + proofs.move's vector — so a proof for a different commitment fails. PTB arg order matches the Move param order; proof bytes pass through un-re-encoded; the immutable-ref repeatable-partial-open design holds; redeploy artifacts are consistent (new package only, no stale IDs, networks.ts in sync). Local: sui move test 15/15, typecheck + builds + drift check all green.

Merging.

[agent comment]

@0xErgod 0xErgod merged commit be18cbb into main May 28, 2026
3 checks passed
@0xErgod 0xErgod deleted the feat/39-on-chain-groth16-proof-flow branch May 28, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dapp Touches apps/protocol in-progress Work is actively being done on this issue move-contracts Touches contracts/sources/*.move sdk Touches packages/sdk zk-migration Atomic ZK-friendly stack migration (kills X25519/Blake2b/HKDF)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 5: on-chain Groth16 proof flow (commitment partial opening)

1 participant