Skip to content

Commit

Permalink
feat(test): Fuzz poseidon hases against an external library (#6273)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Related to #6141 

## Summary\*

Testing `std::hash::poseidon::bn254::hash_[1-12]` against
https://github.com/Lightprotocol/light-poseidon/tree/v0.2.0

The library doesn't support inputs wider than 12 (doesn't have the
parameters for it), so the Noir function `hash_[13-16]` are not covered.

## Additional Context



## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [ ] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
aakoshh authored Oct 10, 2024
1 parent a8bcae2 commit 8d8ea89
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 23 deletions.
28 changes: 20 additions & 8 deletions Cargo.lock

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

9 changes: 3 additions & 6 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ yanked = "warn"

ignore = [
"RUSTSEC-2020-0168", # mach unmaintained
"RUSTSEC-2020-0016" # net2 unmaintained
"RUSTSEC-2020-0016", # net2 unmaintained
]

# This section is considered when running `cargo deny check bans`.
Expand Down Expand Up @@ -58,7 +58,7 @@ allow = [
# bitmaps 2.1.0, im 15.1.0
"MPL-2.0",
# Boost Software License
"BSL-1.0"
"BSL-1.0",
]

# Allow 1 or more licenses on a per-crate basis, so that particular licenses
Expand Down Expand Up @@ -101,7 +101,4 @@ unknown-git = "deny"
#
# crates.io rejects git dependencies so anything depending on these is unpublishable and you'll ruin my day
# when I find out.
allow-git = [
"https://github.com/jfecher/chumsky",
"https://github.com/noir-lang/clap-markdown",
]
allow-git = ["https://github.com/noir-lang/clap-markdown"]
2 changes: 2 additions & 0 deletions tooling/nargo_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -85,6 +86,7 @@ sha2.workspace = true
sha3.workspace = true
iai = "0.1.1"
test-binary = "3.0.2"
light-poseidon = "0.2.0"


[[bench]]
Expand Down
53 changes: 44 additions & 9 deletions tooling/nargo_cli/tests/stdlib-props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,23 +259,17 @@ fn fuzz_sha512_equivalence() {
fn fuzz_poseidon2_equivalence() {
use bn254_blackbox_solver::poseidon_hash;

for max_len in [0, 1, 3, 4, 511, 512] {
// Test empty, small, then around the RATE value, then bigger inputs.
for max_len in [0, 1, 3, 4, 100] {
let source = format!(
"fn main(input: [Field; {max_len}], message_size: u32) -> pub Field {{
std::hash::poseidon2::Poseidon2::hash(input, message_size)
}}"
);

let strategy = (0..=max_len)
.prop_flat_map(|len: usize| {
// Generate Field elements from random 32 byte vectors.
let field = prop::collection::vec(any::<u8>(), 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.
Expand All @@ -296,8 +290,49 @@ fn fuzz_poseidon2_equivalence() {
}
}

#[test]
fn fuzz_poseidon_equivalence() {
use light_poseidon::{Poseidon, PoseidonHasher};

let poseidon_hash = |inputs: &[FieldElement]| {
let mut poseidon = Poseidon::<ark_bn254::Fr>::new_circom(inputs.len()).unwrap();
let frs: Vec<ark_bn254::Fr> = inputs.iter().map(|f| f.into_repr()).collect::<Vec<_>>();
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<Value = Vec<FieldElement>> {
// Generate Field elements from random 32 byte vectors.
let field = prop::collection::vec(any::<u8>(), 32)
.prop_map(|bytes| FieldElement::from_be_bytes_reduce(&bytes));

prop::collection::vec(field, len)
}

0 comments on commit 8d8ea89

Please sign in to comment.