feat(blobs): remove the ts blob oracle - #25442
Draft
AztecBot wants to merge 1 commit into
Draft
Conversation
Collaborator
Author
|
⏳ Claude Review — starting review session... |
Collaborator
Author
|
✅ Claude Review — completed VERDICT: ERROR The reviewer did not complete successfully or did not emit the required PASS/BLOCK verdict. Failed to authenticate. API Error: 401 The API Key appears to be invalid or may have expired. Please verify your credentials and try again. |
Collaborator
Author
|
Claude Review: Starting automated code review... workflow run |
Collaborator
Author
|
Claude Review: Starting automated code review... workflow run |
AztecBot
force-pushed
the
cb/remove-blob-oracle
branch
from
September 10, 2026 08:01
d80e535 to
6eb5600
Compare
AztecBot
changed the base branch from
cb/unconstrained-blob-eval-speedup
to
next
September 10, 2026 08:01
Collaborator
Author
|
Claude Review: Starting automated code review... workflow run |
AztecBot
added a commit
that referenced
this pull request
Sep 10, 2026
Refs #10323. Independent of the rest of that work — it lands on `next` on its own. `__compute_fracs` builds `w^i / (z - w^i)` for all 4096 blob positions. It was a 4096-element Montgomery batch inversion (3 multiplications per element) plus a pass to apply the numerator: ~4d multiplications and one inversion. The roots of unity make almost all of that unnecessary. ## The identity Write `z_k = z^(2^k)` and `w_k = w^(2^k)`, and let `I_k[t] = 1 / (z_k - w_k^t)` over `t = 0 .. d/2^k - 1`. The top level is a single entry, `1 / (z^d - 1)` — the one inversion. Each level below follows by a difference of two squares, since `w_{k-1}^(t+m) = -w_{k-1}^t` for `m = d/2^k`: ``` (z_{k-1} - w_{k-1}^t)(z_{k-1} + w_{k-1}^t) = z_k - w_k^t ``` so one parent yields two children for two multiplications: ``` I_{k-1}[t] = (z_{k-1} + w_{k-1}^t) * I_k[t] I_{k-1}[t + m] = (z_{k-1} - w_{k-1}^t) * I_k[t] ``` Levels halve going up, so reaching `I_1` costs about `d` multiplications in total. The last level folds the `w^i` numerator in as it descends, sharing the `z*w^t` product across each pair — 3 multiplications per pair rather than the 4 a separate numerator pass needs. Total: ~2.5d multiplications and one inversion, against ~4d and one inversion. ## The indexing falls out of the EIP-4844 layout `ROOTS` is stored bit-reversed. That turns out to make the recursion trivial to index: a parent at array index `u` has its children at `2u` and `2u + 1`, and the root it needs is `ROOTS[2u]` at every level. At the final level `ROOTS[2u]` is `w^t` and `ROOTS[u]` is `w^2t`, so both terms are plain lookups. I got this wrong first time by assuming natural order — the halves are `i` and `i + d/2` there, not `2u` and `2u + 1` — and it produced fracs that failed the defining relation. `test_roots_are_stored_adjacent_to_their_negations` pins the property the indexing rests on, because the failure mode is silent corruption of half the array. ## Measurements `compute_fracs` alone, native ACVM, harness overhead subtracted, min of 5 on a loaded shared host: | | time | |---|---| | batch inversion | 0.915s | | roots-of-unity halving | 0.585s | **1.56x**, saving ~0.33s. This is a hint used by *both* execution paths, so it should take roughly the same ~0.33s off constrained witness generation as well — `__compute_fracs` is ~0.86s of the ~1.4s of Brillig hint work inside the ~11s constrained `rollup-checkpoint-root` witness generation. I have not measured the constrained side (it needs an 8-minute circuit compile per arm); worth confirming before quoting that number. It also matters for #10323: with the oracle removed (#25442) this takes a checkpoint-root simulation from ~1.40s to ~1.05s. ## Correctness The ACIR is untouched — this only changes how the hint is produced, and `validate_fracs` constrains the result exactly as before. The existing `test_validate_fracs_*` tests pass, which is the real contract: the new hint still satisfies the circuit's constraints. New `test_compute_fracs_satisfies_defining_relation_everywhere` checks `fracs[i] * (z - ROOTS[i]) == ROOTS[i]` for **all** 4096 entries rather than spot-checking a few indices, because an error at one level of the recursion corrupts a whole contiguous run of the output and a spot check would miss it. Degenerate input behaves as before: if `z` is itself a d-th root of unity the single inversion is zero and the array degenerates to zeros, exactly as `batch_invert` mapped a zero denominator to zero. `validate_fracs` rejects that either way, and reaching it requires a Poseidon2 preimage. Ran: the `blob` crate's `fracs` and `validate_fracs` tests (6 passing) plus the new invariant test. Not the full suite — CI is the complete check. The `blob` and `bignum` crates are byte-identical between the commit those runs were made against and current `next`, so the rebase did not move the code under the measurements or the test results. --- *Created by [claudebox](https://claudebox.work/v2/sessions/99aba4482349eaa5/jobs/9) · group: `slackbot` · requested by Tom (@TomAFrench) · [Slack thread](https://aztecfoundation.slack.com/archives/D0B586H14KG/p1788969921421879?thread_ts=1788969921.421879&cid=D0B586H14KG)*
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.
The unconstrained path now computes the blob accumulator itself instead of
asking TypeScript for it.
Refs #10323 — deliberately not
Closes, because the aztec-node side stillcarries a now-dead
evaluateBlobsforeign call handler. Close the issue by handonce that handler is deleted too.
Independent of #25439 and #25444: this touches
blob/src/lib.nr, the checkpointroot composer and the rollup-lib tests, none of which those PRs modify. #25439
makes the path this PR takes ~1.7x faster, and #25444 a further ~1.3x, but
neither is a prerequisite — they just improve the number below.
Why this is affordable
The oracle existed because simulating the blob evaluation was too slow. It no
longer is, relative to what the same checkpoint already pays. Measured on
rollup-checkpoint-root's checked-inProver.toml, native ACVM, interleavedround-robin so host drift hits both arms equally:
Simulation is ~8x cheaper than the witness generation the same checkpoint
already pays for the identical blob math, before proving even starts. The
constrained circuit is fixed-size, so witness generation stays ~11s however full
the blobs are, while simulation scales with the number of accumulated blobs —
~3.2s for six full blobs, still ~3.5x under.
(Those figures are with #25439 applied. Without it the simulation arm is 2.34s
rather than 1.41s — still comfortably under witness generation.)
Full method and tables: https://gist.github.com/AztecBot/4509eae64b383132e5cb3f6eaca04710
Changes
checkpoint_rollup_public_inputs_composer.nr: drop theis_unconstrained()split, call
evaluate_blobs_and_batchunconditionally.blob/src/mock_blob_oracle.nrand its module declaration.checkpoint_root/tests/mod.nr:execute_with_mock/execute_with_mock_and_fail/assert_mock_calledbecomeexecute_unconstrained/execute_unconstrained_and_fail. The tests still runin Brillig — that was always for speed, not for the mock — they just no longer
intercept a foreign call. ~60 call sites updated mechanically.
blob_tests.nr: bothTODO(#10323)tests(
correct_end_blob_accumulator_with_one_bloband..._with_max_blobs) are nowunconstrained, which is what those TODOs asked for. They were constrained only
because the mock would otherwise have replaced the very thing they assert on.
Their expected values come from
yarn-project/blob-lib/src/blob_batching.test.tsand are unchanged, so they still pin the Noir result to the TS implementation.
Correctness
On
rollup-checkpoint-root's real prover input, the oracle-free simulatedcircuit and the constrained circuit produce byte-identical public inputs —
checked by running both artifacts and diffing the full
CheckpointRollupPublicInputsdump.Coverage of the constrained composer path does not depend on the tests changed
here: CI already runs
nargo executeagainstrollup-checkpoint-rootandrollup-checkpoint-root-single-block(seecircuits_to_executeinnoir-projects/fnd/noir-protocol-circuits/bootstrap.sh), which is constrainedwitness generation over the same inputs.
Testing
Type-checks clean (
nargo checkonblobandrollup-lib), formatted withnargo fmt. On an earlier run of exactly this composer change I ran the fullcheckpoint_root::tests::rollup_structure_testssuite (11/11 passing). The firstCI run of this branch had all ~60
checkpoint_roottests green, including thetwo conversions above (
..._with_one_blob23s,..._with_max_blobs32s); itsonly failure was an unrelated
ENOENTrace in the prover client's artifact cacheduring
multi_proof.test.ts, on a circuit (CheckpointPaddingRollup, which isjust
CheckpointRollupPublicInputs::empty()) that this diff cannot affect.Follow-up
The
evaluateBlobsforeign call handler in aztec-node is now unreachable andshould be deleted. Separate repo, separate PR.
Created by claudebox · group:
slackbot· requested by Tom (@TomAFrench) · Slack thread