Skip to main content

osom_lib_hashes/sha2/sha2_256/
portable.rs

1//! Holds the portable implementation of the `SHA2-256` algorithm.
2
3#![allow(clippy::many_single_char_names)]
4
5use osom_lib_arrays::const_helpers::{from_be_const_u32, subslice_mut_const};
6use osom_lib_arrays::fixed_array::ConstBuffer;
7use osom_lib_reprc::macros::reprc;
8
9use super::sha2_256_shared::{INITIAL_STATE, K, calculate_final_blocks};
10use crate::traits::HashFunction;
11
12const CHUNK_SIZE_U32: usize = 16;
13
14/// A portable implementation of the `SHA2-256` algorithm.
15///
16/// This implementation is portable, will work on any platform
17/// and is const friendly.
18///
19/// # Notes
20///
21/// The concrete algorithm follows closely the `SHA2-256` specification
22/// taken from [RFC 4634](https://www.rfc-editor.org/rfc/rfc4634).
23#[reprc]
24#[must_use]
25pub struct SHA2_256_Portable {
26    // This field is used in the final block calculation.
27    // It is first due to its size (we are using #[repr(C)]).
28    pub(super) total_length: u64,
29
30    // The internal state of SHA2-256 hasher.
31    pub(super) state: [u32; 8],
32
33    // The buffered message block. We need to keep it because final block needs to be processed differently.
34    pub(super) bufferer: ConstBuffer<64, u8>,
35}
36
37#[inline(always)]
38const fn ch(x: u32, y: u32, z: u32) -> u32 {
39    (x & y) ^ (!x & z)
40}
41
42#[inline(always)]
43const fn maj(x: u32, y: u32, z: u32) -> u32 {
44    (x & y) ^ (x & z) ^ (y & z)
45}
46
47#[inline(always)]
48const fn bsig0(x: u32) -> u32 {
49    x.rotate_right(2) ^ x.rotate_right(13) ^ x.rotate_right(22)
50}
51
52#[inline(always)]
53const fn bsig1(x: u32) -> u32 {
54    x.rotate_right(6) ^ x.rotate_right(11) ^ x.rotate_right(25)
55}
56
57#[inline(always)]
58const fn ssig0(x: u32) -> u32 {
59    x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3)
60}
61
62#[inline(always)]
63const fn ssig1(x: u32) -> u32 {
64    x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10)
65}
66
67const fn prepare_w_schedule(data: &[u32; 16]) -> [u32; 4 * CHUNK_SIZE_U32] {
68    let mut w = [0u32; 4 * CHUNK_SIZE_U32];
69    let mut index = 0;
70    while index < CHUNK_SIZE_U32 {
71        w[index] = data[index];
72        index += 1;
73    }
74
75    let mut index = CHUNK_SIZE_U32;
76    while index < 4 * CHUNK_SIZE_U32 {
77        w[index] = ssig1(w[index - 2])
78            .wrapping_add(w[index - 7])
79            .wrapping_add(ssig0(w[index - 15]))
80            .wrapping_add(w[index - 16]);
81        index += 1;
82    }
83
84    w
85}
86
87const fn update_state(state: &mut [u32; 8], data: &[u8; 64]) {
88    let data = {
89        let mut initial_data = [0u32; CHUNK_SIZE_U32];
90        let mut index = 0;
91        while index < CHUNK_SIZE_U32 {
92            initial_data[index] = unsafe { from_be_const_u32(data, index * 4) };
93            index += 1;
94        }
95        initial_data
96    };
97
98    let w = prepare_w_schedule(&data);
99
100    let mut a = state[0];
101    let mut b = state[1];
102    let mut c = state[2];
103    let mut d = state[3];
104    let mut e = state[4];
105    let mut f = state[5];
106    let mut g = state[6];
107    let mut h = state[7];
108
109    let mut index = 0;
110    while index < 64 {
111        let temp1 = h
112            .wrapping_add(bsig1(e))
113            .wrapping_add(ch(e, f, g))
114            .wrapping_add(K[index])
115            .wrapping_add(w[index]);
116        let temp2 = bsig0(a).wrapping_add(maj(a, b, c));
117        h = g;
118        g = f;
119        f = e;
120        e = d.wrapping_add(temp1);
121        d = c;
122        c = b;
123        b = a;
124        a = temp1.wrapping_add(temp2);
125        index += 1;
126    }
127
128    state[0] = state[0].wrapping_add(a);
129    state[1] = state[1].wrapping_add(b);
130    state[2] = state[2].wrapping_add(c);
131    state[3] = state[3].wrapping_add(d);
132    state[4] = state[4].wrapping_add(e);
133    state[5] = state[5].wrapping_add(f);
134    state[6] = state[6].wrapping_add(g);
135    state[7] = state[7].wrapping_add(h);
136}
137
138impl SHA2_256_Portable {
139    /// Creates a new [`SHA2_256_Portable`] instance.
140    #[inline(always)]
141    pub const fn new() -> Self {
142        Self {
143            total_length: 0,
144            state: INITIAL_STATE,
145            bufferer: ConstBuffer::new(),
146        }
147    }
148
149    #[inline(always)]
150    pub(super) const fn from_pieces(total_length: u64, state: [u32; 8], bufferer: ConstBuffer<64, u8>) -> Self {
151        Self {
152            total_length,
153            state,
154            bufferer,
155        }
156    }
157
158    /// Writes a block of data to the underlying state.
159    ///
160    /// # Panics
161    ///
162    /// This function will panic if the length of the data is greater than `u32::MAX`,
163    /// and when the total processed length exceeds `u64::MAX`.
164    pub const fn update_const(&mut self, data: &[u8]) {
165        let len = data.len();
166        if len == 0 {
167            return;
168        }
169
170        assert!(
171            len <= u32::MAX as usize,
172            "The max size of a chunk for SHA2_256_Portable is u32::MAX."
173        );
174
175        let len: u64 = len as u64;
176
177        assert!(
178            self.total_length <= (u64::MAX / 8) - len,
179            "The total length of the data that SHA2-256 can process is u64::MAX / 8. This limit is reached."
180        );
181
182        // This is safe due to previous assertions.
183        self.total_length = unsafe { self.total_length.unchecked_add(len) };
184
185        let mut iterator = self.bufferer.buffer_const(data);
186        while let Some(block) = iterator.next() {
187            update_state(&mut self.state, block);
188        }
189    }
190
191    /// Writes the final has value to the passed output.
192    ///
193    /// # Notes
194    ///
195    /// This function does not update the internal state, and thus
196    /// the [`SHA2_256_Portable`] can still be used afterwards.
197    pub const fn write_result_const(&self, output: &mut [u8; 32]) {
198        // Build the final SHA2-256 block.
199        let mut state = self.state;
200
201        let final_blocks = calculate_final_blocks(self.total_length, self.bufferer.current_state_const());
202        let mut tmp_buffer = ConstBuffer::<64, u8>::new();
203        let mut iterator = tmp_buffer.buffer_const(final_blocks.as_slice());
204        while let Some(block) = iterator.next() {
205            update_state(&mut state, block);
206        }
207
208        // Finally write the output via big-endian encoding.
209        let mut index = 0;
210        while index < 8 {
211            let dst = unsafe {
212                let range = (4 * index)..(4 * (index + 1));
213                subslice_mut_const(output, range)
214            };
215            let src = &state[index].to_be_bytes();
216            dst.copy_from_slice(src);
217            index += 1;
218        }
219    }
220
221    /// Returns the final hash value.
222    ///
223    /// # Notes
224    ///
225    /// This function does not update the internal state, and thus
226    /// the [`SHA2_256_Portable`] can still be used afterwards.
227    #[inline(always)]
228    #[must_use]
229    pub const fn result_const(&self) -> [u8; 32] {
230        let mut array = core::mem::MaybeUninit::<[u8; 32]>::uninit();
231        let raw_ref = unsafe { &mut *array.as_mut_ptr() };
232        self.write_result_const(raw_ref);
233        unsafe { array.assume_init() }
234    }
235}
236
237impl Default for SHA2_256_Portable {
238    #[inline(always)]
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244impl HashFunction for SHA2_256_Portable {
245    type Output = [u8; 32];
246
247    #[inline(always)]
248    fn update(&mut self, data: impl AsRef<[u8]>) {
249        self.update_const(data.as_ref());
250    }
251
252    #[inline(always)]
253    fn write_result(&self, output: &mut Self::Output) {
254        self.write_result_const(output);
255    }
256}