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
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_new_manifest_has_zero_hash() {
138 let m = ModuleManifest::new("test".into(), "0.1.0".into());
139 assert_eq!(m.manifest_hash, [0u8; 32]);
140 assert!(m.exports.is_empty());
141 assert!(m.type_schemas.is_empty());
142 }
143
144 #[test]
145 fn test_finalize_produces_nonzero_hash() {
146 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
147 m.add_export("greet".into(), [1u8; 32]);
148 m.finalize();
149 assert_ne!(m.manifest_hash, [0u8; 32]);
150 }
151
152 #[test]
153 fn test_verify_integrity_passes_after_finalize() {
154 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
155 m.add_export("greet".into(), [1u8; 32]);
156 m.add_type_schema("MyType".into(), [2u8; 32]);
157 m.required_permission_bits = 0x03;
158 m.finalize();
159 assert!(m.verify_integrity());
160 }
161
162 #[test]
163 fn test_verify_integrity_fails_after_mutation() {
164 let mut m = ModuleManifest::new("mymod".into(), "1.0.0".into());
165 m.add_export("greet".into(), [1u8; 32]);
166 m.finalize();
167 assert!(m.verify_integrity());
168
169 m.add_export("farewell".into(), [3u8; 32]);
170 assert!(!m.verify_integrity());
171 }
172
173 #[test]
174 fn test_deterministic_hash() {
175 let build = || {
176 let mut m = ModuleManifest::new("det".into(), "0.0.1".into());
177 m.add_export("b_fn".into(), [10u8; 32]);
178 m.add_export("a_fn".into(), [20u8; 32]);
179 m.add_type_schema("Z".into(), [30u8; 32]);
180 m.add_type_schema("A".into(), [40u8; 32]);
181 m.finalize();
182 m.manifest_hash
183 };
184 assert_eq!(build(), build());
185 }
186
187 #[test]
188 fn test_serde_roundtrip() {
189 let mut m = ModuleManifest::new("serde_test".into(), "2.0.0".into());
190 m.add_export("run".into(), [7u8; 32]);
191 m.required_permission_bits = 0xFF;
192 m.finalize();
193
194 let json = serde_json::to_string(&m).expect("serialize");
195 let restored: ModuleManifest = serde_json::from_str(&json).expect("deserialize");
196
197 assert_eq!(restored.name, "serde_test");
198 assert_eq!(restored.version, "2.0.0");
199 assert_eq!(restored.exports.get("run"), Some(&[7u8; 32]));
200 assert_eq!(restored.required_permission_bits, 0xFF);
201 assert!(restored.verify_integrity());
202 }
203}