1use crate::merkle::Hash;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63pub struct IdentityData {
64 pub aid: String,
65 pub identity_type: String,
66}
67
68#[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct Capsule {
81 pub capsule_id: String,
82 pub intent: IntentData,
84 pub authority: AuthorityData,
86 pub identity: IdentityData,
88 pub witness: WitnessData,
90
91 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 pub crypto: CryptoData,
100}
101
102impl Capsule {
103 pub fn to_composite_hash(&self) -> Result<Hash, String> {
106 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 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 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}