diff --git a/yarn-project/circuit-types/src/l2_block.ts b/yarn-project/circuit-types/src/l2_block.ts index f3d570d3705..2a8efb85a3d 100644 --- a/yarn-project/circuit-types/src/l2_block.ts +++ b/yarn-project/circuit-types/src/l2_block.ts @@ -8,6 +8,7 @@ import { MAX_NEW_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + NUM_BYTES_PER_SHA256, PartialStateReference, STRING_ENCODING, StateReference, @@ -223,7 +224,7 @@ export class L2Block { */ newContractData: ContractData[]; /** - * The L1 to L2 messages to be inserted into the L2 toL2 message tree. + * The L1 to L2 messages to be inserted into the L1 to L2 message tree. */ newL1ToL2Messages: Fr[]; /** @@ -357,7 +358,7 @@ export class L2Block { ); const state = new StateReference(endL1ToL2MessageTreeSnapshot, partial); // TODO(#3938): populate bodyHash - const header = new Header(startArchiveSnapshot, [Fr.ZERO, Fr.ZERO], state, globalVariables); + const header = new Header(startArchiveSnapshot, Buffer.alloc(NUM_BYTES_PER_SHA256), state, globalVariables); return L2Block.fromFields( { diff --git a/yarn-project/circuits.js/src/structs/global_variables.ts b/yarn-project/circuits.js/src/structs/global_variables.ts index 7fd59476772..2a569fb984c 100644 --- a/yarn-project/circuits.js/src/structs/global_variables.ts +++ b/yarn-project/circuits.js/src/structs/global_variables.ts @@ -53,6 +53,7 @@ export class GlobalVariables { } static getFields(fields: FieldsOf) { + // Note: The order here must match the order in the HeaderDecoder solidity library. return [fields.chainId, fields.version, fields.blockNumber, fields.timestamp] as const; } diff --git a/yarn-project/circuits.js/src/structs/header.test.ts b/yarn-project/circuits.js/src/structs/header.test.ts new file mode 100644 index 00000000000..4cc4e60d8b5 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/header.test.ts @@ -0,0 +1,12 @@ +import { makeHeader } from '../tests/factories.js'; +import { Header } from './header.js'; + +describe('Header', () => { + it(`serializes to buffer and deserializes it back`, () => { + const randomInt = Math.floor(Math.random() * 1000); + const expected = makeHeader(randomInt, undefined); + const buffer = expected.toBuffer(); + const res = Header.fromBuffer(buffer); + expect(res).toEqual(expected); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/header.ts b/yarn-project/circuits.js/src/structs/header.ts index 13e518a7306..12aebcf39af 100644 --- a/yarn-project/circuits.js/src/structs/header.ts +++ b/yarn-project/circuits.js/src/structs/header.ts @@ -1,25 +1,30 @@ -import { Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { NUM_FIELDS_PER_SHA256 } from '../constants.gen.js'; import { GlobalVariables } from './global_variables.js'; import { AppendOnlyTreeSnapshot } from './rollup/append_only_tree_snapshot.js'; import { StateReference } from './state_reference.js'; +export const NUM_BYTES_PER_SHA256 = 32; + /** A header of an L2 block. */ export class Header { constructor( /** Snapshot of archive before the block is applied. */ public lastArchive: AppendOnlyTreeSnapshot, /** Hash of the body of an L2 block. */ - public bodyHash: [Fr, Fr], + public bodyHash: Buffer, /** State reference. */ public state: StateReference, /** Global variables of an L2 block. */ public globalVariables: GlobalVariables, - ) {} + ) { + if (bodyHash.length !== 32) { + throw new Error('Body hash buffer must be 32 bytes'); + } + } toBuffer() { + // Note: The order here must match the order in the HeaderDecoder solidity library. return serializeToBuffer(this.lastArchive, this.bodyHash, this.state, this.globalVariables); } @@ -27,7 +32,7 @@ export class Header { const reader = BufferReader.asReader(buffer); return new Header( reader.readObject(AppendOnlyTreeSnapshot), - reader.readArray(NUM_FIELDS_PER_SHA256, Fr) as [Fr, Fr], + reader.readBytes(NUM_BYTES_PER_SHA256), reader.readObject(StateReference), reader.readObject(GlobalVariables), ); diff --git a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts index 5a4b34df659..2a839e6eda6 100644 --- a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts @@ -95,23 +95,6 @@ export class RootRollupPublicInputs { return new RootRollupPublicInputs(...RootRollupPublicInputs.getFields(fields)); } - /** - * Returns the sha256 hash of the calldata. - * @returns The sha256 hash of the calldata. - */ - public sha256CalldataHash(): Buffer { - const high = this.header.bodyHash[0].toBuffer(); - const low = this.header.bodyHash[1].toBuffer(); - - const hash = Buffer.alloc(32); - for (let i = 0; i < 16; i++) { - hash[i] = high[i + 16]; - hash[i + 16] = low[i + 16]; - } - - return hash; - } - /** * Deserializes a buffer into a `RootRollupPublicInputs` object. * @param buffer - The buffer to deserialize. diff --git a/yarn-project/circuits.js/src/structs/state_reference.ts b/yarn-project/circuits.js/src/structs/state_reference.ts index 88b649f601b..21c9359434f 100644 --- a/yarn-project/circuits.js/src/structs/state_reference.ts +++ b/yarn-project/circuits.js/src/structs/state_reference.ts @@ -15,6 +15,7 @@ export class StateReference { ) {} toBuffer() { + // Note: The order here must match the order in the HeaderDecoder solidity library. return serializeToBuffer(this.l1ToL2MessageTree, this.partial); } diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 93439eea2bf..6586d23ceb1 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -3,6 +3,8 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { numToUInt32BE } from '@aztec/foundation/serialize'; +import { randomBytes } from 'crypto'; + import { SchnorrSignature } from '../barretenberg/index.js'; import { ARCHIVE_HEIGHT, @@ -101,7 +103,7 @@ import { WitnessedPublicCallData, } from '../index.js'; import { GlobalVariables } from '../structs/global_variables.js'; -import { Header } from '../structs/header.js'; +import { Header, NUM_BYTES_PER_SHA256 } from '../structs/header.js'; /** * Creates an arbitrary side effect object with the given seed. @@ -882,7 +884,7 @@ export function makeRootRollupPublicInputs( export function makeHeader(seed = 0, globalVariables: GlobalVariables | undefined): Header { return new Header( makeAppendOnlyTreeSnapshot(seed + 0x100), - [new Fr(5n), new Fr(6n)], + randomBytes(NUM_BYTES_PER_SHA256), makeStateReference(seed + 0x200), globalVariables ?? makeGlobalVariables((seed += 0x100)), ); diff --git a/yarn-project/foundation/src/serialize/free_funcs.test.ts b/yarn-project/foundation/src/serialize/free_funcs.test.ts new file mode 100644 index 00000000000..e3be26dd2d8 --- /dev/null +++ b/yarn-project/foundation/src/serialize/free_funcs.test.ts @@ -0,0 +1,18 @@ +import { randomBytes } from '../crypto/index.js'; +import { from2Fields, to2Fields } from './free_funcs.js'; + +describe('buffer to fields and back', () => { + it('should correctly serialize and deserialize a buffer', () => { + // Generate a random 32-byte buffer + const originalBuffer = randomBytes(32); + + // Serialize the buffer to two fields + const [field1, field2] = to2Fields(originalBuffer); + + // Deserialize the fields back to a buffer + const reconstructedBuffer = from2Fields(field1, field2); + + // Check if the original buffer and reconstructed buffer are identical + expect(reconstructedBuffer).toEqual(originalBuffer); + }); +}); diff --git a/yarn-project/foundation/src/serialize/free_funcs.ts b/yarn-project/foundation/src/serialize/free_funcs.ts index 6165657f0f9..a9f9dcdfc1e 100644 --- a/yarn-project/foundation/src/serialize/free_funcs.ts +++ b/yarn-project/foundation/src/serialize/free_funcs.ts @@ -126,3 +126,22 @@ export function to2Fields(buf: Buffer): [Fr, Fr] { return [Fr.fromBuffer(buf1), Fr.fromBuffer(buf2)]; } + +/** + * Reconstructs the original 32 bytes of data from 2 field elements. + * @param field1 - First field element + * @param field2 - Second field element + * @returns 32 bytes of data as a Buffer + */ +export function from2Fields(field1: Fr, field2: Fr): Buffer { + // Convert the field elements back to buffers + const buf1 = field1.toBuffer(); + const buf2 = field2.toBuffer(); + + // Remove the padding (first 16 bytes) from each buffer + const originalPart1 = buf1.slice(16, 32); + const originalPart2 = buf2.slice(16, 32); + + // Concatenate the two parts to form the original buffer + return Buffer.concat([originalPart1, originalPart2]); +} diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/root.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/root.nr index 0fff41cc8fe..e44769c9719 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/root.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/root.nr @@ -65,11 +65,6 @@ impl RootRollupInputs { 0 ); - let zeroed_out_snapshot = AppendOnlyTreeSnapshot { - root : 0, - next_available_leaf_index : 0 - }; - let header = Header { last_archive: left.constants.last_archive, body_hash: components::compute_calldata_hash(self.previous_rollup_data), diff --git a/yarn-project/noir-protocol-circuits/src/type_conversion.ts b/yarn-project/noir-protocol-circuits/src/type_conversion.ts index 260d5946f8e..9b598111b4a 100644 --- a/yarn-project/noir-protocol-circuits/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits/src/type_conversion.ts @@ -72,7 +72,7 @@ import { TxContext, TxRequest, } from '@aztec/circuits.js'; -import { Tuple, mapTuple } from '@aztec/foundation/serialize'; +import { Tuple, from2Fields, mapTuple } from '@aztec/foundation/serialize'; import { BlockHeader as BlockHeaderNoir, @@ -137,6 +137,8 @@ import { AppendOnlyTreeSnapshot as AppendOnlyTreeSnapshotNoir, BaseOrMergeRollupPublicInputs as BaseOrMergeRollupPublicInputsNoir, ConstantRollupData as ConstantRollupDataNoir, + Field, + FixedLengthArray, GlobalVariables as GlobalVariablesNoir, Header as HeaderNoir, PartialStateReference as PartialStateReferenceNoir, @@ -678,6 +680,15 @@ export function mapTupleFromNoir( return Array.from({ length }, (_, idx) => mapper(noirArray[idx])) as Tuple; } +/** + * Maps a SHA256 hash from noir to the parsed type. + * @param hash - The hash as it is represented in Noir (2 fields). + * @returns The hash represented as a 32 bytes long buffer. + */ +export function mapSha256HashFromNoir(hash: FixedLengthArray): Buffer { + return from2Fields(mapFieldFromNoir(hash[0]), mapFieldFromNoir(hash[1])); +} + /** * Maps optionally revealed data from noir to the parsed type. * @param optionallyRevealedData - The noir optionally revealed data. @@ -1318,7 +1329,7 @@ export function mapRootRollupPublicInputsFromNoir( export function mapHeaderFromNoir(header: HeaderNoir): Header { return new Header( mapAppendOnlyTreeSnapshotFromNoir(header.last_archive), - mapTupleFromNoir(header.body_hash, 2, mapFieldFromNoir), + mapSha256HashFromNoir(header.body_hash), mapStateReferenceFromNoir(header.state), mapGlobalVariablesFromNoir(header.global_variables), ); diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts index 5a390b36fa6..a0e03bb5afc 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts @@ -250,11 +250,7 @@ describe('sequencer/solo_block_builder', () => { newUnencryptedLogs, }); - const callDataHash = l2Block.getCalldataHash(); - const high = Fr.fromBuffer(callDataHash.slice(0, 16)); - const low = Fr.fromBuffer(callDataHash.slice(16, 32)); - - rootRollupOutput.header.bodyHash = [high, low]; + rootRollupOutput.header.bodyHash = l2Block.getCalldataHash(); return txs; }; diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts index d8958a6d409..72a4fbf741e 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts @@ -144,11 +144,11 @@ export class SoloBlockBuilder implements BlockBuilder { newUnencryptedLogs, }); - if (!l2Block.getCalldataHash().equals(circuitsOutput.sha256CalldataHash())) { + if (!l2Block.getCalldataHash().equals(circuitsOutput.header.bodyHash)) { throw new Error( - `Calldata hash mismatch, ${l2Block.getCalldataHash().toString('hex')} == ${circuitsOutput - .sha256CalldataHash() - .toString('hex')} `, + `Calldata hash mismatch, ${l2Block + .getCalldataHash() + .toString('hex')} == ${circuitsOutput.header.bodyHash.toString('hex')} `, ); }