|
|
|
|
|
|
|
|
|
|
|
|
|
|
| use sha2::{Sha256, Digest};
|
| use serde::{Deserialize, Serialize};
|
|
|
| #[cfg(feature = "wasm")]
|
| use wasm_bindgen::prelude::*;
|
|
|
|
|
|
|
| const GENESIS_INPUT: &[u8] = b"LEDGE_GENESIS:SOVEREIGN_CHAIN_INIT";
|
|
|
|
|
|
|
| fn compute_genesis() -> [u8; 32] {
|
| Sha256::digest(GENESIS_INPUT).into()
|
| }
|
|
|
| fn compute_seal(prev: &[u8; 32], payload_json: &[u8], timestamp_ms: u64, index: u64) -> [u8; 32] {
|
| let mut h = Sha256::new();
|
| h.update(prev);
|
| h.update(payload_json);
|
| h.update(timestamp_ms.to_be_bytes());
|
| h.update(index.to_be_bytes());
|
| h.finalize().into()
|
| }
|
|
|
| fn build_merkle_root(leaves: &[[u8; 32]]) -> [u8; 32] {
|
| if leaves.is_empty() {
|
| return Sha256::digest(b"EMPTY").into();
|
| }
|
| let mut layer: Vec<[u8; 32]> = leaves.to_vec();
|
| while layer.len() > 1 {
|
| let mut next = Vec::with_capacity((layer.len() + 1) / 2);
|
| let mut i = 0;
|
| while i < layer.len() {
|
| let left = layer[i];
|
| let right = if i + 1 < layer.len() { layer[i + 1] } else { layer[i] };
|
| let mut h = Sha256::new();
|
| h.update(left);
|
| h.update(right);
|
| next.push(h.finalize().into());
|
| i += 2;
|
| }
|
| layer = next;
|
| }
|
| layer[0]
|
| }
|
|
|
| fn hex_to_32(s: &str) -> Option<[u8; 32]> {
|
| let bytes = hex::decode(s).ok()?;
|
| if bytes.len() != 32 { return None; }
|
| let mut arr = [0u8; 32];
|
| arr.copy_from_slice(&bytes);
|
| Some(arr)
|
| }
|
|
|
|
|
|
|
| #[derive(Debug, Clone, Serialize, Deserialize)]
|
| #[serde(rename_all = "camelCase")]
|
| pub struct SealedEvent {
|
| pub index: u64,
|
| pub seal: String,
|
| pub previous_seal: String,
|
| pub payload: serde_json::Value,
|
| pub timestamp_ms: u64,
|
| }
|
|
|
| #[derive(Debug)]
|
| pub struct VerifyResult {
|
| pub valid: bool,
|
| pub failures: Vec<u64>,
|
| pub merkle_root: String,
|
| pub event_count: usize,
|
| }
|
|
|
|
|
|
|
| pub struct LedgeChain {
|
| events: Vec<SealedEvent>,
|
| head: [u8; 32],
|
| genesis: [u8; 32],
|
| }
|
|
|
| impl LedgeChain {
|
| pub fn new() -> Self {
|
| let genesis = compute_genesis();
|
| Self { events: Vec::new(), head: genesis, genesis }
|
| }
|
|
|
| pub fn seal(&mut self, payload: serde_json::Value, timestamp_ms: u64) -> SealedEvent {
|
| let index = self.events.len() as u64;
|
| let payload_json = payload.to_string();
|
| let seal_bytes = compute_seal(&self.head, payload_json.as_bytes(), timestamp_ms, index);
|
|
|
| let event = SealedEvent {
|
| index,
|
| seal: hex::encode(seal_bytes),
|
| previous_seal: hex::encode(self.head),
|
| payload,
|
| timestamp_ms,
|
| };
|
|
|
| self.head = seal_bytes;
|
| self.events.push(event.clone());
|
| event
|
| }
|
|
|
| pub fn verify(&self) -> VerifyResult {
|
| let mut prev = self.genesis;
|
| let mut failures = Vec::new();
|
|
|
| for e in &self.events {
|
| let payload_json = e.payload.to_string();
|
| let expected = compute_seal(&prev, payload_json.as_bytes(), e.timestamp_ms, e.index);
|
|
|
| if hex::encode(expected) != e.seal {
|
| failures.push(e.index);
|
|
|
| if let Some(b) = hex_to_32(&e.seal) { prev = b; } else { break; }
|
| } else {
|
| prev = expected;
|
| }
|
| }
|
|
|
| let leaves: Vec<[u8; 32]> = self.events.iter()
|
| .filter_map(|e| hex_to_32(&e.seal))
|
| .collect();
|
|
|
| VerifyResult {
|
| valid: failures.is_empty(),
|
| failures,
|
| merkle_root: hex::encode(build_merkle_root(&leaves)),
|
| event_count: self.events.len(),
|
| }
|
| }
|
|
|
| pub fn merkle_root(&self) -> String {
|
| let leaves: Vec<[u8; 32]> = self.events.iter()
|
| .filter_map(|e| hex_to_32(&e.seal))
|
| .collect();
|
| hex::encode(build_merkle_root(&leaves))
|
| }
|
|
|
| pub fn genesis(&self) -> String {
|
| hex::encode(self.genesis)
|
| }
|
|
|
| pub fn events(&self) -> &[SealedEvent] {
|
| &self.events
|
| }
|
|
|
| pub fn len(&self) -> usize {
|
| self.events.len()
|
| }
|
|
|
| pub fn is_empty(&self) -> bool {
|
| self.events.is_empty()
|
| }
|
| }
|
|
|
| impl Default for LedgeChain {
|
| fn default() -> Self {
|
| Self::new()
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| #[cfg(feature = "wasm")]
|
| #[wasm_bindgen]
|
| pub fn ledge_genesis() -> String {
|
| hex::encode(compute_genesis())
|
| }
|
|
|
|
|
|
|
|
|
| #[cfg(feature = "wasm")]
|
| #[wasm_bindgen]
|
| pub fn ledge_seal(prev_seal_hex: &str, payload_json: &str, timestamp_ms: u64, index: u64) -> String {
|
| let prev = hex_to_32(prev_seal_hex).unwrap_or_else(compute_genesis);
|
| hex::encode(compute_seal(&prev, payload_json.as_bytes(), timestamp_ms, index))
|
| }
|
|
|
|
|
|
|
| #[cfg(feature = "wasm")]
|
| #[wasm_bindgen]
|
| pub fn ledge_verify(events_json: &str) -> String {
|
| let events: Vec<SealedEvent> = match serde_json::from_str(events_json) {
|
| Ok(v) => v,
|
| Err(e) => return format!(r#"{{"valid":false,"failures":[],"merkleRoot":"","eventCount":0,"error":"{}"}}"#, e),
|
| };
|
|
|
| let mut prev = compute_genesis();
|
| let mut failures = Vec::new();
|
|
|
| for e in &events {
|
| let payload_json = e.payload.to_string();
|
| let expected = compute_seal(&prev, payload_json.as_bytes(), e.timestamp_ms, e.index);
|
| if hex::encode(expected) != e.seal {
|
| failures.push(e.index);
|
| }
|
| prev = hex_to_32(&e.seal).unwrap_or(expected);
|
| }
|
|
|
| let leaves: Vec<[u8; 32]> = events.iter().filter_map(|e| hex_to_32(&e.seal)).collect();
|
| let root = hex::encode(build_merkle_root(&leaves));
|
| let valid = failures.is_empty();
|
|
|
| serde_json::json!({
|
| "valid": valid,
|
| "failures": failures,
|
| "merkleRoot": root,
|
| "eventCount": events.len(),
|
| }).to_string()
|
| }
|
|
|
|
|
| #[cfg(feature = "wasm")]
|
| #[wasm_bindgen]
|
| pub fn ledge_merkle_root(seals_json: &str) -> String {
|
| let seals: Vec<String> = match serde_json::from_str(seals_json) {
|
| Ok(v) => v,
|
| Err(_) => return "0".repeat(64),
|
| };
|
| let leaves: Vec<[u8; 32]> = seals.iter().filter_map(|s| hex_to_32(s)).collect();
|
| hex::encode(build_merkle_root(&leaves))
|
| }
|
|
|
|
|
|
|
| #[cfg(test)]
|
| mod tests {
|
| use super::*;
|
| use serde_json::json;
|
|
|
| #[test]
|
| fn seal_and_verify_clean() {
|
| let mut chain = LedgeChain::new();
|
| chain.seal(json!({"event": "PAYMENT", "amount": 5000}), 1716000000000);
|
| chain.seal(json!({"event": "APPROVAL", "agent": "VAULT"}), 1716000001000);
|
| chain.seal(json!({"event": "COMMIT", "ref": "abc123"}), 1716000002000);
|
| let result = chain.verify();
|
| assert!(result.valid);
|
| assert!(result.failures.is_empty());
|
| assert_eq!(result.event_count, 3);
|
| }
|
|
|
| #[test]
|
| fn tamper_breaks_chain() {
|
| let mut chain = LedgeChain::new();
|
| chain.seal(json!({"event": "COMMIT", "ref": "main"}), 1716000000000);
|
| chain.seal(json!({"event": "DECISION", "approved": true}), 1716000001000);
|
| chain.events[0].payload = json!({"event": "COMMIT", "ref": "BACKDOOR"});
|
| let result = chain.verify();
|
| assert!(!result.valid);
|
| assert!(result.failures.contains(&0));
|
| }
|
|
|
| #[test]
|
| fn merkle_root_deterministic() {
|
| let mut a = LedgeChain::new();
|
| a.seal(json!({"v": 1}), 0);
|
| a.seal(json!({"v": 2}), 1);
|
|
|
| let mut b = LedgeChain::new();
|
| b.seal(json!({"v": 1}), 0);
|
| b.seal(json!({"v": 2}), 1);
|
|
|
| assert_eq!(a.merkle_root(), b.merkle_root());
|
| }
|
|
|
| #[test]
|
| fn empty_chain_has_genesis_root() {
|
| let chain = LedgeChain::new();
|
| let result = chain.verify();
|
| assert!(result.valid);
|
| assert_eq!(result.event_count, 0);
|
| }
|
|
|
| #[test]
|
| fn seal_is_deterministic() {
|
| let prev = compute_genesis();
|
| let payload = b"{}";
|
| let ts: u64 = 1716000000000;
|
| let s1 = compute_seal(&prev, payload, ts, 0);
|
| let s2 = compute_seal(&prev, payload, ts, 0);
|
| assert_eq!(s1, s2);
|
| }
|
| }
|
|
|