commonware_cryptography/sha256/
mod.rs

1//! SHA-256 implementation of the `Hasher` trait.
2//!
3//! This implementation uses the `sha2` crate to generate SHA-256 digests.
4//!
5//! # Example
6//! ```rust
7//! use commonware_cryptography::{Hasher, Sha256};
8//!
9//! // Create a new SHA-256 hasher
10//! let mut hasher = Sha256::new();
11//!
12//! // Update the hasher with some messages
13//! hasher.update(b"hello,");
14//! hasher.update(b"world!");
15//!
16//! // Finalize the hasher to get the digest
17//! let digest = hasher.finalize();
18//!
19//! // Print the digest
20//! println!("digest: {:?}", digest);
21//! ```
22
23use crate::Hasher;
24#[cfg(not(feature = "std"))]
25use alloc::vec;
26use bytes::{Buf, BufMut};
27use commonware_codec::{DecodeExt, Error as CodecError, FixedSize, Read, ReadExt, Write};
28use commonware_utils::{hex, Array, Span};
29use core::{
30    fmt::{Debug, Display},
31    ops::Deref,
32};
33use rand_core::CryptoRngCore;
34use sha2::{Digest as _, Sha256 as ISha256};
35use zeroize::Zeroize;
36
37/// Re-export `sha2::Sha256` as `CoreSha256` for external use if needed.
38pub type CoreSha256 = ISha256;
39
40const DIGEST_LENGTH: usize = 32;
41
42/// SHA-256 hasher.
43#[derive(Debug, Default)]
44pub struct Sha256 {
45    hasher: ISha256,
46}
47
48impl Clone for Sha256 {
49    fn clone(&self) -> Self {
50        // We manually implement `Clone` to avoid cloning the hasher state.
51        Self::default()
52    }
53}
54
55impl Sha256 {
56    /// Convenience function for testing that creates an easily recognizable digest by repeating a
57    /// single byte.
58    pub fn fill(b: u8) -> <Self as Hasher>::Digest {
59        <Self as Hasher>::Digest::decode(vec![b; DIGEST_LENGTH].as_ref()).unwrap()
60    }
61}
62
63impl Hasher for Sha256 {
64    type Digest = Digest;
65
66    fn update(&mut self, message: &[u8]) -> &mut Self {
67        self.hasher.update(message);
68        self
69    }
70
71    fn finalize(&mut self) -> Self::Digest {
72        let finalized = self.hasher.finalize_reset();
73        let array: [u8; DIGEST_LENGTH] = finalized.into();
74        Self::Digest::from(array)
75    }
76
77    fn reset(&mut self) -> &mut Self {
78        self.hasher = ISha256::new();
79        self
80    }
81
82    fn empty() -> Self::Digest {
83        Self::new().finalize()
84    }
85}
86
87/// Digest of a SHA-256 hashing operation.
88#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
89#[repr(transparent)]
90pub struct Digest(pub [u8; DIGEST_LENGTH]);
91
92impl Write for Digest {
93    fn write(&self, buf: &mut impl BufMut) {
94        self.0.write(buf);
95    }
96}
97
98impl Read for Digest {
99    type Cfg = ();
100
101    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
102        let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
103        Ok(Self(array))
104    }
105}
106
107impl FixedSize for Digest {
108    const SIZE: usize = DIGEST_LENGTH;
109}
110
111impl Span for Digest {}
112
113impl Array for Digest {}
114
115impl From<[u8; DIGEST_LENGTH]> for Digest {
116    fn from(value: [u8; DIGEST_LENGTH]) -> Self {
117        Self(value)
118    }
119}
120
121impl AsRef<[u8]> for Digest {
122    fn as_ref(&self) -> &[u8] {
123        &self.0
124    }
125}
126
127impl Deref for Digest {
128    type Target = [u8];
129    fn deref(&self) -> &[u8] {
130        &self.0
131    }
132}
133
134impl Debug for Digest {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        write!(f, "{}", hex(&self.0))
137    }
138}
139
140impl Display for Digest {
141    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142        write!(f, "{}", hex(&self.0))
143    }
144}
145
146impl crate::Digest for Digest {
147    fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
148        let mut array = [0u8; DIGEST_LENGTH];
149        rng.fill_bytes(&mut array);
150        Self(array)
151    }
152}
153
154impl Zeroize for Digest {
155    fn zeroize(&mut self) {
156        self.0.zeroize();
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use commonware_codec::{DecodeExt, Encode};
164    use commonware_utils::hex;
165
166    const HELLO_DIGEST: [u8; DIGEST_LENGTH] =
167        hex!("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
168    const EMPTY_DIGEST: [u8; DIGEST_LENGTH] =
169        hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
170
171    #[test]
172    fn test_sha256() {
173        let msg = b"hello world";
174
175        // Generate initial hash
176        let mut hasher = Sha256::new();
177        hasher.update(msg);
178        let digest = hasher.finalize();
179        assert!(Digest::decode(digest.as_ref()).is_ok());
180        assert_eq!(digest.as_ref(), HELLO_DIGEST);
181
182        // Reuse hasher
183        hasher.update(msg);
184        let digest = hasher.finalize();
185        assert!(Digest::decode(digest.as_ref()).is_ok());
186        assert_eq!(digest.as_ref(), HELLO_DIGEST);
187
188        // Test simple hasher
189        let hash = Sha256::hash(msg);
190        assert_eq!(hash.as_ref(), HELLO_DIGEST);
191    }
192
193    #[test]
194    fn test_sha256_len() {
195        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
196    }
197
198    #[test]
199    fn test_hash_empty() {
200        let digest1 = Sha256::empty();
201        let digest2 = Sha256::empty();
202
203        assert_eq!(digest1, digest2);
204    }
205
206    #[test]
207    fn test_sha256_empty() {
208        let empty_digest = Sha256::empty();
209        let expected_digest = Digest::from(EMPTY_DIGEST);
210
211        assert_eq!(empty_digest, expected_digest);
212    }
213
214    #[test]
215    fn test_codec() {
216        let msg = b"hello world";
217        let mut hasher = Sha256::new();
218        hasher.update(msg);
219        let digest = hasher.finalize();
220
221        let encoded = digest.encode();
222        assert_eq!(encoded.len(), DIGEST_LENGTH);
223        assert_eq!(encoded, digest.as_ref());
224
225        let decoded = Digest::decode(encoded).unwrap();
226        assert_eq!(digest, decoded);
227    }
228}