From e394f28a8af4ec702897def2e5844e81f2c41bba Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 13:41:38 -0700 Subject: [PATCH 01/23] fix(lfm): the block path names its hasher at the call site, not in build_artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts 13453ef9's scope while keeping what it was right about. That commit fixed a real defect: lfm_prove_batched takes artifacts.hasher, so the aggregator inherited a toy permutation from a defaulted build and proved under it while the host committed under RPO. But it fixed the defect at the SHARED entry point rather than at the block path's call sites, and the hasher is part of program IDENTITY — HasherKind::as_tag is folded into lfm_program_id, and LFM_REGISTRY is blessed under compute_lfm_registry's REGISTRY_HASHER = Test, whose own doc calls changing it a re-blessing of the whole table rather than a re-run. So every registry program's identity moved away from the blessed table. The tree states the invariant in an assertion message. rpo_chip_tests::the_rpo_choice_moves_the_program_digest_and_no_root asserts that build_artifacts must default to Test, and poseidon_chip_tests and blake3_socket_tests carry the same contract under the same name shape. Their subject is exactly that two hashers are two program identities. So build_artifacts defaults again, and the block path names BLOCK_HASHER where it builds: the aggregator's seven sites and the wrap leg's eight. The rule is checkable rather than a judgement — a program built at WrapHash::production() emits Instr::Hash and must be proved under the pin; a program that pins a byte hash on its own builder emits none, never consults the socket, and is correct at the default under every pin. wrap_tests' keccak-chain census site is the one exception in that file and keeps the default for that reason. Not verified: no local runs. The ~21 registry-identity failures this addresses are machine_tests' registry drift and prove-and-verify set plus the three chip identity tests; the box is the oracle for whether the count is right. Note for whoever reads the regeneration control in HASH-PINNING.md: matching all 13,056 registry hex bytes proved the regeneration deterministic and was SILENT on whether an identity had moved, because compute_lfm_registry pins REGISTRY_HASHER explicitly and never reads the entry point 13453ef9 changed. The control validated the generator against itself. (cherry picked from commit 5e92356715342c4a29468ab07df6b0b7c5f583e8) --- prover/src/lfm/aggregator_tests.rs | 19 +++++++++++-------- prover/src/lfm/registry.rs | 28 +++++++++++++++++----------- prover/src/lfm/wrap_tests.rs | 20 +++++++++++--------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/prover/src/lfm/aggregator_tests.rs b/prover/src/lfm/aggregator_tests.rs index 14d48c405..6183a5f3c 100644 --- a/prover/src/lfm/aggregator_tests.rs +++ b/prover/src/lfm/aggregator_tests.rs @@ -36,7 +36,7 @@ use super::epoch::RootCells; use super::executor::execute; use super::instr::ArenaId; use super::proof::{BatchedLfmProof, aggregation_wrap_options, verify_against_batched}; -use super::registry::{LfmArtifacts, build_artifacts}; +use super::registry::{LfmArtifacts, build_artifacts_with_hasher}; use super::statement::{LFM_MACHINE_VERSION, LFM_STATEMENT_TAG, absorb_lfm_statement}; use super::transcript_replay::{Candidate, TranscriptReplay, assert_canonical, candidate_to_felt}; use super::word::{LfmWord, base_word, ext_word}; @@ -1679,7 +1679,7 @@ fn fixture_leg() -> (RealBatchedLfm, LfmProgram) { let opts = aggregation_wrap_options(); let program = trivial_program(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let arenas: Vec> = vec![ (0..4u64) .map(|i| core::array::from_fn(|j| FE::from(1_000 * (i + 1) + j as u64))) @@ -1877,7 +1877,7 @@ fn fixture_wraps() -> ( let mut arenas = super::epoch_tests::batched_epoch_arenas(&e); arenas.push(super::epoch_verify_tests::batched_opening_arena(&e)); arenas.push(super::epoch_verify_tests::batched_fri_arena(&e)); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let proved = match lfm_prove_batched(&program, &artifacts, &arenas, &opts) { Ok(p) => p, Err(e) => { @@ -1925,7 +1925,7 @@ fn fixture_aggregate() -> FixtureAggregate { let g = real_global(&elf_bytes, &bundle, &inner); let program = global_verifier_program(&g); let arenas = global_arena_words(&g); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let proved = lfm_prove_batched(&program, &artifacts, &arenas, &opts) .expect("the global wrap must prove batched at the aggregation preset"); let global_wrap = real_batched_lfm(artifacts, opts, &proved); @@ -2195,7 +2195,7 @@ fn the_aggregate_leg_census_matches_the_closed_form() { let opts = aggregation_wrap_options(); let program = trivial_program(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let arenas: Vec> = vec![ (0..4u64) .map(|i| core::array::from_fn(|j| FE::from(1_000 * (i + 1) + j as u64))) @@ -2420,7 +2420,8 @@ fn the_real_block_aggregates_end_to_end() { let mut arenas = super::epoch_tests::batched_epoch_arenas(&e); arenas.push(super::epoch_verify_tests::batched_opening_arena(&e)); arenas.push(super::epoch_verify_tests::batched_fri_arena(&e)); - let artifacts = build_artifacts(&program, &agg_opts); + let artifacts = + build_artifacts_with_hasher(&program, &agg_opts, crate::hash_pin::BLOCK_HASHER); let wrap_file = format!("wrap_{k}.rkyv"); let cached = cache_path(&wrap_file).is_some_and(|p| p.exists()); let tp = Instant::now(); @@ -2454,7 +2455,8 @@ fn the_real_block_aggregates_end_to_end() { let g = real_global(&inputs.elf_bytes, &bundle, &inner); let g_program = global_verifier_program(&g); let g_arenas = global_arena_words(&g); - let g_artifacts = build_artifacts(&g_program, &agg_opts); + let g_artifacts = + build_artifacts_with_hasher(&g_program, &agg_opts, crate::hash_pin::BLOCK_HASHER); let g_cached = cache_path("global_wrap.rkyv").is_some_and(|p| p.exists()); let tp = Instant::now(); let g_proved = if g_cached { @@ -2583,7 +2585,8 @@ fn the_real_block_aggregates_end_to_end() { Err(_) => agg_opts.clone(), }; let t = Instant::now(); - let agg_artifacts = build_artifacts(&program, &terminal_opts); + let agg_artifacts = + build_artifacts_with_hasher(&program, &terminal_opts, crate::hash_pin::BLOCK_HASHER); println!( " aggregation artifacts built in {:.1}s", t.elapsed().as_secs_f64() diff --git a/prover/src/lfm/registry.rs b/prover/src/lfm/registry.rs index 8fad7a29c..6ddb1554b 100644 --- a/prover/src/lfm/registry.rs +++ b/prover/src/lfm/registry.rs @@ -499,19 +499,25 @@ impl LfmArtifacts { /// root above moves with it and the tag on its own stops being the whole /// binding. pub fn build_artifacts(program: &LfmProgram, options: &ProofOptions) -> LfmArtifacts { - // ★ The block path's PINNED socket permutation, not `HasherKind::default()`. + // ⛔ **DEFAULTS TO `Test`, AND MUST.** The hasher is part of program + // IDENTITY — `HasherKind::as_tag` is folded into `lfm_program_id` — and + // `LFM_REGISTRY` is blessed under `compute_lfm_registry`'s + // `REGISTRY_HASHER = Test`, whose own doc calls changing it a re-blessing of + // the whole table rather than a re-run. // - // ⚠ The default is `Test`, a one-round toy, and under a BYTE hash that is - // free and correct: `ByteWrapHash::hash_bytes` lowers to the dedicated - // KECCAK / `LFM_BLAKE3` chips and emits no `Instr::Hash` at all, so the - // socket hasher is never consulted. The algebraic arm goes through - // `compress` / `permute`, which ARE `Instr::Hash` — so a program built here - // would be proved with the toy permutation while the host committed under - // RPO, and every digest downstream would be wrong. It surfaces as the - // grinding check refusing an honest nonce, naming nothing. + // ⚠ This entry point was briefly changed to name `hash_pin::BLOCK_HASHER`, + // to fix a real defect on the AGGREGATOR path where `lfm_prove_batched` + // inherited a toy permutation from a defaulted build. That fix was correct + // about the defect and wrong about its scope: it moved every registry + // program's identity away from the blessed table, and + // `rpo_chip_tests::the_rpo_choice_moves_the_program_digest_and_no_root` + // asserts this function defaults to `Test` in as many words. // - // On a byte pin this is `HasherKind::Test` and the call is unchanged. - build_artifacts_with_hasher(program, options, crate::hash_pin::BLOCK_HASHER) + // ★ The block path names its hasher AT THE CALL SITE + // ([`build_artifacts_with_hasher`]) instead. Registry programs pin a byte + // hash on their own builders, emit no `Instr::Hash`, and never consult the + // socket — they are correct at `Test` under every pin. + build_artifacts_with_hasher(program, options, HasherKind::default()) } /// [`build_artifacts`] for a program proved under an explicitly chosen diff --git a/prover/src/lfm/wrap_tests.rs b/prover/src/lfm/wrap_tests.rs index 127cbbba0..dd47b0ae1 100644 --- a/prover/src/lfm/wrap_tests.rs +++ b/prover/src/lfm/wrap_tests.rs @@ -43,7 +43,7 @@ use super::executor::execute; use super::hash::TestPermutation; use super::instr::Instr; use super::proof::{LfmProveError, lfm_prove, lfm_prove_with_residency, verify_against}; -use super::registry::build_artifacts; +use super::registry::{build_artifacts, build_artifacts_with_hasher}; use crate::tables::types::FE; @@ -731,7 +731,7 @@ fn the_wrap_reports_gpu_counters() { let program = super::epoch_tests::epoch_program(&e, true); let arenas = super::epoch_tests::epoch_arena_words(&e, true); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); println!(" chip log-heights: {:?}", artifacts.log_heights); g::reset_all_gpu_call_counters(); @@ -922,7 +922,7 @@ fn wrap_run_from(inner: ProofOptions, inputs: EpochInputs) { report_ratio(&e, main, aux); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); println!( " wrap options: blowup {}, {} queries, grinding {}\n chip log-heights: {:?}", opts.blowup_factor, opts.fri_number_of_queries, opts.grinding_factor, artifacts.log_heights @@ -1101,7 +1101,7 @@ fn the_wrap_commitments_match_across_residency_modes() { let program = super::epoch_tests::epoch_program(&e, true); let arenas = super::epoch_tests::epoch_arena_words(&e, true); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let prove_under = |residency: ResidencyMode| { let t = Instant::now(); @@ -1781,7 +1781,7 @@ fn batched_wrap_run_from(inner: ProofOptions, inputs: EpochInputs) { ); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); println!( " wrap options: blowup {}, {} queries, grinding {}\n chip log-heights: {:?}", opts.blowup_factor, opts.fri_number_of_queries, opts.grinding_factor, artifacts.log_heights @@ -1944,7 +1944,7 @@ fn the_fixture_continuation_epoch_wraps_batched_from_proofs() { arenas.push(super::epoch_verify_tests::batched_opening_arena(&e)); arenas.push(super::epoch_verify_tests::batched_fri_arena(&e)); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let proved = lfm_prove(&program, &artifacts, &arenas, &opts).expect("the carved wrap must prove"); @@ -2027,7 +2027,7 @@ fn the_from_proof_final_epoch_wraps() { let program = super::epoch_tests::epoch_program(&e, true); let arenas = super::epoch_tests::epoch_arena_words(&e, true); let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let t = Instant::now(); let proved = lfm_prove(&program, &artifacts, &arenas, &opts).expect("the wrap must prove"); @@ -2181,7 +2181,8 @@ fn the_real_block_proves_and_wraps_end_to_end() { .unwrap_or_else(|err| panic!("epoch {i} must reconstruct from the bundle: {err}")); let program = super::epoch_tests::epoch_program(&e, true); let arenas = super::epoch_tests::epoch_arena_words(&e, true); - let artifacts = build_artifacts(&program, &wrap_opts); + let artifacts = + build_artifacts_with_hasher(&program, &wrap_opts, crate::hash_pin::BLOCK_HASHER); let c = t.elapsed().as_secs_f64(); construct_secs += c; @@ -2383,7 +2384,8 @@ fn the_real_block_proves_and_wraps_end_to_end_batched() { let mut arenas = super::epoch_tests::batched_epoch_arenas(&e); arenas.push(super::epoch_verify_tests::batched_opening_arena(&e)); arenas.push(super::epoch_verify_tests::batched_fri_arena(&e)); - let artifacts = build_artifacts(&program, &wrap_opts); + let artifacts = + build_artifacts_with_hasher(&program, &wrap_opts, crate::hash_pin::BLOCK_HASHER); let c = t.elapsed().as_secs_f64(); construct_secs += c; From e6a4e27d7fbb91b100d9ad63959513ccdef0d767 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 13:44:12 -0700 Subject: [PATCH 02/23] refactor(lfm): build_artifacts defaults to REGISTRY_HASHER by name, not to a silent global MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same behaviour, different failure mode. HasherKind::default() on this path was a silent global: it happened to be Test, and nothing at the call site said why that was correct. REGISTRY_HASHER is the blessed tenant of LFM_REGISTRY, a named constant whose doc records that it is folded into every program_id and that a second hasher becomes additional ROWS rather than a replacement — so the default now states a fact about registry identity that a reader can check against the table. The constant moves from compute_lfm_registry into registry.rs, which is its proper home: it is a property of the TABLE, not of the generator, and build_artifacts has to name it. The generator imports it, so the value the table is blessed under and the value the unparameterised builder defaults to cannot drift apart — they are one definition. It also shrinks the enumeration gate's name-based residue. That gate's stark::config coverage is an allowlist and cannot lag, but HasherKind::default() and the Prover/Verifier call forms remain name-based and are where a fourth miss would live. One fewer occurrence on the path that matters most. (cherry picked from commit a6e7f0b79aa4d5ea418d4e70186c38ca0f878163) --- prover/src/bin/compute_lfm_registry.rs | 11 ++++------- prover/src/lfm/registry.rs | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/prover/src/bin/compute_lfm_registry.rs b/prover/src/bin/compute_lfm_registry.rs index f032825d9..647ae7e84 100644 --- a/prover/src/bin/compute_lfm_registry.rs +++ b/prover/src/bin/compute_lfm_registry.rs @@ -6,7 +6,6 @@ //! never re-blessed (the `compute_static_commitments` policy). use lambda_vm_prover::GoldilocksCubicProofOptions; -use lambda_vm_prover::lfm::hash::HasherKind; use lambda_vm_prover::lfm::programs::{ KECCAK_SPONGE_LEN, fri_toy_program, keccak_chain_program, keccak_sponge_program, statement_replay_program, transcript_replay_program, trivial_program, @@ -18,12 +17,10 @@ use lambda_vm_prover::lfm::validate; /// other presets come online). const REGISTRY_BLOWUP_FACTORS: &[u8] = &[2]; -/// The `LFM_HASH` permutation the v0 registry is generated under. -/// -/// Bound into every digest below, so changing it here is a re-blessing of the -/// whole table, not a re-run. A second hasher becomes additional rows, never a -/// silent replacement of these. -const REGISTRY_HASHER: HasherKind = HasherKind::Test; +// The permutation this table is blessed under is `registry::REGISTRY_HASHER` — +// a property of the TABLE rather than of this generator, and the same constant +// `build_artifacts` defaults to, so the two cannot drift apart. +use lambda_vm_prover::lfm::registry::REGISTRY_HASHER; fn fmt_bytes(bytes: &[u8; 32]) -> String { let inner = bytes diff --git a/prover/src/lfm/registry.rs b/prover/src/lfm/registry.rs index 6ddb1554b..7be31db62 100644 --- a/prover/src/lfm/registry.rs +++ b/prover/src/lfm/registry.rs @@ -498,6 +498,20 @@ impl LfmArtifacts { /// ever also selects the commitment scheme the roots are committed under, every /// root above moves with it and the tag on its own stops being the whole /// binding. +/// ★ The `LFM_HASH` permutation `LFM_REGISTRY` is blessed under. +/// +/// Bound into every digest in that table — `HasherKind::as_tag` is folded into +/// `lfm_program_id` — so changing it here is a re-blessing of the whole table, +/// not a re-run. **A second hasher becomes additional ROWS, never a silent +/// replacement of these.** +/// +/// ⚠ Lives here rather than in `compute_lfm_registry` because it is a property +/// of the TABLE, not of the generator, and because [`build_artifacts`] has to +/// name it: a default spelled `HasherKind::default()` is a silent global, while +/// one spelled `REGISTRY_HASHER` is a statement about registry identity that a +/// reader can check against the table. +pub const REGISTRY_HASHER: HasherKind = HasherKind::Test; + pub fn build_artifacts(program: &LfmProgram, options: &ProofOptions) -> LfmArtifacts { // ⛔ **DEFAULTS TO `Test`, AND MUST.** The hasher is part of program // IDENTITY — `HasherKind::as_tag` is folded into `lfm_program_id` — and @@ -517,7 +531,7 @@ pub fn build_artifacts(program: &LfmProgram, options: &ProofOptions) -> LfmArtif // ([`build_artifacts_with_hasher`]) instead. Registry programs pin a byte // hash on their own builders, emit no `Instr::Hash`, and never consult the // socket — they are correct at `Test` under every pin. - build_artifacts_with_hasher(program, options, HasherKind::default()) + build_artifacts_with_hasher(program, options, REGISTRY_HASHER) } /// [`build_artifacts`] for a program proved under an explicitly chosen From 40d2f09ab4bc79ffad82352860e97c24de8f1989 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Wed, 2 Sep 2026 13:57:51 -0700 Subject: [PATCH 03/23] fix(lfm): the two proves-and-verifies legs name the pin, closing a C-review miss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Option C reverted build_artifacts to the registry's blessed hasher and pinned the block path at its call sites — the aggregator's seven and the wrap leg's eight. It missed two: fri_tests::the_fri_leg_proves_and_verifies and join_tests::the_join_proves_and_verifies each build artifacts at a single site this lane had not classified, because the earlier pass through those two files fixed only their execute hasher and never looked at how they built artifacts. Both programs are built at WrapHash::production(), so they emit Instr::Hash and the AIR set must carry the pin's tenant. They were passing before C only because build_artifacts was globally returning BLOCK_HASHER — the same accidental cover that had been holding up seven wrap_tests sites, which were caught by classification before the revert could regress them. These two were not, because the classification sweep enumerated call sites in the files C touched rather than in every file that builds artifacts for a production-hash program. The rule that names the fix is the one in HASH-PINNING.md and it applied cleanly once asked: which program does the site build. Predicted as a C-review finding before the leg ran, and it is one — a pin missing where the blessed default was wrong, the opposite-direction error to the one that started this. (cherry picked from commit 28b7a36053c62678f0317b52c33e132da57daeeb) --- prover/src/lfm/fri_tests.rs | 8 ++++++-- prover/src/lfm/join_tests.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index 71ac14e51..47eecff15 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -1174,7 +1174,7 @@ fn the_shape_pins_the_lengths_production_must_check_at_runtime() { #[test] fn the_fri_leg_proves_and_verifies() { use super::proof::{lfm_prove, verify_against}; - use super::registry::build_artifacts; + use super::registry::build_artifacts_with_hasher; let h = host_fri(512, 2); assert_eq!( @@ -1201,7 +1201,11 @@ fn the_fri_leg_proves_and_verifies() { let mut arenas = h.trace.arenas(&queries); arenas.extend(h.fri_arenas(&queries)); - let artifacts = build_artifacts(&program, &opts); + // Built at `WrapHash::production()`, so it emits `Instr::Hash` and the + // artifacts must carry the pin's tenant — the classification rule in + // HASH-PINNING.md. `build_artifacts` defaults to the registry's blessed + // hasher, which is correct for registry programs and wrong for this one. + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let proved = lfm_prove(&program, &artifacts, &arenas, &opts) .expect("the joined trace+DEEP+FRI program must prove"); diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index 0459d8569..723e518e3 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -814,7 +814,7 @@ fn join_leg_cost() { use super::builder::{Bit, Cell, Ext, Felt}; use super::deep::{DeepOpening, emit_deep_invariants, emit_deep_point}; use super::proof::{lfm_prove, verify_against}; -use super::registry::build_artifacts; +use super::registry::build_artifacts_with_hasher; use super::sub_proof::{ GroupCommitment, GroupOpening, emit_group_authentication, emit_query_points, }; @@ -1018,7 +1018,11 @@ fn the_join_proves_and_verifies() { b.public(s.as_cell()); } let program = compile(b.finish()); - let artifacts = build_artifacts(&program, &opts); + // Built at `WrapHash::production()`, so it emits `Instr::Hash` and the + // artifacts must carry the pin's tenant — the classification rule in + // HASH-PINNING.md. `build_artifacts` defaults to the registry's blessed + // hasher, which is correct for registry programs and wrong for this one. + let artifacts = build_artifacts_with_hasher(&program, &opts, crate::hash_pin::BLOCK_HASHER); let proved = lfm_prove(&program, &artifacts, &h.arenas(&queries), &opts) .expect("the joined sub-proof must prove"); From a36f0c6df3e7e5aca175752ef6ea0c592186b1fd Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 11:17:45 -0700 Subject: [PATCH 04/23] fix(lfm): every test that names a hash takes it from one source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three arms of one class, each a pair of things that must agree on the hash and did not. All three are no-ops on a byte pin, where BLOCK_HASHER is HasherKind::Test and a digest is two words, so the base does not move. (builder, executor) — 24 sites in fri_tests, join_tests and epoch_verify_tests built their programs at WrapHash::production() and then handed execute the one-round toy. Under an algebraic pin the program emits Instr::Hash and the walk reconstructs nothing, surfacing as DivByZero on HONEST paths several thousand instructions from the cause. Every LfmBuilder::new() in those three files uses WrapHash::production(), so the replacement is exact rather than a blanket; the five other files using TestPermutation pin a byte hash on their own builders and are legitimately toy-hash tests. (AIR, trace) — 12 sites in machine_tests, blake3_chip_tests and wrap_tests built traces through the defaulting build_traces while proving against artifacts whose AIR set follows the pin. The socket chip's WIDTH is tenant-dependent, so HashConstraints::eval indexes a trace built for another tenant and panics out of bounds inside a rayon worker. They now take the tenant from artifacts.hasher — the artifacts they are about to prove against, one source rather than two. wrap_tests' census test needed its artifacts moved above the execute so that execution, traces, census and AIRs share it. This mismatch did not pre-exist: before build_artifacts was pinned, artifacts.hasher was ALSO Test and the pair agreed by both being wrong. Correcting one half and leaving the other is what created it. (writer, reader) — machine_tests' digest_bytes read eight halves from two public words. An algebraic digest is ONE word of four canonical felts. It now discriminates on the slice's own length rather than on WrapHash::production(), because its callers mix registry-pinned two-word programs with configuration-following one-word ones and a global branch would break the first group. Same reason the R1f instrument's fix moved this failure here: an emitter migrated without its reader. wrap_tests' module doc claimed every permutation in it is TestPermutation, which these changes make false; corrected rather than left, per SOUNDNESS.md 6.8. (cherry picked from commit d66b568218be72d9033f72de45f71b5dd6568649) --- prover/src/lfm/blake3_chip_tests.rs | 6 ++-- prover/src/lfm/fri_tests.rs | 17 +++++------ prover/src/lfm/join_tests.rs | 27 +++++++++-------- prover/src/lfm/machine_tests.rs | 45 ++++++++++++++++++++++------- prover/src/lfm/wrap_tests.rs | 37 +++++++++++++++++------- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/prover/src/lfm/blake3_chip_tests.rs b/prover/src/lfm/blake3_chip_tests.rs index 349b92249..4bf99eef3 100644 --- a/prover/src/lfm/blake3_chip_tests.rs +++ b/prover/src/lfm/blake3_chip_tests.rs @@ -502,7 +502,7 @@ fn tampering_with_the_blake3_witness_is_not_accepted() { let program = blake3_sponge_program(65); let artifacts = build_artifacts(&program, &opts); let exec = execute(&program, &sponge_arenas(&msg), &TestPermutation).expect("execute"); - let mut traces = super::trace::build_traces(&program, &exec.records); + let mut traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); // One output byte of the first compression. let col = cols::out_word(0, 0); @@ -1596,7 +1596,7 @@ fn blake3_chunking_splits_the_chain_into_uneven_chunks() { &TestPermutation, ) .expect("honest execution"); - let traces = super::trace::build_traces(&program, &exec.records); + let traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); assert_eq!(traces.blake3.len(), 3, "one LFM_BLAKE3 trace per chunk"); assert_eq!( traces @@ -1780,7 +1780,7 @@ fn a_tampered_non_first_blake3_chunk_rejects() { let artifacts = build_artifacts(&program, &opts); let exec = execute(&program, &sponge_arenas(&msg), &TestPermutation).expect("execute"); - let mut traces = super::trace::build_traces(&program, &exec.records); + let mut traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); assert_eq!(traces.blake3.len(), 3); // One output byte of the LAST chunk's first compression — the eleventh of // the twelve, which no other chunk carries. diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index 47eecff15..81c7e01c5 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -48,7 +48,6 @@ use super::executor::execute; use super::fri::{ FRI_LEAF_GROUP, FriQuery, FriShape, declare_fri, emit_query_fri, hint_layer_openings, }; -use super::hash::TestPermutation; use super::join_tests::{HostSubProof, build_host_sub_proof}; use super::validator::validate; use super::word::{LfmWord, base_word, ext_word, word_as_ext}; @@ -299,7 +298,7 @@ fn the_fri_leaf_is_byte_identical_to_productions_own_backends() { let mut digests = Vec::new(); for (i, (a, c)) in vectors.iter().enumerate() { let arenas = vec![vec![ext_word(a), ext_word(c)]]; - let exec = execute(&program, &arenas, &TestPermutation).expect("the leaf hash executes"); + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).expect("the leaf hash executes"); let got = [exec.public_words[0].1, exec.public_words[1].1]; let batched = @@ -622,7 +621,7 @@ fn the_fri_emitter_verifies_every_query_of_a_real_folding_proof() { let h = host_fri(rows, 2); let all: Vec = (0..h.trace.iotas.len()).collect(); let program = fri_only_program(h.shape, all.len()); - let exec = execute(&program, &h.all_arenas(&all), &TestPermutation).expect( + let exec = execute(&program, &h.all_arenas(&all), &crate::hash_pin::BLOCK_HASHER).expect( "an honest FRI decommitment must authenticate every layer and reach \ the terminal polynomial", ); @@ -707,7 +706,7 @@ fn the_two_legs_verify_one_real_folding_proof_as_one_program() { let mut arenas = h.trace.arenas(&queries); arenas.extend(h.fri_arenas(&queries)); - let exec = execute(&program, &arenas, &TestPermutation) + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .expect("the honest proof must authenticate, fold and reach the terminal"); let codeword = h.terminal_codeword(); @@ -1031,7 +1030,7 @@ fn no_tampered_fri_value_can_pass() { }; let program = fri_only_program(shape, queries.len()); let honest = h.all_arenas(&queries); - execute(&program, &honest, &TestPermutation).expect("the honest run must execute"); + execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER).expect("the honest run must execute"); let stride = h.shape.query_words(); // (label, arena, word) — arena order is the driver's: deep, roots, zetas, @@ -1057,7 +1056,7 @@ fn no_tampered_fri_value_can_pass() { for (label, arena, word) in bump { let mut tampered = honest.clone(); tampered[arena][word][0] += FE::one(); - let err = execute(&program, &tampered, &TestPermutation).expect_err(&format!( + let err = execute(&program, &tampered, &crate::hash_pin::BLOCK_HASHER).expect_err(&format!( "moving the {label} must make the program unexecutable" )); println!(" {label:<40} rejected: {err:?}"); @@ -1076,7 +1075,7 @@ fn no_tampered_fri_value_can_pass() { splice is a no-op and this vector proves nothing" ); spliced[4][to..to + len].copy_from_slice(&borrowed); - let err = execute(&program, &spliced, &TestPermutation).expect_err( + let err = execute(&program, &spliced, &crate::hash_pin::BLOCK_HASHER).expect_err( "a REAL leaf and a REAL path, at the wrong index, must still be rejected \ — the walk climbs at this query's own bits", ); @@ -1116,7 +1115,7 @@ fn the_shape_pins_the_lengths_production_must_check_at_runtime() { }; let program = fri_only_program(shape, 1); let honest = h.all_arenas(&queries); - execute(&program, &honest, &TestPermutation).expect("the honest run must execute"); + execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER).expect("the honest run must execute"); // (label, arena, what the truncation would buy a prover) let attacks: [(&str, usize, &str); 3] = [ @@ -1140,7 +1139,7 @@ fn the_shape_pins_the_lengths_production_must_check_at_runtime() { for (label, arena, mirrors) in attacks { let mut truncated = honest.clone(); truncated[arena].clear(); - let err = execute(&program, &truncated, &TestPermutation) + let err = execute(&program, &truncated, &crate::hash_pin::BLOCK_HASHER) .expect_err(&format!("{label} must be refused")); assert!( matches!(err, LfmExecError::ArenaLenMismatch { .. }), diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index 723e518e3..bb656ae29 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -38,7 +38,6 @@ use super::builder::LfmBuilder; use super::compiler::compile; use super::constraint_tests::{deep_shape, open_sub_proof, real_fixture}; use super::executor::execute; -use super::hash::TestPermutation; use super::sub_proof::{ GroupShape, ROWS_PER_LEAF, SubProofShape, emit_sub_proof, emit_sub_proof_with_bits, }; @@ -398,7 +397,7 @@ fn the_join_premises_hold_on_a_real_proof() { for (q, iota) in h.iotas.iter().enumerate() { let arenas = vec![vec![base_word(FE::from(*iota as u64))]]; - let exec = execute(&program, &arenas, &TestPermutation).expect("the derivation executes"); + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).expect("the derivation executes"); assert_eq!( exec.public_words[0].1[0], h.points[q].0, "query {q}: the machine's point must be \ @@ -438,7 +437,7 @@ fn the_join_matches_the_production_verifier_on_every_query() { let program = compile(b.finish()); validate(&program).expect("the joined sub-proof program is admissible"); - let exec = execute(&program, &h.arenas(&all), &TestPermutation) + let exec = execute(&program, &h.arenas(&all), &crate::hash_pin::BLOCK_HASHER) .expect("an honest sub-proof must authenticate and fold"); let mut nonzero = 0usize; @@ -1088,7 +1087,7 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { b.public(s.as_cell()); } let program = compile(b.finish()); - let honest = execute(&program, &h.arenas(&[q]), &TestPermutation).expect("honest"); + let honest = execute(&program, &h.arenas(&[q]), &crate::hash_pin::BLOCK_HASHER).expect("honest"); // Sweep every value slot of every group, so no vector class (first group, // first column, regular point) is silently the only one tested. @@ -1107,7 +1106,7 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { arenas[4][word_of_slot][0] += FE::one(); // Incoherent: the real roots, a moved leaf. - let err = execute(&program, &arenas, &TestPermutation) + let err = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() .unwrap_or_else(|| { panic!("{label}: group {g} slot {slot}: a moved value must not authenticate") @@ -1125,7 +1124,7 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { let mut coherent_roots = h.roots.clone(); coherent_roots[g] = forged; arenas[3] = commitments_to_arena(&coherent_roots); - let forged_run = execute(&program, &arenas, &TestPermutation).unwrap_or_else(|e| { + let forged_run = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).unwrap_or_else(|e| { panic!("{label}: group {g} slot {slot}: the coherent forgery must execute: {e:?}") }); // Which of the two points moves is not incidental: a leaf holds @@ -1192,14 +1191,14 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { trees are degenerate at this index and the walk half of this vector \ tests nothing" ); - execute(&program, &arenas, &TestPermutation) + execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() .unwrap_or_else(|| { panic!("{label}: index bit {level}: a moved index must not authenticate") }); arenas[3] = commitments_to_arena(&coherent_roots); - let forged = execute(&program, &arenas, &TestPermutation).unwrap_or_else(|e| { + let forged = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).unwrap_or_else(|e| { panic!("{label}: index bit {level}: coherent forgery must execute: {e:?}") }); assert_ne!( @@ -1221,7 +1220,7 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { let base = 1 + groups[0].num_values(); arenas[4][base..base + 2 * h.shape.merkle_depth] .copy_from_slice(&commitments_to_arena(&siblings)); - execute(&program, &arenas, &TestPermutation) + execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() .unwrap_or_else(|| { panic!("{label}: sibling level {level}: a moved path must not authenticate") @@ -1270,7 +1269,7 @@ fn the_controls_show_what_the_join_denies() { validate(&program).expect("admissible"); let mut arenas = h.arenas(&[q]); arenas.push(h.split_values(q)); - let clean = execute(&program, &arenas, &TestPermutation) + let clean = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .expect("the control must accept honest inputs"); assert_eq!( word_as_ext(&clean.public_words[0].1).expect("ext"), @@ -1281,7 +1280,7 @@ fn the_controls_show_what_the_join_denies() { let mut attacked = arenas.clone(); attacked[5][0][0] += FE::one(); - let forged = execute(&program, &attacked, &TestPermutation).expect( + let forged = execute(&program, &attacked, &crate::hash_pin::BLOCK_HASHER).expect( "SplitValues: authenticating one set of values and folding another is \ exactly what this control permits", ); @@ -1302,7 +1301,7 @@ fn the_controls_show_what_the_join_denies() { validate(&program).expect("admissible"); let mut arenas = h.arenas(&[q]); arenas.push(vec![base_word(h.points[q].0), base_word(h.points[q].1)]); - let clean = execute(&program, &arenas, &TestPermutation).expect("honest"); + let clean = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).expect("honest"); assert_eq!( word_as_ext(&clean.public_words[0].1).expect("ext"), h.expected[q].0 @@ -1310,7 +1309,7 @@ fn the_controls_show_what_the_join_denies() { let mut attacked = arenas.clone(); attacked[5] = vec![base_word(h.points[other].0), base_word(h.points[other].1)]; - let forged = execute(&program, &attacked, &TestPermutation).expect( + let forged = execute(&program, &attacked, &crate::hash_pin::BLOCK_HASHER).expect( "HintedPoint: a hinted point is not tied to the authenticated index, \ which is what this control permits", ); @@ -1533,7 +1532,7 @@ fn the_precomputed_group_comes_first_and_that_is_checkable() { } let program = compile(b.finish()); validate(&program).expect("admissible"); - let exec = execute(&program, &h.arenas(&queries), &TestPermutation) + let exec = execute(&program, &h.arenas(&queries), &crate::hash_pin::BLOCK_HASHER) .expect("the four-group sub-proof must authenticate and fold"); for (k, q) in queries.iter().enumerate() { assert_eq!( diff --git a/prover/src/lfm/machine_tests.rs b/prover/src/lfm/machine_tests.rs index 614425482..82bf1a35a 100644 --- a/prover/src/lfm/machine_tests.rs +++ b/prover/src/lfm/machine_tests.rs @@ -285,7 +285,7 @@ use super::layout::keccak as klayout; use super::programs::{keccak_chain_program, keccak_chain_program_source}; use super::proof::prove_traces; use super::registry::LfmArtifacts; -use super::trace::{LfmTraces, build_traces}; +use super::trace::{LfmTraces, build_traces_with_hasher}; use super::validator::LfmViolation; use crate::lfm::chips::keccak as kchip; use crate::tables::types::VmTable; @@ -321,7 +321,7 @@ fn prove_keccak_chain_with_tamper( let exec = super::executor::execute(program, &keccak_arenas(seed), &super::hash::TestPermutation) .expect("honest execution"); - let mut traces = build_traces(program, &exec.records); + let mut traces = build_traces_with_hasher(program, &exec.records, artifacts.hasher); mutate(&mut traces); let proof = prove_traces(artifacts, &mut traces, &exec.public_words, &opts)?; Ok((proof, exec.public_words)) @@ -606,10 +606,33 @@ fn sponge_arenas(msg: &[u8]) -> Vec> { vec![halves.into_iter().map(super::word::base_word).collect()] } -/// The 32-byte digest from the two public words: byte `j` is byte `j % 4` of -/// half `j / 4`, and half `h` is lane `h % 4` of word `h / 4`. +/// The 32 bytes a published digest stands for, at whatever width it was +/// published. +/// +/// A BYTE digest is two words read as eight little-endian `u32` halves: byte +/// `j` is byte `j % 4` of half `j / 4`, and half `h` is lane `h % 4` of word +/// `h / 4`. An ALGEBRAIC digest is ONE word of four canonical felts, and its 32 +/// bytes are the backend's own serialisation of them. +/// +/// ⛔ **The discriminator is the slice's own length, NOT +/// `WrapHash::production()`, and that distinction is the whole point.** This +/// helper's callers mix two kinds of program: ones that pin a byte hash on their +/// own builder (`keccak_sponge_program`, `blake3_sponge_program` — always two +/// words, on every branch, because their identity is registry-pinned) and ones +/// that follow the configuration (`merkle_opening_program` — one word on an +/// algebraic arm). Branching on the global configuration would render the first +/// group wrong on an algebraic branch, which is the same scope error as reading +/// a root's width from the configuration instead of from the root. fn digest_bytes(public: &[(u32, LfmWord)]) -> [u8; 32] { use math::field::traits::IsPrimeField; + if public.len() == 1 { + return super::algebraic_commit::digest_to_commitment(&public[0].1); + } + assert_eq!( + public.len(), + 2, + "a digest is one algebraic word or two byte words" + ); let mut out = [0u8; 32]; for h in 0..8 { let lane = public[h / 4].1[h % 4]; @@ -766,7 +789,7 @@ fn tampered_absorb_xor_rejects() { &super::hash::TestPermutation, ) .expect("honest execution"); - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); // Rate byte 5 of the first absorb row: XOR(state, block) no longer holds. let col = kchip::cols::PERM_IN + 5; let old = traces.keccak.main_table.get_row(0)[col]; @@ -873,7 +896,7 @@ fn permute_row_cannot_substitute_the_permuted_state() { exec.records.public[1] = words[0]; exec.records.public[2] = words[1]; - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); let proof = prove_traces(&artifacts, &mut traces, &exec.public_words, &opts) .expect("the prover has no constraint checks, so it accepts"); assert!( @@ -1272,7 +1295,7 @@ fn canonicity_guard_rejects_an_out_of_range_candidate_in_the_proof() { exec.records.public[0] = super::word::base_word(FE::zero()); exec.public_words[0].1 = super::word::base_word(FE::zero()); - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); let proof = prove_traces(&artifacts, &mut traces, &exec.public_words, &opts) .expect("the prover has no constraint checks, so it accepts"); assert!( @@ -2689,7 +2712,7 @@ fn chunking_splits_the_sponge_into_two_uneven_chunks() { &super::hash::TestPermutation, ) .expect("honest execution"); - let traces = build_traces(&program, &exec.records); + let traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); assert_eq!(traces.keccak_rnd.len(), 2, "one KECCAK_RND trace per chunk"); assert_eq!( traces @@ -2802,7 +2825,7 @@ fn tampered_second_chunk_permutation_rejects() { ) .expect("honest execution"); - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); assert_eq!(traces.keccak_rnd.len(), 2); // Byte 0 of lane (0,0) on the second chunk's first row: the `Keccak` // receive token no longer matches the send that fed it. @@ -2846,7 +2869,7 @@ fn dropping_the_second_chunks_permutation_rejects() { ) .expect("honest execution"); - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); // Same chunk COUNT — so the AIR set and the digest still match — but the // last chunk is now empty. traces.keccak_rnd[1] = keccak_rnd::generate_keccak_rnd_trace(&[]); @@ -2890,7 +2913,7 @@ fn permutations_may_be_reassigned_across_chunk_boundaries() { let round_ops = round_ops_of(&program, &sponge_arenas(&msg)); assert_eq!(round_ops.len(), 3); - let mut traces = build_traces(&program, &exec.records); + let mut traces = build_traces_with_hasher(&program, &exec.records, artifacts.hasher); // Canonical split is 2 + 1; re-split as 1 + 2. traces.keccak_rnd[0] = keccak_rnd::generate_keccak_rnd_trace(&round_ops[..1]); traces.keccak_rnd[1] = keccak_rnd::generate_keccak_rnd_trace(&round_ops[1..]); diff --git a/prover/src/lfm/wrap_tests.rs b/prover/src/lfm/wrap_tests.rs index dd47b0ae1..47eff9bf8 100644 --- a/prover/src/lfm/wrap_tests.rs +++ b/prover/src/lfm/wrap_tests.rs @@ -23,10 +23,17 @@ //! //! ## What this module cannot see //! -//! The hash. Every permutation here is `TestPermutation` inside the LFM chips -//! plus the production keccak family hosted for `keccak256`; the point of -//! measuring cells at all is to have the first column of a matrix whose other -//! columns (blake, Poseidon) do not exist yet. It also cannot see prove time or +//! The hash — MOSTLY. This module was written when every permutation here was +//! `TestPermutation` inside the LFM chips plus the production keccak family +//! hosted for `keccak256`, and the point of measuring cells at all was to have +//! the first column of a matrix whose other columns did not exist yet. Those +//! columns exist now (BLAKE3, RPO, RPX, Poseidon), so +//! ⚠ [`the_census_agrees_with_the_traces_the_prover_builds`] takes its tenant +//! from `artifacts.hasher` rather than defaulting: it compares a census, a trace +//! set and an AIR set, and the socket chip's WIDTH is tenant-dependent, so three +//! defaults against one pinned artifact set is an out-of-bounds index rather +//! than a disagreement. Everything else here is still tenant-agnostic. It also +//! cannot see prove time or //! peak memory as a property of the machine — those are measured around the //! process, by the harness that runs it, and are reported as observations of one //! box rather than as machine invariants. @@ -40,7 +47,6 @@ use super::compiler::LfmProgram; use super::edsl::WrapHash; use super::epoch_tests::EpochInputs; use super::executor::execute; -use super::hash::TestPermutation; use super::instr::Instr; use super::proof::{LfmProveError, lfm_prove, lfm_prove_with_residency, verify_against}; use super::registry::{build_artifacts, build_artifacts_with_hasher}; @@ -1452,9 +1458,19 @@ fn the_census_agrees_with_the_traces_the_prover_builds() { let state: [u64; 25] = core::array::from_fn(|i| 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i as u64 + 1)); let arenas = vec![super::keccak_adapter::state_to_words(&state).to_vec()]; - let exec = execute(&program, &arenas, &TestPermutation).expect("the chain program runs"); - let traces = super::trace::build_traces(&program, &exec.records); - let census = lfm_chip_census(&program); + // ⚠ ONE tenant for all four of execution, traces, census and AIRs, taken + // from the artifacts this test is about to compare against. The four used to + // default to `HasherKind::Test` while `build_artifacts` named the pin, and a + // trace built for one tenant against constraints built for another is an + // out-of-bounds index inside `HashConstraints::eval` — the socket chip's + // width is tenant-dependent (436 columns for RPO against 3,056 for BLAKE3). + // It surfaces as a bounds panic in a rayon worker, which reaches the test as + // "a scoped thread panicked" and names nothing at all. + let opts = wrap_options(); + let artifacts = build_artifacts(&program, &opts); + let exec = execute(&program, &arenas, &artifacts.hasher).expect("the chain program runs"); + let traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); + let census = super::airs::lfm_chip_census_with_hasher(&program, artifacts.hasher); // The frozen AIR order, as the census emits it and `air_trace_pairs` proves // it. Built from the trace set so a chip whose height the census got from the @@ -1517,12 +1533,11 @@ fn the_census_agrees_with_the_traces_the_prover_builds() { } // ---- the AIR set: the names and the widths, in the frozen order. - let opts = wrap_options(); - let artifacts = build_artifacts(&program, &opts); - let airs = super::airs::LfmAirs::new( + let airs = super::airs::LfmAirs::new_with_hasher( &artifacts.roots, &opts, artifacts.keccak_rnd_chunks, + artifacts.hasher, artifacts.chip_set, ); let refs = airs.air_refs(); From 00f1838103ebfc5a72cfdba47d9e43a0390b26fb Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 11:42:03 -0700 Subject: [PATCH 05/23] =?UTF-8?q?fix(lfm,tests):=20the=20prover=20and=20ve?= =?UTF-8?q?rifier=20aliases=20ARE=20the=20default=20hash=20=E2=80=94=20nam?= =?UTF-8?q?e=20the=20pin=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stark::prover::Prover and stark::verifier::Verifier are GenericProver and GenericVerifier AT DefaultStarkHash. They are the silent spelling of the symbol the alias-implied-hash class is named after, and commit 98b07988 closed that class for PRODUCTION paths only while claiming it closed. Nineteen files kept calling the BLAKE3 alias on branches whose whole purpose is that the pin differs. Two distinct pairings, both mechanical: (prover, verifier) — 28 call sites across seven files proved through multi_prove_ram, which is BlockProver and follows the pin, then verified through the BLAKE3 alias. bitwise_bus, bitwise, branch_bus, local_to_global_bus, lt_bus, logup_tests and prove_elfs_tests. prove_elfs_tests was half-migrated across four axes inside one function: pinned prover, byte prove transcript, pinned bus-balance replay transcript, aliased verifier. That is the axis-2 half-flip hash_pin's own module doc calls silent, and it is loud here only because the replay transcript disagrees with the other two. The inconsistency is what saved it. (blessed roots, prover) — keccak_probe, blake3_probe, framework_probe and the two PoCs are internally consistent on the default, so they looked fine. Their AIRs carry the preprocessed commitments regenerated under the pin, so an alias prover recomputes a BLAKE3 root against an RPO constant and refuses at PrecomputedCommitmentMismatch. That is exactly keccak_probe.rs:170's message, "should reach the verifier, but the prover refused first". Self-consistency between prover and verifier is not enough when a third thing already moved. Everything here is a no-op on a byte pin by construction: BlockProver and BlockVerifier ARE the aliases when BlockStarkHash is DefaultStarkHash, and block_transcript is DefaultStarkTranscript::new. The gate gains the two symbols and a different question. It searched for DefaultStarkHash, DefaultStarkTranscript and HasherKind::default() and not for the two aliases that silently mean the first — so it looked for the class by the one name the class never uses, and reported green. It now matches the call forms Prover::multi_prove and Verifier::multi_verify, excluding lines that name BlockProver or BlockVerifier. And its blessed list documents the corrected question: not "is this reachable from production?" but "is this default PAIRED with a non-default?". Test-only is not safe, it is only production-safe. Recorded with it: a correct fix to one half of a wrong-but-consistent pair CREATES the failure, so a red test after such a fix is evidence the fix worked. (cherry picked from commit 75efe380b4d92b4e930e70f324b8756e53f6bcb1) --- prover/src/lfm/blake3_probe.rs | 12 +-- prover/src/lfm/framework_probe.rs | 18 ++-- prover/src/lfm/keccak_probe.rs | 12 +-- prover/src/lfm/logup_tests.rs | 14 +-- prover/src/tests/bitwise_bus_tests.rs | 14 +-- prover/src/tests/bitwise_tests.rs | 20 ++--- prover/src/tests/branch_bus_tests.rs | 14 +-- prover/src/tests/hash_pin_enumeration.rs | 42 ++++++++- prover/src/tests/local_to_global_bus_tests.rs | 34 +++---- prover/src/tests/lt_bus_tests.rs | 14 +-- prover/src/tests/page_offset_forgery_poc.rs | 10 +-- prover/src/tests/prove_elfs_tests.rs | 90 +++++++++---------- .../src/tests/recursion_soundness_gap_poc.rs | 6 +- 13 files changed, 170 insertions(+), 130 deletions(-) diff --git a/prover/src/lfm/blake3_probe.rs b/prover/src/lfm/blake3_probe.rs index f7b1834c4..ad5ff8894 100644 --- a/prover/src/lfm/blake3_probe.rs +++ b/prover/src/lfm/blake3_probe.rs @@ -39,10 +39,10 @@ use stark::lookup::{ }; use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions}; use stark::proof::view::MultiProofView; -use stark::prover::{IsStarkProver, Prover}; +use stark::prover::IsStarkProver; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::bitwise; use crate::tables::types::{BusId, FE, FEE, GoldilocksExtension, GoldilocksField, VmTable}; @@ -70,8 +70,8 @@ fn options() -> ProofOptions { GoldilocksCubicProofOptions::with_blowup(2).expect("probe options") } -fn transcript() -> DefaultTranscript { - let mut t = DefaultTranscript::::new(&[]); +fn transcript() -> crate::hash_pin::BlockTranscript { + let mut t = crate::hash_pin::block_transcript(&[]); t.append_bytes(PROBE_TAG); t } @@ -268,7 +268,7 @@ fn prove_traces( let pairs: Vec<(DynAir, &mut TraceTable, &())> = vec![(chip, t0, &()), (&mirror, t1, &()), (&bw_air, t2, &())]; let mut t = transcript(); - Prover::multi_prove( + crate::hash_pin::BlockProver::multi_prove( pairs, &mut t, #[cfg(feature = "disk-spill")] @@ -289,7 +289,7 @@ fn verify_proof( ); let refs: Vec = vec![chip, &mirror, &bw_air]; let mut vt = transcript(); - Verifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) + crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) } /// Prove + verify, optionally corrupting the chip trace in between. diff --git a/prover/src/lfm/framework_probe.rs b/prover/src/lfm/framework_probe.rs index 6368c2030..37bd79dc6 100644 --- a/prover/src/lfm/framework_probe.rs +++ b/prover/src/lfm/framework_probe.rs @@ -20,10 +20,10 @@ use stark::lookup::{ }; use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions}; use stark::proof::view::MultiProofView; -use stark::prover::{IsStarkProver, Prover}; +use stark::prover::IsStarkProver; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::types::{FE, FEE, GoldilocksExtension, GoldilocksField}; @@ -113,8 +113,8 @@ fn prep_root(opts: &ProofOptions) -> Commitment { commit_columns(&[values(), vec![FE::one(); NUM_ROWS]], opts) } -fn transcript() -> DefaultTranscript { - let mut t = DefaultTranscript::::new(&[]); +fn transcript() -> crate::hash_pin::BlockTranscript { + let mut t = crate::hash_pin::block_transcript(&[]); t.append_bytes(PROBE_TAG); t } @@ -128,7 +128,7 @@ fn prove( let pairs: Vec<(DynAir, &mut TraceTable, &())> = vec![(sender, &mut st, &()), (receiver, &mut rt, &())]; let mut t = transcript(); - Prover::multi_prove( + crate::hash_pin::BlockProver::multi_prove( pairs, &mut t, #[cfg(feature = "disk-spill")] @@ -148,7 +148,7 @@ fn b0_preprocessed_multiplicity_round_trips() { let refs: Vec = vec![&sender, &receiver]; let mut vt = transcript(); assert!( - Verifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), "honest proof must verify" ); } @@ -180,7 +180,7 @@ fn b0_verifier_rejects_wrong_preprocessed_root() { let refs: Vec = vec![&bad_sender, &receiver]; let mut vt = transcript(); assert!( - !Verifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + !crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), "a supplied root differing from the proof's must reject" ); } @@ -200,7 +200,7 @@ fn b0_tampered_witness_value_breaks_balance() { let pairs: Vec<(DynAir, &mut TraceTable, &())> = vec![(&sender, &mut st, &()), (&receiver, &mut rt, &())]; let mut t = transcript(); - let proof = Prover::multi_prove( + let proof = crate::hash_pin::BlockProver::multi_prove( pairs, &mut t, #[cfg(feature = "disk-spill")] @@ -212,7 +212,7 @@ fn b0_tampered_witness_value_breaks_balance() { let refs: Vec = vec![&sender, &receiver]; let mut vt = transcript(); assert!( - !Verifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + !crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), "unbalanced bus must reject" ); } diff --git a/prover/src/lfm/keccak_probe.rs b/prover/src/lfm/keccak_probe.rs index b8a8dd208..abac4a433 100644 --- a/prover/src/lfm/keccak_probe.rs +++ b/prover/src/lfm/keccak_probe.rs @@ -13,10 +13,10 @@ use stark::constraints::builder::EmptyConstraints; use stark::lookup::{AirWithBuses, AuxiliaryTraceBuildData, NullBoundaryConstraintBuilder}; use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions}; use stark::proof::view::MultiProofView; -use stark::prover::{IsStarkProver, Prover}; +use stark::prover::IsStarkProver; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::types::{FE, FEE, GoldilocksExtension, GoldilocksField, VmTable}; use crate::tables::{bitwise, keccak_rc, keccak_rnd}; @@ -35,8 +35,8 @@ fn options() -> ProofOptions { GoldilocksCubicProofOptions::with_blowup(2).expect("probe options") } -fn transcript() -> DefaultTranscript { - let mut t = DefaultTranscript::::new(&[]); +fn transcript() -> crate::hash_pin::BlockTranscript { + let mut t = crate::hash_pin::block_transcript(&[]); t.append_bytes(PROBE_TAG); t } @@ -115,7 +115,7 @@ fn prove_traces( (&bw_air, t3, &()), ]; let mut t = transcript(); - Prover::multi_prove( + crate::hash_pin::BlockProver::multi_prove( pairs, &mut t, #[cfg(feature = "disk-spill")] @@ -140,7 +140,7 @@ fn verify_proof( ); let refs: Vec = vec![adapter, &rnd_air, &rc_air, &bw_air]; let mut vt = transcript(); - Verifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) + crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) } /// Prove + verify, optionally corrupting the adapter trace in between. diff --git a/prover/src/lfm/logup_tests.rs b/prover/src/lfm/logup_tests.rs index 76179939f..21dc409c8 100644 --- a/prover/src/lfm/logup_tests.rs +++ b/prover/src/lfm/logup_tests.rs @@ -3,7 +3,7 @@ //! ## The oracles //! //! Two, both production's own. `compute_commit_bus_offset` (`lib.rs`) for the -//! COMMIT-bus target, and `Verifier::multi_verify` for the balance itself — the +//! COMMIT-bus target, and `crate::hash_pin::BlockVerifier::multi_verify` for the balance itself — the //! fixture is a real sender/receiver pair whose bus genuinely closes, and //! production accepting it at target zero is what says so. Nothing here asserts //! a balance this file computed. @@ -23,7 +23,7 @@ use stark::proof::stark::MultiProof; use stark::proof::view::StarkProofView; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::types::{FE, FEE, GoldilocksExtension, GoldilocksField}; @@ -278,7 +278,7 @@ fn the_closure_matches_a_bus_that_really_balances() { airs.iter().map(|a| &**a).collect(); assert!( - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &air_refs, &proof, &mut crate::hash_pin::block_transcript(&[]), @@ -757,7 +757,7 @@ fn the_closure_accumulates_per_chunk_not_per_family() { assert_eq!(air_refs.len(), 3, "one sender and two chunks of one family"); assert!( - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &air_refs, &proof, &mut crate::hash_pin::block_transcript(&[]), @@ -963,7 +963,7 @@ enum RowWitness { /// One REAL continuation epoch — epoch 0 of the LFM fixture guest, built by /// `Traces::from_image_and_logs` and proved over the production epoch AIR set /// (`VmAirs` + the epoch-local L2G table) under the real epoch statement, then -/// ACCEPTED by `Verifier::multi_verify_views` against production's own +/// ACCEPTED by `crate::hash_pin::BlockVerifier::multi_verify_views` against production's own /// `compute_expected_commit_bus_balance_view`. The acceptance is load-bearing /// twice over: it is what makes this "what a verifying epoch proof carries" /// rather than "what some prover run emitted", and it is what runs @@ -1232,7 +1232,7 @@ fn a_zero_row_fixed_table_carries_some_zero_not_none() { ) .expect("the COMMIT-bus target must exist"); assert!( - Verifier::multi_verify_views(&refs, view, &mut seed(), &expected), + crate::hash_pin::BlockVerifier::multi_verify_views(&refs, view, &mut seed(), &expected), "production must ACCEPT this epoch proof — the measurement is about what \ a VERIFYING proof carries, and this is also the run of \ verifier.rs:1238's presence check" @@ -1359,7 +1359,7 @@ fn a_zero_row_fixed_table_carries_some_zero_not_none() { let mut tampered = proof.clone(); tampered.proofs[i].bus_public_inputs = None; assert!( - !Verifier::multi_verify_views( + !crate::hash_pin::BlockVerifier::multi_verify_views( &refs, MultiProofView::Owned(&tampered), &mut seed(), diff --git a/prover/src/tests/bitwise_bus_tests.rs b/prover/src/tests/bitwise_bus_tests.rs index 1782bd0fc..2958853ad 100644 --- a/prover/src/tests/bitwise_bus_tests.rs +++ b/prover/src/tests/bitwise_bus_tests.rs @@ -17,7 +17,7 @@ use stark::lookup::{ use stark::proof::options::ProofOptions; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; use crate::test_utils::multi_prove_ram; @@ -195,15 +195,15 @@ fn prove_and_verify(sender_lookups: &[(u8, u8, u8)]) -> bool { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -305,15 +305,15 @@ fn prove_and_verify_custom( ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } diff --git a/prover/src/tests/bitwise_tests.rs b/prover/src/tests/bitwise_tests.rs index c824764d3..af9c4011f 100644 --- a/prover/src/tests/bitwise_tests.rs +++ b/prover/src/tests/bitwise_tests.rs @@ -424,7 +424,7 @@ mod soundness_tests { use stark::prover::{IsStarkProver, Prover}; use stark::trace::TraceTable; use stark::traits::AIR; - use stark::verifier::{IsStarkVerifier, Verifier}; + use stark::verifier::IsStarkVerifier; use crate::tables::types::{GoldilocksExtension, GoldilocksField}; @@ -626,15 +626,15 @@ mod soundness_tests { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - let result = Verifier::multi_verify( + let result = crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ); @@ -674,15 +674,15 @@ mod soundness_tests { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - let result = Verifier::multi_verify( + let result = crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ); @@ -744,16 +744,16 @@ mod soundness_tests { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); // Verifier uses DIFFERENT AIR with honest commitment let verifier_airs: Vec<&dyn AIR> = vec![&sender_air, &verifier_receiver_air]; - let result = Verifier::multi_verify( + let result = crate::hash_pin::BlockVerifier::multi_verify( &verifier_airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ); diff --git a/prover/src/tests/branch_bus_tests.rs b/prover/src/tests/branch_bus_tests.rs index ee81ebb5a..82a66ee32 100644 --- a/prover/src/tests/branch_bus_tests.rs +++ b/prover/src/tests/branch_bus_tests.rs @@ -19,7 +19,7 @@ use stark::lookup::{ use stark::proof::options::ProofOptions; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::branch::{BranchOperation, cols, generate_branch_trace}; use crate::tables::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; @@ -336,15 +336,15 @@ fn prove_and_verify(ops: &[BranchOperation]) -> bool { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -426,15 +426,15 @@ fn prove_and_verify_custom(ops: &[BranchOperation], receiver_rows: &[CustomBranc ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } diff --git a/prover/src/tests/hash_pin_enumeration.rs b/prover/src/tests/hash_pin_enumeration.rs index 3f358d5c2..2cae726e6 100644 --- a/prover/src/tests/hash_pin_enumeration.rs +++ b/prover/src/tests/hash_pin_enumeration.rs @@ -24,6 +24,24 @@ use std::collections::BTreeSet; use std::path::Path; +/// ★★ Calls that reach a prover or verifier through the WORKSPACE ALIAS. +/// +/// ⛔ `stark::prover::Prover` and `stark::verifier::Verifier` **are** +/// `GenericProver` / `GenericVerifier` AT `DefaultStarkHash`. They are the +/// SILENT spelling of the first symbol below, and this gate's first version +/// omitted them — so it searched for the class by the one name the class never +/// uses. Nineteen files kept calling the BLAKE3 alias against artifacts that +/// follow the pin, on branches whose entire purpose is that the two differ, and +/// the gate reported green. +/// +/// Matched as call forms rather than as bare identifiers, because `Prover` and +/// `Verifier` appear inside `IsStarkProver`, `BlockProver` and ordinary prose. +/// A line naming the pin is excluded by [`PIN_CALLS`] rather than by the pattern. +const ALIAS_CALLS: &[&str] = &["Prover::multi_prove", "Verifier::multi_verify"]; + +/// The pinned spellings, which contain [`ALIAS_CALLS`] as substrings. +const PIN_CALLS: &[&str] = &["BlockProver::", "BlockVerifier::"]; + /// The symbols that silently select a hash when nobody names one. const IMPLIED_HASH_SYMBOLS: &[&str] = &[ // The workspace's commitment configuration, and the `Prover` / `Verifier` @@ -39,6 +57,25 @@ const IMPLIED_HASH_SYMBOLS: &[&str] = &[ /// Files allowed to mention an implied-hash symbol, each with its reason. /// +/// ⚠⚠ **THE QUESTION THIS LIST ANSWERS IS "IS THIS DEFAULT PAIRED WITH A +/// NON-DEFAULT?", NOT "IS THIS REACHABLE FROM PRODUCTION?"** The first version +/// asked the second, and every entry's reasoning was *true* and one scope too +/// wide. `build_traces` really is test-only and production really does reach +/// `build_traces_with_hasher` — and twelve tests still built traces at +/// `HasherKind::Test` while proving against artifacts that followed the pin, +/// which is an out-of-bounds index inside `HashConstraints::eval` because the +/// socket chip's width is tenant-dependent. +/// +/// ★ **"Test-only" is not "safe" — it is only "production-safe."** Before +/// blessing an entry, name the CONSUMER the default is handed to and check what +/// tenant *it* follows. +/// +/// ⚖ And note the subtlest part: that mismatch did not pre-exist. Before +/// `build_artifacts` was pinned, `artifacts.hasher` was ALSO `Test`, so the pair +/// agreed **by both being wrong**. A correct fix to one half of a +/// wrong-but-consistent pair CREATES the failure — so a red test after such a +/// fix is evidence the fix worked, not that it broke something. +/// /// Paths are relative to `prover/src`. Two files are excluded from the scan /// rather than blessed: `hash_pin.rs`, because naming the default is what it is /// FOR, and this file, because it has to spell the symbols it searches for. @@ -166,7 +203,10 @@ fn no_call_site_outside_the_pin_reaches_a_default_alias() { let text = std::fs::read_to_string(root.join(rel)).expect("a readable source file"); for line in text.lines() { let Some(code) = code_of(line) else { continue }; - if IMPLIED_HASH_SYMBOLS.iter().any(|s| code.contains(s)) { + let implied = IMPLIED_HASH_SYMBOLS.iter().any(|s| code.contains(s)); + let aliased = ALIAS_CALLS.iter().any(|s| code.contains(s)) + && !PIN_CALLS.iter().any(|s| code.contains(s)); + if implied || aliased { found.insert(rel.to_string_lossy().replace('\\', "/")); } } diff --git a/prover/src/tests/local_to_global_bus_tests.rs b/prover/src/tests/local_to_global_bus_tests.rs index 8025596d6..1181abdc7 100644 --- a/prover/src/tests/local_to_global_bus_tests.rs +++ b/prover/src/tests/local_to_global_bus_tests.rs @@ -21,7 +21,7 @@ use stark::proof::stark::MultiProof; use stark::proof::view::MultiProofView; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::bitwise::{BitwiseOperation, BitwiseOperationType}; use crate::tables::local_to_global::{ @@ -340,12 +340,12 @@ fn prove_verify_memory(l2g_boundary: &[CellBoundary], memw_boundary: &[CellBound _, _, )> = vec![(&l2g, &mut l2g_trace, &()), (&memw, &mut memw_trace, &())]; - let proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + let proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&l2g, &memw]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -367,13 +367,13 @@ fn prove_verify_l2g_range_with_trace( (&l2g, l2g_trace, &()), (&receiver, &mut receiver_trace, &()), ]; - let proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + let proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&l2g, &receiver]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -407,7 +407,7 @@ fn l2g_root(boundary: &[CellBoundary]) -> Commitment { _, _, )> = vec![(&air, &mut trace, &())]; - let proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + let proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); proof.proofs[0].lde_trace_main_merkle_root } @@ -468,7 +468,7 @@ pub(crate) fn prove_global(boundaries: &[Vec]) -> MultiProof::new(&[])).unwrap() + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap() } pub(crate) fn prove_and_verify(boundaries: &[Vec]) -> bool { @@ -489,10 +489,10 @@ pub(crate) fn prove_and_verify(boundaries: &[Vec]) -> bool { airs.push(&genesis_anchor); airs.push(&program_end_anchor); - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -593,12 +593,12 @@ fn prove_verify_memory_with_trace( _, _, )> = vec![(&l2g, l2g_trace, &()), (&memw, &mut memw_trace, &())]; - let proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + let proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&l2g, &memw]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -656,7 +656,7 @@ fn prove_and_verify_global_with_traces( air_trace_pairs.push((&genesis_anchor, &mut genesis_trace, &())); air_trace_pairs.push((&program_end_anchor, &mut program_end_trace, &())); - let proof = multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + let proof = multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let mut airs: Vec<&dyn AIR> = l2g_airs .iter() @@ -665,10 +665,10 @@ fn prove_and_verify_global_with_traces( airs.push(&genesis_anchor); airs.push(&program_end_anchor); - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } diff --git a/prover/src/tests/lt_bus_tests.rs b/prover/src/tests/lt_bus_tests.rs index e95a81285..7f3c94a1c 100644 --- a/prover/src/tests/lt_bus_tests.rs +++ b/prover/src/tests/lt_bus_tests.rs @@ -19,7 +19,7 @@ use stark::lookup::{ use stark::proof::options::ProofOptions; use stark::trace::TraceTable; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::tables::lt::{LtOperation, cols, generate_lt_trace}; use crate::tables::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; @@ -289,15 +289,15 @@ fn prove_and_verify(ops: &[LtOperation]) -> bool { ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } @@ -373,15 +373,15 @@ fn prove_and_verify_custom(ops: &[LtOperation], receiver_rows: &[CustomLtRow]) - ]; let multi_proof = - multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let airs: Vec<&dyn AIR> = vec![&sender_air, &receiver_air]; - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ) } diff --git a/prover/src/tests/page_offset_forgery_poc.rs b/prover/src/tests/page_offset_forgery_poc.rs index fe676df63..cfcecbd27 100644 --- a/prover/src/tests/page_offset_forgery_poc.rs +++ b/prover/src/tests/page_offset_forgery_poc.rs @@ -39,7 +39,7 @@ // `config.rs` warns about; the warning applies to test harnesses too. use stark::config::DefaultStarkTranscript as DefaultTranscript; use stark::proof::options::ProofOptions; -use stark::prover::{IsStarkProver, Prover}; +use stark::prover::IsStarkProver; use crate::statement::{StatementKind, absorb_statement}; use crate::tables::bitwise::{cols as bw_cols, row_index as bw_row_index}; @@ -196,7 +196,7 @@ fn craft_proof( .filter(|c| c.is_private_input) .count(); - let mut transcript = DefaultTranscript::::new(&[]); + let mut transcript = crate::hash_pin::block_transcript(&[]); absorb_statement( &mut transcript, StatementKind::Monolithic, @@ -208,7 +208,7 @@ fn craft_proof( options.fri_final_poly_log_degree, ); - let proof = Prover::multi_prove( + let proof = crate::hash_pin::BlockProver::multi_prove( airs.air_trace_pairs(&mut traces), &mut transcript, #[cfg(feature = "disk-spill")] @@ -701,7 +701,7 @@ fn craft_proof_with_duplicate_page( None, ); - let mut transcript = DefaultTranscript::::new(&[]); + let mut transcript = crate::hash_pin::block_transcript(&[]); absorb_statement( &mut transcript, StatementKind::Monolithic, @@ -713,7 +713,7 @@ fn craft_proof_with_duplicate_page( options.fri_final_poly_log_degree, ); - let proof = Prover::multi_prove( + let proof = crate::hash_pin::BlockProver::multi_prove( airs.air_trace_pairs(&mut traces), &mut transcript, #[cfg(feature = "disk-spill")] diff --git a/prover/src/tests/prove_elfs_tests.rs b/prover/src/tests/prove_elfs_tests.rs index a7ab54ff0..cc414a0b9 100644 --- a/prover/src/tests/prove_elfs_tests.rs +++ b/prover/src/tests/prove_elfs_tests.rs @@ -35,7 +35,7 @@ use stark::lookup::{AirWithBuses, AuxiliaryTraceBuildData}; use stark::proof::options::ProofOptions; use stark::proof::view::{MultiProofView, StarkProofView}; use stark::traits::AIR; -use stark::verifier::{IsStarkVerifier, Verifier}; +use stark::verifier::IsStarkVerifier; use crate::VmProof; use crate::tables::MaxRowsConfig; @@ -84,7 +84,7 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool { // Build air_trace_pairs for all tables let air_trace_pairs = airs.air_trace_pairs(traces); - let multi_proof = match multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])) + let multi_proof = match multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])) { Ok(proof) => proof, Err(_) => return false, @@ -107,10 +107,10 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool { .expect("fingerprint collision in test"); // Verify using centralized air_refs() which includes all tables - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &airs.air_refs(), &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ) } @@ -143,7 +143,7 @@ fn prove_vm_minimal(elf_bytes: &[u8], private_inputs: &[u8], max_rows: &MaxRowsC let runtime_page_ranges = traces.runtime_page_ranges(); let proof = multi_prove_ram( airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("prove"); let num_private_input_pages = traces @@ -201,10 +201,10 @@ fn verify_vm_minimal(vm_proof: &VmProof, elf_bytes: &[u8]) -> bool { &mut replay_transcript, ) .expect("fingerprint collision in test"); - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &air_refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ) } @@ -255,15 +255,15 @@ fn test_cpu_only_no_bus() { _, )> = vec![(&cpu_air, &mut cpu_trace, &())]; - let multi_proof = multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])) + let multi_proof = multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])) .expect("Prover failed"); let airs: Vec<&dyn AIR> = vec![&cpu_air]; assert!( - Verifier::multi_verify( + crate::hash_pin::BlockVerifier::multi_verify( &airs, &multi_proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ), "CPU-only verification failed" @@ -1919,7 +1919,7 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() { ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("Prover failed"); @@ -1951,10 +1951,10 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() { ) .expect("fingerprint collision in test"); - let verified = Verifier::multi_verify_views( + let verified = crate::hash_pin::BlockVerifier::multi_verify_views( &verifier_air_refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ); assert!( @@ -2678,7 +2678,7 @@ fn test_deep_stack_runtime_pages_roundtrip() { ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("Prover failed"); // Verifier reconstructs from ELF + runtime_page_ranges hint @@ -2710,10 +2710,10 @@ fn test_deep_stack_runtime_pages_roundtrip() { ) .expect("fingerprint collision in test"); - let verified = Verifier::multi_verify_views( + let verified = crate::hash_pin::BlockVerifier::multi_verify_views( &verifier_air_refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ); assert!( @@ -2755,7 +2755,7 @@ fn test_deep_stack_missing_pages_rejected() { ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("Prover failed"); // Verifier uses EMPTY runtime_page_ranges → missing stack/heap pages @@ -2786,10 +2786,10 @@ fn test_deep_stack_missing_pages_rejected() { ) .expect("fingerprint collision in test"); - let verified = Verifier::multi_verify_views( + let verified = crate::hash_pin::BlockVerifier::multi_verify_views( &verifier_air_refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ); assert!( @@ -2866,7 +2866,7 @@ fn test_heap_alloc_runtime_pages_roundtrip() { ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("Prover failed"); // Verifier reconstructs from ELF + runtime hint (ranges decoded to pages) @@ -2898,10 +2898,10 @@ fn test_heap_alloc_runtime_pages_roundtrip() { ) .expect("fingerprint collision in test"); - let verified = Verifier::multi_verify_views( + let verified = crate::hash_pin::BlockVerifier::multi_verify_views( &verifier_air_refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ); assert!( @@ -3079,15 +3079,15 @@ fn test_crafted_zero_count_proof_must_not_verify() { (airs.decode.as_ref(), &mut decode_trace, &()), ]; - let proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])) + let proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])) .expect("Proof generation should succeed"); assert_eq!(proof.proofs.len(), 2); - let verified = Verifier::multi_verify( + let verified = crate::hash_pin::BlockVerifier::multi_verify( &verifier_air_refs, &proof, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &FieldElement::zero(), ); @@ -3546,7 +3546,7 @@ fn test_prove_first_epoch_without_halt() { let multi_proof = multi_prove_ram( airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("first epoch failed to prove"); @@ -3566,10 +3566,10 @@ fn test_prove_first_epoch_without_halt() { .expect("fingerprint collision in test"); assert!( - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &airs.air_refs(), &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ), "first epoch (HALT excluded) failed to verify" @@ -3635,7 +3635,7 @@ fn test_prove_second_epoch_from_snapshot() { let multi_proof = multi_prove_ram( airs.air_trace_pairs(&mut traces), - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), ) .expect("second epoch failed to prove"); @@ -3655,10 +3655,10 @@ fn test_prove_second_epoch_from_snapshot() { .expect("fingerprint collision in test"); assert!( - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &airs.air_refs(), &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ), "second epoch (register init from snapshot) failed to verify" @@ -3749,7 +3749,7 @@ fn test_epoch_proof_commits_l2g() { let mut pairs = airs.air_trace_pairs(&mut traces); pairs.push((&inert_l2g_air, &mut l2g_trace, &())); - let multi_proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])) + let multi_proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])) .expect("epoch proof with inert L2G failed to prove"); let mut refs = airs.air_refs(); @@ -3771,10 +3771,10 @@ fn test_epoch_proof_commits_l2g() { .expect("fingerprint collision in test"); assert!( - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ), "epoch proof with inert L2G failed to verify" @@ -3911,7 +3911,7 @@ fn test_continuation_pipeline_end_to_end() { let mut pairs = airs.air_trace_pairs(&mut traces); pairs.push((&inert_l2g_air, &mut l2g_trace, &())); - let multi_proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])) + let multi_proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])) .expect("epoch proof failed to prove"); let mut refs = airs.air_refs(); @@ -3931,10 +3931,10 @@ fn test_continuation_pipeline_end_to_end() { ) .expect("fingerprint collision in test"); assert!( - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ), "epoch {i} failed to verify" @@ -4054,7 +4054,7 @@ fn test_epoch_memory_bus_with_l2g_bookend() { let mut pairs = airs.air_trace_pairs(&mut traces); pairs.push((&l2g_air, &mut l2g_trace, &())); - let multi_proof = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])) + let multi_proof = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])) .expect("epoch with L2G memory bookend failed to prove"); let mut refs = airs.air_refs(); @@ -4075,10 +4075,10 @@ fn test_epoch_memory_bus_with_l2g_bookend() { .expect("fingerprint collision in test"); assert!( - Verifier::multi_verify_views( + crate::hash_pin::BlockVerifier::multi_verify_views( &refs, &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected_bus_balance, ), "epoch Memory bus must balance with L2G bookend + PAGE excluding touched cells" @@ -4290,7 +4290,7 @@ fn a_blake3_workload_claiming_no_blake3_table_is_rejected() { assert!(!airs.include_blake3, "the forged shape must omit the table"); let pairs = airs.air_trace_pairs(&mut traces); - let proved = multi_prove_ram(pairs, &mut DefaultTranscript::::new(&[])); + let proved = multi_prove_ram(pairs, &mut crate::hash_pin::block_transcript(&[])); let verified = match &proved { Err(_) => false, @@ -4300,7 +4300,7 @@ fn a_blake3_workload_claiming_no_blake3_table_is_rejected() { .iter() .map(StarkProofView::Owned) .collect(); - let mut replay = DefaultTranscript::::new(&[]); + let mut replay = crate::hash_pin::block_transcript(&[]); match crate::compute_expected_commit_bus_balance_view( &airs.air_refs(), &views, @@ -4309,10 +4309,10 @@ fn a_blake3_workload_claiming_no_blake3_table_is_rejected() { &mut replay, ) { None => false, - Some(expected) => Verifier::multi_verify_views( + Some(expected) => crate::hash_pin::BlockVerifier::multi_verify_views( &airs.air_refs(), &views, - &mut DefaultTranscript::::new(&[]), + &mut crate::hash_pin::block_transcript(&[]), &expected, ), } @@ -4368,7 +4368,7 @@ fn the_blake3_count_is_bound_into_the_statement() { }; let challenge_for = |counts: &crate::TableCounts| { - let mut t = DefaultTranscript::::new(&[]); + let mut t = crate::hash_pin::block_transcript(&[]); absorb_statement( &mut t, StatementKind::Monolithic, diff --git a/prover/src/tests/recursion_soundness_gap_poc.rs b/prover/src/tests/recursion_soundness_gap_poc.rs index 5c6dc3395..db6d3d99b 100644 --- a/prover/src/tests/recursion_soundness_gap_poc.rs +++ b/prover/src/tests/recursion_soundness_gap_poc.rs @@ -22,7 +22,7 @@ use std::path::PathBuf; // honest proof it builds is rejected at challenge derivation. Same half-flip // `config.rs` warns about; the warning applies to test harnesses too. use stark::config::DefaultStarkTranscript as DefaultTranscript; -use stark::prover::{IsStarkProver, Prover}; +use stark::prover::IsStarkProver; use crate::recursion::{MIN_PROOF_OPTIONS, precomputed_commitments}; use crate::statement::{StatementKind, absorb_statement, elf_digest}; @@ -172,7 +172,7 @@ fn custom_prove_with_statement_elf( .filter(|c| c.is_private_input) .count(); - let mut transcript = DefaultTranscript::::new(&[]); + let mut transcript = crate::hash_pin::block_transcript(&[]); absorb_statement( &mut transcript, StatementKind::Monolithic, @@ -184,7 +184,7 @@ fn custom_prove_with_statement_elf( opts.fri_final_poly_log_degree, ); - let proof = Prover::multi_prove( + let proof = crate::hash_pin::BlockProver::multi_prove( airs.air_trace_pairs(&mut traces), &mut transcript, #[cfg(feature = "disk-spill")] From 8c8a48763f487732752f44c06c5e0738f8953b5e Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:31:31 -0300 Subject: [PATCH 06/23] style(tests): drop the transcript imports the alias sweep orphaned Eleven files imported DefaultTranscript only to construct one; block_transcript replaced every construction, so the imports are dead and -D warnings would refuse the build. Two test_utils::E imports go with them for the same reason. (cherry picked from commit 048d744cfe0694a22da6158d3f867a57591404d1) --- prover/src/lfm/blake3_chip_tests.rs | 6 ++-- prover/src/lfm/blake3_probe.rs | 9 ++++-- prover/src/lfm/framework_probe.rs | 23 ++++++++++---- prover/src/lfm/fri_tests.rs | 22 +++++++++----- prover/src/lfm/join_tests.rs | 30 ++++++++++++------- prover/src/lfm/keccak_probe.rs | 9 ++++-- prover/src/tests/bitwise_bus_tests.rs | 4 +-- prover/src/tests/bitwise_tests.rs | 1 - prover/src/tests/branch_bus_tests.rs | 4 +-- prover/src/tests/local_to_global_bus_tests.rs | 7 ++--- prover/src/tests/lt_bus_tests.rs | 4 +-- prover/src/tests/page_offset_forgery_poc.rs | 3 +- prover/src/tests/prove_elfs_tests.rs | 11 ++++--- .../src/tests/recursion_soundness_gap_poc.rs | 2 -- 14 files changed, 81 insertions(+), 54 deletions(-) diff --git a/prover/src/lfm/blake3_chip_tests.rs b/prover/src/lfm/blake3_chip_tests.rs index 4bf99eef3..63aeee27a 100644 --- a/prover/src/lfm/blake3_chip_tests.rs +++ b/prover/src/lfm/blake3_chip_tests.rs @@ -502,7 +502,8 @@ fn tampering_with_the_blake3_witness_is_not_accepted() { let program = blake3_sponge_program(65); let artifacts = build_artifacts(&program, &opts); let exec = execute(&program, &sponge_arenas(&msg), &TestPermutation).expect("execute"); - let mut traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); + let mut traces = + super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); // One output byte of the first compression. let col = cols::out_word(0, 0); @@ -1780,7 +1781,8 @@ fn a_tampered_non_first_blake3_chunk_rejects() { let artifacts = build_artifacts(&program, &opts); let exec = execute(&program, &sponge_arenas(&msg), &TestPermutation).expect("execute"); - let mut traces = super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); + let mut traces = + super::trace::build_traces_with_hasher(&program, &exec.records, artifacts.hasher); assert_eq!(traces.blake3.len(), 3); // One output byte of the LAST chunk's first compression — the eleventh of // the twelve, which no other chunk carries. diff --git a/prover/src/lfm/blake3_probe.rs b/prover/src/lfm/blake3_probe.rs index ad5ff8894..6e786f3ad 100644 --- a/prover/src/lfm/blake3_probe.rs +++ b/prover/src/lfm/blake3_probe.rs @@ -26,8 +26,6 @@ //! The permutation count comes from wave 8's rate-parameterised closed form //! and is inherited, not re-established here. //! - **Anything cryptographic about the 6-round variant** (assumption A6R). - -use crypto::fiat_shamir::default_transcript::DefaultTranscript; use crypto::fiat_shamir::is_transcript::IsTranscript; use stark::config::Commitment; use stark::constraints::builder::{ @@ -289,7 +287,12 @@ fn verify_proof( ); let refs: Vec = vec![chip, &mirror, &bw_air]; let mut vt = transcript(); - crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) + crate::hash_pin::BlockVerifier::multi_verify_views( + &refs, + MultiProofView::Owned(proof), + &mut vt, + &FEE::zero(), + ) } /// Prove + verify, optionally corrupting the chip trace in between. diff --git a/prover/src/lfm/framework_probe.rs b/prover/src/lfm/framework_probe.rs index 37bd79dc6..f866d58e7 100644 --- a/prover/src/lfm/framework_probe.rs +++ b/prover/src/lfm/framework_probe.rs @@ -9,8 +9,6 @@ //! a flipped preprocessed root is rejected by the prover (recommit mismatch) //! and by the verifier (root equality), and a tampered witness value breaks //! the bus balance. - -use crypto::fiat_shamir::default_transcript::DefaultTranscript; use crypto::fiat_shamir::is_transcript::IsTranscript; use stark::config::Commitment; use stark::constraints::builder::EmptyConstraints; @@ -148,7 +146,12 @@ fn b0_preprocessed_multiplicity_round_trips() { let refs: Vec = vec![&sender, &receiver]; let mut vt = transcript(); assert!( - crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + crate::hash_pin::BlockVerifier::multi_verify_views( + &refs, + MultiProofView::Owned(&proof), + &mut vt, + &FEE::zero(), + ), "honest proof must verify" ); } @@ -180,7 +183,12 @@ fn b0_verifier_rejects_wrong_preprocessed_root() { let refs: Vec = vec![&bad_sender, &receiver]; let mut vt = transcript(); assert!( - !crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + !crate::hash_pin::BlockVerifier::multi_verify_views( + &refs, + MultiProofView::Owned(&proof), + &mut vt, + &FEE::zero(), + ), "a supplied root differing from the proof's must reject" ); } @@ -212,7 +220,12 @@ fn b0_tampered_witness_value_breaks_balance() { let refs: Vec = vec![&sender, &receiver]; let mut vt = transcript(); assert!( - !crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(&proof), &mut vt, &FEE::zero(),), + !crate::hash_pin::BlockVerifier::multi_verify_views( + &refs, + MultiProofView::Owned(&proof), + &mut vt, + &FEE::zero(), + ), "unbalanced bus must reject" ); } diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index 81c7e01c5..4df63d079 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -298,7 +298,8 @@ fn the_fri_leaf_is_byte_identical_to_productions_own_backends() { let mut digests = Vec::new(); for (i, (a, c)) in vectors.iter().enumerate() { let arenas = vec![vec![ext_word(a), ext_word(c)]]; - let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).expect("the leaf hash executes"); + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) + .expect("the leaf hash executes"); let got = [exec.public_words[0].1, exec.public_words[1].1]; let batched = @@ -621,7 +622,12 @@ fn the_fri_emitter_verifies_every_query_of_a_real_folding_proof() { let h = host_fri(rows, 2); let all: Vec = (0..h.trace.iotas.len()).collect(); let program = fri_only_program(h.shape, all.len()); - let exec = execute(&program, &h.all_arenas(&all), &crate::hash_pin::BLOCK_HASHER).expect( + let exec = execute( + &program, + &h.all_arenas(&all), + &crate::hash_pin::BLOCK_HASHER, + ) + .expect( "an honest FRI decommitment must authenticate every layer and reach \ the terminal polynomial", ); @@ -1030,7 +1036,8 @@ fn no_tampered_fri_value_can_pass() { }; let program = fri_only_program(shape, queries.len()); let honest = h.all_arenas(&queries); - execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER).expect("the honest run must execute"); + execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER) + .expect("the honest run must execute"); let stride = h.shape.query_words(); // (label, arena, word) — arena order is the driver's: deep, roots, zetas, @@ -1056,9 +1063,9 @@ fn no_tampered_fri_value_can_pass() { for (label, arena, word) in bump { let mut tampered = honest.clone(); tampered[arena][word][0] += FE::one(); - let err = execute(&program, &tampered, &crate::hash_pin::BLOCK_HASHER).expect_err(&format!( - "moving the {label} must make the program unexecutable" - )); + let err = execute(&program, &tampered, &crate::hash_pin::BLOCK_HASHER).expect_err( + &format!("moving the {label} must make the program unexecutable"), + ); println!(" {label:<40} rejected: {err:?}"); } @@ -1115,7 +1122,8 @@ fn the_shape_pins_the_lengths_production_must_check_at_runtime() { }; let program = fri_only_program(shape, 1); let honest = h.all_arenas(&queries); - execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER).expect("the honest run must execute"); + execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER) + .expect("the honest run must execute"); // (label, arena, what the truncation would buy a prover) let attacks: [(&str, usize, &str); 3] = [ diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index bb656ae29..c8a1e05e9 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -397,7 +397,8 @@ fn the_join_premises_hold_on_a_real_proof() { for (q, iota) in h.iotas.iter().enumerate() { let arenas = vec![vec![base_word(FE::from(*iota as u64))]]; - let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).expect("the derivation executes"); + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) + .expect("the derivation executes"); assert_eq!( exec.public_words[0].1[0], h.points[q].0, "query {q}: the machine's point must be \ @@ -1087,7 +1088,8 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { b.public(s.as_cell()); } let program = compile(b.finish()); - let honest = execute(&program, &h.arenas(&[q]), &crate::hash_pin::BLOCK_HASHER).expect("honest"); + let honest = + execute(&program, &h.arenas(&[q]), &crate::hash_pin::BLOCK_HASHER).expect("honest"); // Sweep every value slot of every group, so no vector class (first group, // first column, regular point) is silently the only one tested. @@ -1124,9 +1126,12 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { let mut coherent_roots = h.roots.clone(); coherent_roots[g] = forged; arenas[3] = commitments_to_arena(&coherent_roots); - let forged_run = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).unwrap_or_else(|e| { - panic!("{label}: group {g} slot {slot}: the coherent forgery must execute: {e:?}") - }); + let forged_run = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) + .unwrap_or_else(|e| { + panic!( + "{label}: group {g} slot {slot}: the coherent forgery must execute: {e:?}" + ) + }); // Which of the two points moves is not incidental: a leaf holds // the row PAIR, its first half is the regular point and its second // the symmetric, and folding the halves into the wrong point is a @@ -1198,9 +1203,10 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { }); arenas[3] = commitments_to_arena(&coherent_roots); - let forged = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).unwrap_or_else(|e| { - panic!("{label}: index bit {level}: coherent forgery must execute: {e:?}") - }); + let forged = + execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).unwrap_or_else(|e| { + panic!("{label}: index bit {level}: coherent forgery must execute: {e:?}") + }); assert_ne!( forged.public_words[0].1, honest.public_words[0].1, "{label}: index bit {level}: the index derives the evaluation point, so a \ @@ -1532,8 +1538,12 @@ fn the_precomputed_group_comes_first_and_that_is_checkable() { } let program = compile(b.finish()); validate(&program).expect("admissible"); - let exec = execute(&program, &h.arenas(&queries), &crate::hash_pin::BLOCK_HASHER) - .expect("the four-group sub-proof must authenticate and fold"); + let exec = execute( + &program, + &h.arenas(&queries), + &crate::hash_pin::BLOCK_HASHER, + ) + .expect("the four-group sub-proof must authenticate and fold"); for (k, q) in queries.iter().enumerate() { assert_eq!( word_as_ext(&exec.public_words[2 * k].1).expect("ext"), diff --git a/prover/src/lfm/keccak_probe.rs b/prover/src/lfm/keccak_probe.rs index abac4a433..24b511eef 100644 --- a/prover/src/lfm/keccak_probe.rs +++ b/prover/src/lfm/keccak_probe.rs @@ -6,8 +6,6 @@ //! recursion machine's AIR set: it establishes that the family's only coupling //! to the VM is the core chip's two `Keccak` bus tokens, and that a chip owning //! nothing but those tokens is a sufficient driver. - -use crypto::fiat_shamir::default_transcript::DefaultTranscript; use crypto::fiat_shamir::is_transcript::IsTranscript; use stark::constraints::builder::EmptyConstraints; use stark::lookup::{AirWithBuses, AuxiliaryTraceBuildData, NullBoundaryConstraintBuilder}; @@ -140,7 +138,12 @@ fn verify_proof( ); let refs: Vec = vec![adapter, &rnd_air, &rc_air, &bw_air]; let mut vt = transcript(); - crate::hash_pin::BlockVerifier::multi_verify_views(&refs, MultiProofView::Owned(proof), &mut vt, &FEE::zero()) + crate::hash_pin::BlockVerifier::multi_verify_views( + &refs, + MultiProofView::Owned(proof), + &mut vt, + &FEE::zero(), + ) } /// Prove + verify, optionally corrupting the adapter trace in between. diff --git a/prover/src/tests/bitwise_bus_tests.rs b/prover/src/tests/bitwise_bus_tests.rs index 2958853ad..93f83e607 100644 --- a/prover/src/tests/bitwise_bus_tests.rs +++ b/prover/src/tests/bitwise_bus_tests.rs @@ -4,12 +4,10 @@ //! - Completeness: Valid lookups to BITWISE are accepted //! - Soundness: Invalid lookups to BITWISE are rejected +use math::field::element::FieldElement; use stark::constraints::builder::EmptyConstraints; use std::collections::HashMap; -use crypto::fiat_shamir::default_transcript::DefaultTranscript; -use math::field::element::FieldElement; - use stark::lookup::{ AirWithBuses, AuxiliaryTraceBuildData, BusInteraction, BusValue, Multiplicity, NullBoundaryConstraintBuilder, Packing, diff --git a/prover/src/tests/bitwise_tests.rs b/prover/src/tests/bitwise_tests.rs index af9c4011f..738af5cb4 100644 --- a/prover/src/tests/bitwise_tests.rs +++ b/prover/src/tests/bitwise_tests.rs @@ -415,7 +415,6 @@ fn test_preprocessed_commitment_is_nonzero() { #[cfg(test)] mod soundness_tests { use super::*; - use crypto::fiat_shamir::default_transcript::DefaultTranscript; use stark::lookup::{ AirWithBuses, AuxiliaryTraceBuildData, BusInteraction, BusValue, Multiplicity, NullBoundaryConstraintBuilder, Packing, diff --git a/prover/src/tests/branch_bus_tests.rs b/prover/src/tests/branch_bus_tests.rs index 82a66ee32..c5b3897ff 100644 --- a/prover/src/tests/branch_bus_tests.rs +++ b/prover/src/tests/branch_bus_tests.rs @@ -6,12 +6,10 @@ //! - Padding: Auto-padding to power of 2 works correctly //! - Border cases: Edge values (0, MAX, signed boundaries) work +use math::field::element::FieldElement; use stark::constraints::builder::EmptyConstraints; use std::collections::HashMap; -use crypto::fiat_shamir::default_transcript::DefaultTranscript; -use math::field::element::FieldElement; - use stark::lookup::{ AirWithBuses, AuxiliaryTraceBuildData, BusInteraction, BusValue, LinearTerm, Multiplicity, NullBoundaryConstraintBuilder, Packing, diff --git a/prover/src/tests/local_to_global_bus_tests.rs b/prover/src/tests/local_to_global_bus_tests.rs index 1181abdc7..7908ca2c2 100644 --- a/prover/src/tests/local_to_global_bus_tests.rs +++ b/prover/src/tests/local_to_global_bus_tests.rs @@ -5,12 +5,10 @@ //! program-end receiver (final value of each cell). The bus balances iff every //! epoch's `fini` matches the next epoch's `init` (the cross-epoch telescoping). +use math::field::element::FieldElement; use stark::constraints::builder::EmptyConstraints; use std::collections::HashMap; -use crypto::fiat_shamir::default_transcript::DefaultTranscript; -use math::field::element::FieldElement; - use stark::config::Commitment; use stark::lookup::{ AirWithBuses, AuxiliaryTraceBuildData, BusInteraction, BusValue, Multiplicity, @@ -656,7 +654,8 @@ fn prove_and_verify_global_with_traces( air_trace_pairs.push((&genesis_anchor, &mut genesis_trace, &())); air_trace_pairs.push((&program_end_anchor, &mut program_end_trace, &())); - let proof = multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); + let proof = + multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])).unwrap(); let mut airs: Vec<&dyn AIR> = l2g_airs .iter() diff --git a/prover/src/tests/lt_bus_tests.rs b/prover/src/tests/lt_bus_tests.rs index 7f3c94a1c..7f21cd2db 100644 --- a/prover/src/tests/lt_bus_tests.rs +++ b/prover/src/tests/lt_bus_tests.rs @@ -6,12 +6,10 @@ //! - Padding: Auto-padding to power of 2 works correctly //! - Border cases: Edge values (0, MAX, signed boundaries) work +use math::field::element::FieldElement; use stark::constraints::builder::EmptyConstraints; use std::collections::HashMap; -use crypto::fiat_shamir::default_transcript::DefaultTranscript; -use math::field::element::FieldElement; - use stark::lookup::{ AirWithBuses, AuxiliaryTraceBuildData, BusInteraction, BusValue, Multiplicity, NullBoundaryConstraintBuilder, Packing, diff --git a/prover/src/tests/page_offset_forgery_poc.rs b/prover/src/tests/page_offset_forgery_poc.rs index cfcecbd27..a42e09056 100644 --- a/prover/src/tests/page_offset_forgery_poc.rs +++ b/prover/src/tests/page_offset_forgery_poc.rs @@ -37,7 +37,6 @@ // Fiat-Shamir from the hash production replays (`DefaultStarkHash`), or every // honest proof it builds is rejected at challenge derivation. Same half-flip // `config.rs` warns about; the warning applies to test harnesses too. -use stark::config::DefaultStarkTranscript as DefaultTranscript; use stark::proof::options::ProofOptions; use stark::prover::IsStarkProver; @@ -46,7 +45,7 @@ use crate::tables::bitwise::{cols as bw_cols, row_index as bw_row_index}; use crate::tables::page::cols as page_cols; use crate::tables::trace_builder::Traces; use crate::tables::types::{FE, VmTable}; -use crate::test_utils::{E, asm_elf_bytes}; +use crate::test_utils::asm_elf_bytes; use crate::{MaxRowsConfig, VmAirs, VmProof}; use executor::elf::Elf; diff --git a/prover/src/tests/prove_elfs_tests.rs b/prover/src/tests/prove_elfs_tests.rs index cc414a0b9..e0102b728 100644 --- a/prover/src/tests/prove_elfs_tests.rs +++ b/prover/src/tests/prove_elfs_tests.rs @@ -27,7 +27,6 @@ // about exactly this half-flip ("the type system cannot force this; naming the // alias is what makes the production path follow DefaultStarkHash"); the warning // applies to the test harness too. -use stark::config::DefaultStarkTranscript as DefaultTranscript; use math::field::element::FieldElement; use stark::constraints::builder::EmptyConstraints; @@ -84,11 +83,11 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool { // Build air_trace_pairs for all tables let air_trace_pairs = airs.air_trace_pairs(traces); - let multi_proof = match multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])) - { - Ok(proof) => proof, - Err(_) => return false, - }; + let multi_proof = + match multi_prove_ram(air_trace_pairs, &mut crate::hash_pin::block_transcript(&[])) { + Ok(proof) => proof, + Err(_) => return false, + }; // Compute the verifier-side expected COMMIT bus balance from public output bytes let views: Vec> = multi_proof diff --git a/prover/src/tests/recursion_soundness_gap_poc.rs b/prover/src/tests/recursion_soundness_gap_poc.rs index db6d3d99b..05260a1f2 100644 --- a/prover/src/tests/recursion_soundness_gap_poc.rs +++ b/prover/src/tests/recursion_soundness_gap_poc.rs @@ -21,13 +21,11 @@ use std::path::PathBuf; // Fiat-Shamir from the hash production replays (`DefaultStarkHash`), or every // honest proof it builds is rejected at challenge derivation. Same half-flip // `config.rs` warns about; the warning applies to test harnesses too. -use stark::config::DefaultStarkTranscript as DefaultTranscript; use stark::prover::IsStarkProver; use crate::recursion::{MIN_PROOF_OPTIONS, precomputed_commitments}; use crate::statement::{StatementKind, absorb_statement, elf_digest}; use crate::tables::trace_builder::Traces; -use crate::test_utils::E; use crate::{MaxRowsConfig, VmAirs, VmProof}; use executor::elf::Elf; From 0024ed1448a77c3a3d3a40c05531d4dbc4120da0 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 13:27:52 -0700 Subject: [PATCH 07/23] fix(lfm): the Merkle BACKEND aliases are the default hash too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An eighth variant, and the third time the gate's symbol list has been short. stark::config::BatchedMerkleTreeBackend is BatchBlake3Backend by definition, and FriLayerMerkleTreeBackend is PairBlake3Backend. They are type aliases rather than calls, so neither the DefaultStarkHash search nor the Prover/Verifier call-form search sees them — and a test comparing a machine leaf against one of them compares against BLAKE3 whatever the branch pins. Six sites: fri_tests' byte-identical leaf gate, which is precisely the test whose whole claim is that the machine's leaf IS the verifier's leaf, and four in join_tests' coherent-forgery construction. Both now go through proof_arena::BlockBatched and BlockPair, defined beside MainBackend so the pin's associated types have one spelling in the crate rather than one per caller. Two more defects surfaced while doing it, both in fri_tests' leaf gate: The leaf was published as two cells. An algebraic digest is one cell whose second slot repeats the first, so the comparison read one lane twice and would have passed on a duplicated value. It now publishes leaf.cells() and reads back whatever width that is. And join_tests reached hash_data_from_slices as an INHERENT method, which only the byte backend has. That is the same defect the original trial flip found in proof_arena::leaf_hash — the algebraic backend implements it through IsStreamingLeafBackend, so it has to be named through the trait. Four sites. Not verified: no local runs. The box is the oracle. (cherry picked from commit 73ccc6d7afd7185c40e9c4b2bc98953a9387c2c8) --- prover/src/lfm/fri_tests.rs | 17 ++++++++++------- prover/src/lfm/join_tests.rs | 17 +++++++++-------- prover/src/lfm/proof_arena.rs | 17 +++++++++++++++-- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index 4df63d079..33d7e0340 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -254,8 +254,8 @@ impl HostFri { /// 48, and all of them move at least one. #[test] fn the_fri_leaf_is_byte_identical_to_productions_own_backends() { + use super::proof_arena::{BlockBatched, BlockPair}; use crypto::merkle_tree::traits::IsMerkleTreeBackend; - use stark::config::{BatchedMerkleTreeBackend, FriLayerMerkleTreeBackend}; // Six distinct components, each with six distinct nonzero bytes in // descending positions, so no two of the 48 bytes agree and no component is @@ -290,8 +290,12 @@ fn the_fri_leaf_is_byte_identical_to_productions_own_backends() { let v0 = b.hint_word(arena, 0); let v1 = b.hint_word(arena, 1); let leaf = super::sub_proof::emit_leaf_hash(&mut b, FRI_LEAF_GROUP, &[v0, v1]); - b.public(leaf[0]); - b.public(leaf[1]); + // ⚠ The digest's OWN width. Two publishes assumed a byte digest; an + // algebraic one is a single cell whose second slot repeats the first, so + // the comparison below would have read one lane twice. + for cell in leaf.cells() { + b.public(*cell); + } let program = compile(b.finish()); validate(&program).expect("the leaf program is admissible"); @@ -300,11 +304,10 @@ fn the_fri_leaf_is_byte_identical_to_productions_own_backends() { let arenas = vec![vec![ext_word(a), ext_word(c)]]; let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .expect("the leaf hash executes"); - let got = [exec.public_words[0].1, exec.public_words[1].1]; + let got: Vec = exec.public_words.iter().map(|(_, w)| *w).collect(); - let batched = - as IsMerkleTreeBackend>::hash_data(&vec![*a, *c]); - let paired = as IsMerkleTreeBackend>::hash_data(&[*a, *c]); + let batched = as IsMerkleTreeBackend>::hash_data(&vec![*a, *c]); + let paired = as IsMerkleTreeBackend>::hash_data(&[*a, *c]); assert_eq!( batched, paired, "vector {i}: the spec's claim is that the prover's pair backend and \ diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index c8a1e05e9..582dadb0d 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -26,6 +26,7 @@ //! distinguish a per-level walk from a two-level one; it is not enough to catch //! something that only appears past a word boundary in the index. +use crypto::merkle_tree::traits::IsStreamingLeafBackend; use math::field::traits::IsFFTField; use stark::config::Commitment; use stark::domain::new_verifier_domain; @@ -1179,13 +1180,13 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { for (g, group) in groups.iter().enumerate() { let words = &h.openings[q][g].values; let leaf = if group.is_ext { - type ExtBackend = stark::config::BatchedMerkleTreeBackend; + type ExtBackend = super::proof_arena::BlockBatched; let v: Vec = words.iter().map(|w| FEE::new([w[0], w[1], w[2]])).collect(); - ExtBackend::hash_data_from_slices(&v, &[]) + >::hash_data_from_slices(&v, &[]) } else { - type BaseBackend = stark::config::BatchedMerkleTreeBackend; + type BaseBackend = super::proof_arena::BlockBatched; let v: Vec = words.iter().map(|w| w[0]).collect(); - BaseBackend::hash_data_from_slices(&v, &[]) + >::hash_data_from_slices(&v, &[]) }; coherent_roots[g] = walk_to_root(leaf, bad, &h.openings[q][g].siblings); moved_a_root |= coherent_roots[g] != h.roots[g]; @@ -1240,18 +1241,18 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { /// The leaf hash a tampered opening really produces, under production's own /// backend rather than a local model. fn tampered_leaf(h: &HostSubProof, q: usize, g: usize, slot: usize) -> Commitment { - type BaseBackend = stark::config::BatchedMerkleTreeBackend; - type ExtBackend = stark::config::BatchedMerkleTreeBackend; + type BaseBackend = super::proof_arena::BlockBatched; + type ExtBackend = super::proof_arena::BlockBatched; let group = h.shape.groups()[g]; let words = &h.openings[q][g].values; if group.is_ext { let mut v: Vec = words.iter().map(|w| FEE::new([w[0], w[1], w[2]])).collect(); v[slot] = &v[slot] + FEE::new([FE::one(), FE::zero(), FE::zero()]); - ExtBackend::hash_data_from_slices(&v, &[]) + >::hash_data_from_slices(&v, &[]) } else { let mut v: Vec = words.iter().map(|w| w[0]).collect(); v[slot] += FE::one(); - BaseBackend::hash_data_from_slices(&v, &[]) + >::hash_data_from_slices(&v, &[]) } } } diff --git a/prover/src/lfm/proof_arena.rs b/prover/src/lfm/proof_arena.rs index 07dd2b096..07789b79d 100644 --- a/prover/src/lfm/proof_arena.rs +++ b/prover/src/lfm/proof_arena.rs @@ -28,11 +28,24 @@ use super::word::{LfmWord, base_word}; type FE = FieldElement; +/// The BATCHED Merkle backend the block path commits under, over any field. +/// +/// ⛔ Use this rather than `stark::config::BatchedMerkleTreeBackend`, which is +/// `BatchBlake3Backend` by definition — a workspace-default ALIAS, and therefore +/// the same silent spelling of the default that `Prover` and `Verifier` are. A +/// test comparing a machine leaf against that alias compares against BLAKE3 +/// whatever the branch pins. +pub type BlockBatched = + ::Batched; + +/// The PAIR backend FRI layers commit under. See [`BlockBatched`]; the alias it +/// replaces is `stark::config::FriLayerMerkleTreeBackend` = `PairBlake3Backend`. +pub type BlockPair = ::Pair; + /// The Merkle backend the main trace is committed under — the BLOCK PATH's pin, /// not a locally chosen equivalent and no longer `stark`'s default alias, so a /// branch that pins a different hash reaches this module too. -type MainBackend = - ::Batched; +type MainBackend = BlockBatched; /// Halves in one 32-byte commitment. pub const ROOT_HALVES: usize = 8; From a655d8b523f6044b9ae9724873a2d233a3210ecf Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 1 Sep 2026 13:33:40 -0700 Subject: [PATCH 08/23] test(lfm): the gate stops enumerating spellings of the default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three misses share one root, and it is not carelessness about any particular name: the gate is LEXICAL and the class is TYPE-LEVEL, so a list of forbidden names always lags one spelling behind the newest way to denote the default. DefaultStarkHash as a substring, then the Prover and Verifier call forms, then the Merkle backend type aliases — each was added after it bit. So stark::config is now handled by an ALLOWLIST over the namespace rather than a list of names. Prover legitimately needs exactly three items from that module, all hash-agnostic: Commitment, CommitmentHash and StarkHash. Everything else it exports is a spelling of the workspace default, so a new alias added there is flagged the first time prover names it, without anyone remembering to extend this file. An allowlist cannot lag. Deletion would be stronger — let the compiler refuse the spelling rather than a test — and it was considered and is not available. BatchedMerkleTreeBackend and FriLayerMerkleTreeBackend have twelve legitimate consumers inside crypto/stark itself: commitment.rs, gpu_lde.rs and the cuda tests, where the workspace default IS the correct hash. Marking them deprecated would fire on those under -D warnings, in the very crate that must keep using them. A clippy disallowed-types entry was considered too and rejected as unverifiable here: a workspace-root clippy.toml applies to every crate including crypto/stark, and whether a prover-local one scopes correctly is version-dependent behaviour this lane cannot test without running lint. The blessed list also loses three entries. prove_elfs_tests, page_offset_forgery_poc and recursion_soundness_gap_poc no longer mention an implied hash at all after the alias sweep, and the gate's own staleness check caught them — a blessing kept past its cause is a wish rather than an inventory. (cherry picked from commit 7226c63c3d2f376bb52937484cb589bd30f5ce1a) --- prover/src/tests/hash_pin_enumeration.rs | 71 +++++++++++++++++++----- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/prover/src/tests/hash_pin_enumeration.rs b/prover/src/tests/hash_pin_enumeration.rs index 2cae726e6..1ee9fb1e2 100644 --- a/prover/src/tests/hash_pin_enumeration.rs +++ b/prover/src/tests/hash_pin_enumeration.rs @@ -42,6 +42,58 @@ const ALIAS_CALLS: &[&str] = &["Prover::multi_prove", "Verifier::multi_verify"]; /// The pinned spellings, which contain [`ALIAS_CALLS`] as substrings. const PIN_CALLS: &[&str] = &["BlockProver::", "BlockVerifier::"]; +/// ★★ Items `prover` may name from `stark::config` — the hash-AGNOSTIC three. +/// +/// **Everything else that module exports is a spelling of the workspace +/// DEFAULT**, so this is an ALLOWLIST over a namespace rather than a list of +/// forbidden names, and that difference is the point. +/// +/// ⚠ The three misses this gate has had — `DefaultStarkHash` as a substring, +/// then the `Prover`/`Verifier` call forms, then the Merkle backend type +/// aliases — share one root: **the gate is lexical and the class is +/// type-level**, so a name list always lags one spelling behind the newest way +/// to denote the default. An allowlist cannot lag: a new alias added to +/// `stark::config` is flagged the first time `prover` names it, without anyone +/// remembering to extend this file. +/// +/// ⚖ Deletion would be stronger still — let the compiler refuse the spelling +/// rather than a test — and it was considered and is NOT available: +/// `BatchedMerkleTreeBackend` and `FriLayerMerkleTreeBackend` have twelve +/// legitimate consumers inside `crypto/stark` itself (`commitment.rs`, +/// `gpu_lde.rs`, the cuda tests), where the workspace default IS the correct +/// hash. `#[deprecated]` would fire on those under `-D warnings`, in the very +/// crate that must keep using them. +const CONFIG_ALLOWED: &[&str] = &["Commitment", "CommitmentHash", "StarkHash"]; + +/// Every item named from `stark::config` on this line, `use` lists included. +fn config_items(code: &str) -> Vec { + const PREFIX: &str = "stark::config::"; + let mut out = Vec::new(); + let mut rest = code; + while let Some(i) = rest.find(PREFIX) { + rest = &rest[i + PREFIX.len()..]; + if let Some(stripped) = rest.strip_prefix('{') { + let end = stripped.find('}').unwrap_or(stripped.len()); + for part in stripped[..end].split(',') { + let name = part.trim().split_whitespace().next().unwrap_or(""); + if !name.is_empty() { + out.push(name.to_string()); + } + } + rest = &stripped[end.min(stripped.len())..]; + } else { + let end = rest + .find(|c: char| !c.is_alphanumeric() && c != '_') + .unwrap_or(rest.len()); + if end > 0 { + out.push(rest[..end].to_string()); + } + rest = &rest[end..]; + } + } + out +} + /// The symbols that silently select a hash when nobody names one. const IMPLIED_HASH_SYMBOLS: &[&str] = &[ // The workspace's commitment configuration, and the `Prover` / `Verifier` @@ -129,20 +181,6 @@ const BLESSED: &[(&str, &str)] = &[ "Host-side BYTE-transcript differentials: the oracle for the machine's \ byte `TranscriptReplay` arm is deliberately the byte transcript.", ), - ( - "tests/prove_elfs_tests.rs", - "Names `DefaultStarkTranscript` deliberately — its header records that \ - the production path's transcript must be the one the default \ - commitment configuration names, and the test exists to hold that.", - ), - ( - "tests/recursion_soundness_gap_poc.rs", - "A proof-of-concept against the workspace default configuration.", - ), - ( - "tests/page_offset_forgery_poc.rs", - "As `recursion_soundness_gap_poc.rs`.", - ), ]; /// Every `.rs` under `dir`, relative to `root`. @@ -203,7 +241,10 @@ fn no_call_site_outside_the_pin_reaches_a_default_alias() { let text = std::fs::read_to_string(root.join(rel)).expect("a readable source file"); for line in text.lines() { let Some(code) = code_of(line) else { continue }; - let implied = IMPLIED_HASH_SYMBOLS.iter().any(|s| code.contains(s)); + let implied = IMPLIED_HASH_SYMBOLS.iter().any(|s| code.contains(s)) + || config_items(code) + .iter() + .any(|item| !CONFIG_ALLOWED.contains(&item.as_str())); let aliased = ALIAS_CALLS.iter().any(|s| code.contains(s)) && !PIN_CALLS.iter().any(|s| code.contains(s)); if implied || aliased { From 25d56ec438ad3a25668752019d634800da5b7118 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:35:11 -0300 Subject: [PATCH 09/23] test(lfm): the gate's config allowlist admits DeviceTreeBackend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The allowlist names the hash-agnostic items `prover` may take from `stark::config`. `DeviceTreeBackend` is one: it is the marker a Merkle backend implements to name its OWN hash as the device dispatch key, and `algebraic_commit` implements it for the algebraic backends — the opposite of reaching a default. Without it the gate flags that file the moment the allowlist lands on a branch carrying the algebraic dispatch. --- prover/src/tests/hash_pin_enumeration.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prover/src/tests/hash_pin_enumeration.rs b/prover/src/tests/hash_pin_enumeration.rs index 1ee9fb1e2..92c822834 100644 --- a/prover/src/tests/hash_pin_enumeration.rs +++ b/prover/src/tests/hash_pin_enumeration.rs @@ -63,7 +63,16 @@ const PIN_CALLS: &[&str] = &["BlockProver::", "BlockVerifier::"]; /// `gpu_lde.rs`, the cuda tests), where the workspace default IS the correct /// hash. `#[deprecated]` would fire on those under `-D warnings`, in the very /// crate that must keep using them. -const CONFIG_ALLOWED: &[&str] = &["Commitment", "CommitmentHash", "StarkHash"]; +/// `DeviceTreeBackend` is allowed for the same reason: it is the marker a Merkle +/// backend implements to name its OWN hash as the device dispatch key, so it +/// carries no default — `algebraic_commit` implements it for the algebraic +/// backends, which is the opposite of reaching a default. +const CONFIG_ALLOWED: &[&str] = &[ + "Commitment", + "CommitmentHash", + "StarkHash", + "DeviceTreeBackend", +]; /// Every item named from `stark::config` on this line, `use` lists included. fn config_items(code: &str) -> Vec { From 2cffabd60b768a335f4bd985cbcaf5da238aabb4 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:35:26 -0300 Subject: [PATCH 10/23] fix(lfm): the arena stride is the builder's digest width, not the configuration's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SubProofShape::{query_words, opening_words}, FriShape::query_words and TableVerifyShape::{opening_words, fri_words} sized the sibling stride from proof_arena::words_per_root(), which reads WrapHash::production(), while every emitter advances its cursor by edsl::digest_words(b), the BUILDER's width. The two agree only for a builder at production(); an explicit WrapHash::Algebraic build on a byte-pinned branch — or a byte-pinned builder under an algebraic pin — trips the emitters' own stride assertion. The width is now an argument: the machine side passes edsl::digest_words(b), the host side passes proof_arena::words_per_root(), the width it serialises roots at, and neither shape reads the configuration. The tests' literal-two strides go the same way. The host-side arena offsets and Select counts in fri_tests / join_tests read words_per_root(); the control program in join_tests declared its roots arena and indexed its root hints at a literal two words per digest on the MACHINE side, which the executor's arena-length check refuses outright under an algebraic pin — it now reads its own builder's width. Ports hash-rpo's 0998fd68 and closes the two sites it left. --- prover/src/lfm/epoch_tests.rs | 3 ++- prover/src/lfm/epoch_verify.rs | 29 +++++++++++++--------- prover/src/lfm/epoch_verify_tests.rs | 10 ++++---- prover/src/lfm/fri.rs | 18 +++++++++----- prover/src/lfm/fri_tests.rs | 36 +++++++++++++++++----------- prover/src/lfm/join_tests.rs | 15 ++++++++---- prover/src/lfm/sub_proof.rs | 25 ++++++++++++------- 7 files changed, 87 insertions(+), 49 deletions(-) diff --git a/prover/src/lfm/epoch_tests.rs b/prover/src/lfm/epoch_tests.rs index 0c3872bfb..7e944d1cf 100644 --- a/prover/src/lfm/epoch_tests.rs +++ b/prover/src/lfm/epoch_tests.rs @@ -4370,7 +4370,8 @@ fn expected_arena_words(e: &RealEpoch, with_legs: bool) -> usize { total += s.fri.num_terminal_coeffs(); total += usize::from(s.grinding_factor > 0); if with_legs { - total += leg.verify.opening_words() + leg.verify.fri_words(); + let dw = super::proof_arena::words_per_root(); + total += leg.verify.opening_words(dw) + leg.verify.fri_words(dw); } } total diff --git a/prover/src/lfm/epoch_verify.rs b/prover/src/lfm/epoch_verify.rs index aba339816..3138efb14 100644 --- a/prover/src/lfm/epoch_verify.rs +++ b/prover/src/lfm/epoch_verify.rs @@ -127,14 +127,17 @@ impl TableVerifyShape { ); } - /// Arena words this sub-proof's trace openings occupy. - pub fn opening_words(&self) -> usize { - self.num_queries * self.sub.opening_words() + /// Arena words this sub-proof's trace openings occupy, at `digest_words` + /// per sibling digest — the builder's width on the machine side, the host's + /// on the host side (see `SubProofShape::query_words`). + pub fn opening_words(&self, digest_words: usize) -> usize { + self.num_queries * self.sub.opening_words(digest_words) } - /// Arena words this sub-proof's FRI openings occupy. - pub fn fri_words(&self) -> usize { - self.num_queries * self.fri.query_words() + /// Arena words this sub-proof's FRI openings occupy, at `digest_words` per + /// sibling digest. + pub fn fri_words(&self, digest_words: usize) -> usize { + self.num_queries * self.fri.query_words(digest_words) } } @@ -146,8 +149,9 @@ impl TableVerifyShape { /// coefficients — reaches the legs as cells the spine already bound. #[derive(Clone, Copy, Debug)] pub struct TableQueryArenas { - /// Per query, per group: the row-pair values then the sibling digests (two - /// words per level). NO index word — the index is the transcript's. + /// Per query, per group: the row-pair values then the sibling digests + /// (`edsl::digest_words` per level). NO index word — the index is the + /// transcript's. pub openings: ArenaId, /// Per query, per committed FRI layer: the symmetric evaluation then the /// sibling digests. @@ -156,9 +160,10 @@ pub struct TableQueryArenas { /// Declare the query arenas for one sub-proof. pub fn declare_table_arenas(b: &mut LfmBuilder, shape: &TableVerifyShape) -> TableQueryArenas { + let digest_words = super::edsl::digest_words(b) as usize; TableQueryArenas { - openings: b.declare_arena(shape.opening_words() as u32), - fri: b.declare_arena(shape.fri_words() as u32), + openings: b.declare_arena(shape.opening_words(digest_words) as u32), + fri: b.declare_arena(shape.fri_words(digest_words) as u32), } } @@ -310,7 +315,9 @@ pub fn emit_table_verification( }; // ---- (4) per query: authenticate, fold DEEP, then fold FRI. - let stride = shape.sub.opening_words(); + let stride = shape + .sub + .opening_words(super::edsl::digest_words(b) as usize); let mut fri_terminal = Vec::with_capacity(shape.num_queries); for (qi, bits) in challenges.iota_bits.iter().enumerate() { let mut cursor = (qi * stride) as u32; diff --git a/prover/src/lfm/epoch_verify_tests.rs b/prover/src/lfm/epoch_verify_tests.rs index 36b9e86bb..a49f02ab7 100644 --- a/prover/src/lfm/epoch_verify_tests.rs +++ b/prover/src/lfm/epoch_verify_tests.rs @@ -316,7 +316,8 @@ impl TableLegs { } assert_eq!( out.len(), - self.verify.opening_words(), + self.verify + .opening_words(super::proof_arena::words_per_root()), "the opening arena must fill exactly what the shape declares" ); out @@ -333,7 +334,7 @@ impl TableLegs { } assert_eq!( out.len(), - self.verify.fri_words(), + self.verify.fri_words(super::proof_arena::words_per_root()), "the FRI arena must fill exactly what the shape declares" ); out @@ -1078,13 +1079,14 @@ fn the_assembled_verifier_rejects_tampered_leg_data() { let ix = arena_index(&e, t); assert_eq!( good[ix.openings].len(), - leg.verify.opening_words(), + leg.verify + .opening_words(super::proof_arena::words_per_root()), "table {t}: the arena at the computed openings index is not the \ openings arena" ); assert_eq!( good[ix.fri].len(), - leg.verify.fri_words(), + leg.verify.fri_words(super::proof_arena::words_per_root()), "table {t}: the arena at the computed FRI index is not the FRI arena" ); assert_eq!( diff --git a/prover/src/lfm/fri.rs b/prover/src/lfm/fri.rs index c8be56008..d47a524e7 100644 --- a/prover/src/lfm/fri.rs +++ b/prover/src/lfm/fri.rs @@ -158,10 +158,14 @@ impl FriShape { } /// Arena words one query's FRI opening occupies: per committed layer the - /// symmetric evaluation (one word) and its path (two words per level). - pub fn query_words(self) -> usize { + /// symmetric evaluation (one word) and its path (`digest_words` per level). + /// + /// `digest_words` is the BUILDER's digest width on the machine side + /// (`edsl::digest_words(b)`) and `proof_arena::words_per_root()` on the + /// host side — see `SubProofShape::query_words` for why it is an argument. + pub fn query_words(self, digest_words: usize) -> usize { // The path stride is the DIGEST's width, not a literal two. - self.num_committed() + super::proof_arena::words_per_root() * self.path_steps_per_query() + self.num_committed() + digest_words * self.path_steps_per_query() } /// Keccak permutations the whole sub-proof's FRI costs. @@ -372,7 +376,8 @@ pub fn declare_fri( let roots = b.declare_arena(edsl::digest_words(b) * c as u32); let zetas = b.declare_arena(num_zetas as u32); let coeffs = b.declare_arena(shape.num_terminal_coeffs() as u32); - let queries = b.declare_arena((num_queries * shape.query_words()) as u32); + let queries = + b.declare_arena((num_queries * shape.query_words(edsl::digest_words(b) as usize)) as u32); let layers = (0..c) .map(|i| LayerCommitment::hint(b, roots, edsl::digest_words(b) * i as u32)) @@ -421,7 +426,8 @@ pub fn hint_layer_openings_from( arena: ArenaId, query: usize, ) -> Vec { - let mut cursor = (query * shape.query_words()) as u32; + let stride = shape.query_words(edsl::digest_words(b) as usize); + let mut cursor = (query * stride) as u32; let openings: Vec = (0..shape.num_committed()) .map(|layer| { let sym = b.hint_word(arena, cursor).as_ext(); @@ -439,7 +445,7 @@ pub fn hint_layer_openings_from( .collect(); assert_eq!( cursor as usize, - (query + 1) * shape.query_words(), + (query + 1) * stride, "the emitter's cursor must agree with the declared query stride" ); openings diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index 33d7e0340..be46c0326 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -907,16 +907,18 @@ fn the_emitted_permutation_count_meets_the_pinned_prediction() { /// /// ```text /// selects/query = index_bits (pow_bits, once per query) -/// + 2 · merkle_depth · num_groups (trace walks) +/// + w · merkle_depth · num_groups (trace walks) /// + num_committed (FRI leaf ordering) -/// + 2 · path_steps_per_query (FRI walks) +/// + w · path_steps_per_query (FRI walks) /// ``` /// -/// `pow_bits` emits one `Select` per bit (`edsl.rs:257-262`) and each walk level -/// two, since a digest is two words and both must swap on the same bit -/// (`edsl.rs:164-169`). A second derivation makes the measured count exceed the -/// closed form by exactly `index_bits`, and nothing cancels it. Re-falsified in -/// that form: the injected defect now fails with "a surplus of 11 index bits". +/// where `w` is the digest's width in arena words — two on a byte hash, one on +/// an algebraic one. `pow_bits` emits one `Select` per bit (`edsl.rs:257-262`) +/// and each walk level one per digest word, since every word of a digest must +/// swap on the same bit (`edsl.rs:164-169`). A second derivation makes the +/// measured count exceed the closed form by exactly `index_bits`, and nothing +/// cancels it. Re-falsified in that form: the injected defect now fails with "a +/// surplus of 11 index bits". #[test] fn the_fri_join_adds_no_second_point_derivation() { let h = host_fri(2048, 2); @@ -947,10 +949,13 @@ fn the_fri_join_adds_no_second_point_derivation() { let per_query_selects = selects(&two) - selects(&one); let per_query_decs = decs(&two) - decs(&one); + // The digest's width, as the HOST reads it: `emit` builds at + // `WrapHash::production()`, whose builder width this is the counterpart of. + let dw = super::proof_arena::words_per_root(); let expected_selects = h.shape.index_bits() - + 2 * sub.merkle_depth * groups.len() + + dw * sub.merkle_depth * groups.len() + h.shape.num_committed() - + 2 * h.shape.path_steps_per_query(); + + dw * h.shape.path_steps_per_query(); assert_eq!( per_query_selects, expected_selects, @@ -959,11 +964,11 @@ fn the_fri_join_adds_no_second_point_derivation() { steps. A surplus of {} index bits is a second point derivation or a \ second index decomposition", h.shape.index_bits(), - 2 * sub.merkle_depth * groups.len(), + dw * sub.merkle_depth * groups.len(), groups.len(), sub.merkle_depth, h.shape.num_committed(), - 2 * h.shape.path_steps_per_query(), + dw * h.shape.path_steps_per_query(), h.shape.path_steps_per_query(), h.shape.index_bits(), ); @@ -1042,7 +1047,10 @@ fn no_tampered_fri_value_can_pass() { execute(&program, &honest, &crate::hash_pin::BLOCK_HASHER) .expect("the honest run must execute"); - let stride = h.shape.query_words(); + // Host-side offsets at the host's digest width; the program was built at + // `WrapHash::production()`, which this is the counterpart of. + let dw = super::proof_arena::words_per_root(); + let stride = h.shape.query_words(dw); // (label, arena, word) — arena order is the driver's: deep, roots, zetas, // coeffs, queries. let bump: Vec<(&str, usize, usize)> = vec![ @@ -1059,7 +1067,7 @@ fn no_tampered_fri_value_can_pass() { ( "layer 0 sibling, top level", 4, - 2 * h.shape.layer_path_len(0) - 1, + dw * h.shape.layer_path_len(0) - 1, ), ("second query's layer 0 evaluation", 4, stride), ]; @@ -1076,7 +1084,7 @@ fn no_tampered_fri_value_can_pass() { // decommitment. Every word is a real prover value. let mut spliced = honest.clone(); let (from, to) = (stride, 0usize); - let len = 1 + 2 * h.shape.layer_path_len(0); + let len = 1 + dw * h.shape.layer_path_len(0); let borrowed: Vec = spliced[4][from..from + len].to_vec(); assert_ne!( borrowed, diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index 582dadb0d..cffd3e766 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -857,8 +857,12 @@ fn control_program_source( let uniforms = b.declare_arena(2); let ood = b.declare_arena((shape.deep.num_eval_points * shape.deep.num_total_cols) as u32); let parts_arena = b.declare_arena(shape.deep.num_composition_parts as u32); - let roots = b.declare_arena(2 * groups.len() as u32); - let queries = b.declare_arena(shape.query_words() as u32); + // The roots and the query stride follow THIS builder's digest width, as the + // production emitter's do — a literal two here is a byte-hash assumption + // that the executor's arena-length check refuses under an algebraic pin. + let dw = super::edsl::digest_words(&b); + let roots = b.declare_arena(dw * groups.len() as u32); + let queries = b.declare_arena(shape.query_words(dw as usize) as u32); let extra = b.declare_arena(match control { // A second copy of every folded value, both points. Control::SplitValues => { @@ -888,7 +892,7 @@ fn control_program_source( let commitments: Vec = groups .iter() .enumerate() - .map(|(i, g)| GroupCommitment::hint(&mut b, roots, 2 * i as u32, *g)) + .map(|(i, g)| GroupCommitment::hint(&mut b, roots, dw * i as u32, *g)) .collect(); let inv = emit_deep_invariants(&mut b, &shape.deep, gamma, zeta, &ood_steps, &claimed_parts); @@ -1102,7 +1106,8 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { // Offset of this group's value `slot` inside the query arena. let mut off = 1usize; for prior in groups.iter().take(g) { - off += prior.num_values() + 2 * h.shape.merkle_depth; + off += prior.num_values() + + super::proof_arena::words_per_root() * h.shape.merkle_depth; } off + slot }; @@ -1225,7 +1230,7 @@ fn sweep_tampers(h: &HostSubProof, label: &str) { siblings[level][0] ^= 1; let mut arenas = h.arenas(&[q]); let base = 1 + groups[0].num_values(); - arenas[4][base..base + 2 * h.shape.merkle_depth] + arenas[4][base..base + super::proof_arena::words_per_root() * h.shape.merkle_depth] .copy_from_slice(&commitments_to_arena(&siblings)); execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() diff --git a/prover/src/lfm/sub_proof.rs b/prover/src/lfm/sub_proof.rs index 787b224f3..663441405 100644 --- a/prover/src/lfm/sub_proof.rs +++ b/prover/src/lfm/sub_proof.rs @@ -132,9 +132,18 @@ impl SubProofShape { } /// Arena words one query's openings occupy — every group's values, plus - /// the index and the sibling digests (two words per level per group). - pub fn query_words(&self) -> usize { - 1 + self.opening_words() + /// the index and the sibling digests (`digest_words` per level per group). + /// + /// ★ `digest_words` is the DIGEST's width in arena words, and it is an + /// argument rather than a read of the configuration: the machine side + /// passes `edsl::digest_words(b)` — the BUILDER's width, the one every + /// emitter advances its cursor by — and the host side passes + /// `proof_arena::words_per_root()`, the width it serialises roots at. A + /// shape that read the configuration here would agree with a builder at + /// `WrapHash::production()` and disagree with any other, and the + /// disagreement would surface as the emitter's own stride assertion. + pub fn query_words(&self, digest_words: usize) -> usize { + 1 + self.opening_words(digest_words) } /// [`Self::query_words`] WITHOUT the index word. @@ -143,10 +152,9 @@ impl SubProofShape { /// all but the transcript's own bits, so the arena carries only the opened /// values and the paths. An arena that still carried an index would be /// offering the prover a second one. - pub fn opening_words(&self) -> usize { + pub fn opening_words(&self, digest_words: usize) -> usize { let values: usize = self.groups().iter().map(GroupShape::num_values).sum(); - let siblings = - super::proof_arena::words_per_root() * self.merkle_depth * self.groups().len(); + let siblings = digest_words * self.merkle_depth * self.groups().len(); values + siblings } @@ -570,7 +578,8 @@ pub fn emit_sub_proof_with_bits( let ood = b.declare_arena((shape.deep.num_eval_points * shape.deep.num_total_cols) as u32); let parts = b.declare_arena(shape.deep.num_composition_parts as u32); let roots = b.declare_arena(edsl::digest_words(b) * groups.len() as u32); - let queries = b.declare_arena((num_queries * shape.query_words()) as u32); + let queries = + b.declare_arena((num_queries * shape.query_words(edsl::digest_words(b) as usize)) as u32); let arenas = SubProofArenas { uniforms, ood, @@ -644,7 +653,7 @@ pub fn emit_sub_proof_with_bits( } assert_eq!( cursor as usize, - num_queries * shape.query_words(), + num_queries * shape.query_words(edsl::digest_words(b) as usize), "the emitter's cursor must agree with the declared query stride" ); From 39eba7907eeaf6a0f7cd0c8947e83d8816d3d960 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:37:06 -0300 Subject: [PATCH 11/23] style(tests): clippy trim_split_whitespace in the gate's config_items split_whitespace already ignores leading and trailing whitespace, so the trim() the ported allowlist parser did first is redundant; clippy 1.94 refuses it under -D warnings. --- prover/src/tests/hash_pin_enumeration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prover/src/tests/hash_pin_enumeration.rs b/prover/src/tests/hash_pin_enumeration.rs index 92c822834..59ee0e04d 100644 --- a/prover/src/tests/hash_pin_enumeration.rs +++ b/prover/src/tests/hash_pin_enumeration.rs @@ -84,7 +84,7 @@ fn config_items(code: &str) -> Vec { if let Some(stripped) = rest.strip_prefix('{') { let end = stripped.find('}').unwrap_or(stripped.len()); for part in stripped[..end].split(',') { - let name = part.trim().split_whitespace().next().unwrap_or(""); + let name = part.split_whitespace().next().unwrap_or(""); if !name.is_empty() { out.push(name.to_string()); } From 603c1e15537c18664d22dd3c8c0a22f2ffb43222 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:37:15 -0300 Subject: [PATCH 12/23] feat(lfm): pin per-table-gpu's block path to RPX256 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three axes of prover/src/hash_pin.rs move together and nothing else does: BlockStarkHash = algebraic_commit::RpxStarkHash (what the host commits under), BlockTranscript = algebraic_transcript::AlgebraicTranscript built by block_transcript() with BLOCK_HASHER (the Fiat-Shamir object, pinned separately because StarkHash::Transcript cannot force it), and BLOCK_HASHER = HasherKind::Rpx (the LFM_HASH socket permutation the block path's programs are executed and proved under). The workspace default stays BLAKE3 and its assertions stay intact; the registry keeps its blessed identities because build_artifacts defaults to REGISTRY_HASHER and the block path names the pin at its call sites. This commit carries NO regenerated tables. It is the trial flip HASH-PINNING prescribes: the prover recomputes an RPX root for a static preprocessed table, compares it against the blessed BLAKE3 constant, and must fail loudly at prove time with ProvingError::PrecomputedCommitmentMismatch. A green run here means the static-commitment path was not exercised. Both regenerations follow. Under `cuda` this compiles — the algebraic backends are DeviceTreeBackends — and aborts at the first device commit with `unimplemented!` naming the hash; every proving gate under this pin is a CPU run until the RPX kernels land. --- prover/src/hash_pin.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prover/src/hash_pin.rs b/prover/src/hash_pin.rs index debe390bb..c9e2a8e7b 100644 --- a/prover/src/hash_pin.rs +++ b/prover/src/hash_pin.rs @@ -90,14 +90,13 @@ /// Every `multi_prove` / `multi_verify` instantiation in this crate names this /// rather than `stark::config::DefaultStarkHash`, so the two can differ on a /// branch without the workspace default moving. -pub type BlockStarkHash = stark::config::DefaultStarkHash; +pub type BlockStarkHash = crate::lfm::algebraic_commit::RpxStarkHash; /// The Fiat–Shamir transcript OBJECT the block path builds. /// /// See the module header for why this is pinned separately from /// [`BlockStarkHash`] rather than derived from it. -pub type BlockTranscript = - stark::config::DefaultStarkTranscript; +pub type BlockTranscript = crate::lfm::algebraic_transcript::AlgebraicTranscript; /// A fresh block-path transcript over `seed`. /// @@ -106,7 +105,7 @@ pub type BlockTranscript = /// algebraic one absorbs it as its first `append_bytes` call. Callers should not /// have to know which. pub fn block_transcript(seed: &[u8]) -> BlockTranscript { - BlockTranscript::new(seed) + BlockTranscript::with_seed(BLOCK_HASHER, seed) } /// The prover the block path drives, at [`BlockStarkHash`]. @@ -145,7 +144,7 @@ pub type BlockVerifier = /// Every `execute` and prove call on the block path names this rather than a /// literal, so the two axes cannot drift apart in a test harness while /// production stays correct. -pub const BLOCK_HASHER: crate::lfm::hash::HasherKind = crate::lfm::hash::HasherKind::Test; +pub const BLOCK_HASHER: crate::lfm::hash::HasherKind = crate::lfm::hash::HasherKind::Rpx; /// The [`CommitmentHash`] the block path's roots may be called by. /// From a7aadc95d0e971a1cd294f04ee1989a0fd5c3207 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:40:56 -0300 Subject: [PATCH 13/23] docs(lfm): HASH-PINNING for the RPX pin, and the four static families named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HASH-PINNING.md records the per-table-gpu pin: the three axes and their RPX values, REGISTRY_HASHER and the call-site classification rule, the enumeration gate's allowlist, the builder-width arena stride, both regenerations with the control-first discipline and what the control does not prove, the width defect, and the running rules (CPU-only proving under this pin, a fresh artifact directory, no byte comparison of proofs). Provenance is stated as it is: miden publishes no known-answer table for RPX, so the anchor is the repo's own host KAT harness, a self-built oracle. The cuda section says the build COMPILES and aborts loudly at the first device commit — no compile_error!, no byte-hash fallback, both lint arms gate. The regenerator's header and hash_pin.rs's regeneration note both said three static families; compute_static_commitments rewrites four (page carries a zero-page and a private-page constant), and now both say so. --- HASH-PINNING.md | 231 +++++++++++++++++++ prover/src/bin/compute_static_commitments.rs | 16 +- prover/src/hash_pin.rs | 9 +- 3 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 HASH-PINNING.md diff --git a/HASH-PINNING.md b/HASH-PINNING.md new file mode 100644 index 000000000..e21b50471 --- /dev/null +++ b/HASH-PINNING.md @@ -0,0 +1,231 @@ +# HASH PINNING — `per-table-gpu` under RPX256 + +The integration branch's BLOCK PATH is pinned to RPX256. The workspace default +stays BLAKE3, with its `const` assertions intact; nothing in `crypto/stark` +moves. This file records what the pin is, what enforces it, and what a person +running a box under it must not get wrong. + +**This branch's hash: RPX256** — Rescue-Prime eXtended (XHash12), state 12 / +rate 8 / capacity 4, a **one-cell four-felt digest** against the byte hashes' +two-cell 32-byte one. RPO's geometry with RPO's constants and a different round +schedule, `FB E FB E FB E M`: three of the seven rounds trade the ~2^63-dense +inverse S-box for a seventh power in the degree-3 EXTENSION field, and the last +round is linear. Cheaper on the host and narrower in the AIR than RPO +(`prover/src/lfm/hash.rs`, `HasherKind::Rpx`). + +| pin item | value | +|---|---| +| `BlockStarkHash` | `algebraic_commit::RpxStarkHash` | +| `BlockTranscript` / `block_transcript()` | `algebraic_transcript::AlgebraicTranscript`, built `with_seed(BLOCK_HASHER, seed)` | +| `BLOCK_HASHER` | `hash::HasherKind::Rpx` | + +⚖ **Provenance, stated plainly.** Miden publishes no known-answer table for +RPX — the opposite of RPO's nineteen published vectors — so this hash has a +weaker external anchor than the RPO pin had. What anchors it is the repo's own +host known-answer harness (`make test-rpx-host-kat`; +`crypto/math-cuda/tests/host_kat/rpx_host_kat.cpp` against +`prover/tests/rpx_host_kat_vectors.rs`), which checks the C reference and the +Rust permutation against each other. That is a **self-built oracle**, not an +external one, and it must be described as such. Domain separation is through +the capacity, exactly as for RPO: the three algebraic candidates share one +leaf and parent construction (`prover/src/lfm/algebraic_commit.rs`) and differ +only in the permutation. + +## The pin, mechanically — `prover/src/hash_pin.rs`, and the workspace default does NOT move + +`crypto/stark`'s `DefaultStarkHash` is the *workspace's* default. It names the +hash behind `Commitment`, `BatchedMerkleTree` and every blessed constant in the +repo, and a `const` assertion in `config.rs` makes re-pointing it a compile +error so those artifacts cannot drift. The pin therefore lives one layer up: +`IsStarkProver` is generic over the +configuration, and `prover` names the configuration explicitly at every prove +and verify call site. Collecting those names behind `hash_pin.rs` turns "which +hash does the block path use" into a property of one module. + +**THREE ORTHOGONAL AXES, all named in `hash_pin.rs` and nowhere else:** + +| axis | name | this pin | +|---|---|---| +| what the HOST commits under | `BlockStarkHash` | `algebraic_commit::RpxStarkHash` | +| the Fiat–Shamir transcript OBJECT | `BlockTranscript` / `block_transcript` | `algebraic_transcript::AlgebraicTranscript` | +| the `LFM_HASH` socket permutation | `BLOCK_HASHER` | `hash::HasherKind::Rpx` | + +⚠ **Axis 2 is the dangerous one.** `StarkHash::Transcript` names a *digest* +configuration, which is what GRINDING computes over; the Fiat–Shamir transcript +*object* is built by the caller and handed to `multi_prove`, so the type system +does not force it to match. For the byte hashes the two coincide. For an +algebraic hash they do not, and a branch that pinned only `BlockStarkHash` +would commit under RPX while sponging Fiat–Shamir through bytes — self-consistent +between prover and verifier, and therefore **silent**. + +⚠ **Axis 3 is consulted only by algebraic programs.** Under a byte hash the +emitter's Merkle work lowers to the dedicated KECCAK / `LFM_BLAKE3` chips and +emits no `Instr::Hash` at all, so the socket hasher handed to `execute` is never +consulted and a toy permutation is free and correct. The algebraic arm goes +through `compress` / `permute`, which ARE `Instr::Hash`, executed by whatever is +passed. + +### `REGISTRY_HASHER` and the classification rule + +`registry::build_artifacts` defaults to `REGISTRY_HASHER = HasherKind::Test`, +the permutation `LFM_REGISTRY` is blessed under, and the generator +(`compute_lfm_registry`) imports that same constant, so the blessed value and +the builder default are one definition. The hasher is part of program IDENTITY +(`HasherKind::as_tag` is folded into `lfm_program_id`), so the block path names +its hasher AT THE CALL SITE instead: + +> **A program built at `WrapHash::production()` emits `Instr::Hash` and must be +> proved under `BLOCK_HASHER`. A program that pins a byte hash on its own +> builder emits none, never consults the socket, and is correct at the +> registry's blessed default under every pin.** + +It is *checkable*, not a judgement — read which program the site builds. The 17 +block-path sites (`wrap_tests` 8, `aggregator_tests` 7, `fri_tests` 1, +`join_tests` 1) name `BLOCK_HASHER` through `build_artifacts_with_hasher`; +`wrap_tests`' keccak-chain census site keeps the default because +`keccak_chain_program` pins keccak on its own builder. The registry-identity +suites (`machine_tests`, the chip suites) keep the default too — that is what +`registry_drift_*` compares against. + +⛔ **Do not "fix" a `registry_drift_*` failure by re-blessing the registry under +the pin.** Beyond violating the table's own doctrine — *a second hasher becomes +additional ROWS, never a silent replacement* — it would move registry +identities on the BLAKE3 control, converting "control drifted → STOP and +investigate" into a self-inflicted alarm on the one measurement the comparison +turns on. `build_artifacts` was briefly made to name `BLOCK_HASHER` itself; +that fixed a real aggregator defect at the wrong scope, and every registry +identity moved. + +### Enforcement in-tree + +`prover/src/tests/hash_pin_enumeration.rs` scans the crate for any code line +reaching `DefaultStarkHash`, `DefaultStarkTranscript` or `HasherKind::default()`, +any `Prover::multi_prove` / `Verifier::multi_verify` call that is not the +`BlockProver::` / `BlockVerifier::` spelling, and any item taken from +`stark::config` outside the hash-agnostic allowlist (`Commitment`, +`CommitmentHash`, `StarkHash`, `DeviceTreeBackend`) — an allowlist over a +namespace, because a name list always lags one spelling behind the newest way +to denote the default. A new site fails the test and names itself. What the +gate cannot see is a site that names a hash explicitly and names the *wrong* +one; the instruments for that are `hash_pin::tests` and the differentials in +`algebraic_commit` / `algebraic_transcript`. + +### The arena stride is the BUILDER's digest width + +`SubProofShape::{query_words, opening_words}`, `FriShape::query_words` and +`TableVerifyShape::{opening_words, fri_words}` take the digest width as an +argument. The machine side passes `edsl::digest_words(b)` — the builder's width, +the one every emitter advances its cursor by — and the host side passes +`proof_arena::words_per_root()`, the width it serialises roots at. A shape that +read the configuration instead agreed with a builder at `WrapHash::production()` +and disagreed with any other, and the executor's arena-length check is strict: +a program declaring a roots arena at a literal two words per digest is an +`ArenaLenMismatch` under this pin, not a slow path. + +## `cuda` on an algebraic pin — COMPILES, and still cannot prove under the wrong hash + +`--features cuda` builds on this branch. The algebraic backends are +`DeviceTreeBackend`s carrying their own `CommitmentHash` as the device dispatch +key, so the type system pairs a device tree with the permutation it was named +for and cannot produce a keccak tree *labelled* RPX. `math-cuda` has no kernel +for the RPX permutation wired into its dispatch yet, so a GPU run under this pin +aborts at its first device commit with `unimplemented!` naming the hash — loud, +at launch, naming the cause. + +⛔ **Neither a `compile_error!` nor a byte-hash fallback belongs here.** The +first hides the cuda lint arm from the branch, which is how a dispatch +regression would reach main unseen; the second is exactly the silent wrong-hash +build this pin exists to make impossible. A build that aborts is safe; a build +that quietly proves under the wrong hash is not. + +**Lint standard on this branch: BOTH passes gate.** `make lint`'s cuda +combination is a real signal here, unlike on the pre-dispatch `hash-rpo` cut +where it was expected to fail. + +**Consequence for box work:** proving a block under this pin means CPU until the +RPX kernels land in the dispatch. GPU boxes remain useful for the byte-hash lanes +only. + +## ⚠ TWO REGENERATIONS — a pin change is not complete without both + +Every root blessed under BLAKE3 has to be regenerated, and there are two +families of them. This is why the pin PR is large and mostly generated tables — +that is EXPECTED, not a mistake. + +1. **`LFM_REGISTRY`** — the hasher and the commitment hash are both folded into + every `program_id`. `cargo run --bin compute_lfm_registry --release`. Per + entry the `roots`, `program_id` and `prep_root` move; `log_heights`, + `prep_widths`, `chip_set` and `keccak_rnd_chunks` are shape and must not. +2. **The static preprocessed commitments** — FOUR families, not three: + `bitwise`, `keccak_rc`, and `page`'s zero-page AND private-page (OFFSET-only) + constants, each at blowup 2/4/8. Each returns a BLESSED CONSTANT from + `preprocessed_commitment` rather than recomputing, so under a new pin the + prover recomputes an RPX root, compares it against a BLAKE3 constant and + fails with `ProvingError::PrecomputedCommitmentMismatch`. + `cargo run --bin compute_static_commitments --release`. + +That failure is the **trial flip** this pin's PR performs on purpose: flip the +four `hash_pin.rs` lines without regenerating, run the crate's own prove/verify +legs, and expect the mismatch — loud, at prove time, naming the cause. A trial +flip that is GREEN before regeneration means the static-commitment path was not +exercised; treat that as a coverage hole, not as good news. + +★ **Regenerate control-first.** Run each regenerator under the outgoing pin and +confirm it reproduces the existing table byte for byte (rustfmt's trailing +commas are the only expected textual difference) BEFORE trusting it on RPX. + +⛔ **AND THAT IS ALL THE CONTROL PROVES.** `compute_lfm_registry` names +`REGISTRY_HASHER` explicitly and never reads `build_artifacts`, so re-running it +validates the generator **against itself**. When `build_artifacts` was briefly +changed to name `BLOCK_HASHER`, every registry `program_id` moved and this +control reproduced byte-for-byte anyway — it could not have fired. **The check +that fires is `machine_tests::registry_drift_*`**, because it recomputes from +the changed path and compares against the blessed table. A self-consistency +check and an independent check are not substitutes, and quoting the first for a +claim only the second can support is how a green number gets trusted for +something it never examined. A drift failure is investigated, never re-blessed +to silence the test, and neither table is ever hand-edited. + +## ⚠ THE ONE WIDTH DEFECT THAT COULD HAVE PASSED + +Every digest-width defect on the algebraic migration failed loudly, and there +is a reason rather than luck: the machine reconstructs a root matching nothing, +and nothing downstream can proceed. **One shape sidestepped reconstruction +entirely.** `fri_tests`' leaf gate published its digest as two cells and +compared them pairwise. An algebraic `WrapDigest` is ONE cell whose second slot +**repeats the first** (`WrapDigest::from_cell`), so the comparison read one lane +twice and would have **passed on a duplicated value** — a green test asserting +nothing, in the one place whose entire claim is that the machine's leaf IS the +verifier's leaf. It now publishes the digest's own cells. Re-audit any new +comparison that could pass on a repeated cell; `edsl::keccak256` and the BLAKE3 +chain return `[Cell; 2]` because those digests genuinely are two cells. + +## RUNNING UNDER THIS PIN + +- **Every proving gate is a CPU run** (see the cuda section). The gates the pin + PR ran: `machine_tests::registry_drift_*` unchanged, `hash_pin`, + `tests::hash_pin_enumeration`, `fri_tests::the_fri_leg_proves_and_verifies`, + `join_tests::the_join_proves_and_verifies`, + `wrap_tests::the_fixture_epoch_wraps`, the four grinding differentials in + `algebraic_commit.rs`, then the full `--lib` suite and `make lint` on both + arms. +- **`P3_ARTIFACT_DIR` must be a FRESH directory for any block run.** The block + driver *loads* cached artifacts when it finds them, so a directory carrying a + BLAKE3 run's bundle and wraps would feed byte-hash proofs to an RPX verifier. + A fresh directory still persists artifacts, so an aggregation OOM does not + cost the first hour again. +- The fixture cache is separate and IS keyed on the pin: + `proof_fixture::cache_format_key()` reads `BLOCK_COMMITMENT_HASH`, so this pin + gets its own blob for free. +- **Proof BYTES do not reproduce run to run** (grinding draws a nonce + non-deterministically); roots do. Never `sha256`-compare proofs. +- **Numbers:** memory and time are separate verdicts on separate lines, block + level only. The BLAKE3 batched record the hash comparison is measured against + (`hash-blake3`, `HASH-PINNING.md` there: 104.2 min wall, 358.2 GiB peak RSS, + 36.9 MB block proof) is the control, and this branch's per-table prover is a + different aggregator from the one that set it — a per-table number and a + batched number are not comparable, and no projected RPX line is carried over + from the RPO pin. + +Poseidon is **UNSHIPPABLE** (broken family, eprint 2026/306 and 2026/1692) and +remains a reference column only; XHash8 is flagged and **not adopted**. diff --git a/prover/src/bin/compute_static_commitments.rs b/prover/src/bin/compute_static_commitments.rs index a4de1ddaa..709257192 100644 --- a/prover/src/bin/compute_static_commitments.rs +++ b/prover/src/bin/compute_static_commitments.rs @@ -1,7 +1,9 @@ -//! Prints static `(bitwise, keccak_rc, zero_page)` preprocessed-table commitments -//! for a fixed set of `blowup_factor` values. The output is pasted into the +//! Prints the static preprocessed-table commitments — FOUR families: `bitwise`, +//! `keccak_rc`, and `page`'s zero-page and private-page (OFFSET-only) constants +//! — for a fixed set of `blowup_factor` values. The output is pasted into the //! `static_commitment` match bodies in `prover/src/tables/{bitwise,keccak_rc}.rs` -//! and the `static_zero_page_commitment` match body in `prover/src/tables/page.rs`. +//! and the `static_zero_page_commitment` / `static_private_page_commitment` +//! match bodies in `prover/src/tables/page.rs`. //! The `static_commitments_tests` test suite pins the values so any drift in //! the AIR or FFT pipeline is caught at test time. //! @@ -10,8 +12,9 @@ //! //! ⚠️ Do not run this just to silence a failing drift test — see the //! "Regenerating" section on `static_commitment` in `bitwise.rs` / -//! `keccak_rc.rs` and `static_zero_page_commitment` in `page.rs` for when -//! it's actually appropriate to bless new bytes. +//! `keccak_rc.rs` and the two `page.rs` constants for when it's actually +//! appropriate to bless new bytes. A hash-pin change is one such time, and it +//! regenerates all four families together (`prover/src/hash_pin.rs`). use lambda_vm_prover::tables::{STATIC_BLOWUP_FACTORS, bitwise, keccak_rc, page}; use stark::config::Commitment; @@ -37,7 +40,8 @@ fn main() { println!( "// Paste these match arms into the `static_commitment` match bodies\n\ // in `prover/src/tables/{{bitwise,keccak_rc}}.rs` and the\n\ - // `static_zero_page_commitment` match body in `prover/src/tables/page.rs`.\n" + // `static_zero_page_commitment` / `static_private_page_commitment`\n\ + // match bodies in `prover/src/tables/page.rs`.\n" ); let zero_page_config = page::PageConfig::zero_init(0); diff --git a/prover/src/hash_pin.rs b/prover/src/hash_pin.rs index c9e2a8e7b..0eebf1581 100644 --- a/prover/src/hash_pin.rs +++ b/prover/src/hash_pin.rs @@ -73,10 +73,11 @@ //! //! 1. **`LFM_REGISTRY`** — the hasher is folded into every `program_id`. //! `cargo run --bin compute_lfm_registry --release`. -//! 2. **The static preprocessed commitments** — `bitwise`, `keccak_rc` and -//! `page` each return a BLESSED CONSTANT from `preprocessed_commitment` -//! rather than recomputing, so under a new pin the prover recomputes an -//! RPO root, compares it against a BLAKE3 constant, and fails with +//! 2. **The static preprocessed commitments** — FOUR families: `bitwise`, +//! `keccak_rc`, and `page`'s zero-page AND private-page constants. Each +//! returns a BLESSED CONSTANT from `preprocessed_commitment` rather than +//! recomputing, so under a new pin the prover recomputes an algebraic +//! root, compares it against a BLAKE3 constant, and fails with //! `ProvingError::PrecomputedCommitmentMismatch`. //! `cargo run --bin compute_static_commitments --release`. //! From f5307957d2a517862b720778065a13eea4526d64 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 17:32:24 -0300 Subject: [PATCH 14/23] feat(lfm): both regenerations under the RPX pin, reproduced control-first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LFM_REGISTRY and the four static preprocessed families (bitwise, keccak_rc, page zero-page, page private-page, each at blowup 2/4/8), regenerated with compute_lfm_registry and compute_static_commitments at the pinned head on a CPU box and pasted from their output. Control first, at the pre-pin head: both generators reproduced the committed tables exactly — the registry byte for byte on all six entries after normalising rustfmt's line wrapping and trailing commas, and all twelve static constants. Only then were they trusted under the pin. What the pin moved, per registry entry: 12 of the 15 roots (the three zero-width slots are unchanged), program_id and prep_root. What it left alone: kind, blowup_factor, log_heights, keccak_rnd_chunks, chip_set, prep_widths and hasher, which stays HasherKind::Test because the registry is blessed under REGISTRY_HASHER, not under the block path's BLOCK_HASHER. All twelve static constants moved. These tables are generated output and are never hand-edited; a registry_drift_* failure against them is investigated, not re-blessed. --- prover/src/lfm/registry.rs | 504 ++++++++++++++++----------------- prover/src/tables/bitwise.rs | 18 +- prover/src/tables/keccak_rc.rs | 18 +- prover/src/tables/page.rs | 36 +-- 4 files changed, 288 insertions(+), 288 deletions(-) diff --git a/prover/src/lfm/registry.rs b/prover/src/lfm/registry.rs index 7be31db62..3481c4ecf 100644 --- a/prover/src/lfm/registry.rs +++ b/prover/src/lfm/registry.rs @@ -799,64 +799,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0xc4, 0x37, 0x4f, 0xb8, 0xb4, 0xfb, 0x57, 0x10, 0xd0, 0x8a, 0x30, 0xd5, 0xfe, 0xee, - 0x32, 0x36, 0x95, 0xfb, 0x55, 0xaa, 0x27, 0x9d, 0x4c, 0x5b, 0xf9, 0xff, 0xd6, 0xed, - 0xc0, 0xb9, 0x9d, 0x4f, + 0x82, 0x54, 0x4b, 0xc4, 0x61, 0x8d, 0x49, 0x29, 0x43, 0x97, 0x57, 0x20, 0xca, 0x82, + 0xd6, 0x74, 0xfe, 0x72, 0x82, 0x09, 0x39, 0x3c, 0xe6, 0x45, 0x42, 0xab, 0xbf, 0x15, + 0x9f, 0xec, 0xad, 0xfd, ], [ - 0x39, 0xa6, 0xc2, 0x1c, 0xed, 0x86, 0x48, 0x8a, 0xcf, 0xdc, 0xa7, 0xcf, 0x82, 0xd0, - 0x2d, 0x16, 0xc7, 0x65, 0xb4, 0x7b, 0x75, 0x79, 0x15, 0x9b, 0xfe, 0xfc, 0xe4, 0x36, - 0x67, 0x6b, 0x9e, 0x6a, + 0xe7, 0x02, 0x4b, 0x94, 0x35, 0x37, 0xe8, 0x74, 0x72, 0x94, 0xc0, 0x66, 0xf7, 0xa7, + 0x40, 0x08, 0xbb, 0x59, 0x87, 0xa4, 0xe2, 0xf6, 0x50, 0x80, 0x4b, 0xe0, 0xb3, 0x67, + 0x01, 0xbc, 0x82, 0x91, ], [ - 0x3a, 0x2f, 0xf0, 0xdd, 0x38, 0x45, 0x69, 0x6f, 0xa9, 0xc9, 0xd3, 0xc7, 0xe4, 0xa1, - 0xc0, 0x46, 0x30, 0x01, 0xb3, 0xd5, 0xad, 0x16, 0xb4, 0x89, 0x25, 0xc6, 0xdc, 0x5a, - 0xa8, 0x47, 0xe9, 0x1c, + 0x05, 0x9b, 0xa3, 0x44, 0xe5, 0x9c, 0x68, 0xbd, 0xa5, 0x82, 0xee, 0xec, 0xca, 0xee, + 0x30, 0xbb, 0x3f, 0x0e, 0x73, 0x19, 0x07, 0x82, 0x52, 0x8d, 0x39, 0x43, 0x78, 0x04, + 0x61, 0x09, 0xc5, 0x26, ], [ - 0xd5, 0x12, 0xa3, 0xb7, 0xcd, 0x73, 0x29, 0xe1, 0x4f, 0x2a, 0x69, 0x49, 0xa2, 0xe2, - 0x1a, 0xbe, 0x4a, 0xf0, 0xf6, 0xd4, 0xd5, 0xd1, 0x21, 0x91, 0x93, 0x55, 0x9e, 0x04, - 0x99, 0xc2, 0x9c, 0xbd, + 0x87, 0xfb, 0xc4, 0x76, 0x2e, 0x30, 0xa3, 0xe6, 0x6e, 0x84, 0x5a, 0xd8, 0xec, 0x2b, + 0x40, 0xe7, 0x45, 0x95, 0xb1, 0xa4, 0x59, 0xbd, 0x1c, 0x4b, 0xed, 0xcc, 0x9d, 0xb1, + 0xa6, 0xdd, 0x7b, 0x52, ], [ - 0xd6, 0xd8, 0x93, 0x9a, 0xcb, 0x60, 0xe0, 0xd5, 0x37, 0x9c, 0x9e, 0xc6, 0x44, 0x9e, - 0xa0, 0x46, 0x6a, 0xb2, 0xc4, 0x2b, 0x53, 0x9a, 0x9f, 0xa7, 0x1d, 0xd8, 0x02, 0x16, - 0x36, 0x9d, 0xf3, 0xdb, + 0x0d, 0x28, 0x93, 0xa3, 0xb5, 0xa3, 0x06, 0x0c, 0xab, 0x08, 0x2f, 0x56, 0x8f, 0x9a, + 0x4b, 0x02, 0x6b, 0x58, 0x55, 0x45, 0xf5, 0xc3, 0xfd, 0x0d, 0x12, 0x39, 0x4d, 0x99, + 0x48, 0x41, 0x4b, 0xf7, ], [ - 0x4d, 0xee, 0x07, 0x52, 0x0b, 0x48, 0xd9, 0x42, 0x4c, 0xb7, 0xc5, 0x5f, 0x70, 0xbf, - 0xbd, 0xed, 0xf1, 0xc5, 0x85, 0xaf, 0xea, 0xdd, 0xfc, 0x8d, 0xb8, 0x0b, 0x7c, 0x64, - 0x35, 0xe2, 0x81, 0x31, + 0xee, 0x9a, 0x81, 0xfb, 0xc8, 0xbd, 0x72, 0x5f, 0x89, 0x27, 0x3f, 0xd4, 0xe8, 0xcb, + 0x20, 0x07, 0xf8, 0x01, 0x7f, 0x7e, 0x78, 0x34, 0x38, 0x9a, 0xef, 0xce, 0xfd, 0x39, + 0xaf, 0xe0, 0x9c, 0x6d, ], [ - 0x80, 0xe6, 0x2e, 0x9d, 0xac, 0x64, 0x93, 0x41, 0xae, 0x34, 0x6a, 0xba, 0xaa, 0x9a, - 0x1f, 0x49, 0x0c, 0x3f, 0xfd, 0xd3, 0x20, 0x6c, 0x97, 0xdc, 0xcd, 0xc1, 0x95, 0x99, - 0x6f, 0xe5, 0x6b, 0x70, + 0x8d, 0xa4, 0x49, 0x54, 0x80, 0xa3, 0xfe, 0x4d, 0x2f, 0xb1, 0xe0, 0x1d, 0xd9, 0x6b, + 0x82, 0xa6, 0x65, 0x36, 0x1f, 0x21, 0xf9, 0x92, 0x67, 0x4f, 0x88, 0x90, 0x70, 0xab, + 0xff, 0x31, 0xec, 0xb2, ], [ - 0xa2, 0xf9, 0x94, 0x17, 0x1f, 0xb2, 0x5f, 0x6c, 0x9d, 0x29, 0x94, 0xb7, 0xc2, 0x7f, - 0x6b, 0x1d, 0x6a, 0xe7, 0xad, 0x66, 0x5b, 0x41, 0xa7, 0xd1, 0xf2, 0x53, 0x80, 0xa6, - 0x34, 0x5d, 0x27, 0x87, + 0x9b, 0xd2, 0x23, 0x26, 0x3c, 0xa7, 0x25, 0xb3, 0xc1, 0x3c, 0x18, 0xe9, 0x9c, 0xb2, + 0x28, 0xdb, 0x4f, 0x29, 0x58, 0xac, 0xdb, 0x52, 0x58, 0xe8, 0x23, 0x2e, 0xb0, 0x8b, + 0x0d, 0xd5, 0xed, 0x41, ], [ - 0x18, 0x61, 0x3e, 0x19, 0xc1, 0x22, 0x91, 0x76, 0xa8, 0x95, 0x99, 0xba, 0x0d, 0x28, - 0x28, 0xad, 0x60, 0xe9, 0x02, 0x41, 0x85, 0x6f, 0xa5, 0xfa, 0x7c, 0xef, 0xd6, 0x90, - 0x97, 0x13, 0x2b, 0x26, + 0xd7, 0x1c, 0x38, 0x03, 0x8f, 0x76, 0x18, 0x6f, 0xde, 0xf9, 0x59, 0x89, 0x9e, 0xc4, + 0x6d, 0xa2, 0x5a, 0x5b, 0x94, 0xd0, 0x5f, 0x34, 0x32, 0x0a, 0xd8, 0xbc, 0xf3, 0x54, + 0xda, 0xf0, 0x81, 0xcb, ], [ - 0xc5, 0x58, 0xc5, 0x04, 0x78, 0xb4, 0x99, 0xd5, 0x48, 0x59, 0x23, 0x3a, 0xed, 0xf0, - 0x78, 0x5e, 0xea, 0xa1, 0x67, 0xb4, 0x9a, 0xec, 0xfc, 0x7e, 0xd5, 0x99, 0x2e, 0xf0, - 0x04, 0xd5, 0xdc, 0x7f, + 0xca, 0x9c, 0x81, 0x3f, 0xdc, 0xd4, 0x49, 0x28, 0x06, 0xd2, 0x9b, 0x22, 0x09, 0x77, + 0x0f, 0x27, 0x87, 0x6e, 0xb1, 0x28, 0xf4, 0x3c, 0x4a, 0x38, 0xf7, 0x57, 0x42, 0x92, + 0x29, 0xeb, 0x86, 0x8c, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x5b, 0x89, 0xe1, 0x60, 0xe1, 0xcf, 0x68, 0x81, 0xd2, 0xeb, 0x40, 0x21, 0x62, 0x5f, - 0x7a, 0x76, 0x96, 0x9e, 0xc8, 0x0b, 0x01, 0x3f, 0x0a, 0xfb, 0x3f, 0x24, 0x73, 0xcd, - 0x55, 0xd3, 0xea, 0x27, + 0x4b, 0xd7, 0x50, 0x10, 0xec, 0x2d, 0xfb, 0x77, 0xbd, 0xdd, 0x72, 0x55, 0xec, 0xb4, + 0x35, 0x03, 0xb1, 0x99, 0x66, 0xac, 0xe9, 0x33, 0xe6, 0xcc, 0x08, 0xae, 0xba, 0x7c, + 0x54, 0xdb, 0x18, 0x9c, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -882,14 +882,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0x5b, 0xbd, 0x2f, 0x24, 0x96, 0x05, 0x49, 0x0e, 0x26, 0xb6, 0xd7, 0xaa, 0xcb, 0x7f, - 0xde, 0x61, 0xdb, 0xac, 0x1d, 0xa4, 0x09, 0x69, 0xac, 0x91, 0xdb, 0x48, 0xd6, 0xaa, - 0x5b, 0x23, 0xb9, 0x1c, + 0x72, 0xad, 0x4e, 0x78, 0x4f, 0xa5, 0x36, 0x84, 0x4c, 0x45, 0xf6, 0xf4, 0xb6, 0x10, + 0x47, 0x9f, 0xc8, 0xfa, 0x1d, 0x10, 0x7c, 0x4d, 0x9d, 0xf0, 0x02, 0x54, 0xe2, 0x13, + 0x93, 0x48, 0xf2, 0x16, ], prep_root: [ - 0x67, 0xb6, 0x24, 0xb7, 0x38, 0x62, 0xfb, 0x62, 0x9d, 0x64, 0x01, 0x95, 0x07, 0xd1, - 0x05, 0xa5, 0x30, 0x8a, 0xd8, 0x7a, 0xdb, 0x50, 0x10, 0xdb, 0x4e, 0x2c, 0x94, 0x0c, - 0x3d, 0xd4, 0xe1, 0x58, + 0x23, 0x57, 0x15, 0x53, 0x9b, 0xdb, 0xf1, 0x9e, 0x9e, 0x6f, 0x9b, 0xce, 0x1d, 0x51, + 0x9e, 0x57, 0x28, 0x28, 0x47, 0x36, 0x03, 0x3b, 0x0b, 0x78, 0xd9, 0xdb, 0x7b, 0x1b, + 0x80, 0x2b, 0xf9, 0xac, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, @@ -898,64 +898,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0x70, 0xac, 0x06, 0xa6, 0xd5, 0xad, 0x1b, 0xa7, 0xdf, 0xa1, 0xea, 0x71, 0x28, 0x70, - 0x1b, 0xc8, 0xa4, 0xc5, 0xc3, 0xe4, 0x53, 0x23, 0x4a, 0x30, 0x7c, 0x6d, 0x73, 0x4b, - 0x88, 0xa1, 0x0f, 0x43, + 0x9a, 0x59, 0x5c, 0x71, 0xd1, 0xa7, 0x6a, 0xf8, 0x76, 0x0a, 0x56, 0x5f, 0x3d, 0xf5, + 0x87, 0x9f, 0x71, 0x48, 0xf0, 0xf0, 0xd3, 0x49, 0x38, 0x5c, 0x2b, 0x9a, 0x6d, 0x05, + 0x91, 0x18, 0x93, 0x0d, ], [ - 0xfb, 0x9a, 0x60, 0xbc, 0x12, 0xeb, 0x89, 0x64, 0xf8, 0x55, 0xf3, 0xc7, 0x63, 0xc0, - 0x7c, 0x6a, 0x4b, 0x4c, 0x96, 0xd3, 0x54, 0xfe, 0x10, 0xcb, 0x0a, 0x50, 0xb7, 0x13, - 0xe2, 0xbf, 0x9f, 0x89, + 0xed, 0x75, 0x5c, 0x0a, 0x8b, 0x67, 0x41, 0x15, 0x9c, 0x13, 0xdd, 0xcc, 0xbb, 0x80, + 0x34, 0x20, 0xf9, 0x11, 0x8e, 0x74, 0x4b, 0xe2, 0x4c, 0xc7, 0x79, 0xe0, 0x4f, 0x4d, + 0x76, 0x31, 0x09, 0xd0, ], [ - 0x9f, 0xab, 0xcd, 0xbd, 0x46, 0xd9, 0x37, 0x10, 0x80, 0x58, 0xb4, 0x00, 0x7f, 0x72, - 0x84, 0x15, 0x7f, 0x46, 0xde, 0xc1, 0x09, 0x7a, 0xd9, 0xe5, 0x72, 0xac, 0xae, 0xf0, - 0xfd, 0xf4, 0xc9, 0xb1, + 0x20, 0x8b, 0x3c, 0x06, 0x1e, 0x1c, 0xf4, 0x7a, 0xe3, 0x6b, 0xb6, 0xf3, 0x62, 0x1e, + 0xda, 0x7e, 0x0e, 0x5a, 0x1c, 0x7c, 0x77, 0x4d, 0xf5, 0x1e, 0xb9, 0xd1, 0xad, 0x27, + 0xed, 0xac, 0xba, 0x44, ], [ - 0x63, 0x15, 0x10, 0x60, 0x57, 0x2c, 0xc4, 0x75, 0xb2, 0x86, 0xee, 0x3e, 0x39, 0x3a, - 0xf4, 0xd5, 0x1d, 0xe5, 0x98, 0x6b, 0xe3, 0x22, 0xbd, 0xc5, 0x4e, 0x6f, 0xf9, 0x31, - 0x45, 0xe3, 0x38, 0xd8, + 0x62, 0x88, 0x6d, 0xac, 0x61, 0xce, 0x6f, 0x89, 0x64, 0x26, 0x67, 0x3d, 0xcb, 0x77, + 0x0a, 0xbb, 0x85, 0x53, 0x99, 0xf4, 0x09, 0xc0, 0x33, 0x1b, 0xe6, 0xf7, 0x93, 0x65, + 0x17, 0x7f, 0x15, 0x5c, ], [ - 0xcf, 0xe1, 0xf7, 0xb5, 0xfa, 0x6a, 0xbe, 0xec, 0x67, 0x88, 0xc2, 0xc4, 0x0d, 0x65, - 0xab, 0x7b, 0x94, 0x09, 0x21, 0xcf, 0x71, 0x22, 0x4a, 0xc6, 0x20, 0x96, 0x2d, 0x7d, - 0x50, 0x22, 0xa3, 0xf9, + 0xd9, 0xe6, 0xaf, 0xf7, 0x07, 0x25, 0xaa, 0x8c, 0x15, 0x0f, 0x41, 0x3e, 0xd5, 0x46, + 0x13, 0x06, 0x70, 0x75, 0x63, 0x1b, 0xc0, 0x65, 0x98, 0x0f, 0xa9, 0x25, 0x2e, 0x5f, + 0xdc, 0x62, 0xeb, 0x65, ], [ - 0xfa, 0xf3, 0x6c, 0x05, 0x83, 0x57, 0xa3, 0x6b, 0x72, 0x33, 0x48, 0x07, 0xba, 0xa7, - 0x86, 0x78, 0x1a, 0xa4, 0xcb, 0xe6, 0x49, 0xf4, 0x74, 0x20, 0xe3, 0x49, 0xcb, 0x0e, - 0x18, 0x91, 0xab, 0x72, + 0x7f, 0x87, 0x55, 0xc5, 0x81, 0x3b, 0xeb, 0x94, 0x20, 0x1d, 0xf1, 0xe5, 0x17, 0x93, + 0x1a, 0xe2, 0xe9, 0x46, 0x58, 0xb6, 0x00, 0x04, 0x99, 0xb1, 0x54, 0xdc, 0xa2, 0x7a, + 0xa8, 0xf4, 0xe3, 0xa4, ], [ - 0x80, 0xe6, 0x2e, 0x9d, 0xac, 0x64, 0x93, 0x41, 0xae, 0x34, 0x6a, 0xba, 0xaa, 0x9a, - 0x1f, 0x49, 0x0c, 0x3f, 0xfd, 0xd3, 0x20, 0x6c, 0x97, 0xdc, 0xcd, 0xc1, 0x95, 0x99, - 0x6f, 0xe5, 0x6b, 0x70, + 0x8d, 0xa4, 0x49, 0x54, 0x80, 0xa3, 0xfe, 0x4d, 0x2f, 0xb1, 0xe0, 0x1d, 0xd9, 0x6b, + 0x82, 0xa6, 0x65, 0x36, 0x1f, 0x21, 0xf9, 0x92, 0x67, 0x4f, 0x88, 0x90, 0x70, 0xab, + 0xff, 0x31, 0xec, 0xb2, ], [ - 0xf7, 0x6a, 0x87, 0xdd, 0xf9, 0x66, 0xfc, 0x2f, 0x04, 0x98, 0x52, 0x2a, 0x46, 0xfe, - 0x7c, 0x21, 0x6a, 0x41, 0xc3, 0x7d, 0x29, 0xdd, 0xae, 0xf6, 0xb5, 0xaa, 0x67, 0x27, - 0x2b, 0x76, 0x82, 0x0d, + 0x96, 0x15, 0x02, 0xab, 0x70, 0xc5, 0x29, 0x42, 0xe0, 0xa6, 0xd7, 0xe6, 0x8c, 0x4e, + 0x0e, 0xf6, 0x1f, 0x28, 0x9b, 0xc5, 0xfa, 0x61, 0xa5, 0xc6, 0x96, 0x3a, 0xf1, 0xd4, + 0xe6, 0x91, 0xe5, 0x2b, ], [ - 0xc5, 0x38, 0x40, 0x49, 0xab, 0xb6, 0xa4, 0xb0, 0x38, 0x08, 0x83, 0x85, 0x42, 0x03, - 0x59, 0x34, 0xb4, 0x6d, 0x52, 0xa7, 0x5e, 0xf2, 0x53, 0x93, 0x6a, 0xcf, 0x9b, 0x6f, - 0x28, 0x57, 0x45, 0xe5, + 0xd2, 0x8e, 0xe0, 0xb7, 0x30, 0x2f, 0xc0, 0xf3, 0x1c, 0x75, 0xe3, 0x19, 0x08, 0x9c, + 0xd3, 0x00, 0x7b, 0x8c, 0xbe, 0x40, 0x85, 0xad, 0xf6, 0xbe, 0xb8, 0xc6, 0x2f, 0x46, + 0x8f, 0xfd, 0x03, 0xcd, ], [ - 0x83, 0xdb, 0xad, 0xc4, 0x15, 0x82, 0x82, 0x91, 0x87, 0x33, 0xf4, 0xe2, 0x34, 0x96, - 0xa6, 0xad, 0x5c, 0x29, 0xa9, 0xe4, 0x02, 0x11, 0x47, 0x4e, 0xfc, 0x76, 0x4d, 0xb2, - 0x63, 0x8f, 0xdc, 0x06, + 0x51, 0x61, 0x61, 0x29, 0x2f, 0x64, 0x86, 0xf3, 0x77, 0x19, 0x29, 0xae, 0x32, 0x5d, + 0x7e, 0xbb, 0xe2, 0x8a, 0x8d, 0x56, 0x27, 0x43, 0x1e, 0xf3, 0xd2, 0x3b, 0x82, 0x79, + 0xec, 0x71, 0x01, 0x6f, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x5b, 0x89, 0xe1, 0x60, 0xe1, 0xcf, 0x68, 0x81, 0xd2, 0xeb, 0x40, 0x21, 0x62, 0x5f, - 0x7a, 0x76, 0x96, 0x9e, 0xc8, 0x0b, 0x01, 0x3f, 0x0a, 0xfb, 0x3f, 0x24, 0x73, 0xcd, - 0x55, 0xd3, 0xea, 0x27, + 0x4b, 0xd7, 0x50, 0x10, 0xec, 0x2d, 0xfb, 0x77, 0xbd, 0xdd, 0x72, 0x55, 0xec, 0xb4, + 0x35, 0x03, 0xb1, 0x99, 0x66, 0xac, 0xe9, 0x33, 0xe6, 0xcc, 0x08, 0xae, 0xba, 0x7c, + 0x54, 0xdb, 0x18, 0x9c, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -981,14 +981,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0x7e, 0x33, 0xb7, 0xb4, 0xd0, 0x9a, 0xbc, 0xcb, 0xd8, 0xd5, 0xae, 0x44, 0x47, 0xd4, - 0x7a, 0x04, 0x53, 0xd2, 0x29, 0xd8, 0xcd, 0xe8, 0x36, 0x86, 0x8f, 0x7f, 0x1a, 0x83, - 0xed, 0x0c, 0x24, 0xe1, + 0xcf, 0x55, 0x15, 0x9a, 0x28, 0xd4, 0xd1, 0x60, 0x3f, 0x2d, 0x38, 0x2a, 0x86, 0x1a, + 0x58, 0x75, 0x6c, 0x91, 0x18, 0xea, 0x60, 0xf3, 0x27, 0x5a, 0xec, 0xb9, 0xe8, 0x3b, + 0xc8, 0x34, 0xff, 0x18, ], prep_root: [ - 0x26, 0xc7, 0x59, 0x52, 0x12, 0x62, 0x93, 0x9d, 0xb0, 0x2c, 0x6c, 0xe2, 0x1c, 0xa7, - 0x14, 0xa7, 0x08, 0xf6, 0x77, 0x06, 0x7c, 0xfd, 0xdf, 0xce, 0xee, 0x88, 0xfd, 0xf3, - 0xcc, 0xda, 0x67, 0x5d, + 0x81, 0x31, 0x40, 0x97, 0xdc, 0x51, 0x37, 0x09, 0x39, 0x04, 0x60, 0x51, 0xe7, 0x3c, + 0x35, 0x58, 0x21, 0x69, 0xdd, 0x0e, 0x5f, 0xbf, 0x0f, 0x69, 0x1d, 0xb4, 0xff, 0x7a, + 0xae, 0x80, 0x43, 0x5c, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, @@ -997,64 +997,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0xff, 0x4b, 0xdf, 0x1d, 0x97, 0x16, 0xf4, 0xea, 0x96, 0x06, 0xb1, 0x02, 0xe2, 0xb9, - 0xdf, 0x8e, 0xb0, 0x65, 0x3f, 0x64, 0x7b, 0xe5, 0x44, 0x92, 0x15, 0x61, 0x44, 0x31, - 0x0f, 0x55, 0x00, 0x9b, + 0xe1, 0xc3, 0x27, 0x68, 0xac, 0x08, 0x46, 0xd6, 0x86, 0xeb, 0xae, 0x4c, 0xff, 0x10, + 0x92, 0x9e, 0x5c, 0xcc, 0x6a, 0xdd, 0xd5, 0xcd, 0xf2, 0xce, 0x5c, 0x1e, 0x60, 0x7d, + 0x6f, 0xd1, 0x77, 0x33, ], [ - 0xfe, 0xd5, 0xf3, 0x81, 0xda, 0x69, 0x9d, 0x2a, 0xad, 0x4f, 0x07, 0x3a, 0xf5, 0x16, - 0xdf, 0xc0, 0x5f, 0x87, 0x08, 0x4a, 0x64, 0x91, 0x48, 0xc7, 0x16, 0xcb, 0xe8, 0x73, - 0x8d, 0x94, 0x28, 0xd5, + 0x31, 0xf9, 0x22, 0xf3, 0x9d, 0xf1, 0xcb, 0xd0, 0xeb, 0x62, 0x25, 0x93, 0xea, 0x75, + 0x43, 0x30, 0x8b, 0xb1, 0x02, 0x45, 0x20, 0xde, 0xf9, 0x17, 0x78, 0x17, 0x27, 0xc6, + 0xc4, 0x9a, 0x99, 0x28, ], [ - 0x03, 0x45, 0x83, 0x23, 0x1a, 0xc2, 0xf0, 0x18, 0x7f, 0xd6, 0x9e, 0xb6, 0x94, 0x3b, - 0xdb, 0xd2, 0x5f, 0x6b, 0x2f, 0x9f, 0x74, 0x1a, 0xa5, 0x7e, 0x8e, 0x2a, 0x45, 0x99, - 0xc5, 0x92, 0x2b, 0x13, + 0x07, 0xdb, 0x70, 0x37, 0x6d, 0xff, 0x1c, 0x50, 0x7f, 0x82, 0xf7, 0x83, 0x73, 0x7c, + 0x2e, 0xad, 0xab, 0x48, 0xbc, 0x68, 0x82, 0xf7, 0xff, 0x66, 0x80, 0x88, 0xe9, 0x2c, + 0xae, 0x17, 0x71, 0x37, ], [ - 0x78, 0xd1, 0xac, 0x7d, 0xcc, 0x52, 0x49, 0x8e, 0x04, 0x25, 0x96, 0xa6, 0x28, 0xfa, - 0x63, 0x7b, 0xfe, 0x0b, 0xbb, 0xa7, 0xff, 0xbf, 0x4e, 0x71, 0x08, 0x2b, 0x29, 0xf4, - 0x9b, 0xfa, 0xab, 0x84, + 0x54, 0xb6, 0x8d, 0xc0, 0x91, 0x2b, 0x77, 0xc9, 0xeb, 0x5e, 0xe5, 0x1a, 0x1d, 0x13, + 0x91, 0x0a, 0x95, 0xdb, 0xec, 0x96, 0x87, 0x73, 0x43, 0x0c, 0x3d, 0xf9, 0xe6, 0x5d, + 0x80, 0xee, 0x1d, 0xd5, ], [ - 0xbd, 0xa4, 0x7d, 0xf2, 0x23, 0x60, 0x44, 0x08, 0xda, 0x87, 0xe5, 0xc6, 0x34, 0xea, - 0xf1, 0xab, 0x25, 0x6c, 0x45, 0xca, 0x50, 0xd0, 0x3f, 0x6a, 0x65, 0x15, 0xb6, 0x25, - 0x80, 0x89, 0xb6, 0xe4, + 0x16, 0x7c, 0xec, 0xa5, 0x7c, 0x2b, 0x9b, 0x02, 0x25, 0x0b, 0x93, 0x86, 0xdc, 0xe0, + 0x05, 0x81, 0xa3, 0x67, 0xf4, 0xbe, 0xd3, 0x4e, 0x06, 0x78, 0xae, 0xb6, 0x52, 0x05, + 0xff, 0xdd, 0xfb, 0xd5, ], [ - 0x8b, 0xac, 0x90, 0x86, 0xc2, 0x4a, 0xed, 0xe7, 0x89, 0xa8, 0x0f, 0x5c, 0x26, 0x4a, - 0x0f, 0x3f, 0x6c, 0xb2, 0x4c, 0xe7, 0x87, 0x31, 0x1b, 0xd7, 0xe7, 0x3d, 0xce, 0xd6, - 0x49, 0x3e, 0xd3, 0xe6, + 0x7d, 0x25, 0xc1, 0xee, 0x40, 0x2b, 0x03, 0x6b, 0xf9, 0x14, 0x9a, 0xa3, 0x50, 0x04, + 0xb8, 0x62, 0x5f, 0x24, 0x5b, 0x6d, 0x11, 0x4b, 0x36, 0xa9, 0xea, 0x0b, 0xd0, 0x85, + 0x58, 0xd8, 0x02, 0xf5, ], [ - 0x46, 0xc6, 0xfc, 0x88, 0xd0, 0xc0, 0x51, 0xec, 0x08, 0x58, 0x81, 0xae, 0xa8, 0x23, - 0xc1, 0xb8, 0x3f, 0x57, 0xb5, 0x55, 0xfb, 0xe0, 0x67, 0xfb, 0x18, 0x54, 0x3b, 0x95, - 0x25, 0x87, 0xe6, 0x15, + 0xfc, 0x62, 0x00, 0xa7, 0x23, 0x66, 0x4f, 0x48, 0xcf, 0xc1, 0x4b, 0xd8, 0xf7, 0x3a, + 0x4c, 0x77, 0x05, 0xe4, 0x5c, 0x49, 0x3c, 0x9f, 0x95, 0x3d, 0x5e, 0xbe, 0x60, 0x95, + 0x38, 0x8e, 0x26, 0xfc, ], [ - 0xa2, 0xf9, 0x94, 0x17, 0x1f, 0xb2, 0x5f, 0x6c, 0x9d, 0x29, 0x94, 0xb7, 0xc2, 0x7f, - 0x6b, 0x1d, 0x6a, 0xe7, 0xad, 0x66, 0x5b, 0x41, 0xa7, 0xd1, 0xf2, 0x53, 0x80, 0xa6, - 0x34, 0x5d, 0x27, 0x87, + 0x9b, 0xd2, 0x23, 0x26, 0x3c, 0xa7, 0x25, 0xb3, 0xc1, 0x3c, 0x18, 0xe9, 0x9c, 0xb2, + 0x28, 0xdb, 0x4f, 0x29, 0x58, 0xac, 0xdb, 0x52, 0x58, 0xe8, 0x23, 0x2e, 0xb0, 0x8b, + 0x0d, 0xd5, 0xed, 0x41, ], [ - 0x6f, 0x6c, 0x7b, 0xdf, 0xd5, 0x99, 0xb3, 0xa8, 0x7f, 0x1a, 0x1a, 0x07, 0x00, 0x5c, - 0xe0, 0xa1, 0x77, 0x77, 0x1a, 0x68, 0xea, 0x04, 0x24, 0xff, 0x55, 0x35, 0xb8, 0x1a, - 0x76, 0xfd, 0x4b, 0x83, + 0x2f, 0x2d, 0xfa, 0xe3, 0x1c, 0xbe, 0x00, 0xe3, 0xdc, 0x4a, 0xa5, 0x48, 0xc6, 0x72, + 0x28, 0x7b, 0x26, 0x05, 0x9a, 0x42, 0xf3, 0x3c, 0x72, 0xb0, 0xbe, 0x5f, 0x73, 0x47, + 0x00, 0x0e, 0xf3, 0xeb, ], [ - 0xe0, 0x47, 0x17, 0x6f, 0x21, 0xe3, 0x91, 0x8d, 0x5d, 0x4b, 0x56, 0xb0, 0x5b, 0x31, - 0x4d, 0x8f, 0x3d, 0x8e, 0xd1, 0x4d, 0xc8, 0x5c, 0xb4, 0x2b, 0x38, 0xc6, 0x9a, 0x4d, - 0x2a, 0x53, 0x36, 0xc3, + 0x9a, 0x39, 0xe1, 0x95, 0x0c, 0x1b, 0x7a, 0x36, 0x16, 0xfc, 0xb5, 0xde, 0xf0, 0x2f, + 0xac, 0x61, 0x3b, 0x32, 0xa3, 0xe9, 0x4a, 0x63, 0xfe, 0x66, 0x59, 0x08, 0x5b, 0x22, + 0xe3, 0x87, 0xab, 0x5c, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x5b, 0x89, 0xe1, 0x60, 0xe1, 0xcf, 0x68, 0x81, 0xd2, 0xeb, 0x40, 0x21, 0x62, 0x5f, - 0x7a, 0x76, 0x96, 0x9e, 0xc8, 0x0b, 0x01, 0x3f, 0x0a, 0xfb, 0x3f, 0x24, 0x73, 0xcd, - 0x55, 0xd3, 0xea, 0x27, + 0x4b, 0xd7, 0x50, 0x10, 0xec, 0x2d, 0xfb, 0x77, 0xbd, 0xdd, 0x72, 0x55, 0xec, 0xb4, + 0x35, 0x03, 0xb1, 0x99, 0x66, 0xac, 0xe9, 0x33, 0xe6, 0xcc, 0x08, 0xae, 0xba, 0x7c, + 0x54, 0xdb, 0x18, 0x9c, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1080,14 +1080,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0xb5, 0x6b, 0xe4, 0x22, 0xcb, 0xc2, 0x5f, 0x19, 0x0b, 0xb8, 0x11, 0xd0, 0xfa, 0x5d, - 0x36, 0xae, 0x7a, 0x40, 0xb0, 0xd8, 0x73, 0xbd, 0x83, 0x10, 0x68, 0x1b, 0xf2, 0x04, - 0xe4, 0x31, 0x71, 0x97, + 0x3f, 0x12, 0xac, 0x61, 0xde, 0x5a, 0x8a, 0x02, 0xa9, 0xb3, 0x32, 0xa8, 0x16, 0xa1, + 0x28, 0xa0, 0xd1, 0x75, 0x3f, 0x4f, 0x64, 0xa4, 0x51, 0x8c, 0x37, 0x39, 0xa6, 0x3f, + 0x0d, 0x97, 0xda, 0xf9, ], prep_root: [ - 0x32, 0x84, 0x97, 0xf3, 0x4e, 0x2c, 0x81, 0xd6, 0x0c, 0x22, 0xa6, 0x5f, 0xc6, 0xe5, - 0x52, 0xc5, 0xe2, 0x5a, 0x79, 0xfe, 0xd7, 0xc1, 0x9b, 0x4e, 0x9c, 0xe8, 0x2c, 0xe6, - 0x0e, 0x5b, 0x74, 0x63, + 0x17, 0xb9, 0x2d, 0x29, 0xbd, 0x27, 0x65, 0xb1, 0x9f, 0xb3, 0xe7, 0x4e, 0x89, 0xb8, + 0x89, 0x66, 0xc6, 0xd1, 0xc5, 0x63, 0x0f, 0x8f, 0x12, 0x0b, 0x4e, 0xff, 0x73, 0x86, + 0x1f, 0x03, 0xf2, 0x5b, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, @@ -1096,64 +1096,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0xb0, 0xf7, 0xa5, 0x7c, 0x6f, 0xd3, 0x93, 0xd2, 0xd4, 0xc2, 0x23, 0x68, 0x02, 0x9b, - 0xa1, 0xa6, 0x76, 0x27, 0x91, 0x9b, 0xc8, 0x82, 0xba, 0xfb, 0x98, 0xb0, 0x13, 0x48, - 0x76, 0x86, 0x23, 0x9a, + 0xc7, 0xa2, 0x00, 0xa8, 0x75, 0xaa, 0x69, 0x79, 0xe0, 0x0a, 0x38, 0x87, 0x0a, 0xb4, + 0x1b, 0x04, 0x7a, 0x8c, 0xa0, 0x57, 0x10, 0x3f, 0xbc, 0xb8, 0x28, 0xa8, 0x2c, 0xc8, + 0x53, 0x41, 0x3b, 0xf5, ], [ - 0x6d, 0x44, 0x6e, 0x89, 0x9c, 0xb6, 0xb0, 0x08, 0x0d, 0x3d, 0x95, 0x93, 0xd1, 0xa3, - 0x5a, 0x92, 0xd8, 0x11, 0x45, 0x20, 0xbc, 0x34, 0x82, 0xfe, 0xa9, 0x2e, 0x3d, 0x50, - 0xe6, 0x6f, 0xfc, 0xae, + 0x13, 0xf7, 0xa3, 0xbe, 0x73, 0x3b, 0xec, 0x61, 0xcf, 0xd6, 0xc1, 0x69, 0x8c, 0xef, + 0x90, 0x3a, 0x89, 0x8b, 0x76, 0xeb, 0x9f, 0x0d, 0x1d, 0x87, 0x7d, 0xe5, 0xac, 0x22, + 0x82, 0xef, 0x4f, 0xd8, ], [ - 0x03, 0x45, 0x83, 0x23, 0x1a, 0xc2, 0xf0, 0x18, 0x7f, 0xd6, 0x9e, 0xb6, 0x94, 0x3b, - 0xdb, 0xd2, 0x5f, 0x6b, 0x2f, 0x9f, 0x74, 0x1a, 0xa5, 0x7e, 0x8e, 0x2a, 0x45, 0x99, - 0xc5, 0x92, 0x2b, 0x13, + 0x07, 0xdb, 0x70, 0x37, 0x6d, 0xff, 0x1c, 0x50, 0x7f, 0x82, 0xf7, 0x83, 0x73, 0x7c, + 0x2e, 0xad, 0xab, 0x48, 0xbc, 0x68, 0x82, 0xf7, 0xff, 0x66, 0x80, 0x88, 0xe9, 0x2c, + 0xae, 0x17, 0x71, 0x37, ], [ - 0x78, 0xd1, 0xac, 0x7d, 0xcc, 0x52, 0x49, 0x8e, 0x04, 0x25, 0x96, 0xa6, 0x28, 0xfa, - 0x63, 0x7b, 0xfe, 0x0b, 0xbb, 0xa7, 0xff, 0xbf, 0x4e, 0x71, 0x08, 0x2b, 0x29, 0xf4, - 0x9b, 0xfa, 0xab, 0x84, + 0x54, 0xb6, 0x8d, 0xc0, 0x91, 0x2b, 0x77, 0xc9, 0xeb, 0x5e, 0xe5, 0x1a, 0x1d, 0x13, + 0x91, 0x0a, 0x95, 0xdb, 0xec, 0x96, 0x87, 0x73, 0x43, 0x0c, 0x3d, 0xf9, 0xe6, 0x5d, + 0x80, 0xee, 0x1d, 0xd5, ], [ - 0xbd, 0xa4, 0x7d, 0xf2, 0x23, 0x60, 0x44, 0x08, 0xda, 0x87, 0xe5, 0xc6, 0x34, 0xea, - 0xf1, 0xab, 0x25, 0x6c, 0x45, 0xca, 0x50, 0xd0, 0x3f, 0x6a, 0x65, 0x15, 0xb6, 0x25, - 0x80, 0x89, 0xb6, 0xe4, + 0x16, 0x7c, 0xec, 0xa5, 0x7c, 0x2b, 0x9b, 0x02, 0x25, 0x0b, 0x93, 0x86, 0xdc, 0xe0, + 0x05, 0x81, 0xa3, 0x67, 0xf4, 0xbe, 0xd3, 0x4e, 0x06, 0x78, 0xae, 0xb6, 0x52, 0x05, + 0xff, 0xdd, 0xfb, 0xd5, ], [ - 0x8b, 0xac, 0x90, 0x86, 0xc2, 0x4a, 0xed, 0xe7, 0x89, 0xa8, 0x0f, 0x5c, 0x26, 0x4a, - 0x0f, 0x3f, 0x6c, 0xb2, 0x4c, 0xe7, 0x87, 0x31, 0x1b, 0xd7, 0xe7, 0x3d, 0xce, 0xd6, - 0x49, 0x3e, 0xd3, 0xe6, + 0x7d, 0x25, 0xc1, 0xee, 0x40, 0x2b, 0x03, 0x6b, 0xf9, 0x14, 0x9a, 0xa3, 0x50, 0x04, + 0xb8, 0x62, 0x5f, 0x24, 0x5b, 0x6d, 0x11, 0x4b, 0x36, 0xa9, 0xea, 0x0b, 0xd0, 0x85, + 0x58, 0xd8, 0x02, 0xf5, ], [ - 0xf9, 0x99, 0x84, 0x64, 0x5f, 0x19, 0xc7, 0x85, 0x09, 0x04, 0xa0, 0x3b, 0x36, 0x8f, - 0xab, 0xd8, 0xda, 0x0a, 0x67, 0xd8, 0x92, 0x7e, 0x0b, 0xd3, 0x89, 0xf4, 0xff, 0xab, - 0x79, 0x43, 0xe6, 0x6b, + 0xde, 0x04, 0x16, 0x1b, 0x9a, 0x9a, 0x84, 0x02, 0xd9, 0x21, 0x50, 0xa3, 0x6b, 0x62, + 0xb6, 0x0a, 0x0e, 0x69, 0x9d, 0x6f, 0xe3, 0xea, 0x80, 0x0d, 0x0a, 0x9b, 0x76, 0xee, + 0x82, 0x89, 0x46, 0xf2, ], [ - 0xd2, 0x56, 0xf8, 0xf5, 0x6b, 0x34, 0xaf, 0xf5, 0xa6, 0x1d, 0xf5, 0xe2, 0x8a, 0x4a, - 0x99, 0xef, 0xed, 0xf2, 0x55, 0x7c, 0xdb, 0xf3, 0xdd, 0x80, 0x5c, 0x09, 0x55, 0xd6, - 0x47, 0x75, 0xbc, 0x4f, + 0x5e, 0x14, 0xfe, 0xa2, 0x64, 0x4f, 0x4a, 0x70, 0xbf, 0x0e, 0x0d, 0x93, 0x09, 0xd8, + 0xb4, 0xbb, 0xba, 0xd1, 0xe6, 0x91, 0x4a, 0x71, 0x7b, 0xec, 0x11, 0xf5, 0x37, 0x3b, + 0x3f, 0xe9, 0xe8, 0x5a, ], [ - 0x4d, 0x97, 0x51, 0x99, 0xc3, 0xc6, 0x4e, 0x95, 0xa1, 0xc3, 0xb8, 0xcc, 0x2e, 0x18, - 0x29, 0xfc, 0x24, 0xd6, 0xf7, 0x97, 0x30, 0x6f, 0x83, 0xab, 0x0b, 0x69, 0x23, 0xd9, - 0x58, 0x46, 0xbe, 0xb3, + 0x5a, 0xa5, 0xed, 0xac, 0xa3, 0x06, 0xbb, 0x97, 0x08, 0xc3, 0xe3, 0x1e, 0x0d, 0x8c, + 0x00, 0x1d, 0xed, 0xd2, 0x6e, 0x49, 0x6d, 0xd9, 0xc7, 0x85, 0xd6, 0xd4, 0xd6, 0xbf, + 0x83, 0x2d, 0x6d, 0xb9, ], [ - 0xce, 0xa8, 0x2c, 0x02, 0x28, 0x09, 0xb9, 0xdb, 0xc9, 0x9c, 0xe3, 0x56, 0x3b, 0xde, - 0xad, 0x9b, 0x5d, 0x87, 0x02, 0xed, 0xa6, 0x33, 0x14, 0x17, 0xb2, 0x86, 0xf6, 0xb1, - 0x93, 0xea, 0xdc, 0xef, + 0xec, 0x03, 0x54, 0x70, 0x9b, 0x7f, 0x21, 0x5b, 0xf9, 0x48, 0x5c, 0xd8, 0x64, 0xa8, + 0xb5, 0x2b, 0x6e, 0x22, 0x79, 0x74, 0x50, 0x93, 0xbb, 0x21, 0x04, 0x53, 0x80, 0xd7, + 0x99, 0x26, 0x5d, 0xd3, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x5b, 0x89, 0xe1, 0x60, 0xe1, 0xcf, 0x68, 0x81, 0xd2, 0xeb, 0x40, 0x21, 0x62, 0x5f, - 0x7a, 0x76, 0x96, 0x9e, 0xc8, 0x0b, 0x01, 0x3f, 0x0a, 0xfb, 0x3f, 0x24, 0x73, 0xcd, - 0x55, 0xd3, 0xea, 0x27, + 0x4b, 0xd7, 0x50, 0x10, 0xec, 0x2d, 0xfb, 0x77, 0xbd, 0xdd, 0x72, 0x55, 0xec, 0xb4, + 0x35, 0x03, 0xb1, 0x99, 0x66, 0xac, 0xe9, 0x33, 0xe6, 0xcc, 0x08, 0xae, 0xba, 0x7c, + 0x54, 0xdb, 0x18, 0x9c, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1179,14 +1179,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0xb9, 0xf8, 0xcf, 0x8a, 0xcb, 0x89, 0x30, 0xc9, 0xe9, 0xfd, 0x1c, 0xc7, 0x5d, 0x7e, - 0xac, 0x51, 0x66, 0x66, 0x6c, 0x74, 0xb6, 0x46, 0x67, 0x94, 0x55, 0xbd, 0x3a, 0x04, - 0x5a, 0x60, 0x29, 0x8f, + 0x3d, 0x84, 0x8a, 0x1b, 0x5d, 0xd3, 0x8e, 0x99, 0x67, 0xc6, 0x08, 0xb6, 0x81, 0x9d, + 0x4e, 0x16, 0x47, 0x26, 0x84, 0xe1, 0x3a, 0xef, 0xd2, 0x4e, 0xb7, 0xc1, 0x36, 0x92, + 0x59, 0x52, 0xf8, 0xa6, ], prep_root: [ - 0xd4, 0x11, 0x7a, 0x0a, 0x76, 0x8a, 0xb9, 0x41, 0xcc, 0xf2, 0x3d, 0xa8, 0x1a, 0x06, - 0x35, 0x72, 0x70, 0x10, 0xc2, 0x12, 0x9f, 0x7f, 0xd9, 0x8e, 0x22, 0x12, 0x8c, 0x47, - 0x07, 0xf0, 0xae, 0x7f, + 0xa6, 0x14, 0xdf, 0x60, 0xda, 0x68, 0x8c, 0xfc, 0x67, 0x5d, 0x4b, 0x31, 0xaa, 0xce, + 0xa4, 0x82, 0x1e, 0xf0, 0xfb, 0x02, 0x08, 0xf4, 0x0e, 0x4b, 0xd4, 0x6f, 0xba, 0x2e, + 0x85, 0x07, 0xb8, 0xe2, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, @@ -1195,64 +1195,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0x04, 0x22, 0x0e, 0xfa, 0xe5, 0xda, 0xac, 0xb8, 0x36, 0x5f, 0xe8, 0x15, 0xbb, 0xa7, - 0x04, 0xff, 0x25, 0x84, 0x0b, 0x22, 0x3b, 0xda, 0xf7, 0x13, 0xab, 0x56, 0x5e, 0x50, - 0x53, 0x2d, 0x63, 0x45, + 0x05, 0xd2, 0x17, 0x2e, 0x98, 0xf4, 0x8e, 0xc9, 0xb2, 0x7e, 0xd4, 0xbc, 0xd9, 0x4a, + 0xba, 0x0d, 0x16, 0x43, 0x99, 0xcf, 0x6e, 0x13, 0xe6, 0xa5, 0x44, 0x4e, 0xda, 0x9d, + 0xfe, 0xb3, 0x6e, 0xed, ], [ - 0x93, 0xec, 0x2a, 0x0e, 0x5b, 0x45, 0x00, 0x32, 0x31, 0x49, 0x52, 0x1e, 0xd5, 0x0e, - 0x83, 0x9d, 0xd5, 0x7d, 0xb8, 0xe3, 0x1f, 0xe3, 0xf7, 0xa4, 0xa9, 0xa9, 0x00, 0x85, - 0x22, 0x2e, 0xe6, 0x05, + 0x89, 0x96, 0xd2, 0xab, 0x4d, 0xee, 0xd9, 0x5d, 0x70, 0x0b, 0xae, 0x67, 0xe9, 0xca, + 0xb1, 0xf4, 0xbf, 0x1b, 0x70, 0x6a, 0x58, 0x4c, 0xc0, 0xfe, 0x29, 0x05, 0xb8, 0xea, + 0x74, 0xd6, 0xb5, 0xfe, ], [ - 0x03, 0x45, 0x83, 0x23, 0x1a, 0xc2, 0xf0, 0x18, 0x7f, 0xd6, 0x9e, 0xb6, 0x94, 0x3b, - 0xdb, 0xd2, 0x5f, 0x6b, 0x2f, 0x9f, 0x74, 0x1a, 0xa5, 0x7e, 0x8e, 0x2a, 0x45, 0x99, - 0xc5, 0x92, 0x2b, 0x13, + 0x07, 0xdb, 0x70, 0x37, 0x6d, 0xff, 0x1c, 0x50, 0x7f, 0x82, 0xf7, 0x83, 0x73, 0x7c, + 0x2e, 0xad, 0xab, 0x48, 0xbc, 0x68, 0x82, 0xf7, 0xff, 0x66, 0x80, 0x88, 0xe9, 0x2c, + 0xae, 0x17, 0x71, 0x37, ], [ - 0xf7, 0x3a, 0x8e, 0x97, 0xe7, 0xd3, 0x89, 0xc7, 0x6e, 0xed, 0x62, 0x87, 0x7d, 0x85, - 0x82, 0x88, 0xdc, 0x5d, 0x45, 0xc8, 0x12, 0xe2, 0x2d, 0x1d, 0xb5, 0x82, 0x61, 0xb1, - 0x48, 0xb4, 0x97, 0xc6, + 0xc4, 0xed, 0x3f, 0xac, 0x6e, 0x05, 0x97, 0xc7, 0x01, 0x3c, 0xba, 0x47, 0x1a, 0x9e, + 0x0d, 0xfc, 0x71, 0x40, 0x55, 0x2d, 0xa8, 0xec, 0xa4, 0xee, 0xd2, 0x13, 0x17, 0xd9, + 0x7f, 0xb8, 0x62, 0x61, ], [ - 0x4a, 0x32, 0x91, 0x14, 0x1b, 0x2a, 0x59, 0x9e, 0xe1, 0x97, 0x2f, 0xff, 0x81, 0x63, - 0xbe, 0x90, 0x03, 0xda, 0x0f, 0xc3, 0x8a, 0x95, 0x3f, 0xb5, 0x58, 0x08, 0xda, 0x0b, - 0xb7, 0x64, 0xfb, 0xab, + 0x2a, 0x1f, 0xcd, 0x6c, 0x67, 0x77, 0xdc, 0x14, 0x3c, 0x7b, 0xb8, 0x48, 0x24, 0xdb, + 0xde, 0x29, 0x86, 0xaa, 0x0a, 0x0b, 0xe4, 0x82, 0xcd, 0x83, 0x67, 0xbe, 0xff, 0xbe, + 0xda, 0x77, 0x03, 0x1c, ], [ - 0x8b, 0xac, 0x90, 0x86, 0xc2, 0x4a, 0xed, 0xe7, 0x89, 0xa8, 0x0f, 0x5c, 0x26, 0x4a, - 0x0f, 0x3f, 0x6c, 0xb2, 0x4c, 0xe7, 0x87, 0x31, 0x1b, 0xd7, 0xe7, 0x3d, 0xce, 0xd6, - 0x49, 0x3e, 0xd3, 0xe6, + 0x7d, 0x25, 0xc1, 0xee, 0x40, 0x2b, 0x03, 0x6b, 0xf9, 0x14, 0x9a, 0xa3, 0x50, 0x04, + 0xb8, 0x62, 0x5f, 0x24, 0x5b, 0x6d, 0x11, 0x4b, 0x36, 0xa9, 0xea, 0x0b, 0xd0, 0x85, + 0x58, 0xd8, 0x02, 0xf5, ], [ - 0x80, 0xe6, 0x2e, 0x9d, 0xac, 0x64, 0x93, 0x41, 0xae, 0x34, 0x6a, 0xba, 0xaa, 0x9a, - 0x1f, 0x49, 0x0c, 0x3f, 0xfd, 0xd3, 0x20, 0x6c, 0x97, 0xdc, 0xcd, 0xc1, 0x95, 0x99, - 0x6f, 0xe5, 0x6b, 0x70, + 0x8d, 0xa4, 0x49, 0x54, 0x80, 0xa3, 0xfe, 0x4d, 0x2f, 0xb1, 0xe0, 0x1d, 0xd9, 0x6b, + 0x82, 0xa6, 0x65, 0x36, 0x1f, 0x21, 0xf9, 0x92, 0x67, 0x4f, 0x88, 0x90, 0x70, 0xab, + 0xff, 0x31, 0xec, 0xb2, ], [ - 0x06, 0x87, 0x09, 0xed, 0x6f, 0x37, 0xa4, 0x63, 0xed, 0x6e, 0x7d, 0xbb, 0x3b, 0x6c, - 0xca, 0x14, 0x0c, 0xe5, 0x32, 0xf5, 0x61, 0x6b, 0xb0, 0xe4, 0x51, 0x9c, 0x7c, 0x81, - 0x29, 0xad, 0x0d, 0x75, + 0xef, 0x55, 0xdd, 0x07, 0x83, 0xed, 0x34, 0xf5, 0xd0, 0xed, 0xd3, 0x97, 0x85, 0xf8, + 0x0c, 0x84, 0x2e, 0x58, 0xd2, 0x5b, 0xfc, 0x7f, 0x13, 0x5f, 0x80, 0x2f, 0xca, 0xd9, + 0x33, 0xb3, 0x4a, 0x79, ], [ - 0xd8, 0x44, 0x51, 0x6e, 0x0d, 0x66, 0xaf, 0x31, 0x9d, 0x5b, 0xd1, 0x15, 0xdf, 0x75, - 0x56, 0x35, 0xd2, 0x85, 0x9c, 0x22, 0xe2, 0x93, 0x96, 0x46, 0xc9, 0x0b, 0x4f, 0x1a, - 0xec, 0x20, 0x23, 0xcc, + 0xf5, 0x03, 0xc2, 0x09, 0xbd, 0xdd, 0x4a, 0x2c, 0x01, 0x55, 0xba, 0x0a, 0x7a, 0x8d, + 0xe6, 0x7c, 0xf7, 0xeb, 0x3a, 0x3e, 0x66, 0xb9, 0x55, 0x99, 0x8c, 0xab, 0x9b, 0x6f, + 0xde, 0x6a, 0xa9, 0x80, ], [ - 0x4f, 0x83, 0x48, 0x8c, 0x38, 0xb0, 0x33, 0x1f, 0x1e, 0xea, 0x4a, 0x30, 0x36, 0x4b, - 0xc7, 0x90, 0x55, 0x6d, 0x21, 0x5d, 0xf3, 0xc9, 0xd7, 0x30, 0xc0, 0x2a, 0x60, 0x5a, - 0x78, 0x32, 0x13, 0x32, + 0x58, 0x9a, 0x27, 0xbe, 0x91, 0xff, 0x77, 0x5d, 0x0c, 0xba, 0x66, 0x5d, 0x35, 0x7b, + 0xa0, 0x91, 0x86, 0x81, 0x5a, 0xf6, 0x84, 0x3b, 0xd1, 0x35, 0x66, 0x51, 0x91, 0xd6, + 0x63, 0x4e, 0xbd, 0xec, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x3a, 0x8f, 0xde, 0xb3, 0x01, 0x7d, 0x7b, 0xce, 0xc3, 0x96, 0xac, 0xb8, 0xf1, 0x55, - 0x6b, 0xe6, 0x62, 0x5f, 0xa0, 0xb1, 0x00, 0x0c, 0x4d, 0x48, 0xfd, 0x66, 0x94, 0xb4, - 0x42, 0x68, 0x42, 0x44, + 0x42, 0xd6, 0x1f, 0x87, 0x71, 0x67, 0xbf, 0xcf, 0x68, 0x1b, 0xb3, 0x7d, 0x5f, 0xfb, + 0x2e, 0x0f, 0x03, 0x3f, 0x1b, 0x4b, 0xbe, 0x5b, 0xb4, 0x01, 0x3e, 0x47, 0x88, 0x43, + 0x6e, 0xd9, 0xfc, 0xdd, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1278,14 +1278,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: true, }, program_id: [ - 0x36, 0xaf, 0x61, 0xc0, 0xec, 0x4b, 0x4e, 0x1e, 0xc0, 0xae, 0x1d, 0x11, 0x87, 0xbe, - 0xb6, 0xfb, 0xaf, 0x59, 0x85, 0xf4, 0xd6, 0x03, 0xc3, 0x23, 0x0e, 0x31, 0xdc, 0x0b, - 0x43, 0x46, 0xdf, 0x0e, + 0x49, 0x70, 0xaf, 0x9a, 0x21, 0x09, 0x26, 0x03, 0xe7, 0x80, 0x85, 0x84, 0xf8, 0x5f, + 0x4a, 0x5b, 0xdb, 0xd9, 0x1e, 0x88, 0x1a, 0x4a, 0x01, 0xc7, 0xe8, 0x05, 0xf5, 0x05, + 0xb9, 0x09, 0xb3, 0x5c, ], prep_root: [ - 0x80, 0xe1, 0x7a, 0x56, 0x9f, 0xc7, 0x1a, 0x5a, 0x59, 0x09, 0x88, 0x65, 0x2a, 0x45, - 0x22, 0xf3, 0x94, 0x53, 0x11, 0x52, 0x77, 0x88, 0x06, 0x24, 0x5f, 0x96, 0x06, 0x8a, - 0x6d, 0xb8, 0x33, 0x6b, + 0x77, 0x62, 0x5c, 0x36, 0x2d, 0xd7, 0xe8, 0xbf, 0xbf, 0x58, 0x2e, 0xdd, 0x42, 0x73, + 0x72, 0x7c, 0x5d, 0xf0, 0x74, 0x17, 0xb2, 0xdb, 0xa3, 0xbf, 0x11, 0x8f, 0x30, 0xfe, + 0x20, 0xab, 0x63, 0x4e, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, @@ -1294,64 +1294,64 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blowup_factor: 2, roots: [ [ - 0xab, 0xd6, 0xc8, 0xc9, 0x6c, 0x1b, 0x74, 0x41, 0xf1, 0xb4, 0x6f, 0xe5, 0xb5, 0x6e, - 0xef, 0xe4, 0xcd, 0x4c, 0x4a, 0x4a, 0x44, 0x4a, 0x04, 0xbd, 0x9c, 0x37, 0xbb, 0x3d, - 0x3f, 0xcd, 0x15, 0xb1, + 0x8e, 0x12, 0x29, 0x48, 0x9e, 0xac, 0x16, 0x2c, 0x45, 0x9a, 0x99, 0xbf, 0xde, 0x4a, + 0x9a, 0xd5, 0x57, 0x3c, 0xff, 0x61, 0xee, 0x7a, 0xd5, 0x20, 0xa0, 0x1d, 0x7a, 0x27, + 0x4c, 0x93, 0xef, 0xb4, ], [ - 0xbb, 0x71, 0x84, 0xe7, 0xbd, 0x9d, 0x5d, 0xed, 0x6d, 0x19, 0x39, 0xe3, 0xb1, 0x43, - 0x1a, 0x9b, 0xdb, 0x96, 0x47, 0xf3, 0x9e, 0x40, 0x12, 0xec, 0x5d, 0xc0, 0xa4, 0xa4, - 0x41, 0x0b, 0xad, 0x74, + 0x79, 0x6f, 0x6d, 0xe6, 0xb0, 0x27, 0x4a, 0x4f, 0x35, 0x50, 0x4f, 0x02, 0x76, 0x26, + 0x3c, 0x73, 0x2b, 0xbe, 0xa6, 0xdd, 0xab, 0xb3, 0x9e, 0xf7, 0x4a, 0xce, 0x51, 0xc1, + 0x12, 0xa2, 0x80, 0x40, ], [ - 0x03, 0x45, 0x83, 0x23, 0x1a, 0xc2, 0xf0, 0x18, 0x7f, 0xd6, 0x9e, 0xb6, 0x94, 0x3b, - 0xdb, 0xd2, 0x5f, 0x6b, 0x2f, 0x9f, 0x74, 0x1a, 0xa5, 0x7e, 0x8e, 0x2a, 0x45, 0x99, - 0xc5, 0x92, 0x2b, 0x13, + 0x07, 0xdb, 0x70, 0x37, 0x6d, 0xff, 0x1c, 0x50, 0x7f, 0x82, 0xf7, 0x83, 0x73, 0x7c, + 0x2e, 0xad, 0xab, 0x48, 0xbc, 0x68, 0x82, 0xf7, 0xff, 0x66, 0x80, 0x88, 0xe9, 0x2c, + 0xae, 0x17, 0x71, 0x37, ], [ - 0x87, 0x12, 0x45, 0xcc, 0xe4, 0xa3, 0x38, 0x2c, 0xbc, 0x5d, 0xe4, 0x20, 0xa7, 0x6d, - 0xac, 0xc3, 0x53, 0x00, 0x51, 0xbb, 0x11, 0xff, 0x1e, 0x1e, 0x65, 0xff, 0x0b, 0x57, - 0x2b, 0x36, 0xf1, 0x8f, + 0x06, 0xfe, 0x81, 0xac, 0x1e, 0xd6, 0x2e, 0x90, 0xec, 0xd5, 0x9a, 0x03, 0x33, 0xe2, + 0xdf, 0xeb, 0xa5, 0xa5, 0x42, 0xe4, 0x7a, 0xec, 0x27, 0x4c, 0xda, 0x74, 0x87, 0x31, + 0xe1, 0x40, 0xe0, 0x58, ], [ - 0x9b, 0xfb, 0xe0, 0xa9, 0x13, 0x3e, 0xc0, 0x12, 0x95, 0x8f, 0xe6, 0x04, 0xd8, 0xe6, - 0x04, 0xcd, 0x08, 0xd8, 0x24, 0xc9, 0x7f, 0x51, 0x39, 0xb6, 0xd6, 0x4d, 0x1d, 0xab, - 0xf6, 0x35, 0x00, 0x1d, + 0x60, 0xdf, 0x05, 0x40, 0x1e, 0x94, 0x4d, 0x5a, 0x43, 0xd7, 0x0b, 0xe9, 0x8e, 0x38, + 0x1e, 0x3d, 0x22, 0xa5, 0x91, 0xeb, 0xa4, 0x1f, 0xf8, 0xff, 0x1f, 0x96, 0x8b, 0x53, + 0xb6, 0x48, 0x97, 0xd7, ], [ - 0x8b, 0xac, 0x90, 0x86, 0xc2, 0x4a, 0xed, 0xe7, 0x89, 0xa8, 0x0f, 0x5c, 0x26, 0x4a, - 0x0f, 0x3f, 0x6c, 0xb2, 0x4c, 0xe7, 0x87, 0x31, 0x1b, 0xd7, 0xe7, 0x3d, 0xce, 0xd6, - 0x49, 0x3e, 0xd3, 0xe6, + 0x7d, 0x25, 0xc1, 0xee, 0x40, 0x2b, 0x03, 0x6b, 0xf9, 0x14, 0x9a, 0xa3, 0x50, 0x04, + 0xb8, 0x62, 0x5f, 0x24, 0x5b, 0x6d, 0x11, 0x4b, 0x36, 0xa9, 0xea, 0x0b, 0xd0, 0x85, + 0x58, 0xd8, 0x02, 0xf5, ], [ - 0x80, 0xe6, 0x2e, 0x9d, 0xac, 0x64, 0x93, 0x41, 0xae, 0x34, 0x6a, 0xba, 0xaa, 0x9a, - 0x1f, 0x49, 0x0c, 0x3f, 0xfd, 0xd3, 0x20, 0x6c, 0x97, 0xdc, 0xcd, 0xc1, 0x95, 0x99, - 0x6f, 0xe5, 0x6b, 0x70, + 0x8d, 0xa4, 0x49, 0x54, 0x80, 0xa3, 0xfe, 0x4d, 0x2f, 0xb1, 0xe0, 0x1d, 0xd9, 0x6b, + 0x82, 0xa6, 0x65, 0x36, 0x1f, 0x21, 0xf9, 0x92, 0x67, 0x4f, 0x88, 0x90, 0x70, 0xab, + 0xff, 0x31, 0xec, 0xb2, ], [ - 0x36, 0x22, 0xcc, 0x16, 0x23, 0x02, 0xe9, 0xdb, 0x18, 0x9b, 0x32, 0xc1, 0x20, 0x33, - 0x4d, 0xf0, 0x22, 0x34, 0xe6, 0x72, 0xf1, 0xf4, 0x30, 0xd1, 0x5d, 0x23, 0xa1, 0x56, - 0xcb, 0x0f, 0x89, 0xf4, + 0x92, 0x13, 0x7a, 0xa0, 0xb8, 0xe0, 0xe3, 0xb7, 0xb0, 0x4e, 0x49, 0x3d, 0x2b, 0x8f, + 0x7d, 0x1a, 0xef, 0x0b, 0x47, 0x68, 0x28, 0xd7, 0x23, 0x8c, 0xa4, 0x4c, 0x10, 0x2c, + 0x2e, 0x1c, 0x6d, 0x0c, ], [ - 0x34, 0x94, 0x94, 0xe2, 0xfb, 0x65, 0xc5, 0x44, 0x3a, 0xd7, 0x02, 0x77, 0x34, 0xfb, - 0xc7, 0xab, 0x39, 0xfc, 0xda, 0x74, 0x14, 0x2b, 0xfc, 0x66, 0xdc, 0x70, 0x49, 0x93, - 0x04, 0xda, 0xcc, 0x55, + 0x8a, 0xc9, 0x0a, 0xc6, 0x8d, 0x5c, 0x71, 0xf7, 0x0d, 0x60, 0x13, 0x14, 0x21, 0xcc, + 0x03, 0x06, 0x79, 0x30, 0x77, 0x81, 0xd9, 0xc1, 0xc4, 0x1c, 0x9f, 0x55, 0xa8, 0x02, + 0xc5, 0xb2, 0x5e, 0x21, ], [ - 0xad, 0xea, 0x6a, 0xb5, 0xcd, 0x68, 0x68, 0x36, 0x0d, 0x92, 0x94, 0xfb, 0x39, 0xc6, - 0xeb, 0xd1, 0x5d, 0x18, 0x25, 0xf8, 0x08, 0xe8, 0x2e, 0x9a, 0x48, 0x20, 0x62, 0x8c, - 0x27, 0x4a, 0x4d, 0x4b, + 0xe5, 0x6d, 0x6f, 0x2a, 0x3b, 0x38, 0x2f, 0xd0, 0xcf, 0x6f, 0xcb, 0x53, 0xb3, 0xfa, + 0x03, 0x1b, 0x4e, 0x99, 0xda, 0x4a, 0xa2, 0xf7, 0x82, 0x23, 0x9f, 0x03, 0x59, 0x06, + 0x51, 0xec, 0x4d, 0x4f, ], [ - 0x6c, 0xf6, 0x04, 0x71, 0x7c, 0x74, 0x88, 0x1d, 0x1b, 0x74, 0x19, 0x74, 0xbe, 0xfd, - 0x67, 0x74, 0x13, 0xdc, 0xd0, 0x05, 0x4d, 0xcf, 0x8f, 0xbf, 0x98, 0x61, 0xa6, 0x21, - 0x1d, 0x6b, 0xa2, 0xbe, + 0xa3, 0xd2, 0x1d, 0x58, 0xbf, 0x0c, 0x09, 0xb2, 0x14, 0xce, 0xe6, 0x4f, 0x94, 0xf8, + 0x10, 0xc2, 0xc2, 0xf0, 0xcb, 0x13, 0xd8, 0x8a, 0x66, 0xbc, 0x8f, 0x4c, 0x76, 0x72, + 0xe8, 0xc4, 0x17, 0x99, ], [ - 0x7d, 0xff, 0xd0, 0x62, 0xc9, 0x18, 0x47, 0x3a, 0xdb, 0xe7, 0x43, 0xe8, 0x4e, 0x4c, - 0xe2, 0x6a, 0xb4, 0x60, 0x6c, 0x03, 0x9a, 0x88, 0xce, 0x38, 0xb2, 0x33, 0xb1, 0xcb, - 0x7c, 0xc3, 0x82, 0x46, + 0x6a, 0xc2, 0xa2, 0x99, 0xe6, 0x48, 0x90, 0x8b, 0xe6, 0xa1, 0xca, 0xbf, 0x87, 0x9c, + 0x70, 0x73, 0xe2, 0x70, 0x50, 0x82, 0xf9, 0xeb, 0xdf, 0xeb, 0x61, 0x21, 0x06, 0x7f, + 0x37, 0x78, 0x2a, 0x1d, ], [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1377,14 +1377,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: true, }, program_id: [ - 0x07, 0xef, 0x50, 0xba, 0xed, 0x14, 0x7d, 0x67, 0x83, 0xaf, 0xa4, 0x38, 0x56, 0x17, - 0x5f, 0xb7, 0x22, 0x89, 0x94, 0x25, 0xa9, 0x2a, 0x66, 0x90, 0x27, 0x8e, 0x5a, 0x04, - 0x9c, 0x75, 0xcc, 0x95, + 0xa2, 0xc2, 0x49, 0x3b, 0xc8, 0x6d, 0xb5, 0x59, 0xf7, 0x40, 0x8b, 0x09, 0x7a, 0x7a, + 0x1d, 0x62, 0xa0, 0xdb, 0x2c, 0xe4, 0x36, 0x0d, 0x30, 0xfe, 0xda, 0xc7, 0x56, 0xb0, + 0x15, 0xb7, 0x91, 0x8f, ], prep_root: [ - 0xc6, 0xdf, 0x01, 0xb3, 0x62, 0x13, 0x01, 0x8f, 0xef, 0xd9, 0xf0, 0x81, 0xcc, 0xea, - 0x15, 0x7b, 0x84, 0xc3, 0xf4, 0x5b, 0x96, 0x60, 0x23, 0xde, 0x38, 0xde, 0xbe, 0x08, - 0x70, 0x1d, 0x90, 0xc6, + 0x47, 0x89, 0xd7, 0x30, 0x6e, 0x18, 0xd5, 0x29, 0x48, 0x34, 0x27, 0x88, 0x91, 0x55, + 0x33, 0x26, 0x90, 0xf5, 0x33, 0x4d, 0x88, 0xe8, 0xdb, 0x90, 0x73, 0xff, 0x38, 0xc1, + 0xae, 0xc4, 0xf4, 0xc9, ], prep_widths: [6, 10, 11, 8, 134, 13, 56, 12, 2, 3, 1, 20, 0, 0, 0], }, diff --git a/prover/src/tables/bitwise.rs b/prover/src/tables/bitwise.rs index fede150c2..dc5f73f0f 100644 --- a/prover/src/tables/bitwise.rs +++ b/prover/src/tables/bitwise.rs @@ -194,19 +194,19 @@ pub const fn is_preprocessed() -> bool { fn static_commitment(blowup_factor: u8) -> Option { match blowup_factor { 2 => Some([ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ]), 4 => Some([ - 0x26, 0xe3, 0xea, 0x30, 0xb6, 0xbe, 0x1c, 0xf8, 0xd3, 0x90, 0x3b, 0x41, 0xf2, 0x11, - 0xec, 0xcb, 0x42, 0x63, 0xd1, 0x77, 0x5b, 0x9d, 0x81, 0x1b, 0x13, 0xfc, 0x7b, 0x87, - 0xa7, 0xae, 0xb7, 0x1d, + 0x88, 0x7b, 0x04, 0x68, 0xdd, 0x29, 0xf5, 0x0a, 0x87, 0x05, 0xcc, 0xdb, 0x48, 0xce, + 0x38, 0x11, 0xf5, 0x49, 0x2f, 0xe2, 0x73, 0x15, 0x03, 0xa7, 0xb9, 0x86, 0x04, 0x60, + 0xcc, 0x28, 0xa5, 0x9b, ]), 8 => Some([ - 0x9e, 0x11, 0x8d, 0x0c, 0x06, 0x45, 0x1c, 0x8c, 0x25, 0x1e, 0x16, 0x40, 0x17, 0x94, - 0xf5, 0x88, 0x03, 0x26, 0xba, 0x0c, 0xa4, 0x13, 0xbe, 0x82, 0x48, 0x50, 0xa1, 0xd1, - 0xe4, 0xc2, 0x30, 0xe0, + 0x7d, 0x85, 0xb2, 0x07, 0x0e, 0xdb, 0x9c, 0x89, 0xd1, 0x91, 0xda, 0x78, 0xe7, 0x11, + 0x13, 0x77, 0xe3, 0x1e, 0xe9, 0xbe, 0x3e, 0x3d, 0xd4, 0x26, 0x86, 0xbb, 0x4a, 0xe9, + 0x2c, 0x51, 0x1e, 0x44, ]), _ => None, } diff --git a/prover/src/tables/keccak_rc.rs b/prover/src/tables/keccak_rc.rs index d8d3525f3..bc01003ee 100644 --- a/prover/src/tables/keccak_rc.rs +++ b/prover/src/tables/keccak_rc.rs @@ -96,19 +96,19 @@ pub const fn generate_row(round: usize) -> [u64; NUM_PRECOMPUTED_COLS] { fn static_commitment(blowup_factor: u8) -> Option { match blowup_factor { 2 => Some([ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ]), 4 => Some([ - 0xbf, 0x23, 0x00, 0x05, 0x30, 0x52, 0x47, 0x59, 0xff, 0xf4, 0xec, 0x7c, 0x40, 0x89, - 0x15, 0xd7, 0x0e, 0x85, 0x24, 0x75, 0x28, 0xdd, 0xb2, 0x3f, 0x01, 0x83, 0x7c, 0x61, - 0x45, 0x0c, 0x56, 0x4e, + 0xe9, 0xd8, 0x8a, 0xfe, 0x76, 0xf2, 0x68, 0x30, 0x55, 0xf6, 0xe7, 0x7c, 0x5e, 0x21, + 0x91, 0x7e, 0xa6, 0x59, 0x3b, 0xb1, 0x4e, 0xaa, 0xc0, 0x29, 0xf5, 0x00, 0xc0, 0x13, + 0xca, 0xc6, 0x6d, 0x10, ]), 8 => Some([ - 0xf4, 0xc0, 0x24, 0x38, 0xf9, 0x16, 0x8d, 0x35, 0x9f, 0xad, 0x2a, 0xc2, 0x79, 0x07, - 0x07, 0x7a, 0xa6, 0x0b, 0xa0, 0x4d, 0x7b, 0x71, 0x0e, 0xb9, 0xb4, 0x35, 0x5e, 0x84, - 0x71, 0xfa, 0xcc, 0x68, + 0x78, 0xfe, 0x1a, 0x99, 0xd6, 0x8b, 0x7d, 0x79, 0xd7, 0x3c, 0xd9, 0x8d, 0xc4, 0x81, + 0x82, 0x63, 0x82, 0x9a, 0x01, 0x3f, 0xed, 0x73, 0xab, 0xf9, 0x9d, 0xfa, 0xc7, 0xa1, + 0x99, 0x0f, 0xdf, 0xc2, ]), _ => None, } diff --git a/prover/src/tables/page.rs b/prover/src/tables/page.rs index 07f7e9a25..06c33309f 100644 --- a/prover/src/tables/page.rs +++ b/prover/src/tables/page.rs @@ -410,19 +410,19 @@ pub fn generate_page_trace_from_dense( pub(crate) fn static_zero_page_commitment(blowup_factor: u8) -> Option { match blowup_factor { 2 => Some([ - 0x17, 0xca, 0x82, 0xd0, 0x53, 0x18, 0x0e, 0x72, 0x0d, 0xa6, 0x33, 0x27, 0xe8, 0x64, - 0xb4, 0x61, 0xe5, 0xfa, 0x96, 0x65, 0x09, 0xa3, 0x41, 0x09, 0xff, 0x3f, 0x62, 0x81, - 0x3a, 0xe8, 0x8c, 0x62, + 0x3b, 0x8d, 0xfb, 0xfc, 0xab, 0xcb, 0x13, 0x98, 0x1b, 0xec, 0xe7, 0x72, 0x38, 0x50, + 0xcc, 0xd1, 0x9a, 0x95, 0xe6, 0xcc, 0x6c, 0xf7, 0xf0, 0x4a, 0x49, 0xc9, 0x5d, 0x52, + 0x42, 0x2b, 0x8c, 0x4b, ]), 4 => Some([ - 0x7a, 0x23, 0x4c, 0x3d, 0x8c, 0x24, 0xcc, 0x88, 0x6c, 0xb6, 0xe4, 0xd5, 0xc9, 0xe3, - 0xf0, 0x4e, 0x87, 0x56, 0xb5, 0xe0, 0x17, 0x98, 0x45, 0xd3, 0x7c, 0x3e, 0x4d, 0xcc, - 0x08, 0x66, 0xc3, 0xab, + 0x0b, 0x95, 0xb7, 0xe1, 0x62, 0xe4, 0xec, 0x59, 0xd5, 0xb7, 0x41, 0x7f, 0xc6, 0xe7, + 0x03, 0x7c, 0xee, 0x3c, 0xc2, 0x10, 0x29, 0xea, 0xbf, 0xae, 0x5a, 0xfd, 0x5e, 0x5d, + 0x00, 0x0b, 0x41, 0xdb, ]), 8 => Some([ - 0x9d, 0xc4, 0x23, 0xf5, 0xad, 0x9c, 0x10, 0x12, 0xc5, 0x5d, 0xa5, 0x9f, 0xd4, 0x0e, - 0x55, 0x99, 0x0a, 0x9a, 0x3c, 0x3a, 0x77, 0xd7, 0x27, 0xfb, 0xbe, 0xd1, 0xc8, 0x5a, - 0xb0, 0x45, 0x62, 0xf6, + 0x20, 0x86, 0x24, 0x23, 0x30, 0x0a, 0xea, 0xdc, 0x58, 0x37, 0x88, 0xf7, 0x33, 0x39, + 0x89, 0x2c, 0xf5, 0x2b, 0xfa, 0x89, 0x8b, 0x06, 0x60, 0x4d, 0x6f, 0x21, 0xc7, 0xfb, + 0x6f, 0xcb, 0xac, 0x84, ]), _ => None, } @@ -436,19 +436,19 @@ pub(crate) fn static_zero_page_commitment(blowup_factor: u8) -> Option Option { match blowup_factor { 2 => Some([ - 0xbc, 0x98, 0x15, 0x92, 0xb7, 0xfb, 0x9b, 0x50, 0x5a, 0xfb, 0xa7, 0x48, 0x40, 0x0a, - 0x67, 0xb9, 0x56, 0x2d, 0x54, 0x6e, 0x56, 0xb0, 0x32, 0xf1, 0x19, 0x57, 0x86, 0x36, - 0x30, 0xa0, 0x76, 0x42, + 0x04, 0x31, 0x10, 0xb3, 0x61, 0xbd, 0xd4, 0x6d, 0x92, 0x65, 0x0e, 0x2d, 0xc9, 0xe6, + 0x1d, 0x20, 0x78, 0xc3, 0xd6, 0xc6, 0x8f, 0x12, 0xb0, 0x45, 0xf0, 0x76, 0x68, 0x27, + 0x5e, 0x2b, 0xa7, 0xbd, ]), 4 => Some([ - 0x1a, 0x60, 0x17, 0xe1, 0x43, 0x1b, 0x15, 0xe5, 0xe8, 0x33, 0x39, 0xae, 0xc6, 0x6a, - 0x62, 0x72, 0x2b, 0xd0, 0x95, 0xbe, 0xb3, 0x05, 0x06, 0x9f, 0x72, 0x32, 0xea, 0xac, - 0x07, 0x0a, 0xfa, 0x6f, + 0xef, 0x71, 0xa3, 0x11, 0x4a, 0xb3, 0xf7, 0xd6, 0x77, 0xe4, 0x79, 0x0e, 0xd1, 0x96, + 0xf3, 0x36, 0x19, 0x70, 0x6b, 0xd7, 0x2e, 0x04, 0x86, 0x13, 0xfd, 0x81, 0xd1, 0xf4, + 0x26, 0xca, 0xd2, 0x41, ]), 8 => Some([ - 0xb5, 0x27, 0xbd, 0x0d, 0xa1, 0x14, 0xfe, 0xe0, 0x45, 0xe6, 0x32, 0xb7, 0x86, 0xf0, - 0x68, 0x26, 0x05, 0x19, 0xa1, 0x57, 0x46, 0x24, 0xc3, 0x0d, 0x2f, 0xdd, 0x16, 0xe3, - 0x72, 0x3a, 0xbf, 0x3b, + 0xf7, 0x2d, 0x07, 0xc5, 0x84, 0x5c, 0xc8, 0x92, 0x79, 0x75, 0xf3, 0x56, 0xb7, 0xf9, + 0xe5, 0xd5, 0xb5, 0x8f, 0x62, 0x63, 0x36, 0x8f, 0xd0, 0x8c, 0x42, 0xce, 0xfa, 0x2c, + 0xd2, 0x79, 0xd2, 0x8e, ]), _ => None, } From 9a9d92469675d15c71066d3a01ce617afe054557 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 23:44:34 -0300 Subject: [PATCH 15/23] fix(lfm): regenerate the registry AFTER the statics it embeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit registry.rs:661-664 fills roots[13] and roots[14] from keccak_rc::preprocessed_commitment and bitwise::preprocessed_commitment, and at coset offset 3 both RETURN the blessed static_commitment constant from the tree rather than recomputing; lfm_program_id folds every root, so program_id inherits them. The previous registry regeneration ran in a tree whose statics were still BLAKE3, so it embedded the old constants in slots 13/14 of every entry, and all six registry_drift_* fired at exactly those two slots once the RPX statics were in the tree. The pre-pin control could not see this: statics and registry were BLAKE3-consistent there. This is the registry regenerated again at the pinned head, AFTER the RPX statics — the correct order is statics first, registry second. Prediction: all six registry_drift_* pass; on every entry slots 13 and 14 and program_id move (to the RPX keccak_rc@2 / bitwise@2 constants), and slots 0-12, prep_root, log_heights, prep_widths, chip_set, keccak_rnd_chunks and hasher (Test) are unchanged. Statics untouched. --- prover/src/lfm/registry.rs | 108 ++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/prover/src/lfm/registry.rs b/prover/src/lfm/registry.rs index 3481c4ecf..a61cf9f16 100644 --- a/prover/src/lfm/registry.rs +++ b/prover/src/lfm/registry.rs @@ -864,14 +864,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 16, 2, 0, 5, 20], @@ -882,9 +882,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0x72, 0xad, 0x4e, 0x78, 0x4f, 0xa5, 0x36, 0x84, 0x4c, 0x45, 0xf6, 0xf4, 0xb6, 0x10, - 0x47, 0x9f, 0xc8, 0xfa, 0x1d, 0x10, 0x7c, 0x4d, 0x9d, 0xf0, 0x02, 0x54, 0xe2, 0x13, - 0x93, 0x48, 0xf2, 0x16, + 0xff, 0xaf, 0xf6, 0xee, 0xf4, 0xdc, 0x28, 0x7f, 0xf3, 0x94, 0xd1, 0x91, 0x61, 0x3c, + 0xda, 0x00, 0x7d, 0x2e, 0xc7, 0x6d, 0xaa, 0x6e, 0x38, 0x96, 0x64, 0x81, 0xde, 0xed, + 0x27, 0xfd, 0x68, 0xde, ], prep_root: [ 0x23, 0x57, 0x15, 0x53, 0x9b, 0xdb, 0xf1, 0x9e, 0x9e, 0x6f, 0x9b, 0xce, 0x1d, 0x51, @@ -963,14 +963,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [5, 8, 7, 7, 2, 7, 2, 5, 7, 2, 16, 2, 0, 5, 20], @@ -981,9 +981,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0xcf, 0x55, 0x15, 0x9a, 0x28, 0xd4, 0xd1, 0x60, 0x3f, 0x2d, 0x38, 0x2a, 0x86, 0x1a, - 0x58, 0x75, 0x6c, 0x91, 0x18, 0xea, 0x60, 0xf3, 0x27, 0x5a, 0xec, 0xb9, 0xe8, 0x3b, - 0xc8, 0x34, 0xff, 0x18, + 0x55, 0xb6, 0x4c, 0xed, 0x57, 0x0c, 0xb1, 0x99, 0xc6, 0x05, 0xd8, 0x19, 0x77, 0x07, + 0x01, 0xc2, 0x3e, 0x0b, 0x93, 0xda, 0x00, 0xc6, 0xb4, 0x6d, 0x99, 0xd5, 0x7e, 0xc0, + 0x6b, 0xb6, 0x49, 0xf4, ], prep_root: [ 0x81, 0x31, 0x40, 0x97, 0xdc, 0x51, 0x37, 0x09, 0x39, 0x04, 0x60, 0x51, 0xe7, 0x3c, @@ -1062,14 +1062,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 16, 2, 0, 5, 20], @@ -1080,9 +1080,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0x3f, 0x12, 0xac, 0x61, 0xde, 0x5a, 0x8a, 0x02, 0xa9, 0xb3, 0x32, 0xa8, 0x16, 0xa1, - 0x28, 0xa0, 0xd1, 0x75, 0x3f, 0x4f, 0x64, 0xa4, 0x51, 0x8c, 0x37, 0x39, 0xa6, 0x3f, - 0x0d, 0x97, 0xda, 0xf9, + 0x55, 0x79, 0xf4, 0xc0, 0x15, 0xb3, 0xd9, 0x73, 0xc1, 0x7d, 0xe0, 0x41, 0x58, 0xaa, + 0x06, 0xff, 0x4e, 0x56, 0x57, 0x50, 0x3c, 0x9e, 0x51, 0xc9, 0xe0, 0x40, 0x3c, 0xc8, + 0x58, 0xd4, 0x2a, 0x33, ], prep_root: [ 0x17, 0xb9, 0x2d, 0x29, 0xbd, 0x27, 0x65, 0xb1, 0x9f, 0xb3, 0xe7, 0x4e, 0x89, 0xb8, @@ -1161,14 +1161,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [2, 2, 2, 2, 2, 2, 2, 5, 6, 2, 16, 2, 0, 5, 20], @@ -1179,9 +1179,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: false, }, program_id: [ - 0x3d, 0x84, 0x8a, 0x1b, 0x5d, 0xd3, 0x8e, 0x99, 0x67, 0xc6, 0x08, 0xb6, 0x81, 0x9d, - 0x4e, 0x16, 0x47, 0x26, 0x84, 0xe1, 0x3a, 0xef, 0xd2, 0x4e, 0xb7, 0xc1, 0x36, 0x92, - 0x59, 0x52, 0xf8, 0xa6, + 0x0a, 0x94, 0x02, 0xe9, 0x21, 0x34, 0xae, 0xbc, 0x2a, 0xcb, 0xbb, 0x94, 0x2b, 0xdf, + 0xa9, 0x91, 0xaf, 0x2c, 0xd1, 0xc7, 0xb2, 0xff, 0xbb, 0xf9, 0x14, 0xed, 0x40, 0xde, + 0xf6, 0x54, 0x80, 0xf0, ], prep_root: [ 0xa6, 0x14, 0xdf, 0x60, 0xda, 0x68, 0x8c, 0xfc, 0x67, 0x5d, 0x4b, 0x31, 0xaa, 0xce, @@ -1260,14 +1260,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [5, 10, 2, 4, 4, 2, 2, 6, 6, 3, 16, 3, 0, 5, 20], @@ -1278,9 +1278,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: true, }, program_id: [ - 0x49, 0x70, 0xaf, 0x9a, 0x21, 0x09, 0x26, 0x03, 0xe7, 0x80, 0x85, 0x84, 0xf8, 0x5f, - 0x4a, 0x5b, 0xdb, 0xd9, 0x1e, 0x88, 0x1a, 0x4a, 0x01, 0xc7, 0xe8, 0x05, 0xf5, 0x05, - 0xb9, 0x09, 0xb3, 0x5c, + 0x4d, 0xf6, 0x91, 0xc0, 0x40, 0xd6, 0x21, 0x39, 0xb8, 0x4a, 0x72, 0xe0, 0xcc, 0x1c, + 0x08, 0x77, 0xa5, 0x67, 0x4e, 0x53, 0x1e, 0x36, 0xdd, 0x56, 0x89, 0xc9, 0x4c, 0xc9, + 0x88, 0x15, 0x64, 0x4f, ], prep_root: [ 0x77, 0x62, 0x5c, 0x36, 0x2d, 0xd7, 0xe8, 0xbf, 0xbf, 0x58, 0x2e, 0xdd, 0x42, 0x73, @@ -1359,14 +1359,14 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ 0x00, 0x00, 0x00, 0x00, ], [ - 0x48, 0xad, 0x9f, 0x53, 0x9b, 0x8e, 0x46, 0x7e, 0x45, 0x9d, 0x3a, 0x14, 0xe1, 0x22, - 0x80, 0xec, 0xd5, 0xfd, 0x7a, 0x0f, 0x77, 0x84, 0xb8, 0xf0, 0xc4, 0x53, 0x4d, 0xb2, - 0x83, 0x90, 0xcb, 0x99, + 0x0a, 0x3c, 0x86, 0xe2, 0xd3, 0x37, 0x98, 0xf2, 0x68, 0x45, 0x42, 0xbf, 0x0b, 0x3d, + 0x52, 0x02, 0x4b, 0x08, 0x8b, 0x7a, 0x4b, 0xc8, 0xb0, 0x2a, 0xe4, 0x14, 0x86, 0x09, + 0x35, 0xdc, 0xa6, 0x7c, ], [ - 0x9f, 0xbc, 0x26, 0x49, 0xce, 0x62, 0x34, 0xb5, 0x87, 0x3b, 0x91, 0xd8, 0x45, 0x2c, - 0x60, 0x74, 0x9b, 0x28, 0xaf, 0xdb, 0xf5, 0x1f, 0x3b, 0x9d, 0xde, 0x7c, 0xd0, 0xc2, - 0x38, 0x74, 0x8a, 0xeb, + 0x46, 0x8d, 0xfe, 0x65, 0xed, 0x50, 0x25, 0xbf, 0x7e, 0xfa, 0x04, 0x11, 0x28, 0x36, + 0x29, 0x06, 0xbd, 0x97, 0x29, 0xeb, 0x5b, 0x3a, 0xae, 0xbf, 0x66, 0xea, 0x5e, 0x59, + 0xe4, 0xd7, 0x17, 0x6c, ], ], log_heights: [5, 12, 2, 4, 7, 2, 2, 6, 6, 2, 16, 4, 0, 5, 20], @@ -1377,9 +1377,9 @@ pub static LFM_REGISTRY: &[LfmRegistryEntry] = &[ blake3: true, }, program_id: [ - 0xa2, 0xc2, 0x49, 0x3b, 0xc8, 0x6d, 0xb5, 0x59, 0xf7, 0x40, 0x8b, 0x09, 0x7a, 0x7a, - 0x1d, 0x62, 0xa0, 0xdb, 0x2c, 0xe4, 0x36, 0x0d, 0x30, 0xfe, 0xda, 0xc7, 0x56, 0xb0, - 0x15, 0xb7, 0x91, 0x8f, + 0xd9, 0x80, 0x4c, 0xab, 0xc9, 0xa2, 0xbb, 0x15, 0xb2, 0x36, 0xe3, 0x4b, 0x50, 0x05, + 0x97, 0x07, 0x99, 0xf6, 0x54, 0xa3, 0x87, 0x07, 0x34, 0xa5, 0x14, 0x8a, 0xd5, 0x63, + 0x21, 0xd6, 0xa6, 0x4d, ], prep_root: [ 0x47, 0x89, 0xd7, 0x30, 0x6e, 0x18, 0xd5, 0x29, 0x48, 0x34, 0x27, 0x88, 0x91, 0x55, From 92968a2e3812d6c6af3f7c9a90a8279695bb5dc9 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 23:45:28 -0300 Subject: [PATCH 16/23] fix(tests): the bitwise soundness control commits under the pinned prover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bitwise_tests::compute_trace_commitment built the honest and malicious preprocessed commitments through stark::prover::Prover — the workspace alias, GenericProver at DefaultStarkHash, BLAKE3 whatever the pin says — while multi_prove_ram recomputes the AIR's commitment under the pinned hash. Under an algebraic pin the honest arm therefore fails at prove time with PrecomputedCommitmentMismatch before the test reaches the tamper it is about. It now goes through hash_pin::BlockProver. The enumeration gate lists Prover::compute_precomputed_commitment_for_testing as a third alias call form, so this spelling of the default is detected from now on; the BlockProver:: exclusion already covers the pinned spelling. --- prover/src/tests/bitwise_tests.rs | 12 +++++++++--- prover/src/tests/hash_pin_enumeration.rs | 9 ++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/prover/src/tests/bitwise_tests.rs b/prover/src/tests/bitwise_tests.rs index 738af5cb4..920a1dbd8 100644 --- a/prover/src/tests/bitwise_tests.rs +++ b/prover/src/tests/bitwise_tests.rs @@ -420,7 +420,7 @@ mod soundness_tests { NullBoundaryConstraintBuilder, Packing, }; use stark::proof::options::ProofOptions; - use stark::prover::{IsStarkProver, Prover}; + use stark::prover::IsStarkProver; use stark::trace::TraceTable; use stark::traits::AIR; use stark::verifier::IsStarkVerifier; @@ -557,8 +557,14 @@ mod soundness_tests { let dummy_air = create_receiver_air(proof_options); // Use the prover's commitment computation (3 precomputed cols: X, Y, AND) - Prover::compute_precomputed_commitment_for_testing(trace, &dummy_air, 3) - .expect("Failed to compute commitment") + // — the PINNED prover, so the commitment the AIRs declare is built under + // the same hash `multi_prove_ram` recomputes it with. The workspace + // `Prover` alias is BLAKE3 whatever the pin says, and under an algebraic + // pin it makes the honest arm fail with PrecomputedCommitmentMismatch. + crate::hash_pin::BlockProver::compute_precomputed_commitment_for_testing( + trace, &dummy_air, 3, + ) + .expect("Failed to compute commitment") } fn create_sender_trace(x: u8, y: u8, claimed_result: u8) -> TraceTable { diff --git a/prover/src/tests/hash_pin_enumeration.rs b/prover/src/tests/hash_pin_enumeration.rs index 59ee0e04d..d0dd85b6a 100644 --- a/prover/src/tests/hash_pin_enumeration.rs +++ b/prover/src/tests/hash_pin_enumeration.rs @@ -37,7 +37,14 @@ use std::path::Path; /// Matched as call forms rather than as bare identifiers, because `Prover` and /// `Verifier` appear inside `IsStarkProver`, `BlockProver` and ordinary prose. /// A line naming the pin is excluded by [`PIN_CALLS`] rather than by the pattern. -const ALIAS_CALLS: &[&str] = &["Prover::multi_prove", "Verifier::multi_verify"]; +/// `compute_precomputed_commitment_for_testing` joined the list when a test +/// declared BLAKE3 preprocessed commitments through it while the pinned prover +/// recomputed them under RPX — the same alias, one more call form. +const ALIAS_CALLS: &[&str] = &[ + "Prover::multi_prove", + "Verifier::multi_verify", + "Prover::compute_precomputed_commitment_for_testing", +]; /// The pinned spellings, which contain [`ALIAS_CALLS`] as substrings. const PIN_CALLS: &[&str] = &["BlockProver::", "BlockVerifier::"]; From d2e6362ebd15552bd555f796415bf2ba2b231d57 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 23:51:37 -0300 Subject: [PATCH 17/23] fix(lfm,tests): the remaining digest strides follow the digest's width Under the RPX pin a root is one arena word, and these sites still spelled two: - join_tests' control program walked its sibling digests as `lo/hi` pairs with `cursor += 2` while declaring the arena at the builder's width, so the walk ran off the arena (ArenaOutOfBounds); it hints each digest at `edsl::digest_words` now, like the production emitter. - fri_tests' tamper table addressed the layer-2 root at `2 * (c - 1)` and named a "second word" of the layer-0 root that an algebraic root does not have; the offset follows `words_per_root()` and the second-word vector exists only at width two. - epoch_tests' split-DECODE control declared its extra root arena as a literal two words; it declares `RootCells::words_per_root` of the builder, the width `RootCells::hint` reads back at. - epoch_tests' two arena-schema closed forms (`expected_arena_words`, `expected_batched_arena_words`) counted every root as two words; each root term now counts `words_per_root()`. The register vectors, `pc_start` and the page bases are not roots and keep their widths. - wrap_tests read the carved L2G root back as the last eight byte halves; it reads the last `lanes_per_root()` lanes and compares them to `commitment_lanes` of the bundle's root. - programs.rs' L2G binding built on the default (keccak) builder and hard-coded two words per root. The L2G roots are the block path's own commitments, so the program follows `WrapHash::production()` and reads each root through `hint_digest`, publishing the digest's own cells; machine_tests' `published_root` reads them back at `words_per_root()` through `digest_bytes`. Prediction: the_controls_show_what_the_join_denies, no_tampered_fri_value_can_pass, a_split_decode_cell_forges_the_attestation, both *_declares_*_exactly_the_shape_words, the_fixture_continuation_epoch_wraps_batched_from_proofs and the two l2g_binding tests go from ArenaLenMismatch / ArenaOutOfBounds / schema mismatch to green under the pin, and stay green at the BLAKE3 default where every width is two. --- prover/src/lfm/epoch_tests.rs | 59 +++++++++++++++++++-------------- prover/src/lfm/fri_tests.rs | 9 +++-- prover/src/lfm/machine_tests.rs | 14 +++----- prover/src/lfm/programs.rs | 26 +++++++-------- prover/src/lfm/wrap_tests.rs | 18 +++++----- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/prover/src/lfm/epoch_tests.rs b/prover/src/lfm/epoch_tests.rs index 7e944d1cf..72785ff68 100644 --- a/prover/src/lfm/epoch_tests.rs +++ b/prover/src/lfm/epoch_tests.rs @@ -3207,17 +3207,19 @@ fn the_assembled_batched_epoch_verifier_runs() { /// emitter, so the comparison against the compiled program is absolute. fn expected_batched_arena_words(e: &RealBatchedEpoch, with_legs: bool) -> usize { let num_reg = crate::tables::register::NUM_REGISTER_ADDRESSES; + // A root's width in arena words (see `expected_arena_words`). + let dw = super::proof_arena::words_per_root(); let mut total = 8 + e.statement.public_output_len.div_ceil(4) + 2; - total += 2 * e - .prep_sources - .iter() - .filter(|p| p.is_some_and(PrepSource::is_arena)) - .count(); - total += 2 * usize::from(e.shape.carved_main.is_some()); // the carved root - total += 2; // main_root — ONE, which is the whole batched economy + total += dw + * e.prep_sources + .iter() + .filter(|p| p.is_some_and(PrepSource::is_arena)) + .count(); + total += dw * usize::from(e.shape.carved_main.is_some()); // the carved root + total += dw; // main_root — ONE, which is the whole batched economy total += 2 * num_reg; total += 2; // pc_start - total += 2 * usize::from(e.proof.aux_root.is_some()); + total += dw * usize::from(e.proof.aux_root.is_some()); total += e .proof .tables @@ -3229,13 +3231,13 @@ fn expected_batched_arena_words(e: &RealBatchedEpoch, with_legs: bool) -> usize total += t.trace_ood_next_evaluations.width * t.trace_ood_next_evaluations.height; total += t.composition_poly_parts_ood_evaluation.len(); } - total += 2; // parts_root + total += dw; // parts_root for t in &e.proof.tables { if let Some(coeffs) = t.standalone_final_poly_coeffs.as_ref() { total += coeffs.len(); } } - total += 2 * e.proof.fri_layer_roots.len(); + total += dw * e.proof.fri_layer_roots.len(); total += e.proof.fri_final_poly_coeffs.len(); total += usize::from(e.fri_params.grinding_factor > 0); if with_legs { @@ -3690,7 +3692,10 @@ fn epoch_program_with(e: &RealEpoch, with_legs: bool, split_decode: bool) -> Lfm }) .collect(); // Last in declaration order, so turning the control on shifts no other arena. - let a_split_decode = split_decode.then(|| b.declare_arena(2)); + // One root, at THIS builder's digest width — the width `RootCells::hint` + // reads it back at. + let root_words = RootCells::words_per_root(&b); + let a_split_decode = split_decode.then(|| b.declare_arena(root_words)); // ---- the statement ---- let stmt: Vec<_> = (0..stmt_halves as u32) @@ -4344,33 +4349,37 @@ fn the_spine_hints_each_proof_value_once() { /// comparison against the compiled program is absolute. fn expected_arena_words(e: &RealEpoch, with_legs: bool) -> usize { let num_reg = crate::tables::register::NUM_REGISTER_ADDRESSES; + // A root's width in arena words — the host's counterpart of the emitter's + // `digest_words`: two on a byte hash, one on an algebraic one. The register + // vectors, `pc_start` and the page bases are NOT roots and keep their own + // widths below. + let dw = super::proof_arena::words_per_root(); let mut total = 8 + e.statement.public_output_len.div_ceil(4) + 2; - // ★ Two words per ELF-DEPENDENT preprocessed root and NOT ONE MORE. The - // options-only roots are program text and the REGISTER root is derived, so a - // program that hinted any of them — or that kept a second copy of DECODE for - // the attestation fold — declares more words than this. - total += 2 * e - .phase_a - .iter() - .filter(|(p, _)| p.is_some_and(PrepSource::is_arena)) - .count(); - total += 2 * e.tables.len(); + // ★ One root's width per ELF-DEPENDENT preprocessed root and NOT ONE MORE. + // The options-only roots are program text and the REGISTER root is derived, + // so a program that hinted any of them — or that kept a second copy of + // DECODE for the attestation fold — declares more words than this. + total += dw + * e.phase_a + .iter() + .filter(|(p, _)| p.is_some_and(PrepSource::is_arena)) + .count(); + total += dw * e.tables.len(); total += 2 * num_reg; total += 2; total += 10 * e.page_commitments.len(); for (h, leg) in e.tables.iter().zip(&e.legs) { let s = &h.shape; - total += 2 * usize::from(s.has_aux_root); + total += dw * usize::from(s.has_aux_root); total += usize::from(s.has_contribution); - total += 2; + total += dw; total += s.ood_current_dims.0 * s.ood_current_dims.1; total += s.ood_next_dims.0 * s.ood_next_dims.1; total += s.num_parts; - total += 2 * s.fri.num_committed(); + total += dw * s.fri.num_committed(); total += s.fri.num_terminal_coeffs(); total += usize::from(s.grinding_factor > 0); if with_legs { - let dw = super::proof_arena::words_per_root(); total += leg.verify.opening_words(dw) + leg.verify.fri_words(dw); } } diff --git a/prover/src/lfm/fri_tests.rs b/prover/src/lfm/fri_tests.rs index be46c0326..895b6c8ef 100644 --- a/prover/src/lfm/fri_tests.rs +++ b/prover/src/lfm/fri_tests.rs @@ -1053,11 +1053,10 @@ fn no_tampered_fri_value_can_pass() { let stride = h.shape.query_words(dw); // (label, arena, word) — arena order is the driver's: deep, roots, zetas, // coeffs, queries. - let bump: Vec<(&str, usize, usize)> = vec![ + let mut bump: Vec<(&str, usize, usize)> = vec![ ("query index", 0, 0), ("layer 0 root", 1, 0), - ("layer 0 root, second word", 1, 1), - ("layer 2 root", 1, 2 * (c - 1)), + ("layer 2 root", 1, dw * (c - 1)), ("zeta_0 (the DEEP fold's challenge)", 2, 0), ("zeta_C (the uncommitted final fold)", 2, c), ("terminal coefficient 0", 3, 0), @@ -1071,6 +1070,10 @@ fn no_tampered_fri_value_can_pass() { ), ("second query's layer 0 evaluation", 4, stride), ]; + if dw == 2 { + // Only a byte digest has a second word to move; an algebraic root is one. + bump.push(("layer 0 root, second word", 1, 1)); + } for (label, arena, word) in bump { let mut tampered = honest.clone(); tampered[arena][word][0] += FE::one(); diff --git a/prover/src/lfm/machine_tests.rs b/prover/src/lfm/machine_tests.rs index 82bf1a35a..f74300e0f 100644 --- a/prover/src/lfm/machine_tests.rs +++ b/prover/src/lfm/machine_tests.rs @@ -3722,16 +3722,12 @@ use super::programs::l2g_binding_program; /// loud — the same discipline `R1F_SHAPE` uses. const R1G_EPOCHS: usize = 2; -/// The `i`-th 32-byte root in a program's published words. +/// The `i`-th 32-byte root in a program's published words, at the root's own +/// width — the L2G binding follows the configuration, so a root is +/// `words_per_root()` words: two byte words or one algebraic word. fn published_root(public: &[(u32, LfmWord)], i: usize) -> [u8; 32] { - use math::field::traits::IsPrimeField; - let mut out = [0u8; 32]; - for h in 0..8 { - let lane = public[2 * i + h / 4].1[h % 4]; - let half = crate::tables::types::GoldilocksField::canonical(lane.value()) as u32; - out[4 * h..4 * h + 4].copy_from_slice(&half.to_le_bytes()); - } - out + let w = super::proof_arena::words_per_root(); + digest_bytes(&public[w * i..w * (i + 1)]) } fn l2g_arenas( diff --git a/prover/src/lfm/programs.rs b/prover/src/lfm/programs.rs index cfdd50d5d..afdec9134 100644 --- a/prover/src/lfm/programs.rs +++ b/prover/src/lfm/programs.rs @@ -948,24 +948,22 @@ pub fn l2g_binding_program_source(num_epochs: usize) -> LfmProgramSource { assert!(num_epochs > 0, "a continuation has at least one epoch"); - let words = 2 * num_epochs as u32; - let mut b = LfmBuilder::new(); + // The L2G roots are the block path's own commitments, so this program + // follows the configuration and reads each root at the DIGEST's width — + // two words on a byte hash, one on an algebraic one — never a literal two. + let mut b = LfmBuilder::new().with_wrap_hash(edsl::WrapHash::production()); + let dw = edsl::digest_words(&b); + let words = dw * num_epochs as u32; let epoch_arena = b.declare_arena(words); let global_arena = b.declare_arena(words); for i in 0..num_epochs as u32 { - let epoch = [ - b.hint_word(epoch_arena, 2 * i), - b.hint_word(epoch_arena, 2 * i + 1), - ]; - let global = [ - b.hint_word(global_arena, 2 * i), - b.hint_word(global_arena, 2 * i + 1), - ]; - edsl::assert_word_eq(&mut b, epoch[0], global[0]); - edsl::assert_word_eq(&mut b, epoch[1], global[1]); - b.public(epoch[0]); - b.public(epoch[1]); + let epoch = edsl::hint_digest(&mut b, epoch_arena, dw * i); + let global = edsl::hint_digest(&mut b, global_arena, dw * i); + for (e, g) in epoch.cells().iter().zip(global.cells()) { + edsl::assert_word_eq(&mut b, *e, *g); + b.public(*e); + } } b.finish() } diff --git a/prover/src/lfm/wrap_tests.rs b/prover/src/lfm/wrap_tests.rs index 47eff9bf8..b2f119611 100644 --- a/prover/src/lfm/wrap_tests.rs +++ b/prover/src/lfm/wrap_tests.rs @@ -1977,19 +1977,17 @@ fn the_fixture_continuation_epoch_wraps_batched_from_proofs() { "the carved wrap of the final epoch must verify" ); - // The published-word schema's aggregator-facing check: the last 8 words - // are the carved L2G root, byte-equal to the bundle's claimed root. + // The published-word schema's aggregator-facing check: the last + // `lanes_per_root()` words are the carved L2G root — eight byte halves on a + // byte hash, four felts on an algebraic one — equal to the bundle's claimed + // root as the host publishes it. let root = bundle.epoch_view(n - 1).l2g_root(); - let published_root: Vec = proved.public_words[proved.public_words.len() - 8..] + let lanes = super::proof_arena::lanes_per_root(); + let published_root: Vec = proved.public_words[proved.public_words.len() - lanes..] .iter() - .map(|w| super::word::word_as_base(&w.1).expect("a root half is a base word")) - .collect(); - let expected_root: Vec = root - .chunks(4) - .map(|c: &[u8]| { - FE::from(u32::from_le_bytes(c.try_into().expect("a root is 32 bytes")) as u64) - }) + .map(|w| super::word::word_as_base(&w.1).expect("a root lane is a base word")) .collect(); + let expected_root: Vec = super::proof_arena::commitment_lanes(&root); assert_eq!( published_root, expected_root, "the wrap must publish the carved L2G root it verified under" From 82900936150279706428ec44f7ca08a234ba6778 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 23:55:45 -0300 Subject: [PATCH 18/23] fix(lfm): a host serializer at an explicit wrap hash's digest width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proof_arena's `commitment_words` / `commitments_to_arena` serialise a root at the CONFIGURATION's width, which is right for every program that follows `WrapHash::production()` and wrong for the ones that pin a byte hash on their own builder: those read two words per root whatever the pin says, and under an algebraic pin the host handed them one, so the executor's arena-length check refused the program (blake3_chip_tests' host/machine tree differential: "arena 0 expected 8 found 4"). `commitments_to_arena_for(roots, WrapHash)` and `commitment_words_for` are the host half of the rule the emitters already follow — the stride is the BUILDER's digest width. The differential feeds its keccak and BLAKE3 programs through it. Prediction: the_merkle_constructions_agree_with_the_host_under_both_hashes is green under the pin; the configuration-following helpers are unchanged. --- prover/src/lfm/blake3_chip_tests.rs | 7 +++---- prover/src/lfm/proof_arena.rs | 30 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/prover/src/lfm/blake3_chip_tests.rs b/prover/src/lfm/blake3_chip_tests.rs index 63aeee27a..8f639f4e0 100644 --- a/prover/src/lfm/blake3_chip_tests.rs +++ b/prover/src/lfm/blake3_chip_tests.rs @@ -721,10 +721,9 @@ fn the_merkle_constructions_agree_with_the_host_under_both_hashes() { b.public(root[1]); let program = compile(b.finish()); - let arena_words: Vec = leaves - .iter() - .flat_map(super::proof_arena::commitment_words) - .collect(); + // At THIS program's width (a byte hash, chosen on its builder above), + // not the configuration's: under an algebraic pin the two differ. + let arena_words: Vec = super::proof_arena::commitments_to_arena_for(&leaves, hash); let exec = execute(&program, &[arena_words], &TestPermutation) .unwrap_or_else(|e| panic!("{hash:?}: the tree build must execute: {e:?}")); assert_eq!( diff --git a/prover/src/lfm/proof_arena.rs b/prover/src/lfm/proof_arena.rs index 07789b79d..fd9374a30 100644 --- a/prover/src/lfm/proof_arena.rs +++ b/prover/src/lfm/proof_arena.rs @@ -372,6 +372,36 @@ pub fn commitments_to_arena(roots: &[Commitment]) -> Vec { roots.iter().flat_map(commitment_words).collect() } +/// [`commitments_to_arena`] at the width of an EXPLICIT wrap hash rather than +/// the configuration's — the host half of the rule that the arena stride is the +/// BUILDER's digest width. +/// +/// A program that pins a byte hash on its own builder reads two words per root +/// whatever the pin says (`edsl::digest_words` of that builder), so the host +/// feeding it must serialise at that width too; under an algebraic pin the +/// configuration-following [`commitments_to_arena`] would hand it one word per +/// root and the executor's arena-length check refuses the program outright. +pub fn commitments_to_arena_for(roots: &[Commitment], hash: super::edsl::WrapHash) -> Vec { + roots + .iter() + .flat_map(|c| commitment_words_for(c, hash)) + .collect() +} + +/// [`commitment_words`] at the width of an explicit wrap hash. See +/// [`commitments_to_arena_for`]. +pub fn commitment_words_for(c: &Commitment, hash: super::edsl::WrapHash) -> Vec { + if hash == super::edsl::WrapHash::Algebraic { + return vec![super::algebraic_commit::commitment_to_digest(c)]; + } + let halves = pack_stream(c); + debug_assert_eq!(halves.len(), ROOT_HALVES); + vec![ + [halves[0], halves[1], halves[2], halves[3]], + [halves[4], halves[5], halves[6], halves[7]], + ] +} + // ==================== the attestation's program id ==================== /// The inner ELF bytes the guest input carries. From 5b31faf48db7d6cec953f5ff38cf2c1561419bf8 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 23:56:36 -0300 Subject: [PATCH 19/23] test(decode): re-bless the sub ELF's offline decode commitment under the RPX pin SUB_DECODE_COMMITMENT_BLOWUP_2 is a blessed constant outside the four families compute_static_commitments regenerates: the DECODE preprocessed commitment of the `sub` asm ELF at blowup 2, computed offline once and embedded the way the recursion guest embeds its inner program's. It is committed under the block pin, so it moved with it; regenerated with the `print_decode_commitment_for_sub` helper at the pinned head. Its doc now names it as the fifth constant and its regeneration route; HASH-PINNING.md lists it with the other four. Prediction: decode_commitment_compile_time_const_accepts is green under the pin. --- prover/src/tests/decode_tests.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/prover/src/tests/decode_tests.rs b/prover/src/tests/decode_tests.rs index 4f7fa0221..44aa9ae14 100644 --- a/prover/src/tests/decode_tests.rs +++ b/prover/src/tests/decode_tests.rs @@ -237,13 +237,18 @@ fn decode_commitment_zero_bytes_rejects() { } /// DECODE preprocessed commitment for the `sub` asm test ELF at blowup=2, -/// computed offline once. Mirrors how the recursion guest embeds the -/// commitment as a compile-time constant for its inner program. If the -/// AIR or FFT pipeline changes, this drifts and the test fails — -/// regenerate via the `print_decode_commitment_for_sub` helper below. +/// computed offline once UNDER THE BLOCK PIN (`hash_pin::BLOCK_STARK_HASH`, +/// RPX256 here). Mirrors how the recursion guest embeds the commitment as a +/// compile-time constant for its inner program. +/// +/// ⚠ A fifth blessed constant, outside the four families +/// `compute_static_commitments` regenerates: it moves with the pin exactly as +/// they do, and `HASH-PINNING.md` lists it with them. If the pin, the AIR or the +/// FFT pipeline changes, this drifts and the test fails — regenerate via the +/// `print_decode_commitment_for_sub` helper below (`--ignored --nocapture`). const SUB_DECODE_COMMITMENT_BLOWUP_2: [u8; 32] = [ - 0xcd, 0xf1, 0xa8, 0xdf, 0x5b, 0x32, 0x67, 0xa4, 0x3c, 0x3d, 0x6b, 0x60, 0x1e, 0xe2, 0xbd, 0x22, - 0xb1, 0xc1, 0x7d, 0xb7, 0x8f, 0x67, 0x59, 0x70, 0x99, 0xbb, 0xa4, 0xfd, 0x0b, 0xca, 0x01, 0x2d, + 0xe6, 0xa9, 0x9f, 0x70, 0xfd, 0xdc, 0x7a, 0x0a, 0x65, 0xe1, 0x00, 0x56, 0x03, 0x0a, 0xe5, 0xc9, + 0x25, 0xef, 0xa9, 0x02, 0xe1, 0xd9, 0xd9, 0xd7, 0x62, 0xb1, 0x64, 0x11, 0x65, 0xe3, 0xa1, 0x5e, ]; #[test] From a0f1edb4ecf51ac06c5c048aaa79e709650b5b3b Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 8 Sep 2026 00:06:59 -0300 Subject: [PATCH 20/23] fix(tests): the merkle-opening and L2G production twins run under the pin's tenant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit merkle_opening_program and l2g_binding_program build at WrapHash::production(), so under an algebraic pin they emit Instr::Hash and must be proved and executed under BLOCK_HASHER — the classification rule in HASH-PINNING.md. Their four machine_tests built artifacts through build_artifacts (the registry's Test default) and their two tamper controls executed at a literal TestPermutation, so the walks ran a toy permutation over an algebraic tree. The keccak instruments and the BLAKE3-built register derivation keep the default: they pin a byte hash on their own builders and never consult the socket. Prediction: the_merkle_walk_authenticates_a_real_opening, tampered_merkle_opening_rejects, l2g_binding_proves_and_verifies and tampered_l2g_binding_rejects are green under the pin; the two DivByZero failures were the toy permutation, and if one survives this change it is a real emitter defect to investigate, not to paper over. --- prover/src/lfm/machine_tests.rs | 34 +++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/prover/src/lfm/machine_tests.rs b/prover/src/lfm/machine_tests.rs index f74300e0f..45bf1fde5 100644 --- a/prover/src/lfm/machine_tests.rs +++ b/prover/src/lfm/machine_tests.rs @@ -3285,7 +3285,13 @@ fn the_merkle_walk_authenticates_a_real_opening() { // root the host actually built. The keccak instrument cannot, and the name // moved with the hash rather than outliving it. let program = merkle_opening_program(R1F_SHAPE); - let artifacts = build_artifacts(&program, &opts); + // Built at `WrapHash::production()`, so it emits `Instr::Hash` and must be + // proved under the pin's tenant — the classification rule in HASH-PINNING.md. + let artifacts = super::registry::build_artifacts_with_hasher( + &program, + &opts, + crate::hash_pin::BLOCK_HASHER, + ); let proved = lfm_prove(&program, &artifacts, &merkle_arenas(opening, *index), &opts) .expect("the honest opening must execute and prove"); @@ -3336,7 +3342,13 @@ fn tampered_merkle_opening_rejects() { // Same production twin as the honest-path test above — a tamper control is // only a control over the walk the honest path uses. let program = merkle_opening_program(R1F_SHAPE); - let artifacts = build_artifacts(&program, &opts); + // Built at `WrapHash::production()`, so it emits `Instr::Hash` and must be + // proved under the pin's tenant — the classification rule in HASH-PINNING.md. + let artifacts = super::registry::build_artifacts_with_hasher( + &program, + &opts, + crate::hash_pin::BLOCK_HASHER, + ); let honest = lfm_prove(&program, &artifacts, &merkle_arenas(opening, *index), &opts) .expect("honest prove"); @@ -3404,7 +3416,7 @@ fn tampered_merkle_opening_rejects() { ); // Incoherent: still claiming the real root. - let err = super::executor::execute(&program, &arenas, &super::hash::TestPermutation) + let err = super::executor::execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() .unwrap_or_else(|| panic!("{what}: claiming the real root must not execute")); println!("R1f tamper {what}: incoherent run rejected with {err:?}"); @@ -3796,7 +3808,12 @@ fn l2g_binding_proves_and_verifies() { let opts = options(); let (epoch, global) = r1g_l2g_roots(); let program = l2g_binding_program(R1G_EPOCHS); - let artifacts = build_artifacts(&program, &opts); + // A production() program: proved under the pin's tenant, as above. + let artifacts = super::registry::build_artifacts_with_hasher( + &program, + &opts, + crate::hash_pin::BLOCK_HASHER, + ); let proved = lfm_prove(&program, &artifacts, &l2g_arenas(epoch, global), &opts) .expect("the honest binding must execute and prove"); @@ -3833,7 +3850,12 @@ fn tampered_l2g_binding_rejects() { let opts = options(); let (epoch, global) = r1g_l2g_roots(); let program = l2g_binding_program(R1G_EPOCHS); - let artifacts = build_artifacts(&program, &opts); + // A production() program: proved under the pin's tenant, as above. + let artifacts = super::registry::build_artifacts_with_hasher( + &program, + &opts, + crate::hash_pin::BLOCK_HASHER, + ); let honest = lfm_prove(&program, &artifacts, &l2g_arenas(epoch, global), &opts).expect("honest prove"); @@ -3861,7 +3883,7 @@ fn tampered_l2g_binding_rejects() { l2g_arenas(&swapped_one_side, global), ), ] { - let err = super::executor::execute(&program, &arenas, &super::hash::TestPermutation) + let err = super::executor::execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) .err() .unwrap_or_else(|| panic!("{what}: must not execute")); println!("R1g tamper {what}: rejected with {err:?}"); From 6ea2cfc8d4cff8ca35712f18867017761a7edd7b Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 8 Sep 2026 00:07:23 -0300 Subject: [PATCH 21/23] docs(lfm): regeneration order is statics first, registry second The registry embeds the static constants: registry.rs fills slots 13 and 14 of every entry from keccak_rc and bitwise's preprocessed_commitment, which at coset offset 3 return the BLESSED static constant in the tree rather than recomputing, and lfm_program_id folds every root. A registry generated before the statics are pasted therefore carries the outgoing hash's constants, the drift gate fires at exactly those two slots, and the control-first re-run under the outgoing pin cannot see it because both tables are self-consistent there. HASH-PINNING.md, the hash_pin.rs header and both generators' doc comments now state the order and the mechanism, and list the fifth blessed constant (SUB_DECODE_COMMITMENT_BLOWUP_2) with its regeneration route. HASH-PINNING.md's cuda section says what is true after the RPX device kernels landed: the RPX pin commits on the device, RPO256 and Poseidon still abort at the first device launch; the GPU wrap under cuda is the gate after the merge. It also records the arena-stride rule for both sides and the tests known red under this pin with their mechanism and disposition, so a red run is not rediscovered from scratch. --- HASH-PINNING.md | 79 ++++++++++++++------ prover/src/bin/compute_lfm_registry.rs | 8 ++ prover/src/bin/compute_static_commitments.rs | 5 ++ prover/src/hash_pin.rs | 51 ++++++++----- 4 files changed, 102 insertions(+), 41 deletions(-) diff --git a/HASH-PINNING.md b/HASH-PINNING.md index e21b50471..7c24b7a2a 100644 --- a/HASH-PINNING.md +++ b/HASH-PINNING.md @@ -122,15 +122,16 @@ and disagreed with any other, and the executor's arena-length check is strict: a program declaring a roots arena at a literal two words per digest is an `ArenaLenMismatch` under this pin, not a slow path. -## `cuda` on an algebraic pin — COMPILES, and still cannot prove under the wrong hash +## `cuda` on an algebraic pin — COMPILES, and cannot prove under the wrong hash `--features cuda` builds on this branch. The algebraic backends are `DeviceTreeBackend`s carrying their own `CommitmentHash` as the device dispatch key, so the type system pairs a device tree with the permutation it was named -for and cannot produce a keccak tree *labelled* RPX. `math-cuda` has no kernel -for the RPX permutation wired into its dispatch yet, so a GPU run under this pin -aborts at its first device commit with `unimplemented!` naming the hash — loud, -at launch, naming the cause. +for and cannot produce a keccak tree *labelled* RPX. RPX256 has device kernels +(`math_cuda::rpx`, with the leaf and tree dispatch arms wired), so a GPU run +under THIS pin commits on the device. RPO256 and Poseidon do not yet: a GPU run +under one of those pins aborts at its first device commit with `unimplemented!` +naming the hash — loud, at launch, naming the cause. ⛔ **Neither a `compile_error!` nor a byte-hash fallback belongs here.** The first hides the cuda lint arm from the branch, which is how a dispatch @@ -142,27 +143,41 @@ that quietly proves under the wrong hash is not. combination is a real signal here, unlike on the pre-dispatch `hash-rpo` cut where it was expected to fail. -**Consequence for box work:** proving a block under this pin means CPU until the -RPX kernels land in the dispatch. GPU boxes remain useful for the byte-hash lanes -only. +**Consequence for box work:** the RPX pin proves on the GPU; the first +end-to-end GPU prove under it (the wrap under `cuda`) is the milestone gate after +the pin lands. An RPO or Poseidon pin still means CPU-only proving. -## ⚠ TWO REGENERATIONS — a pin change is not complete without both +## ⚠ TWO REGENERATIONS — in THIS order, plus one stray constant -Every root blessed under BLAKE3 has to be regenerated, and there are two -families of them. This is why the pin PR is large and mostly generated tables — -that is EXPECTED, not a mistake. +Every root blessed under BLAKE3 has to be regenerated. There are two families of +them and the order between them is load-bearing. This is why the pin PR is large +and mostly generated tables — that is EXPECTED, not a mistake. -1. **`LFM_REGISTRY`** — the hasher and the commitment hash are both folded into - every `program_id`. `cargo run --bin compute_lfm_registry --release`. Per - entry the `roots`, `program_id` and `prep_root` move; `log_heights`, - `prep_widths`, `chip_set` and `keccak_rnd_chunks` are shape and must not. -2. **The static preprocessed commitments** — FOUR families, not three: +1. **The static preprocessed commitments — FIRST.** FOUR families, not three: `bitwise`, `keccak_rc`, and `page`'s zero-page AND private-page (OFFSET-only) constants, each at blowup 2/4/8. Each returns a BLESSED CONSTANT from `preprocessed_commitment` rather than recomputing, so under a new pin the prover recomputes an RPX root, compares it against a BLAKE3 constant and fails with `ProvingError::PrecomputedCommitmentMismatch`. - `cargo run --bin compute_static_commitments --release`. + `cargo run --bin compute_static_commitments --release`, then paste. +2. **`LFM_REGISTRY` — SECOND, only once the statics are in the tree.** The + hasher and the commitment hash are both folded into every `program_id`. + `cargo run --bin compute_lfm_registry --release`. Per entry the `roots`, + `program_id` and `prep_root` move; `log_heights`, `prep_widths`, `chip_set`, + `keccak_rnd_chunks` and `hasher` (`Test`, the registry's own) must not. + ⛔ **Why the order:** `registry.rs` fills slots 13 and 14 of every entry from + `keccak_rc` and `bitwise`'s `preprocessed_commitment` — the blessed constants + of item 1, not a recomputation — and `lfm_program_id` folds every root. A + registry generated before the statics were pasted embeds the OUTGOING hash's + constants in those two slots and in `program_id`, and + `machine_tests::registry_drift_*` fires at exactly slots 13 and 14. ✓ It did, + on this pin's first regeneration. The control-first re-run under the + outgoing pin cannot see this: both tables are self-consistent there. +3. **`SUB_DECODE_COMMITMENT_BLOWUP_2`** (`prover/src/tests/decode_tests.rs`) — + a test-local blessed constant outside both generators: the `sub` ELF's + DECODE commitment at blowup 2, embedded the way the recursion guest embeds + its inner program's. Regenerated by the `#[ignore]` test + `print_decode_commitment_for_sub` (`--ignored --nocapture`). That failure is the **trial flip** this pin's PR performs on purpose: flip the four `hash_pin.rs` lines without regenerating, run the crate's own prove/verify @@ -202,13 +217,20 @@ chain return `[Cell; 2]` because those digests genuinely are two cells. ## RUNNING UNDER THIS PIN -- **Every proving gate is a CPU run** (see the cuda section). The gates the pin - PR ran: `machine_tests::registry_drift_*` unchanged, `hash_pin`, - `tests::hash_pin_enumeration`, `fri_tests::the_fri_leg_proves_and_verifies`, +- **The gates the pin PR ran** (CPU, box B): `machine_tests::registry_drift_*` + unchanged, `hash_pin`, `tests::hash_pin_enumeration`, + `fri_tests::the_fri_leg_proves_and_verifies`, `join_tests::the_join_proves_and_verifies`, `wrap_tests::the_fixture_epoch_wraps`, the four grinding differentials in `algebraic_commit.rs`, then the full `--lib` suite and `make lint` on both - arms. + arms. The GPU wrap under `cuda` is the gate after the merge (see the cuda + section). +- **The arena stride is the BUILDER's digest width on both sides.** Emitters + read `edsl::digest_words(b)`; host serialisers read `words_per_root()` for a + configuration-following program and `commitments_to_arena_for(roots, hash)` + for a program that pins a byte hash on its own builder. A literal two words + per root is an `ArenaLenMismatch` under this pin, not a slow path — the + executor's arena-length check is strict. - **`P3_ARTIFACT_DIR` must be a FRESH directory for any block run.** The block driver *loads* cached artifacts when it finds them, so a directory carrying a BLAKE3 run's bundle and wraps would feed byte-hash proofs to an RPX verifier. @@ -227,5 +249,18 @@ chain return `[Cell; 2]` because those digests genuinely are two cells. batched number are not comparable, and no projected RPX line is carried over from the RPO pin. +## KNOWN RED UNDER THIS PIN — out of the pin PR's scope, by decision + +At the pin PR's head the full `--lib` suite is green except for these, each +recorded here so nobody rediscovers them from a red run: + +| test | mechanism | disposition | +|---|---|---| +| `epoch_tests::the_batched_query_census_matches_the_closed_form`, `epoch_tests::the_assembled_carved_batched_epoch_verifier_runs` | the census closures count `Instr::KeccakF` / `Instr::Blake3` only; the algebraic wrap hash is `Instr::Hash`, and `batched_query_permutations_for` has no algebraic arm | byte-hash counter model; algebraic arm owed | +| `fri_tests::the_emitted_permutation_count_meets_the_pinned_prediction`, `fri_tests::the_fri_join_adds_no_second_point_derivation` | "six component byteswaps per layer" and the leaf-swap decompositions are byte-encoding costs; an algebraic leaf needs none | byte-hash counter model; algebraic arm owed | +| `machine_tests::transcript_replay_cell_counts`, `machine_tests::register_derivation_cost` | `wrap_hash_rows` dispatches on `WrapHash::production()`, not on the PROGRAM's own builder hash, so a registry program pinned to BLAKE3 counts zero rows under an algebraic pin | the helper must read the program's hash | +| `machine_tests::the_register_derivation_matches_production`, `machine_tests::the_register_derivation_proves_and_verifies` | `register_derivation_program` is built at `WrapHash::Blake3` and has no algebraic arm, while production's REGISTER commitment now follows the pin | ⚠ FEATURE GAP (the machine REGISTER derivation under an algebraic hash), planned as its own item, not a test fix | +| `epoch_tests::the_closure_rejects_a_moved_index_or_output` | the fixture epoch reports an empty public output; fails at the pre-pin head too | PRE-EXISTING on `per-table-gpu`, not the pin's | + Poseidon is **UNSHIPPABLE** (broken family, eprint 2026/306 and 2026/1692) and remains a reference column only; XHash8 is flagged and **not adopted**. diff --git a/prover/src/bin/compute_lfm_registry.rs b/prover/src/bin/compute_lfm_registry.rs index 647ae7e84..ddc06ef70 100644 --- a/prover/src/bin/compute_lfm_registry.rs +++ b/prover/src/bin/compute_lfm_registry.rs @@ -4,6 +4,14 @@ //! output over the generated block in `prover/src/lfm/registry.rs`. Drift //! tests recompute and compare on every PR; a drift failure is investigated, //! never re-blessed (the `compute_static_commitments` policy). +//! +//! ⚠ ORDER: on a hash-pin change run this AFTER `compute_static_commitments` +//! has been run and its output pasted. Slots 13 and 14 of every entry are +//! `keccak_rc` and `bitwise`'s `preprocessed_commitment`, which return the +//! BLESSED static constants in the tree rather than recomputing, and +//! `program_id` folds every root — so a table generated before the statics +//! embeds the outgoing hash's constants, and `machine_tests::registry_drift_*` +//! fires at exactly those two slots. use lambda_vm_prover::GoldilocksCubicProofOptions; use lambda_vm_prover::lfm::programs::{ diff --git a/prover/src/bin/compute_static_commitments.rs b/prover/src/bin/compute_static_commitments.rs index 709257192..3f7bc9fa7 100644 --- a/prover/src/bin/compute_static_commitments.rs +++ b/prover/src/bin/compute_static_commitments.rs @@ -10,6 +10,11 @@ //! Run with: //! cargo run --bin compute_static_commitments --release //! +//! ⚠ On a hash-pin change run this FIRST and paste before `compute_lfm_registry`: +//! the registry embeds these constants (slots 13 and 14 of every entry, and +//! `program_id` folds them), so a registry generated before the paste carries +//! the outgoing hash's statics and the drift gate catches it. +//! //! ⚠️ Do not run this just to silence a failing drift test — see the //! "Regenerating" section on `static_commitment` in `bitwise.rs` / //! `keccak_rc.rs` and the two `page.rs` constants for when it's actually diff --git a/prover/src/hash_pin.rs b/prover/src/hash_pin.rs index 0eebf1581..051e8f96b 100644 --- a/prover/src/hash_pin.rs +++ b/prover/src/hash_pin.rs @@ -56,33 +56,46 @@ //! //! # `cuda` on an algebraic pin //! -//! Compiles, and still cannot prove under the wrong hash. The algebraic -//! backends are `DeviceTreeBackend`s carrying their own `CommitmentHash` as the -//! device dispatch key, and `math-cuda` has no kernels for those permutations -//! yet, so a GPU run under an algebraic pin aborts at its first device commit +//! Compiles, and cannot prove under the wrong hash. The algebraic backends are +//! `DeviceTreeBackend`s carrying their own `CommitmentHash` as the device +//! dispatch key, so a device tree is built by the kernels of the hash it is +//! named for or not built at all. RPX256 has those kernels (`math_cuda::rpx`), +//! so a GPU run under this pin commits on the device; RPO256 and Poseidon do +//! not yet, and a GPU run under one of them aborts at its first device commit //! with `unimplemented!` naming the hash. ⛔ Neither a `compile_error!` nor a //! byte-hash fallback belongs here: the first hides the cuda lint arm from the //! branch, the second is exactly the silent wrong-hash build this module exists -//! to make impossible. Proving a block under an algebraic pin on GPU means -//! landing the kernels, and nothing less. +//! to make impossible. //! -//! # ⚠ TWO regenerations, not one +//! # ⚠ TWO regenerations, not one — in THIS order, plus one stray constant //! //! A pin change is **not** complete until every root blessed under the old hash -//! is regenerated, and there are two families of them: +//! is regenerated. There are two families of them and the order is load-bearing: //! -//! 1. **`LFM_REGISTRY`** — the hasher is folded into every `program_id`. -//! `cargo run --bin compute_lfm_registry --release`. -//! 2. **The static preprocessed commitments** — FOUR families: `bitwise`, -//! `keccak_rc`, and `page`'s zero-page AND private-page constants. Each -//! returns a BLESSED CONSTANT from `preprocessed_commitment` rather than -//! recomputing, so under a new pin the prover recomputes an algebraic -//! root, compares it against a BLAKE3 constant, and fails with +//! 1. **The static preprocessed commitments — FIRST.** FOUR families: `bitwise`, +//! `keccak_rc`, and `page`'s zero-page AND private-page constants, at blowup +//! 2/4/8. Each returns a BLESSED CONSTANT from `preprocessed_commitment` +//! rather than recomputing, so under a new pin the prover recomputes an +//! algebraic root, compares it against a BLAKE3 constant, and fails with //! `ProvingError::PrecomputedCommitmentMismatch`. -//! `cargo run --bin compute_static_commitments --release`. -//! -//! ✓ VERIFIED (2) empirically: it is exactly how the trial flip failed, and it -//! is the correct failure — loud, at prove time, naming the cause. `registry.rs` +//! `cargo run --bin compute_static_commitments --release`, then paste. +//! 2. **`LFM_REGISTRY` — SECOND, only once the statics are in the tree.** +//! `registry.rs` fills slots 13 and 14 of every entry from `keccak_rc` and +//! `bitwise`'s `preprocessed_commitment` — the blessed constants above, not a +//! recomputation — and `lfm_program_id` folds every root. A registry generated +//! before the statics were pasted therefore embeds the OUTGOING hash's +//! constants, and `machine_tests::registry_drift_*` fires at exactly those two +//! slots. The control-first re-run under the outgoing pin cannot see this: +//! both tables are self-consistent there. +//! `cargo run --bin compute_lfm_registry --release`. +//! 3. **`SUB_DECODE_COMMITMENT_BLOWUP_2`** in `tests/decode_tests.rs` — a +//! test-local blessed constant outside both generators, regenerated by the +//! `#[ignore]` test `print_decode_commitment_for_sub`. +//! +//! ✓ VERIFIED (1) empirically: it is exactly how the trial flip failed, and it +//! is the correct failure — loud, at prove time, naming the cause. ✓ VERIFIED +//! (2) empirically too: the first RPX regeneration ran the registry before the +//! statics and all six drift tests fired at slots 13 and 14. `registry.rs` //! governs both: a drift failure is investigated, never re-blessed to silence //! the test, and neither table is ever hand-edited. From b71f0e6e72a207b03305eaf4e4e134cd1d3027bf Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 8 Sep 2026 02:21:49 -0300 Subject: [PATCH 22/23] fix(tests): the join control's sibling walk and the split-cell surplus follow the digest's width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two sites the stride commit described but did not change. join_tests' control program still hinted each sibling as a lo/hi pair with cursor += 2 while declaring its query arena at the builder's width, so the validator refused the program (ArenaOutOfBounds, arena 4); it hints each digest through edsl::hint_digest and advances by digest_words, as the production emitter does. epoch_tests' positive control asserted that the split-cell program declares exactly two surplus words; the surplus is one root, so it is words_per_root() — one under the pin, two at the BLAKE3 default. HASH-PINNING's known-red table gains per_table_census_tests::the_blake3_tenant_socket_matches_the_record: lane C's guard firing correctly, because the recorded census was produced under the Test/BLAKE3 socket and this build's is RPX; the guard is not weakened, the census is re-recorded as a follow-up. Prediction: the_controls_show_what_the_join_denies and the_assembled_verifier_declares_exactly_the_shape_words green under the pin and unchanged at the default. --- HASH-PINNING.md | 1 + prover/src/lfm/epoch_tests.rs | 6 +++--- prover/src/lfm/join_tests.rs | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/HASH-PINNING.md b/HASH-PINNING.md index 7c24b7a2a..2b49bed65 100644 --- a/HASH-PINNING.md +++ b/HASH-PINNING.md @@ -260,6 +260,7 @@ recorded here so nobody rediscovers them from a red run: | `fri_tests::the_emitted_permutation_count_meets_the_pinned_prediction`, `fri_tests::the_fri_join_adds_no_second_point_derivation` | "six component byteswaps per layer" and the leaf-swap decompositions are byte-encoding costs; an algebraic leaf needs none | byte-hash counter model; algebraic arm owed | | `machine_tests::transcript_replay_cell_counts`, `machine_tests::register_derivation_cost` | `wrap_hash_rows` dispatches on `WrapHash::production()`, not on the PROGRAM's own builder hash, so a registry program pinned to BLAKE3 counts zero rows under an algebraic pin | the helper must read the program's hash | | `machine_tests::the_register_derivation_matches_production`, `machine_tests::the_register_derivation_proves_and_verifies` | `register_derivation_program` is built at `WrapHash::Blake3` and has no algebraic arm, while production's REGISTER commitment now follows the pin | ⚠ FEATURE GAP (the machine REGISTER derivation under an algebraic hash), planned as its own item, not a test fix | +| `per_table_census_tests::the_blake3_tenant_socket_matches_the_record` | lane C's guard, firing correctly: the recorded census — and the lever-0 figure of record it anchors — was produced under the Test/BLAKE3 socket, and this build's socket is RPX | re-record the census under the pin (follow-up); do NOT weaken the guard | | `epoch_tests::the_closure_rejects_a_moved_index_or_output` | the fixture epoch reports an empty public output; fails at the pre-pin head too | PRE-EXISTING on `per-table-gpu`, not the pin's | Poseidon is **UNSHIPPABLE** (broken family, eprint 2026/306 and 2026/1692) and diff --git a/prover/src/lfm/epoch_tests.rs b/prover/src/lfm/epoch_tests.rs index 72785ff68..ae5aaec16 100644 --- a/prover/src/lfm/epoch_tests.rs +++ b/prover/src/lfm/epoch_tests.rs @@ -4421,9 +4421,9 @@ fn the_assembled_verifier_declares_exactly_the_shape_words() { let split_declared: usize = split.arena_schema.lens.iter().map(|l| *l as usize).sum(); assert_eq!( split_declared, - expected_arena_words(&e, false) + 2, - "the split-cell control must declare exactly two surplus words, or it is \ - not the forgery this guard claims to deny" + expected_arena_words(&e, false) + super::proof_arena::words_per_root(), + "the split-cell control must declare exactly one root's width of surplus \ + words, or it is not the forgery this guard claims to deny" ); } diff --git a/prover/src/lfm/join_tests.rs b/prover/src/lfm/join_tests.rs index cffd3e766..c9e859811 100644 --- a/prover/src/lfm/join_tests.rs +++ b/prover/src/lfm/join_tests.rs @@ -911,10 +911,11 @@ fn control_program_source( .collect(); let siblings: Vec = (0..shape.merkle_depth) .map(|_| { - let lo = b.hint_word(queries, cursor); - let hi = b.hint_word(queries, cursor + 1); - cursor += 2; - super::edsl::WrapDigest::from_pair(lo, hi) + // The stride follows THIS builder's digest width, as the + // production emitter's does — not a literal two. + let d = super::edsl::hint_digest(&mut b, queries, cursor); + cursor += dw; + d }) .collect(); GroupOpening { values, siblings } From 789c04c8a67e8cd476dc6d95a3c2da100478900e Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 8 Sep 2026 02:24:06 -0300 Subject: [PATCH 23/23] test(continuation): headroom on the mid-pipeline error liveness guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_prove_error_mid_pipeline_returns_err bounds a liveness property — the pipeline shuts down on error rather than wedging forever — with a fixed 300 s receive timeout. The regression it guards hangs FOREVER, so any finite bound catches it; the bound only has to clear an honest run under load. An algebraic hash pin doubles this test's own proving work (alone, three runs each: 7.3-7.6 s at the BLAKE3 default, 15.7-15.9 s under RPX), and inside the full --lib suite's parallel load the old bound fired while the test was still making progress. It is 1800 s now; the property asserted is unchanged. Prediction: the test passes inside the full suite under the pin; a wedge still fails, thirty minutes in instead of five. --- prover/src/continuation.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prover/src/continuation.rs b/prover/src/continuation.rs index db651e6b0..29da614f9 100644 --- a/prover/src/continuation.rs +++ b/prover/src/continuation.rs @@ -2489,7 +2489,15 @@ mod tests { let _ = done_tx.send(r.map(|_| ())); }); let result = done_rx - .recv_timeout(std::time::Duration::from_secs(300)) + // 1800 s of headroom rather than 300. This is a liveness guard — the + // regression it catches wedges the pipeline FOREVER, so any finite + // bound still catches it — and the bound has to clear an honest run + // under load: an algebraic hash pin doubles this test's own proving + // work (alone, three runs each: 7.3-7.6 s at the BLAKE3 default, + // 15.7-15.9 s under RPX), and inside the full `--lib` suite's + // parallel load the old 300 s fired while the test was still making + // progress. + .recv_timeout(std::time::Duration::from_secs(1800)) .expect("prove_continuation wedged: the pipeline did not shut down on error"); let err = result.expect_err("the injected fault must surface as Err"); assert!(