From 320a23b15656f05238ebb5bffba0a0697456f22c Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 7 Sep 2026 16:19:16 -0300 Subject: [PATCH] test(lfm): the assembled epoch verifier executes under the PINNED permutation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `epoch_program` builds at `WrapHash::production()`, so by the classification rule the program emits `Instr::Hash` and must run under `BLOCK_HASHER`. Three call sites in this file passed a literal `TestPermutation` instead. Both tests stay non-ignored. - `the_assembled_epoch_verifier_runs` — the execution. - `the_assembled_verifier_rejects_tampered_leg_data` — the honest control, and the tamper loop. The middle one is the reason this is worth a PR rather than a tidy-up. The honest control is what a wrong socket permutation breaks first, and a tamper suite whose control is broken rejects every vector and reports a pass. Behaviour is unchanged at the current pin, and that is checkable rather than assumed: `impl LfmHasher for HasherKind` (hash.rs:299) dispatches `HasherKind::Test` to `TestPermutation` for `permute`, `compress_iv`, `transcript_iv`, `leaf_iv`, `compress`, `compress_out` and `transcript_out`, and the one defaulted method, `mode_iv`, composes three of those. So the two spellings agree across the whole trait surface while `BLOCK_HASHER` is `Test`, and diverge exactly when the pin moves — which is the point. Under a byte pin the naming is inert: `ByteWrapHash` lowers to the KECCAK / `LFM_BLAKE3` chips and emits no `Instr::Hash`, so the socket is never consulted. Under an algebraic pin the walks ARE `Instr::Hash`, and a toy permutation would rebuild roots the host never committed. No `hash_pin_enumeration` change: the gate scans for `DefaultStarkHash`, `DefaultStarkTranscript` and `HasherKind::default()`, this file names none of them, and its one `stark::config::` item is `Commitment`, which is allowed. Blessing the file would fire the gate's own `stale` assertion. --- prover/src/lfm/epoch_verify_tests.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/prover/src/lfm/epoch_verify_tests.rs b/prover/src/lfm/epoch_verify_tests.rs index 51c693d3d..36b9e86bb 100644 --- a/prover/src/lfm/epoch_verify_tests.rs +++ b/prover/src/lfm/epoch_verify_tests.rs @@ -53,7 +53,6 @@ use super::epoch_tests::RealBatchedEpoch; use super::epoch_verify::{TableVerifyShape, boundary_terms}; use super::executor::execute; use super::fri::FriShape; -use super::hash::TestPermutation; use super::sub_proof::{GroupShape, SubProofShape}; use super::word::{LfmWord, base_word, ext_word, word_as_ext}; @@ -506,8 +505,18 @@ fn the_assembled_epoch_verifier_runs() { let e = super::epoch_tests::real_epoch(); let program = super::epoch_tests::epoch_program(&e, true); let arenas = super::epoch_tests::epoch_arena_words(&e, true); - let exec = - execute(&program, &arenas, &TestPermutation).expect("the assembled verifier must execute"); + // ★ The PINNED socket permutation, not a literal. `epoch_program` builds at + // `WrapHash::production()`, and the classification rule is that such a + // program emits `Instr::Hash` and must run under `BLOCK_HASHER`; only a + // program pinning a byte hash on its own builder may take the default. + // + // ⚠ Under a BYTE pin this is inert — `ByteWrapHash` lowers to the KECCAK / + // `LFM_BLAKE3` chips and emits no `Instr::Hash`, so the socket is never + // consulted and a toy permutation was free and correct. Under an ALGEBRAIC + // pin the walks ARE `Instr::Hash`: a toy would rebuild roots the host never + // committed, and this test would fail on its HONEST path, naming nothing. + let exec = execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER) + .expect("the assembled verifier must execute"); // ---- the spine's differential, unchanged: production's own challenges. let pub_ext = |i: usize| word_as_ext(&exec.public_words[i].1).expect("an ext challenge"); @@ -1045,8 +1054,12 @@ fn the_assembled_verifier_rejects_tampered_leg_data() { let e = super::epoch_tests::real_epoch(); let program = super::epoch_tests::epoch_program(&e, true); let good = super::epoch_tests::epoch_arena_words(&e, true); + // The pin, for the same reason as `the_assembled_epoch_verifier_runs`: this + // is the same `WrapHash::production()` program. It matters most on THIS + // arm — the honest control is what a wrong socket permutation breaks first, + // and a tamper suite whose control is broken rejects everything and passes. assert!( - execute(&program, &good, &TestPermutation).is_ok(), + execute(&program, &good, &crate::hash_pin::BLOCK_HASHER).is_ok(), "the untampered assembled verifier must run" ); @@ -1116,7 +1129,7 @@ fn the_assembled_verifier_rejects_tampered_leg_data() { let before = arenas[*arena][*word]; arenas[*arena][*word][0] = before[0] + FE::one(); assert!( - execute(&program, &arenas, &TestPermutation).is_err(), + execute(&program, &arenas, &crate::hash_pin::BLOCK_HASHER).is_err(), "tampering {label} must make the assembled verifier unexecutable, \ and did not" );