1#![allow(non_snake_case)]
20
21use crate::header::L2BlockHeader;
22use sha2::{Digest, Sha256};
23use thiserror::Error;
24
25pub type Hash32 = [u8; 32];
27
28pub const HEADER_FIELD_DOMAIN: &[u8] = b"dig:l2:header_field:";
30pub const BLOCK_ROOT_DOMAIN: &[u8] = b"dig:l2:block_root:";
32pub const DATA_HASH_DOMAIN: &[u8] = b"dig:l2:data:";
34pub const EMISSION_HASH_DOMAIN: &[u8] = b"dig:l2:emission:";
36pub const MERKLE_LEAF_DOMAIN: &[u8] = b"dig:l2:merkle:leaf:";
38pub const MERKLE_NODE_DOMAIN: &[u8] = b"dig:l2:merkle:node:";
40pub const MERKLE_EMPTY_DOMAIN: &[u8] = b"dig:l2:merkle:empty:";
42
43#[derive(Debug, Error)]
45pub enum DefinitionError {
46 #[error("attester_reward_share is non-zero but no attesters provided")]
48 NoAttestersForNonZeroShare,
49}
50
51fn sha256_concat(parts: &[&[u8]]) -> Hash32 {
52 let mut hasher = Sha256::new();
53 for p in parts {
54 hasher.update(p);
55 }
56 hasher.finalize().into()
57}
58
59pub fn COMPUTE_DATA_HASH(data_byte: u8) -> Hash32 {
63 let b = [data_byte];
64 sha256_concat(&[DATA_HASH_DOMAIN, &b])
65}
66
67pub fn COMPUTE_EMISSION_HASH(pubkey: &[u8; 48], weight: u64) -> Hash32 {
71 let w = weight.to_le_bytes();
72 sha256_concat(&[EMISSION_HASH_DOMAIN, pubkey, &w])
73}
74
75pub fn MERKLE_ROOT(leaves: &[Hash32]) -> Hash32 {
82 if leaves.is_empty() {
83 return sha256_concat(&[MERKLE_EMPTY_DOMAIN]);
84 }
85
86 let mut level: Vec<Hash32> = leaves
87 .iter()
88 .map(|leaf| sha256_concat(&[MERKLE_LEAF_DOMAIN, leaf]))
89 .collect();
90
91 while level.len() > 1 {
92 if level.len() % 2 == 1 {
93 let last = *level.last().unwrap();
94 level.push(last);
95 }
96 let mut next = Vec::with_capacity(level.len() / 2);
97 for pair in level.chunks(2) {
98 let combined = sha256_concat(&[MERKLE_NODE_DOMAIN, &pair[0], &pair[1]]);
99 next.push(combined);
100 }
101 level = next;
102 }
103 level[0]
104}
105
106pub fn COMPUTE_BODY_ROOT(data_root: &Hash32, emissions_root: &Hash32) -> Hash32 {
110 MERKLE_ROOT(&[*data_root, *emissions_root])
111}
112
113pub fn COMPUTE_HEADER_ROOT(args: &L2BlockHeader) -> Hash32 {
118 let v_bytes = args.version.to_le_bytes();
119 let e_bytes = args.epoch.to_le_bytes();
120 let dc_bytes = args.data_count.to_le_bytes();
121 let ec_bytes = args.emissions_count.to_le_bytes();
122
123 let leaves: [Hash32; 8] = [
124 sha256_concat(&[HEADER_FIELD_DOMAIN, b"version", &v_bytes]),
125 sha256_concat(&[HEADER_FIELD_DOMAIN, b"network_id", &args.network_id]),
126 sha256_concat(&[HEADER_FIELD_DOMAIN, b"epoch", &e_bytes]),
127 sha256_concat(&[
128 HEADER_FIELD_DOMAIN,
129 b"prev_block_root",
130 &args.prev_block_root,
131 ]),
132 sha256_concat(&[HEADER_FIELD_DOMAIN, b"body_root", &args.body_root]),
133 sha256_concat(&[HEADER_FIELD_DOMAIN, b"data_count", &dc_bytes]),
134 sha256_concat(&[HEADER_FIELD_DOMAIN, b"emissions_count", &ec_bytes]),
135 sha256_concat(&[
136 HEADER_FIELD_DOMAIN,
137 b"proposer_pubkey",
138 &args.proposer_pubkey,
139 ]),
140 ];
141 MERKLE_ROOT(&leaves)
142}
143
144pub fn COMPUTE_BLOCK_ROOT(header_root: &Hash32, body_root: &Hash32) -> Hash32 {
148 sha256_concat(&[BLOCK_ROOT_DOMAIN, header_root, body_root])
149}
150
151pub type EmissionTuple = ([u8; 48], u64);
154
155pub fn BUILD_CONSENSUS_EMISSIONS(
161 proposer_pubkey: [u8; 48],
162 attester_pubkeys: &[[u8; 48]],
163 proposer_reward_share: u64,
164 attester_reward_share: u64,
165) -> Result<Vec<EmissionTuple>, DefinitionError> {
166 let mut out = Vec::with_capacity(1 + attester_pubkeys.len());
167 out.push((proposer_pubkey, proposer_reward_share));
168
169 if attester_pubkeys.is_empty() {
170 if attester_reward_share > 0 {
171 return Err(DefinitionError::NoAttestersForNonZeroShare);
172 }
173 return Ok(out);
174 }
175
176 let per_attester = attester_reward_share / (attester_pubkeys.len() as u64);
177 for pk in attester_pubkeys {
178 out.push((*pk, per_attester));
179 }
180 Ok(out)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 fn h32(x: u8) -> Hash32 {
188 let mut a = [0u8; 32];
190 a[0] = x;
191 a
192 }
193
194 #[test]
195 fn data_hash_changes_with_value() {
196 let h1 = COMPUTE_DATA_HASH(0);
197 let h2 = COMPUTE_DATA_HASH(1);
198 assert_ne!(h1, h2);
199 }
200
201 #[test]
202 fn emission_hash_domain_separated() {
203 let pk = [1u8; 48];
204 let h1 = COMPUTE_EMISSION_HASH(&pk, 10);
205 let h2 = sha256_concat(&[DATA_HASH_DOMAIN, &pk, &10u64.to_le_bytes()]);
206 assert_ne!(h1, h2); }
208
209 #[test]
210 fn merkle_root_empty() {
211 let r = MERKLE_ROOT(&[]);
212 let expect = sha256_concat(&[MERKLE_EMPTY_DOMAIN]);
213 assert_eq!(r, expect);
214 }
215
216 #[test]
217 fn merkle_root_single() {
218 let leaf = h32(7);
219 let r = MERKLE_ROOT(&[leaf]);
220 assert_eq!(r, sha256_concat(&[MERKLE_LEAF_DOMAIN, &leaf]));
222 }
223
224 #[test]
225 fn merkle_root_odd_duplication() {
226 let leaves = [h32(1), h32(2), h32(3)];
227 let r = MERKLE_ROOT(&leaves);
228 let r2 = MERKLE_ROOT(&[h32(1), h32(2), h32(4)]);
230 assert_ne!(r, r2);
231 }
232
233 #[test]
234 fn body_root_is_merkle_of_two() {
235 let d = h32(0x11);
236 let e = h32(0x22);
237 let r = COMPUTE_BODY_ROOT(&d, &e);
238 let r2 = MERKLE_ROOT(&[d, e]);
239 assert_eq!(r, r2);
240 }
241
242 #[test]
243 fn header_root_field_permutation_changes_root() {
244 let network_id = [2u8; 32];
245 let prev = [3u8; 32];
246 let body = [4u8; 32];
247 let proposer = [5u8; 48];
248 let r1_header = L2BlockHeader {
249 version: 1,
250 network_id,
251 epoch: 2,
252 prev_block_root: prev,
253 body_root: body,
254 data_count: 3,
255 emissions_count: 4,
256 proposer_pubkey: proposer,
257 };
258 let r2_header = L2BlockHeader {
259 version: 1,
260 network_id,
261 epoch: 2,
262 prev_block_root: prev,
263 body_root: body,
264 data_count: 4,
265 emissions_count: 3,
266 proposer_pubkey: proposer,
267 };
268 let r1 = COMPUTE_HEADER_ROOT(&r1_header);
269 let r2 = COMPUTE_HEADER_ROOT(&r2_header);
270 assert_ne!(r1, r2);
271 }
272
273 #[test]
274 fn block_root_composition() {
275 let header_root = h32(0xaa);
276 let body_root = h32(0xbb);
277 let r = COMPUTE_BLOCK_ROOT(&header_root, &body_root);
278 let expect = sha256_concat(&[BLOCK_ROOT_DOMAIN, &header_root, &body_root]);
279 assert_eq!(r, expect);
280 }
281
282 #[test]
283 fn build_consensus_emissions_basic() {
284 let proposer = [7u8; 48];
285 let attesters = vec![[1u8; 48], [2u8; 48], [3u8; 48]];
286 let v = BUILD_CONSENSUS_EMISSIONS(proposer, &attesters, 12, 88).unwrap();
287 assert_eq!(v.len(), 1 + attesters.len());
288 assert_eq!(v[0], (proposer, 12));
289 assert_eq!(v[1].1, 29);
291 assert_eq!(v[2].1, 29);
292 assert_eq!(v[3].1, 29);
293 }
294
295 #[test]
296 fn build_consensus_emissions_zero_attesters_policy() {
297 let proposer = [9u8; 48];
298 let v = BUILD_CONSENSUS_EMISSIONS(proposer, &[], 12, 0).unwrap();
299 assert_eq!(v.len(), 1);
300
301 let err = BUILD_CONSENSUS_EMISSIONS(proposer, &[], 12, 1).unwrap_err();
302 match err {
303 DefinitionError::NoAttestersForNonZeroShare => {}
304 }
305 }
306}