Skip to main content

hopper_native/
sha256.rs

1//! Const SHA-256 implementation used for Hopper-owned discriminators.
2
3const K: [u32; 64] = [
4    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
5    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
6    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
7    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
8    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
9    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
10    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
11    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
12];
13
14/// Incremental SHA-256 state that can be evaluated at compile time.
15///
16/// This is intended for generated, static commitments whose canonical input
17/// is assembled from many slices. It avoids allocating one concatenated
18/// buffer and avoids restarting the compression function for every field.
19#[derive(Clone, Copy)]
20pub struct ConstSha256 {
21    state: [u32; 8],
22    block: [u8; 64],
23    block_len: usize,
24    total_len: u64,
25}
26
27impl ConstSha256 {
28    /// Create an empty SHA-256 state.
29    pub const fn new() -> Self {
30        Self {
31            state: [
32                0x6a09e667u32,
33                0xbb67ae85,
34                0x3c6ef372,
35                0xa54ff53a,
36                0x510e527f,
37                0x9b05688c,
38                0x1f83d9ab,
39                0x5be0cd19,
40            ],
41            block: [0; 64],
42            block_len: 0,
43            total_len: 0,
44        }
45    }
46
47    /// Append bytes to the hash input.
48    pub const fn update(mut self, input: &[u8]) -> Self {
49        let mut i = 0;
50        while i < input.len() {
51            self.block[self.block_len] = input[i];
52            self.block_len += 1;
53            self.total_len = self.total_len.wrapping_add(1);
54            i += 1;
55            if self.block_len == 64 {
56                self.state = compress(self.state, self.block);
57                self.block = [0; 64];
58                self.block_len = 0;
59            }
60        }
61        self
62    }
63
64    /// Finalize the digest without modifying the original state.
65    pub const fn finalize(mut self) -> [u8; 32] {
66        self.block[self.block_len] = 0x80;
67        if self.block_len >= 56 {
68            self.state = compress(self.state, self.block);
69            self.block = [0; 64];
70        }
71        write_len(&mut self.block, self.total_len.wrapping_mul(8));
72        self.state = compress(self.state, self.block);
73        state_to_bytes(self.state)
74    }
75}
76
77impl Default for ConstSha256 {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83/// Compute SHA-256(data) at compile time.
84pub const fn sha256(data: &[u8]) -> [u8; 32] {
85    sha256_concat(data, &[])
86}
87
88/// Compute SHA-256(left || right) at compile time without allocating.
89pub const fn sha256_concat(left: &[u8], right: &[u8]) -> [u8; 32] {
90    let mut state = [
91        0x6a09e667u32,
92        0xbb67ae85,
93        0x3c6ef372,
94        0xa54ff53a,
95        0x510e527f,
96        0x9b05688c,
97        0x1f83d9ab,
98        0x5be0cd19,
99    ];
100    let len = left.len() + right.len();
101    let full_blocks = len / 64;
102    let mut block_index = 0;
103    while block_index < full_blocks {
104        let mut block = [0u8; 64];
105        let mut i = 0;
106        while i < 64 {
107            block[i] = concat_byte(left, right, block_index * 64 + i);
108            i += 1;
109        }
110        state = compress(state, block);
111        block_index += 1;
112    }
113
114    let rem = len % 64;
115    let mut block = [0u8; 64];
116    let mut i = 0;
117    while i < rem {
118        block[i] = concat_byte(left, right, full_blocks * 64 + i);
119        i += 1;
120    }
121    block[rem] = 0x80;
122    let bit_len = (len as u64).wrapping_mul(8);
123    if rem <= 55 {
124        write_len(&mut block, bit_len);
125        state = compress(state, block);
126    } else {
127        state = compress(state, block);
128        let mut last = [0u8; 64];
129        write_len(&mut last, bit_len);
130        state = compress(state, last);
131    }
132
133    state_to_bytes(state)
134}
135
136/// Compute SHA-256(left || right)[0..8].
137pub const fn sha256_prefix8(left: &[u8], right: &[u8]) -> [u8; 8] {
138    let hash = sha256_concat(left, right);
139    [
140        hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
141    ]
142}
143
144const fn concat_byte(left: &[u8], right: &[u8], index: usize) -> u8 {
145    if index < left.len() {
146        left[index]
147    } else {
148        right[index - left.len()]
149    }
150}
151
152const fn write_len(block: &mut [u8; 64], bit_len: u64) {
153    let bytes = bit_len.to_be_bytes();
154    let mut i = 0;
155    while i < 8 {
156        block[56 + i] = bytes[i];
157        i += 1;
158    }
159}
160
161const fn compress(mut state: [u32; 8], block: [u8; 64]) -> [u32; 8] {
162    let mut w = [0u32; 64];
163    let mut i = 0;
164    while i < 16 {
165        let j = i * 4;
166        w[i] = u32::from_be_bytes([block[j], block[j + 1], block[j + 2], block[j + 3]]);
167        i += 1;
168    }
169    while i < 64 {
170        let s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
171        let s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
172        w[i] = w[i - 16]
173            .wrapping_add(s0)
174            .wrapping_add(w[i - 7])
175            .wrapping_add(s1);
176        i += 1;
177    }
178
179    let mut a = state[0];
180    let mut b = state[1];
181    let mut c = state[2];
182    let mut d = state[3];
183    let mut e = state[4];
184    let mut f = state[5];
185    let mut g = state[6];
186    let mut h = state[7];
187    i = 0;
188    while i < 64 {
189        let s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
190        let ch = (e & f) ^ ((!e) & g);
191        let temp1 = h
192            .wrapping_add(s1)
193            .wrapping_add(ch)
194            .wrapping_add(K[i])
195            .wrapping_add(w[i]);
196        let s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
197        let maj = (a & b) ^ (a & c) ^ (b & c);
198        let temp2 = s0.wrapping_add(maj);
199        h = g;
200        g = f;
201        f = e;
202        e = d.wrapping_add(temp1);
203        d = c;
204        c = b;
205        b = a;
206        a = temp1.wrapping_add(temp2);
207        i += 1;
208    }
209
210    state[0] = state[0].wrapping_add(a);
211    state[1] = state[1].wrapping_add(b);
212    state[2] = state[2].wrapping_add(c);
213    state[3] = state[3].wrapping_add(d);
214    state[4] = state[4].wrapping_add(e);
215    state[5] = state[5].wrapping_add(f);
216    state[6] = state[6].wrapping_add(g);
217    state[7] = state[7].wrapping_add(h);
218    state
219}
220
221const fn rotr(value: u32, by: u32) -> u32 {
222    value.rotate_right(by)
223}
224
225const fn state_to_bytes(state: [u32; 8]) -> [u8; 32] {
226    let mut out = [0u8; 32];
227    let mut i = 0;
228    while i < 8 {
229        let bytes = state[i].to_be_bytes();
230        out[i * 4] = bytes[0];
231        out[i * 4 + 1] = bytes[1];
232        out[i * 4 + 2] = bytes[2];
233        out[i * 4 + 3] = bytes[3];
234        i += 1;
235    }
236    out
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn sha256_empty_matches_known_vector() {
245        assert_eq!(
246            sha256(b""),
247            [
248                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
249                0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
250                0x78, 0x52, 0xb8, 0x55,
251            ]
252        );
253    }
254
255    #[test]
256    fn sha256_abc_matches_known_vector() {
257        assert_eq!(
258            sha256(b"abc"),
259            [
260                0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
261                0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
262                0xf2, 0x00, 0x15, 0xad,
263            ]
264        );
265    }
266
267    #[test]
268    fn concat_matches_single_slice() {
269        assert_eq!(
270            sha256_concat(b"global:", b"initialize"),
271            sha256(b"global:initialize")
272        );
273    }
274
275    #[test]
276    fn incremental_matches_single_slice_across_block_boundaries() {
277        let left = b"a canonical prefix longer than one short field";
278        let right = b" plus enough suffix bytes to cross the sixty-four-byte block boundary";
279        let incremental = ConstSha256::new().update(left).update(right).finalize();
280        assert_eq!(incremental, sha256_concat(left, right));
281    }
282
283    #[test]
284    fn incremental_empty_matches_one_shot_empty() {
285        assert_eq!(ConstSha256::new().finalize(), sha256(b""));
286    }
287}