use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::sync::{Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct MerkleChainViolationError {
pub message: String,
}
impl std::fmt::Display for MerkleChainViolationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MerkleChainViolation: {}", self.message)
}
}
impl std::error::Error for MerkleChainViolationError {}
pub fn canonicalize_state(state: &serde_json::Value) -> String {
let mut buf = Vec::new();
let mut serializer =
serde_json::Serializer::with_formatter(&mut buf, olpc_cjson::CanonicalFormatter::new());
serde::Serialize::serialize(state, &mut serializer).unwrap();
String::from_utf8(buf).unwrap_or_default()
}
pub fn compute_merkle_hash(canonical_state: &str, prior_hash: &str) -> String {
let combined = format!("{}:{}", prior_hash, canonical_state);
let mut hasher = Sha256::new();
hasher.update(combined.as_bytes());
format!("{:x}", hasher.finalize())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LedgerEntry {
pub sequence_id: u64,
pub workflow_id: String,
pub merkle_hash: String,
pub prior_hash: String,
pub canonical_state: String,
pub timestamp: f64,
}
const GENESIS_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
pub struct WORMCommitLedger {
last_hash: String,
sequence_id: u64,
entries: Vec<LedgerEntry>,
}
impl WORMCommitLedger {
pub fn new() -> Self {
Self {
last_hash: GENESIS_HASH.to_string(),
sequence_id: 0,
entries: Vec::new(),
}
}
pub fn append(&mut self, workflow_id: &str, state_delta: &serde_json::Value) -> String {
let canonical = canonicalize_state(state_delta);
let new_hash = compute_merkle_hash(&canonical, &self.last_hash);
self.sequence_id += 1;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64();
let entry = LedgerEntry {
sequence_id: self.sequence_id,
workflow_id: workflow_id.to_string(),
merkle_hash: new_hash.clone(),
prior_hash: self.last_hash.clone(),
canonical_state: canonical,
timestamp: now,
};
self.entries.push(entry);
self.last_hash = new_hash.clone();
new_hash
}
pub fn verify_chain(&self) -> Result<bool, MerkleChainViolationError> {
let mut expected_prior = GENESIS_HASH.to_string();
for entry in &self.entries {
let expected_hash = compute_merkle_hash(&entry.canonical_state, &expected_prior);
if entry.merkle_hash != expected_hash {
return Err(MerkleChainViolationError {
message: format!(
"Tamper detected at sequence {}: expected hash {}... but found {}... \
This violates SEC Rule 17a-4/204-2 WORM storage compliance.",
entry.sequence_id,
&expected_hash[..16],
&entry.merkle_hash[..16],
),
});
}
if entry.prior_hash != expected_prior {
return Err(MerkleChainViolationError {
message: format!(
"Chain break at sequence {}: prior_hash {}... does not match expected {}...",
entry.sequence_id,
&entry.prior_hash[..16],
&expected_prior[..16],
),
});
}
expected_prior = entry.merkle_hash.clone();
}
Ok(true)
}
pub fn last_hash(&self) -> &str {
&self.last_hash
}
pub fn len(&self) -> u64 {
self.sequence_id
}
pub fn is_empty(&self) -> bool {
self.sequence_id == 0
}
pub fn entries(&self) -> &[LedgerEntry] {
&self.entries
}
pub fn query_by_time_range(&self, start_time: f64, end_time: f64) -> Vec<&LedgerEntry> {
self.entries
.iter()
.filter(|e| e.timestamp >= start_time && e.timestamp <= end_time)
.collect()
}
pub fn query_by_id(&self, sequence_id: u64) -> Option<&LedgerEntry> {
self.entries.iter().find(|e| e.sequence_id == sequence_id)
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"last_hash": self.last_hash,
"sequence_id": self.sequence_id,
"entries": self.entries,
})
}
}
impl Default for WORMCommitLedger {
fn default() -> Self {
Self::new()
}
}
pub fn get_worm_ledger() -> &'static Mutex<WORMCommitLedger> {
static INSTANCE: OnceLock<Mutex<WORMCommitLedger>> = OnceLock::new();
INSTANCE.get_or_init(|| Mutex::new(WORMCommitLedger::new()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_genesis_state() {
let ledger = WORMCommitLedger::new();
assert_eq!(ledger.last_hash(), GENESIS_HASH);
assert_eq!(ledger.len(), 0);
assert!(ledger.is_empty());
}
#[test]
fn test_append_single_entry() {
let mut ledger = WORMCommitLedger::new();
let state = serde_json::json!({"action": "deploy", "version": "1.0"});
let hash = ledger.append("wf-001", &state);
assert_ne!(hash, GENESIS_HASH);
assert_eq!(ledger.len(), 1);
assert!(!ledger.is_empty());
assert_eq!(ledger.last_hash(), hash);
}
#[test]
fn test_append_multiple_entries_chain() {
let mut ledger = WORMCommitLedger::new();
let h1 = ledger.append("wf-001", &serde_json::json!({"step": 1}));
let h2 = ledger.append("wf-001", &serde_json::json!({"step": 2}));
let h3 = ledger.append("wf-002", &serde_json::json!({"step": 3}));
assert_eq!(ledger.len(), 3);
assert_ne!(h1, h2);
assert_ne!(h2, h3);
assert_eq!(ledger.entries()[0].prior_hash, GENESIS_HASH);
assert_eq!(ledger.entries()[1].prior_hash, h1);
assert_eq!(ledger.entries()[2].prior_hash, h2);
}
#[test]
fn test_verify_chain_valid() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"a": 1}));
ledger.append("wf-001", &serde_json::json!({"b": 2}));
ledger.append("wf-002", &serde_json::json!({"c": 3}));
assert!(ledger.verify_chain().is_ok());
}
#[test]
fn test_verify_chain_detects_tamper() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"a": 1}));
ledger.append("wf-001", &serde_json::json!({"b": 2}));
ledger.entries[0].canonical_state = "{\"tampered\":true}".to_string();
let result = ledger.verify_chain();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.message.contains("Tamper detected at sequence 1"));
}
#[test]
fn test_verify_chain_detects_prior_hash_break() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"a": 1}));
ledger.append("wf-001", &serde_json::json!({"b": 2}));
ledger.entries[1].prior_hash =
"bad_hash_0000000000000000000000000000000000000000000000000000".to_string();
let canonical = &ledger.entries[1].canonical_state;
ledger.entries[1].merkle_hash =
compute_merkle_hash(canonical, &ledger.entries[1].prior_hash);
let result = ledger.verify_chain();
assert!(result.is_err());
}
#[test]
fn test_verify_empty_chain() {
let ledger = WORMCommitLedger::new();
assert!(ledger.verify_chain().is_ok());
}
#[test]
fn test_canonicalize_state_deterministic() {
let a = serde_json::json!({"z": 1, "a": 2, "m": 3});
let b = serde_json::json!({"a": 2, "m": 3, "z": 1});
assert_eq!(canonicalize_state(&a), canonicalize_state(&b));
}
#[test]
fn test_canonicalize_nested_objects() {
let state = serde_json::json!({"outer": {"b": 2, "a": 1}});
let canonical = canonicalize_state(&state);
let a_pos = canonical.find("\"a\"").unwrap();
let b_pos = canonical.find("\"b\"").unwrap();
assert!(a_pos < b_pos);
}
#[test]
fn test_compute_merkle_hash_deterministic() {
let h1 = compute_merkle_hash("test_state", GENESIS_HASH);
let h2 = compute_merkle_hash("test_state", GENESIS_HASH);
assert_eq!(h1, h2);
assert_eq!(h1.len(), 64); }
#[test]
fn test_compute_merkle_hash_different_inputs() {
let h1 = compute_merkle_hash("state_a", GENESIS_HASH);
let h2 = compute_merkle_hash("state_b", GENESIS_HASH);
assert_ne!(h1, h2);
}
#[test]
fn test_query_by_id() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"x": 1}));
ledger.append("wf-002", &serde_json::json!({"x": 2}));
let entry = ledger.query_by_id(1).unwrap();
assert_eq!(entry.workflow_id, "wf-001");
let entry2 = ledger.query_by_id(2).unwrap();
assert_eq!(entry2.workflow_id, "wf-002");
assert!(ledger.query_by_id(999).is_none());
}
#[test]
fn test_query_by_time_range() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"x": 1}));
let all = ledger.query_by_time_range(0.0, f64::MAX);
assert_eq!(all.len(), 1);
let none = ledger.query_by_time_range(0.0, 1.0);
assert!(none.is_empty());
}
#[test]
fn test_to_json() {
let mut ledger = WORMCommitLedger::new();
ledger.append("wf-001", &serde_json::json!({"action": "test"}));
let json = ledger.to_json();
assert!(json["last_hash"].is_string());
assert_eq!(json["sequence_id"], 1);
assert!(json["entries"].is_array());
assert_eq!(json["entries"].as_array().unwrap().len(), 1);
}
#[test]
fn test_singleton_ledger() {
let ledger = get_worm_ledger();
let mut guard = ledger.lock().unwrap();
let initial_len = guard.len();
guard.append("singleton-test", &serde_json::json!({"singleton": true}));
assert_eq!(guard.len(), initial_len + 1);
}
}