feat: introduce inboxRollingHash end to end, dual with inHash (A-1373)#24600
feat: introduce inboxRollingHash end to end, dual with inHash (A-1373)#24600spalladino wants to merge 5 commits into
Conversation
| const start = baseParityIndex * NUM_MSGS_PER_BASE_PARITY; | ||
| const realMessages = this.l1ToL2Messages.slice(start, start + NUM_MSGS_PER_BASE_PARITY); | ||
| const messages = padArrayEnd(realMessages, Fr.ZERO, NUM_MSGS_PER_BASE_PARITY); | ||
| // Thread the rolling hash: this base's start is the chain value after all real messages in earlier bases, so the | ||
| // four bases chain sequentially and the parity root ends at the checkpoint's rolling hash. Only real (non-padding) | ||
| // messages are absorbed, matching the proposer's `accumulateInboxRollingHash`. | ||
| const startRollingHash = accumulateInboxRollingHash( | ||
| this.startInboxRollingHash, | ||
| this.l1ToL2Messages.slice(0, start), | ||
| ); |
There was a problem hiding this comment.
I don't think this is correct. Aren't the this.l1ToL2Messages already padded? And shouldn't realMessages drop the pads? Or is that left for a future PR?
There was a problem hiding this comment.
No — this.l1ToL2Messages is unpadded here. It's the array handed to CheckpointSubTreeOrchestrator.start, which comes from l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber) — the archiver's message_store.getL1ToL2Messages, which returns only the real messages (e.g. archiver-sync.test.ts expects length 0 for an empty checkpoint). Padding happens downstream in two places: per-base in this very method (padArrayEnd to NUM_MSGS_PER_BASE_PARITY, unchanged from before this PR) and inside appendL1ToL2MessagesToTree for the world-state insert. So realMessages is exactly the real slice, realMessages.length is the true per-base num_msgs, and the rolling hash accumulates over real messages only — matching the proposer's accumulateInboxRollingHash over the same raw archiver list. (In #24603 this class additionally gains an explicit paddedL1ToL2Messages for the sponge/first-block bundle, padded once in the constructor, which keeps the two representations clearly separated.)
There was a problem hiding this comment.
Why is this needed? How were those inputs generated before for the tests? Or are these new tests?
There was a problem hiding this comment.
The committed crates/rollup-*/Prover.toml sample inputs (which CI nargo executes) had to be regenerated because this PR changes the rollup public-input ABIs. Previously they were regenerated by running the heavyweight single-node prover e2e (end-to-end/src/single-node/prover/server/full.test.ts) with AZTEC_GENERATE_TEST_DATA=1 — and that test's dump list has several of the affected circuits commented out (rollup-block-merge, rollup-block-root-first-empty-tx, …), so it couldn't refresh everything this PR touches. The new file is a lightweight prover-client-level harness: it drives the simulated orchestrator through a handful of scenarios chosen to produce every changed circuit's inputs and dumps them via the same updateProtocolCircuitSampleInputs mechanism. It's describe.skip unless AZTEC_GENERATE_TEST_DATA=1, so it's a no-op in normal CI. Follow-up worth considering: dedup with (or replace) the e2e dump list so there's one canonical regeneration path.
c84a6cc to
c818a3d
Compare
e5d2b0e to
1e17c26
Compare
Adds the Fast Inbox (AZIP-22) rolling-hash commitment as a dual of the
legacy inHash, flowing through parity circuits, block/checkpoint rollup
public inputs, the checkpoint header, L1, and the node/prover.
Circuits (noir):
- Parity base absorbs only real leaves into a truncated-to-field sha256
chain (accumulate_inbox_rolling_hash, guarded by num_msgs +
assert_trailing_zeros); merkle roots keep absorbing padding. Parity
public inputs gain start_rolling_hash / end_rolling_hash / num_msgs;
parity root threads the four base segments sequentially and sums counts.
- BlockRollupPublicInputs and CheckpointRollupPublicInputs carry a
{start,end} pair propagated exactly like in_hash (first block root sets
it, merges take left's, validate asserts right's is zero). Checkpoint
merge asserts right.start == left.end (decision 11). CheckpointHeader
gains inbox_rolling_hash (the end value) immediately after in_hash.
- Root rollup public inputs are unchanged: exposing the epoch pair is
inseparable from the L1 epoch-proof anchoring that is out of scope here
(FI-08/FI-14), so it is deferred with the rest of L1 anchoring.
TS: stdlib inbox_rolling_hash mirror (unit-tested against the FI-02
vectors), CheckpointHeader / parity / block / checkpoint PI classes,
noir-protocol-circuits-types conversions, orchestrator threading of
per-base start hashes + counts, and node header population at the
sequencer / prover / validator sites (sourced from the previous
checkpoint header via getPreviousCheckpointInboxRollingHash).
L1: ProposedHeader + ProposedHeaderLib.hash pack inboxRollingHash after
inHash (no new propose checks); test fixtures + headerHash regenerated.
Add the {previous, end} inbox rolling-hash pair to RootRollupPublicInputs so
the epoch's consumed chain segment is carried through to proof verification.
The root rollup sources the pair from the merged checkpoint public inputs;
continuity within the range is already enforced by the checkpoint merges.
On L1, PublicInputArgs gains previousInboxRollingHash/endInboxRollingHash and
EpochProofLib places them at public-input positions 3 and 4 (header hashes,
fees, constants and blob inputs shift by two). Both values are deliberately
unvalidated until the Fast Inbox flip, when they will be checked against the
per-checkpoint records written at propose; for now they are pass-through only.
Also fixes two ivc-integration benchmark generators whose hand-built parity
base inputs were missing start_rolling_hash/num_msgs.
…1373) Exposing the inbox rolling hash on the parity-root and rollup public inputs changed the ABI of the block-root-first variants, block-merge, checkpoint-merge and root circuits, but their committed crates/rollup-*/Prover.toml sample inputs were not refreshed. CI runs `nargo execute` against these fixtures, so all six failed to deserialize (e.g. missing parity_root.public_inputs.start_rolling_hash and previous_rollups[].public_inputs.start_inbox_rolling_hash). Regenerate the six stale fixtures. The circuits push their serialized inputs via pushTestData when run through the prover, so a new prover-client test drives representative epochs through the simulated orchestrator and dumps the captured inputs, replacing the dead orchestrator_single_checkpoint.test.ts regeneration path. The suite is skipped unless AZTEC_GENERATE_TEST_DATA=1 is set.
A-1373 changes the rollup public-input ABIs, so the base branch's committed pinned-build.tar.gz no longer matches the compiled circuits. Remove it rather than carry a stale archive: bootstrap recompiles from source whenever the pin is absent, and the pin is regenerated once the ABI settles.
fe246df to
b76ab03
Compare
Stacked on #24587 (A-1372). Part of the AZIP-22 Fast Inbox work.
What
Introduces the
inboxRollingHash— a truncated-to-field sha256 rolling chain over the L1→L2 message leaves — end to end, carried as a dual of the legacyinHash. The legacyinHashremains authoritative; the rolling hash is computed and threaded everywhere but not yet enforced on L1, so this change is behavior-preserving pre-flip.Each link is
h' = sha256ToField(h_be32 || leaf_be32)with the top byte of the digest zeroed, genesis value zero.Changes
Circuits (noir)
start_rolling_hash→end_rolling_hash,num_msgs), asserting trailing padding lanes are zero. Parity root asserts chunk continuity (children[i].start == children[i-1].end) and sums the counts.{start, end}rolling-hash pair, propagated exactly likein_hash. Checkpoint merges assertright.start == left.end(decision 11 anchoring).inbox_rolling_hashimmediately afterin_hash.{previous, end}pair sourced from the merged checkpoint public inputs, so the epoch's consumed chain segment is passed through to proof verification.L1 (Solidity)
ProposedHeaderLibserializes the new header field (header 348 → 380 bytes).PublicInputArgsgainspreviousInboxRollingHash/endInboxRollingHash;EpochProofLibplaces them at public-input positions 3 and 4 (header hashes, fees, constants and blob inputs shift by two). Both values are deliberately unvalidated until the Fast Inbox flip — for now they are pass-through only.TypeScript
updateInboxRollingHash/accumulateInboxRollingHashmirror the circuit chain;getPreviousCheckpointInboxRollingHashsources the previous checkpoint's end value (returns zero for checkpoint ≤ 1). The sequencer, validator, and prover populate the header field; the orchestrator threads per-base start hashes; the prover-node publisher fills the two newPublicInputArgs.RootRollupPublicInputsgains the pair with matching serialization, conversion, factories and viem types.Constants
CHECKPOINT_HEADER_LENGTH13 → 14,BLOCK_ROLLUP_PUBLIC_INPUTS_LENGTH56 → 58,CHECKPOINT_ROLLUP_PUBLIC_INPUTS_LENGTH149 → 151,ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH111 → 113.pinned-build.tar.gzno longer matches the compiled circuits. It is dropped here rather than carried stale —bootstrap.shrecompiles from source whenever the pin is absent — and regenerated once the ABI settles. (feat: message-bundle components in rollup-lib (A-1372) #24587 keeps the base pin untouched, so the artifacts stay pinned on the train until this PR.)Testing
yarn buildgreen;forge testgreen (870 passed); noir rollup-lib root suites green (176 passed).l1_to_l2e2e confirms the header field round-trips through L1 and the archiver (decodedinboxRollingHash= 0 for the genesis checkpoint, as expected). This suite is registered flaky (.test_patterns.yml); the repeated-consumption cases exhibit a pre-existing nullifier-sync timing race unrelated to this change.