ndn_security/did/
document.rs

1//! W3C DID Core document types.
2//!
3//! Implements the full DID Document data model per W3C DID Core §5.
4//!
5//! References:
6//! - <https://www.w3.org/TR/did-core/#did-documents>
7//! - <https://www.w3.org/TR/did-core/#verification-methods>
8//! - <https://www.w3.org/TR/did-core/#verification-relationships>
9//! - <https://www.w3.org/TR/did-core/#services>
10
11use serde::{Deserialize, Serialize};
12
13// ── Controller ────────────────────────────────────────────────────────────────
14
15/// The `controller` property — either a single DID string or a set of DIDs.
16///
17/// Per W3C DID Core §5.1.2, the controller can be one or more DID strings.
18/// When multiple DIDs are listed, each controller independently has the
19/// authority to update or deactivate the subject DID.
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21#[serde(untagged)]
22pub enum DidController {
23    One(String),
24    Many(Vec<String>),
25}
26
27impl DidController {
28    /// Iterate over all controller DIDs.
29    pub fn iter(&self) -> impl Iterator<Item = &str> {
30        match self {
31            Self::One(s) => std::slice::from_ref(s).iter().map(String::as_str),
32            Self::Many(v) => v.iter().map(String::as_str),
33        }
34    }
35
36    /// Returns `true` if `did` is listed as a controller.
37    pub fn contains(&self, did: &str) -> bool {
38        self.iter().any(|d| d == did)
39    }
40}
41
42impl From<String> for DidController {
43    fn from(s: String) -> Self {
44        Self::One(s)
45    }
46}
47
48impl From<Vec<String>> for DidController {
49    fn from(v: Vec<String>) -> Self {
50        if v.len() == 1 {
51            Self::One(v.into_iter().next().unwrap())
52        } else {
53            Self::Many(v)
54        }
55    }
56}
57
58// ── Verification method ───────────────────────────────────────────────────────
59
60/// A verification method entry in a DID Document.
61///
62/// Per W3C DID Core §5.2, a verification method has an `id`, `type`,
63/// `controller`, and one public key representation. The most common types are:
64/// - `JsonWebKey2020` + `publicKeyJwk`
65/// - `Ed25519VerificationKey2020` + `publicKeyMultibase`
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct VerificationMethod {
68    /// DID URL identifying this verification method (e.g. `did:ndn:…#key-0`).
69    pub id: String,
70
71    /// Verification method type.
72    ///
73    /// Common values:
74    /// - `"JsonWebKey2020"` — public key as JWK
75    /// - `"Ed25519VerificationKey2020"` — Ed25519 as multibase
76    /// - `"X25519KeyAgreementKey2020"` — X25519 for ECDH key agreement
77    #[serde(rename = "type")]
78    pub typ: String,
79
80    /// DID of the entity that controls this key.
81    pub controller: String,
82
83    /// Public key as a JSON Web Key (for `JsonWebKey2020` type).
84    #[serde(rename = "publicKeyJwk", skip_serializing_if = "Option::is_none")]
85    pub public_key_jwk: Option<serde_json::Map<String, serde_json::Value>>,
86
87    /// Public key as a multibase-encoded string (for `Ed25519VerificationKey2020`
88    /// and `X25519KeyAgreementKey2020` types). The leading character encodes the
89    /// base: `z` = base58btc.
90    #[serde(rename = "publicKeyMultibase", skip_serializing_if = "Option::is_none")]
91    pub public_key_multibase: Option<String>,
92}
93
94impl VerificationMethod {
95    /// Build a `JsonWebKey2020` verification method from raw Ed25519 public key bytes.
96    pub fn ed25519_jwk(
97        id: impl Into<String>,
98        controller: impl Into<String>,
99        key_bytes: &[u8],
100    ) -> Self {
101        use base64::Engine;
102        let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(key_bytes);
103        let mut jwk = serde_json::Map::new();
104        jwk.insert("kty".to_string(), "OKP".into());
105        jwk.insert("crv".to_string(), "Ed25519".into());
106        jwk.insert("x".to_string(), x.into());
107        Self {
108            id: id.into(),
109            typ: "JsonWebKey2020".to_string(),
110            controller: controller.into(),
111            public_key_jwk: Some(jwk),
112            public_key_multibase: None,
113        }
114    }
115
116    /// Build a `X25519KeyAgreementKey2020` verification method from raw X25519 key bytes.
117    pub fn x25519_jwk(
118        id: impl Into<String>,
119        controller: impl Into<String>,
120        key_bytes: &[u8],
121    ) -> Self {
122        use base64::Engine;
123        let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(key_bytes);
124        let mut jwk = serde_json::Map::new();
125        jwk.insert("kty".to_string(), "OKP".into());
126        jwk.insert("crv".to_string(), "X25519".into());
127        jwk.insert("x".to_string(), x.into());
128        Self {
129            id: id.into(),
130            typ: "JsonWebKey2020".to_string(),
131            controller: controller.into(),
132            public_key_jwk: Some(jwk),
133            public_key_multibase: None,
134        }
135    }
136
137    /// Extract the raw Ed25519 public key bytes from a `JsonWebKey2020` VM.
138    pub fn ed25519_key_bytes(&self) -> Option<[u8; 32]> {
139        use base64::Engine;
140        let jwk = self.public_key_jwk.as_ref()?;
141        if jwk.get("crv").and_then(|v| v.as_str()) != Some("Ed25519") {
142            return None;
143        }
144        let x = jwk.get("x")?.as_str()?;
145        let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
146            .decode(x)
147            .ok()?;
148        if bytes.len() != 32 {
149            return None;
150        }
151        let mut arr = [0u8; 32];
152        arr.copy_from_slice(&bytes);
153        Some(arr)
154    }
155
156    /// Extract the raw X25519 public key bytes from a `JsonWebKey2020` VM.
157    pub fn x25519_key_bytes(&self) -> Option<[u8; 32]> {
158        use base64::Engine;
159        let jwk = self.public_key_jwk.as_ref()?;
160        if jwk.get("crv").and_then(|v| v.as_str()) != Some("X25519") {
161            return None;
162        }
163        let x = jwk.get("x")?.as_str()?;
164        let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
165            .decode(x)
166            .ok()?;
167        if bytes.len() != 32 {
168            return None;
169        }
170        let mut arr = [0u8; 32];
171        arr.copy_from_slice(&bytes);
172        Some(arr)
173    }
174}
175
176// ── Verification reference ────────────────────────────────────────────────────
177
178/// A reference to a verification method — either embedded or a URI reference.
179///
180/// Per W3C DID Core §5.3, verification relationships contain either:
181/// - A full embedded `VerificationMethod` object
182/// - A string DID URL (reference to a VM defined in `verificationMethod`)
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184#[serde(untagged)]
185pub enum VerificationRef {
186    Reference(String),
187    Embedded(VerificationMethod),
188}
189
190impl VerificationRef {
191    /// Extract the ID of the referenced or embedded VM.
192    pub fn id(&self) -> &str {
193        match self {
194            Self::Reference(s) => s.as_str(),
195            Self::Embedded(vm) => vm.id.as_str(),
196        }
197    }
198}
199
200// ── Service ───────────────────────────────────────────────────────────────────
201
202/// A service endpoint in a DID Document.
203///
204/// Per W3C DID Core §5.4, services allow discovery of related resources
205/// (e.g., an NDN prefix where this DID's Data packets are published).
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct Service {
208    /// DID URL identifying this service (e.g. `did:ndn:…#ndn-prefix`).
209    pub id: String,
210
211    /// Service type string (e.g. `"NdnNamespace"`, `"LinkedDomains"`).
212    #[serde(rename = "type")]
213    pub typ: String,
214
215    /// The service endpoint location.
216    #[serde(rename = "serviceEndpoint")]
217    pub service_endpoint: ServiceEndpoint,
218}
219
220/// A service endpoint value.
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
222#[serde(untagged)]
223pub enum ServiceEndpoint {
224    /// A URI string (NDN name URI, HTTPS URL, DID URL, etc.).
225    Uri(String),
226    /// A structured JSON object for complex endpoint descriptions.
227    Map(serde_json::Map<String, serde_json::Value>),
228    /// An array of endpoint URIs or objects.
229    Set(Vec<ServiceEndpoint>),
230}
231
232// ── DID Document ──────────────────────────────────────────────────────────────
233
234/// A W3C DID Document.
235///
236/// Implements the complete DID Document data model per W3C DID Core §5.
237/// All optional fields serialize with `skip_serializing_if` to produce
238/// compliant JSON-LD output.
239///
240/// <https://www.w3.org/TR/did-core/#did-documents>
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct DidDocument {
243    /// JSON-LD context array. Always includes `"https://www.w3.org/ns/did/v1"`.
244    #[serde(rename = "@context")]
245    pub context: Vec<String>,
246
247    /// The DID subject — this document's own DID.
248    pub id: String,
249
250    /// The entity (or entities) authorized to make changes to this DID Document.
251    /// When absent the subject is its own controller.
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub controller: Option<DidController>,
254
255    /// All verification methods defined by this DID Document.
256    /// Verification relationships reference VMs by their `id`.
257    #[serde(
258        rename = "verificationMethod",
259        default,
260        skip_serializing_if = "Vec::is_empty"
261    )]
262    pub verification_methods: Vec<VerificationMethod>,
263
264    /// Keys authorized to authenticate as the DID subject (W3C DID Core §5.3.1).
265    #[serde(default, skip_serializing_if = "Vec::is_empty")]
266    pub authentication: Vec<VerificationRef>,
267
268    /// Keys authorized to issue verifiable credentials on behalf of the subject
269    /// (W3C DID Core §5.3.2).
270    #[serde(
271        rename = "assertionMethod",
272        default,
273        skip_serializing_if = "Vec::is_empty"
274    )]
275    pub assertion_method: Vec<VerificationRef>,
276
277    /// Keys used for key agreement / ECDH encryption (W3C DID Core §5.3.3).
278    ///
279    /// Typically X25519 keys (separate from the Ed25519 signing key).
280    /// Critical for NDA's encrypted content tier.
281    #[serde(
282        rename = "keyAgreement",
283        default,
284        skip_serializing_if = "Vec::is_empty"
285    )]
286    pub key_agreement: Vec<VerificationRef>,
287
288    /// Keys authorized to invoke cryptographic capabilities (W3C DID Core §5.3.4).
289    ///
290    /// Used in NDA's access-control model — capability tokens reference VMs
291    /// listed here.
292    #[serde(
293        rename = "capabilityInvocation",
294        default,
295        skip_serializing_if = "Vec::is_empty"
296    )]
297    pub capability_invocation: Vec<VerificationRef>,
298
299    /// Keys authorized to delegate capabilities to others (W3C DID Core §5.3.5).
300    #[serde(
301        rename = "capabilityDelegation",
302        default,
303        skip_serializing_if = "Vec::is_empty"
304    )]
305    pub capability_delegation: Vec<VerificationRef>,
306
307    /// Service endpoints (W3C DID Core §5.4).
308    #[serde(default, skip_serializing_if = "Vec::is_empty")]
309    pub service: Vec<Service>,
310
311    /// Alternative identifiers for the same subject (URIs or DID strings).
312    /// Used for zone succession: old zone DID lists new zone DID here.
313    #[serde(rename = "alsoKnownAs", default, skip_serializing_if = "Vec::is_empty")]
314    pub also_known_as: Vec<String>,
315}
316
317impl DidDocument {
318    /// Build a minimal DID Document with a single Ed25519 verification method.
319    pub fn new_simple(
320        did: impl Into<String>,
321        key_id: impl Into<String>,
322        public_key: &[u8],
323    ) -> Self {
324        let did = did.into();
325        let key_id = key_id.into();
326        let vm = VerificationMethod::ed25519_jwk(&key_id, &did, public_key);
327        Self {
328            context: vec![
329                "https://www.w3.org/ns/did/v1".to_string(),
330                "https://w3id.org/security/suites/jws-2020/v1".to_string(),
331            ],
332            id: did.clone(),
333            controller: None,
334            verification_methods: vec![vm],
335            authentication: vec![VerificationRef::Reference(key_id.clone())],
336            assertion_method: vec![VerificationRef::Reference(key_id.clone())],
337            key_agreement: vec![],
338            capability_invocation: vec![VerificationRef::Reference(key_id.clone())],
339            capability_delegation: vec![VerificationRef::Reference(key_id)],
340            service: vec![],
341            also_known_as: vec![],
342        }
343    }
344
345    /// Return the first Ed25519 public key bytes found in any verification method.
346    pub fn ed25519_public_key(&self) -> Option<[u8; 32]> {
347        for vm in &self.verification_methods {
348            if let Some(bytes) = vm.ed25519_key_bytes() {
349                return Some(bytes);
350            }
351        }
352        // Also check embedded VMs in authentication.
353        for vr in &self.authentication {
354            if let VerificationRef::Embedded(vm) = vr
355                && let Some(bytes) = vm.ed25519_key_bytes()
356            {
357                return Some(bytes);
358            }
359        }
360        None
361    }
362
363    /// Return the first X25519 key agreement public key bytes.
364    pub fn x25519_key_agreement_key(&self) -> Option<[u8; 32]> {
365        for vr in &self.key_agreement {
366            match vr {
367                VerificationRef::Embedded(vm) => {
368                    if let Some(bytes) = vm.x25519_key_bytes() {
369                        return Some(bytes);
370                    }
371                }
372                VerificationRef::Reference(id) => {
373                    // Look up in verificationMethods.
374                    if let Some(vm) = self.find_vm(id)
375                        && let Some(bytes) = vm.x25519_key_bytes()
376                    {
377                        return Some(bytes);
378                    }
379                }
380            }
381        }
382        None
383    }
384
385    /// Find a verification method by its `id`.
386    pub fn find_vm(&self, id: &str) -> Option<&VerificationMethod> {
387        self.verification_methods
388            .iter()
389            .find(|vm| vm.id == id)
390            .or_else(|| {
391                // Also check fragment-only match.
392                let fragment = id.split_once('#').map(|(_, f)| f).unwrap_or(id);
393                self.verification_methods.iter().find(|vm| {
394                    vm.id == id
395                        || vm
396                            .id
397                            .split_once('#')
398                            .map(|(_, f)| f == fragment)
399                            .unwrap_or(false)
400                })
401            })
402    }
403
404    /// Whether this DID document lists the given DID as a controller.
405    ///
406    /// If `controller` is absent, the subject is its own controller.
407    pub fn is_controlled_by(&self, did: &str) -> bool {
408        match &self.controller {
409            None => self.id == did,
410            Some(ctrl) => ctrl.contains(did),
411        }
412    }
413}