Skip to content

Commit aca66f7

Browse files
authored
feat: skip calculation of partial sums when simulating blobs (#11257)
When simulating the bloblib we're constructing this `partial_sums` array despite the fact we're going to throw it away except for the final element. This means that we're - needing to deal with the indirection of reading/writing to arrays - increasing the reference counts of the partial sums so we need to do more copying. This PR adds a specialized version of computing the sum which only runs during simulation which cuts `rollup-block-root` from 54859 brillig opcodes to 51480.
1 parent 54e9602 commit aca66f7

File tree

3 files changed

+77
-28
lines changed

3 files changed

+77
-28
lines changed

noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr

+74-25
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ unconstrained fn __batch_invert_impl<let N: u32>(mut x: [F; N]) -> [F; N] {
3030
}
3131

3232
accumulator = accumulator.__invmod();
33-
let mut T0: F = BigNum::new();
33+
let mut T0: F = BigNum::zero();
3434
for i in 0..N {
3535
let idx = N - 1 - i;
3636
if (x[idx].__is_zero() == false) {
@@ -83,7 +83,7 @@ fn field_to_bignum(x: Field) -> F {
8383
}
8484

8585
fn convert_blob_fields(blob_as_fields: [Field; FIELDS_PER_BLOB]) -> [F; FIELDS_PER_BLOB] {
86-
let mut blob: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
86+
let mut blob: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
8787
for i in 0..FIELDS_PER_BLOB {
8888
blob[i] = field_to_bignum(blob_as_fields[i]);
8989
}
@@ -203,25 +203,27 @@ fn barycentric_evaluate_blob_at_z(z: F, ys: [F; FIELDS_PER_BLOB]) -> F {
203203
// perhaps because all the omega^i terms are constant witnesses?
204204
let fracs = compute_fracs(z, ys);
205205

206-
// OK so...we can add multiple product terms into a sum...but I am not sure how many!
207-
// we are computing 254 * 254 bit products and we need to ensure each product limb doesn't overflow
208-
// each limb is 120 bits => 120 * 120 = 240 bits.
209-
// however when computing a mul we add up to 5 product terms into a single field element => 243 bits (ish)
210-
// when we do a modular reduction we validate that a field element >> 120 bits is less than 2^{126} which implies we have 246 bits to play with
211-
// which implies...we can accommodate up to EIGHT additions of product terms before we risk overflowing
212-
// (this is really messy! I never considered the case of giant linear sequences of products)
206+
let sum = if !std::runtime::is_unconstrained() {
207+
// OK so...we can add multiple product terms into a sum...but I am not sure how many!
208+
// we are computing 254 * 254 bit products and we need to ensure each product limb doesn't overflow
209+
// each limb is 120 bits => 120 * 120 = 240 bits.
210+
// however when computing a mul we add up to 5 product terms into a single field element => 243 bits (ish)
211+
// when we do a modular reduction we validate that a field element >> 120 bits is less than 2^{126} which implies we have 246 bits to play with
212+
// which implies...we can accommodate up to EIGHT additions of product terms before we risk overflowing
213+
// (this is really messy! I never considered the case of giant linear sequences of products)
213214

214-
// Seeking:
215-
// ___d-1
216-
// \ omega^i
217-
// sum = / y_i . ---------
218-
// /____ z - omega^i
219-
// i=0
220-
let NUM_PARTIAL_SUMS = FIELDS_PER_BLOB / 8;
221-
/// Safety: This sum is checked by the following `BigNum::evaluate_quadratic_expression` calls.
222-
let partial_sums: [F; FIELDS_PER_BLOB / 8] = unsafe { __compute_partial_sums(fracs, ROOTS) };
215+
// Seeking:
216+
// ___d-1
217+
// \ omega^i
218+
// sum = / y_i . ---------
219+
// /____ z - omega^i
220+
// i=0
221+
222+
let NUM_PARTIAL_SUMS = FIELDS_PER_BLOB / 8;
223+
/// Safety: This sum is checked by the following `BigNum::evaluate_quadratic_expression` calls.
224+
let partial_sums: [F; FIELDS_PER_BLOB / 8] =
225+
unsafe { __compute_partial_sums(fracs, ROOTS) };
223226

224-
if !std::runtime::is_unconstrained() {
225227
// We split off the first term to check the initial sum
226228

227229
// partial_sums[0] <- (lhs[0] * rhs[0] + ... + lhs[7] * rhs[7])
@@ -295,10 +297,20 @@ fn barycentric_evaluate_blob_at_z(z: F, ys: [F; FIELDS_PER_BLOB]) -> F {
295297
[false, true],
296298
);
297299
}
298-
}
300+
301+
// The final term in `partial_sums` is then the full sum.
302+
partial_sums[FIELDS_PER_BLOB / 8 - 1]
303+
} else {
304+
// There's no need to jump through hoops to check partial sums if we don't need to constrain the result.
305+
// We can just calculate the final sum.
306+
307+
/// Safety: We're running under the condition that `std::runtime::is_unconstrained()` is true.
308+
unsafe {
309+
__compute_sum(fracs, ROOTS)
310+
}
311+
};
299312

300313
let factor = compute_factor(z);
301-
let sum = partial_sums[FIELDS_PER_BLOB / 8 - 1];
302314
factor.mul(sum)
303315
}
304316

@@ -352,7 +364,7 @@ unconstrained fn __compute_fracs(
352364
ys: [F; FIELDS_PER_BLOB],
353365
unconstrained_roots: [F; FIELDS_PER_BLOB],
354366
) -> [F; FIELDS_PER_BLOB] {
355-
let mut denoms = [BigNum::new(); FIELDS_PER_BLOB];
367+
let mut denoms = [BigNum::zero(); FIELDS_PER_BLOB];
356368
for i in 0..FIELDS_PER_BLOB {
357369
denoms[i] = z.__sub(unconstrained_roots[i]); // (z - omega^i)
358370
}
@@ -407,7 +419,7 @@ unconstrained fn __compute_partial_sums(
407419
// k=i*8 + 0
408420

409421
// Need to split off the first iteration.
410-
let mut partial_sum: F = BigNum::new();
422+
let mut partial_sum: F = BigNum::zero();
411423
for i in 0..8 {
412424
// y_k * ( omega^k / (z - omega^k) )
413425
let summand = unconstrained_roots[i].__mul(fracs[i]);
@@ -439,6 +451,28 @@ unconstrained fn __compute_partial_sums(
439451
partial_sums
440452
}
441453

454+
unconstrained fn __compute_sum(
455+
fracs: [F; FIELDS_PER_BLOB],
456+
unconstrained_roots: [F; FIELDS_PER_BLOB],
457+
) -> F {
458+
// Seeking:
459+
// ___d-1
460+
// \ omega^i
461+
// sum = / y_i . ---------
462+
// /____ z - omega^i
463+
// i=0
464+
465+
let mut sum: F = BigNum::zero();
466+
for i in 0..FIELDS_PER_BLOB {
467+
// y_k * ( omega^k / (z - omega^k) )
468+
let summand = unconstrained_roots[i].__mul(fracs[i]);
469+
470+
// partial_sum + ( y_k * ( omega^k / (z - omega^k) ) -> partial_sum
471+
sum = sum.__add(summand);
472+
}
473+
sum
474+
}
475+
442476
mod tests {
443477
// TODO(#9982): Replace unconstrained_config with config and import ROOTS - calculating ROOTS in unconstrained is insecure.
444478
use crate::{
@@ -447,8 +481,9 @@ mod tests {
447481
field_to_bignum,
448482
},
449483
blob_public_inputs::BlobCommitment,
450-
config::{D, D_INV, F},
484+
config::{D, D_INV, F, ROOTS},
451485
};
486+
use super::{__compute_partial_sums, __compute_sum};
452487
use bigint::{BigNum, fields::bls12_381Fr::BLS12_381_Fr_Params};
453488
use bigint::bignum::BigNumTrait;
454489
use types::{
@@ -632,7 +667,7 @@ mod tests {
632667
let z: F = BigNum { limbs: [2, 0, 0] };
633668

634669
// many y's form a blob:
635-
let mut ys: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
670+
let mut ys: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
636671

637672
ys[0] = BigNum { limbs: [0x1234, 0, 0] };
638673
ys[1] = BigNum { limbs: [0xabcd, 0, 0] };
@@ -683,5 +718,19 @@ mod tests {
683718
let d_inversed = D.__invmod();
684719
assert_eq(d_inversed, D_INV);
685720
}
721+
722+
#[test]
723+
fn compute_sum_and_compute_partial_sums_agree() {
724+
let mut fields = [BigNum::zero(); FIELDS_PER_BLOB];
725+
for i in 0..FIELDS_PER_BLOB {
726+
fields[i] = field_to_bignum(i as Field);
727+
}
728+
unsafe {
729+
let partial_sums = __compute_partial_sums(fields, ROOTS);
730+
let sum = __compute_sum(fields, ROOTS);
731+
732+
assert_eq(partial_sums[FIELDS_PER_BLOB / 8 - 1], sum);
733+
}
734+
}
686735
}
687736

noir-projects/noir-protocol-circuits/crates/blob/src/blob_public_inputs.nr

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl BlobPublicInputs {
4949

5050
impl Empty for BlobPublicInputs {
5151
fn empty() -> Self {
52-
Self { z: 0, y: BigNum::new(), kzg_commitment: BlobCommitment::empty() }
52+
Self { z: 0, y: BigNum::zero(), kzg_commitment: BlobCommitment::empty() }
5353
}
5454
}
5555

noir-projects/noir-protocol-circuits/crates/blob/src/unconstrained_config.nr

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ unconstrained fn compute_big_minus_arr(
4747
}
4848

4949
unconstrained fn bit_reversal_permutation(arr: [F; FIELDS_PER_BLOB]) -> [F; FIELDS_PER_BLOB] {
50-
let mut arr_brp: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
50+
let mut arr_brp: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
5151
let PLUS = FIELDS_PER_BLOB >> 1;
5252
let MINUS = PLUS >> 1;
5353
let mut I = 0;
@@ -81,7 +81,7 @@ unconstrained fn bit_reversal_permutation(arr: [F; FIELDS_PER_BLOB]) -> [F; FIEL
8181

8282
// x ^ i for i in 0..4096
8383
unconstrained fn compute_powers(x: F) -> [F; FIELDS_PER_BLOB] {
84-
let mut powers: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
84+
let mut powers: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
8585
let mut current_power: F = BigNum::one();
8686
for i in 0..FIELDS_PER_BLOB {
8787
powers[i] = current_power;

0 commit comments

Comments
 (0)