Testnet live · recursion: 128-bit inner-hash stack complete, on-chain channel replay verified · 2026-07-26

Build the post-quantum migration layer before it becomes urgent

QLSA aggregates thousands of post-quantum signatures into a single constant-size proof, targeting scalable blockchain verification without trusted setup and without forcing a hard fork.

QLSA (Quantum-Layered Signature Aggregation) is an open deep-tech project exploring how ML-DSA, Merkle commitments, and STARK proofs can make post-quantum blockchain systems operationally viable.

FocusInfrastructure, not just cryptography
CI1,011 Solidity + ~560 Python + 516 Rust (152 recursive) + 71 TS — all green
Statusrecursion R0.1–R4.6 ✓ · on-chain replay + binding verified · outer-proof gas: next step

Research prototype — what the on-chain proof does and does not establish

It establishes that the ML-DSA-65 arithmetic relations hold for a committed witness — w' = A·z − c·t1·2d through NTT/INTT, the infinity-norm bound on z, hint decompression and the ω bound, and range checks — proved by a Circle STARK at 130-bit FRI soundness and verified in one Ethereum transaction.

It does not establish that a signature exists. The FIPS 204 hash step (c̃ = SHAKE-256(μ ‖ w1Encode(w1')), c = SampleInBall(c̃)) is outside the circuit. is Fiat-Shamir-bound, but no constraint ties it to w1', so a prover choosing a small-norm z, any valid-shaped c and their own t1 can satisfy every constraint with no signature. Closing this requires SHAKE-256 (Keccak-f[1600]) arithmetized as an AIR — not started, not scheduled.

In the shipped pipeline the prover does run a full ml_dsa_verify before extracting the witness and refuses invalid signatures, so an honest aggregator cannot prove a forgery. That check is off-chain and the contract cannot see it.

One signature per batch is proved, not N. The remaining transactions are committed by the batch Merkle root but their signatures are not proved. The headline above describes the architecture, not a property the deployed contracts enforce. No external cryptographic audit has been performed.

What's been built

Phase 6 complete — live on Ethereum Sepolia (2026-05-05). The V23 production pipeline proves 8 ML-DSA circuits in a single STARK (3,505 trace columns). Since 2026-07-30 the default stack is BatchRegistryV5 + QLSAVerifierVFRI11 — Poseidon2 t=8, both trace groups verified in one atomic transaction at ~6.06M gas, with Merkle-node collision cost ~262 instead of t=4's ~231.

Milestones since then:

  • VFRI11 — Poseidon2 t=8 backend (2026-06-16): 4-word/124-bit nodes → ~262 node collision. At the time, a full-V23 verify() at depth-10 appeared to exceed 100M gas — later shown to be implementation overhead, not the protocol (see R4.8 below).
  • Path decision (2026-06-17): skip a standalone t=16 verifier (~4× worse gas) and go straight to proof recursion — a STARK proving “I verified a VFRI11 STARK”, where the inner hash can be any width for free.
  • Recursion gadget set + soundness closure (R0.1–R3.12): QM31 arithmetic, FRI fold/OODS, inner-hash Merkle path, Fiat-Shamir absorb/draw, per-query composition. Two-expert audits closed every composition-level gap — C1 (outputs pinned in-circuit), C2 (every preprocessed-column verifier pins Tree 0 via a canonical recomputed root), 1a (all verifier-fixed FRI challenges pinned), and Merkle root binding.
  • 128-bit inner-hash ladder complete (R3.13–R3.18): t=2 → t=8 → t=16 (8-word/248-bit nodes, ~2124 ≈ 128-bit), each rung a pure hash-backend swap with compression AIR, Merkle-path AIR and single-/N-query composition.
  • The recursion verifies the real pipeline (R4.1): per-query inputs are extracted from a genuine VFRI11 FRI chain and proven against the committed last-FRI-layer root.
  • On-chain half assembled (R4.2–R4.5): the cheap Poseidon2 channel replay runs on-chain — RecursiveChannelReplay.sol reproduces the Rust reference byte-for-byte, and QLSAVerifierRecursive.sol binds an outer proof to the inner publics via keccak(traceRoot‖lastLayerRoot).
  • The gas barrier was never the protocol (R4.8, 2026-07-30): every recorded "gas wall" was Poseidon2 implementation overhead in Solidity — one t=8 permutation cost ~106k gas while its field arithmetic is ~3k, the rest being memory-array plumbing and a branchy conditional subtract on every addition. Two measurement errors compounded it: a gasLimit above 224 is rejected before execution (EIP-7825), so the "exceeds 29M" path had never actually run; and estimateGas over-provisions nested calls by ~3×. Rewriting with the state on the stack and lazy modular reduction (exact — every S-box mulmod reduces its own output) cut it ~7×. Bit-exactness is guaranteed by the 47 existing cross-check tests against frozen Rust vectors.
  • Full on-chain recursive verification, one transaction (R4.8): verifyRecursive now returns ok=true at 2,289,889 gas. The dual-group V23 submit finalizes in a single transaction at 3.74M (t=4) and 6.06M (t=8) — the two-transaction split that BatchRegistryV6 exists for is now a choice about peak gas, not a requirement.
  • t=8 becomes the default stack (R4.9 / MVP-7, 2026-07-30): strongest available on-chain soundness, atomic, well inside the cap. Validated as a full loop against a live chain — ML-DSA-65 signature → V23 → VFRI11 → finalized — which also exposed a latent bug that made every real submission revert (the registries treat nonce 0 as "unseen", but the 0-based transaction nonce was passed through unshifted; only reachable on a real submit, so dry-runs never showed it).
  • Audit R4.8–R4.9 (2026-07-30): the lazy-reduction claim verified empirically rather than argued — a model mirroring the Solidity byte-for-byte, run against a fully-reduced reference over 3,005 inputs including the maximal reachable value and exactly P: 0 mismatches, peak intermediate 290 against a 2256 ceiling. Three fixes: sponge padding had been cross-checked on neither side (now pinned for every tail length), the registry probe swallowed transport errors, and MAX_SENDERS = 3000 proved unreachable — the O(n²) duplicate scan caps a call at ~212 senders, now measured and documented across all five registries.
Audit R4.8–R4.9 — lazy reduction proven, 3 fixes (2026-07-30)Crypto+code audit. The riskiest claim — that linear layers may add without mod P — was measured: a model mirroring the Solidity byte-for-byte vs a fully-reduced reference over 3,005 inputs (max reachable 2^32−1, exactly P, P±1) → 0 mismatches, peak 2^32/2^90/2^75 for t=2/4/8 vs a 2^256 ceiling. [Fixed] sponge padding was cross-checked on neither side (only rate-multiple lengths) → authoritative Rust vectors frozen for every tail residue; [Fixed] registry probe swallowed transport errors, so an RPC outage let a mismatched submit proceed; [Fixed] MAX_SENDERS=3000 unreachable — O(n²) scan ≈201 gas/n² caps a call at ~212, failing with out-of-gas rather than a clean revert. 1019 Solidity · 589 Python · 521 Rust · 0 warnings.
MVP-7 / R4.9 — t=8 is the default stack (2026-07-30)QLSAVerifierVFRI11 + BatchRegistryV5: node collision ~2^62 vs t=4's ~2^31, both trace groups in ONE atomic transaction. Validated as a full loop on a live chain (ML-DSA-65 → V23 → VFRI11 → finalized, 6,150,487 gas), which exposed a latent bug making every real submit revert with SenderNonceTooLow(0,1) — the registries treat 0 as "unseen" but the 0-based tx nonce was passed unshifted. A registry-shape probe now rejects a mismatched REGISTRY_ADDRESS up front.
Audit — what is actually proved (2026-07-31)Three passes (crypto/security, code, optimisation). Main finding: the ML-DSA hash step is outside the circuit, so the on-chain proof does not establish that a signature exists; and only tx[0] of a batch carries a witness proof. Both are now stated at the top of the README and of the agent guide. Clean on inspection: no tracked secrets, no keys logged, bandit clean, constant-time bearer comparison, rate limiting, zeroize-backed key wipe, nonce registry plus replay guard. Also found: the aggregator/API/SDK layer emits VFRI7–VFRI10, which the default registry rejects by design, so only the testnet path reaches a deployed stack.
R4.22 — t=16: 128-bit node collision in ONE transaction (2026-07-31)QLSAVerifierVFRI12 (Poseidon2 t=16, 8-word/248-bit nodes → ~2124) finalizes both full-V23 groups at 15,436,509 gas, inside the 16,777,216 cap. This reversed a recorded decision to skip a standalone t=16 verifier, which had been made from a ">100M gas" figure that was already an artefact. Scope: 128-bit is the node bound; FRI soundness is separate and is 16-bit at the fixture's one query. The cost driver turned out to be the channel's absorb count, not the width — t=16's sponge has rate 8 and had inherited rate-1 absorb from the narrower widths.
R4.23 — measurement discipline made enforceable (2026-07-31)Three strategic conclusions here were reversed because a number was wrong, and each correction was appended beside the wrong claim instead of replacing it — nine places still asserted superseded figures as current. That is causal: the "skip t=16" decision came from a stale number left in place. Gas figures now live in one fixture that a test re-measures, failing on >3% drift, and which asserts the claims too. This also corrected an error: the "1.79× per permutation" ratio had the shared 21,000-gas transaction base in both terms; marginal cost is 3.04×, so t=16 is dearer per bit of node capacity, not cheaper.
R4.8 — the gas barrier removed (2026-07-30)Every recorded "gas wall" was Poseidon2 implementation overhead, not the protocol: ~106k gas per t=8 permutation of which the field arithmetic is ~3k. Stack state + lazy modular reduction cut it ~7×, bit-exact against 47 frozen Rust cross-check vectors (Rust unchanged). Measured gasUsed: full verifyRecursive 2.29M, ok=true; VFRI10 dual submit 3.74M; VFRI11 (t=8) dual submit 6.06M — all one transaction, inside the 16.78M EIP-7825 cap. Method note: measure gasUsed of a sent transaction — estimateGas over-provisions nested calls ~3×, and a gasLimit above 2^24 is rejected before execution.
Recursion audit R3.13–R4.1 — HIGH input/output binding fixed (2026-07-16)Crypto+code audit of the t=8/t=16 stack + VFRI11 bridge. [HIGH — fixed] poseidon2_t{8,16}_air verify_compress bound (left,right,node) only via Fiat-Shamir → a false compress(FAKE)=node claim could verify (latent, test-only, not production); now pinned in-circuit (raw_pin/node_pin + gated equality, regressions test_forged_input_cannot_prove). + t=16 matrix naive cross-checks + stale-doc/capacity fixes. All else clean. 152 recursive tests, 513 total.
Recursion R4.1 — verifies the REAL VFRI11 pipeline (2026-07-16)Shared vfri11_fri_chain (byte-identical to the ABI hint generator) + gen_vfri11_recursion_inputs: the t=8 composition proves the inner proof's per-query decommitments against the genuine committed last-FRI-layer root. "Root vs committed FRI-layer root" closed at the Rust level; index-oriented twiddle-inverse trick + hard extraction invariant.
Recursion R3.13–R3.18 — 128-bit inner-hash ladder COMPLETE (2026-07-13/16)t=8 (4-word/124-bit → 2^62) and t=16 (8-word/248-bit → ~2^124 ≈ 128-bit, Stwo's native Poseidon2-16 width): compression AIRs + Merkle-path AIRs + single-/N-query compositions. Each rung a pure hash-backend swap binding leaf = hash(finalFold) end-to-end in-circuit. 150→152 recursive tests.
Recursion audit R3.12 — Merkle root binding closed + input caps (2026-07-10)Crypto+code audit of R3.8–R3.11. [C1 root — fixed] claimed root pinned in-circuit (is_root/root preproc columns, is_root·(s0 − root_pinned)=0); before, a malicious prover could prove a FALSE root claim with adversarial siblings (only reuse was blocked). depth is an explicit public input, matching on-chain MerkleVerifier.verify. Regression test_forged_root_cannot_prove. [Code — fixed] MAX_QUERIES/MAX_NUM_FOLDS/MAX_DEPTH/log_size/capacity caps close panic/OOM paths (div-by-zero at depth 0, OOB preproc writes, 2^40 allocs). 104 recursive tests, 453 total.
Recursion R3.10–R3.11 — FRI cherry-pick (1a) fully closed (2026-06-17)Design realization: the cheap Poseidon2 channel stays on-chain → challenges are public inputs, no logup needed. QueryChallenges bundle (alpha, z_x, px, inv) pinned via 17 preprocessed columns + in-circuit equality constraints; threaded through single/multi/composition. test_forged_alpha_cannot_prove, test_forged_zx_inv_px_cannot_prove.
Recursion R3.8–R3.9 — multi-gadget composition, single + N-query (2026-06-17)composition.rs: recursive_verifier + merkle_path in ONE multi-component STARK — fold chain → hashLeaf → Merkle path, value-bound end-to-end; N-query (VFRI11 shape) via multi-path merkle (N paths in one component, AIR unchanged).
Recursion R3.7 — C1/C2 CLOSED codebase-wide (2026-06-17)[C1] outputs pinned via fin preproc columns + is_output-gated constraint; [C2] canonical Tree-0 pinning (canonical_preproc_root) in ALL preprocessed-column verifiers: 4 recursion sub-gadgets + 5 production V23 is_init_uh verifiers + Poseidon2 hash-chain. Forged selectors no longer verify.
Recursion audit — 2 confirmed soundness blockers (2026-06-17)Two-expert audit (crypto + code) of the full recursive gadget set. The gadgets are correct arithmetic relation provers, but composition is not yet sound vs a malicious prover: [C1] public outputs (root/finalFold/digest) bound only via Fiat-Shamir, no in-circuit constraint; [C2, reproduced] preprocessed selectors/constants prover-supplied & unpinned (forged is_step verifies). Same pattern in mature V23/VFRI — per-circuit follow-up. Robustness fixes applied (caps, guards, asserts). Must close C1/C2 before production wiring (R3.7). Resolved: see R3.7–R3.12 above.
Recursion R3.6 — Fiat-Shamir draw AIR (2026-06-17)transcript_draw_air.rs: proves the Poseidon2 t=2 duplex-sponge squeeze chain (drawSecureFelt/drawQueries core) — the dual of channel_air absorb. 8 main + 7 preproc cols; matches P2Channel::draw_pair. 11 Rust tests. Full recursion gadget set complete: 88 tests.
Recursion R3.5 — multi-query aggregation (2026-06-17)prove_recursive_queries: N per-query FRI chains in ONE STARK — N blocks of (1+K) rows, the same AIR (per-row selectors gate each block), all finalFolds bound. 5 Rust tests.
Recursion R3.3–R3.4 — per-query recursive verifier + integration (2026-06-17)recursive_verifier.rs: OODS± + circle fold + K line folds in ONE AIR, data flow enforced by a cross-row constraint (42 main + 2 preproc). integration.rs: recursive_verifier → qm31_leaf_hash → merkle_path_air — full per-query FRI verification value-bound across 3 sub-proofs. 9+3 Rust tests.
Recursion R3.2 — FRI fold chain AIR (2026-06-17)fri_fold_chain_air.rs: K line-fold rounds chained via a cross-row constraint input[k]=output[k−1] (21 main + 1 preproc). 9 Rust tests.
Recursion R3.1 — per-query FRI step AIR (2026-06-17)recursive/query_step_air.rs: first composition gadget — chains OODS± + circle fold in one row per query. 42 cols, 16 degree-2 constraints; step_ref() cross-checks against oods_air + fold_air references; generic qmul<F> deduplicates QM31-mul expansion. 7 Rust tests.
Recursion R2 — Merkle path + Fiat-Shamir transcript AIRs (2026-06-17)merkle_path_air.rs: proves leaf@index+siblings→root via Poseidon2 t=2 compression (10 main + 4 preproc cols; index-bit child selection; cross-row cur chaining). channel_air.rs: proves Poseidon2 duplex sponge absorption — mixU32s core (7 main + 4 preproc cols). 10+9 Rust tests.
Recursion R1 — FRI fold + OODS quotient AIRs (2026-06-17)fold_air.rs: proves folded=(f₊+f₋)+α·(f₊−f₋)·inv (21 cols; helper column p lowers degree to 2). oods_air.rs: proves fₚ·(px−z_x)=compValue−oodsCombo (17 cols; multiplicative form avoids QM31 inverse). 8+8 Rust tests.
Security + code audit — VFRI11/t=8/recursion (2026-06-17)Two-expert review (crypto/blockchain + Rust/systems) of the diff. No Critical/High/Medium. QM31-mul AIR soundness hand-verified (4 degree-2 constraints exactly pin each z limb). Fixed: deploy_v6.sh --network flag (silently ignored → wrong-network deploy risk), CLI arg validation, web3 HTTP timeouts, .env.deployed 0600, t=8 sponge % P input reduction (Rust parity), QM31 limb-canonicity precondition.
Recursion R0.1 — QM31-mul AIR (2026-06-17)recursive/qm31_mul_air.rs proves z = x·y in QM31 = CM31[u]/(u²−R), R = 2+i. 12 cols, 4 degree-2 constraints, no preproc; full Stwo prove/verify roundtrip + cross-check against the u128 reference (u²=R, 1·y=y). The load-bearing primitive for circleFold/lineFold/OODS. Decision: skip the standalone t=16 verifier — t=16 becomes the recursion's inner hash AIR (constant on-chain cost).
Poseidon2 t=8 backend + VFRI11 (2026-06-16)poseidon2_t8.rs + Poseidon2M31T8.sol / Poseidon2MerkleVerifierT8 (4-word/124-bit nodes → ~2^62 collision) / Poseidon2ChannelT8 (217-bit capacity), cross-checked bit-exact. QLSAVerifierVFRI11 = VFRI10 protocol on t=8 (identical ABI, version marker 5); generic verify()==true. The contemporaneous "full-V23 t=8 verify >100M gas" finding was later traced to Poseidon2 implementation overhead, not the width — see R4.8: both full-V23 t=8 groups now verify in ONE transaction at ~6.06M gas.
Security + code audit (2026-06-14)Two-expert audit (crypto/blockchain + systems). Off-chain replay guard (ReplayedTxError — a batched tx can no longer be re-batched), submit error-text hardening, /stats overflow metric, #[cfg(test)] gating test fixtures out of the release library, and an FRI tree_depth ∈ 2..=30 guard. 6 fixed, 1 false-positive, 2 documented research-defaults.
VFRI10 — Poseidon2 t=4 backend (2026-06-14)QLSAVerifierVFRI10 wires the t=4 wide Merkle (Poseidon2MerkleVerifierT4) + t=4 Fiat-Shamir channel (Poseidon2ChannelT4) into the VFRI9 proof path (byte-identical ABI, version marker 4). V23 cross-bound Rust/PyO3/Python pipeline + dual-group on-chain E2E via BatchRegistryV5. Each V23 group verify() ≤16.7M gas; lifts the node/transcript collision wall above the t=2 ceiling (~2^31).
VFRI9 aggregator pipeline + SDK (2026-06-12)BatchResult.has_vfri9 + vfri9 proof/commitment/hints fields; Batcher._try_prove calls prove_mldsa_sig_vfri9_stark; API /batch/run, /batch/{id}/witness, /batches expose has_vfri9; Python SDK WitnessStatus/BatchStatus extended; JS SDK WitnessStatus/BatchStatus gain hasVfri9 + RawBatchStatus deduplication. pyo3 0.24→0.29: RUSTSEC-2026-0176/0177 fixed.
VFRI9 — last-layer FRI check + wide Poseidon2 (2026-06-10)QLSAVerifierVFRI9: prover supplies all 2^(treeDepth−K) final FRI evaluations; verifier rebuilds Merkle tree with wide nodes (62-bit: (s0<<32)|s1) and asserts root == friLayerRoots[K] — closes bounded-degree soundness gap in VFRI5–VFRI8. Full-root Fiat-Shamir: mixRootFull absorbs all 32 bytes. BatchRegistryV5 dual-VFRI9.
Security & code audit (2026-06-10)21 findings (18 fixed): VFRI8 OODS guard, num_folds cross-bound param, short-proof calldataload guard in BatchRegistryV4/V5, API rate limit on /stats+/config, VFRI8 witness_commitment fallback, _sender_txs eviction sync, dead _call_prover removed. Aggregator liveness: prover-crash recovery (≤3 retries, txs returned to mempool), prepend_batch overflow accounting, Bearer-token API auth.
VFRI8 — Poseidon2 trace commitment (2026-06-10)QLSAVerifierVFRI8: Poseidon2 Merkle trees + Poseidon2 Fiat-Shamir channel. 20-query on-chain FRI ≤15M gas. BatchRegistryV5 dual-VFRI8.
Code audit (2026-06-03)HttpClient._decode_json() guards all 7 JSON call-sites — proxy HTML responses (nginx 2xx) no longer crash the SDK. testnet/e2e.py sender_key recomputation eliminated; unused import hashlib removed. 239 Python tests green.
Cryptographic coreML-DSA-65/44/87, SHA3-512 Merkle, address derivation, pubkey validation, batch aggregation
STARK prover (Stwo 2.2.0) — V23Circle STARK, Poseidon2-over-M31 AIR, 8 ML-DSA circuits in 1 FRI commitment (3,505 trace columns): NttBatch + AzFull + Ct1Full + InttBatch + WPrimeFull + NormCheck + UseHintV2 + RangeQBatch (az_hat ∈ [0,Q))
Smart contracts — deployed SepoliaBatchRegistryV2/V3/V4/V5 (nonce replay protection, MAX_SENDERS DoS cap), QLSAVerifierFull + VFRI7–VFRI10 (O(1)-gas FRI, cross-proof binding, Poseidon2 t=2/t=4 Merkle + channel, last-layer check), 950 Solidity tests
Aggregator node + APIThread-safe mempool (atomic drain), auto-batcher, FastAPI REST, rate limiting (100 tx/min per IP, proxy-aware), pk/sig length validation
SDKs (Python + TypeScript)Wallet, LocalClient + HttpClient, WitnessStatus, TypeScript AggregatorClient — mypy strict
Testnet E2E (Sepolia, 2026-05-05)First batch: 4 tx, 3234-byte proof, 9.16 s finalization. submitBatchWithNonces on-chain.
On-chain FRI verifier library stackCM31 · QM31 · Blake2sYul · MerkleVerifier · TwoChannel · CirclePoint · QLSAVerifierV4–V13 · VFRI/VFRI2/VFRI3/VFRI4/VFRI5/VFRI6 — full K-round FRI + OODS + composition tree + O(1)-gas off-chain OODS combo
Security audit round 1 (2026-05-22)Constant-time Merkle (hmac.compare_digest), rate-limit thread-safety, MAX_SENDERS=3000 DoS cap (O(n²) loop), y=0 circle guard (M31.inv panic), _MAX_HISTORY=1000 memory cap, PRIVATE_KEY env fallback
BatchRegistryV4 — dual-VFRI7 (2026-05-30)Requires both LOG=10 (1298 cols) and LOG=8 (2206 cols) VFRI7 proofs. Cross-proof binding: boundRoot10 = keccak256(batchRoot ‖ traceRoot8) — mixing proofs from different witnesses fails Merkle verification
Security audit round 2 (2026-05-30)O(1) batch history (deque + dict index, eliminates O(n) scan DoS), N_FRI_QUERIES range validation, IP address validation for TRUSTED_PROXIES, ML-DSA pubkey size guard, UUID input validation, GET /batch/* rate limit, module-level logger — 24 total findings resolved

The Problem

Direct migration from classical signatures to post-quantum signatures can severely inflate block payloads. That turns protocol safety progress into an operational scaling problem.

Signature Size 3000 tx block Operational impact
ECDSA (current)~70 bytes~220 KBFits today's assumptions
ML-DSA-65 (FIPS 204)~2,420 bytes~7.2 MBSevere throughput penalty
~30–40×Payload overhead from direct migration
FIPS 204Standardized signature base
Hash-firstNo trusted setup in the proving path
L2-readyDeployment path without hard fork

The Solution

QLSA is not a new signature scheme. It is a post-quantum aggregation layer that aims to make large signature sets proof-friendly, compact, and verifiable at fixed on-chain cost.

What QLSA does

  • Aggregates N ML-DSA signatures into one batch commitment
  • Uses Merkle commitments to structure batch data
  • Generates a STARK proof for batch correctness
  • Allows fixed-cost on-chain verification independent of batch size

Why the architecture matters

  • Heavy proving remains off-chain
  • Public inputs can be kept minimal with commitment roots
  • MVP can ship before full ML-DSA-inside-AIR is solved
  • The system remains crypto-agile across future upgrades

Architecture

The system is split into three layers so that user flows stay familiar, proving stays off-chain, and on-chain verification remains lean.

Layer 1

Signing

Wallet-level signing using post-quantum keys.

  • ML-DSA-65 for signatures
  • Address derived from SHA3-256(pubkey)
  • Intended to preserve a familiar wallet-style UX
Layer 2

Aggregation

Batch formation and proving pipeline.

  • Collect transactions off-chain
  • Verify ML-DSA signatures off-chain; V23 proves arithmetic witness (8 circuits) inside STARK
  • Build Merkle tree with SHA3-512; root bound into Fiat-Shamir channel (V22+)
  • Generate STARK proof — c̃ + merkle_root mixed; RangeQBatch proves az_hat ∈ [0,Q)
Layer 3

Verification

Constant-cost contract-side validation.

  • Verify proof in Solidity verifier contract
  • Store merkle_root and finalize the batch
  • onchain_commitment = Blake2s(proof[:32] ∥ c_tilde[:32])[:16] — 128-bit binding
  • MVP-6: VFRI8 — Poseidon2 Merkle + Fiat-Shamir channel; 20-query verification ≤15M gas. Both LOG=10 and LOG=8 trace groups verified on-chain with 130-bit soundness.

MVP Strategy

The project intentionally separates realistic implementation milestones from the hardest research component.

StageWhat STARK provesML-DSA in STARKStatus
MVP-1Core crypto & MerkleNo✓ Done
MVP-2Hash-chain + Poseidon2 AIR (Stwo)No✓ Done
MVP-2bML-DSA batch proof (Rust + STARK)Off-chain✓ Done
MVP-3+All 8 circuits inside AIR + RangeQBatch (V23)Yes (V23)✓ Complete
MVP-4VFRI4/5/6 — O(1)-gas on-chain FRI verifier; BatchRegistryV4Yes✓ Done (2026-05-22)
MVP-5VFRI7 cross-proof binding; security audit round 2; code quality hardeningYes✓ Done (2026-05-30)
VFRI8/9Poseidon2 Merkle+channel; last-layer FRI check; wide nodes (62-bit); aggregator liveness; API authYes✓ Done (2026-06-10)
MVP-6 groundworkPoseidon2 t=4 Rust+Solidity; VFRI9 pipeline; pyo3 CVE fix; JS SDK parityYes✓ Done (2026-06-12)
MVP-6 VFRI10t=4 verifier + V23 pipeline + dual-group E2E; security/code auditYes✓ Done (2026-06-14)
MVP-6 t=8BatchRegistryV6 per-group split; Poseidon2 t=8 backend; VFRI11 (~2^62 nodes)Yes✓ Done (2026-06-16)
Recursion R0.1QM31-mul AIR gadget; path decision: skip standalone t=16, t=16 → inner hash of recursionYes✓ Done (2026-06-17)
Recursion R1FRI fold AIR + OODS quotient AIR — 8+8 Rust testsYes✓ Done (2026-06-17)
Recursion R2Poseidon2 Merkle auth-path AIR + Fiat-Shamir sponge-transcript AIR — 10+9 Rust testsYes✓ Done (2026-06-17)
Recursion R3.1Per-query FRI step (first composition gadget: OODS± + circle fold) — 7 Rust testsYes✓ Done (2026-06-17)
Recursion R3.2–R3.6FRI fold chain, per-query verifier, integration, N-query aggregation, Fiat-Shamir draw — full gadget set (88 tests)Yes✓ Done (2026-06-17)
Recursion auditC1/C2 codebase-wide (R3.7), 1a (R3.10–11), Merkle root binding (R3.12), compression input/output binding (R3.13–R4.1)Yes✓ Done (2026-07-16)
Recursion R3.13–R4.1128-bit inner-hash ladder (t=8/t=16 compression + path + composition) + verifies real VFRI11 pipeline — 152 recursive testsYes✓ Done (2026-07-16)
Recursion R3.7–R3.12Composition (single + N-query, VFRI11 shape), QueryChallenges pinning, root binding — 104 recursive testsYes✓ Done (2026-07-10)
Recursion R3–R4Full recursive verifier composition → QLSAVerifierRecursive (~5M gas constant)Yes⏳ In Progress
MVP-6VFRI8: Poseidon2 Merkle + channel; 20-query ≤15M gas; BatchRegistryV5; security & code auditYes✓ Done (2026-06-10)

Key risks

  • ML-DSA inside STARK: All 8 circuits complete in V23 (NttBatch + AzFull + Ct1Full + InttBatch + WPrimeFull + NormCheckBatch + UseHintBatchV2 + RangeQBatch), 3,505 trace columns, 1 FRI commitment. c̃ + Merkle root Fiat-Shamir bound; RangeQBatch closes AzFull multiplication soundness gap.
  • Aggregator trust model: the MVP relies on off-chain verification before challenge systems exist.
  • Adoption timing: the need is real, but mainstream migration may still take years.
Research-driven Benchmark-first Security-aware

Tech Stack

The technical stack reflects a staged path: fast iteration now, stronger proving infrastructure later.

Cryptographic core

  • ML-DSA-65
  • SHA3-512 for Merkle hashing
  • SHA3-256 for address derivation
  • Python 3.10+
  • liboqs-python >= 0.14.0

STARK layer

  • Stwo 2.2.0 — Circle STARK over M31 field
  • Poseidon2-over-M31 AIR (t=2, α=5, 8 full rounds, 7 columns, 6 constraints ≤ deg 3)
  • V23 single STARK: 8 ML-DSA circuits, 3,505 trace columns, 1 FRI commitment
  • ML-DSA-65 FIPS 204 verifier in Rust; c̃ + Merkle root Fiat-Shamir binding
  • FRI blowup ×64, N_FRI_QUERIES=20, POW_BITS=10 — 130-bit soundness; zeroize crate for secure key wipe
  • nightly-2025-07-01 Rust toolchain

Contracts & infra

  • Solidity + Hardhat 2.28
  • VFRI8 verifier stack: CM31 · QM31 · Blake2s · Poseidon2M31 · Poseidon2MerkleVerifier · Poseidon2Channel · CirclePoint · QLSAVerifierVFRI8
  • BatchRegistryV5 — dual-VFRI8 with cross-proof binding
  • FastAPI aggregator + rate limiting (proxy-aware, stale eviction)
  • Python SDK (Wallet, LocalClient, WitnessStatus) + TypeScript SDK

Performance targets

Batch size3,000 tx
Proof size90–200 KB
On-chain verificationO(1)
ML-DSA-65 sign (avg)0.12 ms / tx
Merkle build N=30007.1 ms
Full pipeline N=3000354 ms
V23 STARK columns3,505 (649+1523+295+649+24+15+61+288+1 preproc)
20-query VFRI9 gas≤15M gas (wide Poseidon2 Merkle, last-layer check)
Solidity tests1,011 passing
Python tests~560 passing (with PyO3) / ~350 without
Rust tests516 passing (152 recursive)
TS tests71 passing
CI pipeline6 / 6 jobs green

Repository snapshot

QLSA/
├── core/          # ML-DSA, Merkle, batch
├── aggregator/    # Mempool, batcher, FastAPI
├── stark/         # Python prover/verifier wrappers
├── stark_stwo/    # Stwo STARK + ML-DSA AIR circuits
├── contracts/
│   └── src/verifier/
│       ├── CM31.sol       # GF((2³¹−1)[i])
│       ├── QM31.sol       # quartic extension
│       ├── Blake2s.sol    # pure-Solidity
│       ├── Poseidon2M31.sol      # t=2 over M31
│       ├── Poseidon2M31T4.sol    # t=4 over M31 (VFRI10)
│       ├── Poseidon2MerkleVerifier{,W,T4}.sol  # t=2 / wide / t=4 nodes
│       ├── Poseidon2Channel{,T4}.sol  # Fiat-Shamir (t=2 / t=4)
│       ├── MerkleVerifier.sol
│       ├── TwoChannel.sol # Fiat-Shamir (Blake2s)
│       └── CirclePoint.sol # CanonicCoset + FRI fold
├── sdk/python/    # Wallet, LocalClient
├── sdk/js/        # TypeScript AggregatorClient
├── testnet/       # E2E demo, deploy, submit
└── tests/         # 1011 Sol + ~560 Py + 516 Rust + 71 TS

Roadmap

The roadmap prioritizes a credible implementation path before expanding into the hardest proving research.

Phase 1 — Core

✓ Complete

ML-DSA-65/44/87 keys, signing, transaction model, SHA3-512 Merkle tree, batch aggregation.

Phase 2 — STARK Prototype

✓ Complete

Stwo 2.2.0 Circle STARK — Poseidon2-over-M31 AIR (t=2, α=5, 8 full rounds, 7 main columns, 4 preproc columns, 6 constraints ≤ deg 3), ML-DSA-65 batch verifier in Rust (FIPS 204), FRI blowup ×4. 128-bit Blake2s commitment. 210 Rust tests green.

Phase 3 — Smart Contracts

✓ Complete

QLSAVerifierFull, QLSAVerifierBound (Merkle-root-bound Blake2s, bytes16 128-bit commitment), BatchRegistry, BatchRegistryV2 — all deployed (Hardhat). Contract tests green.

Phase 4 — Aggregator Node

✓ Complete

In-memory mempool (thread-safe, atomic drain), auto-batcher with transaction recovery, FastAPI REST endpoint. Full STARK prove→submit pipeline via PyO3 native FFI.

Phase 5 — SDK

✓ Complete

Python SDK (wallet + async client) and TypeScript/JavaScript SDK with full input validation and typed API. wipe_key() backed by Rust zeroize crate (volatile writes) for secure key wipe.

Phase 6 — Testnet

✓ Complete

Live on Ethereum Sepolia (2026-05-05). QLSAVerifierBound: 0xF2dcad845F0fb4a11106E4c4cF23D260a3CB65Dd · BatchRegistryV2: 0xF44A51CF1d7620e070ecaf611685418DE4679879. First batch: tx 0x5a9215d2aeb4…bd82, 3234 bytes, finalized in 9.16 s. Security hardening: nonce registry, rate limiting, pubkey validation.

MVP-3+ — ML-DSA Native in AIR (V22/V23)

✓ Complete

All 8 ML-DSA-65 arithmetic circuits unified in one FRI commitment (V23, 3,505 trace columns): NttBatch (LOG=10, 649 cols) + AzFull (LOG=8, 1523 cols) + Ct1Full (LOG=8, 295 cols) + InttBatch (LOG=10, 649 cols) + WPrimeFull (LOG=8, 24 cols) + NormCheckBatch (LOG=8, 15 cols) + UseHintBatchV2 (LOG=8, 61 cols + 1 preproc) + RangeQBatch (LOG=8, 288 cols). Fiat-Shamir transcript: c̃ → merkle_root → Tree0 → Tree1. RangeQBatch closes AzFull multiplication soundness gap.

MVP-4 — Mainnet-grade On-chain Verifier

✓ Complete (2026-05-22)

Full on-chain Circle STARK FRI verifier (Solidity). Verifier library stack: CM31 · QM31 · Blake2sYul · MerkleVerifier · TwoChannel · CirclePoint · QLSAVerifierV4–V13 · VFRI–VFRI6. QLSAVerifierVFRI6: O(1)-gas off-chain OODS combo — 649, 1298 and 2206-column traces all verified within 15M gas. BatchRegistryV4: dual-VFRI6 registry.

MVP-5 — Cross-Proof Binding & Security Hardening

✓ Complete (2026-05-30)

QLSAVerifierVFRI7: adds mixRoot(merkleRoot) before drawQueries — FRI query indices depend on the batch Merkle root, preventing proof-mixing attacks. BatchRegistryV4: boundRoot10 = keccak256(batchRoot ‖ traceRoot8) — cross-binding both LOG groups. Security audit round 2: 24 findings resolved. Code audit 2026-06-03.

VFRI8 + VFRI9 — Poseidon2 + Last-layer FRI (2026-06-10)

✓ Complete

VFRI8: Poseidon2 Merkle trees + Poseidon2Channel — 20-query on-chain FRI ≤15M gas. VFRI9: last-layer bounded-degree check (prover supplies all 2^(treeDepth−K) final evaluations; verifier rebuilds tree with wide 62-bit nodes and asserts root match) — closes the FRI soundness gap in VFRI5–VFRI8. Full-root Fiat-Shamir (mixRootFull: all 32 bytes). Aggregator liveness: prover-crash recovery (≤3 retries), prepend_batch overflow accounting, Bearer-token API auth. Security & code audit: 21 findings, 18 fixed. BatchRegistryV5: dual-VFRI9. 917 Solidity + 552 Python + 315 Rust + 71 TS.

MVP-6 — VFRI10 (Poseidon2 t=4 backend) + audit (2026-06-14)

✓ Complete

The Poseidon2 t=4 permutation (poseidon2_t4.rs + Poseidon2M31T4.sol, cross-checked bit-exact, 124-bit compress, collision ~2^62) is wired into the full proof path: QLSAVerifierVFRI10 swaps the t=2 Merkle/channel for Poseidon2MerkleVerifierT4 + Poseidon2ChannelT4 (byte-identical VFRI9 ABI, version marker 4). V23 cross-bound Rust bridges + PyO3 + Python (prove_mldsa_sig_vfri10_stark) + on-chain dual-group E2E via BatchRegistryV5 — each V23 group verify() ≤16.7M gas (dual-group submitBatch needs a per-group registry or mainnet's 30M). Security + code audit: off-chain replay guard, submit error-text hardening, /stats overflow metric, release-build test-fixture gating, FRI tree_depth guard. 950 Solidity + ~560 Python + 323 Rust + 71 TS.

FAQ

Short answers to the questions most people will ask when they first encounter the project.

Is QLSA a new signature algorithm?

No. QLSA is an aggregation and verification architecture built around post-quantum signatures such as ML-DSA.

Why not just replace ECDSA directly?

Because the size increase of post-quantum signatures can cause major throughput and cost problems if deployed naively.

Does QLSA already prove ML-DSA inside STARK?

Yes — V23 is the production pipeline. All 8 ML-DSA-65 arithmetic circuits are unified in one FRI commitment (3,505 trace columns): NttBatch + AzFull + Ct1Full + InttBatch + WPrimeFull + NormCheckBatch + UseHintBatchV2 + RangeQBatch. c̃ and batch Merkle root are both mixed into Fiat-Shamir, binding each proof to one specific witness and batch.

What on-chain verifier exists?

The latest is QLSAVerifierVFRI10 + BatchRegistryV5 (2026-06-14): the VFRI9 proof protocol (last-layer FRI check + full-root Fiat-Shamir) on the Poseidon2 t=4 hash backend, lifting the node/transcript collision wall above the t=2 ceiling (~2^31) toward 128-bit. Each V23 trace group (LOG=10 / LOG=8) verify() fits within 16.7M gas; the dual-group call needs a per-group registry or mainnet's 30M block limit. Off-chain FRI soundness: LOG_BLOWUP=6 × 20 queries + POW_BITS=10 = 130-bit.

Is this production-ready?

No. QLSA is research-stage software (not audited by external parties; an internal two-expert audit ran 2026-06-14). Infrastructure is fully implemented and tested (950 Solidity + ~560 Python + 323 Rust + 71 TS tests). Not recommended for production use or real funds.

Interested in PQC, ZK proofs, or blockchain infrastructure?

QLSA is an open research and engineering effort. Contributions, technical feedback, research discussion, and collaboration are welcome.