Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
97e2a01
feat: blob batching methods - nr only
MirandaWood Apr 15, 2025
52801e6
chore: fmt, more tests, rearranging
MirandaWood Apr 16, 2025
35e8be2
feat: BLS12 field, curve methods, blob batching methods, ts only
MirandaWood Apr 16, 2025
db03339
chore: lint, cleanup
MirandaWood Apr 16, 2025
68be71e
chore: remove trusted setup file + test using it (size issues)
MirandaWood Apr 16, 2025
1fa5d49
Revert "chore: remove trusted setup file + test using it (size issues)"
MirandaWood Apr 17, 2025
33d62a7
chore: cleanup packages + increase playground size
MirandaWood Apr 17, 2025
46b8866
feat: address some comments, cleanup
MirandaWood Apr 17, 2025
346ca9a
chore: update some comments
MirandaWood Apr 21, 2025
f655bf5
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood Apr 21, 2025
8d57216
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood Apr 21, 2025
9490422
chore: renaming, cleanup
MirandaWood Apr 22, 2025
c300c77
chore: renaming, cleanup
MirandaWood Apr 22, 2025
63ffe96
chore: add issue nums (hopefully force ci cache reset)
MirandaWood Apr 22, 2025
606a942
feat: as isNegative to F, rename proof -> Q
MirandaWood Apr 22, 2025
75d6d35
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood Apr 22, 2025
40ffd8b
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood Apr 22, 2025
951d329
chore: bumped vite kb limit 1700 -> 1720
MirandaWood Apr 22, 2025
307cb09
chore: bumped vite kb limit 1700 -> 1750
MirandaWood Apr 22, 2025
5251cd1
feat: adding helpers, constants, docs, etc. for integration
MirandaWood Apr 24, 2025
de8ec1e
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood Apr 24, 2025
ca0da9d
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 7, 2025
d5f11d4
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 7, 2025
d3ac058
chore: rename v -> blob_commitments_hash, move noir ref further up stack
MirandaWood May 13, 2025
6750476
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 13, 2025
8e968ac
chore: rename v to blobCommitmentsHash
MirandaWood May 13, 2025
1fc2c0d
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 15, 2025
cda163a
chore: use updated methods from bignum, remove warnings
MirandaWood May 15, 2025
4806435
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 15, 2025
49c7be3
chore: switch bigcurve branch to remove visibility warnings
MirandaWood May 16, 2025
ee900fe
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 16, 2025
2216519
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 16, 2025
55bc974
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 20, 2025
df48994
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 20, 2025
dc1bd18
chore: fmt
MirandaWood May 20, 2025
cb146ae
fix: include is_inf in all serialization so recursion works
MirandaWood May 20, 2025
18db30a
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 20, 2025
3398526
chore: update import
MirandaWood May 20, 2025
e0f687a
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 20, 2025
5f02ae1
feat: add point compression unit test
MirandaWood May 20, 2025
480b8de
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 20, 2025
51899bd
chore: add fixture test for point compression, bring down new bls met…
MirandaWood May 21, 2025
4c5c437
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 22, 2025
1b7fbf0
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 22, 2025
8667c5c
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood May 28, 2025
ff52662
chore: bump bignum
MirandaWood May 28, 2025
0c91085
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 28, 2025
3064028
feat: address some comments
MirandaWood May 28, 2025
b358b3e
chore: test using toEqual in jest
MirandaWood May 29, 2025
fb8e45a
feat: init bigint and buffer, remove static compress
MirandaWood May 29, 2025
c75232b
feat: replace empty blob assumption
MirandaWood May 30, 2025
9e80ff2
feat: address some comments
MirandaWood May 30, 2025
2d1f35e
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood May 30, 2025
ea19acd
chore: add extra check before blob acc init
MirandaWood Jun 2, 2025
e89fd4a
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood Jun 2, 2025
92f6e5f
chore: renaming, bring down changes from integration branch, cleanup
MirandaWood Jun 2, 2025
88b4b28
Merge remote-tracking branch 'origin/mw/blob-batching-bls-utils' into…
MirandaWood Jun 2, 2025
0ee11fd
chore: cleanup, bring down changes from other PRs
MirandaWood Jun 2, 2025
f8ea4d2
Merge remote-tracking branch 'origin/mw/blob-batching' into mw/blob-b…
MirandaWood Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export default defineConfig(({ mode }) => {
bundlesize({
// Bump log:
// - AD: bumped from 1600 => 1680 as we now have a 20kb msgpack lib in bb.js and other logic got us 50kb higher, adding some wiggle room.
limits: [{ name: 'assets/index-*', limit: '1700kB' }],
// - MW: bumped from 1700 => 1750 after adding the noble curves pkg to foundation required for blob batching calculations.
limits: [{ name: 'assets/index-*', limit: '1750kB' }],
}),
],
define: {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/blob-lib/src/blob.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Blob as BlobBuffer, Bytes48, KZGProof } from 'c-kzg';
import { Blob } from './index.js';
import { makeEncodedBlob } from './testing.js';

// Importing directly from 'c-kzg' does not work, ignoring import/no-named-as-default-member err:
// Importing directly from 'c-kzg' does not work:

const {
BYTES_PER_BLOB,
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/blob-lib/src/blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { poseidon2Hash, sha256 } from '@aztec/foundation/crypto';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

// Importing directly from 'c-kzg' does not work, ignoring import/no-named-as-default-member err:
// Importing directly from 'c-kzg' does not work:
import cKzg from 'c-kzg';
import type { Blob as BlobBuffer } from 'c-kzg';

Expand Down Expand Up @@ -310,6 +310,8 @@ 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<Blob[]> {
const numBlobs = Math.max(Math.ceil(fields.length / FIELD_ELEMENTS_PER_BLOB), 1);
const multiBlobFieldsHash = await poseidon2Hash(fields);
Expand Down
186 changes: 186 additions & 0 deletions yarn-project/blob-lib/src/blob_batching.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
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 { BLS12Fr, BLS12Point, Fr } from '@aztec/foundation/fields';
import { fileURLToPath } from '@aztec/foundation/url';

import cKzg from 'c-kzg';
import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';

import { BatchedBlob, Blob } from './index.js';

// TODO(MW): Remove below file and test? Only required to ensure commiting and compression are correct.
const trustedSetup = JSON.parse(
readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), 'trusted_setup_bit_reversed.json')).toString(),
);

