From 0c5422446de40a5824daa5af33cad1ec015589a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Mon, 16 Sep 2024 20:18:31 +0200 Subject: [PATCH] feat: update args hash to be a flat poseidon (#8571) We used before a sort of tree structure for args hash to make an incremental hash. Poseidon is incremental in noir, so we can get rid of it and just use a flat poseidon hash. --- .../src/core/libraries/ConstantsGen.sol | 3 -- noir-projects/aztec-nr/aztec/src/hash.nr | 40 ++++++------------- .../crates/types/src/constants.nr | 3 -- .../crates/types/src/hash.nr | 14 +++++++ yarn-project/circuits.js/src/constants.gen.ts | 3 -- .../contract_address.test.ts.snap | 2 +- .../src/hash/__snapshots__/hash.test.ts.snap | 6 +-- .../circuits.js/src/hash/hash.test.ts | 4 +- yarn-project/circuits.js/src/hash/hash.ts | 21 +--------- 9 files changed, 34 insertions(+), 62 deletions(-) diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 0c4f2b65a07..1e1d3606c5c 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -95,9 +95,6 @@ library Constants { uint256 internal constant BLOCK_MERGE_ROLLUP_INDEX = 23; uint256 internal constant ROOT_ROLLUP_INDEX = 24; uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4; - uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 16; - uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16; - uint256 internal constant MAX_ARGS_LENGTH = 256; uint256 internal constant INITIALIZATION_SLOT_SEPARATOR = 1000000000; uint256 internal constant INITIAL_L2_BLOCK_NUM = 1; uint256 internal constant BLOB_SIZE_IN_BYTES = 126976; diff --git a/noir-projects/aztec-nr/aztec/src/hash.nr b/noir-projects/aztec-nr/aztec/src/hash.nr index 3a66e2ca643..1d5f750f2d5 100644 --- a/noir-projects/aztec-nr/aztec/src/hash.nr +++ b/noir-projects/aztec-nr/aztec/src/hash.nr @@ -1,10 +1,8 @@ use dep::protocol_types::{ address::{AztecAddress, EthAddress}, - constants::{ - GENERATOR_INDEX__SECRET_HASH, GENERATOR_INDEX__MESSAGE_NULLIFIER, ARGS_HASH_CHUNK_COUNT, - GENERATOR_INDEX__FUNCTION_ARGS, ARGS_HASH_CHUNK_LENGTH, MAX_ARGS_LENGTH -}, - point::Point, traits::Hash, hash::{sha256_to_field, poseidon2_hash_with_separator} + constants::{GENERATOR_INDEX__SECRET_HASH, GENERATOR_INDEX__MESSAGE_NULLIFIER, GENERATOR_INDEX__FUNCTION_ARGS}, + point::Point, traits::Hash, + hash::{sha256_to_field, poseidon2_hash_with_separator, poseidon2_hash_with_separator_slice} }; use crate::oracle::logs_traits::ToBytesForUnencryptedLog; @@ -106,44 +104,30 @@ impl ArgsHasher { } pub fn hash_args_array(args: [Field; N]) -> Field { - hash_args(args.as_slice()) + if args.len() == 0 { + 0 + } else { + poseidon2_hash_with_separator(args, GENERATOR_INDEX__FUNCTION_ARGS) + } } pub fn hash_args(args: [Field]) -> Field { if args.len() == 0 { 0 } else { - assert(args.len() <= MAX_ARGS_LENGTH, "Args length exceeds maximum"); - let mut chunks_hashes = [0; ARGS_HASH_CHUNK_COUNT]; - let mut current_chunk_values = [0; ARGS_HASH_CHUNK_LENGTH]; - - let mut current_chunk_index = 0; - let mut index_inside_current_chunk = 0; - for i in 0..args.len() { - current_chunk_values[index_inside_current_chunk] = args[i]; - index_inside_current_chunk+=1; - if index_inside_current_chunk == ARGS_HASH_CHUNK_LENGTH { - chunks_hashes[current_chunk_index] = poseidon2_hash_with_separator(current_chunk_values, GENERATOR_INDEX__FUNCTION_ARGS); - current_chunk_values = [0; ARGS_HASH_CHUNK_LENGTH]; - current_chunk_index+=1; - index_inside_current_chunk = 0; - } - } - if index_inside_current_chunk > 0 { - chunks_hashes[current_chunk_index] = poseidon2_hash_with_separator(current_chunk_values, GENERATOR_INDEX__FUNCTION_ARGS); - } - poseidon2_hash_with_separator(chunks_hashes, GENERATOR_INDEX__FUNCTION_ARGS) + poseidon2_hash_with_separator_slice(args, GENERATOR_INDEX__FUNCTION_ARGS) } } #[test] fn compute_var_args_hash() { let mut input = ArgsHasher::new(); - for i in 0..MAX_ARGS_LENGTH { + for i in 0..100 { input.add(i as Field); } let hash = input.hash(); - assert(hash == 0x1cce4dbf69f14c44865919991ee1057922e34d7310ba237d71759aa422621ca9); + dep::std::println(hash); + assert(hash == 0x19b0d74feb06ebde19edd85a28986c97063e84b3b351a8b666c7cac963ce655f); } #[test] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index ed33835c719..d84713c9e86 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -123,9 +123,6 @@ global ROOT_ROLLUP_INDEX: u32 = 24; // MISC CONSTANTS global FUNCTION_SELECTOR_NUM_BYTES: Field = 4; -global ARGS_HASH_CHUNK_LENGTH: u32 = 16; -global ARGS_HASH_CHUNK_COUNT: u32 = 16; -global MAX_ARGS_LENGTH: u32 = ARGS_HASH_CHUNK_COUNT * ARGS_HASH_CHUNK_LENGTH; // The following is used in immutable state variables to compute an initialization slot whose value is used to // determine whether a given variable has been initialized (by asserting that the value in the slot is 0). // The initialization slot is computed by adding the constant below to the variable's storage slot. This constant has diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index 2b8832111a2..ec4ca57fc12 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -277,6 +277,20 @@ pub fn poseidon2_hash_with_separator( sponge.squeeze() } +pub fn poseidon2_hash_with_separator_slice(inputs: [Field], separator: T) -> Field where T: ToField { + let in_len = inputs.len() + 1; + let two_pow_64 = 18446744073709551616; + let iv : Field = (in_len as Field) * two_pow_64; + let mut sponge = std::hash::poseidon2::Poseidon2::new(iv); + sponge.absorb(separator.to_field()); + + for i in 0..inputs.len() { + sponge.absorb(inputs[i]); + } + + sponge.squeeze() +} + #[no_predicates] pub fn poseidon2_hash_bytes(inputs: [u8; N]) -> Field { // We manually hash the inputs here, since we cannot express with the type system a constant size inputs array of Math.ceil(N/31) diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 2f5baddd9ec..7d751f670a1 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -81,9 +81,6 @@ export const BLOCK_ROOT_ROLLUP_INDEX = 22; export const BLOCK_MERGE_ROLLUP_INDEX = 23; export const ROOT_ROLLUP_INDEX = 24; export const FUNCTION_SELECTOR_NUM_BYTES = 4; -export const ARGS_HASH_CHUNK_LENGTH = 16; -export const ARGS_HASH_CHUNK_COUNT = 16; -export const MAX_ARGS_LENGTH = 256; export const INITIALIZATION_SLOT_SEPARATOR = 1000000000; export const INITIAL_L2_BLOCK_NUM = 1; export const BLOB_SIZE_IN_BYTES = 126976; diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap index a8a7d2f571b..17a995704b8 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap @@ -2,7 +2,7 @@ exports[`ContractAddress computeContractAddressFromInstance 1`] = `"0x0ea56faa48431d99cc2d073463d1b718c11174bb551a3d1d6f296b0096089dbb"`; -exports[`ContractAddress computeInitializationHash 1`] = `Fr<0x0a79e35b159f2755e79e7ac21aed31964a6446d279670b499595da24c66144ff>`; +exports[`ContractAddress computeInitializationHash 1`] = `Fr<0x153329c6098512a53cb449057bb5adade93e360575b71192b7b304ace35b2b6b>`; exports[`ContractAddress computePartialAddress 1`] = `Fr<0x2521255ebd14e8e3e7cd1e8a27d26a902ee9e74905b711950d580e826ba9010d>`; diff --git a/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap b/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap index 4f49be9f7aa..397754cb6ef 100644 --- a/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap +++ b/yarn-project/circuits.js/src/hash/__snapshots__/hash.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`hash Var args hash matches noir 1`] = `Fr<0x1cce4dbf69f14c44865919991ee1057922e34d7310ba237d71759aa422621ca9>`; +exports[`hash Var args hash matches noir 1`] = `Fr<0x19b0d74feb06ebde19edd85a28986c97063e84b3b351a8b666c7cac963ce655f>`; exports[`hash compute secret message hash 1`] = `Fr<0x211b950e538ee911389e441942d6646d13d725957a49d06500ae9bb673b0fbc2>`; @@ -18,6 +18,6 @@ exports[`hash computes unique note hash 1`] = `Fr<0x2f9b80d22188c5383a577596538f exports[`hash hashes empty function args 1`] = `Fr<0x0000000000000000000000000000000000000000000000000000000000000000>`; -exports[`hash hashes function args 1`] = `Fr<0x2cb19d7f1c57660c695e279c0163042f20dc89e4ae7bed9ada08b74790df3204>`; +exports[`hash hashes function args 1`] = `Fr<0x08fc05646caf2d63a8f3b424351783a521edaf28da0933d7c5b78a27e7497dac>`; -exports[`hash hashes many function args 1`] = `Fr<0x0f987cfa046685fd829f2cab0cc5e078941719d229df8292924178c77a6f886e>`; +exports[`hash hashes many function args 1`] = `Fr<0x03933d5a59efde5af91c47a93e6c4965f0506d160f76c701b2de1503d74c7775>`; diff --git a/yarn-project/circuits.js/src/hash/hash.test.ts b/yarn-project/circuits.js/src/hash/hash.test.ts index 6a9fb665d1e..fc1f0bc9018 100644 --- a/yarn-project/circuits.js/src/hash/hash.test.ts +++ b/yarn-project/circuits.js/src/hash/hash.test.ts @@ -1,7 +1,7 @@ import { times } from '@aztec/foundation/collection'; import { setupCustomSnapshotSerializers, updateInlineTestData } from '@aztec/foundation/testing'; -import { AztecAddress, EthAddress, Fr, L2ToL1Message, MAX_ARGS_LENGTH, ScopedL2ToL1Message } from '../index.js'; +import { AztecAddress, EthAddress, Fr, L2ToL1Message, ScopedL2ToL1Message } from '../index.js'; import { makeAztecAddress } from '../tests/factories.js'; import { computeNoteHashNonce, @@ -83,7 +83,7 @@ describe('hash', () => { }); it('Var args hash matches noir', () => { - const args = times(MAX_ARGS_LENGTH, i => new Fr(i)); + const args = times(100, i => new Fr(i)); const res = computeVarArgsHash(args); expect(res).toMatchSnapshot(); diff --git a/yarn-project/circuits.js/src/hash/hash.ts b/yarn-project/circuits.js/src/hash/hash.ts index 6226ee72183..6083676209d 100644 --- a/yarn-project/circuits.js/src/hash/hash.ts +++ b/yarn-project/circuits.js/src/hash/hash.ts @@ -1,12 +1,9 @@ import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { padArrayEnd } from '@aztec/foundation/collection'; import { pedersenHashBuffer, poseidon2HashWithSeparator, sha256Trunc } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { numToUInt8, numToUInt16BE, numToUInt32BE } from '@aztec/foundation/serialize'; -import chunk from 'lodash.chunk'; - -import { ARGS_HASH_CHUNK_COUNT, ARGS_HASH_CHUNK_LENGTH, GeneratorIndex, MAX_ARGS_LENGTH } from '../constants.gen.js'; +import { GeneratorIndex } from '../constants.gen.js'; import { type ScopedL2ToL1Message, VerificationKey } from '../structs/index.js'; /** @@ -103,22 +100,8 @@ export function computeVarArgsHash(args: Fr[]) { if (args.length === 0) { return Fr.ZERO; } - if (args.length > MAX_ARGS_LENGTH) { - throw new Error(`Hashing ${args.length} args exceeds max of ${MAX_ARGS_LENGTH}`); - } - - let chunksHashes = chunk(args, ARGS_HASH_CHUNK_LENGTH).map((c: Fr[]) => { - if (c.length < ARGS_HASH_CHUNK_LENGTH) { - c = padArrayEnd(c, Fr.ZERO, ARGS_HASH_CHUNK_LENGTH); - } - return poseidon2HashWithSeparator(c, GeneratorIndex.FUNCTION_ARGS); - }); - - if (chunksHashes.length < ARGS_HASH_CHUNK_COUNT) { - chunksHashes = padArrayEnd(chunksHashes, Fr.ZERO, ARGS_HASH_CHUNK_COUNT); - } - return poseidon2HashWithSeparator(chunksHashes, GeneratorIndex.FUNCTION_ARGS); + return poseidon2HashWithSeparator(args, GeneratorIndex.FUNCTION_ARGS); } /**