miden-crypto/src/hash/rpo/digest.rs
src/hash/rpo/digest.rsRaw
1use super::{Digest, Felt, StarkField, DIGEST_SIZE, ZERO};
2use crate::utils::{
3 bytes_to_hex_string, hex_to_bytes, string::String, ByteReader, ByteWriter, Deserializable,
4 DeserializationError, HexParseError, Serializable,
5};
6use core::{cmp::Ordering, fmt::Display, ops::Deref};
7
8/// The number of bytes needed to encoded a digest
9pub const DIGEST_BYTES: usize = 32;
10
11// DIGEST TRAIT IMPLEMENTATIONS
12// ================================================================================================
13
14#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16#[cfg_attr(feature = "serde", serde(into = "String", try_from = "&str"))]
17pub struct RpoDigest([Felt; DIGEST_SIZE]);
18
19impl RpoDigest {
20 pub const fn new(value: [Felt; DIGEST_SIZE]) -> Self {
21 Self(value)
22 }
23
24 pub fn as_elements(&self) -> &[Felt] {
25 self.as_ref()
26 }
27
28 pub fn as_bytes(&self) -> [u8; DIGEST_BYTES] {
29 <Self as Digest>::as_bytes(self)
30 }
31
32 pub fn digests_as_elements<'a, I>(digests: I) -> impl Iterator<Item = &'a Felt>
33 where
34 I: Iterator<Item = &'a Self>,
35 {
36 digests.flat_map(|d| d.0.iter())
37 }
38}
39
40impl Digest for RpoDigest {
41 fn as_bytes(&self) -> [u8; DIGEST_BYTES] {
42 let mut result = [0; DIGEST_BYTES];
43
44 result[..8].copy_from_slice(&self.0[0].as_int().to_le_bytes());
45 result[8..16].copy_from_slice(&self.0[1].as_int().to_le_bytes());
46 result[16..24].copy_from_slice(&self.0[2].as_int().to_le_bytes());
47 result[24..].copy_from_slice(&self.0[3].as_int().to_le_bytes());
48
49 result
50 }
51}
52
53impl Serializable for RpoDigest {
54 fn write_into<W: ByteWriter>(&self, target: &mut W) {
55 target.write_bytes(&self.as_bytes());
56 }
57}
58
59impl Deserializable for RpoDigest {
60 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
61 let mut inner: [Felt; DIGEST_SIZE] = [ZERO; DIGEST_SIZE];
62 for inner in inner.iter_mut() {
63 let e = source.read_u64()?;
64 if e >= Felt::MODULUS {
65 return Err(DeserializationError::InvalidValue(String::from(
66 "Value not in the appropriate range",
67 )));
68 }
69 *inner = Felt::new(e);
70 }
71
72 Ok(Self(inner))
73 }
74}
75
76impl From<[Felt; DIGEST_SIZE]> for RpoDigest {
77 fn from(value: [Felt; DIGEST_SIZE]) -> Self {
78 Self(value)
79 }
80}
81
82impl From<&RpoDigest> for [Felt; DIGEST_SIZE] {
83 fn from(value: &RpoDigest) -> Self {
84 value.0
85 }
86}
87
88impl From<RpoDigest> for [Felt; DIGEST_SIZE] {
89 fn from(value: RpoDigest) -> Self {
90 value.0
91 }
92}
93
94impl From<&RpoDigest> for [u64; DIGEST_SIZE] {
95 fn from(value: &RpoDigest) -> Self {
96 [
97 value.0[0].as_int(),
98 value.0[1].as_int(),
99 value.0[2].as_int(),
100 value.0[3].as_int(),
101 ]
102 }
103}
104
105impl From<RpoDigest> for [u64; DIGEST_SIZE] {
106 fn from(value: RpoDigest) -> Self {
107 [
108 value.0[0].as_int(),
109 value.0[1].as_int(),
110 value.0[2].as_int(),
111 value.0[3].as_int(),
112 ]
113 }
114}
115
116impl From<&RpoDigest> for [u8; DIGEST_BYTES] {
117 fn from(value: &RpoDigest) -> Self {
118 value.as_bytes()
119 }
120}
121
122impl From<RpoDigest> for [u8; DIGEST_BYTES] {
123 fn from(value: RpoDigest) -> Self {
124 value.as_bytes()
125 }
126}
127
128impl From<RpoDigest> for String {
129 fn from(value: RpoDigest) -> Self {
130 bytes_to_hex_string(value.as_bytes())
131 }
132}
133
134impl From<&RpoDigest> for String {
135 fn from(value: &RpoDigest) -> Self {
136 (*value).into()
137 }
138}
139
140impl TryFrom<[u8; DIGEST_BYTES]> for RpoDigest {
141 type Error = HexParseError;
142
143 fn try_from(value: [u8; DIGEST_BYTES]) -> Result<Self, Self::Error> {
144 // Note: the input length is known, the conversion from slice to array must succeed so the
145 // `unwrap`s below are safe
146 let a = u64::from_le_bytes(value[0..8].try_into().unwrap());
147 let b = u64::from_le_bytes(value[8..16].try_into().unwrap());
148 let c = u64::from_le_bytes(value[16..24].try_into().unwrap());
149 let d = u64::from_le_bytes(value[24..32].try_into().unwrap());
150
151 if [a, b, c, d].iter().any(|v| *v >= Felt::MODULUS) {
152 return Err(HexParseError::OutOfRange);
153 }
154
155 Ok(RpoDigest([Felt::new(a), Felt::new(b), Felt::new(c), Felt::new(d)]))
156 }
157}
158
159impl TryFrom<&str> for RpoDigest {
160 type Error = HexParseError;
161
162 fn try_from(value: &str) -> Result<Self, Self::Error> {
163 hex_to_bytes(value).and_then(|v| v.try_into())
164 }
165}
166
167impl TryFrom<String> for RpoDigest {
168 type Error = HexParseError;
169
170 fn try_from(value: String) -> Result<Self, Self::Error> {
171 value.as_str().try_into()
172 }
173}
174
175impl TryFrom<&String> for RpoDigest {
176 type Error = HexParseError;
177
178 fn try_from(value: &String) -> Result<Self, Self::Error> {
179 value.as_str().try_into()
180 }
181}
182
183impl Deref for RpoDigest {
184 type Target = [Felt; DIGEST_SIZE];
185
186 fn deref(&self) -> &Self::Target {
187 &self.0
188 }
189}
190
191impl Ord for RpoDigest {
192 fn cmp(&self, other: &Self) -> Ordering {
193 // compare the inner u64 of both elements.
194 //
195 // it will iterate the elements and will return the first computation different than
196 // `Equal`. Otherwise, the ordering is equal.
197 //
198 // the endianness is irrelevant here because since, this being a cryptographically secure
199 // hash computation, the digest shouldn't have any ordered property of its input.
200 //
201 // finally, we use `Felt::inner` instead of `Felt::as_int` so we avoid performing a
202 // montgomery reduction for every limb. that is safe because every inner element of the
203 // digest is guaranteed to be in its canonical form (that is, `x in [0,p)`).
204 self.0.iter().map(Felt::inner).zip(other.0.iter().map(Felt::inner)).fold(
205 Ordering::Equal,
206 |ord, (a, b)| match ord {
207 Ordering::Equal => a.cmp(&b),
208 _ => ord,
209 },
210 )
211 }
212}
213
214impl PartialOrd for RpoDigest {
215 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
216 Some(self.cmp(other))
217 }
218}
219
220impl Display for RpoDigest {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 let encoded: String = self.into();
223 write!(f, "{}", encoded)?;
224 Ok(())
225 }
226}
227
228// TESTS
229// ================================================================================================
230
231#[cfg(test)]
232mod tests {
233 use super::{Deserializable, Felt, RpoDigest, Serializable, DIGEST_BYTES};
234 use crate::utils::SliceReader;
235 use rand_utils::rand_value;
236
237 #[test]
238 fn digest_serialization() {
239 let e1 = Felt::new(rand_value());
240 let e2 = Felt::new(rand_value());
241 let e3 = Felt::new(rand_value());
242 let e4 = Felt::new(rand_value());
243
244 let d1 = RpoDigest([e1, e2, e3, e4]);
245
246 let mut bytes = vec![];
247 d1.write_into(&mut bytes);
248 assert_eq!(DIGEST_BYTES, bytes.len());
249
250 let mut reader = SliceReader::new(&bytes);
251 let d2 = RpoDigest::read_from(&mut reader).unwrap();
252
253 assert_eq!(d1, d2);
254 }
255
256 #[cfg(feature = "std")]
257 #[test]
258 fn digest_encoding() {
259 let digest = RpoDigest([
260 Felt::new(rand_value()),
261 Felt::new(rand_value()),
262 Felt::new(rand_value()),
263 Felt::new(rand_value()),
264 ]);
265
266 let string: String = digest.into();
267 let round_trip: RpoDigest = string.try_into().expect("decoding failed");
268
269 assert_eq!(digest, round_trip);
270 }
271}