From f8965810ec2429d9d3a178ec66062148bc99e527 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Wed, 20 Sep 2023 20:17:50 -0600 Subject: [PATCH] Initial commit + Seems to be working, need to add more edge cases + Focus on performance profiling too + clippy + fmt --- .gitignore | 3 + Cargo.toml | 12 ++++ src/lib.rs | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d84f13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ceb9ee3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoll_sha" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.dev] +overflow-checks = false + +[dependencies] + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0b341c8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,191 @@ +const SHA1_CONST: &[u32] = &[0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; + +pub struct SHA1Hash { + len: usize, + data_len: usize, + chunk: [u8; 64], + state: [u32; 5], +} + +impl Default for SHA1Hash { + fn default() -> Self { + let mut starting_state = [0u32; 5]; + + starting_state.copy_from_slice(SHA1_CONST); + + Self { + len: 0, + data_len: 0, + chunk: [0; 64], + state: starting_state, + } + } +} + +impl SHA1Hash { + pub fn new() -> Self { + Default::default() + } + + fn process_chunk(&mut self) { + let mut buffer = [0u32; 80]; + + let mut buffer_ndx = 0; + for chunk_ndx in (0..self.chunk.len()).step_by(4) { + let mut word = [0u8; 4]; + word.copy_from_slice(&self.chunk[chunk_ndx..chunk_ndx + 4]); + buffer[buffer_ndx] = u32::from_be_bytes(word); + buffer_ndx += 1; + } + + for ndx in buffer_ndx..80 { + buffer[ndx] = (buffer[ndx - 3] ^ buffer[ndx - 8] ^ buffer[ndx - 14] ^ buffer[ndx - 16]) + .rotate_left(1); + } + + let mut a = self.state[0]; + let mut b = self.state[1]; + let mut c = self.state[2]; + let mut d = self.state[3]; + let mut e = self.state[4]; + + let mut f; + let mut k; + + for (i, word) in buffer.iter().enumerate() { + match i { + 0..=19 => { + f = d ^ (b & (c ^ d)); + k = 0x5A827999; + } + 20..=39 => { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + 40..=59 => { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + 60..=79 => { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + _ => unreachable!("Loop range is 0 to 79"), + } + + let temp = a.rotate_left(5) + f + e + k + word; + e = d; + d = c; + c = b.rotate_left(30); + b = a; + a = temp; + } + + self.state[0] += a; + self.state[1] += b; + self.state[2] += c; + self.state[3] += d; + self.state[4] += e; + } + + pub fn finalize(&mut self) -> [u8; 20] { + self.len += self.data_len; + self.chunk[self.data_len] = 0x80; + self.data_len += 1; + + if self.data_len < 56 { + self.data_len = 56; + } + + let bit_len = self.len * 8; + self.chunk[self.data_len..self.data_len + 8].copy_from_slice(&bit_len.to_be_bytes()); + + self.process_chunk(); + + let mut output = [0u8; 20]; + + output[0..4].copy_from_slice(&self.state[0].to_be_bytes()); + output[4..8].copy_from_slice(&self.state[1].to_be_bytes()); + output[8..12].copy_from_slice(&self.state[2].to_be_bytes()); + output[12..16].copy_from_slice(&self.state[3].to_be_bytes()); + output[16..20].copy_from_slice(&self.state[4].to_be_bytes()); + + output + } + + pub fn digest(&mut self, data: &[u8]) { + let mut data_ndx = 0usize; + + while data_ndx < data.len() { + let data_needed = 64 - self.data_len; + let data_left = data.len() - data_ndx; + + let data_to_read = data_needed.clamp(0, data_left); + + let data_end = (data_to_read + data_ndx).clamp(0, data.len()); + + self.chunk[self.data_len..self.data_len + data_to_read] + .copy_from_slice(&data[data_ndx..data_end]); + self.data_len += data_to_read; + + if self.data_len == 64 { + self.process_chunk(); + self.data_len = 0; + self.len += 64; + self.chunk.fill(0); + } + + data_ndx += data_to_read; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn verify_single_block() { + let mut sha1 = SHA1Hash::new(); + + let msg = "The quick brown fox jumps over the lazy dog\n"; + + sha1.digest(msg.as_bytes()); + + let output = sha1.finalize(); + + let expected_hash = + b"\xbe\x41\x77\x68\xb5\xc3\xc5\xc1\xd9\xbc\xb2\xe7\xc1\x19\x19\x6d\xd7\x6b\x55\x70"; + assert_eq!(&output, expected_hash); + } + + #[test] + fn verify_multi_blocks() { + let mut sha1 = SHA1Hash::new(); + + let msg = "OOPSIE WOOPSIE!! Uwu We make a fucky wucky!! A wittle fucko boingo! The code monkeys at our headquarters are working VEWY HAWD to fix this!\n"; + + sha1.digest(msg.as_bytes()); + + let output = sha1.finalize(); + + let expected_hash = + b"\x14\x09\x49\x37\xfb\x7c\x3c\x30\x02\xda\x1a\x8d\x30\x84\x7f\x1d\xc9\x2b\x36\x0b"; + assert_eq!(&output, expected_hash); + } + + #[test] + fn verify_64_bytes() { + let mut sha1 = SHA1Hash::new(); + + let msg = "a".repeat(64); + + sha1.digest(msg.as_bytes()); + + let output = sha1.finalize(); + + let expected_hash = + b"\x00\x98\xba\x82\x4b\x5c\x16\x42\x7b\xd7\xa1\x12\x2a\x5a\x44\x2a\x25\xec\x64\x4d"; + assert_eq!(&output, expected_hash); + } +}