// Importing directly from 'c-kzg' does not work:
const { FIELD_ELEMENTS_PER_BLOB, computeKzgProof, loadTrustedSetup, verifyKzgProof } = cKzg;

try {
loadTrustedSetup();
} catch (error: any) {
if (error.message.includes('trusted setup is already loaded')) {
// NB: The c-kzg lib has no way of checking whether the setup is loaded or not,
// and it throws an error if it's already loaded, even though nothing is wrong.
// This is a rudimentary way of ensuring we load the trusted setup if we need it.
} else {
throw new Error(error);
}
}

describe('blob', () => {
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);

const point = BLS12Point.decompress(ourBlob.commitment);

// Double check we correctly decompress the commitment
const recompressed = point.compress();
expect(recompressed.equals(ourBlob.commitment)).toBeTruthy();

let commitment = BLS12Point.ZERO;
const setupG1Points: BLS12Point[] = trustedSetup['g1_lagrange_bit_reversed']
.slice(0, size)
.map((s: string) => BLS12Point.decompress(fromHex(s)));

setupG1Points.forEach((p, i) => {
commitment = commitment.add(p.mul(BLS12Fr.fromBN254Fr(blobItems[i])));
});

expect(commitment.equals(point)).toBeTruthy();
});

it('should construct and verify a batched blob of 400 items', async () => {
// 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);

// Challenge for the final opening (z)
const zis = blobs.map(b => b.challengeZ);
const finalZ = zis[0];

// 'Batched' commitment
const commitments = blobs.map(b => BLS12Point.decompress(b.commitment));

// 'Batched' evaluation
const proofObjects = blobs.map(b => computeKzgProof(b.data, finalZ.toBuffer()));
const evalYs = proofObjects.map(p => BLS12Fr.fromBuffer(Buffer.from(p[1])));
const qs = proofObjects.map(p => BLS12Point.decompress(Buffer.from(p[0])));

// Challenge gamma
const evalYsToBLSBignum = evalYs.map(y => y.toNoirBigNum());
const hashedEvals = await Promise.all(evalYsToBLSBignum.map(e => poseidon2Hash(e.limbs.map(Fr.fromHexString))));
const finalGamma = BLS12Fr.fromBN254Fr(await poseidon2Hash([hashedEvals[0], zis[0]]));

let batchedC = BLS12Point.ZERO;
let batchedQ = BLS12Point.ZERO;
let finalY = BLS12Fr.ZERO;
let powGamma = new BLS12Fr(1n); // Since we start at gamma^0 = 1
let finalBlobCommitmentsHash: Buffer = Buffer.alloc(0);
for (let i = 0; i < blobs.length; i++) {
const cOperand = commitments[i].mul(powGamma);
const yOperand = evalYs[i].mul(powGamma);
const qOperand = qs[i].mul(powGamma);
batchedC = batchedC.add(cOperand);
batchedQ = batchedQ.add(qOperand);
finalY = finalY.add(yOperand);
powGamma = powGamma.mul(finalGamma);
finalBlobCommitmentsHash = sha256ToField([finalBlobCommitmentsHash, blobs[i].commitment]).toBuffer();
}

expect(batchedC.equals(commitments[0])).toBeTruthy();
expect(finalY.equals(evalYs[0])).toBeTruthy();
expect(finalBlobCommitmentsHash.equals(sha256ToField([blobs[0].commitment]).toBuffer())).toBeTruthy();

const batchedBlob = await BatchedBlob.batch(blobs);

expect(batchedC.equals(batchedBlob.commitment)).toBeTruthy();
expect(batchedQ.equals(batchedBlob.q)).toBeTruthy();
expect(finalZ.equals(batchedBlob.z)).toBeTruthy();
expect(finalY.equals(batchedBlob.y)).toBeTruthy();
expect(finalBlobCommitmentsHash.equals(batchedBlob.blobCommitmentsHash.toBuffer())).toBeTruthy();

const isValid = verifyKzgProof(batchedC.compress(), finalZ.toBuffer(), finalY.toBuffer(), batchedQ.compress());
expect(isValid).toBe(true);
});

