pub fn siphash_expand(bits: usize, c: usize, d: usize, key: &[u8; 16], msg: &[u8]) -> Vec<u8> {
assert!(
bits > 0 && bits % 64 == 0,
"bits must be a positive multiple of 64"
);
let blocks = bits / 64;
let output_len = blocks * 8;
let mut output = Vec::with_capacity(output_len);
let mut input = Vec::with_capacity(msg.len() + 8);
input.extend_from_slice(msg);
for i in 0..blocks {
input.clear();
input.extend_from_slice(msg);
input.extend_from_slice(&(i as u64).to_le_bytes());
let hash = siphash_cd(c, d, key, &input);
output.extend_from_slice(&hash.to_le_bytes());
}
output
}
fn siphash_cd(c: usize, d: usize, key: &[u8; 16], msg: &[u8]) -> u64 {
let k0 = u64::from_le_bytes(key[0..8].try_into().unwrap());
let k1 = u64::from_le_bytes(key[8..16].try_into().unwrap());
let mut v0 = k0 ^ 0x736f6d6570736575;
let mut v1 = k1 ^ 0x646f72616e646f6d;
let mut v2 = k0 ^ 0x6c7967656e657261;
let mut v3 = k1 ^ 0x7465646279746573;
let len = msg.len();
let full_blocks = len / 8;
for i in 0..full_blocks {
let m = u64::from_le_bytes(msg[i * 8..(i + 1) * 8].try_into().unwrap());
v3 ^= m;
for _ in 0..c {
sip_round(&mut v0, &mut v1, &mut v2, &mut v3);
}
v0 ^= m;
}
let remainder = len % 8;
let mut last_word = (len as u64 % 256) << 56;
for j in 0..remainder {
last_word |= (msg[full_blocks * 8 + j] as u64) << (j * 8);
}
v3 ^= last_word;
for _ in 0..c {
sip_round(&mut v0, &mut v1, &mut v2, &mut v3);
}
v0 ^= last_word;
v2 ^= 0xff;
for _ in 0..d {
sip_round(&mut v0, &mut v1, &mut v2, &mut v3);
}
v0 ^ v1 ^ v2 ^ v3
}
#[inline(always)]
fn sip_round(v0: &mut u64, v1: &mut u64, v2: &mut u64, v3: &mut u64) {
*v0 = v0.wrapping_add(*v1);
*v1 = v1.rotate_left(13);
*v1 ^= *v0;
*v0 = v0.rotate_left(32);
*v2 = v2.wrapping_add(*v3);
*v3 = v3.rotate_left(16);
*v3 ^= *v2;
*v0 = v0.wrapping_add(*v3);
*v3 = v3.rotate_left(21);
*v3 ^= *v0;
*v2 = v2.wrapping_add(*v1);
*v1 = v1.rotate_left(17);
*v1 ^= *v2;
*v2 = v2.rotate_left(32);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_output() {
let key = [0u8; 16];
let msg = b"test message";
let a = siphash_expand(256, 8, 16, &key, msg);
let b = siphash_expand(256, 8, 16, &key, msg);
assert_eq!(a, b);
assert_eq!(a.len(), 32);
}
#[test]
fn different_keys_different_output() {
let key1 = [0u8; 16];
let key2 = [1u8; 16];
let msg = b"same message";
let a = siphash_expand(256, 8, 16, &key1, msg);
let b = siphash_expand(256, 8, 16, &key2, msg);
assert_ne!(a, b);
}
#[test]
fn output_length_matches_bits() {
let key = [42u8; 16];
let msg = b"foo";
assert_eq!(siphash_expand(64, 2, 4, &key, msg).len(), 8);
assert_eq!(siphash_expand(128, 4, 8, &key, msg).len(), 16);
assert_eq!(siphash_expand(512, 8, 16, &key, msg).len(), 64);
}
}