Skip to content

feat(blobs): remove the ts blob oracle - #25442

Draft
AztecBot wants to merge 1 commit into
nextfrom
cb/remove-blob-oracle
Draft

feat(blobs): remove the ts blob oracle#25442
AztecBot wants to merge 1 commit into
nextfrom
cb/remove-blob-oracle

Conversation

@AztecBot

@AztecBot AztecBot commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

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 still
carries a now-dead evaluateBlobs foreign call handler. Close the issue by hand
once that handler is deleted too.

Independent of #25439 and #25444: this touches blob/src/lib.nr, the checkpoint
root 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-in Prover.toml, native ACVM, interleaved
round-robin so host drift hits both arms equally:

min median
constrained witness generation 9.99s 11.59s
simulation, oracle removed 1.41s 1.47s

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 the is_unconstrained()
    split, call evaluate_blobs_and_batch unconditionally.
  • Delete blob/src/mock_blob_oracle.nr and its module declaration.
  • checkpoint_root/tests/mod.nr: execute_with_mock /
    execute_with_mock_and_fail / assert_mock_called become
    execute_unconstrained / execute_unconstrained_and_fail. The tests still run
    in 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: both TODO(#10323) tests
    (correct_end_blob_accumulator_with_one_blob and ..._with_max_blobs) are now
    unconstrained, 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.ts
    and 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 simulated
circuit and the constrained circuit produce byte-identical public inputs
checked by running both artifacts and diffing the full
CheckpointRollupPublicInputs dump.

Coverage of the constrained composer path does not depend on the tests changed
here: CI already runs nargo execute against rollup-checkpoint-root and
rollup-checkpoint-root-single-block (see circuits_to_execute in
noir-projects/fnd/noir-protocol-circuits/bootstrap.sh), which is constrained
witness generation over the same inputs.

Testing

Type-checks clean (nargo check on blob and rollup-lib), formatted with
nargo fmt. On an earlier run of exactly this composer change I ran the full
checkpoint_root::tests::rollup_structure_tests suite (11/11 passing). The first
CI run of this branch had all ~60 checkpoint_root tests green, including the
two conversions above (..._with_one_blob 23s, ..._with_max_blobs 32s); its
only failure was an unrelated ENOENT race in the prover client's artifact cache
during multi_proof.test.ts, on a circuit (CheckpointPaddingRollup, which is
just CheckpointRollupPublicInputs::empty()) that this diff cannot affect.

Follow-up

The evaluateBlobs foreign call handler in aztec-node is now unreachable and
should be deleted. Separate repo, separate PR.


Created by claudebox · group: slackbot · requested by Tom (@TomAFrench) · Slack thread

@AztecBot AztecBot added ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claude-review Triggers an automated Claude code review claudebox Owned by claudebox. it can push to this PR. labels Sep 10, 2026
@AztecBot

Copy link
Copy Markdown
Collaborator Author

Claude Review — starting review session...

@AztecBot

AztecBot commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

⚠️ Adversarial verdict: ERROR

Claude Review — completed
live status
3m

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.

@AztecBot

Copy link
Copy Markdown
Collaborator Author

Claude Review: Starting automated code review... workflow run

@AztecBot AztecBot added claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention and removed claude-review Triggers an automated Claude code review labels Sep 10, 2026
@AztecBot AztecBot added claude-review Triggers an automated Claude code review and removed claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention labels Sep 10, 2026
@AztecBot

Copy link
Copy Markdown
Collaborator Author

Claude Review: Starting automated code review... workflow run

@AztecBot AztecBot added claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention and removed claude-review Triggers an automated Claude code review labels Sep 10, 2026
@AztecBot
AztecBot force-pushed the cb/remove-blob-oracle branch from d80e535 to 6eb5600 Compare September 10, 2026 08:01
@AztecBot
AztecBot changed the base branch from cb/unconstrained-blob-eval-speedup to next September 10, 2026 08:01
@AztecBot AztecBot added claude-review Triggers an automated Claude code review and removed claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention labels Sep 10, 2026
@AztecBot

Copy link
Copy Markdown
Collaborator Author

Claude Review: Starting automated code review... workflow run

@AztecBot AztecBot added claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention and removed claude-review Triggers an automated Claude code review claude-review-error Adversarial ClaudeBox review needs attention labels Sep 10, 2026
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)*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure claude-review-complete Claude code review has been completed claude-review-error Adversarial ClaudeBox review needs attention claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant