Significant speed up to the hash.

It can now hash data at 9-10 GiB/s.
This commit is contained in:
Nathan Vegdahl 2022-08-25 15:53:29 -07:00
parent a41e1dd7de
commit 7d7ab1acf1

View File

@ -57,11 +57,16 @@ impl LedHash256 {
let mut data = data; let mut data = data;
while !data.is_empty() { while !data.is_empty() {
if self.buf_length >= BLOCK_SIZE { if self.buf_length == BLOCK_SIZE {
// Process the filled buffer // Process the filled buffer.
self.add_buffer_to_state(); add_buffer_to_state(&mut self.state, &self.buf);
self.mix_state(UPDATE_MIX_ROUNDS); mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
self.buf_length = 0; self.buf_length = 0;
} else if self.buf_length == 0 && data.len() >= BLOCK_SIZE {
// Process data directly, skipping the buffer.
add_buffer_to_state(&mut self.state, data);
mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
data = &data[BLOCK_SIZE..];
} else { } else {
// Fill the buffer. // Fill the buffer.
let n = (BLOCK_SIZE - self.buf_length).min(data.len()); let n = (BLOCK_SIZE - self.buf_length).min(data.len());
@ -77,14 +82,14 @@ impl LedHash256 {
// Hash the remaining bytes if there are any. // Hash the remaining bytes if there are any.
if self.buf_length > 0 { if self.buf_length > 0 {
(&mut self.buf[self.buf_length..]).fill(0); (&mut self.buf[self.buf_length..]).fill(0);
self.add_buffer_to_state(); add_buffer_to_state(&mut self.state, &self.buf);
self.mix_state(UPDATE_MIX_ROUNDS); mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
} }
// Incorporate the message length (in bits) and do the // Incorporate the message length (in bits) and do the
// final mixing. // final mixing.
self.state[0] ^= self.message_length * 8; self.state[0] ^= self.message_length * 8;
self.mix_state(FINISH_MIX_ROUNDS); mix_state(&mut self.state, FINISH_MIX_ROUNDS);
// Get the digest as a byte array and return it. // Get the digest as a byte array and return it.
let mut digest = [0u8; BLOCK_SIZE]; let mut digest = [0u8; BLOCK_SIZE];
@ -94,55 +99,60 @@ impl LedHash256 {
digest[24..32].copy_from_slice(&self.state[3].to_le_bytes()); digest[24..32].copy_from_slice(&self.state[3].to_le_bytes());
return digest; return digest;
} }
}
/// Adds the contents of a buffer to the hash state.
///
/// The buffer must be at least 32 bytes long. Only the first 32 bytes
/// are added.
#[inline(always)]
fn add_buffer_to_state(state: &mut [u64; 4], buffer: &[u8]) {
use std::convert::TryInto;
/// Adds the current contents of the buffer to the hash state.
fn add_buffer_to_state(&mut self) {
// Convert the buffer to native endian u64's and xor into the // Convert the buffer to native endian u64's and xor into the
// hash state. // hash state.
let (a, b, c) = unsafe { self.buf.align_to::<u64>() }; assert!(buffer.len() >= BLOCK_SIZE);
debug_assert!(a.is_empty()); state[0] ^= u64::from_le_bytes((&buffer[0..8]).try_into().unwrap());
debug_assert!(c.is_empty()); state[1] ^= u64::from_le_bytes((&buffer[8..16]).try_into().unwrap());
self.state[0] ^= u64::from_le(b[0]); state[2] ^= u64::from_le_bytes((&buffer[16..24]).try_into().unwrap());
self.state[1] ^= u64::from_le(b[1]); state[3] ^= u64::from_le_bytes((&buffer[24..32]).try_into().unwrap());
self.state[2] ^= u64::from_le(b[2]); }
self.state[3] ^= u64::from_le(b[3]);
}
/// The main mix function. Mixes the hash state. /// The main mix function. Mixes the passed hash state.
/// ///
/// Inspired by Skein 1.3, and using its MIX function. /// Inspired by Skein 1.3, and using its MIX function.
/// ///
/// The mix rotation constants are: /// The mix rotation constants are:
/// - 40 50 /// - 40 50
/// - 27 21 /// - 27 21
/// ///
/// They were selected by an exhaustive search of the four-constant /// They were selected by an exhaustive search of the four-constant
/// space, selecting for the best single-bit diffusion at a small /// space, selecting for the best single-bit diffusion at a small
/// number of rounds. /// number of rounds.
/// ///
/// The permute table is: /// The permute table is:
/// - Indices: 0 1 2 3 /// - Indices: 0 1 2 3
/// - Become: 0 1 3 2 /// - Become: 0 1 3 2
fn mix_state(&mut self, rounds: usize) { #[inline(always)]
fn mix_state(state: &mut [u64; 4], rounds: usize) {
for _ in 0..rounds { for _ in 0..rounds {
// Skein MIX function. // MIX function.
self.state[0] = self.state[0].wrapping_add(self.state[2]); state[0] = state[0].wrapping_add(state[2]);
self.state[1] = self.state[1].wrapping_add(self.state[3]); state[2] = state[2].rotate_left(40) ^ state[0];
self.state[2] = self.state[2].rotate_left(40) ^ self.state[0]; state[1] = state[1].wrapping_add(state[3]);
self.state[3] = self.state[3].rotate_left(50) ^ self.state[1]; state[3] = state[3].rotate_left(50) ^ state[1];
// Permute. // Permute.
self.state.swap(2, 3); state.swap(2, 3);
// Skein MIX function. // MIX function.
self.state[0] = self.state[0].wrapping_add(self.state[2]); state[0] = state[0].wrapping_add(state[2]);
self.state[1] = self.state[1].wrapping_add(self.state[3]); state[2] = state[2].rotate_left(27) ^ state[0];
self.state[2] = self.state[2].rotate_left(27) ^ self.state[0]; state[1] = state[1].wrapping_add(state[3]);
self.state[3] = self.state[3].rotate_left(21) ^ self.state[1]; state[3] = state[3].rotate_left(21) ^ state[1];
// Permute. // Permute.
self.state.swap(2, 3); state.swap(2, 3);
}
} }
} }