Skip to content

Add proof-of-work grinding; bind combined_row into Fiat-Shamir#210

Open
latifkasuli wants to merge 9 commits into
NethermindEth:mainfrom
latifkasuli:feat/pow-grinding
Open

Add proof-of-work grinding; bind combined_row into Fiat-Shamir#210
latifkasuli wants to merge 9 commits into
NethermindEth:mainfrom
latifkasuli:feat/pow-grinding

Conversation

@latifkasuli

Copy link
Copy Markdown
Contributor

Closes #149.

Plan / end state

Adds FRI/STIR/WHIR-style grinding so Zip+ can trade a small amount of prover hashing for fewer column openings — the dominant proof-size term. Ships as: (1) a transcript PoW primitive, (2) ZipTypes::GRINDING_BITS (default 0 = off) wired into the open/verify phases, (3) e2e bench recalibrated to 84 openings + 16 grinding bits at the same ~100-bit target, plus a sweep example to reproduce the parameter study. Happy to split into smaller PRs if preferred — the commits are structured for it.

Soundness fix included (pre-existing, found during this work)

On main, combined_row is written to the proof stream via write_const_many, which does not absorb into Fiat-Shamir — so the column-opening indices were derived independently of w. Algorithm 2 samples the spot checks after the prover sends w; with predictable indices a prover can solve the underdetermined linear system for a w passing all spot checks against an invalid commitment. Commit 626a7a6 adds write_const_many_bound/read_const_many_bound and uses them for combined_row on both sides, binding the grinding seed and the indices to it. This re-derives downstream challenges, so proofs differ from pre-fix proofs even with grinding disabled. cc @albert-garreta for the soundness review.

How grinding trades

At rate 1/8 the per-query soundness term is (1−β)^C with β at the 1.5-Johnson bound rate^{1/3} = 1/2, i.e. 1 bit per opening — so g grinding bits allow 100−g openings at the same target. Caveat: grinding amplifies only this final query term; the MCA/list-decoding and algebraic terms are unchanged (they were engineered to the target independently and are unaffected by lowering C). Parameter choice is open for discussion — 16 bits follows the knee in the data below.

Numbers (sweet_spot_sweep example, 5 iterations, nvars=10, -C target-cpu=native, simd+parallel+unchecked)

UAIR rep, openings, grind Prove ms Raw bytes zstd-22
ShaProxy 8, 100, 0 (today) 32.1 462,114 232,109
ShaProxy 8, 84, 16 34.6 (+8%) 431,666 (−6.6%) 210,059 (−9.5%)
BigLinear 8, 100, 0 37.7 484,626 259,185
BigLinear 8, 84, 16 39.8 (+5.6%) 452,922 (−6.5%) 233,373 (−10%)

20 grinding bits is past the knee (+34–60% prove for ~−1.6% more size). Rate 1/16 with 16 bits gives −14.4% raw (−18.9% compressed) for +35% prove — left out of this PR; can follow up with verifier-time data if there's interest. Reproduce: SWEEP_ITERS=5 SWEEP_NVARS=10 RUSTFLAGS='-C target-cpu=native' cargo run -p zinc-protocol --release --features 'parallel simd unchecked' --example sweet_spot_sweep.

Details

  • Prover searches the smallest nonce with ≥GRINDING_BITS leading zero bits in blake3(seed ‖ nonce); deterministic with or without parallel (chunked find_first). Verifier check is one hash.
  • GRINDING_BITS >= 64 is a compile-time error in the phases and a graceful InvalidPcsParam on the transcript methods.
  • Grinding tests are capped/gated to stay cheap under miri; zinc-protocol/parallel now forwards zip-plus/parallel (benches previously ran zip-plus sequential).
  • Also fixes proof-size accounting tests to include the 8-byte nonce when enabled.

FRI/WHIR-style grinding for Fiat-Shamir soundness amplification:

- zinc-transcript: new `pow` module — seed-bound blake3 PoW search and
  check, deterministic (smallest nonce), parallelized under the new
  `parallel` feature — plus `Transcript::{pow_seed, pow_grind, pow_check}`.
- zip-plus: `PcsProverTranscript::grind` searches the nonce, absorbs it,
  and writes it to the proof stream; `PcsVerifierTranscript::check_grinding`
  reads and verifies it. Both are no-ops at 0 bits, keeping current
  proofs byte-identical. New `ZipError::Grinding` variant.

Groundwork for reducing PCS column openings at the same security
target; not yet wired into the PCS phases.
- Add `ZipTypes::GRINDING_BITS` (default 0).
- Prover (`prove_f`) grinds after writing `combined_row` and before
  squeezing column indices; verifier (`verify_with_alphas`) checks the
  nonce at the same point. Both are no-ops at 0 bits, so default proofs
  are byte-identical and Fiat-Shamir stays in sync.
- Make the proof-size accounting tests grinding-aware (account for the
  8-byte nonce when enabled).
- Add GrindZipTypes test helper + a full PCS prove→verify roundtrip test
  that also corrupts the nonce and asserts verification fails at the
  grinding check (covers grind/check_grinding wired into the PCS).
- Turn on grinding (8 bits) in the protocol e2e test configs so the whole
  prover/verifier pipeline is exercised with grinding active.
- Recalibrate the e2e benchmark config to 84 openings + 16 grinding bits
  (≈ 100 openings without grinding at rate 1/8 / ~100-bit), demonstrating
  the proof-size reduction.

Closes NethermindEth#149.
CI runs miri on zinc-transcript, zinc-protocol, and the pcs_transcript/
phase_verify chunks of zip-plus. Nonce searches are ~2^bits interpreted
blake3 hashes there, so: cap unit-test grinding at 8 bits, gate the
16-bit tamper test behind cfg(miri, ignore), and drop test-config
GRINDING_BITS to 2 under miri (keeping the grinding code path covered).
cargo run --release --example sweet_spot_sweep --features "simd parallel
unchecked" sweeps IPRS rate {1/4, 1/8, 1/16} x grinding {0, 8, 16, 20}
bits with column openings recalibrated to hold the ~100-bit target,
printing CSV of average prover time and raw/zstd proof sizes (with a
verifier pass so invalid parameter combinations fail loudly).
On main, combined_row is written to the proof stream via
write_const_many, which does not absorb into the Fiat-Shamir
transcript, so the column-opening indices were derived independently
of it. The IOPP samples the spot checks after the prover sends this
row; with predictable indices a malicious prover could solve for a row
that passes the spot checks against an invalid commitment.

Add write_const_many_bound / read_const_many_bound (write/read plus
absorbing the serialized bytes) and use them for combined_row on both
sides, so the grinding seed and the column indices are bound to it.
A regression test asserts bound messages influence subsequent
challenges and that prover/verifier stay in sync.

Note: this re-derives all downstream challenges, so proofs differ from
pre-fix proofs even with grinding disabled.
Protocol benches and examples built with --features parallel previously
left zip-plus fully sequential (encoding, Merkle, and the grinding
nonce search), since the feature was never propagated.
grind/check_grinding now return ZipError::InvalidPcsParam for
bits >= 64 instead of panicking in the nonce search (the verifier
rejects before touching the proof stream), and prove_f /
verify_with_alphas turn a misconfigured ZipTypes::GRINDING_BITS into
a compile-time error via an inline const assert.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add grinding

1 participant