Skip to content

Implement Falcon-512 verifier (direct-NTT, validated by a real fixture)#1

Open
V-k-h wants to merge 2 commits into
OpenZeppelin:mainfrom
V-k-h:falcon-512-impl
Open

Implement Falcon-512 verifier (direct-NTT, validated by a real fixture)#1
V-k-h wants to merge 2 commits into
OpenZeppelin:mainfrom
V-k-h:falcon-512-impl

Conversation

@V-k-h

@V-k-h V-k-h commented Jul 3, 2026

Copy link
Copy Markdown

Replaces the falcon_512 stub with a real, working Falcon-512 (FN-DSA) verifier.

What

  • Direct-NTT variant (no signer-supplied hint), following crates/falcon_512/PORTING.md
    and the smaller-trust-surface guidance there.
  • Recovers s1 = msg_point - s2*h (mod q) via mul_zq, accepts iff
    ||s1||^2 + ||s2||^2 <= 34_034_726.
  • SHAKE-256 hash-to-point is off-chain; msg_point is carried in the signature (the deployed
    s2morrow interop path).

Correctness gate

Validated against a real fn-dsa Falcon-512 signature (standard SHAKE-256), generated
off-chain with the Falcon reference signer:

  • bench_verify_falcon_512 accepts the genuine signature.
  • falcon_512_rejects_tampered rejects a tampered one.
  • Ported looped NTT is covered by known-answer tests (n = 4..512).

Encoding

  • public_key = 29 felts: base-Q pack(h) (18 coeffs/felt).
  • signature = 58 felts: pack(s2) ++ pack(msg_point).
  • Unpack enforces canonicality (the "PK coefficients < Q on read" check the reference lacked).

Measured cost (over budget — the honest finding)

make all regenerates results/:

  • verify ≈ 125.8M L2 gas / 1.09M stepsover the 100M gas / 1M step validate budget.
  • The looped/direct NTT is audit-friendly but expensive; a production account needs the
    cheaper unrolled or hint-based NTT (smaller gas, larger/harder-to-audit TCB, and it
    currently trips the Sierra compiler under snforge). Documented in the crate README/PORTING.

Provenance

Looped NTT/zq ported from s2morrow (StarkWare Industries, MIT); SPDX headers preserved.

Registry: schemes.json flipped to implemented: true, sig_felts: 58, pubkey_felts: 29.

V-k-h added 2 commits July 3, 2026 01:54
Port step 1-2 of PORTING.md: zq (mod-12289 add/sub/mul) and the recursive
NTT/INTT + mul_zq from s2morrow falcon_old (SPDX headers preserved). Validated
under the repo toolchain (scarb 2.18/snforge 0.59): 9 known-answer NTT tests
(n=4..512) + split_ntt pass. Verifier impl still a stub (schemes.json unchanged,
implemented:false). Next: packing + verify + real fixture.
… fixture

- packing.cairo: base-Q unpack (18 coeffs/felt) with canonical-range check
  (the "PK coeffs < Q on read" validation the reference lacked)
- lib.cairo: real Falcon512Verifier — unpack h + (s2, msg_point), s1 = msg_point
  - s2*h via mul_zq (convention-independent), accept iff ||s1||^2+||s2||^2 <= 34034726
- tests/bench.cairo: paired bench_verify_/bench_baseline_ over a REAL fn-dsa
  Falcon-512 signature + a tampered-rejection correctness test
- schemes.json: implemented=true, sig_felts=58 (pack(s2)++pack(msg_point)), pubkey_felts=29
- README: measured status; PORTING.md: implemented
- results/: regenerated via `make all` — verify ~125.8M L2 gas / 1.09M steps
  (over the validate budget; production needs the unrolled/hint NTT)

Looped NTT ported from s2morrow (StarkWare Industries, MIT; SPDX preserved).
@V-k-h

V-k-h commented Jul 3, 2026

Copy link
Copy Markdown
Author

@ericnordelo ready for review — this fills in the falcon_512 stub.

  • Direct-NTT verifier (looped NTT ported from s2morrow, MIT), following PORTING.md.
  • Validated by a real fn-dsa signature: bench_verify_falcon_512 accepts it, falcon_512_rejects_tampered rejects a tampered one; NTT KATs n=4..512 pass. implemented: true flipped only after that gate.
  • make all regenerated results/: verify ≈ 125.8M L2 gas / 1.09M steps — over the 100M/1M validate budget. That's the honest finding — the audit-friendly looped/direct NTT is too heavy for an account today; the cheaper unrolled/hint NTT is the production path (bigger/harder-to-audit TCB, and it currently trips the Sierra compiler under snforge). Called out in the crate README + PORTING.
  • Encoding: pubkey 29 felts pack(h), signature 58 felts pack(s2)++pack(msg_point), hash-to-point off-chain; canonical-range check added on unpack.

Happy to adjust the encoding or add the hint variant as a separate scheme if you'd prefer.

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