use cml::{AlgView, Hasher, carry, compute_root};
use sha2::{Digest, Sha256};
#[derive(Debug)]
struct Sha256Hasher;
impl Hasher for Sha256Hasher {
fn leaf(&self, data: &[u8]) -> Vec<u8> {
Sha256::digest(data).to_vec()
}
fn node(&self, children: &[&[u8]]) -> Vec<u8> {
let mut h = Sha256::new();
for child in children {
h.update(child);
}
h.finalize().to_vec()
}
fn empty(&self) -> Vec<u8> {
Sha256::digest(b"").to_vec()
}
fn hash(&self, data: &[u8]) -> Vec<u8> {
Sha256::digest(data).to_vec()
}
fn clone_box(&self) -> Box<dyn Hasher> {
Box::new(Sha256Hasher)
}
}
fn fresh_view() -> AlgView {
AlgView {
hasher: Box::new(Sha256Hasher),
epochs: vec![(0, u64::MAX)],
frontier: Vec::new(),
frontier_coords: Vec::new(),
}
}
fn carry_run(digests: &[Vec<u8>], arity: u64) -> AlgView {
let mut view = fresh_view();
let mut sink: Vec<(u64, u64, u32, Vec<u8>)> = Vec::new();
for (i, d) in digests.iter().enumerate() {
carry::<std::convert::Infallible>(&mut view, 0, d.clone(), i as u64, arity, &mut sink)
.expect("well-formed carry never underflows");
}
view
}
#[test]
fn member_root_of_same_value_run_is_the_single_value() {
let hasher = Sha256Hasher;
let arity = 2u64;
let d = hasher.leaf(b"same");
for &width in &[1usize, 2, 4, 8] {
let run = vec![d.clone(); width];
let view = carry_run(&run, arity);
assert_eq!(
compute_root(&view, arity as usize),
d,
"a same-value run of width {width} must reduce to the single value"
);
}
let root_two = compute_root(&carry_run(&vec![d.clone(); 2], arity), arity as usize);
let root_eight = compute_root(&carry_run(&vec![d.clone(); 8], arity), arity as usize);
assert_eq!(
root_two, root_eight,
"the CML member root must not encode pre-collapse multiplicity"
);
}
#[test]
fn member_root_of_distinct_values_hashes_not_collapses() {
let hasher = Sha256Hasher;
let arity = 2u64;
let a = hasher.leaf(b"a");
let b = hasher.leaf(b"b");
let view = carry_run(&[a.clone(), b.clone()], arity);
let root = compute_root(&view, arity as usize);
assert_eq!(root, hasher.node(&[&a, &b]));
assert_ne!(root, a);
assert_ne!(root, b);
}
#[test]
fn member_root_of_null_run_is_null_no_count_tracked() {
let hasher = Sha256Hasher;
let arity = 2u64;
let null = hasher.null();
for &width in &[1usize, 2, 4] {
let run = vec![null.clone(); width];
let view = carry_run(&run, arity);
assert_eq!(
compute_root(&view, arity as usize),
null,
"an all-null run of width {width} reduces to null at the CML layer"
);
}
}