1use blake2::{Blake2b512, Digest};
36use ligerito_binary_fields::{BinaryElem32, BinaryFieldElement};
37
38use crate::error::ZyncError;
39
40pub const FIELDS_PER_HEADER: usize = 32;
42
43pub const TIP_SENTINEL_SIZE: usize = 24;
46
47#[derive(Clone, Debug, Default)]
49pub struct EpochStateRoots {
50 pub epoch: u32,
51 pub height: u32,
52 pub sapling_root: String,
54 pub orchard_root: String,
56 pub nullifier_root: [u8; 32],
58}
59
60#[derive(Clone, Debug)]
63pub struct TraceHeader {
64 pub height: u32,
65 pub hash: String,
67 pub prev_hash: String,
69 pub bits: String,
71}
72
73pub struct HeaderChainTrace {
78 pub trace: Vec<BinaryElem32>,
80 pub num_headers: usize,
82 pub start_height: u32,
83 pub end_height: u32,
84 pub initial_commitment: [u8; 32],
87 pub final_commitment: [u8; 32],
89 pub initial_state_commitment: [u8; 32],
90 pub final_state_commitment: [u8; 32],
91 pub cumulative_difficulty: u64,
93 pub tip_tree_root: [u8; 32],
95 pub tip_nullifier_root: [u8; 32],
97}
98
99pub fn encode_trace(
104 headers: &[TraceHeader],
105 state_roots: &[EpochStateRoots],
106 initial_commitment: [u8; 32],
107 initial_state_commitment: [u8; 32],
108 tip_tree_root: [u8; 32],
109 tip_nullifier_root: [u8; 32],
110 final_actions_commitment: [u8; 32],
111) -> Result<HeaderChainTrace, ZyncError> {
112 if headers.is_empty() {
113 return Err(ZyncError::InvalidData("empty headers".into()));
114 }
115
116 let num_elements = headers.len() * FIELDS_PER_HEADER + TIP_SENTINEL_SIZE;
117 let trace_size = num_elements.next_power_of_two();
118 let mut trace = vec![BinaryElem32::zero(); trace_size];
119
120 let mut running_commitment = initial_commitment;
121 let mut state_commitment = initial_state_commitment;
122 let mut cumulative_difficulty: u64 = 0;
123
124 let state_root_map: std::collections::HashMap<u32, &EpochStateRoots> =
125 state_roots.iter().map(|r| (r.height, r)).collect();
126
127 for (i, header) in headers.iter().enumerate() {
128 let offset = i * FIELDS_PER_HEADER;
129
130 let block_hash = hex_to_bytes(&header.hash)?;
131 let prev_hash = if header.prev_hash.is_empty() {
132 if header.height != 0 {
133 return Err(ZyncError::InvalidData(format!(
134 "block {} has empty prev_hash (only genesis allowed)",
135 header.height
136 )));
137 }
138 vec![0u8; 32]
139 } else {
140 hex_to_bytes(&header.prev_hash)?
141 };
142
143 let nbits = if header.bits.is_empty() {
144 0u32
145 } else {
146 u32::from_str_radix(&header.bits, 16).unwrap_or(0)
147 };
148
149 let block_difficulty = nbits_to_difficulty(nbits);
150 cumulative_difficulty = cumulative_difficulty.saturating_add(block_difficulty);
151
152 trace[offset] = BinaryElem32::from(header.height);
154
155 for j in 0..8 {
157 trace[offset + 1 + j] = bytes_to_field(&block_hash[j * 4..(j + 1) * 4]);
158 }
159
160 for j in 0..8 {
162 trace[offset + 9 + j] = bytes_to_field(&prev_hash[j * 4..(j + 1) * 4]);
163 }
164
165 trace[offset + 17] = BinaryElem32::from(nbits);
167
168 trace[offset + 18] = BinaryElem32::from(cumulative_difficulty as u32);
170
171 running_commitment =
173 update_running_commitment(&running_commitment, &block_hash, &prev_hash, header.height);
174 trace[offset + 19] = bytes_to_field(&running_commitment[0..4]);
175
176 if let Some(roots) = state_root_map.get(&header.height) {
178 let sapling = if roots.sapling_root.is_empty() {
179 vec![0u8; 32]
180 } else {
181 hex_to_bytes(&roots.sapling_root)?
182 };
183 let orchard = if roots.orchard_root.is_empty() {
184 vec![0u8; 32]
185 } else {
186 hex_to_bytes(&roots.orchard_root)?
187 };
188
189 for j in 0..4 {
190 trace[offset + 20 + j] = bytes_to_field(&sapling[j * 4..(j + 1) * 4]);
191 }
192 for j in 0..4 {
193 trace[offset + 24 + j] = bytes_to_field(&orchard[j * 4..(j + 1) * 4]);
194 }
195
196 let nf_root = &roots.nullifier_root;
197 trace[offset + 28] = bytes_to_field(&nf_root[0..4]);
198 trace[offset + 29] = bytes_to_field(&nf_root[4..8]);
199
200 state_commitment = update_state_commitment(
201 &state_commitment,
202 &sapling,
203 &orchard,
204 nf_root,
205 header.height,
206 );
207 trace[offset + 30] = bytes_to_field(&state_commitment[0..4]);
208 } else {
209 trace[offset + 30] = bytes_to_field(&state_commitment[0..4]);
210 }
211 }
212
213 let sentinel_offset = headers.len() * FIELDS_PER_HEADER;
215 for j in 0..8 {
216 trace[sentinel_offset + j] = bytes_to_field(&tip_tree_root[j * 4..(j + 1) * 4]);
217 }
218 for j in 0..8 {
219 trace[sentinel_offset + 8 + j] =
220 bytes_to_field(&tip_nullifier_root[j * 4..(j + 1) * 4]);
221 }
222 for j in 0..8 {
223 trace[sentinel_offset + 16 + j] =
224 bytes_to_field(&final_actions_commitment[j * 4..(j + 1) * 4]);
225 }
226
227 Ok(HeaderChainTrace {
228 trace,
229 num_headers: headers.len(),
230 start_height: headers[0].height,
231 end_height: headers.last().unwrap().height,
232 initial_commitment,
233 final_commitment: running_commitment,
234 initial_state_commitment,
235 final_state_commitment: state_commitment,
236 cumulative_difficulty,
237 tip_tree_root,
238 tip_nullifier_root,
239 })
240}
241
242pub fn bytes_to_field(bytes: &[u8]) -> BinaryElem32 {
244 assert_eq!(bytes.len(), 4);
245 let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
246 BinaryElem32::from(value)
247}
248
249pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, ZyncError> {
251 hex::decode(hex).map_err(|e| ZyncError::InvalidData(e.to_string()))
252}
253
254pub fn update_running_commitment(
258 prev_commitment: &[u8; 32],
259 block_hash: &[u8],
260 prev_hash: &[u8],
261 height: u32,
262) -> [u8; 32] {
263 let mut hasher = Blake2b512::new();
264 hasher.update(b"ZIDECAR_header_commitment");
265 hasher.update(prev_commitment);
266 hasher.update(block_hash);
267 hasher.update(prev_hash);
268 hasher.update(height.to_le_bytes());
269
270 let hash = hasher.finalize();
271 let mut result = [0u8; 32];
272 result.copy_from_slice(&hash[..32]);
273 result
274}
275
276pub fn update_state_commitment(
280 prev_commitment: &[u8; 32],
281 sapling_root: &[u8],
282 orchard_root: &[u8],
283 nullifier_root: &[u8],
284 height: u32,
285) -> [u8; 32] {
286 let mut hasher = Blake2b512::new();
287 hasher.update(b"ZIDECAR_state_commitment");
288 hasher.update(prev_commitment);
289 hasher.update(sapling_root);
290 hasher.update(orchard_root);
291 hasher.update(nullifier_root);
292 hasher.update(height.to_le_bytes());
293
294 let hash = hasher.finalize();
295 let mut result = [0u8; 32];
296 result.copy_from_slice(&hash[..32]);
297 result
298}
299
300pub fn parse_tree_root_bytes(final_state: &str) -> [u8; 32] {
302 use sha2::{Digest as Sha2Digest, Sha256};
303 let mut hasher = Sha256::new();
304 hasher.update(b"ZIDECAR_TREE_ROOT");
305 hasher.update(final_state.as_bytes());
306 hasher.finalize().into()
307}
308
309pub fn nbits_to_difficulty(nbits: u32) -> u64 {
314 if nbits == 0 {
315 return 0;
316 }
317 let exponent = (nbits >> 24) as u64;
318 let mantissa = (nbits & 0x00FFFFFF) as u64;
319 if mantissa == 0 {
320 return 0;
321 }
322 let shift = exponent.saturating_sub(3);
323 if shift < 32 {
324 let base_diff = (1u64 << 32) / mantissa;
325 base_diff >> (shift * 8).min(63)
326 } else {
327 1
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_bytes_to_field() {
337 let bytes = [0x01, 0x02, 0x03, 0x04];
338 let field = bytes_to_field(&bytes);
339 assert_eq!(field.poly().value(), 0x04030201); }
341
342 #[test]
343 fn test_hex_to_bytes() {
344 let bytes = hex_to_bytes("deadbeef").unwrap();
345 assert_eq!(bytes, vec![0xde, 0xad, 0xbe, 0xef]);
346 }
347
348 #[test]
349 fn test_running_commitment_deterministic() {
350 let prev = [0u8; 32];
351 let block = [1u8; 32];
352 let prev_hash = [2u8; 32];
353
354 let c1 = update_running_commitment(&prev, &block, &prev_hash, 100);
355 let c2 = update_running_commitment(&prev, &block, &prev_hash, 100);
356 assert_eq!(c1, c2);
357 }
358
359 #[test]
360 fn test_nbits_to_difficulty() {
361 assert_eq!(nbits_to_difficulty(0), 0);
362 let d = nbits_to_difficulty(0x0400ffff);
364 assert!(d > 0);
365 let d2 = nbits_to_difficulty(0x04007fff);
367 assert_ne!(d, d2);
368 }
369
370 #[test]
371 fn test_encode_single_header() {
372 let headers = vec![TraceHeader {
373 height: 100,
374 hash: "00".repeat(32),
375 prev_hash: "00".repeat(32),
376 bits: "1d00ffff".into(),
377 }];
378 let trace = encode_trace(
379 &headers,
380 &[],
381 [0u8; 32],
382 [0u8; 32],
383 [0u8; 32],
384 [0u8; 32],
385 [0u8; 32],
386 )
387 .unwrap();
388 assert_eq!(trace.num_headers, 1);
389 assert_eq!(trace.start_height, 100);
390 assert_eq!(trace.end_height, 100);
391 assert!(trace.trace.len().is_power_of_two());
393 assert!(trace.trace.len() >= FIELDS_PER_HEADER + TIP_SENTINEL_SIZE);
394 }
395}