Skip to main content

vex_core/
segment.rs

1//! # VEX Segments
2//!
3//! Provides the data structures and JCS canonicalization for the v0.1.0 "Hardened" Commitment model.
4
5use crate::merkle::Hash;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9/// Intent Data (VEX Pillar)
10/// Proves the proposed action before execution.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct IntentData {
13    pub request_sha256: String,
14    pub confidence: f64,
15    #[serde(default)]
16    pub capabilities: Vec<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub magpie_source: Option<String>,
19}
20
21impl IntentData {
22    pub fn to_jcs_hash(&self) -> Result<Hash, String> {
23        let jcs_bytes =
24            serde_jcs::to_vec(self).map_err(|e| format!("JCS serialization failed: {}", e))?;
25
26        let mut hasher = Sha256::new();
27        hasher.update(&jcs_bytes);
28        let result = hasher.finalize();
29
30        Ok(Hash::from_bytes(result.into()))
31    }
32}
33
34/// Authority Data (CHORA Pillar)
35/// Proves the governance decision.
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37pub struct AuthorityData {
38    pub capsule_id: String,
39    pub outcome: String,
40    pub reason_code: String,
41    pub trace_root: String,
42    pub nonce: u64,
43    #[serde(default = "default_sensor_value")]
44    pub gate_sensors: serde_json::Value,
45}
46
47fn default_sensor_value() -> serde_json::Value {
48    serde_json::Value::Null
49}
50
51/// Witness Data (CHORA Append-Only Log)
52/// Proves the receipt issuance parameters.
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub struct WitnessData {
55    pub chora_node_id: String,
56    pub receipt_hash: String,
57    pub timestamp: String,
58}
59
60/// Identity Data (Attest Pillar)
61/// Proves the silicon source.
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63pub struct IdentityData {
64    pub aid: String,
65    pub identity_type: String,
66}
67
68/// Crypto verification details.
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct CryptoData {
71    pub algo: String,
72    pub public_key_endpoint: String,
73    pub signature_scope: String,
74    pub signature_b64: String,
75}
76
77/// A Composite Evidence Capsule (The v0.1.0 "Zero-Trust Singularity" Root)
78/// Binds Intent, Authority, Identity, and Witness into a single commitment.
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct Capsule {
81    pub capsule_id: String,
82    /// VEX Pillar: What was intended
83    pub intent: IntentData,
84    /// CHORA Pillar: Who authorized it
85    pub authority: AuthorityData,
86    /// ATTEST Pillar: Where it executed (Silicon)
87    pub identity: IdentityData,
88    /// CHORA Log Pillar: Where the receipt lives
89    pub witness: WitnessData,
90
91    // Derived hashes for transparency
92    pub intent_hash: String,
93    pub authority_hash: String,
94    pub identity_hash: String,
95    pub witness_hash: String,
96    pub capsule_root: String,
97
98    /// Ed25519 signature details
99    pub crypto: CryptoData,
100}
101
102impl Capsule {
103    /// Compute the canonical "capsule_root" using George's Hash-of-Hashes Spec
104    /// `SHA256(JCS({ intent_hash, authority_hash, identity_hash, witness_hash }))`
105    pub fn to_composite_hash(&self) -> Result<Hash, String> {
106        // Helper to hash a single JCS serializable structure
107        fn hash_seg<T: Serialize>(seg: &T) -> Result<String, String> {
108            let jcs = serde_jcs::to_vec(seg).map_err(|e| e.to_string())?;
109            let mut hasher = Sha256::new();
110            hasher.update(&jcs);
111            Ok(hex::encode(hasher.finalize()))
112        }
113
114        let intent_h = self.intent.to_jcs_hash()?;
115        let intent_hash_hex = intent_h.to_hex();
116
117        let authority_hash_hex = hash_seg(&self.authority)?;
118        let identity_hash_hex = hash_seg(&self.identity)?;
119        let witness_hash_hex = hash_seg(&self.witness)?;
120
121        // Build the Canonical Composite Object
122        let composite_root = serde_json::json!({
123            "intent_hash": intent_hash_hex,
124            "authority_hash": authority_hash_hex,
125            "identity_hash": identity_hash_hex,
126            "witness_hash": witness_hash_hex
127        });
128
129        // Hash the Composite Object
130        let composite_jcs = serde_jcs::to_vec(&composite_root)
131            .map_err(|e| format!("JCS Serialization of composite root failed: {}", e))?;
132
133        let mut hasher = Sha256::new();
134        hasher.update(&composite_jcs);
135        let result = hasher.finalize();
136
137        Ok(Hash::from_bytes(result.into()))
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_intent_segment_jcs_deterministic() {
147        let segment1 = IntentData {
148            request_sha256: "8ee6010d905547c377c67e63559e989b8073b168f11a1ffefd092c7ca962076e"
149                .to_string(),
150            confidence: 0.95,
151            capabilities: vec![],
152            magpie_source: None,
153        };
154        let segment2 = segment1.clone();
155
156        let hash1 = segment1.to_jcs_hash().unwrap();
157        let hash2 = segment2.to_jcs_hash().unwrap();
158
159        assert_eq!(hash1, hash2, "JCS hashing must be deterministic");
160    }
161
162    #[test]
163    fn test_intent_segment_content_change() {
164        let segment1 = IntentData {
165            request_sha256: "a".into(),
166            confidence: 0.5,
167            capabilities: vec![],
168            magpie_source: None,
169        };
170        let mut segment2 = segment1.clone();
171        segment2.confidence = 0.9;
172
173        let hash1 = segment1.to_jcs_hash().unwrap();
174        let hash2 = segment2.to_jcs_hash().unwrap();
175
176        assert_ne!(hash1, hash2, "Hashes must change when content changes");
177    }
178}