Skip to content

Repository files navigation

PQ verifiers for Starknet accounts

CI

Comparing candidate post-quantum signature verifiers for a Starknet account by their verification cost.

Warning

Experimental and unaudited. This repository is intended for research and benchmarking. The verifiers and account contracts have not undergone an independent security audit. This software is provided on an “as is” and “as available” basis, without warranties or liability for any losses incurred through its use.

Why

Starknet's STARK proofs are post-quantum. The ECDSA signatures that authorize account transactions are not, and Shor's algorithm could forge them. Because Starknet accounts are smart contracts (account abstraction), a post-quantum account can ship today by swapping the check inside __validate__, provided that verification fits Starknet's validation limits. This repo measures which schemes fit, and which is cheapest — and ships deployable post-quantum accounts you can try today (see below).

What we measure

For each scheme, the cost of verifying one signature, both alone and inside a real __validate__: L2 gas, Cairo steps, signature and key size, contract class size, and where the steps go.

Validation is capped at 1,000,000 steps and 100,000,000 L2 gas (blockifier v0.13.4; both unchanged as of Starknet 0.14.3), so a verifier has to fit under both.

These are resource measurements — steps and builtins priced with the protocol gas table. Transaction fees meter L2 gas differently for Sierra ≥ 1.7 classes and run between about 1.7× and 2.7× these values, depending on the variant. The CLI prints live receipt gas; the fee meter and devnet-measured receipts per scheme are documented in pq-accounts/USAGE.md.

Snapshot

Current efficiency of every measured entry, per crate (the values pinned in efficiency_baseline.json; caps are 100,000,000 L2 gas and 1,000,000 steps):

Crate Measurement L2 gas Steps % of gas cap
ecdsa_stark verify (classical control) 30,855 152 0.03%
falcon_512 verify, hint variant (BLAKE2s) 12,809,640 104,854 12.8%
falcon_512 verify, direct variant (BLAKE2s) 23,005,760 205,781 23.0%
falcon_512 verify, Poseidon variant (native) 12,045,449 97,681 12.0%
falcon_512 verify, SHAKE-256 variant (standard) 50,163,668 312,892 50.2%
falcon_512 verify, SHAKE-256 direct variant (standard) 60,359,788 413,819 60.4%
bench_targets ECDSA-STARK account, inside __validate__ 160,795 1,437 0.16%
bench_targets Falcon-512 hint account, inside __validate__ 14,380,100 119,124 14.4%
bench_targets Falcon-512 direct account, inside __validate__ 24,525,310 219,565 24.5%
bench_targets Falcon-512 Poseidon account, inside __validate__ 13,615,909 111,950 13.6%
bench_targets Falcon-512 SHAKE-256 account, inside __validate__ 51,733,728 327,157 51.7%
bench_targets Falcon-512 SHAKE-256 direct account, inside __validate__ 61,878,938 427,598 61.9%
ntt forward 512-point transform 8,895,740 81,826 8.9%
ntt forward transform, unreduced output 7,832,270 73,587 7.8%
ntt generated Falcon-512 transform, felt input 2,137,010 15,635 2.1%
ntt generated Falcon-512 transform, u16 input 2,137,010 15,635 2.1%
ntt forward + inverse roundtrip 22,725,980 211,044 22.7%
ml_dsa_44 verify stub (not yet measured)
poseidon_wots verify stub (not yet measured)

ECDSA-STARK is the classical scheme in use today, a cost reference rather than a PQ candidate. Falcon-512 is the first PQ verifier measured, in five variants that share one NTT-domain public key and a canonical 29-felt base-Q encoding. Packed coefficients are validated and decoded directly to u16. The hint variants use two generated, fully unrolled Falcon-512 forward transforms from the shared ntt crate, followed by a fused pointwise hint check and norm pass. The two direct variants (falcon_512_direct, falcon_512_shake_direct) instead compute INTT(NTT(s1) ∘ h_ntt), using the generated forward transform and the generic inverse engine. They trade a more expensive inverse transform for almost half the signature calldata (31 vs 60 felts) and no signer-supplied hint. The variants otherwise differ only in the on-chain hash-to-point that binds the message:

  • BLAKE2s (hint and direct): a non-standard XOF swap via the core::blake builtin. Nearly the cheapest, but needs a matching signer.
  • Poseidon: a native hades_permutation squeeze. Message-binding, the cheapest variant (marginally below BLAKE2s), and the most STARK-proving-friendly since it uses native field arithmetic. Also non-standard.
  • SHAKE-256: the standard Falcon hash-to-point, interoperable with any compliant signer (e.g. falcon.py). The pure-Cairo Keccak-f[1600] (flat unrolled rounds, u128 lanes, lazy block squeezing) dominates this backend, so even the standards-compliant variant fits both validation caps. Its bare hint verifier uses about 4.2× the gas and 3.2× the steps of the native-Poseidon variant. A native keccak_f1600 syscall (SNIP-32) would close the remaining gap. It comes in hint and direct forms; falcon_512_shake_direct carries no hint, mirroring a bare FIPS signature most closely, and is the most expensive of the five.

