Skip to content

Commit

Permalink
feat: skip calculation of partial sums when simulating blobs (#11257)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
TomAFrench authored Feb 3, 2025
1 parent 54e9602 commit aca66f7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 28 deletions.
99 changes: 74 additions & 25 deletions noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ unconstrained fn __batch_invert_impl<let N: u32>(mut x: [F; N]) -> [F; N] {
}

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

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

// OK so...we can add multiple product terms into a sum...but I am not sure how many!
// we are computing 254 * 254 bit products and we need to ensure each product limb doesn't overflow
// each limb is 120 bits => 120 * 120 = 240 bits.
// however when computing a mul we add up to 5 product terms into a single field element => 243 bits (ish)
// 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
// which implies...we can accommodate up to EIGHT additions of product terms before we risk overflowing
// (this is really messy! I never considered the case of giant linear sequences of products)
let sum = if !std::runtime::is_unconstrained() {
// OK so...we can add multiple product terms into a sum...but I am not sure how many!
// we are computing 254 * 254 bit products and we need to ensure each product limb doesn't overflow
// each limb is 120 bits => 120 * 120 = 240 bits.
// however when computing a mul we add up to 5 product terms into a single field element => 243 bits (ish)
// 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
// which implies...we can accommodate up to EIGHT additions of product terms before we risk overflowing
// (this is really messy! I never considered the case of giant linear sequences of products)

// Seeking:
// ___d-1
// \ omega^i
// sum = / y_i . ---------
// /____ z - omega^i
// i=0
let NUM_PARTIAL_SUMS = FIELDS_PER_BLOB / 8;
/// Safety: This sum is checked by the following `BigNum::evaluate_quadratic_expression` calls.
let partial_sums: [F; FIELDS_PER_BLOB / 8] = unsafe { __compute_partial_sums(fracs, ROOTS) };
// Seeking:
// ___d-1
// \ omega^i
// sum = / y_i . ---------
// /____ z - omega^i
// i=0

let NUM_PARTIAL_SUMS = FIELDS_PER_BLOB / 8;
/// Safety: This sum is checked by the following `BigNum::evaluate_quadratic_expression` calls.
let partial_sums: [F; FIELDS_PER_BLOB / 8] =
unsafe { __compute_partial_sums(fracs, ROOTS) };

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

// partial_sums[0] <- (lhs[0] * rhs[0] + ... + lhs[7] * rhs[7])
Expand Down Expand Up @@ -295,10 +297,20 @@ fn barycentric_evaluate_blob_at_z(z: F, ys: [F; FIELDS_PER_BLOB]) -> F {
[false, true],
);
}
}

// The final term in `partial_sums` is then the full sum.
partial_sums[FIELDS_PER_BLOB / 8 - 1]
} else {
// There's no need to jump through hoops to check partial sums if we don't need to constrain the result.
// We can just calculate the final sum.

/// Safety: We're running under the condition that `std::runtime::is_unconstrained()` is true.
unsafe {
__compute_sum(fracs, ROOTS)
}
};

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

Expand Down Expand Up @@ -352,7 +364,7 @@ unconstrained fn __compute_fracs(
ys: [F; FIELDS_PER_BLOB],
unconstrained_roots: [F; FIELDS_PER_BLOB],
) -> [F; FIELDS_PER_BLOB] {
let mut denoms = [BigNum::new(); FIELDS_PER_BLOB];
let mut denoms = [BigNum::zero(); FIELDS_PER_BLOB];
for i in 0..FIELDS_PER_BLOB {
denoms[i] = z.__sub(unconstrained_roots[i]); // (z - omega^i)
}
Expand Down Expand Up @@ -407,7 +419,7 @@ unconstrained fn __compute_partial_sums(
// k=i*8 + 0

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

unconstrained fn __compute_sum(
fracs: [F; FIELDS_PER_BLOB],
unconstrained_roots: [F; FIELDS_PER_BLOB],
) -> F {
// Seeking:
// ___d-1
// \ omega^i
// sum = / y_i . ---------
// /____ z - omega^i
// i=0

let mut sum: F = BigNum::zero();
for i in 0..FIELDS_PER_BLOB {
// y_k * ( omega^k / (z - omega^k) )
let summand = unconstrained_roots[i].__mul(fracs[i]);

// partial_sum + ( y_k * ( omega^k / (z - omega^k) ) -> partial_sum
sum = sum.__add(summand);
}
sum
}

mod tests {
// TODO(#9982): Replace unconstrained_config with config and import ROOTS - calculating ROOTS in unconstrained is insecure.
use crate::{
Expand All @@ -447,8 +481,9 @@ mod tests {
field_to_bignum,
},
blob_public_inputs::BlobCommitment,
config::{D, D_INV, F},
config::{D, D_INV, F, ROOTS},
};
use super::{__compute_partial_sums, __compute_sum};
use bigint::{BigNum, fields::bls12_381Fr::BLS12_381_Fr_Params};
use bigint::bignum::BigNumTrait;
use types::{
Expand Down Expand Up @@ -632,7 +667,7 @@ mod tests {
let z: F = BigNum { limbs: [2, 0, 0] };

// many y's form a blob:
let mut ys: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
let mut ys: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];

ys[0] = BigNum { limbs: [0x1234, 0, 0] };
ys[1] = BigNum { limbs: [0xabcd, 0, 0] };
Expand Down Expand Up @@ -683,5 +718,19 @@ mod tests {
let d_inversed = D.__invmod();
assert_eq(d_inversed, D_INV);
}

#[test]
fn compute_sum_and_compute_partial_sums_agree() {
let mut fields = [BigNum::zero(); FIELDS_PER_BLOB];
for i in 0..FIELDS_PER_BLOB {
fields[i] = field_to_bignum(i as Field);
}
unsafe {
let partial_sums = __compute_partial_sums(fields, ROOTS);
let sum = __compute_sum(fields, ROOTS);

assert_eq(partial_sums[FIELDS_PER_BLOB / 8 - 1], sum);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl BlobPublicInputs {

impl Empty for BlobPublicInputs {
fn empty() -> Self {
Self { z: 0, y: BigNum::new(), kzg_commitment: BlobCommitment::empty() }
Self { z: 0, y: BigNum::zero(), kzg_commitment: BlobCommitment::empty() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ unconstrained fn compute_big_minus_arr(
}

unconstrained fn bit_reversal_permutation(arr: [F; FIELDS_PER_BLOB]) -> [F; FIELDS_PER_BLOB] {
let mut arr_brp: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
let mut arr_brp: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
let PLUS = FIELDS_PER_BLOB >> 1;
let MINUS = PLUS >> 1;
let mut I = 0;
Expand Down Expand Up @@ -81,7 +81,7 @@ unconstrained fn bit_reversal_permutation(arr: [F; FIELDS_PER_BLOB]) -> [F; FIEL

// x ^ i for i in 0..4096
unconstrained fn compute_powers(x: F) -> [F; FIELDS_PER_BLOB] {
let mut powers: [F; FIELDS_PER_BLOB] = [BigNum::new(); FIELDS_PER_BLOB];
let mut powers: [F; FIELDS_PER_BLOB] = [BigNum::zero(); FIELDS_PER_BLOB];
let mut current_power: F = BigNum::one();
for i in 0..FIELDS_PER_BLOB {
powers[i] = current_power;
Expand Down

0 comments on commit aca66f7

Please sign in to comment.