darkpool_crypto/
poseidon.rs1use ark_bn254::Fr;
2use ark_ff::{PrimeField, Zero};
3
4use crate::error::CryptoError;
5
6const RATE: usize = 3;
7const TWO_POW_64: u128 = 18_446_744_073_709_551_616;
8
9pub trait IPoseidonHasher: Send + Sync {
10 fn hash(&self, inputs: &[Fr]) -> Fr;
11 fn string_to_fr(&self, text: &str) -> Result<Fr, CryptoError>;
12}
13
14#[derive(Default)]
15pub struct NoxHasher;
16
17impl NoxHasher {
18 #[must_use]
19 pub fn new() -> Self {
20 Self
21 }
22
23 fn hash_internal(&self, inputs: &[Fr]) -> Fr {
25 let in_len = inputs.len();
26 let iv = Fr::from(in_len as u128 * TWO_POW_64);
27
28 let mut state: [Fr; 4] = [Fr::zero(); 4];
29 state[RATE] = iv;
30
31 let mut cache = [Fr::zero(); RATE];
32 let mut cache_size = 0;
33
34 for &input in inputs {
35 if cache_size == RATE {
36 for i in 0..RATE {
37 state[i] += cache[i];
38 }
39 state = taceo_poseidon2::bn254::t4::permutation(&state);
40
41 cache[0] = input;
42 cache_size = 1;
43 } else {
44 cache[cache_size] = input;
45 cache_size += 1;
46 }
47 }
48
49 for i in 0..RATE {
50 if i < cache_size {
51 state[i] += cache[i];
52 }
53 }
54 let final_state = taceo_poseidon2::bn254::t4::permutation(&state);
55 final_state[0]
56 }
57}
58
59impl IPoseidonHasher for NoxHasher {
60 fn hash(&self, inputs: &[Fr]) -> Fr {
61 self.hash_internal(inputs)
62 }
63
64 fn string_to_fr(&self, text: &str) -> Result<Fr, CryptoError> {
65 let bytes = text.as_bytes();
66 let len = bytes.len();
67 if len > 32 {
68 return Err(CryptoError::InputTooLong { max: 32, got: len });
69 }
70 let mut buffer = [0u8; 32];
71 buffer[32 - len..].copy_from_slice(bytes);
72 let field_element = Fr::from_be_bytes_mod_order(&buffer);
73 Ok(self.hash(&[field_element]))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use ark_ff::Zero;
81
82 #[test]
83 fn test_nox_hasher_direct_matches_wrapper() {
84 let hasher = NoxHasher::new();
85 let inputs = [
86 Fr::from(1u64),
87 Fr::from(2u64),
88 Fr::from(3u64),
89 Fr::from(42u64),
90 ];
91
92 let via_trait = hasher.hash(&inputs);
93 let via_internal = hasher.hash_internal(&inputs);
94
95 assert_eq!(
96 via_trait, via_internal,
97 "hash() and hash_internal() must produce identical results"
98 );
99 assert!(!via_trait.is_zero(), "Hash output must not be zero");
100 }
101
102 #[test]
103 fn test_nox_hasher_empty_input() {
104 let hasher = NoxHasher::new();
105 let via_trait = hasher.hash(&[]);
106 let via_internal = hasher.hash_internal(&[]);
107 assert_eq!(via_trait, via_internal);
108 }
109
110 #[test]
111 fn test_nox_hasher_deterministic() {
112 let hasher = NoxHasher::new();
113 let inputs = [Fr::from(7u64)];
114 let h1 = hasher.hash(&inputs);
115 let h2 = hasher.hash(&inputs);
116 assert_eq!(h1, h2);
117 }
118
119 #[test]
120 fn test_nox_hasher_different_inputs_differ() {
121 let hasher = NoxHasher::new();
122 let h1 = hasher.hash(&[Fr::from(1u64)]);
123 let h2 = hasher.hash(&[Fr::from(2u64)]);
124 assert_ne!(h1, h2);
125 }
126}