Skip to content

test(blobs): pin the fracs hint's root-of-unity contract and widen its coverage - #25450

Merged
TomAFrench merged 1 commit into
cb/blob-fracs-halvingfrom
cb/blob-fracs-halving-review
Sep 10, 2026
Merged

test(blobs): pin the fracs hint's root-of-unity contract and widen its coverage#25450
TomAFrench merged 1 commit into
cb/blob-fracs-halvingfrom
cb/blob-fracs-halving-review

Conversation

@AztecBot

Copy link
Copy Markdown
Collaborator

Follow-up to #25444, targeting its branch cb/blob-fracs-halving. One squashed commit; the diff touches only noir-projects/fnd/noir-protocol-circuits/crates/blob/src/blob.nr. The vendored bignum crate is untouched.

Why

The halving in __compute_fracs derives all 4096 inverses from one inversion of z^d - 1, so it requires z^d != 1. #25444's comment and body say a root-of-unity challenge "behaves as before". It does not: batch_invert skipped the single zero denominator and returned the other 4095 fractions intact, whereas the halving returns all zeros. The difference is unobservable (at a root factor = 0 so y_i = 0 either way, and validate_fracs has no solution there), but the claim was wrong and the precondition was implicit.

What changes

  • compute_factor already has z^d in-circuit, so it asserts z^d != 1 there with bignum's assert_is_not_equal (a few gates, sound against the z^d = 1 + MOD alias). validate_fracs is unsatisfiable at a root anyway, so this changes no accepted statement; it names the precondition in one place. It is a small ACIR change, so perf(blobs): derive the blob fracs by halving the roots of unity #25444's "The ACIR is untouched" no longer holds once this lands.
  • The __compute_fracs hint asserts the same condition on its own z^d with the message blob challenge z is a d-th root of unity rather than returning an all-zero array. Its doc comment states the precondition and drops the equivalence claim.

Tests

  • The all-entry defining-relation check now runs over a corpus of eleven challenges (zero, small values, both sides of each 120-bit limb boundary, -2 and -3 at the top of the field, and a Poseidon2-sized value) instead of one. -1 is deliberately absent: it is ROOTS[1], a root of unity.
  • Every fraction is -1 at z = 0.
  • The hint rejects z = 1, -1 and ROOTS[1000] with its own message (should_fail_with), and compute_factor rejects a root in-circuit with assert_is_not_equal fail.
  • validate_fracs rejects a root even when handed an array that is correct at every other index (built with batch_invert, exactly the old hint), showing the constraint itself has no solution independent of the hint.
  • The squared-parent property ROOTS[2u]^2 == ROOTS[u], which the final level of the halving relies on, is pinned next to the existing adjacent-negation check.
  • A corrupted fraction at the last index is rejected, not only at index 0.

Rejections that fail inside bignum's evaluate_quadratic_expression stay plain should_fail: that assertion carries no message, and giving it one belongs in a bignum change rather than here.

Ran the full blob crate with the repository-pinned compiler (Noir 1.0.0-rc.0, fae3abca7e51): 53 tests pass, up from 45 at the #25444 head.

Analysis and derivation: https://gist.github.com/AztecBot/bfe643f0f99557cf0f6266eefb433c55


Created by claudebox · group: slackbot · requested by Mike (@iAmMichaelConnor) · Slack thread

…s coverage

The barycentric formula only holds off the evaluation domain, so `compute_fracs`
and `compute_factor` require `z^d != 1`. Make that explicit:

- `compute_factor` already has `z^d` in-circuit, so it asserts `z^d != 1` there
  with bignum's `assert_is_not_equal` (a few gates, sound against the `+MOD`
  alias). `validate_fracs` has no solution at a root anyway, so this changes no
  accepted statement; it names the precondition in one place.
- The `__compute_fracs` hint asserts the same condition on its own `z^d`
  instead of deriving every entry from an inverse of zero and returning an
  all-zero array. Its doc comment states the precondition and drops the claim
  that a root behaves as under batch inversion: `batch_invert` skipped the one
  zero denominator and returned the other 4095 fractions intact.

Tests:
- check the defining relation at every index for a corpus of eleven challenges
  (zero, small values, both sides of each 120-bit limb boundary, -2 and -3 at
  the top of the field, and a Poseidon2-sized value) instead of one
- assert every fraction is -1 at z = 0
- the hint rejects z = 1, -1 and ROOTS[1000] with its own message, and
  `compute_factor` rejects a root in-circuit
- `validate_fracs` rejects a root even when handed an array that is correct
  at every other index, showing the constraint has no solution independent of
  the hint
- pin the squared-parent property ROOTS[2u]^2 == ROOTS[u] that the final level
  of the halving relies on, alongside the existing adjacent-negation check
- reject a corrupted fraction at the last index, not only at index 0

Rejections that fail inside bignum's `evaluate_quadratic_expression` stay plain
`should_fail`: that assertion carries no message and bignum is left untouched.
@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 claudebox Owned by claudebox. it can push to this PR. labels Sep 10, 2026
@TomAFrench
TomAFrench marked this pull request as ready for review September 10, 2026 14:18
@TomAFrench
TomAFrench merged commit 8a155dc into cb/blob-fracs-halving Sep 10, 2026
52 of 55 checks passed
@TomAFrench
TomAFrench deleted the cb/blob-fracs-halving-review branch September 10, 2026 14:19
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 claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants