use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MerkleLeaf {
pub invention_id: String,
pub witness_commitment: [u8; 32],
pub registered_at: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MerkleTimestampRoot {
pub root_hash: [u8; 32],
pub leaf_count: usize,
pub created_at: u64,
pub sequence_number: u64,
pub previous_root: Option<[u8; 32]>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MerkleInclusionProof {
pub leaf: MerkleLeaf,
pub leaf_index: usize,
pub siblings: Vec<MerkleSibling>,
pub root: MerkleTimestampRoot,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MerkleSibling {
pub hash: [u8; 32],
pub position: SiblingPosition,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum SiblingPosition {
Left,
Right,
}
pub struct MerkleTimestampBuilder {
leaves: Vec<MerkleLeaf>,
roots: Vec<MerkleTimestampRoot>,
max_roots: usize,
next_sequence: u64,
}
impl MerkleTimestampBuilder {
pub fn new() -> Self {
Self {
leaves: Vec::new(),
roots: Vec::new(),
max_roots: 256,
next_sequence: 0,
}
}
pub fn with_max_roots(max: usize) -> Self {
Self {
leaves: Vec::new(),
roots: Vec::new(),
max_roots: max,
next_sequence: 0,
}
}
pub fn add_leaf(&mut self, leaf: MerkleLeaf) {
self.leaves.push(leaf);
}
pub fn leaf_count(&self) -> usize {
self.leaves.len()
}
pub fn compute_root(&mut self, now: u64) -> MerkleTimestampRoot {
let leaf_hashes: Vec<[u8; 32]> = self.leaves.iter().map(blake3_leaf).collect();
let root_hash = merkle_root_from_hashes(&leaf_hashes);
let previous_root = self.roots.last().map(|r| r.root_hash);
let seq = self.next_sequence;
self.next_sequence += 1;
let root = MerkleTimestampRoot {
root_hash,
leaf_count: self.leaves.len(),
created_at: now,
sequence_number: seq,
previous_root,
};
if self.roots.len() >= self.max_roots {
self.roots.remove(0);
}
self.roots.push(root.clone());
self.leaves.clear();
root
}
pub fn generate_proof(&self, invention_id: &str) -> Option<MerkleInclusionProof> {
let leaf_index = self
.leaves
.iter()
.position(|l| l.invention_id == invention_id)?;
let leaf_hashes: Vec<[u8; 32]> = self.leaves.iter().map(blake3_leaf).collect();
let siblings = merkle_siblings(&leaf_hashes, leaf_index);
let root_hash = merkle_root_from_hashes(&leaf_hashes);
let previous_root = self.roots.last().map(|r| r.root_hash);
let root = MerkleTimestampRoot {
root_hash,
leaf_count: self.leaves.len(),
created_at: 0, sequence_number: self.next_sequence,
previous_root,
};
Some(MerkleInclusionProof {
leaf: self.leaves[leaf_index].clone(),
leaf_index,
siblings,
root,
})
}
pub fn verify_proof(proof: &MerkleInclusionProof) -> bool {
let mut current = blake3_leaf(&proof.leaf);
for sibling in &proof.siblings {
current = match sibling.position {
SiblingPosition::Left => blake3_hash_pair(&sibling.hash, ¤t),
SiblingPosition::Right => blake3_hash_pair(¤t, &sibling.hash),
};
}
current == proof.root.root_hash
}
pub fn latest_root(&self) -> Option<&MerkleTimestampRoot> {
self.roots.last()
}
pub fn root_history(&self) -> &[MerkleTimestampRoot] {
&self.roots
}
pub fn find_root_by_sequence(&self, seq: u64) -> Option<&MerkleTimestampRoot> {
self.roots.iter().find(|r| r.sequence_number == seq)
}
}
impl Default for MerkleTimestampBuilder {
fn default() -> Self {
Self::new()
}
}
fn blake3_hash_pair(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(left);
hasher.update(right);
*hasher.finalize().as_bytes()
}
fn blake3_leaf(leaf: &MerkleLeaf) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(leaf.invention_id.as_bytes());
hasher.update(&leaf.witness_commitment);
hasher.update(&leaf.registered_at.to_le_bytes());
*hasher.finalize().as_bytes()
}
fn merkle_root_from_hashes(hashes: &[[u8; 32]]) -> [u8; 32] {
if hashes.is_empty() {
return *blake3::hash(b"").as_bytes();
}
if hashes.len() == 1 {
return hashes[0];
}
let mut layer = hashes.to_vec();
while layer.len() > 1 {
let mut next = Vec::with_capacity((layer.len() + 1) / 2);
let mut i = 0;
while i + 1 < layer.len() {
next.push(blake3_hash_pair(&layer[i], &layer[i + 1]));
i += 2;
}
if i < layer.len() {
next.push(layer[i]);
}
layer = next;
}
layer[0]
}
fn merkle_siblings(hashes: &[[u8; 32]], index: usize) -> Vec<MerkleSibling> {
if hashes.len() <= 1 {
return Vec::new();
}
let mut siblings = Vec::new();
let mut layer = hashes.to_vec();
let mut idx = index;
while layer.len() > 1 {
let pair_start = idx & !1; let sibling_idx = if idx % 2 == 0 { idx + 1 } else { idx - 1 };
if sibling_idx < layer.len() {
let position = if idx % 2 == 0 {
SiblingPosition::Right
} else {
SiblingPosition::Left
};
siblings.push(MerkleSibling {
hash: layer[sibling_idx],
position,
});
}
let mut next = Vec::with_capacity((layer.len() + 1) / 2);
let mut i = 0;
while i + 1 < layer.len() {
next.push(blake3_hash_pair(&layer[i], &layer[i + 1]));
i += 2;
}
if i < layer.len() {
next.push(layer[i]);
}
idx = pair_start / 2;
layer = next;
}
siblings
}
#[cfg(test)]
mod tests {
use super::*;
fn make_leaf(id: &str, ts: u64) -> MerkleLeaf {
MerkleLeaf {
invention_id: id.to_string(),
witness_commitment: *blake3::hash(id.as_bytes()).as_bytes(),
registered_at: ts,
}
}
#[test]
fn empty_tree_computes_deterministic_root() {
let mut builder = MerkleTimestampBuilder::new();
let root = builder.compute_root(1000);
assert_eq!(root.leaf_count, 0);
assert_eq!(root.root_hash, *blake3::hash(b"").as_bytes());
assert_eq!(root.sequence_number, 0);
assert!(root.previous_root.is_none());
}
#[test]
fn single_leaf_root_equals_leaf_hash() {
let mut builder = MerkleTimestampBuilder::new();
let leaf = make_leaf("inv-001", 100);
let expected = blake3_leaf(&leaf);
builder.add_leaf(leaf);
let root = builder.compute_root(200);
assert_eq!(root.leaf_count, 1);
assert_eq!(root.root_hash, expected);
}
#[test]
fn two_leaves_root_is_pair_hash() {
let mut builder = MerkleTimestampBuilder::new();
let l0 = make_leaf("inv-0", 10);
let l1 = make_leaf("inv-1", 20);
let h0 = blake3_leaf(&l0);
let h1 = blake3_leaf(&l1);
builder.add_leaf(l0);
builder.add_leaf(l1);
let root = builder.compute_root(30);
assert_eq!(root.leaf_count, 2);
assert_eq!(root.root_hash, blake3_hash_pair(&h0, &h1));
}
#[test]
fn three_leaves_odd_promotion() {
let mut builder = MerkleTimestampBuilder::new();
let l0 = make_leaf("a", 1);
let l1 = make_leaf("b", 2);
let l2 = make_leaf("c", 3);
let h0 = blake3_leaf(&l0);
let h1 = blake3_leaf(&l1);
let h2 = blake3_leaf(&l2);
builder.add_leaf(l0);
builder.add_leaf(l1);
builder.add_leaf(l2);
let expected = blake3_hash_pair(&blake3_hash_pair(&h0, &h1), &h2);
let root = builder.compute_root(100);
assert_eq!(root.root_hash, expected);
assert_eq!(root.leaf_count, 3);
}
#[test]
fn four_leaves_balanced() {
let mut builder = MerkleTimestampBuilder::new();
let leaves: Vec<_> = (0..4)
.map(|i| make_leaf(&format!("inv-{i}"), i as u64))
.collect();
let hashes: Vec<_> = leaves.iter().map(|l| blake3_leaf(l)).collect();
for l in leaves {
builder.add_leaf(l);
}
let p01 = blake3_hash_pair(&hashes[0], &hashes[1]);
let p23 = blake3_hash_pair(&hashes[2], &hashes[3]);
let expected = blake3_hash_pair(&p01, &p23);
let root = builder.compute_root(100);
assert_eq!(root.root_hash, expected);
assert_eq!(root.leaf_count, 4);
}
#[test]
fn eight_leaves_balanced() {
let mut builder = MerkleTimestampBuilder::new();
let leaves: Vec<_> = (0..8)
.map(|i| make_leaf(&format!("inv-{i}"), i as u64))
.collect();
let hashes: Vec<_> = leaves.iter().map(|l| blake3_leaf(l)).collect();
for l in leaves {
builder.add_leaf(l);
}
let l1: Vec<_> = hashes
.chunks(2)
.map(|c| blake3_hash_pair(&c[0], &c[1]))
.collect();
let l2: Vec<_> = l1
.chunks(2)
.map(|c| blake3_hash_pair(&c[0], &c[1]))
.collect();
let expected = blake3_hash_pair(&l2[0], &l2[1]);
let root = builder.compute_root(100);
assert_eq!(root.root_hash, expected);
}
#[test]
fn inclusion_proof_verifies_each_position() {
let mut builder = MerkleTimestampBuilder::new();
let ids: Vec<String> = (0..5).map(|i| format!("inv-{i}")).collect();
for (i, id) in ids.iter().enumerate() {
builder.add_leaf(make_leaf(id, i as u64 * 10));
}
for id in &ids {
let proof = builder.generate_proof(id).expect("proof should exist");
assert_eq!(&proof.leaf.invention_id, id);
assert!(
MerkleTimestampBuilder::verify_proof(&proof),
"proof for {id} should verify"
);
}
}
#[test]
fn proof_for_nonexistent_leaf_is_none() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("inv-0", 1));
assert!(builder.generate_proof("inv-999").is_none());
}
#[test]
fn tampered_sibling_fails_verification() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("inv-0", 1));
builder.add_leaf(make_leaf("inv-1", 2));
let mut proof = builder.generate_proof("inv-0").unwrap();
assert!(MerkleTimestampBuilder::verify_proof(&proof));
if let Some(sibling) = proof.siblings.first_mut() {
sibling.hash[0] ^= 0x01;
}
assert!(!MerkleTimestampBuilder::verify_proof(&proof));
}
#[test]
fn tampered_leaf_fails_verification() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("inv-0", 1));
builder.add_leaf(make_leaf("inv-1", 2));
let mut proof = builder.generate_proof("inv-0").unwrap();
proof.leaf.registered_at = 999; assert!(!MerkleTimestampBuilder::verify_proof(&proof));
}
#[test]
fn root_chaining() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("a", 1));
let root1 = builder.compute_root(100);
assert!(root1.previous_root.is_none());
builder.add_leaf(make_leaf("b", 2));
let root2 = builder.compute_root(200);
assert_eq!(root2.previous_root, Some(root1.root_hash));
}
#[test]
fn sequence_numbers_increment() {
let mut builder = MerkleTimestampBuilder::new();
for i in 0..5u64 {
builder.add_leaf(make_leaf(&format!("inv-{i}"), i));
let root = builder.compute_root(i * 100);
assert_eq!(root.sequence_number, i);
}
}
#[test]
fn ring_buffer_drops_oldest() {
let max = 4;
let mut builder = MerkleTimestampBuilder::with_max_roots(max);
for i in 0..6u64 {
builder.add_leaf(make_leaf(&format!("inv-{i}"), i));
builder.compute_root(i * 100);
}
let history = builder.root_history();
assert_eq!(history.len(), max);
assert_eq!(history[0].sequence_number, 2);
assert_eq!(history[3].sequence_number, 5);
}
#[test]
fn serde_roundtrip_root() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("inv-0", 1));
let root = builder.compute_root(100);
let json = serde_json::to_string(&root).unwrap();
let deser: MerkleTimestampRoot = serde_json::from_str(&json).unwrap();
assert_eq!(root, deser);
}
#[test]
fn serde_roundtrip_proof() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("inv-0", 1));
builder.add_leaf(make_leaf("inv-1", 2));
builder.add_leaf(make_leaf("inv-2", 3));
let proof = builder.generate_proof("inv-1").unwrap();
let json = serde_json::to_string(&proof).unwrap();
let deser: MerkleInclusionProof = serde_json::from_str(&json).unwrap();
assert_eq!(proof, deser);
assert!(MerkleTimestampBuilder::verify_proof(&deser));
}
#[test]
fn large_batch_all_proofs_verify() {
let mut builder = MerkleTimestampBuilder::new();
let ids: Vec<String> = (0..100).map(|i| format!("inv-{i:04}")).collect();
for (i, id) in ids.iter().enumerate() {
builder.add_leaf(make_leaf(id, i as u64));
}
for id in &ids {
let proof = builder.generate_proof(id).expect("proof should exist");
assert!(
MerkleTimestampBuilder::verify_proof(&proof),
"proof for {id} should verify"
);
}
}
#[test]
fn find_root_by_sequence_works() {
let mut builder = MerkleTimestampBuilder::new();
for i in 0..5u64 {
builder.add_leaf(make_leaf(&format!("inv-{i}"), i));
builder.compute_root(i * 100);
}
for seq in 0..5u64 {
let root = builder.find_root_by_sequence(seq);
assert!(root.is_some(), "sequence {seq} should exist");
assert_eq!(root.unwrap().sequence_number, seq);
}
assert!(builder.find_root_by_sequence(99).is_none());
}
#[test]
fn latest_root_tracks_most_recent() {
let mut builder = MerkleTimestampBuilder::new();
assert!(builder.latest_root().is_none());
builder.add_leaf(make_leaf("a", 1));
let r1 = builder.compute_root(100);
assert_eq!(builder.latest_root().unwrap().root_hash, r1.root_hash);
builder.add_leaf(make_leaf("b", 2));
let r2 = builder.compute_root(200);
assert_eq!(builder.latest_root().unwrap().root_hash, r2.root_hash);
}
#[test]
fn leaves_cleared_after_compute_root() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("a", 1));
assert_eq!(builder.leaf_count(), 1);
builder.compute_root(100);
assert_eq!(builder.leaf_count(), 0);
}
#[test]
fn proof_single_leaf_batch() {
let mut builder = MerkleTimestampBuilder::new();
builder.add_leaf(make_leaf("solo", 42));
let proof = builder.generate_proof("solo").unwrap();
assert!(proof.siblings.is_empty());
assert!(MerkleTimestampBuilder::verify_proof(&proof));
}
}