Skip to content

Commit b8e3df6

Browse files
TomWambsgansclaude
andcommitted
cleanup: dead code, duplication and comment slop across the workspace
A review pass over every crate, then applied per crate. Net -2.9k lines (95 files, +3585/-7523, of which 1036 lines moved into three new files rather than disappearing). Removed: unused pub items, fields and enum variants; superseded kernels and benchmark-only variants; write-only accumulators and debug scaffolding; a dead compiler macro and a never-called guest function the compiler was lowering into the bytecode regardless. Deduplicated: eight copies of the OOD/fold transcript replay in ligerito; prover/verifier `_base`/`_ext` function pairs behind small traits; the four `memory*` flush builders; `col_kappas`, now derived from `col_kappa_sources` instead of hand-maintained alongside it; two compile-time integer evaluators in the parser; and fifteen hand-rolled test PRNGs, now one helper over `rand::StdRng` behind a `test-util` feature so `rand` stays out of the production graph. Split `ligerito.rs` (4150 lines) into itself plus `ligerito_ntt_ext.rs` and `ligerito_induce.rs`. Tests: deleted those that restate a constructor or call a pure function twice, and folded standalone tamper tests into the table that already runs at two sizes. Ported the one uncovered case out of a deleted program first. Behavior is unchanged and pinned as such: bytecode byte-identical for all test programs and both guests, proof wire format byte-identical, FAMILY_DIGEST unchanged, guest cycles unchanged. Three hot paths are deliberately left duplicated, with comments saying so, because merging them cost measurable time: the per-arch inv_table SIMD kernels (a trait stopped them inlining into flock under thin LTO, worth 2.3%), the interpreter's memory accessors, and `table_message`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 18dc000 commit b8e3df6

100 files changed

Lines changed: 5014 additions & 8582 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Dependency order, leaves first:
2525
| `flock` | batched R1CS over GF(2) for BLAKE3: zerocheck + lincheck |
2626
| `lean_vm` | arithmetization: tables, bus, constraints, `cpu::prove`/`verify` |
2727
| `lean_compiler` | zkDSL (Python subset) → ISA |
28-
| `xmss` | XMSS over BLAKE3 |
28+
| `xmss` | XMSS over BLAKE3; an independent leaf, consumed only by `rec_aggregation` |
2929
| `rec_aggregation` | the three workloads + the N→1 recursion harness |
3030

3131
`src/main.rs` is the CLI; guests are zkDSL under `crates/rec_aggregation/guests/`.
@@ -36,12 +36,12 @@ Dependency order, leaves first:
3636
- always run in `--release` mode any test or benchmark touching the VM (the zkDSL compiler stack-overflows in `debug` mode)
3737

3838
```bash
39-
cargo testall # = test --all --release; 314 tests, seconds
39+
cargo testall # = test --all --release; whole suite in seconds
4040
cargo clippy --release --all-targets
4141
cargo fmt --all # max_width = 120
4242
```
4343

44-
Heavy benches are `#[ignore]`d; run by name with `-- --ignored --nocapture` (`blake3_batch`, `pcs_throughput`, `recursion_soundness_binds`, `recursion_generic_many`).
44+
Heavy benches and measurement harnesses are `#[ignore]`d; run by name with `-- --ignored --nocapture`: `blake3_batch_prove_verify`, `pcs_throughput`, `recursion_soundness_binds`, `recursion_generic_many`, `recursion_guest_profile`, `print_ligerito_query_counts`, `encoding_grinding_bits`.
4545

4646
## Benchmarking
4747

