Files
unshell/src/crypto/hash.rs
T
2026-05-31 13:22:02 -06:00

138 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── 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
}