1use sha2::{Digest, Sha256};
7
8pub fn compute_actions_root(
14 actions: &[([u8; 32], [u8; 32], [u8; 32])],
15) -> [u8; 32] {
16 if actions.is_empty() {
17 return [0u8; 32];
18 }
19
20 let mut leaves: Vec<[u8; 32]> = actions
22 .iter()
23 .map(|(cmx, nullifier, epk)| {
24 let mut hasher = Sha256::new();
25 hasher.update(cmx);
26 hasher.update(nullifier);
27 hasher.update(epk);
28 hasher.finalize().into()
29 })
30 .collect();
31
32 let target_len = leaves.len().next_power_of_two();
34 leaves.resize(target_len, [0u8; 32]);
35
36 let mut level = leaves;
38 while level.len() > 1 {
39 let mut next_level = Vec::with_capacity(level.len() / 2);
40 for pair in level.chunks(2) {
41 let mut hasher = Sha256::new();
42 hasher.update(pair[0]);
43 hasher.update(pair[1]);
44 next_level.push(hasher.finalize().into());
45 }
46 level = next_level;
47 }
48
49 level[0]
50}
51
52pub fn update_actions_commitment(
58 prev: &[u8; 32],
59 actions_root: &[u8; 32],
60 height: u32,
61) -> [u8; 32] {
62 use blake2::digest::typenum::U32;
63 use blake2::Blake2b;
64
65 let mut hasher = <Blake2b<U32>>::new();
66 hasher.update(b"ZYNC_actions_v1");
67 hasher.update(prev);
68 hasher.update(actions_root);
69 hasher.update(height.to_le_bytes());
70
71 let hash = hasher.finalize();
72 let mut result = [0u8; 32];
73 result.copy_from_slice(&hash);
74 result
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_empty_actions_root() {
83 let root = compute_actions_root(&[]);
84 assert_eq!(root, [0u8; 32]);
85 }
86
87 #[test]
88 fn test_single_action_root() {
89 let cmx = [1u8; 32];
90 let nullifier = [2u8; 32];
91 let epk = [3u8; 32];
92
93 let root = compute_actions_root(&[(cmx, nullifier, epk)]);
94
95 let mut hasher = Sha256::new();
98 hasher.update(cmx);
99 hasher.update(nullifier);
100 hasher.update(epk);
101 let leaf: [u8; 32] = hasher.finalize().into();
102
103 assert_eq!(root, leaf);
107 assert_ne!(root, [0u8; 32]);
108 }
109
110 #[test]
111 fn test_actions_root_deterministic() {
112 let actions = vec![
113 ([10u8; 32], [20u8; 32], [30u8; 32]),
114 ([40u8; 32], [50u8; 32], [60u8; 32]),
115 ([70u8; 32], [80u8; 32], [90u8; 32]),
116 ];
117
118 let root1 = compute_actions_root(&actions);
119 let root2 = compute_actions_root(&actions);
120 assert_eq!(root1, root2);
121 assert_ne!(root1, [0u8; 32]);
122 }
123
124 #[test]
125 fn test_actions_commitment_chain() {
126 let prev = [0u8; 32];
127 let root_a = [1u8; 32];
128 let root_b = [2u8; 32];
129
130 let c1 = update_actions_commitment(&prev, &root_a, 100);
132 let c2 = update_actions_commitment(&prev, &root_b, 100);
133 assert_ne!(c1, c2);
134
135 let c3 = update_actions_commitment(&prev, &root_a, 101);
137 assert_ne!(c1, c3);
138
139 let c4 = update_actions_commitment(&[0xffu8; 32], &root_a, 100);
141 assert_ne!(c1, c4);
142
143 let c5 = update_actions_commitment(&prev, &root_a, 100);
145 assert_eq!(c1, c5);
146 }
147
148 #[test]
149 fn test_two_actions_root() {
150 let a1 = ([1u8; 32], [2u8; 32], [3u8; 32]);
151 let a2 = ([4u8; 32], [5u8; 32], [6u8; 32]);
152
153 let root = compute_actions_root(&[a1, a2]);
154
155 let mut h1 = Sha256::new();
157 h1.update(a1.0);
158 h1.update(a1.1);
159 h1.update(a1.2);
160 let leaf1: [u8; 32] = h1.finalize().into();
161
162 let mut h2 = Sha256::new();
163 h2.update(a2.0);
164 h2.update(a2.1);
165 h2.update(a2.2);
166 let leaf2: [u8; 32] = h2.finalize().into();
167
168 let mut h3 = Sha256::new();
169 h3.update(leaf1);
170 h3.update(leaf2);
171 let expected: [u8; 32] = h3.finalize().into();
172
173 assert_eq!(root, expected);
174 }
175
176 #[test]
177 fn test_three_actions_padded() {
178 let a1 = ([1u8; 32], [2u8; 32], [3u8; 32]);
180 let a2 = ([4u8; 32], [5u8; 32], [6u8; 32]);
181 let a3 = ([7u8; 32], [8u8; 32], [9u8; 32]);
182
183 let root = compute_actions_root(&[a1, a2, a3]);
184 assert_ne!(root, [0u8; 32]);
185
186 let root_reordered = compute_actions_root(&[a2, a1, a3]);
188 assert_ne!(root, root_reordered);
189 }
190}