diff --git a/services/engine-sha512/src/api.rs b/services/engine-sha512/src/api.rs index 4c5473c30..a66ca008b 100644 --- a/services/engine-sha512/src/api.rs +++ b/services/engine-sha512/src/api.rs @@ -73,4 +73,11 @@ pub(crate) enum SusResOps { SuspendResume, /// exit the thread Quit, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FallbackStrategy { + HardwareThenSoftware, + WaitForHardware, + SoftwareOnly, } \ No newline at end of file diff --git a/services/engine-sha512/src/lib.rs b/services/engine-sha512/src/lib.rs index 847ec2ebf..5840d66fe 100644 --- a/services/engine-sha512/src/lib.rs +++ b/services/engine-sha512/src/lib.rs @@ -1,575 +1,12 @@ #![cfg_attr(target_os = "none", no_std)] -pub mod api; -use api::*; -use xous::{send_message, Message}; -use num_traits::ToPrimitive; -use xous_ipc::Buffer; - -use core::sync::atomic::{AtomicU32, Ordering}; -/// we have to make the HW_CONN static because the Digest crate assumes you can clone objects -/// and recycle them. However, it's not a problem for every server to have a unique connection -/// to the hasher service, if that's what it comes down to. The burden for tracking connections is on the -/// connector's side, not on the server's side; so when the connecting process that calls this -/// library dies, this static data dies with it. -static HW_CONN: AtomicU32 = AtomicU32::new(0); -/// a unique-enough random ID number to prove we own our connection to the hashing engine hardware -static TOKEN: [AtomicU32; 3] = [AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0)]; - -use digest::{BlockInput, FixedOutputDirty, Reset, Update}; -use digest::generic_array::GenericArray; -use digest::consts::{U28, U32, U48, U64, U128}; -use block_buffer::BlockBuffer; -use core::slice::from_ref; -type BlockSize = U128; - -mod soft; +mod api; +pub use api::*; mod consts; -use soft::*; -use consts::*; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum FallbackStrategy { - HardwareThenSoftware, - WaitForHardware, - SoftwareOnly, -} +mod sha256; +mod sha512; -/* - Software emulation vendored from https://github.com/RustCrypto/hashes/tree/master/sha2/src - License is Apache 2.0 - */ -/// Structure that keeps state of the software-emulated Sha-512 operation and -/// contains the logic necessary to perform the final calculations. -#[derive(Clone)] -struct Engine512 { - len: u128, - buffer: BlockBuffer, - state: [u64; 8], -} - -impl Engine512 { - fn new(h: &[u64; STATE_LEN]) -> Engine512 { - Engine512 { - len: 0, - buffer: Default::default(), - state: *h, - } - } - - fn update(&mut self, input: &[u8]) { - self.len += (input.len() as u128) << 3; - let s = &mut self.state; - self.buffer.input_blocks(input, |b| compress512(s, b)); - } - - fn finish(&mut self) { - let s = &mut self.state; - self.buffer - .len128_padding_be(self.len, |d| compress512(s, from_ref(d))); - } - - fn reset(&mut self, h: &[u64; STATE_LEN]) { - self.len = 0; - self.buffer.reset(); - self.state = *h; - } -} - -// a macro for common communications libraries for SHA2 hardware interfacing -// you can't reference fields in a trait. looks like a macro is the accepted way -// of not having to repeat this code over and over again. -macro_rules! sha512_comms { - () => { - pub(crate) fn ensure_conn(&self) -> u32 { - if HW_CONN.load(Ordering::Relaxed) == 0 { - let xns = xous_names::XousNames::new().unwrap(); - HW_CONN.store(xns.request_connection_blocking(api::SERVER_NAME_SHA512).expect("Can't connect to Sha512 server"), Ordering::Relaxed); - let trng = trng::Trng::new(&xns).expect("Can't connect to TRNG server"); - let id1 = trng.get_u64().unwrap(); - let id2 = trng.get_u32().unwrap(); - TOKEN[0].store((id1 >> 32) as u32, Ordering::Relaxed); - TOKEN[1].store(id1 as u32, Ordering::Relaxed); - TOKEN[2].store(id2, Ordering::Relaxed); - } - HW_CONN.load(Ordering::Relaxed) - } - pub fn is_idle(&self) -> Result { - let response = send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::IsIdle.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("Couldn't make IsIdle query"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - Ok(true) - } else { - Ok(false) - } - } else { - Err(xous::Error::InternalError) - } - } - pub fn acquire_suspend_lock(&self) -> Result { - let response = send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::AcquireSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) - ).expect("Couldn't issue AcquireSuspendLock message"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - Ok(true) - } else { - Ok(false) - } - } else { - Err(xous::Error::InternalError) - } - } - pub fn abort_suspend(&self) -> Result<(), xous::Error> { - // we ignore the result and just turn it into () once we get anything back, as abort_suspend "can't fail" - send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::AbortSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) - ).map(|_| ()) - } - pub(crate) fn try_acquire_hw(&mut self, config: Sha2Config) { - if !self.in_progress && (self.strategy != FallbackStrategy::SoftwareOnly) { - loop { - let conn = self.ensure_conn(); // also ensures the ID - let response = send_message(conn, - Message::new_blocking_scalar(Opcode::AcquireExclusive.to_usize().unwrap(), - TOKEN[0].load(Ordering::Relaxed) as usize, - TOKEN[1].load(Ordering::Relaxed) as usize, - TOKEN[2].load(Ordering::Relaxed) as usize, - config.to_usize().unwrap(), - ) - ).expect("couldn't send AcquireExclusive message to Sha2 hardware!"); - if let xous::Result::Scalar1(result) = response { - if result != 0 { - self.use_soft = false; - self.in_progress = true; - break; - } else { - if self.strategy == FallbackStrategy::HardwareThenSoftware { - self.use_soft = true; - self.in_progress = true; - break; - } else { - // this is hardware-exclusive mode, we block until we can get the hardware - xous::yield_slice(); - } - } - } else { - log::error!("AcquireExclusive had an unexpected error: {:?}", response); - panic!("Internal error in AcquireExclusive"); - } - } - } else if self.strategy == FallbackStrategy::SoftwareOnly { - self.use_soft = true; - self.in_progress = true; - } - } - pub(crate) fn reset_hw(&mut self) { - send_message(self.ensure_conn(), - Message::new_blocking_scalar(Opcode::Reset.to_usize().unwrap(), - TOKEN[0].load(Ordering::Relaxed) as usize, - TOKEN[1].load(Ordering::Relaxed) as usize, - TOKEN[2].load(Ordering::Relaxed) as usize, - 0, - )).expect("couldn't send reset to hardware"); - // reset internal flags - self.length = 0; - self.in_progress = false; - self.use_soft = true; - } - }; -} - -/// The SHA-512 hash algorithm with the SHA-512 initial hash value. -#[derive(Clone)] -pub struct Sha512 { - /// software fallback engine - engine: Engine512, - /// whether or not this current hasher instance will use software or hardware acceleration - use_soft: bool, - /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously - strategy: FallbackStrategy, - /// track if a hash is in progress - in_progress: bool, - /// track the length of the message processed so far - length: u64, -} -impl Sha512 { - // use this function instead of default for more control over configuration of the hardware engine - pub fn new(maybe_strategy: Option) -> Self { - let strategy = if let Some(strat) = maybe_strategy { - strat - } else { - FallbackStrategy::HardwareThenSoftware - }; - Sha512 { - use_soft: false, - strategy, - engine: Engine512::new(&H512), - in_progress: false, - length: 0, - } - } - // make all the boilerplate comms code shared between all sizes of digest - sha512_comms!(); -} - -impl Default for Sha512 { - fn default() -> Self { - Sha512::new(Some(FallbackStrategy::HardwareThenSoftware)) - // xns should Drop here and release the connection allocated by it automatically - } -} - -impl Drop for Sha512 { - fn drop(&mut self) { - // normally, we would de-allocate a connection but because the Digest API assumes that - // all instances are fungible we can't do that, as the connection needs to be persistent - // between invocations of the object. - if !self.use_soft { - self.reset_hw(); - } - } -} - -impl BlockInput for Sha512 { - type BlockSize = BlockSize; -} - -impl Update for Sha512 { - fn update(&mut self, input: impl AsRef<[u8]>) { - self.try_acquire_hw(Sha2Config::Sha512); - // split the incoming blocks to page size and send to the engine - if self.use_soft { - self.engine.update(input.as_ref()); - } else { - for chunk in input.as_ref().chunks(3968) { - // one SHA512 block (128 bytes) short of 4096 to give space for struct overhead in page remap handling - let mut update = Sha2Update { - id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], - buffer: [0; 3968], - len: 0, - }; - self.length += (chunk.len() as u64) * 8; // we need to keep track of length in bits - for (&src, dest) in chunk.iter().zip(&mut update.buffer) { - *dest = src; - } - update.len = chunk.len() as u16; - let buf = Buffer::into_buf(update).expect("couldn't map chunk into IPC buffer"); - buf.lend(self.ensure_conn(), Opcode::Update.to_u32().unwrap()).expect("hardware rejected our hash chunk!"); - } - } - } -} - -impl FixedOutputDirty for Sha512 { - type OutputSize = U64; - - fn finalize_into_dirty(&mut self, out: &mut digest::Output) { - if self.use_soft { - self.engine.finish(); - let s = self.engine.state; - for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { - chunk.copy_from_slice(&v.to_be_bytes()); - } - } else { - let result = Sha2Finalize { - id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], - result: Sha2Result::Uninitialized, - length_in_bits: None, - }; - let mut buf = Buffer::into_buf(result).expect("couldn't map memory for the return buffer"); - buf.lend_mut(self.ensure_conn(), Opcode::Finalize.to_u32().unwrap()).expect("couldn't finalize"); - - let returned: Sha2Finalize = buf.to_original().expect("couldn't decode return buffer"); - match returned.result { - Sha2Result::Sha512Result(s) => { - log::info!("bits hashed: {}", self.length); - if self.length != returned.length_in_bits.expect("hardware did not return a length field!") { - panic!("Sha512 hardware did not hash as many bits as we had expected!") - } - for (dest, &src) in out.chunks_exact_mut(1).zip(s.iter()) { - dest.copy_from_slice(&[src]) - } - } - Sha2Result::Sha512Trunc256Result(_) => { - panic!("Sha512 hardware returned the wrong type of buffer!"); - } - Sha2Result::SuspendError => { - panic!("Hardware was suspended during Sha512 operation, result is invalid."); - } - Sha2Result::Uninitialized => { - panic!("Hardware didn't copy Sha512 hash result to the return buffer."); - } - Sha2Result::IdMismatch => { - panic!("Hardware is not currently processing our block, finalize call has no meaning."); - } - } - } - } -} - -impl Reset for Sha512 { - fn reset(&mut self) { - if self.use_soft { - self.engine.reset(&H512); - } else { - self.reset_hw(); - } - } -} - - -/// The SHA-512 hash algorithm with the SHA-512/256 initial hash value. The -/// result is truncated to 256 bits. - -/// TODO: this is a software-only implementation; this needs to be extended to have hardware -/// support as the constants for this mode are supported in-hardware, once we have validated -/// that the core hardware API even works... -#[derive(Clone)] -pub struct Sha512Trunc256 { - engine: Engine512, - /// whether or not this current hasher instance will use software or hardware acceleration - use_soft: bool, - /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously - strategy: FallbackStrategy, - /// track if a hash is in progress - in_progress: bool, - /// track the length of the message processed so far - length: u64, -} -impl Sha512Trunc256 { - // use this function instead of default for more control over configuration of the hardware engine - pub fn new(maybe_strategy: Option) -> Self { - let strategy = if let Some(strat) = maybe_strategy { - strat - } else { - FallbackStrategy::HardwareThenSoftware - }; - Sha512Trunc256 { - use_soft: false, - strategy, - engine: Engine512::new(&H512_TRUNC_256), - in_progress: false, - length: 0, - } - } - // make all the boilerplate comms code shared between all sizes of digest - sha512_comms!(); -} - -impl Drop for Sha512Trunc256 { - fn drop(&mut self) { - if !self.use_soft { - self.reset_hw(); - } - } -} - -impl Default for Sha512Trunc256 { - fn default() -> Self { - Sha512Trunc256::new(Some(FallbackStrategy::HardwareThenSoftware)) - } -} - -impl BlockInput for Sha512Trunc256 { - type BlockSize = BlockSize; -} - -impl Update for Sha512Trunc256 { - fn update(&mut self, input: impl AsRef<[u8]>) { - self.try_acquire_hw(Sha2Config::Sha512Trunc256); - if self.use_soft { - self.engine.update(input.as_ref()); - } else { - for chunk in input.as_ref().chunks(3968) { - // one SHA512 block (128 bytes) short of 4096 to give space for struct overhead in page remap handling - let mut update = Sha2Update { - id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], - buffer: [0; 3968], - len: 0, - }; - self.length += (chunk.len() as u64) * 8; // we need to keep track of length in bits - for (&src, dest) in chunk.iter().zip(&mut update.buffer) { - *dest = src; - } - update.len = chunk.len() as u16; - let buf = Buffer::into_buf(update).expect("couldn't map chunk into IPC buffer"); - buf.lend(self.ensure_conn(), Opcode::Update.to_u32().unwrap()).expect("hardware rejected our hash chunk!"); - } - } - } -} - -impl FixedOutputDirty for Sha512Trunc256 { - type OutputSize = U32; - - fn finalize_into_dirty(&mut self, out: &mut digest::Output) { - if self.use_soft { - self.engine.finish(); - let s = &self.engine.state[..4]; - for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { - chunk.copy_from_slice(&v.to_be_bytes()); - } - } else { - let result = Sha2Finalize { - id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], - result: Sha2Result::Uninitialized, - length_in_bits: None, - }; - let mut buf = Buffer::into_buf(result).expect("couldn't map memory for the return buffer"); - buf.lend_mut(self.ensure_conn(), Opcode::Finalize.to_u32().unwrap()).expect("couldn't finalize"); - - let returned: Sha2Finalize = buf.to_original().expect("couldn't decode return buffer"); - match returned.result { - Sha2Result::Sha512Trunc256Result(s) => { - if self.length != returned.length_in_bits.expect("hardware did not return a length field!") { - panic!("Sha512 hardware did not hash as many bits as we had expected!") - } - for (dest, &src) in out.chunks_exact_mut(1).zip(s.iter()) { - dest.copy_from_slice(&[src]) - } - } - Sha2Result::Sha512Result(_) => { - panic!("Sha512 hardware returned the wrong type of buffer!"); - } - Sha2Result::SuspendError => { - panic!("Hardware was suspended during Sha512 operation, result is invalid."); - } - Sha2Result::Uninitialized => { - panic!("Hardware didn't copy Sha512 hash result to the return buffer."); - } - Sha2Result::IdMismatch => { - panic!("Hardware is not currently processing our block, finalize call has no meaning."); - } - } - } - } -} - -impl Reset for Sha512Trunc256 { - fn reset(&mut self) { - if self.use_soft { - self.engine.reset(&H512_TRUNC_256); - } else { - self.reset_hw(); - } - } -} - -////////////////////////////////////////////// -//////////////// the following modes are software-only emulation, as we currently do not have the constants in hardware to support these. -////////////////////////////////////////////// - -/// The SHA-512 hash algorithm with the SHA-384 initial hash value. The result -/// is truncated to 384 bits. -#[derive(Clone)] -pub struct Sha384 { - engine: Engine512, -} - -impl Default for Sha384 { - fn default() -> Self { - Sha384 { - engine: Engine512::new(&H384), - } - } -} - -impl BlockInput for Sha384 { - type BlockSize = BlockSize; -} - -impl Update for Sha384 { - fn update(&mut self, input: impl AsRef<[u8]>) { - self.engine.update(input.as_ref()); - } -} - -impl FixedOutputDirty for Sha384 { - type OutputSize = U48; - - fn finalize_into_dirty(&mut self, out: &mut digest::Output) { - self.engine.finish(); - let s = &self.engine.state[..6]; - for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { - chunk.copy_from_slice(&v.to_be_bytes()); - } - } -} - -impl Reset for Sha384 { - fn reset(&mut self) { - self.engine.reset(&H384); - } -} - - -/// The SHA-512 hash algorithm with the SHA-512/224 initial hash value. -/// The result is truncated to 224 bits. -#[derive(Clone)] -pub struct Sha512Trunc224 { - engine: Engine512, -} - -impl Default for Sha512Trunc224 { - fn default() -> Self { - Sha512Trunc224 { - engine: Engine512::new(&H512_TRUNC_224), - } - } -} - -impl BlockInput for Sha512Trunc224 { - type BlockSize = BlockSize; -} - -impl Update for Sha512Trunc224 { - fn update(&mut self, input: impl AsRef<[u8]>) { - self.engine.update(input.as_ref()); - } -} - -impl FixedOutputDirty for Sha512Trunc224 { - type OutputSize = U28; - - fn finalize_into_dirty(&mut self, out: &mut digest::Output) { - self.engine.finish(); - let s = &self.engine.state; - for (chunk, v) in out.chunks_exact_mut(8).zip(s[..3].iter()) { - chunk.copy_from_slice(&v.to_be_bytes()); - } - out[24..28].copy_from_slice(&s[3].to_be_bytes()[..4]); - } -} - -impl Reset for Sha512Trunc224 { - fn reset(&mut self) { - self.engine.reset(&H512_TRUNC_224); - } -} - - -opaque_debug::implement!(Sha384); -opaque_debug::implement!(Sha512); -opaque_debug::implement!(Sha512Trunc224); -opaque_debug::implement!(Sha512Trunc256); - -digest::impl_write!(Sha384); -digest::impl_write!(Sha512); -digest::impl_write!(Sha512Trunc224); -digest::impl_write!(Sha512Trunc256); - -/// Raw SHA-512 compression function. -/// -/// This is a low-level "hazmat" API which provides direct access to the core -/// functionality of SHA-512. -#[cfg_attr(docsrs, doc(cfg(feature = "compress")))] -pub fn compress512(state: &mut [u64; 8], blocks: &[GenericArray]) { - // SAFETY: GenericArray and [u8; 128] have - // exactly the same memory layout - #[allow(unsafe_code)] - let blocks: &[[u8; 128]] = unsafe { &*(blocks as *const _ as *const [[u8; 128]]) }; - compress(state, blocks) -} +pub use digest::{self, Digest}; +pub use sha256::{Sha224, Sha256}; +pub use sha512::{Sha384, Sha512, Sha512Trunc224, Sha512Trunc256}; diff --git a/services/engine-sha512/src/sha256.rs b/services/engine-sha512/src/sha256.rs new file mode 100644 index 000000000..a423510f3 --- /dev/null +++ b/services/engine-sha512/src/sha256.rs @@ -0,0 +1,155 @@ +//! SHA-256 +mod soft; +use soft::compress; + +use crate::consts::{H224, H256, STATE_LEN}; +use block_buffer::BlockBuffer; +use core::slice::from_ref; +use digest::consts::{U28, U32, U64}; +use digest::generic_array::GenericArray; +use digest::{BlockInput, FixedOutputDirty, Reset, Update}; + +type BlockSize = U64; + +/// Structure that keeps state of the Sha-256 operation and +/// contains the logic necessary to perform the final calculations. +#[derive(Clone)] +struct Engine256 { + len: u64, + buffer: BlockBuffer, + state: [u32; 8], +} + +impl Engine256 { + fn new(h: &[u32; STATE_LEN]) -> Engine256 { + Engine256 { + len: 0, + buffer: Default::default(), + state: *h, + } + } + + fn update(&mut self, input: &[u8]) { + // Assumes that input.len() can be converted to u64 without overflow + self.len += (input.len() as u64) << 3; + let s = &mut self.state; + self.buffer.input_blocks(input, |b| compress256(s, b)); + } + + fn finish(&mut self) { + let s = &mut self.state; + let l = self.len; + self.buffer + .len64_padding_be(l, |b| compress256(s, from_ref(b))); + } + + fn reset(&mut self, h: &[u32; STATE_LEN]) { + self.len = 0; + self.buffer.reset(); + self.state = *h; + } +} + +/// The SHA-256 hash algorithm with the SHA-256 initial hash value. +#[derive(Clone)] +pub struct Sha256 { + engine: Engine256, +} + +impl Default for Sha256 { + fn default() -> Self { + Sha256 { + engine: Engine256::new(&H256), + } + } +} + +impl BlockInput for Sha256 { + type BlockSize = BlockSize; +} + +impl Update for Sha256 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.engine.update(input.as_ref()); + } +} + +impl FixedOutputDirty for Sha256 { + type OutputSize = U32; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + self.engine.finish(); + let s = self.engine.state; + for (chunk, v) in out.chunks_exact_mut(4).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } +} + +impl Reset for Sha256 { + fn reset(&mut self) { + self.engine.reset(&H256); + } +} + +/// The SHA-256 hash algorithm with the SHA-224 initial hash value. The result +/// is truncated to 224 bits. +#[derive(Clone)] +pub struct Sha224 { + engine: Engine256, +} + +impl Default for Sha224 { + fn default() -> Self { + Sha224 { + engine: Engine256::new(&H224), + } + } +} + +impl BlockInput for Sha224 { + type BlockSize = BlockSize; +} + +impl Update for Sha224 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.engine.update(input.as_ref()); + } +} + +impl FixedOutputDirty for Sha224 { + type OutputSize = U28; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + self.engine.finish(); + let s = &self.engine.state[..7]; + for (chunk, v) in out.chunks_exact_mut(4).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } +} + +impl Reset for Sha224 { + fn reset(&mut self) { + self.engine.reset(&H224); + } +} + +opaque_debug::implement!(Sha224); +opaque_debug::implement!(Sha256); + +digest::impl_write!(Sha224); +digest::impl_write!(Sha256); + +/// Raw SHA-256 compression function. +/// +/// This is a low-level "hazmat" API which provides direct access to the core +/// functionality of SHA-256. +#[cfg_attr(docsrs, doc(cfg(feature = "compress")))] +pub fn compress256(state: &mut [u32; 8], blocks: &[GenericArray]) { + // SAFETY: GenericArray and [u8; 64] have + // exactly the same memory layout + #[allow(unsafe_code)] + let blocks: &[[u8; 64]] = unsafe { &*(blocks as *const _ as *const [[u8; 64]]) }; + compress(state, blocks) +} diff --git a/services/engine-sha512/src/sha256/soft.rs b/services/engine-sha512/src/sha256/soft.rs new file mode 100644 index 000000000..34826a7e8 --- /dev/null +++ b/services/engine-sha512/src/sha256/soft.rs @@ -0,0 +1,218 @@ +#![allow(clippy::many_single_char_names)] +use crate::consts::BLOCK_LEN; +use core::convert::TryInto; + +#[inline(always)] +fn shl(v: [u32; 4], o: u32) -> [u32; 4] { + [v[0] >> o, v[1] >> o, v[2] >> o, v[3] >> o] +} + +#[inline(always)] +fn shr(v: [u32; 4], o: u32) -> [u32; 4] { + [v[0] << o, v[1] << o, v[2] << o, v[3] << o] +} + +#[inline(always)] +fn or(a: [u32; 4], b: [u32; 4]) -> [u32; 4] { + [a[0] | b[0], a[1] | b[1], a[2] | b[2], a[3] | b[3]] +} + +#[inline(always)] +fn xor(a: [u32; 4], b: [u32; 4]) -> [u32; 4] { + [a[0] ^ b[0], a[1] ^ b[1], a[2] ^ b[2], a[3] ^ b[3]] +} + +#[inline(always)] +fn add(a: [u32; 4], b: [u32; 4]) -> [u32; 4] { + [ + a[0].wrapping_add(b[0]), + a[1].wrapping_add(b[1]), + a[2].wrapping_add(b[2]), + a[3].wrapping_add(b[3]), + ] +} + +fn sha256load(v2: [u32; 4], v3: [u32; 4]) -> [u32; 4] { + [v3[3], v2[0], v2[1], v2[2]] +} + +fn sha256swap(v0: [u32; 4]) -> [u32; 4] { + [v0[2], v0[3], v0[0], v0[1]] +} + +fn sha256msg1(v0: [u32; 4], v1: [u32; 4]) -> [u32; 4] { + // sigma 0 on vectors + #[inline] + fn sigma0x4(x: [u32; 4]) -> [u32; 4] { + let t1 = or(shl(x, 7), shr(x, 25)); + let t2 = or(shl(x, 18), shr(x, 14)); + let t3 = shl(x, 3); + xor(xor(t1, t2), t3) + } + + add(v0, sigma0x4(sha256load(v0, v1))) +} + +fn sha256msg2(v4: [u32; 4], v3: [u32; 4]) -> [u32; 4] { + macro_rules! sigma1 { + ($a:expr) => { + $a.rotate_right(17) ^ $a.rotate_right(19) ^ ($a >> 10) + }; + } + + let [x3, x2, x1, x0] = v4; + let [w15, w14, _, _] = v3; + + let w16 = x0.wrapping_add(sigma1!(w14)); + let w17 = x1.wrapping_add(sigma1!(w15)); + let w18 = x2.wrapping_add(sigma1!(w16)); + let w19 = x3.wrapping_add(sigma1!(w17)); + + [w19, w18, w17, w16] +} + +fn sha256_digest_round_x2(cdgh: [u32; 4], abef: [u32; 4], wk: [u32; 4]) -> [u32; 4] { + macro_rules! big_sigma0 { + ($a:expr) => { + ($a.rotate_right(2) ^ $a.rotate_right(13) ^ $a.rotate_right(22)) + }; + } + macro_rules! big_sigma1 { + ($a:expr) => { + ($a.rotate_right(6) ^ $a.rotate_right(11) ^ $a.rotate_right(25)) + }; + } + macro_rules! bool3ary_202 { + ($a:expr, $b:expr, $c:expr) => { + $c ^ ($a & ($b ^ $c)) + }; + } // Choose, MD5F, SHA1C + macro_rules! bool3ary_232 { + ($a:expr, $b:expr, $c:expr) => { + ($a & $b) ^ ($a & $c) ^ ($b & $c) + }; + } // Majority, SHA1M + + let [_, _, wk1, wk0] = wk; + let [a0, b0, e0, f0] = abef; + let [c0, d0, g0, h0] = cdgh; + + // a round + let x0 = big_sigma1!(e0) + .wrapping_add(bool3ary_202!(e0, f0, g0)) + .wrapping_add(wk0) + .wrapping_add(h0); + let y0 = big_sigma0!(a0).wrapping_add(bool3ary_232!(a0, b0, c0)); + let (a1, b1, c1, d1, e1, f1, g1, h1) = ( + x0.wrapping_add(y0), + a0, + b0, + c0, + x0.wrapping_add(d0), + e0, + f0, + g0, + ); + + // a round + let x1 = big_sigma1!(e1) + .wrapping_add(bool3ary_202!(e1, f1, g1)) + .wrapping_add(wk1) + .wrapping_add(h1); + let y1 = big_sigma0!(a1).wrapping_add(bool3ary_232!(a1, b1, c1)); + let (a2, b2, _, _, e2, f2, _, _) = ( + x1.wrapping_add(y1), + a1, + b1, + c1, + x1.wrapping_add(d1), + e1, + f1, + g1, + ); + + [a2, b2, e2, f2] +} + +fn schedule(v0: [u32; 4], v1: [u32; 4], v2: [u32; 4], v3: [u32; 4]) -> [u32; 4] { + let t1 = sha256msg1(v0, v1); + let t2 = sha256load(v2, v3); + let t3 = add(t1, t2); + sha256msg2(t3, v3) +} + +macro_rules! rounds4 { + ($abef:ident, $cdgh:ident, $rest:expr, $i:expr) => {{ + let t1 = add($rest, crate::consts::K32X4[$i]); + $cdgh = sha256_digest_round_x2($cdgh, $abef, t1); + let t2 = sha256swap(t1); + $abef = sha256_digest_round_x2($abef, $cdgh, t2); + }}; +} + +macro_rules! schedule_rounds4 { + ( + $abef:ident, $cdgh:ident, + $w0:expr, $w1:expr, $w2:expr, $w3:expr, $w4:expr, + $i: expr + ) => {{ + $w4 = schedule($w0, $w1, $w2, $w3); + rounds4!($abef, $cdgh, $w4, $i); + }}; +} + +/// Process a block with the SHA-256 algorithm. +fn sha256_digest_block_u32(state: &mut [u32; 8], block: &[u32; 16]) { + let mut abef = [state[0], state[1], state[4], state[5]]; + let mut cdgh = [state[2], state[3], state[6], state[7]]; + + // Rounds 0..64 + let mut w0 = [block[3], block[2], block[1], block[0]]; + let mut w1 = [block[7], block[6], block[5], block[4]]; + let mut w2 = [block[11], block[10], block[9], block[8]]; + let mut w3 = [block[15], block[14], block[13], block[12]]; + let mut w4; + + rounds4!(abef, cdgh, w0, 0); + rounds4!(abef, cdgh, w1, 1); + rounds4!(abef, cdgh, w2, 2); + rounds4!(abef, cdgh, w3, 3); + schedule_rounds4!(abef, cdgh, w0, w1, w2, w3, w4, 4); + schedule_rounds4!(abef, cdgh, w1, w2, w3, w4, w0, 5); + schedule_rounds4!(abef, cdgh, w2, w3, w4, w0, w1, 6); + schedule_rounds4!(abef, cdgh, w3, w4, w0, w1, w2, 7); + schedule_rounds4!(abef, cdgh, w4, w0, w1, w2, w3, 8); + schedule_rounds4!(abef, cdgh, w0, w1, w2, w3, w4, 9); + schedule_rounds4!(abef, cdgh, w1, w2, w3, w4, w0, 10); + schedule_rounds4!(abef, cdgh, w2, w3, w4, w0, w1, 11); + schedule_rounds4!(abef, cdgh, w3, w4, w0, w1, w2, 12); + schedule_rounds4!(abef, cdgh, w4, w0, w1, w2, w3, 13); + schedule_rounds4!(abef, cdgh, w0, w1, w2, w3, w4, 14); + schedule_rounds4!(abef, cdgh, w1, w2, w3, w4, w0, 15); + + let [a, b, e, f] = abef; + let [c, d, g, h] = cdgh; + + 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); +} + +pub fn compress(state: &mut [u32; 8], blocks: &[[u8; 64]]) { + let mut block_u32 = [0u32; BLOCK_LEN]; + // since LLVM can't properly use aliasing yet it will make + // unnecessary state stores without this copy + let mut state_cpy = *state; + for block in blocks { + for (o, chunk) in block_u32.iter_mut().zip(block.chunks_exact(4)) { + *o = u32::from_be_bytes(chunk.try_into().unwrap()); + } + sha256_digest_block_u32(&mut state_cpy, &block_u32); + } + *state = state_cpy; +} diff --git a/services/engine-sha512/src/sha512.rs b/services/engine-sha512/src/sha512.rs new file mode 100755 index 000000000..91e93beac --- /dev/null +++ b/services/engine-sha512/src/sha512.rs @@ -0,0 +1,565 @@ +//! SHA-512 +mod soft; +use soft::compress; +use crate::consts::*; + +use crate::api::*; +use xous::{send_message, Message}; +use num_traits::ToPrimitive; +use xous_ipc::Buffer; + +use core::sync::atomic::{AtomicU32, Ordering}; +/// we have to make the HW_CONN static because the Digest crate assumes you can clone objects +/// and recycle them. However, it's not a problem for every server to have a unique connection +/// to the hasher service, if that's what it comes down to. The burden for tracking connections is on the +/// connector's side, not on the server's side; so when the connecting process that calls this +/// library dies, this static data dies with it. +static HW_CONN: AtomicU32 = AtomicU32::new(0); +/// a unique-enough random ID number to prove we own our connection to the hashing engine hardware +static TOKEN: [AtomicU32; 3] = [AtomicU32::new(0), AtomicU32::new(0), AtomicU32::new(0)]; + +use digest::{BlockInput, FixedOutputDirty, Reset, Update}; +use digest::generic_array::GenericArray; +use digest::consts::{U28, U32, U48, U64, U128}; +use block_buffer::BlockBuffer; +use core::slice::from_ref; +type BlockSize = U128; + +/* + Software emulation vendored from https://github.com/RustCrypto/hashes/tree/master/sha2/src + License is Apache 2.0 + */ +/// Structure that keeps state of the software-emulated Sha-512 operation and +/// contains the logic necessary to perform the final calculations. +#[derive(Clone)] +struct Engine512 { + len: u128, + buffer: BlockBuffer, + state: [u64; 8], +} + +impl Engine512 { + fn new(h: &[u64; STATE_LEN]) -> Engine512 { + Engine512 { + len: 0, + buffer: Default::default(), + state: *h, + } + } + + fn update(&mut self, input: &[u8]) { + self.len += (input.len() as u128) << 3; + let s = &mut self.state; + self.buffer.input_blocks(input, |b| compress512(s, b)); + } + + fn finish(&mut self) { + let s = &mut self.state; + self.buffer + .len128_padding_be(self.len, |d| compress512(s, from_ref(d))); + } + + fn reset(&mut self, h: &[u64; crate::consts::STATE_LEN]) { + self.len = 0; + self.buffer.reset(); + self.state = *h; + } +} + +// a macro for common communications libraries for SHA2 hardware interfacing +// you can't reference fields in a trait. looks like a macro is the accepted way +// of not having to repeat this code over and over again. +macro_rules! sha512_comms { + () => { + pub(crate) fn ensure_conn(&self) -> u32 { + if HW_CONN.load(Ordering::Relaxed) == 0 { + let xns = xous_names::XousNames::new().unwrap(); + HW_CONN.store(xns.request_connection_blocking(crate::api::SERVER_NAME_SHA512).expect("Can't connect to Sha512 server"), Ordering::Relaxed); + let trng = trng::Trng::new(&xns).expect("Can't connect to TRNG server"); + let id1 = trng.get_u64().unwrap(); + let id2 = trng.get_u32().unwrap(); + TOKEN[0].store((id1 >> 32) as u32, Ordering::Relaxed); + TOKEN[1].store(id1 as u32, Ordering::Relaxed); + TOKEN[2].store(id2, Ordering::Relaxed); + } + HW_CONN.load(Ordering::Relaxed) + } + pub fn is_idle(&self) -> Result { + let response = send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::IsIdle.to_usize().unwrap(), 0, 0, 0, 0) + ).expect("Couldn't make IsIdle query"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + Ok(true) + } else { + Ok(false) + } + } else { + Err(xous::Error::InternalError) + } + } + pub fn acquire_suspend_lock(&self) -> Result { + let response = send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::AcquireSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) + ).expect("Couldn't issue AcquireSuspendLock message"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + Ok(true) + } else { + Ok(false) + } + } else { + Err(xous::Error::InternalError) + } + } + pub fn abort_suspend(&self) -> Result<(), xous::Error> { + // we ignore the result and just turn it into () once we get anything back, as abort_suspend "can't fail" + send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::AbortSuspendLock.to_usize().unwrap(), 0, 0, 0, 0) + ).map(|_| ()) + } + pub(crate) fn try_acquire_hw(&mut self, config: Sha2Config) { + if !self.in_progress && (self.strategy != FallbackStrategy::SoftwareOnly) { + loop { + let conn = self.ensure_conn(); // also ensures the ID + let response = send_message(conn, + Message::new_blocking_scalar(Opcode::AcquireExclusive.to_usize().unwrap(), + TOKEN[0].load(Ordering::Relaxed) as usize, + TOKEN[1].load(Ordering::Relaxed) as usize, + TOKEN[2].load(Ordering::Relaxed) as usize, + config.to_usize().unwrap(), + ) + ).expect("couldn't send AcquireExclusive message to Sha2 hardware!"); + if let xous::Result::Scalar1(result) = response { + if result != 0 { + self.use_soft = false; + self.in_progress = true; + break; + } else { + if self.strategy == FallbackStrategy::HardwareThenSoftware { + self.use_soft = true; + self.in_progress = true; + break; + } else { + // this is hardware-exclusive mode, we block until we can get the hardware + xous::yield_slice(); + } + } + } else { + log::error!("AcquireExclusive had an unexpected error: {:?}", response); + panic!("Internal error in AcquireExclusive"); + } + } + } else if self.strategy == FallbackStrategy::SoftwareOnly { + self.use_soft = true; + self.in_progress = true; + } + } + pub(crate) fn reset_hw(&mut self) { + send_message(self.ensure_conn(), + Message::new_blocking_scalar(Opcode::Reset.to_usize().unwrap(), + TOKEN[0].load(Ordering::Relaxed) as usize, + TOKEN[1].load(Ordering::Relaxed) as usize, + TOKEN[2].load(Ordering::Relaxed) as usize, + 0, + )).expect("couldn't send reset to hardware"); + // reset internal flags + self.length = 0; + self.in_progress = false; + self.use_soft = true; + } + }; +} + +/// The SHA-512 hash algorithm with the SHA-512 initial hash value. +#[derive(Clone)] +pub struct Sha512 { + /// software fallback engine + engine: Engine512, + /// whether or not this current hasher instance will use software or hardware acceleration + use_soft: bool, + /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously + strategy: FallbackStrategy, + /// track if a hash is in progress + in_progress: bool, + /// track the length of the message processed so far + length: u64, +} +impl Sha512 { + // use this function instead of default for more control over configuration of the hardware engine + pub fn new(maybe_strategy: Option) -> Self { + let strategy = if let Some(strat) = maybe_strategy { + strat + } else { + FallbackStrategy::HardwareThenSoftware + }; + Sha512 { + use_soft: false, + strategy, + engine: Engine512::new(&H512), + in_progress: false, + length: 0, + } + } + // make all the boilerplate comms code shared between all sizes of digest + sha512_comms!(); +} + +impl Default for Sha512 { + fn default() -> Self { + Sha512::new(Some(FallbackStrategy::HardwareThenSoftware)) + // xns should Drop here and release the connection allocated by it automatically + } +} + +impl Drop for Sha512 { + fn drop(&mut self) { + // normally, we would de-allocate a connection but because the Digest API assumes that + // all instances are fungible we can't do that, as the connection needs to be persistent + // between invocations of the object. + if !self.use_soft { + self.reset_hw(); + } + } +} + +impl BlockInput for Sha512 { + type BlockSize = BlockSize; +} + +impl Update for Sha512 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.try_acquire_hw(Sha2Config::Sha512); + // split the incoming blocks to page size and send to the engine + if self.use_soft { + self.engine.update(input.as_ref()); + } else { + for chunk in input.as_ref().chunks(3968) { + // one SHA512 block (128 bytes) short of 4096 to give space for struct overhead in page remap handling + let mut update = Sha2Update { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + buffer: [0; 3968], + len: 0, + }; + self.length += (chunk.len() as u64) * 8; // we need to keep track of length in bits + for (&src, dest) in chunk.iter().zip(&mut update.buffer) { + *dest = src; + } + update.len = chunk.len() as u16; + let buf = Buffer::into_buf(update).expect("couldn't map chunk into IPC buffer"); + buf.lend(self.ensure_conn(), Opcode::Update.to_u32().unwrap()).expect("hardware rejected our hash chunk!"); + } + } + } +} + +impl FixedOutputDirty for Sha512 { + type OutputSize = U64; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + if self.use_soft { + self.engine.finish(); + let s = self.engine.state; + for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } else { + let result = Sha2Finalize { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + result: Sha2Result::Uninitialized, + length_in_bits: None, + }; + let mut buf = Buffer::into_buf(result).expect("couldn't map memory for the return buffer"); + buf.lend_mut(self.ensure_conn(), Opcode::Finalize.to_u32().unwrap()).expect("couldn't finalize"); + + let returned: Sha2Finalize = buf.to_original().expect("couldn't decode return buffer"); + match returned.result { + Sha2Result::Sha512Result(s) => { + log::info!("bits hashed: {}", self.length); + if self.length != returned.length_in_bits.expect("hardware did not return a length field!") { + panic!("Sha512 hardware did not hash as many bits as we had expected!") + } + for (dest, &src) in out.chunks_exact_mut(1).zip(s.iter()) { + dest.copy_from_slice(&[src]) + } + } + Sha2Result::Sha512Trunc256Result(_) => { + panic!("Sha512 hardware returned the wrong type of buffer!"); + } + Sha2Result::SuspendError => { + panic!("Hardware was suspended during Sha512 operation, result is invalid."); + } + Sha2Result::Uninitialized => { + panic!("Hardware didn't copy Sha512 hash result to the return buffer."); + } + Sha2Result::IdMismatch => { + panic!("Hardware is not currently processing our block, finalize call has no meaning."); + } + } + } + } +} + +impl Reset for Sha512 { + fn reset(&mut self) { + if self.use_soft { + self.engine.reset(&H512); + } else { + self.reset_hw(); + } + } +} + + +/// The SHA-512 hash algorithm with the SHA-512/256 initial hash value. The +/// result is truncated to 256 bits. + +/// TODO: this is a software-only implementation; this needs to be extended to have hardware +/// support as the constants for this mode are supported in-hardware, once we have validated +/// that the core hardware API even works... +#[derive(Clone)] +pub struct Sha512Trunc256 { + engine: Engine512, + /// whether or not this current hasher instance will use software or hardware acceleration + use_soft: bool, + /// specifies the strategy for fallback in case multiple hashes are initiated simultaneously + strategy: FallbackStrategy, + /// track if a hash is in progress + in_progress: bool, + /// track the length of the message processed so far + length: u64, +} +impl Sha512Trunc256 { + // use this function instead of default for more control over configuration of the hardware engine + pub fn new(maybe_strategy: Option) -> Self { + let strategy = if let Some(strat) = maybe_strategy { + strat + } else { + FallbackStrategy::HardwareThenSoftware + }; + Sha512Trunc256 { + use_soft: false, + strategy, + engine: Engine512::new(&H512_TRUNC_256), + in_progress: false, + length: 0, + } + } + // make all the boilerplate comms code shared between all sizes of digest + sha512_comms!(); +} + +impl Drop for Sha512Trunc256 { + fn drop(&mut self) { + if !self.use_soft { + self.reset_hw(); + } + } +} + +impl Default for Sha512Trunc256 { + fn default() -> Self { + Sha512Trunc256::new(Some(FallbackStrategy::HardwareThenSoftware)) + } +} + +impl BlockInput for Sha512Trunc256 { + type BlockSize = BlockSize; +} + +impl Update for Sha512Trunc256 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.try_acquire_hw(Sha2Config::Sha512Trunc256); + if self.use_soft { + self.engine.update(input.as_ref()); + } else { + for chunk in input.as_ref().chunks(3968) { + // one SHA512 block (128 bytes) short of 4096 to give space for struct overhead in page remap handling + let mut update = Sha2Update { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + buffer: [0; 3968], + len: 0, + }; + self.length += (chunk.len() as u64) * 8; // we need to keep track of length in bits + for (&src, dest) in chunk.iter().zip(&mut update.buffer) { + *dest = src; + } + update.len = chunk.len() as u16; + let buf = Buffer::into_buf(update).expect("couldn't map chunk into IPC buffer"); + buf.lend(self.ensure_conn(), Opcode::Update.to_u32().unwrap()).expect("hardware rejected our hash chunk!"); + } + } + } +} + +impl FixedOutputDirty for Sha512Trunc256 { + type OutputSize = U32; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + if self.use_soft { + self.engine.finish(); + let s = &self.engine.state[..4]; + for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } else { + let result = Sha2Finalize { + id: [TOKEN[0].load(Ordering::Relaxed), TOKEN[1].load(Ordering::Relaxed), TOKEN[2].load(Ordering::Relaxed)], + result: Sha2Result::Uninitialized, + length_in_bits: None, + }; + let mut buf = Buffer::into_buf(result).expect("couldn't map memory for the return buffer"); + buf.lend_mut(self.ensure_conn(), Opcode::Finalize.to_u32().unwrap()).expect("couldn't finalize"); + + let returned: Sha2Finalize = buf.to_original().expect("couldn't decode return buffer"); + match returned.result { + Sha2Result::Sha512Trunc256Result(s) => { + if self.length != returned.length_in_bits.expect("hardware did not return a length field!") { + panic!("Sha512 hardware did not hash as many bits as we had expected!") + } + for (dest, &src) in out.chunks_exact_mut(1).zip(s.iter()) { + dest.copy_from_slice(&[src]) + } + } + Sha2Result::Sha512Result(_) => { + panic!("Sha512 hardware returned the wrong type of buffer!"); + } + Sha2Result::SuspendError => { + panic!("Hardware was suspended during Sha512 operation, result is invalid."); + } + Sha2Result::Uninitialized => { + panic!("Hardware didn't copy Sha512 hash result to the return buffer."); + } + Sha2Result::IdMismatch => { + panic!("Hardware is not currently processing our block, finalize call has no meaning."); + } + } + } + } +} + +impl Reset for Sha512Trunc256 { + fn reset(&mut self) { + if self.use_soft { + self.engine.reset(&H512_TRUNC_256); + } else { + self.reset_hw(); + } + } +} + +////////////////////////////////////////////// +//////////////// the following modes are software-only emulation, as we currently do not have the constants in hardware to support these. +////////////////////////////////////////////// + +/// The SHA-512 hash algorithm with the SHA-384 initial hash value. The result +/// is truncated to 384 bits. +#[derive(Clone)] +pub struct Sha384 { + engine: Engine512, +} + +impl Default for Sha384 { + fn default() -> Self { + Sha384 { + engine: Engine512::new(&H384), + } + } +} + +impl BlockInput for Sha384 { + type BlockSize = BlockSize; +} + +impl Update for Sha384 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.engine.update(input.as_ref()); + } +} + +impl FixedOutputDirty for Sha384 { + type OutputSize = U48; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + self.engine.finish(); + let s = &self.engine.state[..6]; + for (chunk, v) in out.chunks_exact_mut(8).zip(s.iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + } +} + +impl Reset for Sha384 { + fn reset(&mut self) { + self.engine.reset(&H384); + } +} + + +/// The SHA-512 hash algorithm with the SHA-512/224 initial hash value. +/// The result is truncated to 224 bits. +#[derive(Clone)] +pub struct Sha512Trunc224 { + engine: Engine512, +} + +impl Default for Sha512Trunc224 { + fn default() -> Self { + Sha512Trunc224 { + engine: Engine512::new(&H512_TRUNC_224), + } + } +} + +impl BlockInput for Sha512Trunc224 { + type BlockSize = BlockSize; +} + +impl Update for Sha512Trunc224 { + fn update(&mut self, input: impl AsRef<[u8]>) { + self.engine.update(input.as_ref()); + } +} + +impl FixedOutputDirty for Sha512Trunc224 { + type OutputSize = U28; + + fn finalize_into_dirty(&mut self, out: &mut digest::Output) { + self.engine.finish(); + let s = &self.engine.state; + for (chunk, v) in out.chunks_exact_mut(8).zip(s[..3].iter()) { + chunk.copy_from_slice(&v.to_be_bytes()); + } + out[24..28].copy_from_slice(&s[3].to_be_bytes()[..4]); + } +} + +impl Reset for Sha512Trunc224 { + fn reset(&mut self) { + self.engine.reset(&H512_TRUNC_224); + } +} + + +opaque_debug::implement!(Sha384); +opaque_debug::implement!(Sha512); +opaque_debug::implement!(Sha512Trunc224); +opaque_debug::implement!(Sha512Trunc256); + +digest::impl_write!(Sha384); +digest::impl_write!(Sha512); +digest::impl_write!(Sha512Trunc224); +digest::impl_write!(Sha512Trunc256); + +/// Raw SHA-512 compression function. +/// +/// This is a low-level "hazmat" API which provides direct access to the core +/// functionality of SHA-512. +#[cfg_attr(docsrs, doc(cfg(feature = "compress")))] +pub fn compress512(state: &mut [u64; 8], blocks: &[GenericArray]) { + // SAFETY: GenericArray and [u8; 128] have + // exactly the same memory layout + #[allow(unsafe_code)] + let blocks: &[[u8; 128]] = unsafe { &*(blocks as *const _ as *const [[u8; 128]]) }; + compress(state, blocks) +} diff --git a/services/engine-sha512/src/soft.rs b/services/engine-sha512/src/sha512/soft.rs old mode 100644 new mode 100755 similarity index 100% rename from services/engine-sha512/src/soft.rs rename to services/engine-sha512/src/sha512/soft.rs