QuantaStark

Research project. Unaudited.
Measured on a local QRL 2.0 network on 25 August 2026.

Post-quantum STARK verifier for QRL 2.0

QuantaStark verifies Plonky3-compatible STARK proofs on the 512-bit QRL 2.0 virtual machine. The verifier is written in Hyperion and the proof system is hash-based from the prover to the bridge, so the proofs are post-quantum by construction. One verification of the c3 preset costs 2,185,178 gas at 212 trace rows and 4,463,257 gas at 220, within the 20,000,000 gas block cap.

The QRL 2.0 virtual machine offers hashing, modular exponentiation and ML-DSA-87 signature verification as precompiles and has no pairing operation, so a validity proof on QRL takes the hash-and-field-arithmetic shape of a STARK. QuantaStark exists to measure what such proofs cost on QRL 2.0 and to ground an L2 design in those costs. The repository holds the verifier, the Rust prover and its test vectors, a fact registry, a bridge skeleton for the L1 side of a rollup, and the architecture decision derived from the measurements.

Source
github.com/DigitalGuards/QuantaStark, GPL-3.0
Documents
L2 architecture decision, gas report, verifier, protocol, bridge
Authors
DigitalGuards

1 Measurements

Every number is a receipt or a gas estimate from the gqrl developer node at the 20,000,000 block gas limit, optimizer runs 200, with one verifier deployment per parameter preset. Transaction gas is the whole verifyAndLog transaction through the gas meter; the last column is the gasleft() delta around the verifier call. The generated report has all 44 cells under three optimizer settings.

Table 1. Headline cells of the c1, c2 and c3 presets: whole verifyAndLog transactions on the gqrl developer node, optimizer runs 200.
Cell Proof bytes Transaction gas Inside the verifier
fib_c3_n12 34 queries, log_blowup 3, 212 rows 43,440 2,185,178 1,447,427
fib_c3_n20 34 queries, 220 rows 105,873 4,463,257 2,710,754
fib_c2_n20 50 queries, log_blowup 2, 220 rows 134,801 5,920,483 * 3,696,128
fib_c1_n12 100 queries, log_blowup 1, 212 rows 82,672 4,937,007 3,561,810
fib_c1_n20 100 queries, 220 rows 226,641 10,534,049 * 6,807,827

* The transaction exceeds the 131,072-byte transaction-pool cap of the execution client, so its gas comes from qrl_estimateGas and qrl_call. Source: docs/GAS-REPORT.md.

Block cap. 40 of the 44 measured cells sit at or below the 8,000,000 gas target. The largest, fib_c1-binary_n20 with a 388 KB proof, takes 17,065,391 gas, 85 percent of the 20,000,000 consensus block cap.

Transaction-pool limit. The execution client refuses transactions above 131,072 bytes, so proofs above about 123 KB need a raised pool cap or staged verification before they can be submitted as one transaction. Calldata is 20 to 43 percent of a verification transaction, and the FRI fold chains are 52 percent of the verifier's own execution at 212 rows.

Code size and tests. The verifier is 14,458 bytes of runtime code against a 24,576-byte cap. It accepts every valid vector of the sixteen presets and rejects every mutated vector with its recorded custom error. 618 unit tests check the JavaScript references against the Rust vectors on a stock machine; the contract suites run against a live QRVM.

Release gate. The Kurtosis composition, a full execution, beacon and validator network on chain 3151909, deployed every preset and verified fib_c3_n20 end to end on 25 August 2026, and its re-measurement of all 44 cells matched the developer node cell for cell. QRVM gas is deterministic, so the numbers carry over to the public network unchanged.

2 How it works

The proof system is Plonky3 uni-stark over Goldilocks with the quadratic extension and two-adic FRI. Each choice below was priced against the QRVM gas table before it went into the verifier.

Prover

A Rust workspace produces the proofs, the test vectors and their mutation sets. The crate version, Plonky3 0.7.0-rc.1, is pinned in prover/PLONKY3_VERSION and recorded in every compile manifest, so a proof and the verifier that checks it always name the same upstream release. A mirror verifier in the same workspace reproduces the upstream transcript byte for byte.

Hashing

Every hash in the proof system, Merkle leaves, Merkle compression and the Fiat-Shamir transcript, is keccak256 through the KECCAK256 opcode at 30 + 6 gas per 64-byte word. The SHAKE256 precompile costs 388 gas per call; the bridge uses it where ML-DSA-87 needs a 64-byte digest. A proof verifies a few thousand hashes, so the cheaper hash decides the design.

Field arithmetic

Goldilocks, p = 2^64 - 2^32 + 1, needs the fewest extension-field operations of the common STARK fields. Integers are uint512 at the Hyperion level, the hot loops run in Yul on native 512-bit words, and field inversion goes through the 0x05 modexp precompile.

Openings and calldata

Query openings share one pruned Merkle multi-proof per commitment, and FRI folds with arity 8, the cheapest schedule in both bytes and gas. Proofs travel as packed bytes calldata, 8-byte little-endian Goldilocks elements and 32-byte digests exactly as Plonky3 serialises them, and the parser enforces the exact proof length before any query data is read.

Presets

One verifier deployment per parameter preset: the six FRI constants at the top of StarkVerifier.hyp are substituted at compile time. The committed values are preset c3 (log_blowup 3, 34 queries, 16 proof-of-work bits); sixteen presets are measured.

Facts

