diff --git a/Cargo.toml b/Cargo.toml index 8eb15389d..ab0c8fbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "sha1", "sha2", "sha3", + "skein", "streebog", "whirlpool", ] diff --git a/skein/Cargo.toml b/skein/Cargo.toml new file mode 100644 index 000000000..e54e67481 --- /dev/null +++ b/skein/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "skein" +version = "0.3.0" +authors = ["Kaz Wesley "] +license = "MIT/Apache-2.0" +description = "Skein hash functions" +repository = "https://github.com/RustCrypto/hashes" +keywords = ["crypto", "skein", "hash", "digest"] +categories = ["cryptography", "no-std"] + +[dependencies] +block-buffer = "0.7" +block-padding = "0.1.0" +digest = "0.8" +threefish = { git = "git://github.com/kazcw/RustCrypto-block-ciphers", branch = "threefish", version = "0.3.0" } + +[dev-dependencies] +digest = { version = "0.8", features = ["dev"] } diff --git a/skein/src/lib.rs b/skein/src/lib.rs new file mode 100644 index 000000000..04410b8a5 --- /dev/null +++ b/skein/src/lib.rs @@ -0,0 +1,224 @@ +// copyright 2017 Kaz Wesley + +#![no_std] + +extern crate block_buffer; +extern crate block_padding; +extern crate threefish; +pub extern crate digest; + +pub use digest::generic_array::GenericArray; +pub use digest::Digest; + +use block_buffer::BlockBuffer; +use block_buffer::byteorder::{ByteOrder, LE}; +use block_padding::ZeroPadding; +use digest::generic_array::typenum::{NonZero, PartialDiv, Unsigned, U128, U32, U64, U8}; +use digest::generic_array::ArrayLength; +use threefish::{BlockCipher, Threefish1024, Threefish256, Threefish512}; + +/// N word buffer. +#[derive(Copy, Clone)] +union Block +where + N: ArrayLength, + N: PartialDiv, + >::Output: ArrayLength, + N::ArrayType: Copy, + <>::Output as ArrayLength>::ArrayType: Copy, +{ + bytes: GenericArray, + words: GenericArray>::Output>, +} + +impl Block +where + N: ArrayLength, + N: PartialDiv, + >::Output: ArrayLength, + N::ArrayType: Copy, + <>::Output as ArrayLength>::ArrayType: Copy, +{ + fn bytes(&mut self) -> &[u8] { + self.as_byte_array().as_slice() + } + + fn as_byte_array(&self) -> &GenericArray { + unsafe { &self.bytes } + } + + fn as_byte_array_mut(&mut self) -> &mut GenericArray { + unsafe { &mut self.bytes } + } + + fn from_byte_array(block: &GenericArray) -> Self { + Block { bytes: *block } + } +} + +impl Default for Block +where + N: ArrayLength, + N: PartialDiv, + >::Output: ArrayLength, + N::ArrayType: Copy, + <>::Output as ArrayLength>::ArrayType: Copy, +{ + fn default() -> Self { + Block { words: GenericArray::default() } + } +} + +impl core::ops::BitXor> for Block +where + N: ArrayLength, + N: PartialDiv, + >::Output: ArrayLength, + N::ArrayType: Copy, + <>::Output as ArrayLength>::ArrayType: Copy, +{ + type Output = Block; + fn bitxor(mut self, rhs: Block) -> Self::Output { + // XOR is endian-agnostic + for (s, r) in unsafe { &mut self.words }.iter_mut().zip(unsafe { &rhs.words }) { + *s ^= *r; + } + self + } +} + +#[derive(Clone)] +struct State { + t: (u64, u64), + x: X, +} + +impl State { + fn new(t1: u64, x: X) -> Self { + let t = (0, t1); + State { t, x } + } +} + +impl core::fmt::Debug for State { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.debug_struct("State") + .field("t", &"(unknown)") + .field("x", &"(unknown)") + .finish() + } +} + +const VERSION: u64 = 1; +const ID_STRING_LE: u64 = 0x33414853; +const SCHEMA_VER: u64 = (VERSION << 32) | ID_STRING_LE; +const CFG_TREE_INFO_SEQUENTIAL: u64 = 0; +const T1_FLAG_FIRST: u64 = 1 << 62; +const T1_FLAG_FINAL: u64 = 1 << 63; +const T1_BLK_TYPE_CFG: u64 = 4 << 56; +const T1_BLK_TYPE_MSG: u64 = 48 << 56; +const T1_BLK_TYPE_OUT: u64 = 63 << 56; +const CFG_STR_LEN: usize = 4 * 8; + +macro_rules! define_hasher { + ($name:ident, $threefish:ident, $state_bytes:ty, $state_bits:expr) => { + #[derive(Clone)] + pub struct $name+NonZero+Default> { + state: State>, + buffer: BlockBuffer<$state_bytes>, + _output: core::marker::PhantomData> + } + + impl core::fmt::Debug for $name where N: Unsigned+ArrayLength+NonZero+Default { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + f.debug_struct("Skein") + .field("state", &self.state) + .field("buffer.position()", &self.buffer.position()) + .finish() + } + } + + impl $name where N: Unsigned+ArrayLength+NonZero+Default { + fn process_block(state: &mut State>, + block: &GenericArray, byte_count_add: usize) { + let block = Block::from_byte_array(block); + state.t.0 += byte_count_add as u64; + let fish = $threefish::with_tweak(state.x.as_byte_array(), state.t.0, state.t.1); + let mut x = block.clone(); + fish.encrypt_block(x.as_byte_array_mut()); + state.x = x ^ block; + state.t.1 &= !T1_FLAG_FIRST; + } + } + + impl Default for $name where N: Unsigned+ArrayLength+NonZero+Default { + fn default() -> Self { + // build and process config block + let mut state = State::new(T1_FLAG_FIRST | T1_BLK_TYPE_CFG | T1_FLAG_FINAL, Block::default()); + let mut cfg = GenericArray::::default(); + LE::write_u64(&mut cfg[..8], SCHEMA_VER); + LE::write_u64(&mut cfg[8..16], N::to_u64() * 8); + LE::write_u64(&mut cfg[16..24], CFG_TREE_INFO_SEQUENTIAL); + Self::process_block(&mut state, &cfg, CFG_STR_LEN); + + // The chaining vars ctx->X are now initialized for the given hashBitLen. + // Set up to process the data message portion of the hash (default) + state.t = Default::default(); + state.t.1 = T1_FLAG_FIRST | T1_BLK_TYPE_MSG; + Self { + state, + buffer: Default::default(), + _output: Default::default() + } + } + } + + impl digest::BlockInput for $name where N: Unsigned+ArrayLength+NonZero+Default { + type BlockSize = <$threefish as BlockCipher>::BlockSize; + } + + impl digest::Input for $name where N: Unsigned+ArrayLength+NonZero+Default { + fn input>(&mut self, data: T) { + let buffer = &mut self.buffer; + let state = &mut self.state; + buffer.input_lazy(data.as_ref(), |block| Self::process_block(state, block, $state_bits/8)); + } + } + + impl digest::FixedOutput for $name where N: Unsigned+ArrayLength+NonZero+Default { + type OutputSize = N; + + fn fixed_result(mut self) -> GenericArray { + self.state.t.1 |= T1_FLAG_FINAL; + let pos = self.buffer.position(); + let final_block = self.buffer.pad_with::().unwrap(); + Self::process_block(&mut self.state, final_block, pos); + + // run Threefish in "counter mode" to generate output + let mut output = GenericArray::default(); + for (i, chunk) in output.chunks_mut($state_bits / 8).enumerate() { + let mut ctr = State::new(T1_FLAG_FIRST | T1_BLK_TYPE_OUT | T1_FLAG_FINAL, self.state.x); + let mut b = GenericArray::::default(); + LE::write_u64(&mut b[..8], i as u64); + Self::process_block(&mut ctr, &b, 8); + let n = chunk.len(); + chunk.copy_from_slice(&ctr.x.bytes()[..n]); + } + output + } + } + + impl digest::Reset for $name where N: Unsigned+ArrayLength+NonZero+Default { + fn reset(&mut self) { + *self = Self::default(); + } + } + } +} + +#[cfg_attr(rustfmt, skip)] +define_hasher!(Skein256, Threefish256, U32, 256); +#[cfg_attr(rustfmt, skip)] +define_hasher!(Skein512, Threefish512, U64, 512); +#[cfg_attr(rustfmt, skip)] +define_hasher!(Skein1024, Threefish1024, U128, 1024); diff --git a/skein/tests/data/skein1024_32.blb b/skein/tests/data/skein1024_32.blb new file mode 100644 index 000000000..3b750ac6a Binary files /dev/null and b/skein/tests/data/skein1024_32.blb differ diff --git a/skein/tests/data/skein1024_64.blb b/skein/tests/data/skein1024_64.blb new file mode 100644 index 000000000..20b79238f Binary files /dev/null and b/skein/tests/data/skein1024_64.blb differ diff --git a/skein/tests/data/skein256_32.blb b/skein/tests/data/skein256_32.blb new file mode 100644 index 000000000..13a6f6b38 Binary files /dev/null and b/skein/tests/data/skein256_32.blb differ diff --git a/skein/tests/data/skein256_64.blb b/skein/tests/data/skein256_64.blb new file mode 100644 index 000000000..7f05f1268 Binary files /dev/null and b/skein/tests/data/skein256_64.blb differ diff --git a/skein/tests/data/skein512_32.blb b/skein/tests/data/skein512_32.blb new file mode 100644 index 000000000..972253349 Binary files /dev/null and b/skein/tests/data/skein512_32.blb differ diff --git a/skein/tests/data/skein512_64.blb b/skein/tests/data/skein512_64.blb new file mode 100644 index 000000000..58e7057ee Binary files /dev/null and b/skein/tests/data/skein512_64.blb differ diff --git a/skein/tests/lib.rs b/skein/tests/lib.rs new file mode 100644 index 000000000..c9c249ba3 --- /dev/null +++ b/skein/tests/lib.rs @@ -0,0 +1,14 @@ +#![no_std] +extern crate skein; +#[macro_use] +extern crate digest; + +use digest::dev::digest_test; +use digest::generic_array::typenum::{U32, U64}; + +new_test!(skein256_32, "skein256_32", skein::Skein256, digest_test); +new_test!(skein512_32, "skein512_32", skein::Skein512, digest_test); +new_test!(skein1024_32, "skein1024_32", skein::Skein1024, digest_test); +new_test!(skein256_64, "skein256_64", skein::Skein256, digest_test); +new_test!(skein512_64, "skein512_64", skein::Skein512, digest_test); +new_test!(skein1024_64, "skein1024_64", skein::Skein1024, digest_test);