feat(fast-inbox): tag the first message of each inbox bucket in the rolling hash - #25319
Open
spalladino wants to merge 7 commits into
Open
Conversation
This was referenced Aug 26, 2026
spalladino
force-pushed
the
spl/a-1748-inbox-bucket-boundary-separator
branch
from
August 28, 2026 21:27
1b376de to
929edfb
Compare
spalladino
changed the base branch from
spl/rollup-config-immutable
to
spl/inbox-descendant-confirmed-eligibility
August 28, 2026 21:27
…olling hash
The Inbox rolling hash chained every message with a single domain separator, so
it committed to the message order but said nothing about how L1 packed the
messages into buckets; "consume whole buckets" rested entirely on the
ring-snapshot compare at propose.
The first message of a bucket now hashes with a second separator,
DOM_SEP__INBOX_ROLLING_HASH_BUCKET_START, and every other link keeps
DOM_SEP__INBOX_ROLLING_HASH. Preimage shape, length and the zero genesis are
unchanged. Two Inbox histories over the same leaves packed into different L1
blocks now reach different rolling hashes, which also gives the archiver a
hash-level detector for a same-leaves/different-packing L1 reorg.
InboxParityPrivateInputs gains bucket_starts: [bool; S], with one sha256 per
link and a boolean-selected separator. The circuit asserts lane 0 opens a bucket
whenever the checkpoint consumes anything, and that no padding lane claims to.
Cost: +751 / +3,007 / +12,031 gates on inbox_parity_{64,256,1024} (+0.13%), all
inside their existing padded sizes.
Reference vectors for the L1, Noir and TypeScript tests are derived independently
of all three by l1-contracts/scripts/inbox_rolling_hash_vectors.py.
sendL2Message moves 43,269 -> 43,360 (min/median), 46,588 -> 46,678 (mean) and 102,063 -> 102,144 (max) for the bucket-start test and the constant select; the Inbox deploy size grows by 54 bytes. The gas_benchmark scenario consumes no Inbox messages, so gas_benchmark.md and gas_benchmark_results.json are unchanged.
… TS stack
Recomputing the checkpoint's rolling hash now needs to know where each Inbox
bucket begins, which a flat list of leaves cannot express. A checkpoint's (or a
block's) messages are therefore carried as InboxMessageBundle = Fr[][]: the
leaves grouped per bucket in insertion order, every inner array non-empty. The
grouping is what the hash commits to, so there is no parallel flag array to keep
in sync, block bundles concatenate into the checkpoint bundle, and the archiver's
range queries already group per bucket internally.
The archiver's getL1ToL2MessagesBetween{Buckets,LeafCounts} return the bundle,
and it threads through the sequencer's bucket selection, the validator's
re-execution and checkpoint rebuild, the lightweight checkpoint builder and the
prover node. Sites that only need plain leaves - message tree appends, the
message sponge, per-block slices - flatten it. The Fr[][] to leaves + per-lane
bucket-start flags conversion happens once, in
InboxParityPrivateInputs.fromMessages.
…unctions Documents both rolling-hash separators and how buckets group the messages sent in one L1 block, and adds getCurrentBucketSeq/getBucket to the view-function table (the unresolved review thread on the streaming Inbox umbrella).
… tree root The three inbox_parity VKs change with bucket_starts, moving the VK tree root to 0x29b7bb0a371549fbde9a7c1967f9368c8b78f8d537b1c0a47626757643283c1d. Regenerated with AZTEC_GENERATE_TEST_DATA=1; the checkpoint-root crates execute against the new tomls and echo the same root.
spalladino
force-pushed
the
spl/a-1748-inbox-bucket-boundary-separator
branch
from
August 29, 2026 11:10
929edfb to
359ff40
Compare
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.
Stacked on #25351. Fixes A-1748.
What
The Inbox rolling hash now commits to how L1 packed messages into buckets, not just to their order. The first message of every bucket is hashed with a new domain separator,
DOM_SEP__INBOX_ROLLING_HASH_BUCKET_START; every other link keepsDOM_SEP__INBOX_ROLLING_HASH. Genesis stays 0; preimage length, sha256 block count andParityPublicInputsare unchanged. Two Inbox histories with the same leaves regrouped into different buckets now produce different rolling hashes.This gives the archiver's L1-reorg check a hash-level detector for same-leaves/different-packing reorgs, which are invisible to it today.
Hash.accumulateInboxRollingHash(prev, leaf, opensBucket);Inbox._absorbIntoBucketpassesbucket.msgCount == 0. Constants allowlist updated soConstantsGen.solcarries the new separator.InboxParityPrivateInputs<S>gainsbucket_starts: [bool; S];accumulate_inbox_rolling_hashselects the separator per link with a boolean select (still one sha256 per link, sharedaccumulate_sha256*untouched). Guards: lane 0 must open a bucket whennum_msgs > 0; padding lanes must not.InboxMessageBundle = Fr[][]— leaves grouped per bucket, inner arrays never empty. The archiver's range queries return it, and it is threaded through the sequencer, validator, block builder and prover.InboxParityPrivateInputs.fromMessagesis the single place the grouping is turned into flat leaves + flags. (A block's selected range can span several buckets, so the selector passes the archiver's grouping through unchanged.)l1-contracts/scripts/inbox_rolling_hash_vectors.py(plainhashlib) and pinned identically in Noir, Solidity and TS, including a boundary-commitment pair ([[11,22,33,44]]vs[[11,22],[33,44]]).getCurrentBucketSeq/getBucket(the open review thread from feat(fast-inbox): streaming L1-to-L2 messages, AZIP-22 (umbrella) #25041).Cost
bb gates --scheme ultra_honk, before → after: inbox_parity_64 578,633 → 579,384; inbox_parity_256 2,304,521 → 2,307,528; inbox_parity_1024 9,208,073 → 9,220,104 (+0.13% each, ≈11.7 gates per lane). None crosses its power-of-two padding (2^20 / 2^22 / 2^24), so proving time is unchanged. No other circuit is affected.sendL2Message: 43,269 → 43,360 gas (+91).Not a migration
The streaming Inbox has not shipped in any release, so this is a consensus-format change with no deployed state to migrate. It regenerates the three InboxParity VKs and, through the new VK tree root, the
rollup-*/Prover.tomlsample inputs.EpochProvingJobDataserialization changes shape (per-checkpoint bucket count + one vector per bucket); older persisted rerun snapshots will not deserialize.Testing
types(413) androllup_lib inbox(22) suites; the three parity crates compile andnargo executesolves the checkpoint-root sample inputs with the new VK tree root.testBucketBoundariesChangeTheChain.yarn build/format/lintclean.single-node/cross-chain/streaming_inbox.test.ts4/4 locally. The fullrollup_libNoir suite and the remaining e2e runs are left to CI.