credibil_did/key/
resolver.rs

1//! # DID Key Resolver
2//!
3//! The `did:key` method is a DID method for static cryptographic keys. At its
4//! core, it is based on expanding a cryptographic public key into a DID
5//! Document.
6//!
7//! See:
8//!
9//! - <https://w3c-ccg.github.io/did-method-key>
10//! - <https://w3c.github.io/did-resolution>
11
12use std::sync::LazyLock;
13
14use regex::Regex;
15use serde_json::json;
16
17use super::DidKey;
18use crate::document::{CreateOptions, MethodType};
19use crate::error::Error;
20use crate::resolution::{ContentType, Metadata, Resolved};
21use crate::{DidOperator, KeyPurpose, PublicKeyJwk};
22
23static DID_REGEX: LazyLock<Regex> = LazyLock::new(|| {
24    Regex::new("^did:key:(?<identifier>z[a-km-zA-HJ-NP-Z1-9]+)$").expect("should compile")
25});
26
27struct Operator(MethodType);
28impl DidOperator for Operator {
29    fn verification(&self, purpose: KeyPurpose) -> Option<PublicKeyJwk> {
30        match purpose {
31            KeyPurpose::VerificationMethod => self.0.jwk().ok(),
32            _ => panic!("unsupported purpose"),
33        }
34    }
35}
36
37impl DidKey {
38    /// Resolve the provided `did:key` URL to a DID Document.
39    /// 
40    /// # Errors
41    /// 
42    /// Will fail if the DID is not a valid `did:key` URL.
43    pub fn resolve(did: &str) -> crate::Result<Resolved> {
44        // check DID is valid AND extract key
45        let Some(caps) = DID_REGEX.captures(did) else {
46            return Err(Error::InvalidDid("DID is not a valid did:key".into()));
47        };
48        let multikey = &caps["identifier"];
49
50        let op = Operator(MethodType::Multikey {
51            public_key_multibase: multikey.to_string(),
52        });
53
54        // per the spec, use the create operation to generate a DID document
55        let options = CreateOptions {
56            enable_encryption_key_derivation: true,
57            ..CreateOptions::default()
58        };
59
60        let document = Self::create(&op, options).map_err(|e| Error::InvalidDid(e.to_string()))?;
61
62        Ok(Resolved {
63            context: "https://w3id.org/did-resolution/v1".into(),
64            metadata: Metadata {
65                content_type: ContentType::DidLdJson,
66                additional: Some(json!({
67                    "pattern": "^did:key:z[a-km-zA-HJ-NP-Z1-9]+$",
68                    "did": {
69                        "didString": did,
70                        "methodSpecificId": did[8..],
71                        "method": "key"
72                    }
73                })),
74                ..Metadata::default()
75            },
76            document: Some(document),
77            ..Resolved::default()
78        })
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use super::*;
85
86    // const DID: &str = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
87    const DID: &str = "did:key:z6Mkj8Jr1rg3YjVWWhg7ahEYJibqhjBgZt1pDCbT4Lv7D4HX";
88
89    #[tokio::test]
90    async fn resolve() {
91        let resolved = DidKey::resolve(DID).expect("should resolve");
92        println!("{}", serde_json::to_string_pretty(&resolved).unwrap());
93        // assert_snapshot!("resolved", resolved);
94    }
95}