1use crate::Error;
9use crate::integrity::{PublicKey, decode_multikey, encode_ed25519_multikey};
10
11#[must_use]
13pub fn did_key(public_key: &[u8; 32]) -> String {
14 format!("did:key:{}", encode_ed25519_multikey(public_key))
15}
16
17#[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
25pub 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}