Skip to content

Commit

Permalink
feat: Use poseidon for fn selectors (#8239)
Browse files Browse the repository at this point in the history
Switches selectors to use poseidon instead of keccak. A new function to
handle hashing bytes with poseidon is added in noir, ts and the
aztec_macros.
  • Loading branch information
sirasistant authored Sep 15, 2024
1 parent 3377e70 commit 41891db
Show file tree
Hide file tree
Showing 24 changed files with 309 additions and 90 deletions.
18 changes: 9 additions & 9 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,17 @@ library Constants {
uint256 internal constant L2_GAS_PER_NOTE_HASH = 32;
uint256 internal constant L2_GAS_PER_NULLIFIER = 64;
uint256 internal constant CANONICAL_KEY_REGISTRY_ADDRESS =
9694109890306420370616891858093188542026876097103155811681068343994212062621;
6823425185167517386380694778823032861295161686691976789058601691508103815523;
uint256 internal constant CANONICAL_AUTH_REGISTRY_ADDRESS =
16522644890256297179255458951626875692461008240031142745359776058397274208468;
19361441716519463065948254497947932755739298943049449145365332870925554042208;
uint256 internal constant DEPLOYER_CONTRACT_ADDRESS =
19310994760783330368337163480198602393920956587162708699802190083077641908361;
17119407406465801909352274670277571579675739451008438338071219340964365249977;
uint256 internal constant REGISTERER_CONTRACT_ADDRESS =
2631409926445785927331173506476539962589925110142857699603561302478860342858;
12405643717676802437578418009978929188439257864607100766293128479227092050857;
uint256 internal constant FEE_JUICE_ADDRESS =
10248142274714515101077825679585135641434041564851038865006795089686437446849;
12096583827711775893711303288210371301779120762215263550909768879597884314839;
uint256 internal constant ROUTER_ADDRESS =
7268799613082469933251235702514160327341161584122631177360064643484764773587;
13369993014609648298719593339393818582619449364029983937306132176549332172208;
uint256 internal constant AZTEC_ADDRESS_LENGTH = 1;
uint256 internal constant GAS_FEES_LENGTH = 2;
uint256 internal constant GAS_LENGTH = 2;
Expand Down Expand Up @@ -193,16 +193,16 @@ library Constants {
uint256 internal constant PUBLIC_CONTEXT_INPUTS_LENGTH = 42;
uint256 internal constant AGGREGATION_OBJECT_LENGTH = 16;
uint256 internal constant SCOPED_READ_REQUEST_LEN = 3;
uint256 internal constant PUBLIC_DATA_READ_LENGTH = 2;
uint256 internal constant PUBLIC_DATA_READ_LENGTH = 3;
uint256 internal constant PRIVATE_VALIDATION_REQUESTS_LENGTH = 772;
uint256 internal constant PUBLIC_VALIDATION_REQUESTS_LENGTH = 770;
uint256 internal constant PUBLIC_VALIDATION_REQUESTS_LENGTH = 834;
uint256 internal constant PUBLIC_DATA_UPDATE_REQUEST_LENGTH = 3;
uint256 internal constant COMBINED_ACCUMULATED_DATA_LENGTH = 610;
uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH = 43;
uint256 internal constant PRIVATE_ACCUMULATED_DATA_LENGTH = 1336;
uint256 internal constant PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2167;
uint256 internal constant PUBLIC_ACCUMULATED_DATA_LENGTH = 1311;
uint256 internal constant PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 3885;
uint256 internal constant PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 3949;
uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 663;
uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 12;
uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 29;
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/authwit/src/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ use dep::aztec::{prelude::Deserialize, context::{PrivateContext, PublicContext,
*
*
* --- FAQ ---
* Q: Why are we using a success flag of `keccak256("IS_VALID()")` instead of just returning a boolean?
* Q: Why are we using a success flag of `poseidon2_hash_bytes("IS_VALID()")` instead of just returning a boolean?
* A: We want to make sure that we don't accidentally return `true` if there is a collision in the function selector.
* By returning a hash of `IS_VALID()`, it becomes very unlikely that there is both a collision and we return
* a success flag.
Expand All @@ -189,7 +189,7 @@ use dep::aztec::{prelude::Deserialize, context::{PrivateContext, PublicContext,
* chain to avoid a case where the same message could be used across multiple chains.
*/

global IS_VALID_SELECTOR = 0xabf64ad4; // 4 first bytes of keccak256("IS_VALID()")
global IS_VALID_SELECTOR = 0x47dacd73; // 4 last bytes of poseidon2_hash_bytes("IS_VALID()")

/**
* Assert that `on_behalf_of` have authorized the current call with a valid authentication witness
Expand Down
4 changes: 3 additions & 1 deletion noir-projects/aztec-nr/aztec/src/deploy.nr
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ pub fn deploy_contract(context: &mut PrivateContext, target: AztecAddress) {

let _call_result = context.call_private_function(
DEPLOYER_CONTRACT_ADDRESS,
FunctionSelector::from_field(0x7ebd3690),
comptime {
FunctionSelector::from_signature("deploy(Field,(Field),Field,(Field),bool)")
},
serialized_args
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ mod test {
// The following value was generated by `encrypted_event_log_incoming_body.test.ts`
// --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data.
let event_body_ciphertext_from_typescript = [
226, 240, 253, 6, 28, 52, 19, 131, 33, 132, 178, 212, 245, 62, 14, 190, 147, 228, 160, 190, 146, 61, 95, 203, 124, 153, 68, 168, 17, 150, 92, 0, 99, 214, 85, 64, 191, 78, 157, 131, 149, 96, 236, 253, 96, 172, 157, 30, 185, 29, 14, 152, 216, 130, 219, 151, 80, 185, 43, 223, 167, 8, 89, 189, 88, 188, 101, 137, 255, 136, 84, 252, 79, 18, 52, 3, 110, 54, 54, 206, 244, 209, 246, 226, 207, 247, 143, 253, 211, 75, 160, 224, 172, 41, 45, 7, 208, 137, 90, 56, 59, 4, 234, 48, 53, 23, 130, 230, 49, 249, 142, 243, 170, 72, 183, 242, 49, 124, 46, 52, 198, 75, 55, 102, 56, 89, 254, 67, 59, 157, 249, 120, 184, 67, 154, 16, 148, 227, 93, 37, 120, 199, 93, 166, 80, 127, 173, 52, 80, 135, 87, 1, 168, 164, 51, 48, 126, 120, 47, 102, 211, 227, 234, 170, 208, 99, 111, 198, 170, 226, 156, 244, 241, 174, 206, 30
226, 240, 253, 6, 28, 52, 19, 131, 33, 132, 178, 212, 245, 62, 14, 190, 147, 228, 160, 190, 146, 61, 95, 203, 124, 153, 68, 168, 17, 150, 92, 0, 99, 214, 85, 64, 191, 78, 157, 131, 149, 96, 236, 253, 96, 172, 157, 30, 201, 247, 40, 80, 60, 188, 158, 251, 242, 103, 197, 79, 165, 195, 10, 160, 255, 35, 167, 152, 25, 233, 77, 145, 214, 243, 210, 119, 0, 20, 29, 95, 15, 63, 33, 190, 184, 67, 254, 96, 128, 243, 220, 228, 201, 130, 86, 163, 52, 127, 111, 10, 212, 7, 160, 16, 87, 13, 39, 11, 5, 1, 164, 65, 8, 56, 82, 245, 13, 68, 176, 90, 100, 69, 243, 78, 117, 188, 221, 34, 178, 31, 155, 89, 143, 176, 129, 118, 36, 236, 64, 179, 52, 239, 184, 51, 51, 199, 221, 49, 81, 197, 17, 199, 192, 99, 49, 168, 157, 164, 190, 33, 240, 182, 214, 173, 7, 156, 102, 95, 65, 217, 225, 123, 42, 21, 124, 144
];

assert_eq(event_body_ciphertext_from_typescript.len(), ciphertext.len());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,12 @@ impl EventSelector {

pub fn from_signature<let N: u32>(signature: str<N>) -> Self {
let bytes = signature.as_bytes();
let hash = std::hash::keccak256(bytes, bytes.len() as u32);
let hash = crate::hash::poseidon2_hash_bytes(bytes);

let mut selector_be_bytes = [0; SELECTOR_SIZE];
for i in 0..SELECTOR_SIZE {
selector_be_bytes[i] = hash[i];
}
// We choose the last SELECTOR_SIZE bytes of the hash to avoid getting the first byte that is not full
let hash_bytes = hash.to_be_bytes::<SELECTOR_SIZE>();

EventSelector::from_field(field_from_bytes(selector_be_bytes, true))
EventSelector::from_field(field_from_bytes(hash_bytes, true))
}

pub fn zero() -> Self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,27 @@ impl FunctionSelector {

pub fn from_signature<let N: u32>(signature: str<N>) -> Self {
let bytes = signature.as_bytes();
let hash = std::hash::keccak256(bytes, bytes.len() as u32);
let hash = crate::hash::poseidon2_hash_bytes(bytes);

let mut selector_be_bytes = [0; SELECTOR_SIZE];
for i in 0..SELECTOR_SIZE {
selector_be_bytes[i] = hash[i];
}
// We choose the last SELECTOR_SIZE bytes of the hash to avoid getting the first byte that is not full
let hash_bytes = hash.to_be_bytes::<SELECTOR_SIZE>();

FunctionSelector::from_field(field_from_bytes(selector_be_bytes, true))
FunctionSelector::from_field(field_from_bytes(hash_bytes, true))
}

pub fn zero() -> Self {
Self { inner: 0 }
}
}

#[test]
fn test_is_valid_selector() {
let selector = FunctionSelector::from_signature("IS_VALID()");
assert_eq(selector.to_field(), 0x73cdda47);
}

#[test]
fn test_long_selector() {
let selector = FunctionSelector::from_signature("foo_and_bar_and_baz_and_foo_bar_baz_and_bar_foo");
assert_eq(selector.to_field(), 0x7590a997);
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,12 @@ global L2_GAS_PER_NOTE_HASH: u32 = 32;
global L2_GAS_PER_NULLIFIER: u32 = 64;

// CANONICAL CONTRACT ADDRESSES
global CANONICAL_KEY_REGISTRY_ADDRESS = AztecAddress::from_field(0x156eabf84e3ea50d40e3330224f2d2e81648fff8f1f7ec1bc6d2873cca6e959d);
global CANONICAL_AUTH_REGISTRY_ADDRESS = AztecAddress::from_field(0x24877c50868f86712240eb535d90d1c97403d074805dd3758c3aecb02958f8d4);
global DEPLOYER_CONTRACT_ADDRESS = AztecAddress::from_field(0x2ab1a2bd6d07d8d61ea56d85861446349e52c6b7c0612b702cb1e6db6ad0b089);
global REGISTERER_CONTRACT_ADDRESS = AztecAddress::from_field(0x05d15342d76e46e5be07d3cda0d753158431cdc5e39d29ce4e8fe1f5c070564a);
global FEE_JUICE_ADDRESS = AztecAddress::from_field(0x16a83e3395bc921a2441db55dce24f0e0932636901a2e676fa68b9b2b9a644c1);
global ROUTER_ADDRESS = AztecAddress::from_field(0x1011feaa54609098a884322267ec754c637b280c15aa79c3be9f1394e2b29cd3);
global CANONICAL_KEY_REGISTRY_ADDRESS = AztecAddress::from_field(0x0f15ebfaa7e1bb0d1c542c954a8f605909d0fa0a5fd829122bfdec15b4ada163);
global CANONICAL_AUTH_REGISTRY_ADDRESS = AztecAddress::from_field(0x2ace300b02ca5ab0a25052b1e852913a47292096997ca09f758c0e3624e84560);
global DEPLOYER_CONTRACT_ADDRESS = AztecAddress::from_field(0x25d93dc07b5baaf53a98caeae2679df3528cb83e11e2640a57a0a53abbaaadb9);
global REGISTERER_CONTRACT_ADDRESS = AztecAddress::from_field(0x1b6d5873cef5a35f681ab9468527f356c96e09b3c64603aef404ec2ad80aa3a9);
global FEE_JUICE_ADDRESS = AztecAddress::from_field(0x1abe6c7f5c4caf04cbf7556495e08ad9c0a225a5f9d33554ae07285b13c494d7);
global ROUTER_ADDRESS = AztecAddress::from_field(0x1d8f25db3e8faa6a96cb1ecf57876a2ee04581deb3c4f181488ccd817abcbdb0);

// LENGTH OF STRUCTS SERIALIZED TO FIELDS
global AZTEC_ADDRESS_LENGTH = 1;
Expand Down
31 changes: 31 additions & 0 deletions noir-projects/noir-protocol-circuits/crates/types/src/hash.nr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
recursion::verification_key::VerificationKey, traits::{is_empty, ToField},
utils::field::field_from_bytes_32_trunc
};
use super::utils::field::field_from_bytes;

pub fn sha256_to_field<let N: u32>(bytes_to_hash: [u8; N]) -> Field {
let sha256_hashed = std::hash::sha256(bytes_to_hash);
Expand Down Expand Up @@ -276,6 +277,36 @@ pub fn poseidon2_hash_with_separator<let N: u32, T>(
sponge.squeeze()
}

#[no_predicates]
pub fn poseidon2_hash_bytes<let N: u32>(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)
let mut in_len = N / 31;
let mut has_padding = false;
if N % 31 != 0 {
in_len += 1;
has_padding = true;
}

let two_pow_64 = 18446744073709551616;
let iv : Field = (in_len as Field) * two_pow_64;
let mut sponge = std::hash::poseidon2::Poseidon2::new(iv);

let mut current_field = [0; 31];
for i in 0..inputs.len() {
let index = i % 31;
current_field[index] = inputs[i];
if index == 30 {
sponge.absorb(field_from_bytes(current_field, false));
current_field = [0; 31];
}
}
if has_padding {
sponge.absorb(field_from_bytes(current_field, false));
}

sponge.squeeze()
}

#[test]
fn smoke_sha256_to_field() {
let full_buffer = [
Expand Down
19 changes: 3 additions & 16 deletions noir/noir-repo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ mod schnorr;
use ark_ec::AffineRepr;
pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul};
pub use generator::generators::derive_generators;
pub use poseidon2::{field_from_hex, poseidon2_permutation, Poseidon2Config, POSEIDON2_CONFIG};
pub use poseidon2::{
field_from_hex, poseidon2_permutation, poseidon_hash, Poseidon2Config, Poseidon2Sponge,
POSEIDON2_CONFIG,
};

// Temporary hack, this ensure that we always use a bn254 field here
// without polluting the feature flags of the `acir_field` crate.
Expand Down
84 changes: 84 additions & 0 deletions noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,75 @@ impl<'a> Poseidon2<'a> {
}
}

/// Performs a poseidon hash with a sponge construction equivalent to the one in poseidon2.nr
pub fn poseidon_hash(inputs: &[FieldElement]) -> Result<FieldElement, BlackBoxResolutionError> {
let two_pow_64 = 18446744073709551616_u128.into();
let iv = FieldElement::from(inputs.len()) * two_pow_64;
let mut sponge = Poseidon2Sponge::new(iv, 3);
for input in inputs.iter() {
sponge.absorb(*input)?;
}
sponge.squeeze()
}

pub struct Poseidon2Sponge<'a> {
rate: usize,
poseidon: Poseidon2<'a>,
squeezed: bool,
cache: Vec<FieldElement>,
state: Vec<FieldElement>,
}

impl<'a> Poseidon2Sponge<'a> {
pub fn new(iv: FieldElement, rate: usize) -> Poseidon2Sponge<'a> {
let mut result = Poseidon2Sponge {
cache: Vec::with_capacity(rate),
state: vec![FieldElement::zero(); rate + 1],
squeezed: false,
rate,
poseidon: Poseidon2::new(),
};
result.state[rate] = iv;
result
}

fn perform_duplex(&mut self) -> Result<(), BlackBoxResolutionError> {
// zero-pad the cache
for _ in self.cache.len()..self.rate {
self.cache.push(FieldElement::zero());
}
// add the cache into sponge state
for i in 0..self.rate {
self.state[i] += self.cache[i];
}
self.state = self.poseidon.permutation(&self.state, 4)?;
Ok(())
}

pub fn absorb(&mut self, input: FieldElement) -> Result<(), BlackBoxResolutionError> {
assert!(!self.squeezed);
if self.cache.len() == self.rate {
// If we're absorbing, and the cache is full, apply the sponge permutation to compress the cache
self.perform_duplex()?;
self.cache = vec![input];
} else {
// If we're absorbing, and the cache is not full, add the input into the cache
self.cache.push(input);
}
Ok(())
}

pub fn squeeze(&mut self) -> Result<FieldElement, BlackBoxResolutionError> {
assert!(!self.squeezed);
// If we're in absorb mode, apply sponge permutation to compress the cache.
self.perform_duplex()?;
self.squeezed = true;

// Pop one item off the top of the permutation and return it.
Ok(self.state[0])
}
}

#[cfg(test)]
mod test {
use acir::AcirField;
Expand All @@ -562,4 +631,19 @@ mod test {
];
assert_eq!(result, expected_result);
}

#[test]
fn hash_smoke_test() {
let fields = [
FieldElement::from(1u128),
FieldElement::from(2u128),
FieldElement::from(3u128),
FieldElement::from(4u128),
];
let result = super::poseidon_hash(&fields).expect("should hash successfully");
assert_eq!(
result,
field_from_hex("130bf204a32cac1f0ace56c78b731aa3809f06df2731ebcf6b3464a15788b1b9"),
);
}
}
4 changes: 3 additions & 1 deletion noir/noir-repo/aztec_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ acvm.workspace = true
noirc_frontend.workspace = true
noirc_errors.workspace = true
iter-extended.workspace = true
bn254_blackbox_solver.workspace = true
convert_case = "0.6.0"
im.workspace = true
regex = "1.10"
tiny-keccak = { version = "2.0.0", features = ["keccak"] }
itertools = "^0.10"
hex.workspace = true
Loading

0 comments on commit 41891db

Please sign in to comment.