aere-research/pq-stark/export-inner-stark-vector.md
Aere Network 6cb0140fae Republished from a clean root: the compiled artifact is gone from history, and the local line of work joins the sanitized public line
The public history carried kat/__pycache__/mlkem768_reference.cpython-314.pyc,
a compiled Python artifact embedding the operator's absolute local path. Text
secret scanners do not read compiled binaries, which is exactly how it slipped
through, and removing it from the tip would have left it reachable through the
old root commits. So this repository is republished from a single clean root.

This root also carries, from the previously unpublished line of work:
- corrected LICENSE year, LICENSING.md, VERIFY-POLICY.md, and
  CITATIONS-UNRESOLVED.md remeasured 2026-08-11 (101 paths, README aligned)
- O-018: run_consensus_verification.py ran 19 of 29 models and reported PASS;
  it now runs all 29, and computemarket_smt.py gains resolveByTimeout /
  reclaimUnsettled cases plus a negative control
- O-006: the word 'audited' removed from next to Bouncy Castle, twice, after a
  concurrent edit resurrected it
- O-014: prior art named and dated - Algorand's native falcon_verify shipped
  about ten months before AERE's precompiles; the primacy claim is withdrawn
  where it was implied
- bench/ scripts parametrized so they actually run for an outsider (the
  earlier textual sanitization left $STAGING unexpanded inside Python strings)
- AIP-2/AIP-3 errata with measured figures, spec remeasurements at 2026-08-01,
  and the spec-zk-stack retractions (owner is an operational key, not the
  Foundation; 'maximally sound' withdrawn; aggregator V1 deprecated)
The redacted bench-host environment files from the sanitized line are kept
exactly as published; the unredacted local variants are not carried.
2026-08-15 13:52:14 +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.