All five use genuine falcon.py-signed fixtures and fit the validation caps — the non-standard variants with wide margin, and the SHAKE-256 accounts at 51.7–61.9% of the gas cap and 32.7–42.8% of the step cap.

All five derive the message point on-chain from message_hash, which binds the signature to the message. At s2morrow commit 4eff9ab, its packed Poseidon verifier measures 81,212 steps with Scarb 2.15.1 and Starknet Foundry 0.55.0, versus 97,681 here with Scarb 2.18.0 and Starknet Foundry 0.59.0. That comparison is directional, not protocol-equivalent: s2morrow uses a different Poseidon absorb-and-extract construction, while this verifier preserves its existing 16-bit-word rejection stream. The NTT and packing costs here are already comparable to or below s2morrow's; the remaining gap is concentrated in hash-to-point and the fused verification core. Each variant also has an account contract measured inside __validate__, where the realistic cost sits just above bare verify (the verifier dominates; dispatch, the 29-slot key read, and deserialization are small). The remaining PQ verifiers are stubs behind the same interface.

Try the accounts

pq-accounts/ holds deployable SNIP-6 account contracts for every measured verifier, plus three ways to drive them: a CLI whose devnet quickstart declares, funds, deploys, and transacts in one command; an MCP server exposing the same operations to LLM clients; and a local wallet daemon that plugs the accounts into browser dapps (they appear in Voyager's connect-wallet dialog and sign contract calls with Falcon). The walkthrough is pq-accounts/USAGE.md, including deploying to Sepolia.

Report

make report regenerates these from the latest run:

results/report.html      # standalone HTML: sortable table, charts, profiler attribution
results/summary.md       # text summary
results/results.json     # raw data plus metadata

Run

The toolchain is pinned in .tool-versions (Scarb 2.18.0, Starknet Foundry 0.59.0, cairo-profiler 0.16.0, plus starknet-devnet 0.9.0 for the pq-accounts walkthrough). Install it with asdf or starkup.

make all        # measure, profile, then render the report
make test       # run the test suite
make check-eff  # efficiency ratchet: fail if any tracked cost regressed
make ratchet    # lock measured improvements into efficiency_baseline.json

Efficiency is a one-way ratchet: efficiency_baseline.json pins the cost of every tracked benchmark pair (L2 gas and steps), CI fails any change that raises a number, and improvements are committed via make ratchet.

Schemes

Crate Scheme Family Standardization
ecdsa_stark ECDSA-STARK classical EC (control) none
falcon_512 Falcon-512 (FN-DSA); BLAKE2s hint+direct, SHAKE-256 hint+direct, Poseidon variants lattice (NTRU) draft, FIPS 206 (standard SHAKE-256; non-standard BLAKE2s / Poseidon)
ml_dsa_44 ML-DSA-44 (Dilithium) lattice (module) final, FIPS 204
poseidon_wots Poseidon-WOTS+ hashing not standardized

Method

Every scheme implements the one interface in crates/bench_interface; each cost is the measured test minus a baseline test that builds the same inputs but omits the verify call. Bare verify covers the function alone. The validate scenario deploys an account mock from crates/bench_targets and calls it, which adds dispatch, signature deserialization, and a storage read. Numbers come from Starknet Foundry (gas, steps, builtins), a release build (class size), and cairo-profiler (attribution by function).

Layout

crates/
  bench_interface/   # the common PqSignatureVerifier interface plus cap constants
  ecdsa_stark/       # one crate per scheme (see each crate's README)
  falcon_512/
  ml_dsa_44/
  poseidon_wots/
  ntt/               # generic NTT engine + generated Falcon-512 forward path
  bench_targets/     # one account contract per verifier, for the validate scenario
scripts/             # run_bench.py, profile.py, gen_report.py, check_efficiency.py
schemes.json         # the scheme registry
efficiency_baseline.json  # the efficiency ratchet (CI-enforced)
results/             # generated report plus committed snapshot
pq-accounts/         # deployable accounts + CLI, MCP server, browser wallet (USAGE.md)

Adding a scheme is documented in AGENTS.md.

References

License

MIT. See LICENSE.

About

Post Quantum verifiers candidates for a custom Account implementation on Starknet

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages