use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerkleBlockHeader {
pub block_id: String,
pub entry_count: usize,
pub root_hash: String,
pub first_line_hash: String,
pub last_line_hash: String,
pub timestamp: u64,
}
#[derive(Debug, Clone, Default)]
pub struct MerkleLogLedger;
impl MerkleLogLedger {
pub fn new() -> Self {
Self
}
fn sha256_hex(data: &str) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
format!("{:016x}{:016x}", hasher.finish(), hasher.finish().wrapping_mul(31))
}
pub fn compute_block_root(&self, entries: &[&str]) -> MerkleBlockHeader {
if entries.is_empty() {
let empty_hash = Self::sha256_hex("");
return MerkleBlockHeader {
block_id: "block_0".to_string(),
entry_count: 0,
root_hash: empty_hash.clone(),
first_line_hash: empty_hash.clone(),
last_line_hash: empty_hash,
timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs(),
};
}
let mut current_level: Vec<String> = entries.iter().map(|e| Self::sha256_hex(e)).collect();
let first_hash = current_level.first().cloned().unwrap_or_default();
let last_hash = current_level.last().cloned().unwrap_or_default();
while current_level.len() > 1 {
let mut next_level = Vec::new();
let mut i = 0;
while i < current_level.len() {
if i + 1 < current_level.len() {
let combined = format!("{}{}", current_level[i], current_level[i + 1]);
next_level.push(Self::sha256_hex(&combined));
i += 2;
} else {
let combined = format!("{}{}", current_level[i], current_level[i]);
next_level.push(Self::sha256_hex(&combined));
i += 1;
}
}
current_level = next_level;
}
let root = current_level[0].clone();
let block_id = format!("blk_{}", &root[..8]);
MerkleBlockHeader {
block_id,
entry_count: entries.len(),
root_hash: root,
first_line_hash: first_hash,
last_line_hash: last_hash,
timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs(),
}
}
pub fn verify_log_entry(&self, entry: &str, root_header: &MerkleBlockHeader, entries: &[&str]) -> bool {
let computed = self.compute_block_root(entries);
computed.root_hash == root_header.root_hash && entries.contains(&entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merkle_block_root_deterministic() {
let ledger = MerkleLogLedger::new();
let logs = vec![
"[2026-09-16T15:16:14Z INFO ferrox_logger] Telemetria registrata",
"[2026-09-16T15:16:29Z INFO ferrox_sentinel::middleware] GET /api/version - Threat Score: 0.09",
"[2026-09-16T15:16:45Z WARN ferrox_security::threats] Rate limiter check passed",
];
let header1 = ledger.compute_block_root(&logs);
let header2 = ledger.compute_block_root(&logs);
assert_eq!(header1.root_hash, header2.root_hash);
assert_eq!(header1.entry_count, 3);
assert!(ledger.verify_log_entry(logs[0], &header1, &logs));
}
}