feat(fast-inbox): require every block to start consuming at an inbox bucket boundary - #25322
Open
spalladino wants to merge 2 commits into
Open
Conversation
spalladino
marked this pull request as ready for review
August 26, 2026 18:22
spalladino
force-pushed
the
spl/inbox-block-bucket-boundary
branch
from
August 28, 2026 21:49
1820f7c to
c04980f
Compare
…bucket boundary L1 already forces a checkpoint to consume whole Inbox buckets, but nothing in the circuits stopped a proposer from splitting one bucket across two blocks of the same checkpoint: block roots absorbed their message bundle into the threaded L1-to-L2 message sponge with no notion of buckets, and the per-block alignment was only rejected by the validator's TS checks. The block bundle now carries per-lane bucket-start flags, and the message sponge absorbs each leaf packed with its flag as `leaf + flag * 2^248` (leaves are truncated sha256 digests, so they always fit in 248 bits, and the sponge range-checks them so the packing is injective). InboxParity and every block root absorb the same packed stream, so the checkpoint-root sponge equality now also forces the concatenation of the blocks' flags to equal the checkpoint's L1-anchored flags; each block root then asserts its first consumed message opens a bucket. A block consuming several whole buckets is still allowed. Packing the flag rather than absorbing it as a separate field keeps the cost to a range check per lane: +0.14-0.17% on the inbox parity circuits, +0.24% / +0.48% on the block root / single-tx block root, and +3.9k gates on the 67k-gate no-txs block root. Public input shapes are unchanged. On the TS side the prover threads the bucket-grouped bundle down to the block proving state, `sliceBundle` cuts a checkpoint's bundle into per-block bundles and refuses to cut through a bucket, and the message sponge mirror packs identically.
…-boundary block bundle The block bundle gains bucket_starts and the inbox-parity and block-root circuits change bytecode, moving the VK tree root to 0x0113e960c28312482428135b1343817f6d1da382f8324fc6a399eb85e367fb05. Regenerated with AZTEC_GENERATE_TEST_DATA=1; all nine crates execute against the new tomls.
spalladino
force-pushed
the
spl/inbox-block-bucket-boundary
branch
from
August 29, 2026 11:10
c04980f to
0fb5708
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 #25319.
Why
L1 forces a checkpoint to consume whole Inbox buckets (the header rolling hash must match a bucket snapshot), but nothing in the circuits stopped a proposer from splitting one bucket across two blocks of the same checkpoint. Block roots absorbed their bundle into the threaded L1-to-L2 message sponge with no notion of buckets; per-block alignment was only rejected by the validator's TS checks.
What
L1ToL2MessageBundle(Noir + TS) gains per-lanebucket_starts. Each block root asserts its first consumed message opens a bucket (and no flag sits in padding) via a sharedassert_valid_bucket_starts, also used byInboxParity.L1ToL2MessageSpongeabsorbs each leaf packed with its flag:leaf + flag * 2^248, one field per message (stream length unchanged). Leaves are truncated sha256 digests, so they always fit in 248 bits; the sponge range-checks them so the packing is injective (otherwise a block root could absorbleaf + 2^248with the flag cleared and still match the parity's sponge).InboxParityand the block roots absorb the same packed stream, the checkpoint-root sponge equality now forces the blocks' flags to equal the checkpoint's L1-anchored flags. A block consuming several whole buckets is still allowed.InboxMessageBundledown toBlockProvingState;sliceBundlecuts a checkpoint bundle into per-block bundles and refuses to cut through a bucket; the sponge mirror packs identically and rejects oversize leaves. Tests that fedFr.random()as message leaves now userandomL1ToL2MessageLeaf().Public input shapes are unchanged. VKs of the three inbox parity circuits and all block-root variants change.
Cost (
bb gates --scheme ultra_honk)The alternative of absorbing the flag as a separate field was measured at roughly 3x this (+0.44% parity, +0.69% / +1.38% / +15.9% on the block roots) and rejected.
The rollup sample inputs (
Prover.toml) are regenerated for the new VK tree root.