use sha2::{Digest, Sha256};
use crate::encoding::{u64_be, uuid_to_bytes};
use crate::{domain, CryptoError};
#[derive(Debug)]
pub struct LeafParams<'a> {
pub tenant_id: &'a str,
pub store_id: &'a str,
pub sequence_number: u64,
pub event_signing_hash: &'a [u8; 32],
pub agent_signature: &'a [u8; 64],
}
pub fn compute_leaf_hash(params: &LeafParams<'_>) -> Result<[u8; 32], CryptoError> {
let mut hasher = Sha256::new();
hasher.update(domain::LEAF);
hasher.update(uuid_to_bytes(params.tenant_id)?);
hasher.update(uuid_to_bytes(params.store_id)?);
hasher.update(u64_be(params.sequence_number));
hasher.update(params.event_signing_hash);
hasher.update(params.agent_signature);
Ok(hasher.finalize().into())
}
#[must_use]
pub fn compute_node_hash(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(domain::NODE);
hasher.update(left);
hasher.update(right);
hasher.finalize().into()
}
#[must_use]
pub fn compute_pad_leaf() -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(domain::PAD_LEAF);
hasher.finalize().into()
}
pub fn compute_stream_id(tenant_id: &str, store_id: &str) -> Result<[u8; 32], CryptoError> {
let mut hasher = Sha256::new();
hasher.update(domain::STREAM);
hasher.update(uuid_to_bytes(tenant_id)?);
hasher.update(uuid_to_bytes(store_id)?);
Ok(hasher.finalize().into())
}
#[derive(Debug)]
pub struct ReceiptParams<'a> {
pub tenant_id: &'a str,
pub store_id: &'a str,
pub event_id: &'a str,
pub sequence_number: u64,
pub event_signing_hash: &'a [u8; 32],
}
pub fn compute_receipt_hash(params: &ReceiptParams<'_>) -> Result<[u8; 32], CryptoError> {
let mut hasher = Sha256::new();
hasher.update(domain::RECEIPT);
hasher.update(uuid_to_bytes(params.tenant_id)?);
hasher.update(uuid_to_bytes(params.store_id)?);
hasher.update(uuid_to_bytes(params.event_id)?);
hasher.update(u64_be(params.sequence_number));
hasher.update(params.event_signing_hash);
Ok(hasher.finalize().into())
}
#[must_use]
pub fn compute_merkle_root(leaves: &[[u8; 32]]) -> [u8; 32] {
if leaves.is_empty() {
return compute_pad_leaf();
}
if leaves.len() == 1 {
return leaves[0];
}
let target = leaves.len().next_power_of_two();
let pad = compute_pad_leaf();
let mut current: Vec<[u8; 32]> = leaves.to_vec();
current.resize(target, pad);
while current.len() > 1 {
let mut next = Vec::with_capacity(current.len() / 2);
for chunk in current.chunks(2) {
next.push(compute_node_hash(&chunk[0], &chunk[1]));
}
current = next;
}
current[0]
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_UUID: &str = "550e8400-e29b-41d4-a716-446655440000";
#[test]
fn pad_leaf_deterministic() {
let p1 = compute_pad_leaf();
let p2 = compute_pad_leaf();
assert_eq!(p1, p2);
assert_eq!(p1.len(), 32);
}
#[test]
fn node_hash_deterministic() {
let left = [1u8; 32];
let right = [2u8; 32];
let h1 = compute_node_hash(&left, &right);
let h2 = compute_node_hash(&left, &right);
assert_eq!(h1, h2);
}
#[test]
fn node_hash_order_matters() {
let a = [1u8; 32];
let b = [2u8; 32];
let h1 = compute_node_hash(&a, &b);
let h2 = compute_node_hash(&b, &a);
assert_ne!(h1, h2);
}
#[test]
fn stream_id_deterministic() {
let s1 = compute_stream_id(TEST_UUID, TEST_UUID).unwrap();
let s2 = compute_stream_id(TEST_UUID, TEST_UUID).unwrap();
assert_eq!(s1, s2);
}
#[test]
fn leaf_hash_deterministic() {
let event_hash = [0u8; 32];
let sig = [0u8; 64];
let params = LeafParams {
tenant_id: TEST_UUID,
store_id: TEST_UUID,
sequence_number: 1,
event_signing_hash: &event_hash,
agent_signature: &sig,
};
let h1 = compute_leaf_hash(¶ms).unwrap();
let h2 = compute_leaf_hash(¶ms).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn leaf_hash_varies_with_sequence() {
let event_hash = [0u8; 32];
let sig = [0u8; 64];
let p1 = LeafParams {
tenant_id: TEST_UUID,
store_id: TEST_UUID,
sequence_number: 1,
event_signing_hash: &event_hash,
agent_signature: &sig,
};
let p2 = LeafParams {
sequence_number: 2,
..p1
};
assert_ne!(
compute_leaf_hash(&p1).unwrap(),
compute_leaf_hash(&p2).unwrap()
);
}
#[test]
fn receipt_hash_deterministic() {
let event_hash = [0u8; 32];
let params = ReceiptParams {
tenant_id: TEST_UUID,
store_id: TEST_UUID,
event_id: TEST_UUID,
sequence_number: 42,
event_signing_hash: &event_hash,
};
let h1 = compute_receipt_hash(¶ms).unwrap();
let h2 = compute_receipt_hash(¶ms).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn merkle_root_single_leaf() {
let leaf = [42u8; 32];
let root = compute_merkle_root(&[leaf]);
assert_eq!(root, leaf);
}
#[test]
fn merkle_root_empty() {
let root = compute_merkle_root(&[]);
assert_eq!(root, compute_pad_leaf());
}
#[test]
fn merkle_root_two_leaves() {
let a = [1u8; 32];
let b = [2u8; 32];
let root = compute_merkle_root(&[a, b]);
assert_eq!(root, compute_node_hash(&a, &b));
}
#[test]
fn merkle_root_three_leaves_pads_to_four() {
let a = [1u8; 32];
let b = [2u8; 32];
let c = [3u8; 32];
let pad = compute_pad_leaf();
let root = compute_merkle_root(&[a, b, c]);
let expected = compute_node_hash(
&compute_node_hash(&a, &b),
&compute_node_hash(&c, &pad),
);
assert_eq!(root, expected);
}
#[test]
fn merkle_root_four_leaves() {
let a = [1u8; 32];
let b = [2u8; 32];
let c = [3u8; 32];
let d = [4u8; 32];
let root = compute_merkle_root(&[a, b, c, d]);
let expected = compute_node_hash(
&compute_node_hash(&a, &b),
&compute_node_hash(&c, &d),
);
assert_eq!(root, expected);
}
#[test]
fn merkle_root_deterministic() {
let leaves: Vec<[u8; 32]> = (0..7).map(|i| [i as u8; 32]).collect();
let r1 = compute_merkle_root(&leaves);
let r2 = compute_merkle_root(&leaves);
assert_eq!(r1, r2);
}
}