Skip to main content

affinidi_did_common/did_method/
resolve.rs

1//! DID resolution for locally-resolvable methods
2//!
3//! This module implements resolution for methods that can be resolved
4//! without network access (did:key, did:peer).
5//!
6//! For network-resolvable methods (did:web, did:cheqd, etc.), use a `Resolver` trait
7//! implementation that handles HTTP requests and caching.
8
9use std::collections::HashMap;
10
11use serde_json::{Value, json};
12
13use affinidi_crypto::ed25519::ed25519_public_to_x25519;
14use affinidi_encoding::{ED25519_PUB, P256_PUB, P384_PUB, SECP256K1_PUB, X25519_PUB};
15
16use super::DIDMethod;
17use super::peer::{PeerNumAlgo, PeerPurpose, PeerService};
18use crate::{
19    DID, DIDError, Document,
20    service::Service,
21    verification_method::{VerificationMethod, VerificationRelationship},
22};
23
24const PUBLIC_KEY_MULTIBASE: &str = "publicKeyMultibase";
25const MULTIKEY_TYPE: &str = "Multikey";
26
27impl DIDMethod {
28    /// Resolve this DID method to a DID Document
29    ///
30    /// Works for locally-resolvable methods (did:key, did:peer).
31    /// For network methods, returns an error indicating external resolution is needed.
32    pub fn resolve(&self, did: &DID) -> Result<Document, DIDError> {
33        match self {
34            DIDMethod::Key { identifier, .. } => resolve_key(did, identifier),
35            DIDMethod::Peer {
36                numalgo,
37                identifier,
38            } => resolve_peer(did, numalgo, identifier),
39            _ => Err(DIDError::ResolutionError(format!(
40                "DID method '{}' requires network resolution",
41                self.name()
42            ))),
43        }
44    }
45}
46
47/// Resolve a did:key to its DID Document
48fn resolve_key(did: &DID, identifier: &str) -> Result<Document, DIDError> {
49    // Get the codec (already validated at parse time)
50    let (codec, _) = affinidi_encoding::decode_multikey_with_codec(identifier)
51        .map_err(|e| DIDError::ResolutionError(format!("Invalid multikey: {e}")))?;
52
53    let mut vm_id = did.url();
54    vm_id.set_fragment(Some(identifier));
55
56    let mut vms = Vec::new();
57    let mut key_agreement = Vec::new();
58
59    match codec {
60        ED25519_PUB => {
61            // Ed25519 keys also derive an X25519 key for key agreement
62            let (x25519_encoded, _) = ed25519_public_to_x25519(identifier).map_err(|e| {
63                DIDError::ResolutionError(format!("Failed to derive X25519 from Ed25519: {e}"))
64            })?;
65
66            let mut x25519_vm_id = did.url();
67            x25519_vm_id.set_fragment(Some(&x25519_encoded));
68
69            vms.push(VerificationMethod {
70                id: x25519_vm_id.clone(),
71                type_: MULTIKEY_TYPE.to_string(),
72                controller: did.url(),
73                expires: None,
74                revoked: None,
75                property_set: HashMap::from([(
76                    PUBLIC_KEY_MULTIBASE.to_string(),
77                    Value::String(x25519_encoded.to_string()),
78                )]),
79            });
80
81            key_agreement.push(VerificationRelationship::Reference(
82                x25519_vm_id.to_string(),
83            ));
84        }
85        P256_PUB | P384_PUB | SECP256K1_PUB | X25519_PUB => {
86            key_agreement.push(VerificationRelationship::Reference(vm_id.to_string()));
87        }
88        _ => {
89            return Err(DIDError::ResolutionError(format!(
90                "Unsupported key codec: 0x{codec:x}"
91            )));
92        }
93    }
94
95    // Primary verification method (inserted at front)
96    vms.insert(
97        0,
98        VerificationMethod {
99            id: vm_id.clone(),
100            type_: MULTIKEY_TYPE.to_string(),
101            controller: did.url(),
102            expires: None,
103            revoked: None,
104            property_set: HashMap::from([(
105                PUBLIC_KEY_MULTIBASE.to_string(),
106                Value::String(identifier.to_string()),
107            )]),
108        },
109    );
110
111    let vm_relationship = VerificationRelationship::Reference(vm_id.to_string());
112
113    Ok(Document {
114        id: did.url(),
115        verification_method: vms,
116        authentication: vec![vm_relationship.clone()],
117        assertion_method: vec![vm_relationship.clone()],
118        key_agreement,
119        capability_invocation: vec![vm_relationship.clone()],
120        capability_delegation: vec![vm_relationship],
121        service: vec![],
122        parameters_set: HashMap::from([(
123            "@context".to_string(),
124            json!([
125                "https://www.w3.org/ns/did/v1",
126                "https://w3id.org/security/multikey/v1",
127            ]),
128        )]),
129    })
130}
131
132/// Resolve a did:peer to its DID Document
133fn resolve_peer(did: &DID, numalgo: &PeerNumAlgo, identifier: &str) -> Result<Document, DIDError> {
134    match numalgo {
135        PeerNumAlgo::InceptionKey => {
136            // Numalgo 0: The identifier IS the did:key multibase
137            // Strip the leading '0' and treat as did:key
138            let key_multibase = identifier.strip_prefix('0').unwrap_or(identifier);
139            let key_did: DID = format!("did:key:{key_multibase}")
140                .parse()
141                .map_err(|e| DIDError::ResolutionError(format!("Invalid did:peer:0 key: {e}")))?;
142            key_did.resolve()
143        }
144        PeerNumAlgo::MultipleKeys => resolve_peer_2(did, identifier),
145        PeerNumAlgo::GenesisDoc => Err(DIDError::ResolutionError(
146            "did:peer numalgo 1 (genesis doc) is not supported".to_string(),
147        )),
148    }
149}
150
151/// Resolve did:peer:2 format
152///
153/// Format: did:peer:2.<purpose><multibase>.<purpose><multibase>...S<base64-service>...
154fn resolve_peer_2(did: &DID, identifier: &str) -> Result<Document, DIDError> {
155    use std::str::FromStr;
156    use url::Url;
157
158    let did_string = did.to_string();
159
160    // Skip the leading '2' and split on '.'
161    let content = identifier.strip_prefix('2').unwrap_or(identifier);
162    let parts: Vec<&str> = content.split('.').filter(|s| !s.is_empty()).collect();
163
164    let mut verification_methods: Vec<VerificationMethod> = Vec::new();
165    let mut authentication: Vec<VerificationRelationship> = Vec::new();
166    let mut assertion_method: Vec<VerificationRelationship> = Vec::new();
167    let mut key_agreement: Vec<VerificationRelationship> = Vec::new();
168    let mut capability_delegation: Vec<VerificationRelationship> = Vec::new();
169    let mut capability_invocation: Vec<VerificationRelationship> = Vec::new();
170    let mut services: Vec<Service> = Vec::new();
171
172    let mut key_count: u32 = 0;
173    let mut service_idx: u32 = 0;
174
175    for part in parts {
176        if part.is_empty() {
177            continue;
178        }
179
180        let purpose_char = part
181            .chars()
182            .next()
183            .ok_or_else(|| DIDError::ResolutionError("Empty part in did:peer".to_string()))?;
184
185        let purpose = PeerPurpose::from_char(purpose_char).ok_or_else(|| {
186            DIDError::ResolutionError(format!("Invalid purpose code: {purpose_char}"))
187        })?;
188
189        if purpose == PeerPurpose::Service {
190            // Decode service
191            let service = PeerService::decode(part)
192                .map_err(|e| DIDError::ResolutionError(format!("Service decode error: {e}")))?;
193
194            let did_service = service
195                .to_did_service(&did_string, service_idx)
196                .map_err(|e| DIDError::ResolutionError(format!("Service conversion error: {e}")))?;
197
198            services.push(did_service);
199            service_idx += 1;
200        } else {
201            // Key entry
202            key_count += 1;
203            let kid = format!("{did_string}#key-{key_count}");
204            let public_key_multibase = &part[1..]; // Skip purpose char
205
206            let vm = VerificationMethod {
207                id: Url::from_str(&kid)
208                    .map_err(|e| DIDError::ResolutionError(format!("Invalid key ID: {e}")))?,
209                type_: MULTIKEY_TYPE.to_string(),
210                controller: did.url(),
211                expires: None,
212                revoked: None,
213                property_set: HashMap::from([(
214                    PUBLIC_KEY_MULTIBASE.to_string(),
215                    Value::String(public_key_multibase.to_string()),
216                )]),
217            };
218
219            verification_methods.push(vm);
220
221            let relationship = VerificationRelationship::Reference(kid);
222
223            match purpose {
224                PeerPurpose::Verification => {
225                    authentication.push(relationship.clone());
226                    assertion_method.push(relationship);
227                }
228                PeerPurpose::Encryption => {
229                    key_agreement.push(relationship);
230                }
231                PeerPurpose::Assertion => {
232                    assertion_method.push(relationship);
233                }
234                PeerPurpose::Delegation => {
235                    capability_delegation.push(relationship);
236                }
237                PeerPurpose::Invocation => {
238                    capability_invocation.push(relationship);
239                }
240                PeerPurpose::Service => unreachable!(),
241            }
242        }
243    }
244
245    Ok(Document {
246        id: did.url(),
247        verification_method: verification_methods,
248        authentication,
249        assertion_method,
250        key_agreement,
251        capability_delegation,
252        capability_invocation,
253        service: services,
254        parameters_set: HashMap::from([(
255            "@context".to_string(),
256            json!(["https://www.w3.org/ns/did/v1.1"]),
257        )]),
258    })
259}
260
261#[cfg(test)]
262mod tests {
263    use crate::DID;
264
265    #[test]
266    fn test_resolve_ed25519() {
267        let did: DID = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
268            .parse()
269            .unwrap();
270        let doc = did.method().resolve(&did).unwrap();
271
272        assert_eq!(
273            doc.id.as_str(),
274            "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
275        );
276        // Ed25519 should have 2 verification methods (Ed25519 + derived X25519)
277        assert_eq!(doc.verification_method.len(), 2);
278        assert_eq!(doc.key_agreement.len(), 1);
279    }
280
281    #[test]
282    fn test_resolve_p256() {
283        let did: DID = "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
284            .parse()
285            .unwrap();
286        let doc = did.method().resolve(&did).unwrap();
287
288        assert_eq!(
289            doc.id.as_str(),
290            "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
291        );
292        // P-256 should have 1 verification method
293        assert_eq!(doc.verification_method.len(), 1);
294        assert_eq!(doc.key_agreement.len(), 1);
295    }
296
297    #[test]
298    fn test_resolve_secp256k1() {
299        let did: DID = "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1a7y8xs6zTcNNvoB5e"
300            .parse()
301            .unwrap();
302        let doc = did.method().resolve(&did).unwrap();
303
304        assert_eq!(doc.verification_method.len(), 1);
305    }
306
307    #[test]
308    fn test_resolve_web_requires_network() {
309        let did: DID = "did:web:example.com".parse().unwrap();
310        let result = did.method().resolve(&did);
311
312        assert!(result.is_err());
313    }
314
315    #[test]
316    fn test_resolve_peer_numalgo_0() {
317        // did:peer:0 wraps a did:key
318        let did: DID = "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
319            .parse()
320            .unwrap();
321        let doc = did.resolve().unwrap();
322
323        // Should resolve like did:key
324        assert_eq!(doc.verification_method.len(), 2); // Ed25519 + derived X25519
325        assert_eq!(doc.authentication.len(), 1);
326        assert_eq!(doc.key_agreement.len(), 1);
327    }
328
329    #[test]
330    fn test_resolve_peer_numalgo_2() {
331        // did:peer:2 with V (verification) and E (encryption) keys
332        let did: DID = "did:peer:2.Vz6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"
333            .parse()
334            .unwrap();
335        let doc = did.resolve().unwrap();
336
337        // Should have 2 verification methods
338        assert_eq!(doc.verification_method.len(), 2);
339        // V key goes to authentication and assertion
340        assert_eq!(doc.authentication.len(), 1);
341        assert_eq!(doc.assertion_method.len(), 1);
342        // E key goes to key_agreement
343        assert_eq!(doc.key_agreement.len(), 1);
344    }
345
346    #[test]
347    fn test_resolve_peer_numalgo_2_with_service() {
348        // did:peer:2 with service encoded
349        // Service: {"t":"dm","s":"https://example.com/didcomm"}
350        let did: DID = "did:peer:2.Vz6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9kaWRjb21tIn0"
351            .parse()
352            .unwrap();
353        let doc = did.resolve().unwrap();
354
355        assert_eq!(doc.verification_method.len(), 1);
356        assert_eq!(doc.service.len(), 1);
357        assert_eq!(doc.service[0].type_, vec!["DIDCommMessaging".to_string()]);
358    }
359}