miden-crypto/src/hash/rescue/rpx/tests.rs
src/hash/rescue/rpx/tests.rsRaw
1use alloc::{collections::BTreeSet, vec::Vec};
2
3use proptest::prelude::*;
4use rand_utils::rand_value;
5
6use super::{Felt, Hasher, Rpx256, StarkField, ZERO};
7use crate::{hash::rescue::RpxDigest, ONE};
8
9#[test]
10fn hash_elements_vs_merge() {
11 let elements = [Felt::new(rand_value()); 8];
12
13 let digests: [RpxDigest; 2] = [
14 RpxDigest::new(elements[..4].try_into().unwrap()),
15 RpxDigest::new(elements[4..].try_into().unwrap()),
16 ];
17
18 let m_result = Rpx256::merge(&digests);
19 let h_result = Rpx256::hash_elements(&elements);
20 assert_eq!(m_result, h_result);
21}
22
23#[test]
24fn merge_vs_merge_in_domain() {
25 let elements = [Felt::new(rand_value()); 8];
26
27 let digests: [RpxDigest; 2] = [
28 RpxDigest::new(elements[..4].try_into().unwrap()),
29 RpxDigest::new(elements[4..].try_into().unwrap()),
30 ];
31 let merge_result = Rpx256::merge(&digests);
32
33 // ----- merge with domain = 0 ----------------------------------------------------------------
34
35 // set domain to ZERO. This should not change the result.
36 let domain = ZERO;
37
38 let merge_in_domain_result = Rpx256::merge_in_domain(&digests, domain);
39 assert_eq!(merge_result, merge_in_domain_result);
40
41 // ----- merge with domain = 1 ----------------------------------------------------------------
42
43 // set domain to ONE. This should change the result.
44 let domain = ONE;
45
46 let merge_in_domain_result = Rpx256::merge_in_domain(&digests, domain);
47 assert_ne!(merge_result, merge_in_domain_result);
48}
49
50#[test]
51fn hash_elements_vs_merge_with_int() {
52 let tmp = [Felt::new(rand_value()); 4];
53 let seed = RpxDigest::new(tmp);
54
55 // ----- value fits into a field element ------------------------------------------------------
56 let val: Felt = Felt::new(rand_value());
57 let m_result = Rpx256::merge_with_int(seed, val.as_int());
58
59 let mut elements = seed.as_elements().to_vec();
60 elements.push(val);
61 let h_result = Rpx256::hash_elements(&elements);
62
63 assert_eq!(m_result, h_result);
64
65 // ----- value does not fit into a field element ----------------------------------------------
66 let val = Felt::MODULUS + 2;
67 let m_result = Rpx256::merge_with_int(seed, val);
68
69 let mut elements = seed.as_elements().to_vec();
70 elements.push(Felt::new(val));
71 elements.push(ONE);
72 let h_result = Rpx256::hash_elements(&elements);
73
74 assert_eq!(m_result, h_result);
75}
76
77#[test]
78fn hash_padding() {
79 // adding a zero bytes at the end of a byte string should result in a different hash
80 let r1 = Rpx256::hash(&[1_u8, 2, 3]);
81 let r2 = Rpx256::hash(&[1_u8, 2, 3, 0]);
82 assert_ne!(r1, r2);
83
84 // same as above but with bigger inputs
85 let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6]);
86 let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 0]);
87 assert_ne!(r1, r2);
88
89 // same as above but with input splitting over two elements
90 let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7]);
91 let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0]);
92 assert_ne!(r1, r2);
93
94 // same as above but with multiple zeros
95 let r1 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0]);
96 let r2 = Rpx256::hash(&[1_u8, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0]);
97 assert_ne!(r1, r2);
98}
99
100#[test]
101fn hash_elements_padding() {
102 let e1 = [Felt::new(rand_value()); 2];
103 let e2 = [e1[0], e1[1], ZERO];
104
105 let r1 = Rpx256::hash_elements(&e1);
106 let r2 = Rpx256::hash_elements(&e2);
107 assert_ne!(r1, r2);
108}
109
110#[test]
111fn hash_elements() {
112 let elements = [
113 ZERO,
114 ONE,
115 Felt::new(2),
116 Felt::new(3),
117 Felt::new(4),
118 Felt::new(5),
119 Felt::new(6),
120 Felt::new(7),
121 ];
122
123 let digests: [RpxDigest; 2] = [
124 RpxDigest::new(elements[..4].try_into().unwrap()),
125 RpxDigest::new(elements[4..8].try_into().unwrap()),
126 ];
127
128 let m_result = Rpx256::merge(&digests);
129 let h_result = Rpx256::hash_elements(&elements);
130 assert_eq!(m_result, h_result);
131}
132
133#[test]
134fn hash_empty() {
135 let elements: Vec<Felt> = vec![];
136
137 let zero_digest = RpxDigest::default();
138 let h_result = Rpx256::hash_elements(&elements);
139 assert_eq!(zero_digest, h_result);
140}
141
142#[test]
143fn hash_empty_bytes() {
144 let bytes: Vec<u8> = vec![];
145
146 let zero_digest = RpxDigest::default();
147 let h_result = Rpx256::hash(&bytes);
148 assert_eq!(zero_digest, h_result);
149}
150
151#[test]
152fn sponge_bytes_with_remainder_length_wont_panic() {
153 // this test targets to assert that no panic will happen with the edge case of having an inputs
154 // with length that is not divisible by the used binary chunk size. 113 is a non-negligible
155 // input length that is prime; hence guaranteed to not be divisible by any choice of chunk
156 // size.
157 //
158 // this is a preliminary test to the fuzzy-stress of proptest.
159 Rpx256::hash(&[0; 113]);
160}
161
162#[test]
163fn sponge_collision_for_wrapped_field_element() {
164 let a = Rpx256::hash(&[0; 8]);
165 let b = Rpx256::hash(&Felt::MODULUS.to_le_bytes());
166 assert_ne!(a, b);
167}
168
169#[test]
170fn sponge_zeroes_collision() {
171 let mut zeroes = Vec::with_capacity(255);
172 let mut set = BTreeSet::new();
173 (0..255).for_each(|_| {
174 let hash = Rpx256::hash(&zeroes);
175 zeroes.push(0);
176 // panic if a collision was found
177 assert!(set.insert(hash));
178 });
179}
180
181proptest! {
182 #[test]
183 fn rpo256_wont_panic_with_arbitrary_input(ref bytes in any::<Vec<u8>>()) {
184 Rpx256::hash(bytes);
185 }
186}