aere-research/pq-stark/export-inner-stark-vector.md
Aere Network 4a0b48588c Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:30 +03:00

5.9 KiB

Exporting a real SP1 v6.1.0 INNER STARK vector (for the PQ verifier KAT corpus)

SCOPE CORRECTION (2026-07-19 research finding; the premise below is wrong for the pinned SP1). This document assumes SP1 6.1.0's inner proof is a BabyBear + Poseidon2 + FRI Plonky3 ShardProof. It is NOT. The pinned SP1 (facade = "=6.1.0", internals sp1-hypercube 6.3.1) is a Hypercube release: its inner recursion proof is a KoalaBear multilinear proof (BaseFold + Jagged/Stacked PCS + sumcheck-zerocheck + LogUp-GKR with a septic-curve digest), with no BabyBear FRI ShardProof anywhere. So exporting a "shrink/compress Plonky3 shard proof over BabyBear + FRI" from SP1 6.1.0 is not possible: what SP1 6.1.0 exports is a Hypercube ShardProof (KoalaBear), which the BabyBear + FRI verifier skeleton does NOT verify. A real KAT corpus for the current skeleton must come from Aere's OWN Plonky3 (zk-circuits/*) BabyBear + FRI provers instead. Building a real SP1 6.1.0 corpus requires first retargeting 0x0AE8 to the SP1 Hypercube stack (a separate ~22 to 32 person-week effort). See ../../docs/AERE-STARK-SP1-RECURSION-AIR-PORT-SPEC-SUMMARY.md and ../../docs/AERE-STARK-SP1-RECURSION-AIR-PORT-SPEC.md. Read the instructions below with that in mind.

The PQ STARK-verify precompile (0x0AE8) verifies a BabyBear/Plonky3 inner hash-based FRI/STARK proof directly, not a BN254 Groth16 wrap. Per the scope correction above, that BabyBear + FRI target is Aere's own Plonky3 circuits, NOT the pinned SP1 6.1.0 (Hypercube). The steps below were written to export a BabyBear + Poseidon2 + FRI "shrink"/compress shard proof; they apply to a BabyBear + FRI Plonky3 prover, not to the SP1 6.1.0 Hypercube toolchain.

This file is instructions + a Rust snippet, NOT a bundled vector. As of 2026-07-18 no real inner vector is committed here, because generating one requires running the SP1 prover (heavy; MUST be on a throwaway non-infra box, never on the live infra host or a validator). Treat every "expect ACCEPT" KAT as PENDING until a vector produced by this procedure is checked in.

Provenance to pin

  • SP1 =6.1.0 (matches the repo's existing pins: rollup-evm-validity/host/Cargo.toml, batch-prover-recovered/bin/aere-prover/Cargo.toml, sp1-verifier = "=6.1.0").
  • The proof object we want is the one BEFORE the Groth16/PLONK wrap:
    • SP1ReduceProof after prover.shrink(...) (config 1, "inner/shrink"), and/or
    • SP1ReduceProof after prover.wrap_bn254(...)'s input (config 2, "wrap"), i.e. the STARK that Groth16 is about to attest, exported before the wrap.
  • These are Plonky3 ShardProof<BabyBearPoseidon2> values. Their serde/bincode bytes are the friProof body the precompile parses (spec doc section 3.2 pins the sub-layout to this exact serialization + version).

Rust snippet (run on a throwaway prover box)

// Cargo.toml: sp1-sdk = "=6.1.0", sp1-prover = "=6.1.0", bincode = "1.3", serde_json = "1"
use sp1_sdk::{ProverClient, SP1Stdin};

fn main() {
    // A tiny guest ELF is enough for a KAT (e.g. the fibonacci example, or the aere-client guest).
    let elf = std::fs::read("guest.elf").unwrap();
    let client = ProverClient::from_env();      // CPU prover; native-gnark not needed for shrink
    let (pk, vk) = client.setup(&elf);

    let mut stdin = SP1Stdin::new();
    stdin.write(&/* public input */ 20u32);

    // 1) core proof -> 2) compress -> 3) shrink. Stop BEFORE groth16/plonk wrap.
    let core = client.prove(&pk, &stdin).core().run().unwrap();
    let compressed = client.prove(&pk, &stdin).compressed().run().unwrap();
    // The inner API name varies by SP1 minor; in 6.1.0 the compressed proof carries the
    // SP1ReduceProof. Serialize the reduce/shrink proof + vk + public values:

    // vkey digest domain the precompile expects (Poseidon2 digest of the recursion vk):
    let vkey_digest = vk.hash_babybear(); // [BabyBear; 8] -> 32 bytes big-endian per element pack
    std::fs::write("sp1_shrink_vkeydigest.bin", pack_babybear8(&vkey_digest)).unwrap();

    let public_values = compressed.public_values.to_vec();
    std::fs::write("sp1_shrink_publicvalues.bin", &public_values).unwrap();

    // The shard/reduce proof, bincode-serialized == the friProof body:
    let proof_bytes = bincode::serialize(&compressed.proof).unwrap();
    std::fs::write("sp1_shrink_proof.bin", &proof_bytes).unwrap();

    // Also dump the FriConfig actually used (num_queries, log_blowup, pow_bits) so section 4 of the
    // spec can pin configId 1 exactly instead of the placeholder values:
    // println!("{:?}", <the shrink FriConfig>);
}

fn pack_babybear8(_x: &[u32; 8]) -> Vec<u8> { /* 4 BE bytes per limb -> 32 bytes */ vec![] }

Assemble the precompile envelope

Concatenate into the v1 wire format the adapter/precompile expect (spec doc section 3):

magic("AS1\0") || version(1)=1 || configId(1)=1 || reserved(2)=0x0000
  || vkeyDigest(32)  = sp1_shrink_vkeydigest.bin
  || publicValuesLen(4 BE) || publicValues = sp1_shrink_publicvalues.bin
  || proofLen(4 BE)        || friProof     = sp1_shrink_proof.bin

Name the file vectors/sp1_shrink_valid_01.bin. Produce the negatives by:

  • sp1_shrink_tampered_01.bin: flip one byte inside a query-opening leaf of the proof (must REJECT).
  • sp1_shrink_wrongpub_01.bin: mutate one byte of publicValues (must REJECT - the transcript binds public values, so the sampled challenges diverge).

What these vectors gate

Once the crypto core is ported (Poseidon2-BabyBear + FRI folding + SP1 recursion AIR), the KAT corpus is the section-4 conformance gate in docs/PQ-STARK-VERIFIER-ACTIVATION-2026-07-18.md: the precompile must ACCEPT every *_valid_* vector and REJECT every *_tampered_* / *_wrongpub_* vector, plus a cross-check that the SAME public values verified through the live BN254 gateway (0x9ca479...) and through 0x0AE8 agree on accept/reject. Until a real vector exists and the core is ported, DO NOT claim the precompile verifies anything.