risc0_zkp/core/
digest.rs

1// Copyright 2024 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A generic (cross hash) digest, which is always 256 bits and composed of 8
16//! words
17
18use alloc::{format, vec::Vec};
19use core::fmt::{Debug, Display, Formatter};
20
21use borsh::{BorshDeserialize, BorshSerialize};
22use bytemuck::{Pod, PodCastError, Zeroable};
23use hex::{FromHex, FromHexError};
24use serde::{Deserialize, Serialize};
25
26pub use crate::digest;
27pub use hex_literal::hex;
28pub use risc0_zkvm_platform::WORD_SIZE;
29
30/// The number of words in the representation of a [Digest].
31pub const DIGEST_WORDS: usize = 8;
32
33/// The number of shorts in the representation of a [Digest].
34pub const DIGEST_SHORTS: usize = DIGEST_WORDS * 2;
35
36/// Size of the [Digest] representation in bytes.
37///
38/// Note that digests are stored in memory as words instead of bytes.
39pub const DIGEST_BYTES: usize = DIGEST_WORDS * WORD_SIZE;
40
41/// Digest represents the results of a hashing function.  It is always 256 bits
42/// of storage although depending on the hash it may have additional structure
43/// (for example Poseidon's output is actually composed of field elements).  The
44/// storage is in u32's in part to simplify alignment requirements, especially
45/// in the zkVM.
46#[derive(
47    Copy,
48    Clone,
49    Eq,
50    Ord,
51    PartialOrd,
52    PartialEq,
53    Hash,
54    Pod,
55    Zeroable,
56    Serialize,
57    Deserialize,
58    BorshSerialize,
59    BorshDeserialize,
60)]
61#[repr(transparent)]
62pub struct Digest([u32; DIGEST_WORDS]);
63
64impl Digest {
65    /// Digest of all zeroes.
66    pub const ZERO: Self = Self::new([0u32; DIGEST_WORDS]);
67
68    /// Constant constructor
69    pub const fn new(data: [u32; DIGEST_WORDS]) -> Self {
70        Self(data)
71    }
72
73    /// Construct a digest from a array of bytes in a const context.
74    /// Outside of const context, `Digest::from` is recommended.
75    pub const fn from_bytes(bytes: [u8; DIGEST_BYTES]) -> Self {
76        let mut digest: Digest = Digest::ZERO;
77        let mut i: usize = 0;
78        while i < DIGEST_WORDS {
79            let mut j = 0;
80            let mut word = 0u32;
81            while j < WORD_SIZE {
82                word <<= 8;
83                word |= bytes[i * WORD_SIZE + j] as u32;
84                j += 1;
85            }
86            word = u32::from_be(word);
87            digest.0[i] = word;
88            i += 1;
89        }
90        digest
91    }
92
93    /// Returns a reference to the [Digest] as a slice of words.
94    pub fn as_words(&self) -> &[u32] {
95        &self.0
96    }
97
98    /// Returns a reference to the [Digest] as a slice of bytes.
99    pub fn as_bytes(&self) -> &[u8] {
100        bytemuck::cast_slice(&self.0)
101    }
102
103    /// Returns a mutable slice of words.
104    pub fn as_mut_words(&mut self) -> &mut [u32] {
105        &mut self.0
106    }
107
108    /// Returns a mutable slice of bytes.
109    pub fn as_mut_bytes(&mut self) -> &mut [u8] {
110        bytemuck::cast_slice_mut(&mut self.0)
111    }
112}
113
114impl Default for Digest {
115    fn default() -> Digest {
116        Digest([0; DIGEST_WORDS])
117    }
118}
119
120/// Create a new [Digest] from an array of words.
121impl From<[u32; DIGEST_WORDS]> for Digest {
122    fn from(data: [u32; DIGEST_WORDS]) -> Self {
123        Self(data)
124    }
125}
126
127/// Create a new [Digest] from an array of bytes.
128impl From<[u8; DIGEST_BYTES]> for Digest {
129    fn from(data: [u8; DIGEST_BYTES]) -> Self {
130        match bytemuck::try_cast(data) {
131            Ok(digest) => digest,
132            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
133                // Bytes are not aligned. Copy the byte array into a new digest.
134                bytemuck::pod_read_unaligned(&data)
135            }
136            Err(e) => unreachable!("failed to cast [u8; DIGEST_BYTES] to Digest: {}", e),
137        }
138    }
139}
140
141impl<'a> From<&'a [u32; DIGEST_WORDS]> for &'a Digest {
142    fn from(data: &'a [u32; DIGEST_WORDS]) -> Self {
143        bytemuck::cast_ref(data)
144    }
145}
146
147impl FromHex for Digest {
148    type Error = FromHexError;
149
150    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
151        Ok(<[u8; DIGEST_BYTES]>::from_hex(hex)?.into())
152    }
153}
154
155impl TryFrom<&[u8]> for Digest {
156    type Error = core::array::TryFromSliceError;
157
158    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
159        Ok(<[u8; DIGEST_BYTES]>::try_from(data)?.into())
160    }
161}
162
163impl TryFrom<&[u32]> for Digest {
164    type Error = core::array::TryFromSliceError;
165
166    fn try_from(data: &[u32]) -> Result<Self, Self::Error> {
167        Ok(<[u32; DIGEST_WORDS]>::try_from(data)?.into())
168    }
169}
170
171impl<'a> TryFrom<&'a [u32]> for &'a Digest {
172    type Error = PodCastError;
173
174    fn try_from(data: &'a [u32]) -> Result<Self, Self::Error> {
175        match bytemuck::try_cast_slice(data) {
176            Ok([digest]) => Ok(digest),
177            Ok(_) => Err(PodCastError::SizeMismatch),
178            Err(e) => Err(e),
179        }
180    }
181}
182
183impl TryFrom<Vec<u8>> for Digest {
184    type Error = Vec<u8>;
185
186    fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
187        Ok(<[u8; DIGEST_BYTES]>::try_from(data)?.into())
188    }
189}
190
191impl TryFrom<Vec<u32>> for Digest {
192    type Error = Vec<u32>;
193
194    fn try_from(data: Vec<u32>) -> Result<Self, Self::Error> {
195        Ok(<[u32; DIGEST_WORDS]>::try_from(data)?.into())
196    }
197}
198
199impl From<Digest> for [u8; DIGEST_BYTES] {
200    fn from(digest: Digest) -> Self {
201        bytemuck::cast(digest)
202    }
203}
204
205impl From<Digest> for [u32; DIGEST_WORDS] {
206    fn from(digest: Digest) -> Self {
207        digest.0
208    }
209}
210
211impl AsRef<[u8; DIGEST_BYTES]> for Digest {
212    fn as_ref(&self) -> &[u8; DIGEST_BYTES] {
213        bytemuck::cast_ref(&self.0)
214    }
215}
216
217impl AsMut<[u8; DIGEST_BYTES]> for Digest {
218    fn as_mut(&mut self) -> &mut [u8; DIGEST_BYTES] {
219        bytemuck::cast_mut(&mut self.0)
220    }
221}
222
223impl AsRef<[u32; DIGEST_WORDS]> for Digest {
224    fn as_ref(&self) -> &[u32; DIGEST_WORDS] {
225        &self.0
226    }
227}
228
229impl AsMut<[u32; DIGEST_WORDS]> for Digest {
230    fn as_mut(&mut self) -> &mut [u32; DIGEST_WORDS] {
231        &mut self.0
232    }
233}
234
235impl AsRef<[u8]> for Digest {
236    fn as_ref(&self) -> &[u8] {
237        self.as_bytes()
238    }
239}
240
241impl AsMut<[u8]> for Digest {
242    fn as_mut(&mut self) -> &mut [u8] {
243        self.as_mut_bytes()
244    }
245}
246
247impl AsRef<[u32]> for Digest {
248    fn as_ref(&self) -> &[u32] {
249        self.as_words()
250    }
251}
252
253impl AsMut<[u32]> for Digest {
254    fn as_mut(&mut self) -> &mut [u32] {
255        self.as_mut_words()
256    }
257}
258
259impl Display for Digest {
260    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
261        f.write_str(&hex::encode(self))
262    }
263}
264
265impl Debug for Digest {
266    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
267        f.write_str(&format!("Digest({})", &hex::encode(self)))
268    }
269}
270
271/// Macro for constructing a Digest from a hex string.
272#[macro_export]
273macro_rules! digest {
274    ($s:literal) => {{
275        const BYTES: [u8; $crate::core::digest::DIGEST_BYTES] = $crate::core::digest::hex!($s);
276        $crate::core::digest::Digest::from_bytes(BYTES)
277    }};
278}
279
280#[cfg(test)]
281mod tests {
282    use hex::FromHex;
283
284    use super::Digest;
285
286    #[test]
287    fn test_from_hex() {
288        assert_eq!(
289            Digest::from_hex("00000077000000AA0000001200000034000000560000007a000000a900000009")
290                .unwrap(),
291            Digest::from([
292                0x77_u32.to_be(),
293                0xaa_u32.to_be(),
294                0x12_u32.to_be(),
295                0x34_u32.to_be(),
296                0x56_u32.to_be(),
297                0x7a_u32.to_be(),
298                0xa9_u32.to_be(),
299                0x09_u32.to_be(),
300            ])
301        );
302    }
303
304    #[test]
305    fn test_roundtrip() {
306        const HEX: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
307        assert_eq!(hex::encode(Digest::from_hex(HEX).unwrap()), HEX);
308    }
309}