diff --git a/Nargo.toml b/Nargo.toml index 015057a..f737b03 100644 --- a/Nargo.toml +++ b/Nargo.toml @@ -4,3 +4,4 @@ type = "lib" authors = ["Oleh Misarosh "] [dependencies] +sha256 = { tag = "v0.1.0", git = "https://github.com/noir-lang/sha256" } diff --git a/README.md b/README.md index 9d661cb..14032ed 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Nodash is a utility library for [Noir](https://github.com/noir-lang/noir) langua Put this into your Nargo.toml. ```toml -nodash = { git = "https://github.com/olehmisar/nodash/", tag = "v0.39.4" } +nodash = { git = "https://github.com/olehmisar/nodash/", tag = "v0.39.5" } ``` ## Docs @@ -46,6 +46,53 @@ use nodash::div_ceil; assert(div_ceil(10 as u64, 3) == 4); ``` +### Hashes + +Hash functions can either accept a `[T; N]` or a `BoundedVec` (if technically possible). + +#### `poseidon2` + +```rs +use nodash::poseidon2; + +// hashes the whole array +let hash = poseidon2([10, 20]); +// hashes elements up to the length (in this case, 2) +let hash = poseidon2(BoundedVec::from_parts([10, 20, 0], 2)); +``` + +#### `pedersen` + +```rs +use nodash::pedersen; + +let hash = pedersen([10, 20]); +``` + +#### `sha256` + +sha256 is expensive to compute in Noir, so use [poseidon2](#poseidon2) where possible. + +```rs +use nodash::sha256; + +let hash = sha256([10, 20]); +// or +let hash = sha256(BoundedVec::from_parts([10, 20, 0], 2)); +``` + +#### `keccak256` + +keccak256 is expensive to compute in Noir, so use [poseidon2](#poseidon2) where possible. + +```rs +use nodash::keccak256; + +let hash = keccak256([10, 20]); +// or +let hash = keccak256(BoundedVec::from_parts([10, 20, 0], 2)); +``` + ### `solidity::encode_with_selector` Equivalent to `abi.encodeWithSelector` in Solidity. diff --git a/src/hash.nr b/src/hash.nr new file mode 100644 index 0000000..1224682 --- /dev/null +++ b/src/hash.nr @@ -0,0 +1,86 @@ +pub fn poseidon2(input: impl ArrayOrBoundedVec) -> Field { + let input = input.as_bounded_vec(); + std::hash::poseidon2::Poseidon2::hash(input.storage(), input.len()) +} + +// TODO: is it possible for pedersen to accept BoundedVec? +pub fn pedersen(input: [Field; N]) -> Field { + std::hash::pedersen_hash(input) +} + +pub fn sha256(input: impl ArrayOrBoundedVec) -> [u8; 32] { + let input = input.as_bounded_vec(); + dep::sha256::sha256_var(input.storage(), input.len() as u64) +} + +pub fn keccak256(input: impl ArrayOrBoundedVec) -> [u8; 32] { + let input = input.as_bounded_vec(); + std::hash::keccak256(input.storage(), input.len()) +} + +/// Needed because of https://github.com/noir-lang/noir/issues/7054 +trait ArrayOrBoundedVec { + fn as_bounded_vec(self) -> BoundedVec; +} + +impl ArrayOrBoundedVec for [T; N] { + fn as_bounded_vec(self) -> BoundedVec { + BoundedVec::from(self) + } +} + +impl ArrayOrBoundedVec for BoundedVec { + fn as_bounded_vec(self) -> BoundedVec { + self + } +} + +mod tests { + use crate::hash::{keccak256, pedersen, poseidon2, sha256}; + + global FIELD_INPUT_ARR: [Field; 2] = [1, 2]; + global FIELD_INPUT_VEC: BoundedVec = BoundedVec::from(FIELD_INPUT_ARR); + global FIELD_INPUT_VEC_LONGER: BoundedVec = BoundedVec::from(FIELD_INPUT_ARR); + + global U8_INPUT_ARR: [u8; 2] = [1, 2]; + global U8_INPUT_VEC: BoundedVec = BoundedVec::from(U8_INPUT_ARR); + global U8_INPUT_VEC_LONGER: BoundedVec = BoundedVec::from(U8_INPUT_ARR); + + #[test] + fn test_equivalence() { + assert( + (poseidon2(FIELD_INPUT_ARR) == poseidon2(FIELD_INPUT_VEC)), + // TODO: is this a bug? https://discord.com/channels/1113924620781883405/1333383938198212659 + // & (poseidon2(FIELD_INPUT_ARR) == poseidon2(FIELD_INPUT_VEC_LONGER)), + ); + assert( + (sha256(U8_INPUT_VEC) == sha256(U8_INPUT_ARR)) + & (sha256(U8_INPUT_VEC_LONGER) == sha256(U8_INPUT_ARR)), + ); + assert( + (keccak256(U8_INPUT_VEC) == keccak256(U8_INPUT_ARR)) + & (keccak256(U8_INPUT_VEC_LONGER) == keccak256(U8_INPUT_ARR)), + ); + } + + #[test] + fn test_against_std() { + assert( + poseidon2(FIELD_INPUT_ARR) + == std::hash::poseidon2::Poseidon2::hash(FIELD_INPUT_ARR, FIELD_INPUT_ARR.len()), + ); + assert( + poseidon2(FIELD_INPUT_VEC_LONGER) + == std::hash::poseidon2::Poseidon2::hash( + FIELD_INPUT_VEC_LONGER.storage(), + FIELD_INPUT_VEC_LONGER.len(), + ), + ); + assert(pedersen(FIELD_INPUT_ARR) == std::hash::pedersen_hash(FIELD_INPUT_ARR)); + assert( + sha256(U8_INPUT_ARR) + == dep::sha256::sha256_var(U8_INPUT_ARR, U8_INPUT_ARR.len() as u64), + ); + assert(keccak256(U8_INPUT_ARR) == std::hash::keccak256(U8_INPUT_ARR, U8_INPUT_ARR.len())); + } +} diff --git a/src/lib.nr b/src/lib.nr index cd7b504..3f569cd 100644 --- a/src/lib.nr +++ b/src/lib.nr @@ -1,3 +1,4 @@ +mod hash; mod math; mod solidity; mod string; @@ -5,6 +6,7 @@ mod tables; mod array; pub use array::pack_bytes; +pub use hash::{keccak256, pedersen, poseidon2, sha256}; pub use math::{clamp, div_ceil, sqrt::sqrt}; pub use string::{field_to_hex, ord, str_to_u64, to_hex_string_bytes};