From 55b05960a1d642d1f3eb582cf7ee3fbb5afb81e1 Mon Sep 17 00:00:00 2001 From: Bao Trinh Date: Mon, 3 Jul 2023 05:59:12 -0500 Subject: [PATCH 1/2] tiger: implement TTH hash algorithm --- tiger/src/lib.rs | 21 ++++--- tiger/src/tth.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++ tiger/tests/mod.rs | 57 ++++++++++++++++++- 3 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 tiger/src/tth.rs diff --git a/tiger/src/lib.rs b/tiger/src/lib.rs index a5f1f8d80..97c84cc80 100644 --- a/tiger/src/lib.rs +++ b/tiger/src/lib.rs @@ -33,6 +33,8 @@ #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] +extern crate alloc; + pub use digest::{self, Digest}; use core::fmt; @@ -50,6 +52,9 @@ mod compress; mod tables; use compress::compress; +mod tth; +use tth::TigerTreeCore; + type State = [u64; 3]; const S0: State = [ 0x0123_4567_89AB_CDEF, @@ -91,7 +96,7 @@ impl UpdateCore for TigerCore { impl FixedOutputCore for TigerCore { #[inline] fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let bs = Self::BlockSize::U64 as u64; + let bs = Self::BlockSize::U64; let pos = buffer.get_pos() as u64; let bit_len = 8 * (pos + bs * self.block_len); @@ -165,7 +170,7 @@ impl UpdateCore for Tiger2Core { impl FixedOutputCore for Tiger2Core { #[inline] fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let bs = Self::BlockSize::U64 as u64; + let bs = Self::BlockSize::U64; let pos = buffer.get_pos() as u64; let bit_len = 8 * (pos + bs * self.block_len); @@ -180,11 +185,7 @@ impl Default for Tiger2Core { fn default() -> Self { Self { block_len: 0, - state: [ - 0x0123_4567_89AB_CDEF, - 0xFEDC_BA98_7654_3210, - 0xF096_A5B4_C3B2_E187, - ], + state: S0, } } } @@ -208,7 +209,9 @@ impl fmt::Debug for Tiger2Core { } } -/// Tiger hasher state. +/// Tiger hasher. pub type Tiger = CoreWrapper; -/// Tiger2 hasher state. +/// Tiger2 hasher. pub type Tiger2 = CoreWrapper; +/// TTH hasher. +pub type TigerTree = CoreWrapper; diff --git a/tiger/src/tth.rs b/tiger/src/tth.rs new file mode 100644 index 000000000..5805454c7 --- /dev/null +++ b/tiger/src/tth.rs @@ -0,0 +1,137 @@ +use crate::{Digest, Tiger, TigerCore}; +use alloc::vec::Vec; +use core::fmt; +use digest::{ + core_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, FixedOutputCore, + OutputSizeUser, Reset, UpdateCore, + }, + typenum::Unsigned, + typenum::U1024, + HashMarker, Output, +}; + +/// Core Tiger hasher state. +#[derive(Clone)] +pub struct TigerTreeCore { + leaves: Vec>, + hasher: Tiger, + blocks_processed: usize, +} + +impl Default for TigerTreeCore { + fn default() -> Self { + Self { + leaves: Vec::default(), + hasher: Tiger::new_with_prefix([LEAF_SIG]), + blocks_processed: 0, + } + } +} + +type DataBlockSize = U1024; +const LEAF_SIG: u8 = 0u8; +const NODE_SIG: u8 = 1u8; +/// The number of TigerCore blocks in a TigerTree data block +const LEAF_BLOCKS: usize = DataBlockSize::USIZE / ::BlockSize::USIZE; + +impl HashMarker for TigerTreeCore {} + +impl BlockSizeUser for TigerTreeCore { + type BlockSize = ::BlockSize; +} + +impl BufferKindUser for TigerTreeCore { + type BufferKind = ::BufferKind; +} + +impl OutputSizeUser for TigerTreeCore { + type OutputSize = ::OutputSize; +} + +impl TigerTreeCore { + #[inline] + fn finalize_blocks(&mut self) { + let hasher = core::mem::replace(&mut self.hasher, Tiger::new_with_prefix([LEAF_SIG])); + let hash = hasher.finalize(); + self.leaves.push(hash); + self.blocks_processed = 0; + } + + #[inline] + fn update_block(&mut self, block: Block) { + self.hasher.update(block); + self.blocks_processed += 1; + if self.blocks_processed == LEAF_BLOCKS { + self.finalize_blocks(); + } + } +} + +impl UpdateCore for TigerTreeCore { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + for block in blocks { + self.update_block(*block); + } + } +} + +impl FixedOutputCore for TigerTreeCore { + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + if buffer.get_pos() > 0 { + self.hasher.update(buffer.get_data()); + self.blocks_processed += 1; + } + + if self.blocks_processed > 0 { + self.finalize_blocks() + } + + let result = hash_nodes(self.leaves.as_slice()); + out.copy_from_slice(&result); + } +} + +#[inline] +fn hash_nodes(hashes: &[Output]) -> Output { + match hashes.len() { + 0 => hash_nodes(&[Tiger::digest([LEAF_SIG])]), + 1 => hashes[0], + _ => { + let left_hashes = hashes.iter().step_by(2); + + let right_hashes = hashes.iter().map(Some).skip(1).chain([None]).step_by(2); + + let next_level_hashes: Vec> = left_hashes + .zip(right_hashes) + .map(|(left, right)| match right { + Some(right) => Tiger::digest([&[NODE_SIG][..], left, right].concat()), + None => *left, + }) + .collect(); + + hash_nodes(next_level_hashes.as_slice()) + } + } +} + +impl Reset for TigerTreeCore { + #[inline] + fn reset(&mut self) { + *self = Default::default(); + } +} + +impl AlgorithmName for TigerTreeCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TigerTree") + } +} + +impl fmt::Debug for TigerTreeCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TigerTreeCore { ... }") + } +} diff --git a/tiger/tests/mod.rs b/tiger/tests/mod.rs index 09e78d6c3..a903c38b1 100644 --- a/tiger/tests/mod.rs +++ b/tiger/tests/mod.rs @@ -1,7 +1,7 @@ use digest::dev::{feed_rand_16mib, fixed_reset_test}; use digest::new_test; use hex_literal::hex; -use tiger::{Digest, Tiger, Tiger2}; +use tiger::{Digest, Tiger, Tiger2, TigerTree}; new_test!(tiger, "tiger", tiger::Tiger, fixed_reset_test); new_test!(tiger2, "tiger2", tiger::Tiger2, fixed_reset_test); @@ -25,3 +25,58 @@ fn tiger2_rand() { hex!("1bb7a80144c97f831fdefb635477776dd6c164048ce5895d")[..] ); } + +#[test] +fn tiger_empty() { + let mut h = Tiger::new(); + h.update(b""); + assert_eq!( + h.finalize()[..], + hex!("3293ac630c13f0245f92bbb1766e16167a4e58492dde73f3")[..] + ); +} + +#[test] +fn tth_empty() { + let mut h = TigerTree::new(); + h.update(b""); + assert_eq!( + h.finalize()[..], + hex!("5d9ed00a030e638bdb753a6a24fb900e5a63b8e73e6c25b6")[..] + ); +} + +#[test] +fn tth_one_block() { + let mut h = TigerTree::new(); + let content = hex!("deadbeef"); + h.update(content); + assert_eq!( + h.finalize()[..], + hex!("f527ab1ddbc4cced8572e0cf7a968604ebccb9414eb438dc")[..] + ); +} + +#[test] +fn tth_two_blocks() { + let content = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + assert!((1025..2049).contains(&content.len())); + let mut h = TigerTree::new(); + h.update(content); + assert_eq!( + h.finalize()[..], + hex!("db67d9e452c3af6c329fb664737bc2378aa364b74c3468bb")[..] + ); +} + +#[test] +fn tth_three_blocks() { + let content = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef8badf00d"); + assert!((2049..3073).contains(&content.len())); + let mut h = TigerTree::new(); + h.update(content); + assert_eq!( + h.finalize()[..], + hex!("f3a63c92019cffb7527e9adfa57a1ecb783728f67b06fed0")[..] + ); +} From a3555de68333139f2fd0a417113a73d6937b5140 Mon Sep 17 00:00:00 2001 From: Bao Trinh Date: Wed, 5 Jul 2023 14:14:36 -0500 Subject: [PATCH 2/2] tiger: avoid alloc for calculating TTH Instead of storing every leaf and processing the tree once at the end, process the tree every time a leaf is added, so that a maximum of N nodes need to be stored, for a tree of height N. Then a final walk of the nodes is performed at the end to promote and merge any single-child leaves. --- Cargo.lock | 7 ++++++ tiger/Cargo.toml | 6 +++-- tiger/src/lib.rs | 5 ++-- tiger/src/tth.rs | 62 ++++++++++++++++++++++++++---------------------- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78a17c4c4..9fda127e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "blake2" version = "0.10.6" @@ -276,6 +282,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" name = "tiger" version = "0.2.1" dependencies = [ + "arrayvec", "digest", "hex-literal", ] diff --git a/tiger/Cargo.toml b/tiger/Cargo.toml index a8f59a4c7..c2531cb1c 100644 --- a/tiger/Cargo.toml +++ b/tiger/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["crypto", "hash", "tiger", "digest"] categories = ["cryptography", "no-std"] [dependencies] +arrayvec = { version = "0.7.4", default-features = false, optional = true } digest = "0.10.7" [dev-dependencies] @@ -19,5 +20,6 @@ digest = { version = "0.10.7", features = ["dev"] } hex-literal = "0.2.2" [features] -default = ["std"] -std = ["digest/std"] +default = ["std", "tth"] +std = ["digest/std", "arrayvec/std"] +tth = ["arrayvec"] diff --git a/tiger/src/lib.rs b/tiger/src/lib.rs index 97c84cc80..7f50c9709 100644 --- a/tiger/src/lib.rs +++ b/tiger/src/lib.rs @@ -33,8 +33,6 @@ #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] -extern crate alloc; - pub use digest::{self, Digest}; use core::fmt; @@ -52,7 +50,9 @@ mod compress; mod tables; use compress::compress; +#[cfg(feature = "tth")] mod tth; +#[cfg(feature = "tth")] use tth::TigerTreeCore; type State = [u64; 3]; @@ -214,4 +214,5 @@ pub type Tiger = CoreWrapper; /// Tiger2 hasher. pub type Tiger2 = CoreWrapper; /// TTH hasher. +#[cfg(feature = "tth")] pub type TigerTree = CoreWrapper; diff --git a/tiger/src/tth.rs b/tiger/src/tth.rs index 5805454c7..6e19208e8 100644 --- a/tiger/src/tth.rs +++ b/tiger/src/tth.rs @@ -1,5 +1,5 @@ use crate::{Digest, Tiger, TigerCore}; -use alloc::vec::Vec; +use arrayvec::ArrayVec; use core::fmt; use digest::{ core_api::{ @@ -11,18 +11,37 @@ use digest::{ HashMarker, Output, }; +#[derive(Clone)] +struct TigerTreeNode { + level: u32, + hash: Output, +} + /// Core Tiger hasher state. #[derive(Clone)] pub struct TigerTreeCore { - leaves: Vec>, + nodes: ArrayVec, hasher: Tiger, blocks_processed: usize, } +impl TigerTreeCore { + fn add_node(&mut self, level: u32, hash: Output) { + match self.nodes.last() { + Some(last) if last.level == level => { + let new_hash = Tiger::digest([&[NODE_SIG][..], &last.hash, &hash].concat()); + self.nodes.pop(); + self.add_node(level + 1, new_hash); + } + _ => self.nodes.push(TigerTreeNode { level, hash }), + } + } +} + impl Default for TigerTreeCore { fn default() -> Self { Self { - leaves: Vec::default(), + nodes: ArrayVec::default(), hasher: Tiger::new_with_prefix([LEAF_SIG]), blocks_processed: 0, } @@ -32,6 +51,9 @@ impl Default for TigerTreeCore { type DataBlockSize = U1024; const LEAF_SIG: u8 = 0u8; const NODE_SIG: u8 = 1u8; +/// The maximum height of a TigerTree based on the maximum addressable data length +// max height = log2(usize::MAX / DataBlockSize) = log2(2^usize::BITS) - log2(DataBlockSize) = usize::BITS - log2(2^10) +const MAX_TREE_HEIGHT: usize = (usize::BITS - 10 + 1) as usize; /// The number of TigerCore blocks in a TigerTree data block const LEAF_BLOCKS: usize = DataBlockSize::USIZE / ::BlockSize::USIZE; @@ -54,7 +76,7 @@ impl TigerTreeCore { fn finalize_blocks(&mut self) { let hasher = core::mem::replace(&mut self.hasher, Tiger::new_with_prefix([LEAF_SIG])); let hash = hasher.finalize(); - self.leaves.push(hash); + self.add_node(0, hash); self.blocks_processed = 0; } @@ -89,31 +111,15 @@ impl FixedOutputCore for TigerTreeCore { self.finalize_blocks() } - let result = hash_nodes(self.leaves.as_slice()); - out.copy_from_slice(&result); - } -} - -#[inline] -fn hash_nodes(hashes: &[Output]) -> Output { - match hashes.len() { - 0 => hash_nodes(&[Tiger::digest([LEAF_SIG])]), - 1 => hashes[0], - _ => { - let left_hashes = hashes.iter().step_by(2); - - let right_hashes = hashes.iter().map(Some).skip(1).chain([None]).step_by(2); - - let next_level_hashes: Vec> = left_hashes - .zip(right_hashes) - .map(|(left, right)| match right { - Some(right) => Tiger::digest([&[NODE_SIG][..], left, right].concat()), - None => *left, - }) - .collect(); - - hash_nodes(next_level_hashes.as_slice()) + let mut hash = self + .nodes + .pop() + .map(|n| n.hash) + .unwrap_or_else(|| Tiger::digest([LEAF_SIG])); + while let Some(left) = self.nodes.pop() { + hash = Tiger::digest([&[NODE_SIG][..], &left.hash, &hash].concat()); } + out.copy_from_slice(hash.as_slice()); } }