use sha2::{Digest, Sha256};
pub fn compute_actions_root(
actions: &[([u8; 32], [u8; 32], [u8; 32])],
) -> [u8; 32] {
if actions.is_empty() {
return [0u8; 32];
}
let mut leaves: Vec<[u8; 32]> = actions
.iter()
.map(|(cmx, nullifier, epk)| {
let mut hasher = Sha256::new();
hasher.update(cmx);
hasher.update(nullifier);
hasher.update(epk);
hasher.finalize().into()
})
.collect();
let target_len = leaves.len().next_power_of_two();
leaves.resize(target_len, [0u8; 32]);
let mut level = leaves;
while level.len() > 1 {
let mut next_level = Vec::with_capacity(level.len() / 2);
for pair in level.chunks(2) {
let mut hasher = Sha256::new();
hasher.update(pair[0]);
hasher.update(pair[1]);
next_level.push(hasher.finalize().into());
}
level = next_level;
}
level[0]
}
pub fn update_actions_commitment(
prev: &[u8; 32],
actions_root: &[u8; 32],
height: u32,
) -> [u8; 32] {
use blake2::digest::typenum::U32;
use blake2::Blake2b;
let mut hasher = <Blake2b<U32>>::new();
hasher.update(b"ZYNC_actions_v1");
hasher.update(prev);
hasher.update(actions_root);
hasher.update(height.to_le_bytes());
let hash = hasher.finalize();
let mut result = [0u8; 32];
result.copy_from_slice(&hash);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_actions_root() {
let root = compute_actions_root(&[]);
assert_eq!(root, [0u8; 32]);
}
#[test]
fn test_single_action_root() {
let cmx = [1u8; 32];
let nullifier = [2u8; 32];
let epk = [3u8; 32];
let root = compute_actions_root(&[(cmx, nullifier, epk)]);
let mut hasher = Sha256::new();
hasher.update(cmx);
hasher.update(nullifier);
hasher.update(epk);
let leaf: [u8; 32] = hasher.finalize().into();
assert_eq!(root, leaf);
assert_ne!(root, [0u8; 32]);
}
#[test]
fn test_actions_root_deterministic() {
let actions = vec![
([10u8; 32], [20u8; 32], [30u8; 32]),
([40u8; 32], [50u8; 32], [60u8; 32]),
([70u8; 32], [80u8; 32], [90u8; 32]),
];
let root1 = compute_actions_root(&actions);
let root2 = compute_actions_root(&actions);
assert_eq!(root1, root2);
assert_ne!(root1, [0u8; 32]);
}
#[test]
fn test_actions_commitment_chain() {
let prev = [0u8; 32];
let root_a = [1u8; 32];
let root_b = [2u8; 32];
let c1 = update_actions_commitment(&prev, &root_a, 100);
let c2 = update_actions_commitment(&prev, &root_b, 100);
assert_ne!(c1, c2);
let c3 = update_actions_commitment(&prev, &root_a, 101);
assert_ne!(c1, c3);
let c4 = update_actions_commitment(&[0xffu8; 32], &root_a, 100);
assert_ne!(c1, c4);
let c5 = update_actions_commitment(&prev, &root_a, 100);
assert_eq!(c1, c5);
}
#[test]
fn test_two_actions_root() {
let a1 = ([1u8; 32], [2u8; 32], [3u8; 32]);
let a2 = ([4u8; 32], [5u8; 32], [6u8; 32]);
let root = compute_actions_root(&[a1, a2]);
let mut h1 = Sha256::new();
h1.update(a1.0);
h1.update(a1.1);
h1.update(a1.2);
let leaf1: [u8; 32] = h1.finalize().into();
let mut h2 = Sha256::new();
h2.update(a2.0);
h2.update(a2.1);
h2.update(a2.2);
let leaf2: [u8; 32] = h2.finalize().into();
let mut h3 = Sha256::new();
h3.update(leaf1);
h3.update(leaf2);
let expected: [u8; 32] = h3.finalize().into();
assert_eq!(root, expected);
}
#[test]
fn test_three_actions_padded() {
let a1 = ([1u8; 32], [2u8; 32], [3u8; 32]);
let a2 = ([4u8; 32], [5u8; 32], [6u8; 32]);
let a3 = ([7u8; 32], [8u8; 32], [9u8; 32]);
let root = compute_actions_root(&[a1, a2, a3]);
assert_ne!(root, [0u8; 32]);
let root_reordered = compute_actions_root(&[a2, a1, a3]);
assert_ne!(root, root_reordered);
}
}