it('should construct and verify a batch of 3 full blobs', async () => {
// The values here are used to test Noir's blob evaluation in noir-projects/noir-protocol-circuits/crates/blob/src/blob_batching.nr -> test_full_blobs_batched
// Initialise enough fields to require 3 blobs
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());

// Challenge for the final opening (z)
const zis = blobs.map(b => b.challengeZ);
const finalZ = await poseidon2Hash([await poseidon2Hash([zis[0], zis[1]]), zis[2]]);

// Batched commitment
const commitments = blobs.map(b => BLS12Point.decompress(b.commitment));

// Batched evaluation
// NB: we share the same finalZ between blobs
const proofObjects = blobs.map(b => computeKzgProof(b.data, finalZ.toBuffer()));
const evalYs = proofObjects.map(p => BLS12Fr.fromBuffer(Buffer.from(p[1])));
const qs = proofObjects.map(p => BLS12Point.decompress(Buffer.from(p[0])));

// Challenge gamma
const evalYsToBLSBignum = evalYs.map(y => y.toNoirBigNum());
const hashedEvals = await Promise.all(evalYsToBLSBignum.map(e => poseidon2Hash(e.limbs.map(Fr.fromHexString))));
const finalGamma = BLS12Fr.fromBN254Fr(
await poseidon2Hash([
await poseidon2Hash([await poseidon2Hash([hashedEvals[0], hashedEvals[1]]), hashedEvals[2]]),
finalZ,
]),
);

let batchedC = BLS12Point.ZERO;
let batchedQ = BLS12Point.ZERO;
let finalY = BLS12Fr.ZERO;
let powGamma = new BLS12Fr(1n); // Since we start at gamma^0 = 1
let finalBlobCommitmentsHash: Buffer = Buffer.alloc(0);
for (let i = 0; i < 3; i++) {
const cOperand = commitments[i].mul(powGamma);
const yOperand = evalYs[i].mul(powGamma);
const qOperand = qs[i].mul(powGamma);
batchedC = batchedC.add(cOperand);
batchedQ = batchedQ.add(qOperand);
finalY = finalY.add(yOperand);
powGamma = powGamma.mul(finalGamma);
finalBlobCommitmentsHash = sha256ToField([finalBlobCommitmentsHash, blobs[i].commitment]).toBuffer();
}

const batchedBlob = await BatchedBlob.batch(blobs);

expect(batchedC.equals(batchedBlob.commitment)).toBeTruthy();
expect(batchedQ.equals(batchedBlob.q)).toBeTruthy();
expect(finalZ.equals(batchedBlob.z)).toBeTruthy();
expect(finalY.equals(batchedBlob.y)).toBeTruthy();
expect(finalBlobCommitmentsHash.equals(batchedBlob.blobCommitmentsHash.toBuffer())).toBeTruthy();

const isValid = verifyKzgProof(batchedC.compress(), finalZ.toBuffer(), finalY.toBuffer(), batchedQ.compress());
expect(isValid).toBe(true);
});

it.each([
3, 5, 10,
// 32 <- NB Full 32 blocks currently takes around 30s to fully batch
])('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)));

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))));
}
// BatchedBlob.batch() performs a verification check:
await BatchedBlob.batch(blobs);
});
});
Loading