@@ -84,15 +84,22 @@ The third is worth understanding before touching the verifier. `guests/recursion
8484

8585
## Conventions that bite
8686

87+
- Use comments only when necessary: uncommented but readable and simple code is better than commented slop.
88+
- Commit tests only that are useful in the future, to prevent regressions / failures. Don't add trivial tests that will always pass.
89+
- Simpler is better.
8790
- **Fiat-Shamir:** `add_scalar`/`next_scalar` bind into the sponge as a side effect. `observe_*` is only for the public statement. Never re-observe data that rode the stream, which silently desynchronizes the two sides.
88-
- **Prover and verifier derive the layout identically** from announced sizes. Changes to `placements_of`, `col_kappas` or the schema land on both sides, and `col_kappa_sources` stays in lockstep with `col_kappas`.
91+
- **Prover and verifier derive the layout identically** from announced sizes. Changes to `placements_of` or the schema land on both sides. `col_kappas` is derived from `col_kappa_sources` rather than written out twice, so the two can no longer drift; keep it that way.
8992
- **A failed guest `assert` surfaces as a write-once memory conflict**, not an assertion message, so disassemble around the reported `pc` (`DBG_DISASM`).
9093
- Guests are single-file; the compiler skips `from snark_lib import *`, which exists only so editors accept the file as Python.
9194
- **One symbol, one meaning, across the whole document.** All notation is defined in `doc/preamble/macros.tex`; define a new macro there rather than inline, and check the letter is free first. Annex B's symbols were deliberately renamed away from the letters WHIR/Ligerito/BCHKS25 use (rate is `\rate`, not `\rho`, which is a sumcheck point) and its "Symbols" table is the map back, so reintroducing a paper's letter silently collides with the main matter.
9295
- **Doc labels are an API.** `crates/pcs` cites `thm:rbr` and `thm:mca-johnson` by name and several crates cite `doc/main.tex` sections, so renaming a label breaks those pointers with nothing to catch it. `doc/body/NN-*.tex` prefixes match section numbers, so inserting a section renumbers the rest.
9396
- **No em-dashes or en-dashes in prose**, anywhere a human reads it: docs, LaTeX, comments, commit messages. Restructure with a comma, colon, parentheses, or two sentences.
9497
- **Never hard-wrap prose in Markdown or LaTeX.** One paragraph is one line; let the editor wrap it. Artificial line breaks make every later edit a reflow, so diffs show rewrapped lines instead of changed words. Applies to `.md` and `.tex` alike; code blocks, tables and list items keep their own line.
9598

99+
## Soundness
100+
101+
- in the recursion program, the prover transmits advice to the verifier, called "hints". These advice should not be truster (a malicious prover should never be able to prove an invalid witness), and carefully checked by the verifier.
102+
96103
## Env knobs
97104

98105
| var | effect |
@@ -103,7 +110,7 @@ The third is worth understanding before touching the verifier. `guests/recursion
103110
| `ZK_ALLOC_STATS` | arena bytes/phase, high water, overflow |
104111
| `BENCH_REPEAT`, `BENCH_COOLDOWN` | `--repeat`/`--cooldown` for `#[ignore]`d benches |
105112
| `LEANVM_XMSS_N`, `LEANVM_HASH_N`, `LEANVM_HASH_UNROLL` | workload sizes in tests |
106-
| `FLOCK_N_LOG`, `FLOCK_PROVE_TRACE`, `FLOCK_ZC_TIMING` | flock batch size, stage traces |
113+
| `FLOCK_N_LOG`, `FLOCK_PROVE_TRACE`, `FLOCK_ZC_TIMING`, `LINCHECK_TRACE` | flock batch size, stage traces |
107114
| `PCS_LOG_N`, `PCS_LOG_INV_RATE`, `PCS_MIN_MU`, `PCS_SAMPLES` | PCS throughput bench |
108115
| `LIGERITO_TRACE`, `LIGERITO_NUM_VARS`, `LIGERITO_LOG_INV_RATE` | Ligerito NTT/Merkle split |
109116
| `DBG_PROF{,_DUMP}`, `DBG_LOOPS`, `DBG_DISASM`, `DBG_LOWER`, `DBG_CSE`, `DBG_NO_CSE`, `DBG_PLACEHOLDERS` | compiler / guest-cycle attribution |

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ edition = "2024"
2424
[workspace]
2525
members = ["crates/*"]
2626

27-
# Hand-tuned numeric kernels (flock, pcs) legitimately index by loop variable,
28-
# take many arguments, and assert on constants; silence those style lints
29-
# workspace-wide rather than annotating every kernel.
27+
# Hand-tuned numeric kernels legitimately index by loop variable (26 sites in
28+
# primitives, pcs, flock, lean_vm) and take many arguments (12 sites in pcs,
29+
# flock, lean_vm); silence those two style lints workspace-wide rather than
30+
# annotating every kernel.
3031
[workspace.lints.clippy]
3132
needless_range_loop = "allow"
3233
too_many_arguments = "allow"
33-
assertions_on_constants = "allow"
34-
explicit_counter_loop = "allow"
3534

3635
[workspace.dependencies]
3736
primitives = { path = "crates/primitives" }

