feat(lfm): pin per-table-gpu's block path to RPX256 - #967
Merged
Conversation
…ild_artifacts Reverts 13453ef'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 13453ef changed. The control validated the generator against itself. (cherry picked from commit 5e92356)
…ot to a silent global 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 a6e7f0b)
…review miss 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 28b7a36)
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 d66b568)
…— name the pin instead 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 98b0798 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 75efe38)
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 048d744)
An eighth variant, and the third time the gate's symbol list has been short. stark::config::BatchedMerkleTreeBackend<F> is BatchBlake3Backend<F> by definition, and FriLayerMerkleTreeBackend<F> is PairBlake3Backend<F>. 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 73ccc6d)
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 7226c63)
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.
…figuration's
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 0998fd6 and closes the two sites it left.
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.
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.
… named 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.
…irst 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.
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.
…over 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.
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.
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.
…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.
… pin's tenant 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.
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.
…s follow the digest's width 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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Pins
per-table-gpu's block path to RPX256, ported from thehash-rpopin stack and translated to RPX, in a commit order that never leaves the tree where flipping the pin moves registry identities.build_artifactsdefaults toREGISTRY_HASHER(ports of hash-rpo5e923567,a6e7f0b7,28b7a360).per-table-gpu'sbuild_artifactsrouted throughhash_pin::BLOCK_HASHER, so a pin flip alone would have moved everyLFM_REGISTRYprogram_id; the blessed value and the builder default are now one definition, and the 17 block-path sites (wrap_tests8,aggregator_tests7,fri_tests1,join_tests1) passBLOCK_HASHERexplicitly.d66b5682,75efe380,048d744c,73ccc6d7): F1, theProver::multi_prove/Verifier::multi_verifyalias sweep across thirteen files, the Merkle backend aliases through the pin.7226c63c):ALIAS_CALLSplus aCONFIG_ALLOWEDallowlist overstark::config, extended here withDeviceTreeBackendand withProver::compute_precomputed_commitment_for_testingas a third alias call form (it let a BLAKE3 commitment into the bitwise soundness control).SubProofShape,FriShapeandTableVerifyShapetake the width as an argument; the machine side passesedsl::digest_words(b), the host sideproof_arena::words_per_root(), andcommitments_to_arena_for(roots, WrapHash)serialises for programs that pin a byte hash on their own builder. Every remaining literal two-words-per-root in the LFM tests and inl2g_binding_program_sourcefollows the width; the executor's arena-length check is strict, so each was anArenaLenMismatchunder the pin.BlockStarkHash = RpxStarkHash,BlockTranscript = AlgebraicTranscriptbuiltwith_seed(BLOCK_HASHER, ..),BLOCK_HASHER = HasherKind::Rpx. Committed first WITHOUT tables as the trial flip HASH-PINNING prescribes.bitwise,keccak_rc,pagezero-page,pageprivate-page at blowup 2/4/8), thenLFM_REGISTRYregenerated in a tree that already carries them, plus the fifth blessed constant (SUB_DECODE_COMMITMENT_BLOWUP_2). The first registry regeneration ran before the statics and all sixregistry_drift_*fired at exactly slots 13 and 14, whichregistry.rsfills from the blessed static constants; the order is now documented in HASH-PINNING.md,hash_pin.rsand both generators.Evidence (box B, CPU; relayed by the coordinator)
7bc4c221: both generators exit 0; compared locally against the committed sources,LFM_REGISTRYreproduces byte for byte on all six entries and all twelve static constants are identical.7bc4c221:13 passed; 0 failed.af8ae82b(pin, no tables): exit 101,0 passed; 4 failed, every failurePrecomputedCommitmentMismatch— the loud prove-time failure HASH-PINNING prescribes.--libat542f9526:1082 passed; 38 failed; the control full--libat7bc4c221:1119 passed; 1 failed(the pre-existing fixture-output test below). All 37 pin-only failures were classified by file:line; 27 are fixed in the commits above, the remaining ten are the table below. The continuation liveness guard's timeout got headroom (300 s → 1800 s) after the test passed alone three times at each head — 7.29 / 7.32 / 7.55 s at the tip, 15.73 / 15.85 / 15.67 s at the pinned head — showing the pin doubles that path and the old bound was the constraint under suite load; it passed inside the final full run.789c04c8(23 signed commits): (5a) the §3b list +registry_drift_+hash_pin+tests::hash_pin_enumeration→ok. 13 passed; 0 failed; 0 ignored; 1163 filtered out; finished in 167.49s, all sixregistry_drift_*included; (5b) the two last-fixed tests by name →ok. 2 passed; 0 failed; (5c) both regenerators re-run under the pin are idempotent against the committed sources (LFM_REGISTRY3,264 hex bytes identical in sequence, all twelve static constants verbatim); (5e) full--lib→FAILED. 1119 passed; 10 failed; 47 ignored— exactly the ten in the table below, nothing else. ⚠ That run's wall time (4,634 s) is not comparable to the earlier 2,944 s: an orphaned test binary from a killed run shared box B for its first hour, so the suite ran contended; it is a pass/fail gate and the result stands, the duration does not.make lint(both arms): exit 0.Laptop:
cargo check -p lambda-vm-prover --lib --testsandcargo clippy -D warnings -A clippy::op_refon both feature arms green at every commit; fmt clean; a local replica of the enumeration gate's scan matches the blessed set exactly.Known red under the pin — out of scope by decision, documented in HASH-PINNING.md
epoch_tests::the_batched_query_census_matches_the_closed_form(3300),epoch_tests::the_assembled_carved_batched_epoch_verifier_runs(3382)Instr::KeccakF/Instr::Blake3only; the algebraic wrap hash isInstr::Hash;batched_query_permutations_forhas no algebraic armfri_tests::the_emitted_permutation_count_meets_the_pinned_prediction(867),fri_tests::the_fri_join_adds_no_second_point_derivation(983)machine_tests::transcript_replay_cell_counts(1543),machine_tests::register_derivation_cost(4437)wrap_hash_rows(3460) dispatches onWrapHash::production()rather than the program's own builder hash, so BLAKE3-built registry programs count zero rowsmachine_tests::the_register_derivation_matches_production(4290),machine_tests::the_register_derivation_proves_and_verifies(4545)register_derivation_programis built atWrapHash::Blake3with no algebraic arm while production's REGISTER commitment follows the pinper_table_census_tests::the_blake3_tenant_socket_matches_the_recordepoch_tests::the_closure_rejects_a_moved_index_or_output(4691)continuation::tests::test_prove_error_mid_pipeline_returns_err(continuation.rs:2492)Behaviour to know
cudathe RPX pin commits on the device (feat(math-cuda): RPX256 leaf and tree kernels, launch module and device dispatch (lane K, phase 2) #962); RPO256 and Poseidon pins still abort at the first device commit withunimplemented!naming the hash. The first end-to-end GPU prove under RPX is the milestone gate after the merge.registry_drift_*must pass unchanged. A moved identity means the pin leaked into registry identity; the fix is never a re-bless.P3_ARTIFACT_DIRmust be fresh for any block run: the driver loads cached artifacts, and a BLAKE3 run's bundle would be fed to an RPX verifier.