From a3555de68333139f2fd0a417113a73d6937b5140 Mon Sep 17 00:00:00 2001 From: Bao Trinh Date: Wed, 5 Jul 2023 14:14:36 -0500 Subject: [PATCH] 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()); } }