Skip to content

Commit

Permalink
feat(perf): speed up construction of bbjs Frs & cache zero hashes in …
Browse files Browse the repository at this point in the history
…ephemeral trees (redo) (#11894)

Redo of AztecProtocol/aztec-packages#11851

---------

Co-authored-by: dbanks12 <david@aztecprotocol.com>
Co-authored-by: David Banks <47112877+dbanks12@users.noreply.github.com>
  • Loading branch information
3 people authored and AztecBot committed Feb 12, 2025
1 parent d982306 commit 9a18a4e
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 30 deletions.
56 changes: 39 additions & 17 deletions ts/src/bigint-array/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
export function toBigIntBE(bytes: Uint8Array) {
// A Buffer in node, *is* a Uint8Array. We can't refuse it's type.
// However the algo below only works on an actual Uint8Array, hence we make a new one to be safe.
bytes = new Uint8Array(bytes);
let bigint = BigInt(0);
const view = new DataView(bytes.buffer);
for (let i = 0; i < bytes.byteLength; i++) {
bigint = (bigint << BigInt(8)) + BigInt(view.getUint8(i));
}
return bigint;
/**
* Convert a 32-byte BE Buffer to a BigInt.
*/
export function buffer32BytesToBigIntBE(buf: Buffer): bigint {
return (
(buf.readBigUInt64BE(0) << 192n) +
(buf.readBigUInt64BE(8) << 128n) +
(buf.readBigUInt64BE(16) << 64n) +
buf.readBigUInt64BE(24)
);
}

/**
* Convert a BE Uint8Array to a BigInt.
*/
export function uint8ArrayToBigIntBE(bytes: Uint8Array): bigint {
const buffer = Buffer.from(bytes);
return buffer32BytesToBigIntBE(buffer);
}

export function toBufferBE(value: bigint, byteLength = 32) {
const bytes = new Uint8Array(byteLength);
const view = new DataView(bytes.buffer);
for (let i = 0; i < byteLength; i++) {
view.setUint8(byteLength - i - 1, Number(value & BigInt(0xff)));
value >>= BigInt(8);
/**
* Convert a BigInt to a 32-byte BE Buffer.
*/
export function bigIntToBufferBE(value: bigint, byteLength = 32): Buffer {
if (byteLength != 32) {
throw new Error(
`Only 32 bytes supported for conversion from bigint to buffer, attempted byte length: ${byteLength}`,
);
}
return bytes;
const buf = Buffer.alloc(byteLength);
buf.writeBigUInt64BE(value >> 192n, 0);
buf.writeBigUInt64BE((value >> 128n) & 0xffffffffffffffffn, 8);
buf.writeBigUInt64BE((value >> 64n) & 0xffffffffffffffffn, 16);
buf.writeBigUInt64BE(value & 0xffffffffffffffffn, 24);
return buf;
}

/**
* Convert a BigInt to a 32-byte BE Uint8Array.
*/
export function bigIntToUint8ArrayBE(value: bigint, byteLength = 32): Uint8Array {
return new Uint8Array(bigIntToBufferBE(value, byteLength));
}
37 changes: 24 additions & 13 deletions ts/src/types/fields.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { randomBytes } from '../random/index.js';
import { toBigIntBE, toBufferBE } from '../bigint-array/index.js';
import {
buffer32BytesToBigIntBE,
uint8ArrayToBigIntBE,
bigIntToBufferBE,
bigIntToUint8ArrayBE,
} from '../bigint-array/index.js';
import { BufferReader, uint8ArrayToHexString } from '../serialize/index.js';

// TODO(#4189): Replace with implementation in yarn-project/foundation/src/fields/fields.ts
Expand All @@ -15,30 +20,36 @@ export class Fr {
static SIZE_IN_BYTES = 32;
value: Uint8Array;

constructor(value: Uint8Array | bigint) {
constructor(value: Uint8Array | Buffer | bigint) {
// We convert buffer value to bigint to be able to check it fits within modulus
const valueBigInt = typeof value === 'bigint' ? value : toBigIntBE(value);
const valueBigInt =
typeof value === 'bigint'
? value
: value instanceof Buffer
? buffer32BytesToBigIntBE(value)
: uint8ArrayToBigIntBE(value);

if (valueBigInt > Fr.MAX_VALUE) {
throw new Error(`Value 0x${valueBigInt.toString(16)} is greater or equal to field modulus.`);
}

this.value = typeof value === 'bigint' ? toBufferBE(value) : value;
this.value =
typeof value === 'bigint' ? bigIntToUint8ArrayBE(value) : value instanceof Buffer ? new Uint8Array(value) : value;
}

static random() {
const r = toBigIntBE(randomBytes(64)) % Fr.MODULUS;
const r = uint8ArrayToBigIntBE(randomBytes(64)) % Fr.MODULUS;
return new this(r);
}

static fromBuffer(buffer: Uint8Array | BufferReader) {
static fromBuffer(buffer: Uint8Array | Buffer | BufferReader) {
const reader = BufferReader.asReader(buffer);
return new this(reader.readBytes(this.SIZE_IN_BYTES));
}

static fromBufferReduce(buffer: Uint8Array | BufferReader) {
const reader = BufferReader.asReader(buffer);
return new this(toBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)) % Fr.MODULUS);
return new this(uint8ArrayToBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)) % Fr.MODULUS);
}

static fromString(str: string) {
Expand Down Expand Up @@ -79,26 +90,26 @@ export class Fq {
}

static random() {
const r = toBigIntBE(randomBytes(64)) % Fq.MODULUS;
const r = uint8ArrayToBigIntBE(randomBytes(64)) % Fq.MODULUS;
return new this(r);
}

static fromBuffer(buffer: Uint8Array | BufferReader) {
static fromBuffer(buffer: Uint8Array | Buffer | BufferReader) {
const reader = BufferReader.asReader(buffer);
return new this(toBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)));
return new this(uint8ArrayToBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)));
}

static fromBufferReduce(buffer: Uint8Array | BufferReader) {
static fromBufferReduce(buffer: Uint8Array | Buffer | BufferReader) {
const reader = BufferReader.asReader(buffer);
return new this(toBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)) % Fr.MODULUS);
return new this(uint8ArrayToBigIntBE(reader.readBytes(this.SIZE_IN_BYTES)) % Fr.MODULUS);
}

static fromString(str: string) {
return this.fromBuffer(Buffer.from(str.replace(/^0x/i, ''), 'hex'));
}

toBuffer() {
return toBufferBE(this.value, Fq.SIZE_IN_BYTES);
return bigIntToBufferBE(this.value, Fq.SIZE_IN_BYTES);
}

toString() {
Expand Down

0 comments on commit 9a18a4e

Please sign in to comment.