diff --git a/Cargo.lock b/Cargo.lock index 0c0d4afc57..b17503b0b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2363,6 +2363,17 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "git+https://github.com/Lightprotocol/light-poseidon.git?tag=v0.2.0#be8e0b58e8a6b932fc75252745db016fbff5a258" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2538,6 +2549,7 @@ name = "nargo_cli" version = "0.35.0" dependencies = [ "acvm", + "ark-bn254", "assert_cmd", "assert_fs", "async-lsp", @@ -2553,6 +2565,7 @@ dependencies = [ "fm", "iai", "iter-extended", + "light-poseidon", "nargo", "nargo_fmt", "nargo_toml", @@ -3006,11 +3019,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3027,19 +3039,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", diff --git a/Cargo.toml b/Cargo.toml index 0a28263128..777e8b9a0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ build-data = "0.1.3" bincode = "1.3.3" hex = "0.4.2" const_format = "0.2.30" -num-bigint = "0.4" +num-bigint = "0.4.4" num-traits = "0.2" similar-asserts = "1.5.0" tempfile = "3.6.0" diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index acd5623871..b4fc1f350b 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -71,6 +71,7 @@ tracing-appender = "0.2.3" tokio-util = { version = "0.7.8", features = ["compat"] } [dev-dependencies] +ark-bn254.workspace = true tempfile.workspace = true dirs.workspace = true assert_cmd = "2.0.8" @@ -85,6 +86,7 @@ sha2.workspace = true sha3.workspace = true iai = "0.1.1" test-binary = "3.0.2" +light-poseidon = { git = "https://github.com/Lightprotocol/light-poseidon.git", tag = "v0.2.0" } [[bench]] diff --git a/tooling/nargo_cli/tests/stdlib-props.rs b/tooling/nargo_cli/tests/stdlib-props.rs index 9347f59625..a6ed0cb053 100644 --- a/tooling/nargo_cli/tests/stdlib-props.rs +++ b/tooling/nargo_cli/tests/stdlib-props.rs @@ -259,7 +259,7 @@ fn fuzz_sha512_equivalence() { fn fuzz_poseidon2_equivalence() { use bn254_blackbox_solver::poseidon_hash; - for max_len in [0, 1, 3, 4, 511, 512] { + for max_len in [0, 1, 100] { let source = format!( "fn main(input: [Field; {max_len}], message_size: u32) -> pub Field {{ std::hash::poseidon2::Poseidon2::hash(input, message_size) @@ -267,15 +267,8 @@ fn fuzz_poseidon2_equivalence() { ); let strategy = (0..=max_len) - .prop_flat_map(|len: usize| { - // Generate Field elements from random 32 byte vectors. - let field = prop::collection::vec(any::(), 32) - .prop_map(|bytes| FieldElement::from_be_bytes_reduce(&bytes)); - - prop::collection::vec(field, len) - }) + .prop_flat_map(field_vec_strategy) .prop_map(move |mut msg| { - // The output hash is a single field element. let output = poseidon_hash(&msg, msg.len() < max_len).expect("failed to hash"); // The input has to be padded to the maximum length. @@ -296,8 +289,49 @@ fn fuzz_poseidon2_equivalence() { } } +#[test] +fn fuzz_poseidon_equivalence() { + use light_poseidon::{Poseidon, PoseidonHasher}; + + let poseidon_hash = |inputs: &[FieldElement]| { + let mut poseidon = Poseidon::::new_circom(inputs.len()).unwrap(); + let frs: Vec = inputs.iter().map(|f| f.into_repr()).collect::>(); + let hash = poseidon.hash(&frs).expect("failed to hash"); + FieldElement::from_repr(hash) + }; + + // Noir has hashes up to length 16, but the reference library won't work with more than 12. + for len in 1..light_poseidon::MAX_X5_LEN { + let source = format!( + "fn main(input: [Field; {len}]) -> pub Field {{ + std::hash::poseidon::bn254::hash_{len}(input) + }}" + ); + + let strategy = field_vec_strategy(len) + .prop_map(move |msg| { + let output = poseidon_hash(&msg); + let inputs = vec![("input", InputValue::Vec(vecmap(msg, InputValue::Field)))]; + + SnippetInputOutput::new(inputs, InputValue::Field(output)) + .with_description(format!("len = {len}")) + }) + .boxed(); + + run_snippet_proptest(source.clone(), false, strategy); + } +} + fn bytes_input(bytes: &[u8]) -> InputValue { InputValue::Vec( bytes.iter().map(|b| InputValue::Field(FieldElement::from(*b as u32))).collect(), ) } + +fn field_vec_strategy(len: usize) -> impl Strategy> { + // Generate Field elements from random 32 byte vectors. + let field = prop::collection::vec(any::(), 32) + .prop_map(|bytes| FieldElement::from_be_bytes_reduce(&bytes)); + + prop::collection::vec(field, len) +}