Skip to main content

darkpool_crypto/
poseidon.rs

1use 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    /// Noir/Aztec Poseidon2 sponge (matches noir-lang/poseidon/src/poseidon2.nr).
24    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}