use crate::{CryptoError, TreeHashAlg};
pub const IMPLEMENTED_TREE_HASH: TreeHashAlg = TreeHashAlg::Blake3;
pub fn ensure_supported(alg: TreeHashAlg) -> Result<(), CryptoError> {
match alg {
TreeHashAlg::Blake3 => Ok(()),
TreeHashAlg::Sha256 => Err(CryptoError::UnsupportedAlgorithm),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Leaf(pub [u8; 32]);
pub fn leaf_of(index: u32, nonce: &[u8; 24], tag: &[u8; 16], ct: &[u8]) -> Leaf {
let mut h = blake3::Hasher::new();
h.update(&[0x00]);
h.update(crate::label::LEAF.as_bytes());
h.update(&index.to_be_bytes());
h.update(&(ct.len() as u64).to_be_bytes());
h.update(nonce);
h.update(tag);
h.update(ct);
Leaf(*h.finalize().as_bytes())
}
pub fn node_of(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(&[0x01]);
h.update(crate::label::NODE.as_bytes());
h.update(left);
h.update(right);
*h.finalize().as_bytes()
}
pub fn root_of(leaf_count: u32, apex: &[u8; 32]) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(&[0x02]);
h.update(crate::label::NODE.as_bytes());
h.update(&leaf_count.to_be_bytes());
h.update(apex);
*h.finalize().as_bytes()
}
fn is_promoted(len: usize, index: usize) -> bool {
len % 2 == 1 && index.saturating_add(1) == len
}
fn parent_of(level: &[[u8; 32]], index: usize) -> Result<[u8; 32], CryptoError> {
if is_promoted(level.len(), index) {
return level.get(index).copied().ok_or(CryptoError::IndexOutOfRange);
}
let left_index = index.saturating_sub(index % 2);
let right_index = left_index.saturating_add(1);
let left = level.get(left_index).ok_or(CryptoError::IndexOutOfRange)?;
let right = level.get(right_index).ok_or(CryptoError::IndexOutOfRange)?;
Ok(node_of(left, right))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MerkleTree {
levels: Vec<Vec<[u8; 32]>>,
}
impl MerkleTree {
pub fn build(leaves: &[Leaf]) -> Result<Self, CryptoError> {
if leaves.is_empty() {
return Err(CryptoError::BadLength);
}
u32::try_from(leaves.len()).map_err(|_| CryptoError::BadLength)?;
let mut current: Vec<[u8; 32]> = leaves.iter().map(|leaf| leaf.0).collect();
let mut levels: Vec<Vec<[u8; 32]>> = Vec::new();
while current.len() > 1 {
let mut next: Vec<[u8; 32]> = Vec::with_capacity(current.len().div_ceil(2));
for pair in current.chunks(2) {
match (pair.first(), pair.get(1)) {
(Some(left), Some(right)) => next.push(node_of(left, right)),
(Some(left), None) => next.push(*left),
(None, _) => {}
}
}
levels.push(core::mem::replace(&mut current, next));
}
levels.push(current);
Ok(Self { levels })
}
pub fn apex(&self) -> [u8; 32] {
self.levels.last().and_then(|top| top.first()).copied().unwrap_or([0u8; 32])
}
pub fn root(&self) -> [u8; 32] {
root_of(self.leaf_count(), &self.apex())
}
pub fn leaf_count(&self) -> u32 {
let count = self.levels.first().map_or(0, Vec::len);
u32::try_from(count).unwrap_or(u32::MAX)
}
pub fn update_leaf(&mut self, index: u32, leaf: Leaf) -> Result<[u8; 32], CryptoError> {
if index >= self.leaf_count() {
return Err(CryptoError::IndexOutOfRange);
}
let mut position = usize::try_from(index).map_err(|_| CryptoError::IndexOutOfRange)?;
*self
.levels
.first_mut()
.and_then(|bottom| bottom.get_mut(position))
.ok_or(CryptoError::IndexOutOfRange)? = leaf.0;
let mut depth = 0usize;
loop {
let level = self.levels.get(depth).ok_or(CryptoError::IndexOutOfRange)?;
if level.len() <= 1 {
break;
}
let parent = parent_of(level, position)?;
let parent_position = position / 2;
let above = depth.saturating_add(1);
*self
.levels
.get_mut(above)
.and_then(|upper| upper.get_mut(parent_position))
.ok_or(CryptoError::TreeMismatch)? = parent;
position = parent_position;
depth = above;
}
Ok(self.root())
}
pub fn proof(&self, index: u32) -> Result<Vec<[u8; 32]>, CryptoError> {
if index >= self.leaf_count() {
return Err(CryptoError::IndexOutOfRange);
}
let mut position = usize::try_from(index).map_err(|_| CryptoError::IndexOutOfRange)?;
let mut path = Vec::with_capacity(self.levels.len());
for level in &self.levels {
if level.len() <= 1 {
break;
}
if !is_promoted(level.len(), position) {
let sibling = if position % 2 == 0 {
position.saturating_add(1)
} else {
position.saturating_sub(1)
};
path.push(*level.get(sibling).ok_or(CryptoError::TreeMismatch)?);
}
position /= 2;
}
Ok(path)
}
pub fn verify_proof(
root: &[u8; 32],
index: u32,
leaf_count: u32,
leaf: &Leaf,
proof: &[[u8; 32]],
) -> bool {
if leaf_count == 0 || index >= leaf_count {
return false;
}
let (Ok(mut len), Ok(mut position)) = (usize::try_from(leaf_count), usize::try_from(index))
else {
return false;
};
let mut accumulator = leaf.0;
let mut used = 0usize;
while len > 1 {
if !is_promoted(len, position) {
let Some(sibling) = proof.get(used) else {
return false;
};
used = used.saturating_add(1);
accumulator = if position % 2 == 0 {
node_of(&accumulator, sibling)
} else {
node_of(sibling, &accumulator)
};
}
position /= 2;
len = len.div_ceil(2);
}
if used != proof.len() {
return false;
}
let candidate = root_of(leaf_count, &accumulator);
crate::digest_eq(&candidate, root)
}
pub fn consistency(&self, old_count: u32) -> Result<Vec<[u8; 32]>, CryptoError> {
let leaves = self.levels.first().ok_or(CryptoError::TreeMismatch)?;
let m = usize::try_from(old_count).map_err(|_| CryptoError::IndexOutOfRange)?;
if m == 0 || m > leaves.len() {
return Err(CryptoError::IndexOutOfRange);
}
let mut path = Vec::new();
if m == leaves.len() {
return Ok(path);
}
if m.is_power_of_two() {
let prefix = leaves.get(..m).ok_or(CryptoError::IndexOutOfRange)?;
path.push(mth(prefix).ok_or(CryptoError::TreeMismatch)?);
}
subproof(m, leaves, true, &mut path).ok_or(CryptoError::TreeMismatch)?;
Ok(path)
}
pub fn verify_consistency(
old_count: u32,
old_root: &[u8; 32],
new_count: u32,
new_root: &[u8; 32],
proof: &[[u8; 32]],
) -> bool {
if old_count == 0 || old_count > new_count {
return false;
}
if old_count == new_count {
return proof.is_empty() && crate::digest_eq(old_root, new_root);
}
let Some((first, rest)) = proof.split_first() else {
return false;
};
let (Some(mut fnode), Some(mut snode)) = (old_count.checked_sub(1), new_count.checked_sub(1)) else {
return false;
};
while fnode % 2 == 1 {
fnode /= 2;
snode /= 2;
}
let mut old_acc = *first;
let mut new_acc = *first;
for sibling in rest {
if snode == 0 {
return false;
}
if fnode % 2 == 1 || fnode == snode {
old_acc = node_of(sibling, &old_acc);
new_acc = node_of(sibling, &new_acc);
while fnode % 2 == 0 && fnode != 0 {
fnode /= 2;
snode /= 2;
}
} else {
new_acc = node_of(&new_acc, sibling);
}
fnode /= 2;
snode /= 2;
}
let old_ok = crate::digest_eq(&root_of(old_count, &old_acc), old_root);
let new_ok = crate::digest_eq(&root_of(new_count, &new_acc), new_root);
snode == 0 && old_ok && new_ok
}
}
fn split_point(n: usize) -> usize {
let mut k = 1usize;
while k.saturating_mul(2) < n {
k = k.saturating_mul(2);
}
k
}
fn mth(leaves: &[[u8; 32]]) -> Option<[u8; 32]> {
match leaves {
[] => None,
[only] => Some(*only),
_ => {
let (left, right) = leaves.split_at_checked(split_point(leaves.len()))?;
Some(node_of(&mth(left)?, &mth(right)?))
}
}
}
fn subproof(m: usize, leaves: &[[u8; 32]], complete: bool, out: &mut Vec<[u8; 32]>) -> Option<()> {
if m == leaves.len() {
if !complete {
out.push(mth(leaves)?);
}
return Some(());
}
let k = split_point(leaves.len());
let (left, right) = leaves.split_at_checked(k)?;
if m <= k {
subproof(m, left, complete, out)?;
out.push(mth(right)?);
} else {
subproof(m.checked_sub(k)?, right, false, out)?;
out.push(mth(left)?);
}
Some(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
mod tests {
use super::*;
use std::collections::BTreeSet;
const SIZES: [u32; 6] = [1, 2, 3, 5, 8, 100];
fn test_leaf(index: u32) -> Leaf {
let seed = *blake3::hash(&index.to_be_bytes()).as_bytes();
let mut nonce = [0u8; 24];
let mut tag = [0u8; 16];
for (dst, src) in nonce.iter_mut().zip(seed.iter()) {
*dst = *src;
}
for (dst, src) in tag.iter_mut().zip(seed.iter().rev()) {
*dst = *src;
}
leaf_of(index, &nonce, &tag, &seed)
}
fn test_leaves(count: u32) -> Vec<Leaf> {
(0..count).map(test_leaf).collect()
}
fn at(leaves: &[Leaf], index: u32) -> Leaf {
leaves.get(usize::try_from(index).unwrap()).copied().unwrap()
}
fn corrupt(leaf: Leaf) -> Leaf {
let mut bytes = leaf.0;
if let Some(first) = bytes.first_mut() {
*first ^= 0x01;
}
Leaf(bytes)
}
fn promotion_pattern(count: u32, index: u32) -> Vec<bool> {
let mut pattern = Vec::new();
let mut len = usize::try_from(count).unwrap();
let mut position = usize::try_from(index).unwrap();
while len > 1 {
pattern.push(is_promoted(len, position));
position /= 2;
len = len.div_ceil(2);
}
pattern
}
#[test]
fn the_only_honoured_tree_hash_is_the_one_the_hasher_computes() {
assert_eq!(IMPLEMENTED_TREE_HASH, TreeHashAlg::Blake3);
assert_eq!(ensure_supported(TreeHashAlg::Blake3), Ok(()));
assert_eq!(
ensure_supported(TreeHashAlg::Sha256),
Err(CryptoError::UnsupportedAlgorithm),
"сборка соглашается на хеш дерева, которого не умеет считать"
);
}
#[test]
fn a_tree_hash_identifier_this_build_cannot_compute_is_refused_at_parse_time() {
assert_eq!(TreeHashAlg::from_u8(1), Ok(TreeHashAlg::Blake3));
assert_eq!(TreeHashAlg::from_u8(2), Err(CryptoError::UnsupportedAlgorithm));
for v in [0u8, 3, 99, 255] {
assert_eq!(TreeHashAlg::from_u8(v), Err(CryptoError::UnsupportedAlgorithm));
}
}
#[test]
fn an_empty_leaf_set_is_rejected() {
assert_eq!(MerkleTree::build(&[]), Err(CryptoError::BadLength));
}
#[test]
fn a_single_leaf_tree_binds_its_root_to_the_leaf_count() {
let leaf = test_leaf(0);
let tree = MerkleTree::build(&[leaf]).unwrap();
assert_eq!(tree.root(), root_of(1, &leaf.0));
assert_ne!(tree.root(), leaf.0, "корень обязан отличаться от вершины");
assert_eq!(tree.leaf_count(), 1);
assert!(tree.proof(0).unwrap().is_empty());
assert!(MerkleTree::verify_proof(&tree.root(), 0, 1, &leaf, &[]));
}
#[test]
fn the_root_of_three_leaves_follows_rfc6962_promotion() {
let leaves = test_leaves(3);
let apex = node_of(&node_of(&at(&leaves, 0).0, &at(&leaves, 1).0), &at(&leaves, 2).0);
assert_eq!(MerkleTree::build(&leaves).unwrap().root(), root_of(3, &apex));
}
#[test]
fn every_leaf_proof_verifies_and_a_corrupted_leaf_does_not() {
for count in SIZES {
let leaves = test_leaves(count);
let tree = MerkleTree::build(&leaves).unwrap();
assert_eq!(tree.leaf_count(), count);
let root = tree.root();
for index in 0..count {
let leaf = at(&leaves, index);
let proof = tree.proof(index).unwrap();
assert!(
MerkleTree::verify_proof(&root, index, count, &leaf, &proof),
"лист {index} из {count} не подтверждается собственным доказательством"
);
assert!(
!MerkleTree::verify_proof(&root, index, count, &corrupt(leaf), &proof),
"изменённый лист {index} из {count} принят как подлинный"
);
}
}
}
#[test]
fn an_incremental_update_equals_a_full_rebuild() {
for count in SIZES {
let leaves = test_leaves(count);
for index in 0..count {
let mut tree = MerkleTree::build(&leaves).unwrap();
let replacement = test_leaf(index.saturating_add(1_000_000));
let new_root = tree.update_leaf(index, replacement).unwrap();
let mut edited = leaves.clone();
*edited.get_mut(usize::try_from(index).unwrap()).unwrap() = replacement;
let rebuilt = MerkleTree::build(&edited).unwrap();
assert_eq!(new_root, rebuilt.root(), "корень после правки {index} из {count}");
assert_eq!(tree, rebuilt, "уровни после правки {index} из {count}");
assert!(MerkleTree::verify_proof(
&new_root,
index,
count,
&replacement,
&tree.proof(index).unwrap()
));
}
}
}
#[test]
fn three_leaves_and_four_leaves_with_a_repeated_tail_have_different_roots() {
let three = test_leaves(3);
let mut four = three.clone();
four.push(at(&three, 2));
let root_three = MerkleTree::build(&three).unwrap().root();
let root_four = MerkleTree::build(&four).unwrap().root();
assert_ne!(root_three, root_four, "продвижение подменено дублированием");
let left = node_of(&at(&three, 0).0, &at(&three, 1).0);
let right = node_of(&at(&three, 2).0, &at(&three, 2).0);
assert_eq!(root_four, root_of(4, &node_of(&left, &right)));
}
#[test]
fn distinct_leaf_sets_produce_distinct_roots() {
let base = test_leaves(8);
let mut sets: Vec<Vec<Leaf>> = (1..=8)
.map(|len| base.iter().copied().take(len).collect::<Vec<Leaf>>())
.collect();
let mut repeated_tail = base.iter().copied().take(3).collect::<Vec<Leaf>>();
repeated_tail.push(at(&base, 2));
sets.push(repeated_tail);
sets.push(vec![at(&base, 1), at(&base, 0)]);
sets.push(vec![at(&base, 0), at(&base, 0)]);
let roots: BTreeSet<[u8; 32]> =
sets.iter().map(|set| MerkleTree::build(set).unwrap().root()).collect();
assert_eq!(roots.len(), sets.len(), "разные наборы листьев дали одинаковый корень");
}
#[test]
fn a_proof_does_not_verify_at_another_index() {
for count in [2u32, 3, 5, 8] {
let leaves = test_leaves(count);
let tree = MerkleTree::build(&leaves).unwrap();
let root = tree.root();
for index in 0..count {
let leaf = at(&leaves, index);
let proof = tree.proof(index).unwrap();
for other in (0..count).filter(|other| *other != index) {
assert!(
!MerkleTree::verify_proof(&root, other, count, &leaf, &proof),
"доказательство листа {index} прошло как доказательство {other} из {count}"
);
}
}
}
}
#[test]
fn a_proof_does_not_verify_under_a_leaf_count_that_changes_the_shape() {
let mut checked = 0u32;
for count in [2u32, 3, 5, 8, 100] {
let leaves = test_leaves(count);
let tree = MerkleTree::build(&leaves).unwrap();
let root = tree.root();
for index in [0, count / 2, count.saturating_sub(1)] {
let leaf = at(&leaves, index);
let proof = tree.proof(index).unwrap();
for wrong in 1..=count.saturating_add(2) {
if wrong <= index
|| promotion_pattern(wrong, index) == promotion_pattern(count, index)
{
continue;
}
checked = checked.saturating_add(1);
assert!(
!MerkleTree::verify_proof(&root, index, wrong, &leaf, &proof),
"доказательство листа {index} прошло при leaf_count {wrong} вместо {count}"
);
}
}
}
assert!(checked > 0, "ни одного различимого leaf_count не нашлось");
}
#[test]
fn a_proof_of_the_wrong_length_is_refused() {
let leaves = test_leaves(5);
let tree = MerkleTree::build(&leaves).unwrap();
let leaf = at(&leaves, 1);
let mut longer = tree.proof(1).unwrap();
longer.push([0x42; 32]);
assert!(!MerkleTree::verify_proof(&tree.root(), 1, 5, &leaf, &longer));
let mut shorter = tree.proof(1).unwrap();
shorter.pop();
assert!(!MerkleTree::verify_proof(&tree.root(), 1, 5, &leaf, &shorter));
}
#[test]
fn an_index_outside_the_tree_is_refused() {
let leaves = test_leaves(5);
let mut tree = MerkleTree::build(&leaves).unwrap();
assert_eq!(tree.proof(5), Err(CryptoError::IndexOutOfRange));
assert_eq!(tree.proof(u32::MAX), Err(CryptoError::IndexOutOfRange));
assert_eq!(tree.update_leaf(5, test_leaf(0)), Err(CryptoError::IndexOutOfRange));
assert!(!MerkleTree::verify_proof(&tree.root(), 5, 5, &at(&leaves, 0), &[]));
assert!(!MerkleTree::verify_proof(&tree.root(), 0, 0, &at(&leaves, 0), &[]));
}
#[test]
fn a_failed_update_leaves_the_tree_untouched() {
let leaves = test_leaves(5);
let mut tree = MerkleTree::build(&leaves).unwrap();
let before = tree.clone();
assert!(tree.update_leaf(99, test_leaf(7)).is_err());
assert_eq!(tree, before);
}
fn raw(leaves: &[Leaf]) -> Vec<[u8; 32]> {
leaves.iter().map(|l| l.0).collect()
}
#[test]
fn the_recursive_mth_is_the_apex_of_the_level_tree() {
for n in 1..=70u32 {
let leaves = test_leaves(n);
let tree = MerkleTree::build(&leaves).unwrap();
assert_eq!(mth(&raw(&leaves)), Some(tree.apex()), "n = {n}");
}
}
#[test]
fn every_prefix_is_proven_consistent_and_nothing_else_is() {
let all = test_leaves(40);
for new in 1..=40u32 {
let tree = MerkleTree::build(&all[..new as usize]).unwrap();
for old in 1..=new {
let old_root = MerkleTree::build(&all[..old as usize]).unwrap().root();
let proof = tree.consistency(old).unwrap();
assert!(
MerkleTree::verify_consistency(old, &old_root, new, &tree.root(), &proof),
"{old} → {new} не сошлось"
);
if old == new {
assert!(proof.is_empty());
continue;
}
let mut bad = old_root;
bad[0] ^= 1;
assert!(!MerkleTree::verify_consistency(old, &bad, new, &tree.root(), &proof));
assert!(!MerkleTree::verify_consistency(old, &old_root, new, &bad, &proof));
if old > 1 {
assert!(!MerkleTree::verify_consistency(old - 1, &old_root, new, &tree.root(), &proof));
}
assert!(!MerkleTree::verify_consistency(old, &old_root, new + 1, &tree.root(), &proof));
for i in 0..proof.len() {
let mut p = proof.clone();
p[i][31] ^= 0x80;
assert!(
!MerkleTree::verify_consistency(old, &old_root, new, &tree.root(), &p),
"{old} → {new}: порча узла {i} прошла"
);
}
let mut shorter = proof.clone();
shorter.pop();
assert!(!MerkleTree::verify_consistency(old, &old_root, new, &tree.root(), &shorter));
let mut longer = proof.clone();
longer.push([7; 32]);
assert!(!MerkleTree::verify_consistency(old, &old_root, new, &tree.root(), &longer));
}
}
}
#[test]
fn a_forked_history_is_not_consistent() {
let honest = test_leaves(9);
let mut forked = honest.clone();
forked[2] = corrupt(forked[2]);
let old_root = MerkleTree::build(&honest[..5]).unwrap().root();
let fork_tree = MerkleTree::build(&forked).unwrap();
let proof = fork_tree.consistency(5).unwrap();
assert!(!MerkleTree::verify_consistency(5, &old_root, 9, &fork_tree.root(), &proof));
let big = MerkleTree::build(&honest).unwrap();
assert!(!MerkleTree::verify_consistency(9, &big.root(), 5, &old_root, &[]));
assert_eq!(big.consistency(0), Err(CryptoError::IndexOutOfRange));
assert_eq!(big.consistency(10), Err(CryptoError::IndexOutOfRange));
}
}