Skip to content

research: Poseidon2 vs Poseidon benchmark for transfer/withdraw circuits - #41

Open
alexandre-mrt wants to merge 1 commit into
mainfrom
research/2026-08-23-poseidon2-benchmark
Open

research: Poseidon2 vs Poseidon benchmark for transfer/withdraw circuits#41
alexandre-mrt wants to merge 1 commit into
mainfrom
research/2026-08-23-poseidon2-benchmark

Conversation

@alexandre-mrt

Copy link
Copy Markdown
Owner

Summary

Nightly research loop, queue item #2 (docs/research/EXPERIMENTS.md). Follows up on open
question #4 from the 2026-07-22 baseline report: what would swapping circomlib's Poseidon for
Poseidon2 actually cost/save, measured, not guessed.

  • Forks transfer.circom and withdraw.circom under scripts/bench/poseidon2/not wired
    into the deployed protocol (pool.move, verifying keys, and the frontend are untouched) — with
    every Poseidon call replaced by a capacity-domain-separated Poseidon2 sponge
    (Poseidon2Hash(N, T, DS), vendored @taceo/circom-lib permutation, cross-checked bit-for-bit
    against the independent @taceo/poseidon2 JS reference).
  • At --O2 (full R1CS simplification): non-linear constraints −1.7% (transfer) /
    −7.7% (withdraw); Groth16 proving time −9.7% / −17.1% (mean of 10 real
    groth16.fullProve runs each, both circuits' proofs groth16.verifyd true).
  • At Veil's actual default compile flags (--O1, same as circuits/scripts/compile.sh), the
    identical swap makes both circuits worse (+10–12% total constraints) — unswept linear
    intermediates in Poseidon2's matrix-multiplication gadgets. A real, previously-unknown
    compiler-flag sensitivity, now its own queued follow-up (item feat: Tier 3 — KYC compliance circuit + ElGamal auditor pattern #2 in EXPERIMENTS.md).
  • One hash shape (compliance's leafHash, needs t=5) is a net loss — the available Poseidon2
    parameter sets skip t=5/t=6, forcing a jump to t=8.
  • A negative test confirms a tampered witness (oldCommitment + 1) is rejected outright at
    witness-generation time (Assert Failed, not just "verification later fails").
  • Also re-attempted on-chain gas (queue item feat: Veil privacy payment protocol — full stack MVP #1): still BLOCKED, now precisely diagnosed as a
    sandbox network-proxy allowlist (403 at the CONNECT layer for both api.github.com and
    fullnode.testnet.sui.io) rather than a retriable tool-approval denial.
  • Found former queue item 13 (circuits' chained npm test hang) was already fixed upstream
    (fix(circuits): exit test runners explicitly after the last proof #17, 2026-07-28) — re-confirmed working, removed as stale rather than left inaccurate.

Verdict: PARK (not KEEP — not a clean sweep across all three circuits, and any real adoption
needs a fresh Groth16 trusted-setup ceremony this PR doesn't attempt; not REJECT — the core
hypothesis measured true for the dominant hash shapes). Full write-up, every raw command and its
output: docs/research/2026-08-23-poseidon2-benchmark.md.

docs/research/LEDGER.md gets tonight's row; docs/research/EXPERIMENTS.md is re-ranked with two
new follow-up items ahead of the old queue.

Test plan

Full suite run this session, everything that could run is green:

  • Circuits (real Groth16 proofs): cd circuits && npm test → 108/108 pass (43 transfer + 30
    compliance + 35 withdraw)
  • Proof converter: cd scripts && bun run src/test-converter.ts → 109/109 pass
  • Compliance utils: cd scripts && bun run src/test-compliance-utils.ts → 67/67 pass
  • Frontend: cd frontend && bunx vitest run → 19/19 pass
  • Property-based fuzz: cd scripts && bun run src/fuzz-tests.ts → 6/6 properties pass (500
    runs each)
  • Move contracts (sui move test) — not run, sui CLI unavailable this session (same
    pre-existing gap as the 2026-07-22 baseline report); no Move code touched by this PR
  • Poseidon2 permutation cross-checked bit-for-bit against the independent @taceo/poseidon2
    JS reference (t=3, input [1,2,3])
  • Negative test: tampered oldCommitment rejected at witness generation
    (Assert Failed. Error in template TransferPoseidon2_29 line: 41)

Generated by Claude Code

Queue item #2. Forks transfer.circom and withdraw.circom under
scripts/bench/poseidon2/ (not wired into the deployed protocol) with every
circomlib Poseidon call replaced by a capacity-domain-separated Poseidon2
sponge (vendored from @taceo/circom-lib, cross-checked bit-for-bit against
the independent @taceo/poseidon2 JS reference).

Measured, real, verifying (groth16.verify = true) results at --O2 (full
R1CS simplification): non-linear constraints -1.7% (transfer) / -7.7%
(withdraw); Groth16 proving time -9.7% / -17.1% (mean of 10 real
fullProve runs each). At Veil's actual default compile flags (--O1, no
--O2 - same as circuits/scripts/compile.sh), the identical swap makes
both circuits worse (+10-12% total constraints) due to unswept linear
intermediates in Poseidon2's matrix-multiplication gadgets - a real
compiler-flag sensitivity discovered mid-experiment. One hash shape
(compliance's leafHash, needs t=5) is a net loss with the available
Poseidon2 parameter sets (forced to t=8).

Verdict: PARK, not KEEP - not a clean sweep, needs a fresh trusted-setup
ceremony to ever deploy, and the O1-vs-O2 build-flag question deserves
its own measurement first (now queued).

Also re-attempted on-chain gas (queue item #1): still BLOCKED, now
precisely diagnosed as a sandbox proxy allowlist (403 at the CONNECT
layer), not a retriable denial. And discovered former queue item 13
(npm test hang) was already fixed upstream (PR #17) - removed as stale.

Full suite green: 108/108 circuit tests, 109/109 converter, 67/67
compliance-utils, 19/19 frontend, 6/6 fuzz properties. Move tests not
run (sui CLI still unavailable; no Move code touched).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014DYmywv78PsjAesXgGzToe
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.

2 participants