File size: 5,054 Bytes
103262d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// 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:<digest>
// 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<Vec<u8>, 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<String, SnapAddrError> {
    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<WormReceipt, SnapAddrError> {
    worm::generate_receipt(value)
}

/// Verify that an address matches a JSON value.
pub fn verify_address(
    value: &serde_json::Value,
    expected_addr: &str,
) -> Result<bool, SnapAddrError> {
    let addr = sovereign_address(value)?;
    Ok(addr == expected_addr)
}

/// Verify a WORM receipt.
pub fn verify_receipt(receipt: &WormReceipt) -> Result<bool, SnapAddrError> {
    worm::verify_receipt(receipt)
}

/// Normalize a JSON value to Unicode NFC (used by datalog).
pub fn normalize_value(value: &serde_json::Value) -> Result<serde_json::Value, SnapAddrError> {
    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());
    }
}