Files
zeekay 369c90e94a p3q-verifier: land audit-grade generic STARK/FRI verifier body
Replace the `not yet audit-gated` stub in verify_sha3/verify_keccak with
a real Fiat-Shamir-driven generic STARK / FRI low-degree verifier.
Wraps cleanly: AIR-specific Z-Chain circuit verifiers compose this
kernel and add their boundary/transition checks on top.

What lands

* wire.rs - canonical wire format v1 (10-byte header + public input +
  trace root + FRI layer roots + final polynomial + per-query
  openings). Every length/range/well-formedness check fires BEFORE
  any crypto. Headers reject non-binary arities, out-of-range
  log_blowup/log_degree_bound/num_queries/log_final_poly, reserved
  bits, wrong version.

* transcript.rs - typed wrapper around p3q-challenger. Profile-keyed
  customisation string (LUX-P3Q-V1-SHA3 / LUX-P3Q-V1-KECCAK) plus
  per-observation tag prefixes (HEADER, PUBLIC-INPUT, TRACE-COMMIT,
  FRI-COMMIT, FRI-FINAL, FRI-QUERY). Field-element squeezing uses
  cSHAKE256 rejection sampling with a finite 32-iter cap.

* domain.rs - LDE-domain generator from Goldilocks 2-adicity (g=7),
  binary FRI fold formula with two-test correctness proof, Horner
  evaluation.

* verify.rs - the kernel: phase-1 transcript replay capturing per-
  round fold challenges, phase-2 query-index re-derivation with echo
  check, per-query trace+layer Merkle authentications, colinearity
  check against folded carry, final-poly evaluation comparison. Every
  failure mode maps to a precise P3qError variant - no unwrap, no
  panic.

* prover.rs (feature `prover`) - minimal toy prover that produces
  golden vectors. Used by in-crate roundtrip tests only; not in the
  audited verifier compile graph.

* p3q-field: pow() and two_adic_generator() (g=7 quadratic non-
  residue gives primitive 2^k-th roots up to k=32).

Verification properties tested

* sha3+keccak golden vectors verify (toy AIR: degree-16 poly, blowup
  8, 16 queries, final poly of size 2)
* tampering rejected: profile byte swap, trace root bit-flip, FRI
  layer root bit-flip, final-poly bit-flip, public-input bit-flip,
  echoed query-index bit-flip (precise Transcript variant), arbitrary
  per-query byte flip
* parsing rejects: short header, wrong version, non-binary arity,
  log_final_poly >= log_degree_bound, reserved nonzero, truncated
  body, trailing bytes, oversized Merkle path length
* never panics on random first-byte fuzz
* profiles do not cross-verify (cSHAKE customisation isolates them)

Golden vectors

vectors/p3q_v1_{sha3,keccak}_golden.{proof,public_input}.bin written
by the `write_golden_vectors` ignored test. `persisted_golden_vectors
_verify` reads them back and confirms acceptance - any wire-format
change forces a regen.

Verify wall-clock

Toy AIR (~30 KB proof): ~1 ms per verify in release on M2 Pro.

Workspace state

cargo fmt --check, cargo clippy --all-targets -- -D warnings, and
cargo test all clean. 87 tests passing (1 ignored vector writer).
scripts/lint_forbidden.sh clean (no bn254/kzg/groth16/blake3/poseidon
imports).

Out of scope for this commit

* AIR-specific Z-Chain circuits (boundary + transition constraints)
  - p3q-zchain stays scaffolded, lands in v0.2.x
* recursive STARK composition - p3q-recursion stays scaffolded
* coset FRI / bit-reverse leaf permutation - dual-auth-path v1 wire
  is intentionally simple; v2 can collapse to single path under
  bit-rev
2026-05-30 18:03:19 -07:00

53 lines
1.9 KiB
Rust

// =============================================================================
// build.rs - cbindgen-driven C header generation for p3q-c-abi
// =============================================================================
//
// Generates `include/lux/p3q.h` next to the crate so consumers can copy
// it into their own `$LUXCPP_PREFIX/include/lux/` (or homebrew's
// `/opt/homebrew/include/lux/`) alongside `lux/crypto/metal_mlkem.h`.
//
// We do NOT write into `$OUT_DIR` because the header is consumed by
// non-Rust callers that don't know about cargo's output convention.
// The header is regenerated on every build so it stays in sync with
// `src/lib.rs` — never edit it by hand.
// =============================================================================
use std::env;
use std::path::PathBuf;
fn main() {
// Re-run if src or cbindgen config changes.
println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=cbindgen.toml");
let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let out = PathBuf::from(&crate_dir)
.join("include")
.join("lux")
.join("p3q.h");
if let Err(e) = std::fs::create_dir_all(out.parent().unwrap()) {
eprintln!("p3q-c-abi: could not create include dir: {e}");
return;
}
let config = cbindgen::Config::from_file(PathBuf::from(&crate_dir).join("cbindgen.toml"))
.unwrap_or_default();
match cbindgen::Builder::new()
.with_crate(&crate_dir)
.with_config(config)
.generate()
{
Ok(bindings) => {
bindings.write_to_file(&out);
}
Err(e) => {
// Don't fail the build on header-generation issues — the
// .so / .dylib is the source of truth. Print so it's
// visible in cargo's output.
println!("cargo:warning=cbindgen failed: {e}");
}
}
}