const ROUNDS: [u64; 24] = [
0x0000000000000001,
0x0000000000008082,
0x800000000000808a,
0x8000000080008000,
0x000000000000808b,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008a,
0x0000000000000088,
0x0000000080008009,
0x000000008000000a,
0x000000008000808b,
0x800000000000008b,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800a,
0x800000008000000a,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008,
];
const ROTATIONS: [u32; 24] = [
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
];
const PI_LANES: [usize; 24] = [
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
];
pub fn is_sha3_width(bits: u32) -> bool {
matches!(bits, 224 | 256 | 384 | 512)
}
pub struct Sha3 {
state: [u64; 25],
rate: usize,
pending: Vec<u8>,
output: usize,
}
impl Sha3 {
pub fn new(bits: u32) -> Sha3 {
let output = (bits as usize) / 8;
Sha3 {
state: [0u64; 25],
rate: 200usize.saturating_sub(output.saturating_mul(2)),
pending: Vec::new(),
output,
}
}
pub fn update(&mut self, data: &[u8]) {
self.pending.extend_from_slice(data);
let mut offset = 0usize;
while offset.saturating_add(self.rate) <= self.pending.len() {
let block: Vec<u8> = self
.pending
.get(offset..offset.saturating_add(self.rate))
.unwrap_or(&[])
.to_vec();
absorb(&mut self.state, &block);
keccak_f(&mut self.state);
offset = offset.saturating_add(self.rate);
}
self.pending.drain(..offset);
}
pub fn finish(mut self) -> Vec<u8> {
let mut last = vec![0u8; self.rate];
for (slot, byte) in last.iter_mut().zip(self.pending.iter()) {
*slot = *byte;
}
if let Some(slot) = last.get_mut(self.pending.len()) {
*slot = 0x06;
}
if let Some(slot) = last.get_mut(self.rate.saturating_sub(1)) {
*slot |= 0x80;
}
absorb(&mut self.state, &last);
keccak_f(&mut self.state);
let mut digest = Vec::with_capacity(self.output);
'squeeze: loop {
for index in 0..(self.rate / 8) {
let bytes = self.state.get(index).copied().unwrap_or(0).to_le_bytes();
for byte in bytes {
if digest.len() == self.output {
break 'squeeze;
}
digest.push(byte);
}
}
if digest.len() == self.output {
break;
}
keccak_f(&mut self.state);
}
digest
}
}
pub fn sha3(data: &[u8], bits: u32) -> Vec<u8> {
let mut sponge = Sha3::new(bits);
sponge.update(data);
sponge.finish()
}
pub fn sha3_256(data: &[u8]) -> [u8; 32] {
let digest = sha3(data, 256);
let mut out = [0u8; 32];
for (slot, byte) in out.iter_mut().zip(digest.iter()) {
*slot = *byte;
}
out
}
fn absorb(state: &mut [u64; 25], block: &[u8]) {
for (index, chunk) in block.chunks_exact(8).enumerate() {
let mut bytes = [0u8; 8];
bytes.copy_from_slice(chunk);
if let Some(lane) = state.get_mut(index) {
*lane ^= u64::from_le_bytes(bytes);
}
}
}
fn lane(state: &[u64; 25], index: usize) -> u64 {
state.get(index).copied().unwrap_or(0)
}
fn keccak_f(state: &mut [u64; 25]) {
for round in ROUNDS {
theta(state);
rho_and_pi(state);
chi(state);
if let Some(first) = state.get_mut(0) {
*first ^= round;
}
}
}
#[allow(clippy::arithmetic_side_effects)]
fn theta(state: &mut [u64; 25]) {
let mut parity = [0u64; 5];
for (column, slot) in parity.iter_mut().enumerate() {
*slot = lane(state, column)
^ lane(state, column + 5)
^ lane(state, column + 10)
^ lane(state, column + 15)
^ lane(state, column + 20);
}
for column in 0..5 {
let left = parity.get((column + 4) % 5).copied().unwrap_or(0);
let right = parity.get((column + 1) % 5).copied().unwrap_or(0);
let mixed = left ^ right.rotate_left(1);
for row in 0..5 {
if let Some(slot) = state.get_mut(row * 5 + column) {
*slot ^= mixed;
}
}
}
}
#[allow(clippy::arithmetic_side_effects)]
fn rho_and_pi(state: &mut [u64; 25]) {
let mut carried = lane(state, 1);
for step in 0..24 {
let target = PI_LANES.get(step).copied().unwrap_or(0);
let rotation = ROTATIONS.get(step).copied().unwrap_or(0);
let displaced = lane(state, target);
if let Some(slot) = state.get_mut(target) {
*slot = carried.rotate_left(rotation);
}
carried = displaced;
}
}
#[allow(clippy::arithmetic_side_effects)]
fn chi(state: &mut [u64; 25]) {
for row in 0..5 {
let base = row * 5;
let mut lanes = [0u64; 5];
for (column, slot) in lanes.iter_mut().enumerate() {
*slot = lane(state, base + column);
}
for column in 0..5 {
let next = lanes.get((column + 1) % 5).copied().unwrap_or(0);
let after = lanes.get((column + 2) % 5).copied().unwrap_or(0);
if let Some(slot) = state.get_mut(base + column) {
*slot ^= (!next) & after;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
#[test]
fn every_width_matches_its_published_vectors() {
assert_eq!(
hex(&sha3(b"", 224)),
"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7"
);
assert_eq!(
hex(&sha3(b"abc", 224)),
"e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
);
assert_eq!(
hex(&sha3(b"", 256)),
"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"
);
assert_eq!(
hex(&sha3(b"abc", 256)),
"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
);
assert_eq!(
hex(&sha3(b"", 384)),
"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004"
);
assert_eq!(
hex(&sha3(b"abc", 384)),
"ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
);
assert_eq!(
hex(&sha3(b"", 512)),
"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"
);
assert_eq!(
hex(&sha3(b"abc", 512)),
"b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
);
}
#[test]
fn a_message_of_exactly_one_block_pads_correctly() {
assert_eq!(
hex(&sha3(&[b'a'; 136], 256)),
"3fc5559f14db8e453a0a3091edbd2bc25e11528d81c66fa570a4efdcc2695ee1"
);
}
#[test]
fn feeding_in_pieces_is_the_same_as_feeding_it_whole() {
let whole = sha3(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
256,
);
let mut sponge = Sha3::new(256);
for piece in [
&b"abcdbcdecdefdefg"[..],
&b"efghfghighijhijk"[..],
&b"ijkljklmklmnlmno"[..],
&b"mnopnopq"[..],
] {
sponge.update(piece);
}
assert_eq!(sponge.finish(), whole);
}
#[test]
fn an_unpublished_width_is_not_one() {
assert!(is_sha3_width(224));
assert!(is_sha3_width(512));
assert!(!is_sha3_width(128));
assert!(!is_sha3_width(0));
}
}