Skip to content

research: Poseidon2 hash swap (REJECT) + adopt circom --O2 (KEEP) - #40

Open
alexandre-mrt wants to merge 1 commit into
mainfrom
research/2026-08-22-poseidon2-hash-swap
Open

research: Poseidon2 hash swap (REJECT) + adopt circom --O2 (KEEP)#40
alexandre-mrt wants to merge 1 commit into
mainfrom
research/2026-08-22-poseidon2-hash-swap

Conversation

@alexandre-mrt

Copy link
Copy Markdown
Owner

Summary

Queue item #2 (docs/research/EXPERIMENTS.md). Full report:
docs/research/2026-08-22-poseidon2-hash-swap.md.

Hypothesis: swapping circomlib's Poseidon for Poseidon2 at the hash arities with a verified
BN254 parameterization (t=3, the depth-20 Merkle path; t=4, amount/nullifier/context-binding
hashes) reduces R1CS constraints and Groth16 proving time.

Result: falsified.

--O1 (circom default) --O2 (full simplification)
transfer.circom total constraints, Poseidon → Poseidon2 13,611 → 15,118 (+11.1%) 6,384 → 6,390 (+0.09%, noise)
compliance.circom 12,743 → 14,497 (+13.8%) 5,979 → 5,991 (+0.20%, noise)
withdraw.circom 3,058 → 3,121 (+2.1%) 1,439 → 1,442 (+0.21%, noise)

Worse at --O1, a wash at --O2. Built and cross-validated anyway
(circuits/experiments/poseidon2/, circuits/vendor/poseidon2/ — vendored from
@taceo/circom-lib MIT, round constants trace to the HorizenLabs reference generator), with a
soundness/leakage writeup and 8/8 new tests including malicious-witness negative tests
(circuits/test/poseidon2-experiment.test.mjs). Not adopted — knowledge kept, not wired into
contracts/. t=5/t=6 (the majority of hash call-sites — both commitments and both nullifiers)
have no audited BN254 Poseidon2 parameter set in any package reachable this session, and I didn't
derive my own for a hash securing commitments.

The actual find: measuring this exposed that Veil's circuits/scripts/compile*.sh were using
circom's --O1 default (only signal/constant simplification), not --O2 (full constraint
simplification). Switching:

  • ~53% fewer total R1CS constraints, all three circuits
  • ~23% faster Node proving time (transfer/compliance), ~5% (withdraw)
  • Zero semantic change — same public interface, same soundness. 108/108 existing circuit tests
    pass identically
    against the new O2 artifacts, including every existing negative test.

Adopted: compile.sh / compile-withdraw.sh / compile-compliance.sh now pass --O2.
BASELINE.md, README.md, docs/threat-model.md, docs/SPEC.md, docs/architecture.md updated
with the new constraint counts (old O1 numbers kept alongside for the delta, not deleted).

Deployment note: switching optimization level changes every circuit's verifying key (different
R1CS layout → different zkey trapdoor → different vk.json, same statement). compliance.move's
1-epoch VK-update timelock applies whenever these circuits are actually redeployed — this PR only
touches local dev-build artifacts, not any live testnet contract state.

On-chain gas — still BLOCKED, third attempt. Root-caused precisely this time: this session's
egress policy denies both github.com release-asset downloads and direct JSON-RPC to
fullnode.testnet.sui.io (403 on both), but permits git clone/ls-remote against
github.com itself (how circom gets built from source each session). sui has no crates.io
package. Stays #1 in EXPERIMENTS.md with this root cause recorded for next time.

Test plan

  • node --experimental-vm-modules test/{transfer,compliance,withdraw}.test.mjs — 43+30+35 =
    108/108 pass against the new --O2 build artifacts (same counts as before, same negative
    tests)
  • node --experimental-vm-modules test/poseidon2-experiment.test.mjs — 8/8 pass (correctness
    cross-check vs @taceo/poseidon2 JS reference + malicious-witness negative tests per variant
    circuit)
  • bun run src/test-converter.ts — 109/109 pass
  • bun run src/test-compliance-utils.ts — 67/67 pass
  • bunx vitest run (frontend) — 19/19 pass
  • bun run src/fuzz-tests.ts — 6/6 properties pass (500 cases each)
  • cd contracts && sui move testNOT RUN, sui CLI unavailable this session (see above;
    no contract code changed in this PR, so risk is low, but this is a real gap, not a passing
    claim)

Generated by Claude Code

Queue item #2. Built and measured a Poseidon2 swap at the two hash arities
(t=3 Merkle path, t=4 amount/nullifier hashes) with a verified BN254
parameterization; t=5/t=6 (the majority of hash call-sites) have no audited
parameter set in any reachable package, so left unchanged. Result: worse
constraints at circom's --O1 default (+11-14%), a wash at --O2 (+0.1-0.2%).
Hypothesis falsified — parked as circuits/experiments/poseidon2/, not adopted.

Measuring it honestly surfaced that Veil's own compile scripts were using
circom's --O1 default instead of --O2 (full constraint simplification):
~53% fewer total constraints, ~23% faster Node proving time (transfer/
compliance), zero semantic change (108/108 existing circuit tests pass
identically, same negative-test behavior). Adopted: compile*.sh now pass
--O2; BASELINE.md/README/threat-model/SPEC updated with the new numbers.

On-chain gas remains BLOCKED (3rd attempt) — root-caused this time to an
egress-policy denial on both github.com release downloads and direct
JSON-RPC to the testnet fullnode; re-ranked to stay #1 in EXPERIMENTS.md
with the precise cause recorded for the next attempt.

Full report: docs/research/2026-08-22-poseidon2-hash-swap.md

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