1use bytemuck::{bytes_of_mut, must_cast};
10use digest::Digest;
11use sha2::{Sha256, block_api::compress256, digest::Output};
12
13use crate::{compress::CompressionFunction, suite::HashSuite};
14
15const BLOCK_LEN: usize = 64;
17
18const DIGEST_LEN: usize = 32;
20
21#[derive(Debug, Clone)]
26pub struct Sha256Compression {
27 initial_state: [u32; 8],
29}
30
31impl Default for Sha256Compression {
32 fn default() -> Self {
33 let initial_state_bytes = Sha256::digest(b"BINIUS SHA-256 COMPRESS");
34 let mut initial_state = [0u32; 8];
35 bytes_of_mut(&mut initial_state).copy_from_slice(&initial_state_bytes);
36 Self { initial_state }
37 }
38}
39
40impl Sha256Compression {
41 pub const fn initial_state(&self) -> &[u32; 8] {
46 &self.initial_state
47 }
48}
49
50impl CompressionFunction<Output<Sha256>, 2> for Sha256Compression {
51 fn compress(&self, input: [Output<Sha256>; 2]) -> Output<Sha256> {
52 let mut block = [0u8; BLOCK_LEN];
54 block[..DIGEST_LEN].copy_from_slice(input[0].as_slice());
55 block[DIGEST_LEN..].copy_from_slice(input[1].as_slice());
56
57 let mut state = self.initial_state;
58 compress256(&mut state, std::slice::from_ref(&block));
59
60 must_cast::<[u32; 8], [u8; DIGEST_LEN]>(state).into()
63 }
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct Sha256HashSuite;
69
70impl HashSuite for Sha256HashSuite {
71 type LeafHash = Sha256;
72 type Compression = Sha256Compression;
73}
74
75#[cfg(test)]
76mod tests {
77 use rand::{Rng, SeedableRng, rngs::StdRng};
78
79 use super::*;
80
81 #[test]
82 fn test_compression_matches_known_answer() {
83 let left: Output<Sha256> = [0u8; DIGEST_LEN].into();
89 let right: Output<Sha256> = std::array::from_fn::<u8, DIGEST_LEN, _>(|i| i as u8).into();
90
91 let got = Sha256Compression::default().compress([left, right]);
92
93 let want = "4731c4e3a3190d19dace68db5752af1b4ecf26305e75e85db86217662bbeff74";
94 let got_hex: String = got.iter().map(|b| format!("{b:02x}")).collect();
95 assert_eq!(got_hex, want, "the compression byte order changed");
96 }
97
98 #[test]
99 fn test_compression_is_one_block_from_the_domain_state() {
100 let mut rng = StdRng::seed_from_u64(0);
101 let compression = Sha256Compression::default();
102
103 for _ in 0..64 {
105 let mut left = Output::<Sha256>::default();
106 let mut right = Output::<Sha256>::default();
107 rng.fill_bytes(&mut left);
108 rng.fill_bytes(&mut right);
109
110 let got = compression.compress([left, right]);
111
112 let mut block = [0u8; BLOCK_LEN];
113 block[..DIGEST_LEN].copy_from_slice(&left);
114 block[DIGEST_LEN..].copy_from_slice(&right);
115 let mut want = *compression.initial_state();
116 compress256(&mut want, std::slice::from_ref(&block));
117
118 assert_eq!(got.as_slice(), must_cast::<[u32; 8], [u8; DIGEST_LEN]>(want));
119 }
120 }
121}