ndn_security/did/resolver/
key.rs

1//! `did:key` resolver — fully local, no network required.
2//!
3//! Only Ed25519 keys (`z6Mk...` — multicodec 0xed01 + base58btc multibase) are
4//! supported. Per the `did:key` specification, the DID Document is derived
5//! directly from the public key bytes without any network fetch.
6//!
7//! Reference: <https://w3c-ccg.github.io/did-method-key/>
8
9use std::{future::Future, pin::Pin};
10
11use crate::did::{
12    document::{DidDocument, VerificationMethod, VerificationRef},
13    metadata::{DidResolutionError, DidResolutionResult},
14    resolver::DidResolver,
15};
16
17/// Multicodec prefix for Ed25519 public keys.
18const ED25519_MULTICODEC: [u8; 2] = [0xed, 0x01];
19
20/// Resolves `did:key` DIDs locally without any network access.
21pub struct KeyDidResolver;
22
23impl DidResolver for KeyDidResolver {
24    fn method(&self) -> &str {
25        "key"
26    }
27
28    fn resolve<'a>(
29        &'a self,
30        did: &'a str,
31    ) -> Pin<Box<dyn Future<Output = DidResolutionResult> + Send + 'a>> {
32        let did = did.to_string();
33        Box::pin(async move {
34            match resolve_key_did(&did) {
35                Ok(doc) => DidResolutionResult::ok(doc),
36                Err(e) => DidResolutionResult::err(
37                    match &e {
38                        s if s.contains("invalid") || s.contains("unsupported") => {
39                            DidResolutionError::InvalidDid
40                        }
41                        _ => DidResolutionError::InternalError,
42                    },
43                    e,
44                ),
45            }
46        })
47    }
48}
49
50fn resolve_key_did(did: &str) -> Result<DidDocument, String> {
51    let key_str = did
52        .strip_prefix("did:key:")
53        .ok_or_else(|| format!("not a did:key DID: {did}"))?;
54
55    let public_key = decode_multibase_key(key_str)?;
56    let key_id = format!("{did}#{key_str}");
57
58    let vm = VerificationMethod::ed25519_jwk(&key_id, did, &public_key);
59
60    Ok(DidDocument {
61        context: vec![
62            "https://www.w3.org/ns/did/v1".to_string(),
63            "https://w3id.org/security/suites/jws-2020/v1".to_string(),
64        ],
65        id: did.to_string(),
66        controller: None,
67        verification_methods: vec![vm],
68        authentication: vec![VerificationRef::Reference(key_id.clone())],
69        assertion_method: vec![VerificationRef::Reference(key_id.clone())],
70        key_agreement: vec![],
71        capability_invocation: vec![VerificationRef::Reference(key_id.clone())],
72        capability_delegation: vec![VerificationRef::Reference(key_id)],
73        service: vec![],
74        also_known_as: vec![],
75    })
76}
77
78fn decode_multibase_key(encoded: &str) -> Result<Vec<u8>, String> {
79    let b58 = encoded
80        .strip_prefix('z')
81        .ok_or_else(|| format!("unsupported multibase prefix in {encoded}"))?;
82
83    let bytes = bs58_decode(b58).map_err(|_| format!("invalid base58 in {encoded}"))?;
84
85    if bytes.len() < 2 {
86        return Err("key too short".to_string());
87    }
88
89    if bytes[0] == ED25519_MULTICODEC[0] && bytes[1] == ED25519_MULTICODEC[1] {
90        Ok(bytes[2..].to_vec())
91    } else {
92        Err(format!(
93            "unsupported key type (multicodec prefix {:02x}{:02x})",
94            bytes[0], bytes[1]
95        ))
96    }
97}
98
99/// Minimal base58 decoder (Bitcoin alphabet).
100fn bs58_decode(s: &str) -> Result<Vec<u8>, ()> {
101    const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
102    let mut result: Vec<u8> = vec![0];
103    for c in s.bytes() {
104        let digit = ALPHABET.iter().position(|&b| b == c).ok_or(())? as u64;
105        let mut carry = digit;
106        for byte in result.iter_mut().rev() {
107            carry += (*byte as u64) * 58;
108            *byte = (carry & 0xFF) as u8;
109            carry >>= 8;
110        }
111        while carry > 0 {
112            result.insert(0, (carry & 0xFF) as u8);
113            carry >>= 8;
114        }
115    }
116    let leading_zeros = s.bytes().take_while(|&b| b == b'1').count();
117    let mut out = vec![0u8; leading_zeros];
118    let trimmed: Vec<u8> = result.into_iter().skip_while(|&b| b == 0).collect();
119    out.extend(trimmed);
120    Ok(out)
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[tokio::test]
128    async fn resolve_known_key_did() {
129        let did = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
130        let resolver = KeyDidResolver;
131        let result = resolver.resolve(did).await;
132        assert!(result.did_resolution_metadata.error.is_none(), "{result:?}");
133        let doc = result.did_document.unwrap();
134        assert_eq!(doc.id, did);
135        assert!(!doc.verification_methods.is_empty());
136        assert!(!doc.authentication.is_empty());
137        assert!(!doc.capability_invocation.is_empty());
138    }
139
140    #[tokio::test]
141    async fn invalid_did_returns_error_result() {
142        let resolver = KeyDidResolver;
143        let result = resolver.resolve("did:key:invalid").await;
144        assert!(result.did_resolution_metadata.error.is_some());
145        assert!(result.did_document.is_none());
146    }
147}