crates/fiat_shamir/src/lib.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
//! The shared Fiat–Shamir layer: the VM-native [`sponge::Sponge`] and the
22
//! [`transcript`] wrapper states (`ProverState`/`VerifierState`) that pair it
3-
//! with the proof transport channels. Every protocol in this workspace — the
4-
//! VM's own reductions, flock's zerocheck/lincheck, and the Ligerito PCS —
5-
//! draws its challenges from this one transcript.
3+
//! with the proof transport channels.
64
75
pub mod sponge;
86
pub mod transcript;
9-
10-
pub use sponge::{Sponge, TraceOp, compress, trace, trace_start, trace_take};
11-
pub use transcript::{Error, Proof, ProverState, VerifierState};

crates/fiat_shamir/src/sponge.rs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
// CREDIT: https://github.com/signalapp/libsignal/blob/main/rust/poksho/src/shosha256.rs, AGPL-3.0-only.
2-
//! The VM-native Fiat–Shamir sponge: THE verifier-randomness source for the
3-
//! whole stack — flock's zerocheck / lincheck, the Ligerito PCS, and
4-
//! leanVM-b's own protocol (whose `ProverState` / `VerifierState` wrap this
5-
//! sponge with the proof transport channels).
2+
//! The VM-native Fiat–Shamir sponge.
63
//!
74
//! A 256-bit chaining value evolved only by the fixed 64→32 BLAKE3 compression
85
//! the VM's `Blake3` opcode computes, so prover, verifier, and a recursive
@@ -20,8 +17,8 @@
2017
//! (`libsignal/rust/poksho/src/shosha256.rs`, © 2020 Signal Messenger, LLC,
2118
//! AGPL-3.0-only): a chaining value advanced by domain-separated absorb /
2219
//! squeeze steps. Here the underlying hash is the VM's BLAKE3 compression
23-
//! rather than SHA-256, inputs are `K = GF(2^64)` field words, and because
24-
//! every absorb is domain-tagged per compression no explicit double-hash
20+
//! rather than SHA-256, inputs are `K = GF(2^64)` field words, and, because
21+
//! every absorb is domain-tagged per compression, no explicit double-hash
2522
//! ratchet is needed.
2623
//!
2724
//! Each challenge is the random-oracle image of the whole prior transcript;
@@ -32,7 +29,7 @@
3229
use primitives::field::{F64, F192};
3330

