Skip to content

Commit

Permalink
tiger: avoid alloc for calculating TTH
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
baodrate committed Jul 5, 2023
1 parent 55b0596 commit f7034fe
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 32 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions tiger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ 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]
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"]
5 changes: 3 additions & 2 deletions tiger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]

extern crate alloc;

pub use digest::{self, Digest};

use core::fmt;
Expand All @@ -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];
Expand Down Expand Up @@ -214,4 +214,5 @@ pub type Tiger = CoreWrapper<TigerCore>;
/// Tiger2 hasher.
pub type Tiger2 = CoreWrapper<Tiger2Core>;
/// TTH hasher.
#[cfg(feature = "tth")]
pub type TigerTree = CoreWrapper<TigerTreeCore>;
60 changes: 32 additions & 28 deletions tiger/src/tth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{Digest, Tiger, TigerCore};
use alloc::vec::Vec;
use arrayvec::ArrayVec;
use core::fmt;
use digest::{
core_api::{
Expand All @@ -11,18 +11,37 @@ use digest::{
HashMarker, Output,
};

#[derive(Clone)]
struct TigerTreeNode {
level: u32,
hash: Output<TigerCore>,
}

/// Core Tiger hasher state.
#[derive(Clone)]
pub struct TigerTreeCore {
leaves: Vec<Output<TigerCore>>,
nodes: ArrayVec<TigerTreeNode, MAX_TREE_HEIGHT>,
hasher: Tiger,
blocks_processed: usize,
}

impl TigerTreeCore {
fn add_node(&mut self, level: u32, hash: Output<TigerCore>) {
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,
}
Expand All @@ -32,6 +51,7 @@ impl Default for TigerTreeCore {
type DataBlockSize = U1024;
const LEAF_SIG: u8 = 0u8;
const NODE_SIG: u8 = 1u8;
const MAX_TREE_HEIGHT: usize = (usize::BITS - DataBlockSize::USIZE.ilog2() + 1) as usize;
/// The number of TigerCore blocks in a TigerTree data block
const LEAF_BLOCKS: usize = DataBlockSize::USIZE / <TigerCore as BlockSizeUser>::BlockSize::USIZE;

Expand All @@ -54,7 +74,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;
}

Expand Down Expand Up @@ -89,31 +109,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<TigerCore>]) -> Output<TigerCore> {
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<Output<TigerCore>> = 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());
}
}

Expand Down

0 comments on commit f7034fe

Please sign in to comment.