diff --git a/noir-projects/noir-protocol-circuits/crates/blob/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/blob/Nargo.toml index 4f60115e56fe..35e028d142a5 100644 --- a/noir-projects/noir-protocol-circuits/crates/blob/Nargo.toml +++ b/noir-projects/noir-protocol-circuits/crates/blob/Nargo.toml @@ -6,6 +6,6 @@ compiler_version = ">=0.30.0" [dependencies] bigint = { tag = "v0.7.3", git = "https://github.com/noir-lang/noir-bignum" } -bigcurve = { tag = "mw/bump", git = "https://github.com/noir-lang/noir_bigcurve" } +bigcurve = { tag = "v0.9.0", git = "https://github.com/noir-lang/noir_bigcurve" } types = { path = "../types" } poseidon = { tag = "v0.1.1", git = "https://github.com/noir-lang/poseidon" } diff --git a/noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr b/noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr index 66ba3b361bf9..0bae92e4369b 100644 --- a/noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr +++ b/noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr @@ -1,104 +1,8 @@ -use crate::{ - blob_public_inputs::{BlobCommitment, BlobPublicInputs, BlockBlobPublicInputs}, - config::{D_INV, LOG_FIELDS_PER_BLOB, ROOTS}, -}; +use crate::config::{D_INV, LOG_FIELDS_PER_BLOB, ROOTS}; use bigint::{BigNum, BLS12_381_Fr as F}; use std::ops::{Mul, Neg}; -use types::{ - abis::sponge_blob::SpongeBlob, - constants::{BLOBS_PER_BLOCK, FIELDS_PER_BLOB}, - hash::poseidon2_hash_subarray, - traits::Empty, - utils::arrays::array_splice, -}; - -// TODO(MW): remove pub when fully moved to batching -pub(crate) fn convert_blob_fields( - blob_as_fields: [Field; FIELDS_PER_BLOB], -) -> [F; FIELDS_PER_BLOB] { - let mut blob: [F; FIELDS_PER_BLOB] = [F::zero(); FIELDS_PER_BLOB]; - for i in 0..FIELDS_PER_BLOB { - blob[i] = F::from(blob_as_fields[i]); - } - blob -} - -pub fn check_block_blob_sponge( - blobs_as_fields: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK], - mut sponge_blob: SpongeBlob, -) -> Field { - // Check that we haven't overfilled the blobs - assert( - sponge_blob.expected_fields <= FIELDS_PER_BLOB * BLOBS_PER_BLOCK, - "Attempted to overfill blobs", - ); - // Check that the blob is full - assert( - sponge_blob.expected_fields == sponge_blob.fields, - "Incorrect number of tx effects added to blob", - ); - let sponge_hash = sponge_blob.squeeze(); - let hash = poseidon2_hash_subarray(blobs_as_fields, sponge_blob.fields); - assert(hash == sponge_hash, "Mismatched hashed tx effects"); - - sponge_hash -} - -// TODO(MW): remove pub when fully moved to batching -pub(crate) fn compute_challenge( - hashed_blobs_fields: Field, - kzg_commitment: BlobCommitment, -) -> Field { - let preimage = [hashed_blobs_fields, kzg_commitment.inner[0], kzg_commitment.inner[1]]; - let challenge = poseidon::poseidon2::Poseidon2::hash(preimage, 3); - challenge -} - -// Note: the kzg_commitment is technically a BLS12-381 point in (Fq, Fq), but -// we don't actually need to operate on it so we've simply encoded it as fitting inside a -// [Field; 2], since two 254-bit fields more-than covers 381+1=382 bits. -// See yarn-project/foundation/src/blob/index.ts -> commitmentToFields() for encoding -// TODO(MW): remove pub when fully moved to batching -pub(crate) fn evaluate_blob( - blob_as_fields: [Field; FIELDS_PER_BLOB], - kzg_commitment: BlobCommitment, - hashed_blobs_fields: Field, -) -> BlobPublicInputs { - let challenge_z: Field = compute_challenge(hashed_blobs_fields, kzg_commitment); - let challenge_z_as_bignum = F::from(challenge_z); - let blob = convert_blob_fields(blob_as_fields); - - let y: F = barycentric_evaluate_blob_at_z(challenge_z_as_bignum, blob); - - BlobPublicInputs { z: challenge_z, y, kzg_commitment } -} - -// Evaluates each blob required for a block -pub fn evaluate_blobs( - blobs_as_fields: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK], - kzg_commitments: [BlobCommitment; BLOBS_PER_BLOCK], - mut sponge_blob: SpongeBlob, -) -> BlockBlobPublicInputs { - // Note that with multiple blobs per block, each blob uses the same hashed_blobs_fields in: - // challenge_z = H(hashed_blobs_fields, kzg_commitment[0], kzg_commitment[1]) - // This is ok, because each commitment is unique to the blob, and we need hashed_blobs_fields to encompass - // all fields in the blob, which it does. - let hashed_blobs_fields = check_block_blob_sponge(blobs_as_fields, sponge_blob); - let mut result = BlockBlobPublicInputs::empty(); - for i in 0..BLOBS_PER_BLOCK { - let single_blob_fields = array_splice(blobs_as_fields, i * FIELDS_PER_BLOB); - result.inner[i] = - evaluate_blob(single_blob_fields, kzg_commitments[i], hashed_blobs_fields); - if (result.inner[i].is_zero()) & (single_blob_fields[0] == 0) { - // We use empty PIs for empty blobs, to make it simpler to verify on L1. - // Since our fields come from the base rollup, we know they are tightly packed - // and should contain no 0 values among valid values => single_blob_fields[0] == 0. - result.inner[i] = BlobPublicInputs::empty(); - } - } - result -} +use types::constants::FIELDS_PER_BLOB; /** * ___d-1 @@ -120,7 +24,6 @@ pub fn evaluate_blobs( * @param ys - the many y_i's of the blob. * * @return y = p(z) - * TODO(MW): remove pub when fully moved to batching */ pub(crate) fn barycentric_evaluate_blob_at_z(z: F, ys: [F; FIELDS_PER_BLOB]) -> F { // Note: it's more efficient (saving 30k constraints) to compute: @@ -403,22 +306,49 @@ unconstrained fn __compute_sum(fracs: [F; FIELDS_PER_BLOB]) -> F { mod tests { // TODO(#9982): Replace unconstrained_config with config and import ROOTS - calculating ROOTS in unconstrained is insecure. - use crate::{ - blob::{ - barycentric_evaluate_blob_at_z, check_block_blob_sponge, evaluate_blob, evaluate_blobs, - }, - blob_public_inputs::BlobCommitment, - config::{D, D_INV}, - }; + use crate::{blob::barycentric_evaluate_blob_at_z, config::{D, D_INV}}; use super::{__compute_partial_sums, __compute_sum}; use bigint::{BigNum, BLS12_381_Fr as F}; use types::{ abis::sponge_blob::SpongeBlob, - constants::{BLOBS_PER_BLOCK, FIELDS_PER_BLOB}, - tests::{fixture_builder::FixtureBuilder, utils::pad_end}, - traits::Serialize, + constants::FIELDS_PER_BLOB, + tests::fixture_builder::FixtureBuilder, + traits::{Empty, Serialize}, }; + // Note: the kzg_commitment is technically a BLS12-381 point in (Fq, Fq), but + // we don't actually need to operate on it so we've simply encoded it as fitting inside a + // [Field; 2], since two 254-bit fields more-than covers 381+1=382 bits. + // See yarn-project/foundation/src/blob/index.ts -> commitmentToFields() for encoding + pub struct TestBlobEvaluationOutputs { + pub z: Field, + pub y: F, + pub c: [Field; 2], + } + + impl Empty for TestBlobEvaluationOutputs { + fn empty() -> Self { + Self { z: 0, y: F::zero(), c: [0, 0] } + } + } + + fn evaluate_blob( + blob_as_fields: [Field; FIELDS_PER_BLOB], + kzg_commitment: [Field; 2], + hashed_blobs_fields: Field, + ) -> TestBlobEvaluationOutputs { + let challenge_z = poseidon::poseidon2::Poseidon2::hash( + [hashed_blobs_fields, kzg_commitment[0], kzg_commitment[1]], + 3, + ); + let challenge_z_as_bignum = F::from(challenge_z); + let blob = blob_as_fields.map(|b| F::from(b)); + + let y: F = barycentric_evaluate_blob_at_z(challenge_z_as_bignum, blob); + + TestBlobEvaluationOutputs { z: challenge_z, y, c: kzg_commitment } + } + #[test] unconstrained fn test_one_note() { let mut tx_data = FixtureBuilder::new(); @@ -431,9 +361,8 @@ mod tests { let mut sponge_blob = SpongeBlob::new(blob_fields.len()); sponge_blob.absorb(blob_fields, blob_fields.len()); - let kzg_commitment_in = BlobCommitment { inner: [1, 2] }; // this is made-up nonsense. - let padded_blob_fields = pad_end(blob, 0); - let hashed_blob = check_block_blob_sponge(padded_blob_fields, sponge_blob); + let kzg_commitment_in = [1, 2]; // this is made-up nonsense. + let hashed_blob = sponge_blob.squeeze(); let output = evaluate_blob(blob, kzg_commitment_in, hashed_blob); let challenge_z = F::from(output.z); let y = output.y; @@ -456,37 +385,6 @@ mod tests { assert_eq(lhs, rhs); } - // TODO: After reverting some noir changes (see PR#10341) the below no longer works in unconstrained - // This happened previously in commit 893f1eca422d952d672eec75e3c9ff8f149a9ae8 but a sync fixed it. - // It works and passes in constrained, just takes a min longer. - #[test] - fn test_base() { - let mut tx_data = FixtureBuilder::new(); - // Add some random bits of state - tx_data.append_note_hashes(50); - tx_data.set_protocol_nullifier(); - tx_data.append_nullifiers(50); - tx_data.append_l2_to_l1_msgs(5); - tx_data.append_public_logs(5); - let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; - let blob_fields = tx_data.to_private_to_rollup_accumulated_data().serialize(); - for i in 0..blob_fields.len() { - blob[i] = blob_fields[i]; - } - let mut sponge_blob = SpongeBlob::new(blob_fields.len()); - sponge_blob.absorb(blob_fields, blob_fields.len()); - - let kzg_commitment_in = BlobCommitment { inner: [1, 2] }; // this is made-up nonsense. - let padded_blob_fields = pad_end(blob, 0); - let hashed_blob = check_block_blob_sponge(padded_blob_fields, sponge_blob); - let output = evaluate_blob(blob, kzg_commitment_in, hashed_blob); - let expected_z = poseidon::poseidon2::Poseidon2::hash( - [sponge_blob.squeeze(), kzg_commitment_in.inner[0], kzg_commitment_in.inner[1]], - 3, - ); - assert(expected_z == output.z); - } - // All hardcoded values in this test are taken from yarn-project/foundation/src/blob/blob.test.ts -> 'should evaluate a blob of 400 items' #[test] unconstrained fn test_400() { @@ -497,15 +395,12 @@ mod tests { let mut sponge_blob = SpongeBlob::new(400); sponge_blob.absorb(blob, 400); - let kzg_commitment_in = BlobCommitment { - inner: [ - 0x00b2803d5fe972914ba3616033e2748bbaa6dbcddefc3721a54895a7a45e7750, - 0x0000000000000000000000000000004dd1a971c7e8d8292be943d05bccebcfea, - ], - }; + let kzg_commitment_in = [ + 0x00b2803d5fe972914ba3616033e2748bbaa6dbcddefc3721a54895a7a45e7750, + 0x0000000000000000000000000000004dd1a971c7e8d8292be943d05bccebcfea, + ]; - let padded_blob_fields = pad_end(blob, 0); - let hashed_blob = check_block_blob_sponge(padded_blob_fields, sponge_blob); + let hashed_blob = sponge_blob.squeeze(); let output = evaluate_blob(blob, kzg_commitment_in, hashed_blob); // y is a BLS field with value 0x212c4f0c0ee5e7dd037110686a4639d191dde7b57ab99b51e4b06e7d827b6c4c @@ -517,79 +412,31 @@ mod tests { assert(expected_y == output.y); } - // All hardcoded values in this test are taken from yarn-project/foundation/src/blob/blob.test.ts -> 'should evaluate full blobs' + // All hardcoded values in this test are taken from yarn-project/foundation/src/blob/blob.test.ts -> 'should evaluate full blob' #[test] - unconstrained fn test_full_blobs() { - let mut blob: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK] = - [0; FIELDS_PER_BLOB * BLOBS_PER_BLOCK]; - for j in 0..BLOBS_PER_BLOCK { - for i in 0..FIELDS_PER_BLOB { - blob[j * FIELDS_PER_BLOB + i] = i as Field + 2; - } + unconstrained fn test_full_blob() { + let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; + for i in 0..FIELDS_PER_BLOB { + blob[i] = i as Field + 2; } + let mut sponge_blob = SpongeBlob::new(FIELDS_PER_BLOB); + sponge_blob.absorb(blob, FIELDS_PER_BLOB); - let mut sponge_blob = SpongeBlob::new(FIELDS_PER_BLOB * BLOBS_PER_BLOCK); - sponge_blob.absorb(blob, FIELDS_PER_BLOB * BLOBS_PER_BLOCK); - - let kzg_commitment_in = BlobCommitment { - inner: [ - 0x00ac771dea41e29fc2b7016c32731602c0812548ba0f491864a4e03fdb94b8d3, - 0x000000000000000000000000000000d195faad1967cdf005acf73088b0e8474a, - ], - }; + let kzg_commitment_in = [ + 0x00ac771dea41e29fc2b7016c32731602c0812548ba0f491864a4e03fdb94b8d3, + 0x000000000000000000000000000000d195faad1967cdf005acf73088b0e8474a, + ]; - let output = evaluate_blobs(blob, [kzg_commitment_in; BLOBS_PER_BLOCK], sponge_blob); + let hashed_blob = sponge_blob.squeeze(); + let output = evaluate_blob(blob, kzg_commitment_in, hashed_blob); - // y is a BLS field with value 0x52fd4e272015a79f3889cc9ab1d84bee4326de7d8ced52612ecc9ec137bd38ee + // y is a BLS field with value 0x0365494e66a289c4509ecf97af4ff92aa7ecc38f478ced014b6ae860502a1b1c let expected_y: F = F::from_limbs([ - 0x26de7d8ced52612ecc9ec137bd38ee, - 0x4e272015a79f3889cc9ab1d84bee43, - 0x52fd, + 0xecc38f478ced014b6ae860502a1b1c, + 0x494e66a289c4509ecf97af4ff92aa7, + 0x0365, ]); - for j in 0..BLOBS_PER_BLOCK { - assert(expected_y == output.inner[j].y); - } - } - - #[test(should_fail_with = "Found non-zero field after breakpoint")] - unconstrained fn test_no_extra_blob_fields() { - let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; - // Fill fields with 50 inputs... - for i in 0..50 { - blob[i] = 3; - } - // ...but the rollup's sponge is only expecting 45... - let mut sponge_blob = SpongeBlob::new(45); - sponge_blob.absorb(blob, 45); - - // ...so the below should fail as it detects we are adding effects which did not come from the rollup. - let padded_blob_fields = pad_end(blob, 0); - let _ = check_block_blob_sponge(padded_blob_fields, sponge_blob); - } - - #[test(should_fail_with = "Incorrect number of tx effects added to blob")] - unconstrained fn test_absorbed_too_few_blob_fields() { - let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; - // Fill fields with 50 inputs... - for i in 0..50 { - blob[i] = 3; - } - // ...but the rollup's sponge is expecting 100... - let mut sponge_blob = SpongeBlob::new(100); - sponge_blob.absorb(blob, 50); - - // ...so the below should fail as it detects we have not added all the tx effects. - let padded_blob_fields = pad_end(blob, 0); - let _ = check_block_blob_sponge(padded_blob_fields, sponge_blob); - } - - #[test] - unconstrained fn test_empty_blob() { - let mut blob: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK] = - [0; FIELDS_PER_BLOB * BLOBS_PER_BLOCK]; - let mut sponge_blob = SpongeBlob::new(0); - // The below should not throw - let _ = check_block_blob_sponge(blob, sponge_blob); + assert(expected_y == output.y); } #[test] diff --git a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr b/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr index edcb6f396339..3e56ef6f2ba4 100644 --- a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr +++ b/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr @@ -1,18 +1,15 @@ use crate::{ - blob::{ - barycentric_evaluate_blob_at_z, check_block_blob_sponge, compute_challenge, - convert_blob_fields, - }, + blob::barycentric_evaluate_blob_at_z, blob_batching_public_inputs::{ BatchingBlobCommitment, BlobAccumulationInputs, BlobAccumulatorPublicInputs, BLSPoint, compress_to_blob_commitment, FinalBlobBatchingChallenges, }, - blob_public_inputs::BlobCommitment, }; use bigint::{BigNum, BLS12_381_Fr as F}; use types::{ abis::sponge_blob::SpongeBlob, constants::{BLOBS_PER_BLOCK, FIELDS_PER_BLOB}, + hash::poseidon2_hash_subarray, traits::is_empty, utils::arrays::array_splice, }; @@ -27,18 +24,23 @@ fn evaluate_blob_for_batching( challenge_z: Field, ) -> (Field, F) { let challenge_z_as_bignum = F::from(challenge_z); - let blob = convert_blob_fields(blob_as_fields); + let blob = blob_as_fields.map(|b| F::from(b)); let y_i: F = barycentric_evaluate_blob_at_z(challenge_z_as_bignum, blob); - let z_i: Field = compute_challenge( - hashed_blobs_fields, - // TODO(MW): At some point BatchingBlobCommitment will replace BlobCommitment and we won't need this silly conversion - BlobCommitment { inner: kzg_commitment.to_compressed_fields() }, - ); + let z_i: Field = + compute_blob_challenge(hashed_blobs_fields, kzg_commitment.to_compressed_fields()); (z_i, y_i) } +// Computes challenge for a single blob: +// - z_i (= H(H(blob_i), C_i)), where C_i = kzg_commitment, and blob_i = blob_as_fields[i]. +fn compute_blob_challenge(hashed_blobs_fields: Field, kzg_commitment: [Field; 2]) -> Field { + let preimage = [hashed_blobs_fields, kzg_commitment[0], kzg_commitment[1]]; + let challenge = poseidon::poseidon2::Poseidon2::hash(preimage, 3); + challenge +} + // Evaluates each blob required for a block: // - Hashes all fields in the block's blobs (to use for the challenges z_i) // - Compresses each of the blob's injected commitments (") @@ -94,14 +96,38 @@ pub fn evaluate_blobs_and_batch( end_accumulator } +// Validates this block's injected blob fields against the validated sponge propagated from the previous circuits: +// - Checks that we haven't accumulated too many fields over the rollup +// - Checks that we have absorbed the exact number of fields we claim to have accumulated +// - Checks that the injected fields match the tx effects absorbed over the rollup +// - Checks that any fields above expected_fields are empty (inside poseidon2_hash_subarray()) +fn check_block_blob_sponge( + blobs_as_fields: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK], + mut sponge_blob: SpongeBlob, +) -> Field { + // Check that we haven't overfilled the blobs + assert( + sponge_blob.expected_fields <= FIELDS_PER_BLOB * BLOBS_PER_BLOCK, + "Attempted to overfill blobs", + ); + // Check that the blob is full + assert( + sponge_blob.expected_fields == sponge_blob.fields, + "Incorrect number of tx effects added to blob", + ); + let sponge_hash = sponge_blob.squeeze(); + let hash = poseidon2_hash_subarray(blobs_as_fields, sponge_blob.fields); + assert(hash == sponge_hash, "Mismatched hashed tx effects"); + + sponge_hash +} + mod tests { - use crate::{ - blob_batching::evaluate_blobs_and_batch, - blob_batching_public_inputs::{ - BatchingBlobCommitment, BlobAccumulatorPublicInputs, compress_to_blob_commitment, - }, + use crate::blob_batching_public_inputs::{ + BatchingBlobCommitment, BlobAccumulatorPublicInputs, compress_to_blob_commitment, + FinalBlobBatchingChallenges, }; - use crate::blob_batching_public_inputs::FinalBlobBatchingChallenges; + use super::{check_block_blob_sponge, evaluate_blobs_and_batch}; use bigcurve::{BigCurveTrait, curves::bls12_381::BLS12_381 as BLSPoint}; use bigint::{BigNum, BLS12_381_Fr as F}; use types::{ @@ -309,4 +335,45 @@ mod tests { assert_eq(final_acc.c, expected_c.to_compressed_fields()); assert_eq(final_acc.blob_commitments_hash, expected_blob_commitments_hash); } + + #[test(should_fail_with = "Found non-zero field after breakpoint")] + unconstrained fn test_no_extra_blob_fields() { + let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; + // Fill fields with 50 inputs... + for i in 0..50 { + blob[i] = 3; + } + // ...but the rollup's sponge is only expecting 45... + let mut sponge_blob = SpongeBlob::new(45); + sponge_blob.absorb(blob, 45); + + // ...so the below should fail as it detects we are adding effects which did not come from the rollup. + let padded_blob_fields = pad_end(blob, 0); + let _ = super::check_block_blob_sponge(padded_blob_fields, sponge_blob); + } + + #[test(should_fail_with = "Incorrect number of tx effects added to blob")] + unconstrained fn test_absorbed_too_few_blob_fields() { + let mut blob: [Field; FIELDS_PER_BLOB] = [0; FIELDS_PER_BLOB]; + // Fill fields with 50 inputs... + for i in 0..50 { + blob[i] = 3; + } + // ...but the rollup's sponge is expecting 100... + let mut sponge_blob = SpongeBlob::new(100); + sponge_blob.absorb(blob, 50); + + // ...so the below should fail as it detects we have not added all the tx effects. + let padded_blob_fields = pad_end(blob, 0); + let _ = check_block_blob_sponge(padded_blob_fields, sponge_blob); + } + + #[test] + unconstrained fn test_empty_blob() { + let mut blob: [Field; FIELDS_PER_BLOB * BLOBS_PER_BLOCK] = + [0; FIELDS_PER_BLOB * BLOBS_PER_BLOCK]; + let mut sponge_blob = SpongeBlob::new(0); + // The below should not throw + let _ = check_block_blob_sponge(blob, sponge_blob); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching_public_inputs.nr index d350405e5c03..83b587f9892b 100644 --- a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching_public_inputs.nr @@ -82,7 +82,6 @@ pub fn compress_to_blob_commitment(point: BLSPoint) -> (BatchingBlobCommitment) let (flags, x) = get_flags(point); let mut compressed = x.to_be_bytes(); let most_sig_bits = byte_to_bits_be(compressed[0]); - // TODO(MW): May not need to assert this since Bignum may check it's 381 bits? for i in 0..3 { assert_eq(most_sig_bits[i], 0, "Invalid BLS12-381 x coordinate given to compress()."); } @@ -249,7 +248,7 @@ impl BlobAccumulatorPublicInputs { gamma == bigint::bignum::to_field(injected.gamma), "Final blob challenge gamma mismatch.", ); - // TODO(MW): Refactor BlobBatchingCommitment once BlobCommitment can be safely deleted + // Compress to 2 fields to reduce number of public inputs to the root rollup: let c = compress_to_blob_commitment(self.c_acc).to_compressed_fields(); FinalBlobAccumulatorPublicInputs { blob_commitments_hash: self.blob_commitments_hash_acc, diff --git a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/blob/src/blob_public_inputs.nr deleted file mode 100644 index fcb8f062c11f..000000000000 --- a/noir-projects/noir-protocol-circuits/crates/blob/src/blob_public_inputs.nr +++ /dev/null @@ -1,135 +0,0 @@ -use bigint::{BigNum, BLS12_381_Fr as F}; -use std::ops::Add; -use types::{ - constants::{BLOB_PUBLIC_INPUTS, BLOBS_PER_BLOCK}, - traits::{Deserialize, Empty, Serialize}, - utils::reader::Reader, -}; - -// TODO(MW): delete - -// NB: This only exists because a nested array of [[Field; 2]; N] did not build with ci2.5, but was fine otherwise -// For blobs, we use the compressed 48 byte BLS12 commitment to compute the challenge. We never need to operate on it, -// so it's encoded as 2 fields. The first is the first 31 bytes, and the second is the next 17 bytes. -pub struct BlobCommitment { - pub inner: [Field; 2], -} - -impl Eq for BlobCommitment { - fn eq(self, other: Self) -> bool { - self.inner.eq(other.inner) - } -} - -impl Empty for BlobCommitment { - fn empty() -> Self { - Self { inner: [0; 2] } - } -} - -pub struct BlobPublicInputs { - pub z: Field, - pub y: F, - pub kzg_commitment: BlobCommitment, -} - -impl BlobPublicInputs { - pub fn accumulate(self, other: Self) -> Self { - // TODO: When we verify root, rather than block root, on L1 we need to accumulate many blob openings - // @Mike this may be where we calculate z_acc, y_acc, C_acc, etc. - // WARNING: unimplemented, below is nonsense to get noir to compile - Self { z: self.z + other.z, y: self.y.add(other.y), kzg_commitment: self.kzg_commitment } - } - - // This checks whether the blob public inputs represent data of all 0s - // This is not equivalent to being empty, since the challenge point z is a hash and won't have 0 value. - pub fn is_zero(self) -> bool { - // Note: there is no constrained is_zero in bignum - (self.y == F::zero()) & (self.kzg_commitment.inner == [0, 0]) - } -} - -impl Empty for BlobPublicInputs { - fn empty() -> Self { - Self { z: 0, y: BigNum::zero(), kzg_commitment: BlobCommitment::empty() } - } -} - -impl Serialize for BlobPublicInputs { - fn serialize(self) -> [Field; BLOB_PUBLIC_INPUTS] { - [ - self.z, - self.y.get_limb(0) as Field, - self.y.get_limb(1) as Field, - self.y.get_limb(2) as Field, - self.kzg_commitment.inner[0], - self.kzg_commitment.inner[1], - ] - } -} - -impl Deserialize for BlobPublicInputs { - fn deserialize(fields: [Field; BLOB_PUBLIC_INPUTS]) -> Self { - Self { - z: fields[0], - y: F::from_limbs([fields[1] as u128, fields[2] as u128, fields[3] as u128]), - kzg_commitment: BlobCommitment { inner: [fields[4], fields[5]] }, - } - } -} - -impl Eq for BlobPublicInputs { - fn eq(self, other: Self) -> bool { - (self.z == other.z) & (self.y.eq(other.y)) & (self.kzg_commitment.eq(other.kzg_commitment)) - } -} - -// NB: it is much cleaner throughout the protocol circuits to define this struct rather than use a nested array. -// Once we accumulate blob inputs, it should be removed, and we just use BlobPublicInputs::accumulate everywhere. -pub struct BlockBlobPublicInputs { - pub inner: [BlobPublicInputs; BLOBS_PER_BLOCK], -} - -impl Empty for BlockBlobPublicInputs { - fn empty() -> Self { - Self { inner: [BlobPublicInputs::empty(); BLOBS_PER_BLOCK] } - } -} - -impl Serialize for BlockBlobPublicInputs { - fn serialize(self) -> [Field; BLOB_PUBLIC_INPUTS * BLOBS_PER_BLOCK] { - let mut fields: BoundedVec = BoundedVec::new(); - for i in 0..BLOBS_PER_BLOCK { - fields.extend_from_array(self.inner[i].serialize()); - } - fields.storage() - } -} - -impl Deserialize for BlockBlobPublicInputs { - fn deserialize(fields: [Field; BLOB_PUBLIC_INPUTS * BLOBS_PER_BLOCK]) -> Self { - let mut reader = Reader::new(fields); - let item = Self { - inner: reader.read_struct_array( - BlobPublicInputs::deserialize, - [BlobPublicInputs::empty(); BLOBS_PER_BLOCK], - ), - }; - reader.finish(); - item - } -} - -impl Eq for BlockBlobPublicInputs { - fn eq(self, other: Self) -> bool { - self.inner.eq(other.inner) - } -} - -#[test] -fn serialization_of_empty() { - let item = BlockBlobPublicInputs::empty(); - let serialized = item.serialize(); - let deserialized = BlockBlobPublicInputs::deserialize(serialized); - assert(item.eq(deserialized)); -} diff --git a/noir-projects/noir-protocol-circuits/crates/blob/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/blob/src/lib.nr index 31b51ca51fff..2b0b72700dd4 100644 --- a/noir-projects/noir-protocol-circuits/crates/blob/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/blob/src/lib.nr @@ -1,4 +1,3 @@ -pub mod blob_public_inputs; pub mod blob_batching_public_inputs; mod blob; mod blob_batching; diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index 4fbf7b9f8ec1..c2d03ab20db6 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -807,7 +807,7 @@ describe('Archiver', () => { */ async function makeRollupTx(l2Block: L2Block) { const header = l2Block.header.toPropose().toViem(); - const blobInput = Blob.getEthBlobEvaluationInputs(await Blob.getBlobs(l2Block.body.toBlobFields())); + const blobInput = Blob.getPrefixedEthBlobCommitments(await Blob.getBlobsPerBlock(l2Block.body.toBlobFields())); const archive = toHex(l2Block.archive.root.toBuffer()); const stateReference = l2Block.header.state.toViem(); const rollupInput = encodeFunctionData({ @@ -841,7 +841,7 @@ async function makeRollupTx(l2Block: L2Block) { * @returns Versioned blob hashes. */ async function makeVersionedBlobHashes(l2Block: L2Block): Promise<`0x${string}`[]> { - const blobHashes = (await Blob.getBlobs(l2Block.body.toBlobFields())).map(b => b.getEthVersionedBlobHash()); + const blobHashes = (await Blob.getBlobsPerBlock(l2Block.body.toBlobFields())).map(b => b.getEthVersionedBlobHash()); return blobHashes.map(h => `0x${h.toString('hex')}` as `0x${string})`); } @@ -851,6 +851,6 @@ async function makeVersionedBlobHashes(l2Block: L2Block): Promise<`0x${string}`[ * @returns The blobs. */ async function makeBlobsFromBlock(block: L2Block) { - const blobs = await Blob.getBlobs(block.body.toBlobFields()); + const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields()); return blobs.map((blob, index) => new BlobWithIndex(blob, index)); } diff --git a/yarn-project/blob-lib/src/blob.test.ts b/yarn-project/blob-lib/src/blob.test.ts index 24403dbe6822..de6c6536158d 100644 --- a/yarn-project/blob-lib/src/blob.test.ts +++ b/yarn-project/blob-lib/src/blob.test.ts @@ -92,51 +92,40 @@ describe('blob', () => { expect(z).toEqual(ourBlob.challengeZ); const res = computeKzgProof(dataWithZeros, ourBlob.challengeZ.toBuffer()); - expect(res[0]).toEqual(ourBlob.proof); - expect(res[1]).toEqual(ourBlob.evaluationY); - - const isValid = verifyKzgProof( - ourBlob.commitment, - ourBlob.challengeZ.toBuffer(), - ourBlob.evaluationY, - ourBlob.proof, - ); + const { y, proof } = ourBlob.evaluate(); + expect(res[0]).toEqual(proof); + expect(res[1]).toEqual(y); + + const isValid = verifyKzgProof(ourBlob.commitment, ourBlob.challengeZ.toBuffer(), y, proof); expect(isValid).toBe(true); }); - it('should evaluate full blobs', async () => { + it('should evaluate full blob', async () => { // This test ensures that the Blob class correctly matches the c-kzg lib - // The values here are used to test Noir's blob evaluation in noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr -> test_full_blobs + // The values here are used to test Noir's blob evaluation in noir-projects/noir-protocol-circuits/crates/blob/src/blob.nr -> test_full_blob const blobItems = []; - for (let j = 0; j < 3; j++) { - for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) { - blobItems[j * FIELD_ELEMENTS_PER_BLOB + i] = new Fr(i + 2); - } + for (let i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) { + blobItems[i] = new Fr(i + 2); } const blobItemsHash = await poseidon2Hash(blobItems); - const blobs = await Blob.getBlobs(blobItems); - for (const ourBlob of blobs) { - // const ourBlob = Blob.fromFields(blobItems.slice(j * FIELD_ELEMENTS_PER_BLOB, (j + 1) * FIELD_ELEMENTS_PER_BLOB), blobItemsHash); - expect(blobItemsHash).toEqual(ourBlob.fieldsHash); - - expect(blobToKzgCommitment(ourBlob.data)).toEqual(ourBlob.commitment); - - const z = await poseidon2Hash([blobItemsHash, ...ourBlob.commitmentToFields()]); - expect(z).toEqual(ourBlob.challengeZ); - - const res = computeKzgProof(ourBlob.data, ourBlob.challengeZ.toBuffer()); - expect(res[0]).toEqual(ourBlob.proof); - expect(res[1]).toEqual(ourBlob.evaluationY); - - const isValid = verifyKzgProof( - ourBlob.commitment, - ourBlob.challengeZ.toBuffer(), - ourBlob.evaluationY, - ourBlob.proof, - ); - expect(isValid).toBe(true); - } + const blobs = await Blob.getBlobsPerBlock(blobItems); + expect(blobs.length).toEqual(1); + const ourBlob = blobs[0]; + expect(blobItemsHash).toEqual(ourBlob.fieldsHash); + + expect(blobToKzgCommitment(ourBlob.data)).toEqual(ourBlob.commitment); + + const z = await poseidon2Hash([blobItemsHash, ...ourBlob.commitmentToFields()]); + expect(z).toEqual(ourBlob.challengeZ); + + const res = computeKzgProof(ourBlob.data, ourBlob.challengeZ.toBuffer()); + const { y, proof } = ourBlob.evaluate(); + expect(res[0]).toEqual(proof); + expect(res[1]).toEqual(y); + + const isValid = verifyKzgProof(ourBlob.commitment, ourBlob.challengeZ.toBuffer(), y, proof); + expect(isValid).toBe(true); }); it('should serialise and deserialise a blob', async () => { diff --git a/yarn-project/blob-lib/src/blob.ts b/yarn-project/blob-lib/src/blob.ts index 5643b5f2d491..598f1d565f30 100644 --- a/yarn-project/blob-lib/src/blob.ts +++ b/yarn-project/blob-lib/src/blob.ts @@ -24,14 +24,10 @@ export class Blob { public readonly data: BlobBuffer, /** The hash of all tx effects inside the blob. Used in generating the challenge z and proving that we have included all required effects. */ public readonly fieldsHash: Fr, - /** Challenge point z (= H(H(tx_effects), kzgCommmitment). Used such that p(z) = y. */ + /** Challenge point z (= H(H(tx_effects), kzgCommmitment). Used such that p(z) = y for a single blob, used as z_i in batching (see ./blob_batching.ts). */ public readonly challengeZ: Fr, - /** Evaluation y = p(z), where p() is the blob polynomial. BLS12 field element, rep. as BigNum in nr, bigint in ts. */ - public readonly evaluationY: Buffer, /** Commitment to the blob C. Used in compressed BLS12 point format (48 bytes). */ public readonly commitment: Buffer, - /** KZG opening proof for y = p(z). The commitment to quotient polynomial Q, used in compressed BLS12 point format (48 bytes). */ - public readonly proof: Buffer, ) {} /** @@ -78,14 +74,8 @@ export class Blob { const fieldsHash = multiBlobFieldsHash ? multiBlobFieldsHash : await poseidon2Hash(fields); const commitment = Buffer.from(blobToKzgCommitment(data)); const challengeZ = await poseidon2Hash([fieldsHash, ...commitmentToFields(commitment)]); - const res = computeKzgProof(data, challengeZ.toBuffer()); - if (!verifyKzgProof(commitment, challengeZ.toBuffer(), res[1], res[0])) { - throw new Error(`KZG proof did not verify.`); - } - const proof = Buffer.from(res[0]); - const evaluationY = Buffer.from(res[1]); - return new Blob(data, fieldsHash, challengeZ, evaluationY, commitment, proof); + return new Blob(data, fieldsHash, challengeZ, commitment); } /** @@ -128,8 +118,6 @@ export class Blob { index: index.toString(), // eslint-disable-next-line camelcase kzg_commitment: `0x${this.commitment.toString('hex')}`, - // eslint-disable-next-line camelcase - kzg_proof: `0x${this.proof.toString('hex')}`, }; } @@ -209,6 +197,26 @@ export class Blob { return hash; } + /** + * Evaluate the blob at a given challenge and return the evaluation and KZG proof. + * + * @param challengeZ - The challenge z at which to evaluate the blob. If not given, assume we want to evaluate at the individual blob's z. + * + * @returns - + * y: Buffer - Evaluation y = p(z), where p() is the blob polynomial. BLS12 field element, rep. as BigNum in nr, bigint in ts + * proof: Buffer - KZG opening proof for y = p(z). The commitment to quotient polynomial Q, used in compressed BLS12 point format (48 bytes). + */ + evaluate(challengeZ?: Fr) { + const z = challengeZ || this.challengeZ; + const res = computeKzgProof(this.data, z.toBuffer()); + if (!verifyKzgProof(this.commitment, z.toBuffer(), res[1], res[0])) { + throw new Error(`KZG proof did not verify.`); + } + const proof = Buffer.from(res[0]); + const y = Buffer.from(res[1]); + return { y, proof }; + } + /** * Get the buffer representation of the ENTIRE blob. * @@ -223,12 +231,8 @@ export class Blob { this.data, this.fieldsHash, this.challengeZ, - this.evaluationY.length, - this.evaluationY, this.commitment.length, this.commitment, - this.proof.length, - this.proof, ), ); } @@ -243,14 +247,7 @@ export class Blob { */ static fromBuffer(buf: Buffer | BufferReader): Blob { const reader = BufferReader.asReader(buf); - return new Blob( - reader.readUint8Array(), - reader.readObject(Fr), - reader.readObject(Fr), - reader.readBuffer(), - reader.readBuffer(), - reader.readBuffer(), - ); + return new Blob(reader.readUint8Array(), reader.readObject(Fr), reader.readObject(Fr), reader.readBuffer()); } /** @@ -261,52 +258,17 @@ export class Blob { } /** - * Returns a proof of opening of the blob to verify on L1 using the point evaluation precompile: - * - * input[:32] - versioned_hash - * input[32:64] - z - * input[64:96] - y - * input[96:144] - commitment C - * input[144:192] - proof (a commitment to the quotient polynomial q(X)) - * - * See https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile + * @param blobs - The blobs to emit + * @returns The blobs' compressed commitments in hex prefixed by the number of blobs + * @dev Used for proposing blocks to validate injected blob commitments match real broadcast blobs: + * One byte for the number blobs + 48 bytes per blob commitment */ - getEthBlobEvaluationInputs(): `0x${string}` { - const buf = Buffer.concat([ - this.getEthVersionedBlobHash(), - this.challengeZ.toBuffer(), - this.evaluationY, - this.commitment, - this.proof, - ]); - return `0x${buf.toString('hex')}`; - } - - static getEthBlobEvaluationInputs(blobs: Blob[]): `0x${string}` { - let buf = Buffer.alloc(0); - blobs.forEach(blob => { - buf = Buffer.concat([ - buf, - blob.getEthVersionedBlobHash(), - blob.challengeZ.toBuffer(), - blob.evaluationY, - blob.commitment, - blob.proof, - ]); - }); - // For multiple blobs, we prefix the number of blobs: - const lenBuf = Buffer.alloc(1); - lenBuf.writeUint8(blobs.length); - buf = Buffer.concat([lenBuf, buf]); - return `0x${buf.toString('hex')}`; - } - static getPrefixedEthBlobCommitments(blobs: Blob[]): `0x${string}` { let buf = Buffer.alloc(0); blobs.forEach(blob => { buf = Buffer.concat([buf, blob.commitment]); }); - // For multiple blobs, we prefix the number of blobs: + // We prefix the number of blobs: const lenBuf = Buffer.alloc(1); lenBuf.writeUint8(blobs.length); buf = Buffer.concat([lenBuf, buf]); @@ -320,11 +282,12 @@ export class Blob { }; } - // Returns as many blobs as we require to broadcast the given fields - // Assumes we share the fields hash between all blobs - // TODO(MW): Rename to more accurate getBlobsPerBlock() - the items here share a fields hash, - // which can only be done for one block because the hash is calculated in block root. - static async getBlobs(fields: Fr[]): Promise { + /** + * @param fields - Fields to broadcast in the blob(s) + * @returns As many blobs as we require to broadcast the given fields for a block + * @dev Assumes we share the fields hash between all blobs which can only be done for ONE BLOCK because the hash is calculated in block root. + */ + static async getBlobsPerBlock(fields: Fr[]): Promise { const numBlobs = Math.max(Math.ceil(fields.length / FIELD_ELEMENTS_PER_BLOB), 1); const multiBlobFieldsHash = await poseidon2Hash(fields); const res = []; diff --git a/yarn-project/blob-lib/src/blob_batching.test.ts b/yarn-project/blob-lib/src/blob_batching.test.ts index 24ad6664cc50..5941652b1661 100644 --- a/yarn-project/blob-lib/src/blob_batching.test.ts +++ b/yarn-project/blob-lib/src/blob_batching.test.ts @@ -1,6 +1,6 @@ import { BLOBS_PER_BLOCK, FIELDS_PER_BLOB } from '@aztec/constants'; import { fromHex } from '@aztec/foundation/bigint-buffer'; -import { poseidon2Hash, randomBigInt, sha256ToField } from '@aztec/foundation/crypto'; +import { poseidon2Hash, randomInt, sha256ToField } from '@aztec/foundation/crypto'; import { BLS12Fr, BLS12Point, Fr } from '@aztec/foundation/fields'; import { fileURLToPath } from '@aztec/foundation/url'; @@ -8,7 +8,7 @@ import cKzg from 'c-kzg'; import { readFileSync } from 'fs'; import { dirname, resolve } from 'path'; -import { BatchedBlob, Blob } from './index.js'; +import { BatchedBlob, BatchedBlobAccumulator, Blob } from './index.js'; // TODO(MW): Remove below file and test? Only required to ensure commiting and compression are correct. const trustedSetup = JSON.parse( @@ -30,7 +30,7 @@ try { } } -describe('blob', () => { +describe('Blob Batching', () => { it.each([10, 100, 400])('our BLS library should correctly commit to a blob of %p items', async size => { const blobItems: Fr[] = Array(size).fill(new Fr(size + 1)); const ourBlob = await Blob.fromFields(blobItems); @@ -57,7 +57,7 @@ describe('blob', () => { // Initialise 400 fields. This test shows that a single blob works with batching methods. // The values here are used to test Noir's blob evaluation in noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr -> test_400_batched const blobItems = Array(400).fill(new Fr(3)); - const blobs = await Blob.getBlobs(blobItems); + const blobs = await Blob.getBlobsPerBlock(blobItems); // Challenge for the final opening (z) const zis = blobs.map(b => b.challengeZ); @@ -114,7 +114,7 @@ describe('blob', () => { const items = [new Fr(3), new Fr(4), new Fr(5)].map(f => new Array(FIELDS_PER_BLOB).fill(f).map((elt, i) => elt.mul(new Fr(i + 1))), ); - const blobs = await Blob.getBlobs(items.flat()); + const blobs = await Blob.getBlobsPerBlock(items.flat()); // Challenge for the final opening (z) const zis = blobs.map(b => b.challengeZ); @@ -173,14 +173,36 @@ describe('blob', () => { ])('should construct and verify a batch of blobs over %p blocks', async blocks => { const items = new Array(FIELD_ELEMENTS_PER_BLOB * blocks * BLOBS_PER_BLOCK) .fill(Fr.ZERO) - .map((_, i) => new Fr(BigInt(i) + randomBigInt(120n))); + .map((_, i) => new Fr(i + randomInt(120))); const blobs = []; for (let i = 0; i < blocks; i++) { const start = i * FIELD_ELEMENTS_PER_BLOB * BLOBS_PER_BLOCK; - blobs.push(...(await Blob.getBlobs(items.slice(start, start + FIELD_ELEMENTS_PER_BLOB * BLOBS_PER_BLOCK)))); + blobs.push( + ...(await Blob.getBlobsPerBlock(items.slice(start, start + FIELD_ELEMENTS_PER_BLOB * BLOBS_PER_BLOCK))), + ); } // BatchedBlob.batch() performs a verification check: await BatchedBlob.batch(blobs); }); }); + +describe('BatchedBlobAccumulator', () => { + let acc: BatchedBlobAccumulator; + let blobs: Blob[]; + + beforeAll(async () => { + const items = new Array(FIELD_ELEMENTS_PER_BLOB * BLOBS_PER_BLOCK) + .fill(Fr.ZERO) + .map((_, i) => new Fr(i + randomInt(120))); + blobs = await Blob.getBlobsPerBlock(items); + acc = await BatchedBlob.newAccumulator(blobs); + }); + + it('clones correctly', async () => { + const clone = acc.clone(); + expect(acc).toEqual(clone); + const modified = await clone.accumulate(blobs[0]); + expect(acc).not.toEqual(modified); + }); +}); diff --git a/yarn-project/blob-lib/src/blob_batching.ts b/yarn-project/blob-lib/src/blob_batching.ts index 2370b1b92fbb..8cf496fe079a 100644 --- a/yarn-project/blob-lib/src/blob_batching.ts +++ b/yarn-project/blob-lib/src/blob_batching.ts @@ -7,7 +7,6 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import cKzg from 'c-kzg'; import { Blob, VERSIONED_HASH_VERSION_KZG } from './blob.js'; -import { BlobAccumulatorPublicInputs, FinalBlobAccumulatorPublicInputs } from './blob_batching_public_inputs.js'; const { computeKzgProof, verifyKzgProof } = cKzg; @@ -30,10 +29,6 @@ export class BatchedBlob { /** * Get the final batched opening proof from multiple blobs. - * - * TODO(MW): Using the old Blob struct means there are ignored values (e.g. blob.evaluationY, because we now evaluate at shared z). - * When switching to batching, create new class w/o useless values. - * * @dev MUST input all blobs to be broadcast. Does not work in multiple calls because z and gamma are calculated * beforehand from ALL blobs. * @@ -222,7 +217,6 @@ export class BatchedBlobAccumulator { * - gamma_acc := poseidon2(y_0.limbs) * - gamma^(i + 1) = gamma^1 = gamma // denoted gamma_pow_acc * - * TODO(MW): When moved to batching, we should ONLY evaluate individual blobs at z => won't need finalZ input. * @returns An initial blob accumulator. */ static async initialize( @@ -264,7 +258,6 @@ export class BatchedBlobAccumulator { /** * Given blob i, accumulate all state. * We assume the input blob has not been evaluated at z. - * TODO(MW): Currently returning new accumulator. May be better to mutate in future? * @returns An updated blob accumulator. */ async accumulate(blob: Blob) { @@ -294,8 +287,8 @@ export class BatchedBlobAccumulator { * @returns An updated blob accumulator. */ async accumulateBlobs(blobs: Blob[]) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - let acc: BatchedBlobAccumulator = this; // TODO(MW): this.clone() + // Initialise the acc to iterate over: + let acc: BatchedBlobAccumulator = this.clone(); for (let i = 0; i < blobs.length; i++) { acc = await acc.accumulate(blobs[i]); } @@ -336,30 +329,6 @@ export class BatchedBlobAccumulator { return new BatchedBlob(this.blobCommitmentsHashAcc, this.zAcc, this.yAcc, this.cAcc, this.qAcc); } - /** - * Converts to a struct for the public inputs of our rollup circuits. - * @returns A BlobAccumulatorPublicInputs instance. - */ - toBlobAccumulatorPublicInputs() { - return new BlobAccumulatorPublicInputs( - this.blobCommitmentsHashAcc, - this.zAcc, - this.yAcc, - this.cAcc, - this.gammaAcc, - this.gammaPow, - ); - } - - /** - * Converts to a struct for the public inputs of our root rollup circuit. - * Warning: MUST be final accumulator state. - * @returns A FinalBlobAccumulatorPublicInputs instance. - */ - toFinalBlobAccumulatorPublicInputs() { - return new FinalBlobAccumulatorPublicInputs(this.blobCommitmentsHashAcc, this.zAcc, this.yAcc, this.cAcc); - } - isEmptyState() { return ( this.blobCommitmentsHashAcc.isZero() && @@ -371,6 +340,19 @@ export class BatchedBlobAccumulator { this.gammaPow.isZero() ); } + + clone() { + return new BatchedBlobAccumulator( + Fr.fromBuffer(this.blobCommitmentsHashAcc.toBuffer()), + Fr.fromBuffer(this.zAcc.toBuffer()), + BLS12Fr.fromBuffer(this.yAcc.toBuffer()), + BLS12Point.fromBuffer(this.cAcc.toBuffer()), + BLS12Point.fromBuffer(this.qAcc.toBuffer()), + Fr.fromBuffer(this.gammaAcc.toBuffer()), + BLS12Fr.fromBuffer(this.gammaPow.toBuffer()), + FinalBlobBatchingChallenges.fromBuffer(this.finalBlobChallenges.toBuffer()), + ); + } } // To mimic the hash accumulation in the rollup circuits, here we hash diff --git a/yarn-project/blob-lib/src/blob_batching_public_inputs.test.ts b/yarn-project/blob-lib/src/blob_batching_public_inputs.test.ts index 61e9bd8972e2..8f4ea690f17c 100644 --- a/yarn-project/blob-lib/src/blob_batching_public_inputs.test.ts +++ b/yarn-project/blob-lib/src/blob_batching_public_inputs.test.ts @@ -38,34 +38,26 @@ describe('BlockBlobPublicInputs', () => { const res = BlockBlobPublicInputs.fromBuffer(buffer); expect(res).toEqual(blobPI); }); - - it('converts correctly from Blob class', async () => { - const blobs = await timesParallel(BLOBS_PER_BLOCK, i => Blob.fromFields(Array(400).fill(new Fr(i + 1)))); - const startBlobAccumulator = makeBatchedBlobAccumulator(randomInt(1000)); - const converted = await BlockBlobPublicInputs.fromBlobs(startBlobAccumulator, blobs); - const expectedEndBlobAccumulator = await startBlobAccumulator.accumulateBlobs(blobs); - expect(converted.endBlobAccumulator).toEqual(expectedEndBlobAccumulator.toBlobAccumulatorPublicInputs()); - }); }); describe('BlobAccumulatorPublicInputs', () => { let blobPI: BlobAccumulatorPublicInputs; beforeAll(() => { - blobPI = makeBatchedBlobAccumulator(randomInt(1000)).toBlobAccumulatorPublicInputs(); + blobPI = BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(makeBatchedBlobAccumulator(randomInt(1000))); }); it('serializes to buffer and deserializes it back', () => { const buffer = blobPI.toBuffer(); const res = BlobAccumulatorPublicInputs.fromBuffer(buffer); - expect(res.equals(blobPI)).toBeTruthy(); + expect(res).toEqual(blobPI); }); it('serializes to fields and deserializes it back', () => { const fields = blobPI.toFields(); expect(fields.length).toEqual(BLOB_ACCUMULATOR_PUBLIC_INPUTS); const res = BlobAccumulatorPublicInputs.fromFields(fields); - expect(res.equals(blobPI)).toBeTruthy(); + expect(res).toEqual(blobPI); }); }); @@ -73,7 +65,7 @@ describe('FinalBlobAccumulatorPublicInputs', () => { let blobPI: FinalBlobAccumulatorPublicInputs; beforeAll(() => { - blobPI = makeBatchedBlobAccumulator(randomInt(1000)).toFinalBlobAccumulatorPublicInputs(); + blobPI = FinalBlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(makeBatchedBlobAccumulator(randomInt(1000))); }); it('serializes to buffer and deserializes it back', () => { diff --git a/yarn-project/blob-lib/src/blob_batching_public_inputs.ts b/yarn-project/blob-lib/src/blob_batching_public_inputs.ts index 4998af4d3a31..7d4bbd0abf45 100644 --- a/yarn-project/blob-lib/src/blob_batching_public_inputs.ts +++ b/yarn-project/blob-lib/src/blob_batching_public_inputs.ts @@ -115,6 +115,21 @@ export class BlobAccumulatorPublicInputs { BLS12Fr.fromNoirBigNum({ limbs: reader.readFieldArray(BLS12_FR_LIMBS).map(f => f.toString()) }), ); } + + /** + * Converts from an accumulator to a struct for the public inputs of our rollup circuits. + * @returns A BlobAccumulatorPublicInputs instance. + */ + static fromBatchedBlobAccumulator(accumulator: BatchedBlobAccumulator) { + return new BlobAccumulatorPublicInputs( + accumulator.blobCommitmentsHashAcc, + accumulator.zAcc, + accumulator.yAcc, + accumulator.cAcc, + accumulator.gammaAcc, + accumulator.gammaPow, + ); + } } /** @@ -155,8 +170,7 @@ export class FinalBlobAccumulatorPublicInputs { this.blobCommitmentsHash, this.z, ...this.y.toNoirBigNum().limbs.map(Fr.fromString), - // TODO(MW): add conversion when public inputs finalised - ...[new Fr(this.c.compress().subarray(0, 31)), new Fr(this.c.compress().subarray(31, 48))], + ...this.c.toBN254Fields(), ]; } @@ -183,6 +197,16 @@ export class FinalBlobAccumulatorPublicInputs { return new FinalBlobAccumulatorPublicInputs(Fr.random(), Fr.random(), BLS12Fr.random(), BLS12Point.random()); } + // Warning: MUST be final accumulator state. + static fromBatchedBlobAccumulator(accumulator: BatchedBlobAccumulator) { + return new FinalBlobAccumulatorPublicInputs( + accumulator.blobCommitmentsHashAcc, + accumulator.zAcc, + accumulator.yAcc, + accumulator.cAcc, + ); + } + [inspect.custom]() { return `FinalBlobAccumulatorPublicInputs { blobCommitmentsHash: ${inspect(this.blobCommitmentsHash)}, @@ -225,17 +249,4 @@ export class BlockBlobPublicInputs { toBuffer() { return serializeToBuffer(this.startBlobAccumulator, this.endBlobAccumulator, this.finalBlobChallenges); } - - // Creates BlockBlobPublicInputs from the starting accumulator state and all blobs in the block. - // Assumes that startBlobAccumulator.finalChallenges have already been precomputed. - // Does not finalise challenge values (this is done in the final root rollup). - // TODO(MW): Integrate with BatchedBlob once old Blob classes removed - static async fromBlobs(startBlobAccumulator: BatchedBlobAccumulator, blobs: Blob[]): Promise { - const endBlobAccumulator = await startBlobAccumulator.accumulateBlobs(blobs); - return new BlockBlobPublicInputs( - startBlobAccumulator.toBlobAccumulatorPublicInputs(), - endBlobAccumulator.toBlobAccumulatorPublicInputs(), - startBlobAccumulator.finalBlobChallenges, - ); - } } diff --git a/yarn-project/blob-lib/src/interface.ts b/yarn-project/blob-lib/src/interface.ts index 22cb5c6ba3e9..94fd7f6be5f0 100644 --- a/yarn-project/blob-lib/src/interface.ts +++ b/yarn-project/blob-lib/src/interface.ts @@ -4,8 +4,5 @@ export interface BlobJson { blob: string; index: string; - kzg_commitment: string; - - kzg_proof: string; } diff --git a/yarn-project/blob-lib/src/testing.ts b/yarn-project/blob-lib/src/testing.ts index ca3b5595a714..bba47ab3a481 100644 --- a/yarn-project/blob-lib/src/testing.ts +++ b/yarn-project/blob-lib/src/testing.ts @@ -4,7 +4,7 @@ import { BLS12Fr, BLS12Point, Fr } from '@aztec/foundation/fields'; import { Blob } from './blob.js'; import { BatchedBlobAccumulator, FinalBlobBatchingChallenges } from './blob_batching.js'; -import { BlockBlobPublicInputs } from './blob_batching_public_inputs.js'; +import { BlobAccumulatorPublicInputs, BlockBlobPublicInputs } from './blob_batching_public_inputs.js'; import { TX_START_PREFIX, TX_START_PREFIX_BYTES_LENGTH } from './encoding.js'; import { Poseidon2Sponge, SpongeBlob } from './sponge_blob.js'; @@ -55,8 +55,8 @@ export function makeBatchedBlobAccumulator(seed = 1): BatchedBlobAccumulator { export function makeBlockBlobPublicInputs(seed = 1): BlockBlobPublicInputs { const startBlobAccumulator = makeBatchedBlobAccumulator(seed); return new BlockBlobPublicInputs( - startBlobAccumulator.toBlobAccumulatorPublicInputs(), - makeBatchedBlobAccumulator(seed + 1).toBlobAccumulatorPublicInputs(), + BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(startBlobAccumulator), + BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(makeBatchedBlobAccumulator(seed + 1)), startBlobAccumulator.finalBlobChallenges, ); } diff --git a/yarn-project/blob-sink/src/archive/blobscan_archive_client.ts b/yarn-project/blob-sink/src/archive/blobscan_archive_client.ts index 0b781329366c..0e14e2920ad9 100644 --- a/yarn-project/blob-sink/src/archive/blobscan_archive_client.ts +++ b/yarn-project/blob-sink/src/archive/blobscan_archive_client.ts @@ -37,8 +37,6 @@ export const BlobscanBlockResponseSchema = z blob: blob.data, // eslint-disable-next-line camelcase kzg_commitment: blob.commitment, - // eslint-disable-next-line camelcase - kzg_proof: blob.proof, })), ) .map((blob, index) => ({ ...blob, index: index.toString() })), diff --git a/yarn-project/blob-sink/src/client/http.test.ts b/yarn-project/blob-sink/src/client/http.test.ts index 79a7b4588a6f..34c46181bac9 100644 --- a/yarn-project/blob-sink/src/client/http.test.ts +++ b/yarn-project/blob-sink/src/client/http.test.ts @@ -83,8 +83,6 @@ describe('HttpBlobSinkClient', () => { blob: `0x${Buffer.from(testEncodedBlob.data).toString('hex')}`, // eslint-disable-next-line camelcase kzg_commitment: `0x${testEncodedBlob.commitment.toString('hex')}`, - // eslint-disable-next-line camelcase - kzg_proof: `0x${testEncodedBlob.proof.toString('hex')}`, }, // Correctly encoded blob, but we do not ask for it in the client { @@ -92,8 +90,6 @@ describe('HttpBlobSinkClient', () => { blob: `0x${Buffer.from(testBlobIgnore.data).toString('hex')}`, // eslint-disable-next-line camelcase kzg_commitment: `0x${testBlobIgnore.commitment.toString('hex')}`, - // eslint-disable-next-line camelcase - kzg_proof: `0x${testBlobIgnore.proof.toString('hex')}`, }, // Incorrectly encoded blob { @@ -101,8 +97,6 @@ describe('HttpBlobSinkClient', () => { blob: `0x${Buffer.from(testNonEncodedBlob.data).toString('hex')}`, // eslint-disable-next-line camelcase kzg_commitment: `0x${testNonEncodedBlob.commitment.toString('hex')}`, - // eslint-disable-next-line camelcase - kzg_proof: `0x${testNonEncodedBlob.proof.toString('hex')}`, }, ]; }); diff --git a/yarn-project/blob-sink/src/server/server.test.ts b/yarn-project/blob-sink/src/server/server.test.ts index 41d1fc273227..0bead5b42672 100644 --- a/yarn-project/blob-sink/src/server/server.test.ts +++ b/yarn-project/blob-sink/src/server/server.test.ts @@ -86,11 +86,11 @@ describe('BlobSinkService', () => { expect(retrievedBlob.fieldsHash.toString()).toBe(blob.fieldsHash.toString()); expect(retrievedBlob.commitment.toString('hex')).toBe(blob.commitment.toString('hex')); - expect(retrievedBlob.proof.toString('hex')).toBe(blob.proof.toString('hex')); + expect(retrievedBlob.evaluate().proof.toString('hex')).toBe(blob.evaluate().proof.toString('hex')); expect(retrievedBlob2.fieldsHash.toString()).toBe(blob2.fieldsHash.toString()); expect(retrievedBlob2.commitment.toString('hex')).toBe(blob2.commitment.toString('hex')); - expect(retrievedBlob2.proof.toString('hex')).toBe(blob2.proof.toString('hex')); + expect(retrievedBlob2.evaluate().proof.toString('hex')).toBe(blob2.evaluate().proof.toString('hex')); }); it('should retrieve specific indicies', async () => { @@ -107,11 +107,11 @@ describe('BlobSinkService', () => { const retrievedBlob2 = await Blob.fromEncodedBlobBuffer(Buffer.from(retrievedBlobs[1].blob.slice(2), 'hex')); expect(retrievedBlob.fieldsHash.toString()).toBe(blob.fieldsHash.toString()); expect(retrievedBlob.commitment.toString('hex')).toBe(blob.commitment.toString('hex')); - expect(retrievedBlob.proof.toString('hex')).toBe(blob.proof.toString('hex')); + expect(retrievedBlob.evaluate().proof.toString('hex')).toBe(blob.evaluate().proof.toString('hex')); expect(retrievedBlob2.fieldsHash.toString()).toBe(blob2.fieldsHash.toString()); expect(retrievedBlob2.commitment.toString('hex')).toBe(blob2.commitment.toString('hex')); - expect(retrievedBlob2.proof.toString('hex')).toBe(blob2.proof.toString('hex')); + expect(retrievedBlob2.evaluate().proof.toString('hex')).toBe(blob2.evaluate().proof.toString('hex')); }); it('should retrieve a single index', async () => { @@ -124,7 +124,7 @@ describe('BlobSinkService', () => { const retrievedBlob = await Blob.fromEncodedBlobBuffer(Buffer.from(retrievedBlobs[0].blob.slice(2), 'hex')); expect(retrievedBlob.fieldsHash.toString()).toBe(blob2.fieldsHash.toString()); expect(retrievedBlob.commitment.toString('hex')).toBe(blob2.commitment.toString('hex')); - expect(retrievedBlob.proof.toString('hex')).toBe(blob2.proof.toString('hex')); + expect(retrievedBlob.evaluate().proof.toString('hex')).toBe(blob2.evaluate().proof.toString('hex')); }); }); diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 901f0b44bd95..ef88b8e0c53a 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -406,7 +406,7 @@ describe('L1Publisher integration', () => { // Check that we have not yet written a root to this blocknumber expect(BigInt(emptyRoot)).toStrictEqual(0n); - const blockBlobs = await Blob.getBlobs(block.body.toBlobFields()); + const blockBlobs = await Blob.getBlobsPerBlock(block.body.toBlobFields()); expect(block.header.contentCommitment.blobsHash).toEqual( sha256ToField(blockBlobs.map(b => b.getEthVersionedBlobHash())), ); diff --git a/yarn-project/foundation/src/fields/bls12_point.ts b/yarn-project/foundation/src/fields/bls12_point.ts index 94027f68e1e5..b653fe842da0 100644 --- a/yarn-project/foundation/src/fields/bls12_point.ts +++ b/yarn-project/foundation/src/fields/bls12_point.ts @@ -9,6 +9,7 @@ import { hexSchemaFor } from '../schemas/utils.js'; import { BufferReader, serializeToBuffer } from '../serialize/index.js'; import { bufferToHex, hexToBuffer } from '../string/index.js'; import { BLS12Fq, BLS12Fr } from './bls12_fields.js'; +import { Fr } from './fields.js'; /** * Represents a Point on an elliptic curve with x and y coordinates. @@ -156,6 +157,33 @@ export class BLS12Point { } } + /** + * Converts a Point to two BN254 Fr elements by storing its compressed form as: + * +------------------+------------------+ + * | Field Element 1 | Field Element 2 | + * | [bytes 0-31] | [bytes 32-47] | + * +------------------+------------------+ + * | 32 bytes | 16 bytes | + * +------------------+------------------+ + * Used in the rollup circuits to store blob commitments in the native field type. See blob.ts. + * @param point - A BLS12Point instance. + * @returns The point fields. + */ + toBN254Fields() { + const compressed = this.compress(); + return [new Fr(compressed.subarray(0, 31)), new Fr(compressed.subarray(31, 48))]; + } + + /** + * Creates a Point instance from 2 BN254 Fr fields as encoded in toBNFields() above. + * Used in the rollup circuits to store blob commitments in the native field type. See blob.ts. + * @param fields - The encoded BN254 fields. + * @returns The point fields. + */ + static fromBN254Fields(fields: [Fr, Fr]) { + return BLS12Point.decompress(Buffer.concat([fields[0].toBuffer().subarray(1), fields[1].toBuffer().subarray(-17)])); + } + /** * Creates a point from an array of 2 fields. * @returns The point diff --git a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts index 77149e65a03b..f5dd516d2928 100644 --- a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts +++ b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts @@ -344,13 +344,7 @@ export function mapFinalBlobAccumulatorPublicInputsFromNoir( mapFieldFromNoir(finalBlobPublicInputs.blob_commitments_hash), mapFieldFromNoir(finalBlobPublicInputs.z), mapBLS12FrFromNoir(finalBlobPublicInputs.y), - // TODO(MW): add conversion when public inputs final - BLS12Point.decompress( - Buffer.concat([ - mapFieldFromNoir(finalBlobPublicInputs.c[0]).toBuffer().subarray(1), - mapFieldFromNoir(finalBlobPublicInputs.c[1]).toBuffer().subarray(-17), - ]), - ), + BLS12Point.fromBN254Fields(mapTupleFromNoir(finalBlobPublicInputs.c, 2, mapFieldFromNoir)), ); } diff --git a/yarn-project/noir-protocol-circuits-types/src/utils/server/foreign_call_handler.ts b/yarn-project/noir-protocol-circuits-types/src/utils/server/foreign_call_handler.ts index 36ed54f2946a..419463998af6 100644 --- a/yarn-project/noir-protocol-circuits-types/src/utils/server/foreign_call_handler.ts +++ b/yarn-project/noir-protocol-circuits-types/src/utils/server/foreign_call_handler.ts @@ -74,7 +74,7 @@ export async function foreignCallHandler(name: string, args: ForeignCallInput[]) // const blobsAsFr: Fr[] = args[0].map((field: string) => Fr.fromString(field)).filter(field => !field.isZero()); // ...but we now have private logs which have a fixed number of fields and may have 0 values. // TODO(Miranda): trim 0 fields from private logs - const blobs = await Blob.getBlobs(blobsAsFr); + const blobs = await Blob.getBlobsPerBlock(blobsAsFr); // Checks on injected values: const hash = await spongeBlob.squeeze(); blobs.forEach((blob, i) => { diff --git a/yarn-project/prover-client/src/block-factory/light.test.ts b/yarn-project/prover-client/src/block-factory/light.test.ts index 403bcef024c3..8256baf47407 100644 --- a/yarn-project/prover-client/src/block-factory/light.test.ts +++ b/yarn-project/prover-client/src/block-factory/light.test.ts @@ -1,5 +1,5 @@ import { TestCircuitProver } from '@aztec/bb-prover'; -import { BatchedBlob, BatchedBlobAccumulator, Blob, SpongeBlob } from '@aztec/blob-lib'; +import { BatchedBlob, BatchedBlobAccumulator, Blob, BlobAccumulatorPublicInputs, SpongeBlob } from '@aztec/blob-lib'; import { BLOBS_PER_BLOCK, FIELDS_PER_BLOB, @@ -347,7 +347,7 @@ describe('LightBlockBuilder', () => { const getBlobData = async (txs: ProcessedTx[]) => { const blobFields = txs.map(tx => tx.txEffect.toBlobFields()).flat(); - const blobs = await Blob.getBlobs(blobFields); + const blobs = await Blob.getBlobsPerBlock(blobFields); const startBlobAccumulator = await BatchedBlob.newAccumulator(blobs); const blobsHash = getBlobsHashFromBlobs(blobs); return { @@ -398,7 +398,7 @@ describe('LightBlockBuilder', () => { previousArchiveSiblingPath, newArchiveSiblingPath, previousBlockHeader, - startBlobAccumulator: startBlobAccumulator.toBlobAccumulatorPublicInputs(), + startBlobAccumulator: BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(startBlobAccumulator), finalBlobChallenges: startBlobAccumulator.finalBlobChallenges, proverId: Fr.ZERO, }); diff --git a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts index ffa97bc34113..955f879d0b2e 100644 --- a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts +++ b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts @@ -253,7 +253,7 @@ export const buildBlobHints = runInSpan( 'buildBlobHints', async (_span: Span, txEffects: TxEffect[]) => { const blobFields = txEffects.flatMap(tx => tx.toBlobFields()); - const blobs = await Blob.getBlobs(blobFields); + const blobs = await Blob.getBlobsPerBlock(blobFields); // TODO(#13430): The blobsHash is confusingly similar to blobCommitmentsHash, calculated from below blobCommitments: // - blobsHash := sha256([blobhash_0, ..., blobhash_m]) = a hash of all blob hashes in a block with m+1 blobs inserted into the header, exists so a user can cross check blobs. // - blobCommitmentsHash := sha256( ...sha256(sha256(C_0), C_1) ... C_n) = iteratively calculated hash of all blob commitments in an epoch with n+1 blobs (see calculateBlobCommitmentsHash()), @@ -270,7 +270,7 @@ export const accumulateBlobs = runInSpan( 'accumulateBlobs', async (_span: Span, txs: ProcessedTx[], startBlobAccumulator: BatchedBlobAccumulator) => { const blobFields = txs.flatMap(tx => tx.txEffect.toBlobFields()); - const blobs = await Blob.getBlobs(blobFields); + const blobs = await Blob.getBlobsPerBlock(blobFields); const endBlobAccumulator = startBlobAccumulator.accumulateBlobs(blobs); return endBlobAccumulator; }, @@ -361,7 +361,7 @@ export const buildHeaderAndBodyFromTxs = runInSpan( hasher, ); const parityShaRoot = new Fr(await parityCalculator.computeTreeRoot(l1ToL2Messages.map(msg => msg.toBuffer()))); - const blobsHash = getBlobsHashFromBlobs(await Blob.getBlobs(body.toBlobFields())); + const blobsHash = getBlobsHashFromBlobs(await Blob.getBlobsPerBlock(body.toBlobFields())); const contentCommitment = new ContentCommitment(blobsHash, parityShaRoot, outHash); @@ -378,6 +378,14 @@ export function getBlobsHashFromBlobs(inputs: Blob[]): Fr { return sha256ToField(inputs.map(b => b.getEthVersionedBlobHash())); } +// Note: tested against the constant values in block_root/empty_block_root_rollup_inputs.nr, set by block_building_helpers.test.ts. +// Having this separate fn hopefully makes it clear how we treat empty blocks and their blobs, and won't break if we decide to change how +// getBlobsPerBlock() works on empty input. +export async function getEmptyBlockBlobsHash(): Promise { + const blobHash = (await Blob.getBlobsPerBlock([])).map(b => b.getEthVersionedBlobHash()); + return sha256ToField(blobHash); +} + // Validate that the roots of all local trees match the output of the root circuit simulation // TODO: does this get called? export async function validateBlockRootOutput( diff --git a/yarn-project/prover-client/src/orchestrator/block-proving-state.ts b/yarn-project/prover-client/src/orchestrator/block-proving-state.ts index a71106ed76c2..be80f1eceaf1 100644 --- a/yarn-project/prover-client/src/orchestrator/block-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/block-proving-state.ts @@ -1,4 +1,4 @@ -import { BatchedBlobAccumulator, SpongeBlob } from '@aztec/blob-lib'; +import { BatchedBlobAccumulator, BlobAccumulatorPublicInputs, SpongeBlob } from '@aztec/blob-lib'; import { type ARCHIVE_HEIGHT, BLOBS_PER_BLOCK, @@ -34,7 +34,12 @@ import type { AppendOnlyTreeSnapshot, MerkleTreeId } from '@aztec/stdlib/trees'; import { type BlockHeader, type GlobalVariables, StateReference } from '@aztec/stdlib/tx'; import { VkData } from '@aztec/stdlib/vks'; -import { accumulateBlobs, buildBlobHints, buildHeaderFromCircuitOutputs } from './block-building-helpers.js'; +import { + accumulateBlobs, + buildBlobHints, + buildHeaderFromCircuitOutputs, + getEmptyBlockBlobsHash, +} from './block-building-helpers.js'; import type { EpochProvingState } from './epoch-proving-state.js'; import type { TxProvingState } from './tx-proving-state.js'; @@ -221,6 +226,8 @@ export class BlockProvingState { protocolContractTreeRoot, }); + this.blobsHash = await getEmptyBlockBlobsHash(); + return { rollupType: 'empty-block-root-rollup' satisfies CircuitName, inputs: EmptyBlockRootRollupInputs.from({ @@ -272,7 +279,7 @@ export class BlockProvingState { previousArchiveSiblingPath: this.lastArchiveSiblingPath, newArchiveSiblingPath: this.newArchiveSiblingPath, previousBlockHeader, - startBlobAccumulator: this.endBlobAccumulator.toBlobAccumulatorPublicInputs(), + startBlobAccumulator: BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(this.endBlobAccumulator), finalBlobChallenges: this.endBlobAccumulator.finalBlobChallenges, proverId, }); @@ -325,18 +332,11 @@ export class BlockProvingState { } const endState = new StateReference(this.l1ToL2MessageTreeSnapshotAfterInsertion, endPartialState); - // TODO(MW): cleanup - if (!this.blobsHash) { - this.blobsHash = ( - await buildBlobHints(this.txs.map(txProvingState => txProvingState.processedTx.txEffect)) - ).blobsHash; - } - return buildHeaderFromCircuitOutputs( previousRollupData.map(d => d.baseOrMergeRollupPublicInputs), this.rootParityProvingOutput!.inputs, this.blockRootProvingOutput!.inputs, - this.blobsHash, + this.blobsHash!, endState, ); } @@ -382,7 +382,7 @@ export class BlockProvingState { previousArchiveSiblingPath: this.lastArchiveSiblingPath, newArchiveSiblingPath: this.newArchiveSiblingPath, previousBlockHeader: this.previousBlockHeader, - startBlobAccumulator: this.startBlobAccumulator!.toBlobAccumulatorPublicInputs(), + startBlobAccumulator: BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(this.startBlobAccumulator!), finalBlobChallenges: this.startBlobAccumulator!.finalBlobChallenges, proverId, }); diff --git a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts index d55a1adcc1d8..6da48c0315ee 100644 --- a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts +++ b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts @@ -2,7 +2,7 @@ import { BLS12Point, Fr } from '@aztec/foundation/fields'; import { updateInlineTestData } from '@aztec/foundation/testing/files'; import { TxEffect, TxHash } from '@aztec/stdlib/tx'; -import { buildBlobHints } from './block-building-helpers.js'; +import { buildBlobHints, getEmptyBlockBlobsHash } from './block-building-helpers.js'; function fieldArrToStr(arr: Fr[]) { return `[${arr.map(f => (f.isZero() ? '0' : f.toString())).join(', ')}]`; @@ -18,19 +18,16 @@ describe('buildBlobHints', () => { const blobCommitmentStr = blobCommitments[0].compress().toString('hex'); expect(blobCommitmentStr).toEqual(BLS12Point.COMPRESSED_ZERO.toString('hex')); + expect(await getEmptyBlockBlobsHash()).toEqual(blobsHash); const blobsHashStr = blobsHash.toString(); expect(blobsHashStr).toMatchInlineSnapshot(`"0x001cedbd7ea5309ef9d1d159209835409bf41b6b1802597a52fa70cc82e934d9"`); expect(blobs.length).toBe(1); - expect(blobs[0].evaluationY).toEqual(Buffer.alloc(32)); + expect(blobs[0].evaluate().y).toEqual(Buffer.alloc(32)); const zStr = blobs[0].challengeZ.toString(); expect(zStr).toMatchInlineSnapshot(`"0x0ac4f3ee53aedc4865073ae7fb664e7401d10eadbe3bbcc266c35059f14826bb"`); - // TODO(MW): add conversion when public inputs finalised - const blobCommitmentsFields = [ - new Fr(blobCommitments[0].compress().subarray(0, 31)), - new Fr(blobCommitments[0].compress().subarray(31, 48)), - ]; + const blobCommitmentsFields = blobCommitments[0].toBN254Fields(); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data. updateInlineTestData( @@ -74,17 +71,13 @@ describe('buildBlobHints', () => { expect(blobsHashStr).toMatchInlineSnapshot(`"0x00a965619c8668b834755678b32d023b9c5e8588ce449f44f7fa9335455b5cc5"`); expect(blobs.length).toBe(1); - expect(blobs[0].evaluationY.toString('hex')).toMatchInlineSnapshot( + expect(blobs[0].evaluate().y.toString('hex')).toMatchInlineSnapshot( `"25fb571bd6a15d4e3a8f6fe199b714c51e1e03ef40366e2e77e5c5733ab9e57d"`, ); const zStr = blobs[0].challengeZ.toString(); expect(zStr).toMatchInlineSnapshot(`"0x1f92b871671f27a378d23f1cef10fbd8f0d90dd7172da9e3c3fc1aa745a072c3"`); - // TODO(MW): add conversion when public inputs finalised - const blobCommitmentsFields = [ - new Fr(blobCommitments[0].compress().subarray(0, 31)), - new Fr(blobCommitments[0].compress().subarray(31, 48)), - ]; + const blobCommitmentsFields = blobCommitments[0].toBN254Fields(); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data. updateInlineTestData( diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator.ts b/yarn-project/prover-client/src/orchestrator/orchestrator.ts index 02fa1e330ee0..b00dd1237a39 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator.ts @@ -1,4 +1,4 @@ -import { FinalBlobBatchingChallenges } from '@aztec/blob-lib'; +import { BlobAccumulatorPublicInputs, FinalBlobBatchingChallenges } from '@aztec/blob-lib'; import { L1_TO_L2_MSG_SUBTREE_HEIGHT, L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, @@ -726,12 +726,14 @@ export class ProvingOrchestrator implements EpochProver { provingState.reject(`New archive root mismatch.`); } - const endBlobAccumulator = provingState.endBlobAccumulator!; + const endBlobAccumulatorPublicInputs = BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator( + provingState.endBlobAccumulator!, + ); const circuitEndBlobAccumulatorState = result.inputs.blobPublicInputs.endBlobAccumulator; - if (!circuitEndBlobAccumulatorState.equals(endBlobAccumulator.toBlobAccumulatorPublicInputs())) { + if (!circuitEndBlobAccumulatorState.equals(endBlobAccumulatorPublicInputs)) { logger.error( `Blob accumulator state mismatch.\nCircuit: ${inspect(circuitEndBlobAccumulatorState)}\nComputed: ${inspect( - endBlobAccumulator.toBlobAccumulatorPublicInputs(), + endBlobAccumulatorPublicInputs, )}`, ); provingState.reject(`Blob accumulator state mismatch.`); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_errors.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_errors.test.ts index 1eac774526fa..a665ad3233d5 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_errors.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_errors.test.ts @@ -29,7 +29,7 @@ describe('prover/orchestrator/errors', () => { it('throws if adding too many transactions', async () => { const txs = await timesParallel(4, i => context.makeProcessedTx(i + 1)); await context.setTreeRoots(txs); - const blobs = await Blob.getBlobs(txs.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(txs.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts index ad4058082758..68f51385523d 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts @@ -34,7 +34,9 @@ describe('prover/orchestrator/failures', () => { // We generate them and add them as part of the pending chain const blocks = await timesAsync(3, i => context.makePendingBlock(3, 1, i + 1, j => ({ privateOnly: j === 1 }))); - const blobs = (await Promise.all(blocks.map(block => Blob.getBlobs(block.block.body.toBlobFields())))).flat(); + const blobs = ( + await Promise.all(blocks.map(block => Blob.getBlobsPerBlock(block.block.body.toBlobFields()))) + ).flat(); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); orchestrator.startNewEpoch(1, 1, 3, finalBlobChallenges); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_mixed_blocks.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_mixed_blocks.test.ts index 6fddb512af8e..e3814093a61d 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_mixed_blocks.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_mixed_blocks.test.ts @@ -18,7 +18,7 @@ describe('prover/orchestrator/mixed-blocks', () => { const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr); - const blobs = await Blob.getBlobs(txs.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(txs.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_multi_public_functions.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_multi_public_functions.test.ts index b4d8dc5c8426..fd5f94ad7ead 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_multi_public_functions.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_multi_public_functions.test.ts @@ -91,7 +91,7 @@ describe('prover/orchestrator/public-functions', () => { expect(processed.length).toBe(numTransactions); expect(failed.length).toBe(0); - const blobs = await Blob.getBlobs(processed.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(processed.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); await context.orchestrator.startNewBlock(context.globalVariables, [], context.getPreviousBlockHeader()); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_multiple_blocks.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_multiple_blocks.test.ts index a04e208ab8b7..f97ceaa53a7b 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_multiple_blocks.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_multiple_blocks.test.ts @@ -30,7 +30,9 @@ describe('prover/orchestrator/multi-block', () => { logger.info(`Seeding world state with ${numBlocks} blocks`); const txCount = 2; const blocks = await timesAsync(numBlocks, i => context.makePendingBlock(txCount, 0, i + 1)); - const blobs = (await Promise.all(blocks.map(block => Blob.getBlobs(block.block.body.toBlobFields())))).flat(); + const blobs = ( + await Promise.all(blocks.map(block => Blob.getBlobsPerBlock(block.block.body.toBlobFields()))) + ).flat(); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); logger.info(`Starting new epoch with ${numBlocks}`); @@ -57,7 +59,9 @@ describe('prover/orchestrator/multi-block', () => { logger.info(`Seeding world state with ${numBlocks} blocks`); const txCount = 2; const blocks = await timesAsync(numBlocks, i => context.makePendingBlock(txCount, 0, i + 1)); - const blobs = (await Promise.all(blocks.map(block => Blob.getBlobs(block.block.body.toBlobFields())))).flat(); + const blobs = ( + await Promise.all(blocks.map(block => Blob.getBlobsPerBlock(block.block.body.toBlobFields()))) + ).flat(); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); logger.info(`Starting new epoch with ${numBlocks}`); @@ -95,7 +99,7 @@ describe('prover/orchestrator/multi-block', () => { logger.info(`Starting epoch ${epochIndex + 1} with ${numBlocks} blocks`); const blocksInEpoch = blocks.slice(epochIndex * numBlocks, (epochIndex + 1) * numBlocks); const blobs = ( - await Promise.all(blocksInEpoch.map(block => Blob.getBlobs(block.block.body.toBlobFields()))) + await Promise.all(blocksInEpoch.map(block => Blob.getBlobsPerBlock(block.block.body.toBlobFields()))) ).flat(); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch( diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_public_functions.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_public_functions.test.ts index b1397f01eef7..2feb4db372af 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_public_functions.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_public_functions.test.ts @@ -39,7 +39,7 @@ describe('prover/orchestrator/public-functions', () => { // Since this TX is mocked/garbage, it will revert because it calls a non-existent contract, // but it reverts in app logic so it can still be included. const [processed, _] = await context.processPublicFunctions([tx], 1); - const blobs = await Blob.getBlobs(processed.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(processed.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); // This will need to be a 2 tx block @@ -66,7 +66,7 @@ describe('prover/orchestrator/public-functions', () => { tx.data.constants.protocolContractTreeRoot = protocolContractTreeRoot; const [processed, _] = await context.processPublicFunctions([tx], 1); - const blobs = await Blob.getBlobs(processed.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(processed.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); await context.orchestrator.startNewBlock(context.globalVariables, [], context.getPreviousBlockHeader()); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_single_blocks.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_single_blocks.test.ts index c3e6c7422612..ad3feb214c06 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_single_blocks.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_single_blocks.test.ts @@ -35,7 +35,7 @@ describe('prover/orchestrator/blocks', () => { await context.setTreeRoots(txs); const blobFields = txs.map(tx => tx.txEffect.toBlobFields()).flat(); - const blobs = await Blob.getBlobs(blobFields); + const blobs = await Blob.getBlobsPerBlock(blobFields); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); // This will need to be a 2 tx block @@ -55,7 +55,7 @@ describe('prover/orchestrator/blocks', () => { const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr); const blobFields = txs.map(tx => tx.txEffect.toBlobFields()).flat(); - const blobs = await Blob.getBlobs(blobFields); + const blobs = await Blob.getBlobsPerBlock(blobFields); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_workflow.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_workflow.test.ts index 21dae423b4f6..af780df387e4 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_workflow.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_workflow.test.ts @@ -109,7 +109,7 @@ describe('prover/orchestrator', () => { it('waits for block to be completed before enqueueing block root proof', async () => { const txs = await Promise.all([context.makeProcessedTx(1), context.makeProcessedTx(2)]); - const blobs = await Blob.getBlobs(txs.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(txs.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); await orchestrator.startNewBlock(globalVariables, [], previousBlockHeader); @@ -129,7 +129,7 @@ describe('prover/orchestrator', () => { it('can start tube proofs before adding processed txs', async () => { const getTubeSpy = jest.spyOn(prover, 'getTubeProof'); const processedTxs = await Promise.all([context.makeProcessedTx(1), context.makeProcessedTx(2)]); - const blobs = await Blob.getBlobs(processedTxs.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(processedTxs.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); diff --git a/yarn-project/prover-client/src/test/bb_prover_full_rollup.test.ts b/yarn-project/prover-client/src/test/bb_prover_full_rollup.test.ts index c17fccce3135..d51156c8726a 100644 --- a/yarn-project/prover-client/src/test/bb_prover_full_rollup.test.ts +++ b/yarn-project/prover-client/src/test/bb_prover_full_rollup.test.ts @@ -65,7 +65,7 @@ describe('prover/bb_prover/full-rollup', () => { expect(processed.length).toBe(nonEmptyTxs); expect(failed.length).toBe(0); processedTxs[blockNum] = processed; - blobs.push(await Blob.getBlobs(processed.flatMap(tx => tx.txEffect.toBlobFields()))); + blobs.push(await Blob.getBlobsPerBlock(processed.flatMap(tx => tx.txEffect.toBlobFields()))); } const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs.flat()); @@ -134,7 +134,7 @@ describe('prover/bb_prover/full-rollup', () => { expect(processed.length).toBe(numTransactions); expect(failed.length).toBe(0); - const blobs = await Blob.getBlobs(processed.map(tx => tx.txEffect.toBlobFields()).flat()); + const blobs = await Blob.getBlobsPerBlock(processed.map(tx => tx.txEffect.toBlobFields()).flat()); const finalBlobChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(blobs); context.orchestrator.startNewEpoch(1, 1, 1, finalBlobChallenges); diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index 6a239084136b..3529dcc837fe 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -115,7 +115,7 @@ export class EpochProvingJob implements Traceable { try { const allBlobs = ( - await Promise.all(this.blocks.map(async block => await Blob.getBlobs(block.body.toBlobFields()))) + await Promise.all(this.blocks.map(async block => await Blob.getBlobsPerBlock(block.body.toBlobFields()))) ).flat(); const finalBlobBatchingChallenges = await BatchedBlob.precomputeBatchedBlobChallenges(allBlobs); diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts index 1e32952b3a30..b3cd055e918a 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.test.ts @@ -195,7 +195,7 @@ describe('SequencerPublisher', () => { it('bundles propose and vote tx to l1', async () => { const kzg = Blob.getViemKzgInstance(); - const expectedBlobs = await Blob.getBlobs(l2Block.body.toBlobFields()); + const expectedBlobs = await Blob.getBlobsPerBlock(l2Block.body.toBlobFields()); // Expect the blob sink server to receive the blobs await runBlobSinkServer(expectedBlobs); diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index 51f2583f617e..21d1d3531211 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -429,7 +429,7 @@ export class SequencerPublisher { const consensusPayload = ConsensusPayload.fromBlock(block); const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation); - const blobs = await Blob.getBlobs(block.body.toBlobFields()); + const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields()); const proposeTxArgs = { header: proposedBlockHeader, archive: block.archive.root.toBuffer(), diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index a5c8f5e32fde..7776614e7366 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -1,3 +1,4 @@ +import { BlobAccumulatorPublicInputs, FinalBlobAccumulatorPublicInputs } from '@aztec/blob-lib'; import { makeBatchedBlobAccumulator, makeBlockBlobPublicInputs, makeSpongeBlob } from '@aztec/blob-lib/testing'; import { ARCHIVE_HEIGHT, @@ -782,7 +783,7 @@ function makeBlockRootRollupData(seed = 0) { makeTuple(ARCHIVE_HEIGHT, fr, 0x2200), makeTuple(ARCHIVE_HEIGHT, fr, 0x2300), makeHeader(seed + 0x2400), - makeBatchedBlobAccumulator(seed + 0x2500).toBlobAccumulatorPublicInputs(), + BlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(makeBatchedBlobAccumulator(seed + 0x2500)), makeBatchedBlobAccumulator(seed + 0x2600).finalBlobChallenges, fr(seed + 0x2700), ); @@ -886,7 +887,7 @@ export function makeRootRollupPublicInputs(seed = 0): RootRollupPublicInputs { fr(seed + 0x702), fr(seed + 0x703), fr(seed + 0x704), - makeBatchedBlobAccumulator(seed).toFinalBlobAccumulatorPublicInputs(), + FinalBlobAccumulatorPublicInputs.fromBatchedBlobAccumulator(makeBatchedBlobAccumulator(seed)), ); }