Add sha256 hash and ordering.

This commit is contained in:
Michael Mikovsky
2026-05-31 13:22:02 -06:00
parent 0b11f8609e
commit b2e2523860
7 changed files with 234 additions and 67 deletions
+2 -4
View File
@@ -1,8 +1,6 @@
use unshell::hash;
macro_rules! hashtest { macro_rules! hashtest {
($input:tt) => { ($input:tt) => {
($input, hash($input)) ($input, unshell::hash_32!($input))
}; };
} }
@@ -17,6 +15,6 @@ const MAP: [(&str, u32); 6] = [
pub fn main() { pub fn main() {
for (a, b) in MAP { for (a, b) in MAP {
println!("unshell::hash(\"{}\") = {}", a, b) println!("unshell::hash_32!(\"{}\") = {}", a, b)
} }
} }
+137
View File
@@ -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
}
+54
View File
@@ -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]])
}
+35
View File
@@ -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)
}
-57
View File
@@ -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)
}
+2 -4
View File
@@ -11,14 +11,12 @@
//! The library requires `alloc` for path and payload management. //! The library requires `alloc` for path and payload management.
#![no_std] #![no_std]
#![feature(const_index)]
#![feature(const_trait_impl)]
pub extern crate alloc; pub extern crate alloc;
mod hash; pub mod crypto;
pub mod interface; pub mod interface;
pub mod logger; pub mod logger;
pub mod protocol; pub mod protocol;
pub use hash::hash; // pub use hash::hash;
+4 -2
View File
@@ -1,8 +1,10 @@
use unshell::hash_32;
/// Leaf id used by the generated fake PTY wrapper. /// 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. /// 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. /// Downward opcode that opens one PTY session.
pub const OP_OPEN: u8 = 0; pub const OP_OPEN: u8 = 0;