Skip to main content

ojak_sig/
did.rs

1//! `did:key`, the DID method every portable object (FEP-ef61) can be
2//! verified with, because it needs no resolution: the DID is the key.
3//!
4//! `did:key:z6Mk…` is the multibase of a Multikey, and its one verification
5//! method is `did:key:z6Mk…#z6Mk…`. FEP-ef61 asks for base58btc only, so
6//! that one key has one DID; a DID in any other multibase is not read.
7
8use crate::Error;
9use crate::integrity::{PublicKey, decode_multikey, encode_ed25519_multikey};
10
11/// The `did:key` of an Ed25519 public key.
12#[must_use]
13pub fn did_key(public_key: &[u8; 32]) -> String {
14    format!("did:key:{}", encode_ed25519_multikey(public_key))
15}
16
17/// The verification method of a `did:key`: the DID, and its multibase as the
18/// fragment.
19#[must_use]
20pub fn did_key_method(did: &str) -> String {
21    let multibase = did.strip_prefix("did:key:").unwrap_or(did);
22    format!("{did}#{multibase}")
23}
24
25/// The key a `did:key` DID URL names: the DID's own key, when the fragment is
26/// absent or is the DID's multibase, as the method defines its one
27/// verification method.
28///
29/// # Errors
30///
31/// When `did_url` is not a `did:key` DID URL in base58btc, names another
32/// verification method, or carries a key type the integrity module cannot
33/// verify with.
34pub fn resolve_did_key(did_url: &str) -> Result<PublicKey, Error> {
35    let not = |why: &str| Error::Key(format!("{did_url} {why}"));
36    let rest = did_url
37        .strip_prefix("did:key:")
38        .ok_or_else(|| not("is not a did:key"))?;
39    let (multibase, fragment) = match rest.split_once('#') {
40        Some((multibase, fragment)) => (multibase, Some(fragment)),
41        None => (rest, None),
42    };
43    if multibase.contains(['/', '?']) {
44        return Err(not("has a path or query"));
45    }
46    if !multibase.starts_with('z') {
47        return Err(not("is not base58btc"));
48    }
49    if fragment.is_some_and(|fragment| fragment != multibase) {
50        return Err(not("names no verification method of its DID"));
51    }
52    decode_multikey(multibase)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn a_did_key_resolves_to_itself() {
61        let public = [7u8; 32];
62        let did = did_key(&public);
63        assert!(did.starts_with("did:key:z6Mk"), "{did}");
64        let method = did_key_method(&did);
65        for url in [did.as_str(), method.as_str()] {
66            let PublicKey::Ed25519(key) = resolve_did_key(url).unwrap() else {
67                panic!("an Ed25519 key");
68            };
69            assert_eq!(*key, public);
70        }
71    }
72
73    #[test]
74    fn nothing_else_resolves() {
75        let did = did_key(&[7u8; 32]);
76        for url in [
77            format!("{did}#other"),
78            format!("{did}/path"),
79            "did:web:a.example".to_owned(),
80            "did:key:u7QEHBwcH".to_owned(),
81            "did:key:z".to_owned(),
82        ] {
83            assert!(resolve_did_key(&url).is_err(), "{url}");
84        }
85    }
86}