From b2e25238603c552be0fb9e275cd8659d84d9c2bb Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Sun, 31 May 2026 13:22:02 -0600 Subject: [PATCH] Add sha256 hash and ordering. --- examples/hashtest.rs | 6 +- src/crypto/hash.rs | 137 +++++++++++++++++++++++ src/crypto/mod.rs | 54 +++++++++ src/crypto/ordering.rs | 35 ++++++ src/hash.rs | 57 ---------- src/lib.rs | 6 +- unshell-leaves/leaf-pty/src/constants.rs | 6 +- 7 files changed, 234 insertions(+), 67 deletions(-) create mode 100644 src/crypto/hash.rs create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/ordering.rs delete mode 100644 src/hash.rs diff --git a/examples/hashtest.rs b/examples/hashtest.rs index 8520c21..25327d3 100644 --- a/examples/hashtest.rs +++ b/examples/hashtest.rs @@ -1,8 +1,6 @@ -use unshell::hash; - macro_rules! hashtest { ($input:tt) => { - ($input, hash($input)) + ($input, unshell::hash_32!($input)) }; } @@ -17,6 +15,6 @@ const MAP: [(&str, u32); 6] = [ pub fn main() { for (a, b) in MAP { - println!("unshell::hash(\"{}\") = {}", a, b) + println!("unshell::hash_32!(\"{}\") = {}", a, b) } } diff --git a/src/crypto/hash.rs b/src/crypto/hash.rs new file mode 100644 index 0000000..91562d0 --- /dev/null +++ b/src/crypto/hash.rs @@ -0,0 +1,137 @@ +// ── Round constants ────────────────────────────────────────────────────────── +const K: [u32; 64] = [ + 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, +]; + +// ── Initial hash values ────────────────────────────────────────────────────── +const H: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; +// ── Internals ──────────────────────────────────────────────────────────────── + +/// Returns what byte `pos` should hold in the padded SHA-256 message, +/// without ever materialising the full padded buffer. +const fn padded_byte(input: &[u8], pos: usize, padded_len: usize) -> u8 { + let bit_len = (input.len() as u64) * 8; + if pos < input.len() { + input[pos] + } else if pos == input.len() { + 0x80 + } else if pos >= padded_len - 8 { + // Big-endian 64-bit length: byte 0 is the most significant. + let byte_index = pos - (padded_len - 8); + (bit_len >> (56 - byte_index * 8)) as u8 + } else { + 0x00 + } +} + +/// SHA-256 compression: mixes one 64-byte block into the hash state. +const fn compress(state: &mut [u32; 8], block: &[u8; 64]) { + // Build the 64-word message schedule from the 16-word block. + let mut w = [0u32; 64]; + let mut i = 0; + while i < 16 { + w[i] = ((block[i * 4] as u32) << 24) + | ((block[i * 4 + 1] as u32) << 16) + | ((block[i * 4 + 2] as u32) << 8) + | (block[i * 4 + 3] as u32); + i += 1; + } + while i < 64 { + let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3); + let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + .wrapping_add(s0) + .wrapping_add(w[i - 7]) + .wrapping_add(s1); + i += 1; + } + + // Initialise working variables from current hash state. + let mut a = state[0]; + let mut b = state[1]; + let mut c = state[2]; + let mut d = state[3]; + let mut e = state[4]; + let mut f = state[5]; + let mut g = state[6]; + let mut h = state[7]; + + // 64 rounds. + i = 0; + while i < 64 { + let sigma1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); + let ch = (e & f) ^ ((!e) & g); + let temp1 = h + .wrapping_add(sigma1) + .wrapping_add(ch) + .wrapping_add(K[i]) + .wrapping_add(w[i]); + + let sigma0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); + let maj = (a & b) ^ (a & c) ^ (b & c); + let temp2 = sigma0.wrapping_add(maj); + + h = g; + g = f; + f = e; + e = d.wrapping_add(temp1); + d = c; + c = b; + b = a; + a = temp1.wrapping_add(temp2); + i += 1; + } + + // Add the compressed chunk back into the hash state. + state[0] = state[0].wrapping_add(a); + state[1] = state[1].wrapping_add(b); + state[2] = state[2].wrapping_add(c); + state[3] = state[3].wrapping_add(d); + state[4] = state[4].wrapping_add(e); + state[5] = state[5].wrapping_add(f); + state[6] = state[6].wrapping_add(g); + state[7] = state[7].wrapping_add(h); +} + +// ── Public API ─────────────────────────────────────────────────────────────── + +/// Returns the SHA-256 digest of `input` as 32 raw bytes. +pub const fn sha256(input: &[u8]) -> [u8; 32] { + // Padded length is the next multiple of 64 that fits input + 1 (0x80) + 8 (length). + let padded_len = ((input.len() + 9 + 63) / 64) * 64; + let mut state = H; + let mut block_start = 0; + + while block_start < padded_len { + // Assemble the current 64-byte block using the virtual padded view. + let mut block = [0u8; 64]; + let mut j = 0; + while j < 64 { + block[j] = padded_byte(input, block_start + j, padded_len); + j += 1; + } + compress(&mut state, &block); + block_start += 64; + } + + // Serialise the 8×u32 state as big-endian bytes. + let mut out = [0u8; 32]; + let mut i = 0; + while i < 8 { + out[i * 4] = (state[i] >> 24) as u8; + out[i * 4 + 1] = (state[i] >> 16) as u8; + out[i * 4 + 2] = (state[i] >> 8) as u8; + out[i * 4 + 3] = state[i] as u8; + i += 1; + } + out +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..ebb81c4 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,54 @@ +use alloc::string::String; + +mod hash; +mod ordering; + +pub use hash::sha256; +pub use ordering::feistel_shuffle; + +#[macro_export] +macro_rules! hash_256 { + ($s:literal) => {{ + // string literal arm + const HASH: [u8; 32] = $crate::crypto::sha256($s.as_bytes()); + HASH + }}; + ($n:expr) => {{ + // integer/expression arm + const BYTES: [u8; 8] = ($n as u64).to_be_bytes(); + const HASH: [u8; 32] = $crate::crypto::sha256(&BYTES); + HASH + }}; +} + +#[macro_export] +macro_rules! hash_32 { + ($s:literal) => {{ + // string literal arm + const HASH: [u8; 32] = $crate::crypto::sha256($s.as_bytes()); + const RESULT: u32 = u32::from_be_bytes([HASH[0], HASH[8], HASH[16], HASH[24]]); + RESULT + }}; + ($n:expr) => {{ + // integer/expression arm + const BYTES: [u8; 8] = ($n as u64).to_be_bytes(); + const HASH: [u8; 32] = $crate::crypto::sha256(&BYTES); + const RESULT: u32 = u32::from_be_bytes([HASH[0], HASH[8], HASH[16], HASH[24]]); + RESULT + }}; +} + +pub fn hash_string_32(input: String) -> u32 { + let hash: [u8; 32] = sha256(input.as_bytes()); + u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]]) +} + +pub fn hash_str_32(input: &str) -> u32 { + let hash: [u8; 32] = sha256(input.as_bytes()); + u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]]) +} + +pub fn hash_32(input: u32) -> u32 { + let hash: [u8; 32] = sha256(&input.to_be_bytes()); + u32::from_be_bytes([hash[0], hash[8], hash[16], hash[24]]) +} diff --git a/src/crypto/ordering.rs b/src/crypto/ordering.rs new file mode 100644 index 0000000..28c3b74 --- /dev/null +++ b/src/crypto/ordering.rs @@ -0,0 +1,35 @@ +/// Performs a deterministic pseudo-random shuffle of a 16-bit index. +/// +/// # Arguments +/// * `index` - The input value (0..65536). +/// * `seed` - The 32-bit seed acting as the encryption key. +/// +/// # Returns +/// A unique 16-bit shuffled value. +pub fn feistel_shuffle(index: u16, seed: u32) -> u16 { + // Split 16-bit index into two 8-bit halves + let mut l = ((index >> 8) & 0xFF) as u8; + let mut r = (index & 0xFF) as u8; + + // Perform 4 rounds of Feistel mixing + for round in 0..4 { + // Derive sub-key: Rotate seed and add golden ratio constant + let rot_amount = (round * 5) % 32; + let sub_key = seed + .rotate_left(rot_amount) + .wrapping_add(round.wrapping_mul(0x9E3779B9)); + + // Round function F: Simple multiplicative hash mixing R and sub_key + // We cast to u32 for multiplication to avoid overflow, then mask back to 8 bits + let r_u32 = r as u32; + let hash_val = ((r_u32.wrapping_mul(sub_key)) ^ (r_u32 >> 4)) as u8 & 0xFF; + + // Feistel step: New L = Old R, New R = Old L XOR F(R, key) + let temp = l; + l = r; + r = temp ^ hash_val; + } + + // Recombine halves + ((l as u16) << 8) | (r as u16) +} diff --git a/src/hash.rs b/src/hash.rs deleted file mode 100644 index a7b6d85..0000000 --- a/src/hash.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Temporary hash function - -const fn hash_recursive(state: &mut [u8; 4], input: &[u8]) { - match input.len() { - 3 => { - state[0] ^= input[0]; - state[1] ^= input[1]; - state[2] ^= input[2]; - } - 2 => { - state[0] ^= input[0]; - state[1] ^= input[1]; - } - 1 => { - state[0] ^= input[0]; - } - 0 => {} - _ => { - state[0] ^= input[0]; - state[1] ^= input[1]; - state[2] ^= input[2]; - state[3] ^= input[3]; - - // Mess with the state quite a bit - state[0] = u8::reverse_bits(state[0]) ^ state[2]; - state[2] = state[0].wrapping_add(state[2]).wrapping_add(state[3]) ^ state[0]; - state[3] = state[2].wrapping_add(state[3] << 2) ^ state[1]; - state[1] = state[3] ^ 0xa3; - - hash_recursive(state, &input[1..]); - } - } -} - -pub const fn hash(input: &'static str) -> u32 { - let mut data = [0xDE, 0xED, 0xBE, 0xEF]; - hash_recursive(&mut data, input.as_bytes()); - - // throw the data back into itself because why not - let input2 = [ - u8::reverse_bits(data[1]), - data[2], - data[2], - data[1], - u8::reverse_bits(data[0]), - data[2], - u8::reverse_bits(data[3]), - u8::reverse_bits(data[2]), - data[3], - u8::reverse_bits(data[3]), - u8::reverse_bits(data[2]), - data[0], - ]; - hash_recursive(&mut data, &input2); - - u32::from_be_bytes(data) -} diff --git a/src/lib.rs b/src/lib.rs index 0d4f202..42bc584 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,14 +11,12 @@ //! The library requires `alloc` for path and payload management. #![no_std] -#![feature(const_index)] -#![feature(const_trait_impl)] pub extern crate alloc; -mod hash; +pub mod crypto; pub mod interface; pub mod logger; pub mod protocol; -pub use hash::hash; +// pub use hash::hash; diff --git a/unshell-leaves/leaf-pty/src/constants.rs b/unshell-leaves/leaf-pty/src/constants.rs index 4341c4d..cfce7fd 100644 --- a/unshell-leaves/leaf-pty/src/constants.rs +++ b/unshell-leaves/leaf-pty/src/constants.rs @@ -1,8 +1,10 @@ +use unshell::hash_32; + /// Leaf id used by the generated fake PTY wrapper. -pub const LEAF_FAKE_PTY: u32 = unshell::hash("dev.unshell.v1.pty"); +pub const LEAF_FAKE_PTY: u32 = hash_32!("dev.unshell.v1.pty"); /// Outer procedure id used by all fake PTY session packets. -pub const PROC_PTY: u32 = unshell::hash("dev.unshell.v1.pty.pty"); +pub const PROC_PTY: u32 = hash_32!("dev.unshell.v1.pty.pty"); /// Downward opcode that opens one PTY session. pub const OP_OPEN: u8 = 0;