1use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ModuleManifest {
14 pub name: String,
15 pub version: String,
16 pub exports: HashMap<String, [u8; 32]>,
18 pub type_schemas: HashMap<String, [u8; 32]>,
20 pub required_permission_bits: u64,
22 #[serde(default)]
24 pub dependency_closure: HashMap<[u8; 32], Vec<[u8; 32]>>,
25 pub manifest_hash: [u8; 32],
27 pub signature: Option<ModuleSignature>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ModuleSignature {
33 pub author_key: [u8; 32],
34 pub signature: Vec<u8>,
37 pub signed_at: u64,
38}
39
40#[derive(Serialize)]
44struct ManifestHashInput<'a> {
45 name: &'a str,
46 version: &'a str,
47 exports: Vec<(&'a String, &'a [u8; 32])>,
48 type_schemas: Vec<(&'a String, &'a [u8; 32])>,
49 required_permission_bits: u64,
50 dependency_closure: Vec<(&'a [u8; 32], &'a Vec<[u8; 32]>)>,
51}
52
53impl ModuleManifest {
54 pub fn new(name: String, version: String) -> Self {
55 Self {
56 name,
57 version,
58 exports: HashMap::new(),
59 type_schemas: HashMap::new(),
60 required_permission_bits: 0,
61 dependency_closure: HashMap::new(),
62 manifest_hash: [0u8; 32],
63 signature: None,
64 }
65 }
66
67 pub fn add_export(&mut self, name: String, hash: [u8; 32]) {
68 self.exports.insert(name, hash);
69 }
70
71 pub fn add_type_schema(&mut self, name: String, hash: [u8; 32]) {
72 self.type_schemas.insert(name, hash);
73 }
74
75 pub fn finalize(&mut self) {
79 let mut exports: Vec<_> = self.exports.iter().collect();
80 exports.sort_by_key(|(k, _)| *k);
81
82 let mut type_schemas: Vec<_> = self.type_schemas.iter().collect();
83 type_schemas.sort_by_key(|(k, _)| *k);
84
85 let mut dep_closure: Vec<_> = self.dependency_closure.iter().collect();
86 dep_closure.sort_by_key(|(k, _)| *k);
87
88 let input = ManifestHashInput {
89 name: &self.name,
90 version: &self.version,
91 exports,
92 type_schemas,
93 required_permission_bits: self.required_permission_bits,
94 dependency_closure: dep_closure,
95 };
96
97 let bytes = rmp_serde::encode::to_vec(&input)
98 .expect("ManifestHashInput serialization should not fail");
99 let digest = Sha256::digest(&bytes);
100 self.manifest_hash.copy_from_slice(&digest);
101 }
102
103 pub fn verify_integrity(&self) -> bool {
105 let mut exports: Vec<_> = self.exports.iter().collect();
106 exports.sort_by_key(|(k, _)| *k);
107
108 let mut type_schemas: Vec<_> = self.type_schemas.iter().collect();
109 type_schemas.sort_by_key(|(k, _)| *k);
110
111 let mut dep_closure: Vec<_> = self.dependency_closure.iter().collect();
112 dep_closure.sort_by_key(|(k, _)| *k);
113
114 let input = ManifestHashInput {
115 name: &self.name,
116 version: &self.version,
117 exports,
118 type_schemas,
119 required_permission_bits: self.required_permission_bits,
120 dependency_closure: dep_closure,
121 };
122
123 let bytes = rmp_serde::encode::to_vec(&input)
124 .expect("ManifestHashInput serialization should not fail");
125 let digest = Sha256::digest(&bytes);
126 let mut expected = [0u8; 32];
127 expected.copy_from_slice(&digest);
128 self.manifest_hash == expected
129 }
130
131 pub fn verify_signature(&self) -> Result<bool, String> {
140 let sig = match &self.signature {
141 Some(s) => s,
142 None => return Ok(false),
143 };
144
145 let sig_data = crate::crypto::ModuleSignatureData {
147 author_key: sig.author_key,
148 signature: sig.signature.clone(),
149 signed_at: sig.signed_at,
150 };
151
152 if sig_data.verify(&self.manifest_hash) {
153 Ok(true)
154 } else {
155 Err(format!(
156 "Invalid signature on manifest '{}' v{}: signature does not match manifest hash",
157 self.name, self.version
158 ))
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_new_manifest_has_zero_hash() {
169 let m = ModuleManifest::new("test".into(), "0.1.0".into());
170 assert_eq!(m.manifest_hash, [0u8; 32]);
171 assert!(m.exports.is_empty());
172 assert!(m.type_schemas.is_empty());
173 }
174
175 #[test]
176 fn test_finalize_produces_nonzero_hash() {
177 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
178 m.add_export("greet".into(), [1u8; 32]);
179 m.finalize();
180 assert_ne!(m.manifest_hash, [0u8; 32]);
181 }
182
183 #[test]
184 fn test_verify_integrity_passes_after_finalize() {
185 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
186 m.add_export("greet".into(), [1u8; 32]);
187 m.add_type_schema("MyType".into(), [2u8; 32]);
188 m.required_permission_bits = 0x03;
189 m.finalize();
190 assert!(m.verify_integrity());
191 }
192
193 #[test]
194 fn test_verify_integrity_fails_after_mutation() {
195 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
196 m.add_export("greet".into(), [1u8; 32]);
197 m.finalize();
198 assert!(m.verify_integrity());
199
200 m.add_export("farewell".into(), [3u8; 32]);
201 assert!(!m.verify_integrity());
202 }
203
204 #[test]
205 fn test_deterministic_hash() {
206 let build = || {
207 let mut m = ModuleManifest::new("det".into(), "0.0.1".into());
208 m.add_export("b_fn".into(), [10u8; 32]);
209 m.add_export("a_fn".into(), [20u8; 32]);
210 m.add_type_schema("Z".into(), [30u8; 32]);
211 m.add_type_schema("A".into(), [40u8; 32]);
212 m.finalize();
213 m.manifest_hash
214 };
215 assert_eq!(build(), build());
216 }
217
218 #[test]
219 fn test_serde_roundtrip() {
220 let mut m = ModuleManifest::new("serde_test".into(), "2.0.0".into());
221 m.add_export("run".into(), [7u8; 32]);
222 m.required_permission_bits = 0xFF;
223 m.finalize();
224
225 let json = serde_json::to_string(&m).expect("serialize");
226 let restored: ModuleManifest = serde_json::from_str(&json).expect("deserialize");
227
228 assert_eq!(restored.name, "serde_test");
229 assert_eq!(restored.version, "2.0.0");
230 assert_eq!(restored.exports.get("run"), Some(&[7u8; 32]));
231 assert_eq!(restored.required_permission_bits, 0xFF);
232 assert!(restored.verify_integrity());
233 }
234
235 #[test]
236 fn test_verify_signature_unsigned() {
237 let mut m = ModuleManifest::new("unsigned".into(), "1.0.0".into());
238 m.add_export("fn_a".into(), [1u8; 32]);
239 m.finalize();
240 assert_eq!(m.verify_signature(), Ok(false));
242 }
243
244 #[test]
245 fn test_verify_signature_valid() {
246 let mut m = ModuleManifest::new("signed".into(), "1.0.0".into());
247 m.add_export("fn_a".into(), [1u8; 32]);
248 m.finalize();
249
250 let sig_data = crate::crypto::signing::sign_manifest_hash(
252 &m.manifest_hash,
253 &[42u8; 32],
254 );
255 m.signature = Some(ModuleSignature {
256 author_key: sig_data.author_key,
257 signature: sig_data.signature,
258 signed_at: sig_data.signed_at,
259 });
260
261 assert_eq!(m.verify_signature(), Ok(true));
262 }
263
264 #[test]
265 fn test_verify_signature_invalid() {
266 let mut m = ModuleManifest::new("badsig".into(), "1.0.0".into());
267 m.add_export("fn_a".into(), [1u8; 32]);
268 m.finalize();
269
270 let wrong_hash = [99u8; 32];
272 let sig_data = crate::crypto::signing::sign_manifest_hash(
273 &wrong_hash,
274 &[42u8; 32],
275 );
276 m.signature = Some(ModuleSignature {
277 author_key: sig_data.author_key,
278 signature: sig_data.signature,
279 signed_at: sig_data.signed_at,
280 });
281
282 assert!(m.verify_signature().is_err());
283 }
284}