Skip to content

Commit

Permalink
feat: body hash as buffer in TS + minor cleanup (#4012)
Browse files Browse the repository at this point in the history
When the block gets submitted on chain the body hash has to be
represented as normal buffer. Since we represent as 2 fields just
because of Noir I decided to do the conversion in `type_conversion.ts`.
  • Loading branch information
benesjan authored Jan 15, 2024
1 parent da419c1 commit e28a6bf
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 42 deletions.
5 changes: 3 additions & 2 deletions yarn-project/circuit-types/src/l2_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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[];
/**
Expand Down Expand Up @@ -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(
{
Expand Down
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/structs/global_variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class GlobalVariables {
}

static getFields(fields: FieldsOf<GlobalVariables>) {
// Note: The order here must match the order in the HeaderDecoder solidity library.
return [fields.chainId, fields.version, fields.blockNumber, fields.timestamp] as const;
}

Expand Down
12 changes: 12 additions & 0 deletions yarn-project/circuits.js/src/structs/header.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
15 changes: 10 additions & 5 deletions yarn-project/circuits.js/src/structs/header.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
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);
}

static fromBuffer(buffer: Buffer | BufferReader): 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),
);
Expand Down
17 changes: 0 additions & 17 deletions yarn-project/circuits.js/src/structs/rollup/root_rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions yarn-project/circuits.js/src/structs/state_reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 4 additions & 2 deletions yarn-project/circuits.js/src/tests/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)),
);
Expand Down
18 changes: 18 additions & 0 deletions yarn-project/foundation/src/serialize/free_funcs.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
19 changes: 19 additions & 0 deletions yarn-project/foundation/src/serialize/free_funcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
15 changes: 13 additions & 2 deletions yarn-project/noir-protocol-circuits/src/type_conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -678,6 +680,15 @@ export function mapTupleFromNoir<T, N extends number, M>(
return Array.from({ length }, (_, idx) => mapper(noirArray[idx])) as Tuple<M, N>;
}

/**
* 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<Field, 2>): 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.
Expand Down Expand Up @@ -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),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')} `,
);
}

Expand Down

0 comments on commit e28a6bf

Please sign in to comment.