diff --git a/Cargo.lock b/Cargo.lock index 55dfba9..c5aaaf0 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,8 +15,10 @@ dependencies = [ name = "algorithms" version = "0.1.0" dependencies = [ + "assert_hex", "criterion", "iai", + "pretty_assertions", ] [[package]] @@ -28,6 +30,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "assert_hex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7010f1430f0fc8ca80bdb5e5d074db68776a2e268ec6cf80b53712d3ea4bca7" + [[package]] name = "atty" version = "0.2.14" @@ -282,6 +290,12 @@ dependencies = [ "defmt 1.0.1", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "either" version = "1.15.0" @@ -517,6 +531,16 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1002,3 +1026,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/crates/algorithms/Cargo.toml b/crates/algorithms/Cargo.toml index d677abe..1267ee2 100755 --- a/crates/algorithms/Cargo.toml +++ b/crates/algorithms/Cargo.toml @@ -6,8 +6,10 @@ edition = "2024" [dependencies] [dev-dependencies] +assert_hex = "0.4.1" criterion = "0.3" iai = "0.1.1" +pretty_assertions = "1.4.1" [[bench]] name = "crc32bench" @@ -16,3 +18,8 @@ harness = false [[bench]] name = "crc32bench_iai" harness = false + +[features] +default = [] +std = [] +show_internals = ["std"] diff --git a/crates/algorithms/algorithms-c/src/hash/sha2.c b/crates/algorithms/algorithms-c/src/hash/sha2.c index 997857f..890957c 100644 --- a/crates/algorithms/algorithms-c/src/hash/sha2.c +++ b/crates/algorithms/algorithms-c/src/hash/sha2.c @@ -6,8 +6,7 @@ #include #ifdef TEST -// #define SHOW_INTERNALS - +#define SHOW_INTERNALS #endif // TEST #ifdef SHOW_INTERNALS @@ -48,7 +47,8 @@ static uint32_t addTemp = 0; #define SHA256_ROTL(bits, word) (((word) << (bits)) | ((word) >> (32 - (bits)))) #define SHA256_ROTR(bits, word) (((word) >> (bits)) | ((word) << (32 - (bits)))) -/* Define the SHA SIGMA and sigma macros */ +/* Define the SHA SIGMA and sigma macros, yes these have the same name and are + * different */ #define SHA256_SIGMA0(word) \ (SHA256_ROTR(2, word) ^ SHA256_ROTR(13, word) ^ SHA256_ROTR(22, word)) #define SHA256_SIGMA1(word) \ @@ -168,9 +168,7 @@ static void sha2_256_process_message_block(SHA2Context *context) { #ifdef SHOW_INTERNALS TRACE("DONE:\t") dump_alphabet; -#endif // SHOW_INTERNALS -#ifdef SHOW_INTERNALS TRACE("Intermediate hash before we add the working variables:\n"); for (int o = 0; o < SHA2_256_HashParts; o++) { TRACE("\t%d:\t%032b\n", o, context->intermediate_hash[o]); @@ -254,14 +252,7 @@ static void sha2_256_pad_message(SHA2Context *context, uint8_t pad_byte) { } static void sha2_256_finalize(SHA2Context *context, uint8_t pad_byte) { - size_t i; sha2_256_pad_message(context, pad_byte); - /* message may be sensitive, so clear it out */ - for (i = 0; i < SHA2_256_BlockSize; ++i) - context->message_block[i] = 0; - context->length_high = 0; /* and clear length */ - context->length_low = 0; - context->computed = true; } /* diff --git a/crates/algorithms/src/hash/ffi/sha2.rs b/crates/algorithms/src/hash/ffi/sha2.rs index d0f0a56..a652ddd 100644 --- a/crates/algorithms/src/hash/ffi/sha2.rs +++ b/crates/algorithms/src/hash/ffi/sha2.rs @@ -7,7 +7,7 @@ use super::super::sha2::{DIGEST256_EMPTY, Digest256, SHA2_256_BLOCK_SIZE}; #[allow(unused)] #[allow(clippy::enum_variant_names)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum SHA2Result { +pub enum SHA2Outcome { shaSuccess = 0, shaNull, /* Null pointer parameter */ shaInputTooLong, /* input data too long */ @@ -50,45 +50,48 @@ impl Default for Sha2_256Context { mod ffi { use super::*; unsafe extern "C" { - pub fn sha2_256_oneshot(data: *const u8, len: usize, digest: *mut Digest256) -> SHA2Result; - pub fn sha2_256_reset(context: *mut Sha2_256Context) -> SHA2Result; + pub fn sha2_256_oneshot(data: *const u8, len: usize, digest: *mut Digest256) + -> SHA2Outcome; + pub fn sha2_256_reset(context: *mut Sha2_256Context) -> SHA2Outcome; pub fn sha2_256_input( context: *mut Sha2_256Context, data: *const u8, len: usize, - ) -> SHA2Result; - pub fn sha2_256_result(context: *mut Sha2_256Context, digest: *mut Digest256) - -> SHA2Result; + ) -> SHA2Outcome; + pub fn sha2_256_result( + context: *mut Sha2_256Context, + digest: *mut Digest256, + ) -> SHA2Outcome; } } #[inline] -pub fn sha2_256_oneshot(data: &[u8]) -> Result { +pub fn sha2_256_oneshot(data: &[u8]) -> Result { let mut digest: Digest256 = DIGEST256_EMPTY; let res = unsafe { ffi::sha2_256_oneshot(ref_to_ptr(data), data.len(), ref_to_ptr_mut(&mut digest)) }; to_res(digest, res) } -pub fn sha2_256_reset(context: &mut Sha2_256Context) -> Result<(), SHA2Result> { +pub fn sha2_256_reset(context: &mut Sha2_256Context) -> Result<(), SHA2Outcome> { let res = unsafe { ffi::sha2_256_reset(ref_to_ptr_mut(context)) }; to_res((), res) } -pub fn sha2_256_input(context: &mut Sha2_256Context, data: &[u8]) -> Result<(), SHA2Result> { +pub fn sha2_256_input(context: &mut Sha2_256Context, data: &[u8]) -> Result<(), SHA2Outcome> { let res = unsafe { ffi::sha2_256_input(ref_to_ptr_mut(context), ref_to_ptr(data), data.len()) }; to_res((), res) } -pub fn sha2_256_result(context: &mut Sha2_256Context) -> Result { +pub fn sha2_256_result(context: &mut Sha2_256Context) -> Result { let mut digest: Digest256 = DIGEST256_EMPTY; let res = unsafe { ffi::sha2_256_result(ref_to_ptr_mut(context), ref_to_ptr_mut(&mut digest)) }; to_res(digest, res) } #[inline] -pub(crate) fn to_res(if_ok: T, res: SHA2Result) -> Result { - if res == SHA2Result::shaSuccess { +pub(crate) fn to_res(if_ok: T, res: SHA2Outcome) -> Result { + if res == SHA2Outcome::shaSuccess { Ok(if_ok) } else { Err(res) diff --git a/crates/algorithms/src/hash/sha2.rs b/crates/algorithms/src/hash/sha2.rs index 63fa2bb..ddbefaa 100644 --- a/crates/algorithms/src/hash/sha2.rs +++ b/crates/algorithms/src/hash/sha2.rs @@ -1,4 +1,4 @@ -use crate::hash::ffi::SHA2Result; +use crate::hash::ffi::SHA2Outcome; pub const SHA2_256_HASH_BITS: usize = 256; pub const SHA2_256_HASH_BYTES: usize = SHA2_256_HASH_BITS / 8; @@ -8,19 +8,48 @@ pub const SHA2_256_BLOCK_SIZE: usize = 2 * SHA2_256_HASH_BYTES; pub type Digest256 = [u32; SHA2_256_HASH_PARTS]; pub(crate) const DIGEST256_EMPTY: Digest256 = [0; SHA2_256_HASH_PARTS]; -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct SHA2_256Context { +macro_rules! show_internals { + ($block:block) => { + #[cfg(feature = "show_internals")] + $block + }; +} + +#[derive(PartialEq, Eq, Clone, Hash)] +pub struct Sha2_256Context { intermediate_hash: Digest256, block: [u8; SHA2_256_BLOCK_SIZE], block_idx: usize, + length_low: usize, + length_high: usize, } -impl SHA2_256Context { +impl Sha2_256Context { + const K: [u32; SHA2_256_BLOCK_SIZE] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, + 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, + 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, + 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, + 0xc67178f2, + ]; + const H: [u32; SHA2_256_HASH_PARTS] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, + 0x5be0cd19, + ]; + const PAD_BYTE: u8 = 0x80; + pub fn new() -> Self { Self { - intermediate_hash: Default::default(), + intermediate_hash: Self::H, block: [0; SHA2_256_BLOCK_SIZE], block_idx: Default::default(), + length_low: Default::default(), + length_high: Default::default(), } } @@ -28,28 +57,293 @@ impl SHA2_256Context { *self = Self::new(); } - pub fn input(&mut self, data: &[u8]) -> Result<(), SHA2Result> { - todo!() + pub fn input(&mut self, data: &[u8]) -> Result<(), SHA2Outcome> { + if data.is_empty() { + return Ok(()); + } + for byte in data { + self.block[self.block_idx] = *byte; + self.block_idx += 1; + if self.add_length(8).is_ok() && self.block_idx == SHA2_256_BLOCK_SIZE { + self.process_block(); + } + } + Ok(()) } - pub fn finish(self) -> Result { - todo!() + pub fn finish(mut self) -> Result { + self.finalize(); + Ok(self.intermediate_hash) + } + + // BUG: false values at end? + fn process_block(&mut self) { + show_internals!({ + self.show_internal_state("start processing"); + }); + + let mut w = [0; SHA2_256_BLOCK_SIZE]; + + #[allow(clippy::needless_range_loop)] + for t in 0..16 { + let t4 = t * 4; + w[t] = ((self.block[t4] as u32) << 24) + | ((self.block[t4 + 1] as u32) << 16) + | ((self.block[t4 + 2] as u32) << 8) + | (self.block[t4 + 3] as u32); + } + + for t in 16..SHA2_256_BLOCK_SIZE { + w[t] = Self::sha256_sigma1_1(w[t - 2]) + .wrapping_add(w[t - 7]) + .wrapping_add(Self::sha256_sigma0_1(w[t - 15])) + .wrapping_add(w[t - 16]); + } + + show_internals!({ + for (i, w) in w.iter().enumerate() { + println!("w[{i}]:\t{w:032b}") + } + }); + + let mut a = self.intermediate_hash[0]; + let mut b = self.intermediate_hash[1]; + let mut c = self.intermediate_hash[2]; + let mut d = self.intermediate_hash[3]; + let mut e = self.intermediate_hash[4]; + let mut f = self.intermediate_hash[5]; + let mut g = self.intermediate_hash[6]; + let mut h = self.intermediate_hash[7]; + + show_internals!({ + println!("A:\t{a:032b}"); + println!("B:\t{b:032b}"); + println!("C:\t{c:032b}"); + println!("D:\t{d:032b}"); + println!("E:\t{e:032b}"); + println!("F:\t{f:032b}"); + println!("G:\t{g:032b}"); + println!("H:\t{h:032b}"); + }); + + let mut temp1; + let mut temp2; + #[allow(clippy::needless_range_loop)] + for t in 0..SHA2_256_BLOCK_SIZE { + show_internals!({ + println!("Iter {t}"); + }); + + // BUG: the alphabet values are not being changed correctly? + temp1 = h + .wrapping_add(Self::sha256_sigma1_0(e)) + .wrapping_add(Self::sha_ch(e, f, g)) + .wrapping_add(Self::K[t]) + .wrapping_add(w[t]); + temp2 = Self::sha256_sigma0_0(a).wrapping_add(Self::sha_maj(a, b, c)); + h = g; + g = f; + f = e; + e = d.wrapping_add(temp1); + d = c; + c = b; + b = a; + a = temp1.wrapping_add(temp2); + + show_internals!({ + println!("A:\t{a:032b} {a:08x}"); + println!("B:\t{b:032b} {b:08x}"); + println!("C:\t{c:032b} {c:08x}"); + println!("D:\t{d:032b} {d:08x}"); + println!("E:\t{e:032b} {e:08x}"); + println!("F:\t{f:032b} {f:08x}"); + println!("G:\t{g:032b} {g:08x}"); + println!("H:\t{h:032b} {h:08x}"); + for (i, h) in self.intermediate_hash.iter().enumerate() { + println!("h[{i}]:\t{h:032b} {h:08x}"); + } + }); + } + + show_internals!({ + self.show_internal_state("assigning intermediate_hash"); + }); + + self.intermediate_hash[0] = self.intermediate_hash[0].wrapping_add(a); + self.intermediate_hash[1] = self.intermediate_hash[1].wrapping_add(b); + self.intermediate_hash[2] = self.intermediate_hash[2].wrapping_add(c); + self.intermediate_hash[3] = self.intermediate_hash[3].wrapping_add(d); + self.intermediate_hash[4] = self.intermediate_hash[4].wrapping_add(e); + self.intermediate_hash[5] = self.intermediate_hash[5].wrapping_add(f); + self.intermediate_hash[6] = self.intermediate_hash[6].wrapping_add(g); + self.intermediate_hash[7] = self.intermediate_hash[7].wrapping_add(h); + + self.block_idx = 0; + + // BUG: something is weird with these hash values, they are almost right? + show_internals!({ + self.show_internal_state("processing end"); + for (i, h) in self.intermediate_hash.iter().enumerate() { + println!("h[{i}]:\t{h:032b}"); + } + }); + } + + fn add_length(&mut self, len: usize) -> Result<(), SHA2Outcome> { + let add_temp = self.length_low; + + // Update length_low and check for overflow + self.length_low = self.length_low.wrapping_add(len); + if self.length_low < add_temp { + // Overflow occurred, increment length_high + self.length_high = self.length_high.wrapping_add(1); + if self.length_high == 0 { + return Err(SHA2Outcome::shaInputTooLong); + } + } + + Ok(()) + } + + fn finalize(&mut self) { + self.pad_block(); + } + + fn pad_block(&mut self) { + if self.block_idx >= (SHA2_256_BLOCK_SIZE - 8) { + self.block[self.block_idx] = Self::PAD_BYTE; + self.block_idx += 1; + while self.block_idx < SHA2_256_BLOCK_SIZE { + self.block[self.block_idx] = 0; + self.block_idx += 1; + } + self.process_block(); + } else { + self.block[self.block_idx] = Self::PAD_BYTE; + self.block_idx += 1; + } + + while self.block_idx < (SHA2_256_BLOCK_SIZE - 8) { + self.block[self.block_idx] = 0; + self.block_idx += 1; + } + + self.block[56] = (self.length_high >> 24) as u8; + self.block[57] = (self.length_high >> 16) as u8; + self.block[58] = (self.length_high >> 8) as u8; + self.block[59] = (self.length_high) as u8; + self.block[60] = (self.length_low >> 24) as u8; + self.block[61] = (self.length_low >> 16) as u8; + self.block[62] = (self.length_low >> 8) as u8; + self.block[63] = (self.length_low) as u8; + + self.process_block(); + } + + #[cfg(feature = "show_internals")] + fn show_internal_state(&self, msg: impl core::fmt::Display) { + println!("{msg}\n{self:#x?}") } } -impl Default for SHA2_256Context { +/// internal functions for SHA2 process_block +impl Sha2_256Context { + #[inline] + fn sha256_shr(bits: u32, word: u32) -> u32 { + word >> bits + } + + #[inline] + fn sha256_rotr(bits: u32, word: u32) -> u32 { + u32::rotate_right(word, bits) + } + + fn sha256_sigma0_0(word: u32) -> u32 { + Self::sha256_rotr(2, word) ^ Self::sha256_rotr(13, word) ^ Self::sha256_shr(22, word) + } + + fn sha256_sigma1_0(word: u32) -> u32 { + Self::sha256_rotr(6, word) ^ Self::sha256_rotr(11, word) ^ Self::sha256_shr(25, word) + } + + fn sha256_sigma0_1(word: u32) -> u32 { + Self::sha256_rotr(7, word) ^ Self::sha256_rotr(18, word) ^ Self::sha256_shr(3, word) + } + + fn sha256_sigma1_1(word: u32) -> u32 { + Self::sha256_rotr(17, word) ^ Self::sha256_rotr(19, word) ^ Self::sha256_shr(10, word) + } + + #[inline] + fn sha_ch(x: u32, y: u32, z: u32) -> u32 { + (x & (y ^ z)) ^ z + } + + #[inline] + fn sha_maj(x: u32, y: u32, z: u32) -> u32 { + (x & (y | z)) | (y & z) + } +} + +impl Default for Sha2_256Context { fn default() -> Self { Self::new() } } -pub fn sha2_256_oneshot(data: &[u8]) -> Result { - todo!() +pub fn sha2_256_oneshot(data: &[u8]) -> Result { + let mut ctx = Sha2_256Context::default(); + ctx.input(data)?; + ctx.finish() } +#[cfg(feature = "show_internals")] +#[allow(unused)] +mod debug { + use super::*; + + struct InlineArrHex2<'a, T: core::fmt::Debug + core::fmt::LowerHex>(&'a [T]); + + impl<'a, T> core::fmt::Debug for InlineArrHex2<'a, T> + where + T: core::fmt::Debug + core::fmt::LowerHex, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[\n")?; + for (i, t) in self.0.iter().enumerate() { + write!(f, "0x{t:02x}")?; + if i < self.0.len() - 1 { + write!(f, ", ")?; + } + if i % 8 == 7 && i < self.0.len() - 1 { + writeln!(f)?; + } + } + write!(f, "\n]")?; + Ok(()) + } + } + + impl core::fmt::Debug for Sha2_256Context { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Sha2_256Context") + .field("intermediate_hash", &InlineArrHex2(&self.intermediate_hash)) + .field("block", &InlineArrHex2(&self.block)) + .field("block_idx", &self.block_idx) + .field("length_low", &self.length_low) + .field("length_high", &self.length_high) + .finish() + } + } +} +#[cfg(feature = "show_internals")] +use debug::*; + #[cfg(test)] mod test { use super::*; + use assert_hex::*; + use pretty_assertions::{assert_eq, assert_ne}; const TEST_VALUES: &[(&str, Digest256)] = &[ ( @@ -70,8 +364,16 @@ mod test { #[test] fn test_check() { + let mut dig; for (input, expected_output) in TEST_VALUES.iter().copied() { - assert_eq!(sha2_256_oneshot(input.as_bytes()), Ok(expected_output)) + dig = sha2_256_oneshot(input.as_bytes()).unwrap(); + println!( + "Binary of first seg matches: {}\n{:032b}\n{:032b}", + dig[0] == expected_output[0], + dig[0], + expected_output[0] + ); + assert_eq_hex!(dig, expected_output) } } } diff --git a/crates/algorithms/src/lib.rs b/crates/algorithms/src/lib.rs index 924196e..c16a2dd 100755 --- a/crates/algorithms/src/lib.rs +++ b/crates/algorithms/src/lib.rs @@ -7,7 +7,7 @@ //! Many thanks to the [crc-catalogue](https://reveng.sourceforge.io/crc-catalogue/) for providing //! an overview over the various algorithms with test vectors and examples. -#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(any(test, feature = "std")), no_std)] pub mod crc; pub mod hash;