Skip to main content

typesec_integrations/did/
document.rs

1//! DID document model and resolver boundary.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::crypto::{hex_decode, hex_encode};
8use super::error::DidError;
9use super::identifier::Did;
10
11/// A verification method from a DID document.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct VerificationMethod {
14    /// DID URL for this key.
15    pub id: String,
16    /// Verification method type.
17    #[serde(rename = "type")]
18    pub method_type: String,
19    /// Controller DID.
20    pub controller: Did,
21    /// Public key bytes encoded as hex for this local integration.
22    pub public_key_hex: String,
23    /// Optional local key version for rotation-aware DID documents.
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub key_version: Option<u64>,
26    /// Optional local rotation status (`active` or `previous`).
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub key_status: Option<String>,
29}
30
31impl VerificationMethod {
32    /// Construct a local Ed25519-like method for examples and tests.
33    pub fn local(id: impl Into<String>, controller: Did, public_key: impl AsRef<[u8]>) -> Self {
34        Self {
35            id: id.into(),
36            method_type: "TypesecDemoKey2026".to_owned(),
37            controller,
38            public_key_hex: hex_encode(public_key.as_ref()),
39            key_version: None,
40            key_status: None,
41        }
42    }
43
44    pub(super) fn public_key(&self) -> Result<Vec<u8>, DidError> {
45        hex_decode(&self.public_key_hex)
46    }
47}
48
49/// Service endpoint metadata from a DID document.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct DidService {
52    /// DID URL for this service.
53    pub id: String,
54    /// Service type.
55    #[serde(rename = "type")]
56    pub service_type: String,
57    /// Endpoint URL.
58    pub service_endpoint: String,
59}
60
61/// Minimal DID document model used by Typesec integrations.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct DidDocument {
64    /// Subject DID.
65    pub id: Did,
66    /// Verification methods available for this DID.
67    #[serde(default)]
68    pub verification_method: Vec<VerificationMethod>,
69    /// Authentication key references.
70    #[serde(default)]
71    pub authentication: Vec<String>,
72    /// Key-agreement key references.
73    #[serde(default)]
74    pub key_agreement: Vec<String>,
75    /// Service endpoints.
76    #[serde(default)]
77    pub service: Vec<DidService>,
78}
79
80impl DidDocument {
81    /// Create a document with one key used for authentication and key agreement.
82    pub fn single_key(did: Did, public_key: impl AsRef<[u8]>) -> Self {
83        let key_id = format!("{did}#key-1");
84        Self {
85            id: did.clone(),
86            verification_method: vec![VerificationMethod::local(&key_id, did, public_key)],
87            authentication: vec![key_id.clone()],
88            key_agreement: vec![key_id],
89            service: Vec::new(),
90        }
91    }
92
93    /// Create a document with separate Ed25519 (authentication) and X25519
94    /// (key-agreement) keys, as produced by [`Ed25519DidKey`].
95    ///
96    /// [`Ed25519DidKey`]: super::keystore::Ed25519DidKey
97    pub fn with_signing_and_agreement_keys(
98        did: Did,
99        signing_public: impl AsRef<[u8]>,
100        agreement_public: impl AsRef<[u8]>,
101    ) -> Self {
102        let signing_id = format!("{did}#key-1");
103        let agreement_id = format!("{did}#key-2");
104        Self {
105            id: did.clone(),
106            verification_method: vec![
107                VerificationMethod {
108                    id: signing_id.clone(),
109                    method_type: "Ed25519VerificationKey2020".to_owned(),
110                    controller: did.clone(),
111                    public_key_hex: hex_encode(signing_public.as_ref()),
112                    key_version: Some(1),
113                    key_status: Some("active".to_owned()),
114                },
115                VerificationMethod {
116                    id: agreement_id.clone(),
117                    method_type: "X25519KeyAgreementKey2020".to_owned(),
118                    controller: did,
119                    public_key_hex: hex_encode(agreement_public.as_ref()),
120                    key_version: Some(1),
121                    key_status: Some("active".to_owned()),
122                },
123            ],
124            authentication: vec![signing_id],
125            key_agreement: vec![agreement_id],
126            service: Vec::new(),
127        }
128    }
129
130    fn method(&self, id: &str) -> Option<&VerificationMethod> {
131        self.verification_method
132            .iter()
133            .find(|method| method.id == id)
134    }
135
136    pub(super) fn authentication_key(&self, kid: &str) -> Result<&VerificationMethod, DidError> {
137        if !self.authentication.iter().any(|id| id == kid) {
138            return Err(DidError::MissingVerificationMethod(kid.to_owned()));
139        }
140        self.method(kid)
141            .ok_or_else(|| DidError::MissingVerificationMethod(kid.to_owned()))
142    }
143
144    pub(super) fn key_agreement_key(&self) -> Result<&VerificationMethod, DidError> {
145        let kid = self
146            .key_agreement
147            .first()
148            .ok_or(DidError::MissingKeyAgreement)?;
149        self.method(kid)
150            .ok_or_else(|| DidError::MissingVerificationMethod(kid.clone()))
151    }
152
153    pub(super) fn key_agreement_keys(&self) -> Result<Vec<&VerificationMethod>, DidError> {
154        if self.key_agreement.is_empty() {
155            return Err(DidError::MissingKeyAgreement);
156        }
157
158        self.key_agreement
159            .iter()
160            .map(|kid| {
161                self.method(kid)
162                    .ok_or_else(|| DidError::MissingVerificationMethod(kid.clone()))
163            })
164            .collect()
165    }
166}
167
168/// DID resolver boundary.
169pub trait DidResolver: Send + Sync {
170    /// Resolve `did` to a DID document.
171    fn resolve(&self, did: &Did) -> Result<DidDocument, DidError>;
172}
173
174/// In-memory DID resolver for tests and local demos.
175#[derive(Debug, Default, Clone)]
176pub struct StaticDidResolver {
177    documents: HashMap<Did, DidDocument>,
178}
179
180impl StaticDidResolver {
181    /// Create an empty resolver.
182    pub fn new() -> Self {
183        Self::default()
184    }
185
186    /// Register a DID document.
187    pub fn with_document(mut self, document: DidDocument) -> Self {
188        self.documents.insert(document.id.clone(), document);
189        self
190    }
191}
192
193impl DidResolver for StaticDidResolver {
194    fn resolve(&self, did: &Did) -> Result<DidDocument, DidError> {
195        self.documents
196            .get(did)
197            .cloned()
198            .ok_or_else(|| DidError::Unresolved(did.to_string()))
199    }
200}