light_hasher/
hash_chain.rs

1use crate::{Hasher, HasherError, Poseidon};
2
3/// Creates a hash chain from an array of [u8;32] arrays.
4///
5/// # Parameters
6/// - `inputs`: An array of [u8;32] arrays to be hashed.
7///
8/// # Returns
9/// - `Result<[u8; 32], HasherError>`: The resulting hash chain or an error.
10pub fn create_hash_chain_from_array<const T: usize>(
11    inputs: [[u8; 32]; T],
12) -> Result<[u8; 32], HasherError> {
13    create_hash_chain_from_slice(&inputs)
14}
15
16/// Creates a hash chain from a slice of [u8;32] arrays.
17///
18/// # Parameters
19/// - `inputs`: A slice of [u8;32] array to be hashed.
20///
21/// # Returns
22/// - `Result<[u8; 32], HasherError>`: The resulting hash chain or an error.
23pub fn create_hash_chain_from_slice(inputs: &[[u8; 32]]) -> Result<[u8; 32], HasherError> {
24    if inputs.is_empty() {
25        return Ok([0u8; 32]);
26    }
27    let mut hash_chain = inputs[0];
28    for input in inputs.iter().skip(1) {
29        hash_chain = Poseidon::hashv(&[&hash_chain, input])?;
30    }
31    Ok(hash_chain)
32}
33
34/// Creates a two inputs hash chain from two slices of [u8;32] arrays.
35/// The two slices must have the same length.
36/// Hashes are hashed in pairs, with the first hash from
37/// the first slice and the second hash from the second slice.
38/// H(i) = H(H(i-1), hashes_first[i], hashes_second[i])
39///
40/// # Parameters
41/// - `hashes_first`: A slice of [u8;32] arrays to be hashed first.
42/// - `hashes_second`: A slice of [u8;32] arrays to be hashed second.
43///
44/// # Returns
45/// - `Result<[u8; 32], HasherError>`: The resulting hash chain or an error.
46pub fn create_two_inputs_hash_chain(
47    hashes_first: &[[u8; 32]],
48    hashes_second: &[[u8; 32]],
49) -> Result<[u8; 32], HasherError> {
50    let first_len = hashes_first.len();
51    if first_len != hashes_second.len() {
52        return Err(HasherError::InvalidInputLength(
53            first_len,
54            hashes_second.len(),
55        ));
56    }
57    if hashes_first.is_empty() {
58        return Ok([0u8; 32]);
59    }
60    let mut hash_chain = Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]])?;
61
62    if first_len == 1 {
63        return Ok(hash_chain);
64    }
65
66    for i in 1..first_len {
67        hash_chain = Poseidon::hashv(&[&hash_chain, &hashes_first[i], &hashes_second[i]])?;
68    }
69    Ok(hash_chain)
70}
71
72#[cfg(test)]
73mod hash_chain_tests {
74    use ark_ff::PrimeField;
75    use light_poseidon::PoseidonError;
76    use num_bigint::BigUint;
77
78    use super::*;
79    use crate::{bigint::bigint_to_be_bytes_array, Hasher, HasherError, Poseidon};
80
81    /// Tests for `create_hash_chain_from_slice` function:
82    /// Functional tests:
83    /// 1. Functional - with hardcoded values.
84    /// 2. Functional - with manual hash comparison.
85    /// 3. Functional - for determinism (hashing the same input twice).
86    /// 4. Functional - empty input case returns zero hash.
87    ///
88    /// Failing tests:
89    /// 5. Failing - input larger than modulus
90    #[test]
91    fn test_create_hash_chain_from_slice() {
92        // 1. Functional test with hardcoded values.
93        {
94            // Define hardcoded inputs.
95            let inputs: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]];
96
97            // Manually compute the expected hash chain using Poseidon.
98            // Note: The expected hash values should be precomputed using the same Poseidon parameters.
99            // For demonstration purposes, we'll assume hypothetical hash outputs.
100            // In a real scenario, replace these with actual expected values.
101            let intermediate_hash_1 = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap();
102            let expected_hash = Poseidon::hashv(&[&intermediate_hash_1, &inputs[2]]).unwrap();
103
104            // Call the function under test.
105            let result = create_hash_chain_from_slice(&inputs).unwrap();
106
107            // Assert that the result matches the expected hash.
108            assert_eq!(
109                result, expected_hash,
110                "Functional test with hardcoded values failed."
111            );
112        }
113
114        // 2. Functional test with manual hash comparison.
115        {
116            let inputs: [[u8; 32]; 2] = [[4u8; 32], [5u8; 32]];
117
118            // Manually compute the expected hash.
119            let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap();
120            let hard_coded_expected_hash = [
121                13, 250, 206, 124, 182, 159, 160, 87, 57, 23, 80, 155, 25, 43, 40, 136, 228, 255,
122                201, 1, 22, 168, 211, 220, 176, 187, 23, 176, 46, 198, 140, 211,
123            ];
124
125            let result = create_hash_chain_from_slice(&inputs).unwrap();
126
127            assert_eq!(
128                result, expected_hash,
129                "Functional test with manual hash comparison failed."
130            );
131            assert_eq!(result, hard_coded_expected_hash);
132        }
133
134        // 2. Functional test with manual hash comparison.
135        {
136            let inputs = [[4u8; 32], [5u8; 32], [6u8; 32]];
137
138            let expected_hash = Poseidon::hashv(&[&inputs[0], &inputs[1]]).unwrap();
139            let expected_hash = Poseidon::hashv(&[&expected_hash, &inputs[2]]).unwrap();
140            let hard_coded_expected_hash = [
141                12, 74, 32, 81, 132, 82, 10, 115, 75, 248, 169, 125, 228, 230, 140, 167, 149, 181,
142                244, 194, 63, 201, 26, 150, 142, 4, 60, 16, 77, 145, 194, 152,
143            ];
144
145            let result = create_hash_chain_from_slice(&inputs).unwrap();
146
147            assert_eq!(
148                result, expected_hash,
149                "Functional test with manual hash comparison failed."
150            );
151            assert_eq!(result, hard_coded_expected_hash);
152        }
153
154        // 3. Functional test for determinism (hashing the same input twice).
155        {
156            // Define inputs.
157            let inputs: [[u8; 32]; 2] = [[6u8; 32], [7u8; 32]];
158
159            // Compute hash chain the first time.
160            let first_hash = create_hash_chain_from_slice(&inputs).unwrap();
161
162            // Compute hash chain the second time.
163            let second_hash = create_hash_chain_from_slice(&inputs).unwrap();
164
165            // Assert that both hashes are identical.
166            assert_eq!(
167                first_hash, second_hash,
168                "Determinism test failed: Hashes do not match."
169            );
170        }
171
172        // 4. Test empty input case
173        {
174            let inputs: [[u8; 32]; 0] = [];
175            let result = create_hash_chain_from_slice(&inputs).unwrap();
176            assert_eq!(result, [0u8; 32], "Empty input should return zero hash");
177        }
178
179        // 5. Failing - input larger than modulus
180        {
181            let modulus: BigUint = ark_bn254::Fr::MODULUS.into();
182            let modulus_bytes: [u8; 32] = bigint_to_be_bytes_array(&modulus).unwrap();
183            let huge_input = vec![modulus_bytes, modulus_bytes];
184            let result = create_hash_chain_from_slice(&huge_input);
185            assert!(
186                matches!(result, Err(HasherError::Poseidon(error)) if error  == PoseidonError::InputLargerThanModulus),
187            );
188        }
189    }
190
191    /// Tests for `create_two_inputs_hash_chain` function:
192    /// 1. Functional - empty inputs.
193    /// 2. Functional - 1 input each.
194    /// 3. Functional - 2 inputs each.
195    /// 4. Failing - invalid input length for hashes_first.
196    /// 5. Failing - invalid input length for hashes_second.
197    #[test]
198    fn test_create_two_inputs_hash_chain() {
199        // 1. Functional test with empty inputs.
200        {
201            let hashes_first: &[[u8; 32]] = &[];
202            let hashes_second: &[[u8; 32]] = &[];
203            let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap();
204            assert_eq!(result, [0u8; 32], "Empty input should return zero hash");
205        }
206
207        // 2. Functional test with 1 input each.
208        {
209            let hashes_first: &[[u8; 32]] = &[[1u8; 32]];
210            let hashes_second: &[[u8; 32]] = &[[2u8; 32]];
211            let expected_hash = Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap();
212            let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap();
213            assert_eq!(result, expected_hash, "Single input each test failed");
214        }
215
216        // 3. Functional test with 2 inputs each.
217        {
218            let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]];
219            let hashes_second: &[[u8; 32]] = &[[3u8; 32], [4u8; 32]];
220            let intermediate_hash =
221                Poseidon::hashv(&[&hashes_first[0], &hashes_second[0]]).unwrap();
222            let expected_hash =
223                Poseidon::hashv(&[&intermediate_hash, &hashes_first[1], &hashes_second[1]])
224                    .unwrap();
225            let result = create_two_inputs_hash_chain(hashes_first, hashes_second).unwrap();
226            assert_eq!(result, expected_hash, "Two inputs each test failed");
227        }
228
229        // 4. Failing test with invalid input length for hashes_first.
230        {
231            let hashes_first: &[[u8; 32]] = &[[1u8; 32]];
232            let hashes_second: &[[u8; 32]] = &[[2u8; 32], [3u8; 32]];
233            let result = create_two_inputs_hash_chain(hashes_first, hashes_second);
234            assert!(
235                matches!(result, Err(HasherError::InvalidInputLength(1, 2))),
236                "Invalid input length for hashes_first test failed"
237            );
238        }
239
240        // 5. Failing test with invalid input length for hashes_second.
241        {
242            let hashes_first: &[[u8; 32]] = &[[1u8; 32], [2u8; 32]];
243            let hashes_second: &[[u8; 32]] = &[[3u8; 32]];
244            let result = create_two_inputs_hash_chain(hashes_first, hashes_second);
245            assert!(
246                matches!(result, Err(HasherError::InvalidInputLength(2, 1))),
247                "Invalid input length for hashes_second test failed"
248            );
249        }
250    }
251}