// snapkitty-sovereign-addr — Non-recursive artifact addressing // // Algorithm: // 1. validate JSON admissibility // 2. normalize all strings to Unicode NFC // 3. serialize canonical JSON with sorted keys, no whitespace // 4. hash canonical bytes with SHA-256 // 5. emit snapaddr: // 6. generate WORM receipt // 7. never recurse in validation rules; use explicit staged traversal pub mod canonical; pub mod admissibility; pub mod datalog; pub mod receipt; pub mod worm; pub mod cli; use serde::{Deserialize, Serialize}; use sha2::{Sha256, Digest}; use thiserror::Error; /// Errors from sovereign addressing. #[derive(Error, Debug, Clone)] pub enum SnapAddrError { #[error("not JSON admissible: {0}")] NotAdmissible(String), #[error("Unicode NFC normalization failed: {0}")] NormalizationFailed(String), #[error("canonical serialization failed: {0}")] CanonicalFailed(String), #[error("empty canonical bytes")] EmptyCanonical, #[error("address must be exactly 64 hex chars")] InvalidAddressLength, } /// WORM receipt for a sovereign address. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct WormReceipt { pub standard: String, pub algorithm: String, pub canonicalization: String, pub address: String, pub status: String, pub seal: String, pub governance: String, } /// Compute the canonical bytes of a JSON value. /// /// Algorithm: /// 1. Normalize all strings to Unicode NFC /// 2. Sort object keys recursively /// 3. Serialize with no whitespace pub fn canonical_bytes(value: &serde_json::Value) -> Result, SnapAddrError> { canonical::to_canonical(value) } /// Compute the sovereign address of a JSON value. /// /// Returns: snapaddr:<64 lowercase hex chars> pub fn sovereign_address(value: &serde_json::Value) -> Result { let bytes = canonical_bytes(value)?; Ok(format!("snapaddr:{}", hex::encode(bytes))) } /// Compute the sovereign address as raw bytes. pub fn sovereign_address_bytes(value: &serde_json::Value) -> Result<[u8; 32], SnapAddrError> { let bytes = canonical_bytes(value)?; let mut hasher = Sha256::new(); hasher.update(&bytes); let result = hasher.finalize(); let mut out = [0u8; 32]; out.copy_from_slice(&result); Ok(out) } /// Generate a WORM receipt for a JSON value. pub fn worm_receipt(value: &serde_json::Value) -> Result { worm::generate_receipt(value) } /// Verify that an address matches a JSON value. pub fn verify_address( value: &serde_json::Value, expected_addr: &str, ) -> Result { let addr = sovereign_address(value)?; Ok(addr == expected_addr) } /// Verify a WORM receipt. pub fn verify_receipt(receipt: &WormReceipt) -> Result { worm::verify_receipt(receipt) } /// Normalize a JSON value to Unicode NFC (used by datalog). pub fn normalize_value(value: &serde_json::Value) -> Result { canonical::normalize_value(value) } #[cfg(test)] mod tests { use super::*; #[test] fn test_sovereign_address_simple() { let value = serde_json::json!({"key": "value"}); let addr = sovereign_address(&value).unwrap(); assert!(addr.starts_with("snapaddr:")); assert_eq!(addr.len(), 64 + "snapaddr:".len()); } #[test] fn test_sovereign_address_deterministic() { let v1 = serde_json::json!({"a": 1, "b": 2}); let v2 = serde_json::json!({"b": 2, "a": 1}); assert_eq!(sovereign_address(&v1).unwrap(), sovereign_address(&v2).unwrap()); } #[test] fn test_sovereign_address_bytes() { let value = serde_json::json!({"test": true}); let bytes = sovereign_address_bytes(&value).unwrap(); assert_eq!(bytes.len(), 32); } #[test] fn test_worm_receipt() { let value = serde_json::json!({"prime": 42}); let receipt = worm_receipt(&value).unwrap(); assert_eq!(receipt.standard, "SNAPKITTY-SOVEREIGN-ADDR-1"); assert_eq!(receipt.status, "accepted"); assert!(receipt.seal.starts_with("snapaddr:")); } #[test] fn test_verify_address() { let value = serde_json::json!({"check": "this"}); let addr = sovereign_address(&value).unwrap(); assert!(verify_address(&value, &addr).unwrap()); assert!(!verify_address(&value, "snapaddr:0000000000000000000000000000000000000000000000000000000000000000").unwrap()); } #[test] fn test_unicode_nfc_equivalence() { // "é" can be U+00E9 or U+0065 U+0301 — both should produce same address let v1 = serde_json::json!({"name": "\u{00e9}"}); let v2 = serde_json::json!({"name": "\u{0065}\u{0301}"}); assert_eq!(sovereign_address(&v1).unwrap(), sovereign_address(&v2).unwrap()); } }