StarkFactRegistry turns a verified proof into a storage fact keyed by keccak256(verifier, programId, keccak256(publicValues)). Proof bytes contain witness bytes the verifier skips and are therefore malleable, so the key is built from the public values and the bridge looks facts up by that key.

Proof flow

  1. The prover (Rust, Plonky3 0.7.0-rc.1) produces the proof and its public values.
  2. The proof travels as packed calldata: 43,440 bytes for fib_c3_n12.
  3. StarkVerifier.verify(bytes proof, bytes publicValues), one deployment per preset, decodes and scans the layout, replays the keccak transcript, checks the constraints at the out-of-domain point and runs FRI: input batches, reduced openings, fold chains, round Merkle checks. Any failed check reverts with a custom error.
  4. StarkFactRegistry.registerFact calls the verifier and stores the fact keyed by the public values.
  5. StateBridge.submitBatch looks the fact up, or registers it in the same transaction, and advances the state root.

Normative details: docs/PROTOCOL.md (transcript, openings, calldata layout) and docs/VERIFIER.md (memory layout, check order, phase measurements).

3 Bridge preview

StateBridge is the L1 contract that consumes registry facts: it advances a state root per proven batch, records deposits in an append-only accumulator and pays withdrawals that carry a Merkle inclusion proof and an ML-DSA-87 signature. The panel below shows its interface with every control disabled.

StateBridge preset c3

Network: none. Awaiting the QRL 2.0 public testnet relaunch.

State root
0x0000…0000 (genesis)
Batch index
0
Deposits
0
Verifier
StarkVerifier[c3]
Deposit measured 68,800 gas
Appends a leaf to the deposit accumulator.
Withdraw measured 345,791 gas
Signature checked through precompile 0x03.

What the bridge does

Three flows, written in high-level Hyperion. Gas is the receipt on the developer node with a four-leaf withdrawal tree.

Deposits. deposit() hashes the sender, the amount and the deposit index into a leaf, folds it into an append-only accumulator and emits Deposited. An L2 batch later proves that it consumed the deposits in order.

Batches. submitBatch(prevRoot, newRoot, publicValues, proof) requires prevRoot to equal the current state root and the public values to encode both roots as sixteen Goldilocks limbs. The registry verifies the proof, or finds the fact already registered, and the state root advances: 155,618 gas plus the verifier's own gas.

Withdrawals. withdraw proves a leaf under the state root and checks an ML-DSA-87 signature through precompile 0x03 over a SHAKE256 digest (precompile 0x06) of the leaf and the recipient, with the context QP-WITHDRAW-v1. The leaf is marked spent before the transfer. 345,791 gas, 125,000 of them in the precompile.

Still missing from the skeleton: an escape hatch, a challenge window, forced inclusion, a sequencer role, pausing and upgradeability. docs/BRIDGE.md lists every simplification, and the architecture document gives the order in which they get added.

4 Status and roadmap

QuantaStark is a DigitalGuards research project: experimental, unaudited and subject to breaking changes. It exists to measure what post-quantum validity proofs cost on QRL 2.0 and to inform an L2 design. Do not use anything in the repository to secure funds.

Table 2. Status on 25 August 2026.
Item State
Verifier, prover, vectors, fact registry, bridge skeleton Complete and measured on the developer node (milestones M0 to M8). Every valid vector of the sixteen presets verifies; every mutated vector reverts with its recorded error.
Release gate Run on the Kurtosis composition, chain 3151909, on 25 August 2026: all presets deployed, fib_c3_n20 verified end to end, 44 cells re-measured.
L2 architecture decision Written from the measured numbers (milestone M9), with a decision table and the open questions.
Soundness Every current preset is an experimental benchmark profile: 116 to 118 conjectured bits under the ethSTARK conjecture, with about 66 to 70 bits in the rough proven regime. A production profile remains open.
Privacy The initial track is transparent validity and scaling. Proof layout version 1 does not provide zero knowledge.
Circuit hash Selection is open. Poseidon2 remains an experimental benchmark candidate while M10 reviews the exact parameters and keeps the proof layout hash-agile.
External review None to date. An audit of the verifier and the circuits is part of the plan.
Public deployment Waits for the QRL 2.0 public testnet relaunch.

Recommendation. Build a validity rollup whose data availability lives in QRL calldata and whose final on-chain proof is the c3 STARK that StarkVerifier already checks. Aggregate batch proofs with two-layer recursion, a reviewed circuit hash inside and keccak256 at the last layer. Bind the selected suite to a versioned identifier, let L2 accounts use a reviewed hash-based signature scheme, and keep ML-DSA-87 at the L1 boundary through the bridge. Start with a centralized sequencer and a single prover, with forced inclusion and an escape hatch on L1, then move to a proof market and to sequencer decentralization.

Next steps, in order

  1. M10 Hash security gate and layout version 2. Add a versioned hash-suite identifier and multi-commitment layout with quotient chunks. Benchmark candidate suites against a conservative keccak baseline before selection.
  2. M11 Data binding in the bridge. A keccak AIR, postData and the public-values schema in StateBridge, the withdrawal root and the forced-inclusion inbox.
  3. M12 Recursion prototype. Selected circuit hash in the inner layer, keccak256 in the final layer, with the Fibonacci AIR as the payload.
  4. M13 Payment AIR set. Hash-based signatures and the lookup layer; wallet key derivation and index persistence.
  5. M14 Escape hatch, governance, defense in depth. External review of the verifier and the circuits, then a public testnet deployment of the sequencer, the prover and the submitter.