3431
/// `f(a, b) = BLAKE3(a‖b)` on two 256-bit halves laid out little-endian into 64
35-
/// bytes *exactly* the VM's `Blake3` opcode: 64 input bytes → 32-byte digest,
32+
/// bytes, *exactly* the VM's `Blake3` opcode: 64 input bytes → 32-byte digest,
3633
/// split back into four field words. THE primitive; the sponge is a chain of
3734
/// these, so a zkDSL program replays it with one `blake3(...)` per step.
3835
pub fn compress(a: [F64; 4], b: [F64; 4]) -> [F64; 4] {
@@ -55,10 +52,9 @@ const DS_SQUEEZE: F64 = F64(4);
5552
const DS_POW: F64 = F64(5);
5653

5754
/// `compress(base, (nonce.c0, nonce.c1, nonce.c2, DS_POW))` has its low `bits`
58-
/// bits zero — the
59-
/// grinding predicate over the VM compression. A CONTIGUOUS low-bit window
60-
/// (rather than byte-wise leading zeros) so a recursive verifier re-checks it
61-
/// with a single loop over the bit decomposition of the digest word
55+
/// bits zero: the grinding predicate over the VM compression. A CONTIGUOUS
56+
/// low-bit window (rather than byte-wise leading zeros) so a recursive verifier
57+
/// re-checks it with a single loop over the bit decomposition of the digest word
6258
/// (`grind_check` in `guests/recursion.py`). `bits` is always `< 64`.
6359
#[inline]
6460
fn pow_bits_ok(base: [F64; 4], nonce: F192, bits: u32) -> bool {
@@ -79,7 +75,7 @@ pub struct Sponge {
7975
impl Sponge {
8076
/// Seed with the domain `label` and the PUBLIC `statement` scalars (the public
8177
/// input). Both sides seed identically, so the whole statement is bound before
82-
/// any challenge there is no mid-protocol "observe public data" step to get
78+
/// any challenge; there is no mid-protocol "observe public data" step to get
8379
/// wrong (or forget). (Untraced: the seed is the replay STARTING state, not an
8480
/// op of the recorded transcript.)
8581
pub fn new(label: &[u8], statement: &[F192]) -> Self {
@@ -92,12 +88,6 @@ impl Sponge {
9288
s
9389
}
9490

95-
/// A fresh chain at the zero state: the guest-side aggregation and export
96-
/// transcripts start here (no label), and the harness mirrors them.
97-
pub fn empty() -> Self {
98-
Self { cv: [F64::ZERO; 4] }
99-
}
100-
10191
/// Absorb one 24-byte scalar (three little-endian `K` limbs):
10292
/// `cv ← compress(cv, (c0, c1, c2, DS_SCALAR))`.
10393
pub fn observe(&mut self, x: F192) {
@@ -130,7 +120,7 @@ impl Sponge {
130120

131121
/// Squeeze a challenge and ratchet: the challenge's three limbs are the
132122
/// first three words of `compress(cv, (0, 0, DS_SQUEEZE, 0))`, whose full output
133-
/// becomes the new state domain-separated from absorbs, so a challenge
123+
/// becomes the new state, domain-separated from absorbs, so a challenge
134124
/// cannot be confused with a continued absorb. In Fiat–Shamir everything is
135125
/// public; soundness comes from each challenge being a random-oracle image
136126
/// of the entire prior transcript.
@@ -164,12 +154,12 @@ impl Sponge {
164154

165155
/// The grinding digest word this state yields for `nonce` (read-only preview;
166156
/// [`Self::verify_pow`] is the mutating check).
167-
pub fn pow_digest(&self, nonce: F192) -> F64 {
157+
fn pow_digest(&self, nonce: F192) -> F64 {
168158
compress(self.pow_base(), [F64(nonce.c0), F64(nonce.c1), F64(nonce.c2), DS_POW])[0]
169159
}
170160

171161
/// Re-run recorded verifier transcript ops through this sponge, asserting
172-
/// every recorded sample (and grind) matches what this state re-derives
162+
/// every recorded sample (and grind) matches what this state re-derives;
173163
/// any prefix of a real verify trace yields the exact state reached there.
174164
/// (Untraced throughout: a replay must never re-record.)
175165
pub fn replay(&mut self, ops: &[TraceOp]) {
@@ -233,7 +223,7 @@ impl Sponge {
233223

234224
/// Verifier-side mirror of [`Self::grind_pow`]: check `nonce` clears the `bits`
235225
/// PoW against the current state, then bind it regardless (so the sponge stays
236-
/// in lockstep with an honest prover a failed check rejects at the call
226+
/// in lockstep with an honest prover; a failed check rejects at the call
237227
/// site). `bits = 0` accepts only the canonical nonce `0`, which keeps proofs
238228
/// non-malleable at zero-bit grinding sites.
239229
pub fn verify_pow(&mut self, nonce: u64, bits: u32) -> bool {
@@ -276,7 +266,7 @@ impl Sponge {
276266
pub enum TraceOp {
277267
/// A stream word consumed without binding (grinding nonces).
278268
StreamRaw(F192),
279-
/// An absorbed scalar (transmitted or derived the sponge cannot tell).
269+
/// An absorbed scalar (transmitted or derived: the sponge cannot tell).
280270
Observe(F192),
281271
/// `absorb_bytes` (labels, roots).
282272
AbsorbBytes(Vec<u8>),
@@ -359,11 +349,11 @@ mod tests {
359349
bytes[..8].copy_from_slice(&x.c0.to_le_bytes());
360350
bytes[8..16].copy_from_slice(&x.c1.to_le_bytes());
361351
bytes[16..].copy_from_slice(&x.c2.to_le_bytes());
362-
b.absorb_bytes(&bytes);
352+
b.absorb_bytes_untraced(&bytes);
363353
assert_ne!(a.sample(), b.sample());
364354
}
365355

366-
/// A grind clears its own PoW; a nonce that does not is rejected.
356+
/// A grind clears its own PoW, and returns the SMALLEST nonce that does.
367357
#[test]
368358
fn pow_predicate() {
369359
let sp = Sponge::new(b"t", &[f(1)]);
@@ -373,8 +363,12 @@ mod tests {
373363
clone.grind_pow(8)
374364
};
375365
assert!(pow_bits_ok(base, F192::new(good, 0, 0), 8));
376-
// A random wrong nonce almost surely fails an 8-bit grind.
377-
assert!(!pow_bits_ok(base, F192::new(good.wrapping_add(1).wrapping_mul(3) | 1, 0, 0), 8,) || good != 0);
366+
for n in 0..good {
367+
assert!(
368+
!pow_bits_ok(base, F192::new(n, 0, 0), 8),
369+
"nonce {n} < {good} also clears"
370+
);
371+
}
378372
}
379373

380374
/// Recursive proofs transport the nonce as one field word. Its high limb is

0 commit comments

Comments
 (0)