Skip to content

Commit f7034fe

Browse files
committed
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.
1 parent 55b0596 commit f7034fe

File tree

4 files changed

+46
-32
lines changed

4 files changed

+46
-32
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tiger/Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ keywords = ["crypto", "hash", "tiger", "digest"]
1212
categories = ["cryptography", "no-std"]
1313

1414
[dependencies]
15+
arrayvec = { version = "0.7.4", default-features = false, optional = true }
1516
digest = "0.10.7"
1617

1718
[dev-dependencies]
1819
digest = { version = "0.10.7", features = ["dev"] }
1920
hex-literal = "0.2.2"
2021

2122
[features]
22-
default = ["std"]
23-
std = ["digest/std"]
23+
default = ["std", "tth"]
24+
std = ["digest/std", "arrayvec/std"]
25+
tth = ["arrayvec"]

tiger/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@
3333
#![forbid(unsafe_code)]
3434
#![warn(missing_docs, rust_2018_idioms)]
3535

36-
extern crate alloc;
37-
3836
pub use digest::{self, Digest};
3937

4038
use core::fmt;
@@ -52,7 +50,9 @@ mod compress;
5250
mod tables;
5351
use compress::compress;
5452

53+
#[cfg(feature = "tth")]
5554
mod tth;
55+
#[cfg(feature = "tth")]
5656
use tth::TigerTreeCore;
5757

5858
type State = [u64; 3];
@@ -214,4 +214,5 @@ pub type Tiger = CoreWrapper<TigerCore>;
214214
/// Tiger2 hasher.
215215
pub type Tiger2 = CoreWrapper<Tiger2Core>;
216216
/// TTH hasher.
217+
#[cfg(feature = "tth")]
217218
pub type TigerTree = CoreWrapper<TigerTreeCore>;

tiger/src/tth.rs

+32-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{Digest, Tiger, TigerCore};
2-
use alloc::vec::Vec;
2+
use arrayvec::ArrayVec;
33
use core::fmt;
44
use digest::{
55
core_api::{
@@ -11,18 +11,37 @@ use digest::{
1111
HashMarker, Output,
1212
};
1313

14+
#[derive(Clone)]
15+
struct TigerTreeNode {
16+
level: u32,
17+
hash: Output<TigerCore>,
18+
}
19+
1420
/// Core Tiger hasher state.
1521
#[derive(Clone)]
1622
pub struct TigerTreeCore {
17-
leaves: Vec<Output<TigerCore>>,
23+
nodes: ArrayVec<TigerTreeNode, MAX_TREE_HEIGHT>,
1824
hasher: Tiger,
1925
blocks_processed: usize,
2026
}
2127

28+
impl TigerTreeCore {
29+
fn add_node(&mut self, level: u32, hash: Output<TigerCore>) {
30+
match self.nodes.last() {
31+
Some(last) if last.level == level => {
32+
let new_hash = Tiger::digest([&[NODE_SIG][..], &last.hash, &hash].concat());
33+
self.nodes.pop();
34+
self.add_node(level + 1, new_hash);
35+
}
36+
_ => self.nodes.push(TigerTreeNode { level, hash }),
37+
}
38+
}
39+
}
40+
2241
impl Default for TigerTreeCore {
2342
fn default() -> Self {
2443
Self {
25-
leaves: Vec::default(),
44+
nodes: ArrayVec::default(),
2645
hasher: Tiger::new_with_prefix([LEAF_SIG]),
2746
blocks_processed: 0,
2847
}
@@ -32,6 +51,7 @@ impl Default for TigerTreeCore {
3251
type DataBlockSize = U1024;
3352
const LEAF_SIG: u8 = 0u8;
3453
const NODE_SIG: u8 = 1u8;
54+
const MAX_TREE_HEIGHT: usize = (usize::BITS - DataBlockSize::USIZE.ilog2() + 1) as usize;
3555
/// The number of TigerCore blocks in a TigerTree data block
3656
const LEAF_BLOCKS: usize = DataBlockSize::USIZE / <TigerCore as BlockSizeUser>::BlockSize::USIZE;
3757

@@ -54,7 +74,7 @@ impl TigerTreeCore {
5474
fn finalize_blocks(&mut self) {
5575
let hasher = core::mem::replace(&mut self.hasher, Tiger::new_with_prefix([LEAF_SIG]));
5676
let hash = hasher.finalize();
57-
self.leaves.push(hash);
77+
self.add_node(0, hash);
5878
self.blocks_processed = 0;
5979
}
6080

@@ -89,31 +109,15 @@ impl FixedOutputCore for TigerTreeCore {
89109
self.finalize_blocks()
90110
}
91111

92-
let result = hash_nodes(self.leaves.as_slice());
93-
out.copy_from_slice(&result);
94-
}
95-
}
96-
97-
#[inline]
98-
fn hash_nodes(hashes: &[Output<TigerCore>]) -> Output<TigerCore> {
99-
match hashes.len() {
100-
0 => hash_nodes(&[Tiger::digest([LEAF_SIG])]),
101-
1 => hashes[0],
102-
_ => {
103-
let left_hashes = hashes.iter().step_by(2);
104-
105-
let right_hashes = hashes.iter().map(Some).skip(1).chain([None]).step_by(2);
106-
107-
let next_level_hashes: Vec<Output<TigerCore>> = left_hashes
108-
.zip(right_hashes)
109-
.map(|(left, right)| match right {
110-
Some(right) => Tiger::digest([&[NODE_SIG][..], left, right].concat()),
111-
None => *left,
112-
})
113-
.collect();
114-
115-
hash_nodes(next_level_hashes.as_slice())
112+
let mut hash = self
113+
.nodes
114+
.pop()
115+
.map(|n| n.hash)
116+
.unwrap_or_else(|| Tiger::digest([LEAF_SIG]));
117+
while let Some(left) = self.nodes.pop() {
118+
hash = Tiger::digest([&[NODE_SIG][..], &left.hash, &hash].concat());
116119
}
120+
out.copy_from_slice(hash.as_slice());
117121
}
118122
}
119123

0 commit comments

Comments
 (0)