miden_node_proto/domain/
digest.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use hex::{FromHex, ToHex};
4use miden_objects::note::NoteId;
5use miden_objects::{Felt, StarkField, Word};
6
7use crate::errors::ConversionError;
8use crate::generated as proto;
9
10// CONSTANTS
11// ================================================================================================
12
13pub const DIGEST_DATA_SIZE: usize = 32;
14
15// FORMATTING
16// ================================================================================================
17
18impl Display for proto::primitives::Digest {
19    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20        f.write_str(&self.encode_hex::<String>())
21    }
22}
23
24impl Debug for proto::primitives::Digest {
25    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26        Display::fmt(self, f)
27    }
28}
29
30impl ToHex for &proto::primitives::Digest {
31    fn encode_hex<T: FromIterator<char>>(&self) -> T {
32        (*self).encode_hex()
33    }
34
35    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
36        (*self).encode_hex_upper()
37    }
38}
39
40impl ToHex for proto::primitives::Digest {
41    fn encode_hex<T: FromIterator<char>>(&self) -> T {
42        let mut data: Vec<char> = Vec::with_capacity(DIGEST_DATA_SIZE);
43        data.extend(format!("{:016x}", self.d0).chars());
44        data.extend(format!("{:016x}", self.d1).chars());
45        data.extend(format!("{:016x}", self.d2).chars());
46        data.extend(format!("{:016x}", self.d3).chars());
47        data.into_iter().collect()
48    }
49
50    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
51        let mut data: Vec<char> = Vec::with_capacity(DIGEST_DATA_SIZE);
52        data.extend(format!("{:016X}", self.d0).chars());
53        data.extend(format!("{:016X}", self.d1).chars());
54        data.extend(format!("{:016X}", self.d2).chars());
55        data.extend(format!("{:016X}", self.d3).chars());
56        data.into_iter().collect()
57    }
58}
59
60impl FromHex for proto::primitives::Digest {
61    type Error = ConversionError;
62
63    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
64        let data = hex::decode(hex)?;
65
66        match data.len() {
67            size if size < DIGEST_DATA_SIZE => {
68                Err(ConversionError::InsufficientData { expected: DIGEST_DATA_SIZE, got: size })
69            },
70            size if size > DIGEST_DATA_SIZE => {
71                Err(ConversionError::TooMuchData { expected: DIGEST_DATA_SIZE, got: size })
72            },
73            _ => {
74                let d0 = u64::from_be_bytes(data[..8].try_into().unwrap());
75                let d1 = u64::from_be_bytes(data[8..16].try_into().unwrap());
76                let d2 = u64::from_be_bytes(data[16..24].try_into().unwrap());
77                let d3 = u64::from_be_bytes(data[24..32].try_into().unwrap());
78
79                Ok(proto::primitives::Digest { d0, d1, d2, d3 })
80            },
81        }
82    }
83}
84
85// INTO
86// ================================================================================================
87
88impl From<[u64; 4]> for proto::primitives::Digest {
89    fn from(value: [u64; 4]) -> Self {
90        Self {
91            d0: value[0],
92            d1: value[1],
93            d2: value[2],
94            d3: value[3],
95        }
96    }
97}
98
99impl From<&[u64; 4]> for proto::primitives::Digest {
100    fn from(value: &[u64; 4]) -> Self {
101        (*value).into()
102    }
103}
104
105impl From<[Felt; 4]> for proto::primitives::Digest {
106    fn from(value: [Felt; 4]) -> Self {
107        Self {
108            d0: value[0].as_int(),
109            d1: value[1].as_int(),
110            d2: value[2].as_int(),
111            d3: value[3].as_int(),
112        }
113    }
114}
115
116impl From<&[Felt; 4]> for proto::primitives::Digest {
117    fn from(value: &[Felt; 4]) -> Self {
118        (*value).into()
119    }
120}
121
122impl From<Word> for proto::primitives::Digest {
123    fn from(value: Word) -> Self {
124        Self {
125            d0: value[0].as_int(),
126            d1: value[1].as_int(),
127            d2: value[2].as_int(),
128            d3: value[3].as_int(),
129        }
130    }
131}
132
133impl From<&Word> for proto::primitives::Digest {
134    fn from(value: &Word) -> Self {
135        (*value).into()
136    }
137}
138
139impl From<&NoteId> for proto::primitives::Digest {
140    fn from(value: &NoteId) -> Self {
141        value.as_word().into()
142    }
143}
144
145impl From<NoteId> for proto::primitives::Digest {
146    fn from(value: NoteId) -> Self {
147        value.as_word().into()
148    }
149}
150
151// FROM DIGEST
152// ================================================================================================
153
154impl From<proto::primitives::Digest> for [u64; 4] {
155    fn from(value: proto::primitives::Digest) -> Self {
156        [value.d0, value.d1, value.d2, value.d3]
157    }
158}
159
160impl TryFrom<proto::primitives::Digest> for [Felt; 4] {
161    type Error = ConversionError;
162
163    fn try_from(value: proto::primitives::Digest) -> Result<Self, Self::Error> {
164        if [value.d0, value.d1, value.d2, value.d3]
165            .iter()
166            .any(|v| *v >= <Felt as StarkField>::MODULUS)
167        {
168            return Err(ConversionError::NotAValidFelt);
169        }
170
171        Ok([
172            Felt::new(value.d0),
173            Felt::new(value.d1),
174            Felt::new(value.d2),
175            Felt::new(value.d3),
176        ])
177    }
178}
179
180impl TryFrom<proto::primitives::Digest> for Word {
181    type Error = ConversionError;
182
183    fn try_from(value: proto::primitives::Digest) -> Result<Self, Self::Error> {
184        Ok(Self::new(value.try_into()?))
185    }
186}
187
188impl TryFrom<&proto::primitives::Digest> for [Felt; 4] {
189    type Error = ConversionError;
190
191    fn try_from(value: &proto::primitives::Digest) -> Result<Self, Self::Error> {
192        (*value).try_into()
193    }
194}
195
196impl TryFrom<&proto::primitives::Digest> for Word {
197    type Error = ConversionError;
198
199    fn try_from(value: &proto::primitives::Digest) -> Result<Self, Self::Error> {
200        (*value).try_into()
201    }
202}
203
204// TESTS
205// ================================================================================================
206
207#[cfg(test)]
208mod test {
209    use hex::{FromHex, ToHex};
210    use proptest::prelude::*;
211
212    use crate::generated as proto;
213
214    #[test]
215    fn hex_digest() {
216        let digest = proto::primitives::Digest {
217            d0: 0x306A_B7A6_F795_CAD7,
218            d1: 0x4927_3716_D099_AA04,
219            d2: 0xF741_2C3D_E726_4450,
220            d3: 0x976B_8764_9DB3_B82F,
221        };
222        let encoded: String = ToHex::encode_hex(&digest);
223        let round_trip: Result<proto::primitives::Digest, _> =
224            FromHex::from_hex::<&[u8]>(encoded.as_ref());
225        assert_eq!(digest, round_trip.unwrap());
226
227        let digest = proto::primitives::Digest { d0: 0, d1: 0, d2: 0, d3: 0 };
228        let encoded: String = ToHex::encode_hex(&digest);
229        let round_trip: Result<proto::primitives::Digest, _> =
230            FromHex::from_hex::<&[u8]>(encoded.as_ref());
231        assert_eq!(digest, round_trip.unwrap());
232    }
233
234    proptest! {
235        #[test]
236        fn encode_decode(
237            d0: u64,
238            d1: u64,
239            d2: u64,
240            d3: u64,
241        ) {
242            let digest = proto::primitives::Digest { d0, d1, d2, d3 };
243            let encoded: String = ToHex::encode_hex(&digest);
244            let round_trip: Result<proto::primitives::Digest, _> = FromHex::from_hex::<&[u8]>(encoded.as_ref());
245            assert_eq!(digest, round_trip.unwrap());
246        }
247    }
248}