1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct DidDocument {
14 #[serde(rename = "@context")]
16 pub context: Vec<String>,
17 pub id: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub controller: Option<String>,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
24 pub verification_method: Vec<VerificationMethod>,
25 #[serde(default, skip_serializing_if = "Vec::is_empty")]
27 pub authentication: Vec<String>,
28 #[serde(default, skip_serializing_if = "Vec::is_empty")]
30 pub assertion_method: Vec<String>,
31 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub key_agreement: Vec<String>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub service: Vec<DidService>,
37}
38
39impl DidDocument {
40 pub fn new(id: impl Into<String>) -> Self {
42 Self {
43 context: vec![
44 "https://www.w3.org/ns/did/v1".to_string(),
45 "https://w3id.org/security/suites/ed25519-2020/v1".to_string(),
46 "https://ns.tenzro.network/v1".to_string(),
47 ],
48 id: id.into(),
49 controller: None,
50 verification_method: Vec::new(),
51 authentication: Vec::new(),
52 assertion_method: Vec::new(),
53 key_agreement: Vec::new(),
54 service: Vec::new(),
55 }
56 }
57
58 pub fn with_controller(mut self, controller: impl Into<String>) -> Self {
60 self.controller = Some(controller.into());
61 self
62 }
63
64 pub fn add_verification_method(&mut self, method: VerificationMethod) {
66 let method_id = method.id.clone();
67 for purpose in &method.purposes {
68 match purpose {
69 VerificationPurpose::Authentication => {
70 self.authentication.push(method_id.clone());
71 }
72 VerificationPurpose::AssertionMethod => {
73 self.assertion_method.push(method_id.clone());
74 }
75 VerificationPurpose::KeyAgreement => {
76 self.key_agreement.push(method_id.clone());
77 }
78 }
79 }
80 self.verification_method.push(method);
81 }
82
83 pub fn add_service(&mut self, service: DidService) {
85 self.service.push(service);
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct VerificationMethod {
92 pub id: String,
94 pub controller: String,
96 #[serde(rename = "type")]
98 pub method_type: String,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub public_key_multibase: Option<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub public_key_jwk: Option<serde_json::Value>,
105 #[serde(skip)]
107 pub purposes: Vec<VerificationPurpose>,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum VerificationPurpose {
113 Authentication,
115 AssertionMethod,
117 KeyAgreement,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct DidService {
124 pub id: String,
126 #[serde(rename = "type")]
128 pub service_type: String,
129 #[serde(rename = "serviceEndpoint")]
131 pub service_endpoint: String,
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_create_did_document() {
140 let doc = DidDocument::new("did:tenzro:human:alice-uuid");
141 assert_eq!(doc.id, "did:tenzro:human:alice-uuid");
142 assert!(doc.context.contains(&"https://www.w3.org/ns/did/v1".to_string()));
143 assert!(doc.controller.is_none());
144 }
145
146 #[test]
147 fn test_did_document_with_controller() {
148 let doc = DidDocument::new("did:tenzro:machine:ctrl:bot1")
149 .with_controller("did:tenzro:human:ctrl");
150 assert_eq!(doc.controller, Some("did:tenzro:human:ctrl".to_string()));
151 }
152
153 #[test]
154 fn test_add_verification_method() {
155 let mut doc = DidDocument::new("did:tenzro:human:alice");
156
157 let method = VerificationMethod {
158 id: "did:tenzro:human:alice#key-1".to_string(),
159 controller: "did:tenzro:human:alice".to_string(),
160 method_type: "Ed25519VerificationKey2020".to_string(),
161 public_key_multibase: Some("z6Mk...".to_string()),
162 public_key_jwk: None,
163 purposes: vec![
164 VerificationPurpose::Authentication,
165 VerificationPurpose::AssertionMethod,
166 ],
167 };
168
169 doc.add_verification_method(method);
170
171 assert_eq!(doc.verification_method.len(), 1);
172 assert_eq!(doc.authentication.len(), 1);
173 assert_eq!(doc.assertion_method.len(), 1);
174 assert_eq!(doc.key_agreement.len(), 0);
175 }
176
177 #[test]
178 fn test_add_service() {
179 let mut doc = DidDocument::new("did:tenzro:human:alice");
180
181 doc.add_service(DidService {
182 id: "did:tenzro:human:alice#inference".to_string(),
183 service_type: "InferenceEndpoint".to_string(),
184 service_endpoint: "https://node.tenzro.com/inference".to_string(),
185 });
186
187 assert_eq!(doc.service.len(), 1);
188 assert_eq!(doc.service[0].service_type, "InferenceEndpoint");
189 }
190
191 #[test]
192 fn test_serialize_did_document() {
193 let doc = DidDocument::new("did:tenzro:human:alice");
194 let json = serde_json::to_string_pretty(&doc).unwrap();
195 assert!(json.contains("@context"));
196 assert!(json.contains("did:tenzro:human:alice"));
197 }
198
199 #[test]
200 fn test_w3c_did_document_json_ld_compliance() {
201 let mut doc = DidDocument::new("did:tenzro:human:alice-uuid");
202
203 doc.add_verification_method(VerificationMethod {
205 id: "did:tenzro:human:alice-uuid#key-1".to_string(),
206 controller: "did:tenzro:human:alice-uuid".to_string(),
207 method_type: "Ed25519VerificationKey2020".to_string(),
208 public_key_multibase: Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()),
209 public_key_jwk: None,
210 purposes: vec![VerificationPurpose::Authentication],
211 });
212
213 let json = serde_json::to_string_pretty(&doc).unwrap();
215
216 assert!(json.contains("@context"), "Missing @context field");
218 assert!(json.contains("https://www.w3.org/ns/did/v1"), "Missing W3C DID context");
219 assert!(json.contains("\"id\""), "Missing id field");
220 assert!(json.contains("verificationMethod"), "Missing verificationMethod");
221 assert!(json.contains("authentication"), "Missing authentication relationship");
222
223 let parsed: DidDocument = serde_json::from_str(&json).unwrap();
225 assert_eq!(parsed.id, "did:tenzro:human:alice-uuid");
226 assert_eq!(parsed.context.len(), 3);
227 assert_eq!(parsed.verification_method.len(), 1);
228 assert_eq!(parsed.authentication.len(), 1);
229 }
230
231 #[test]
232 fn test_did_document_roundtrip_with_all_fields() {
233 let mut doc = DidDocument::new("did:tenzro:machine:ctrl:bot")
234 .with_controller("did:tenzro:human:ctrl");
235
236 doc.add_verification_method(VerificationMethod {
237 id: "did:tenzro:machine:ctrl:bot#key-1".to_string(),
238 controller: "did:tenzro:machine:ctrl:bot".to_string(),
239 method_type: "Ed25519VerificationKey2020".to_string(),
240 public_key_multibase: Some("z6Mk...".to_string()),
241 public_key_jwk: None,
242 purposes: vec![
243 VerificationPurpose::Authentication,
244 VerificationPurpose::AssertionMethod,
245 VerificationPurpose::KeyAgreement,
246 ],
247 });
248
249 doc.add_service(DidService {
250 id: "did:tenzro:machine:ctrl:bot#inference".to_string(),
251 service_type: "InferenceEndpoint".to_string(),
252 service_endpoint: "https://node.example.com/inference".to_string(),
253 });
254
255 let json = serde_json::to_string(&doc).unwrap();
257
258 let parsed: DidDocument = serde_json::from_str(&json).unwrap();
260
261 assert_eq!(parsed.id, doc.id);
263 assert_eq!(parsed.controller, doc.controller);
264 assert_eq!(parsed.verification_method.len(), 1);
265 assert_eq!(parsed.authentication.len(), 1);
266 assert_eq!(parsed.assertion_method.len(), 1);
267 assert_eq!(parsed.key_agreement.len(), 1);
268 assert_eq!(parsed.service.len(), 1);
269 }
270}