1const ED25519_MULTICODEC: [u8; 2] = [0xED, 0x01];
9
10#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
12pub enum DidKeyError {
13 #[error("DID must start with 'did:key:z', got: {0}")]
14 InvalidPrefix(String),
15
16 #[error("Base58 decoding failed: {0}")]
17 Base58DecodeFailed(String),
18
19 #[error("Unsupported or malformed multicodec: expected Ed25519 [0xED, 0x01]")]
20 UnsupportedMulticodec,
21
22 #[error("Invalid Ed25519 key length: expected 32 bytes, got {0}")]
23 InvalidKeyLength(usize),
24}
25
26pub fn did_key_to_ed25519(did: &str) -> Result<[u8; 32], DidKeyError> {
36 let encoded = strip_did_key_prefix(did)?;
37 let decoded = decode_base58(encoded)?;
38 validate_multicodec_and_extract(&decoded)
39}
40
41pub fn ed25519_pubkey_to_did_key(public_key: &[u8; 32]) -> String {
52 let mut prefixed = vec![0xED, 0x01];
53 prefixed.extend_from_slice(public_key);
54 let encoded = bs58::encode(prefixed).into_string();
55 format!("did:key:z{encoded}")
56}
57
58pub fn ed25519_pubkey_to_did_keri(pk: &[u8]) -> String {
63 format!("did:keri:{}", bs58::encode(pk).into_string())
64}
65
66fn strip_did_key_prefix(did: &str) -> Result<&str, DidKeyError> {
67 did.strip_prefix("did:key:z")
68 .ok_or_else(|| DidKeyError::InvalidPrefix(did.to_string()))
69}
70
71fn decode_base58(encoded: &str) -> Result<Vec<u8>, DidKeyError> {
72 bs58::decode(encoded)
73 .into_vec()
74 .map_err(|e| DidKeyError::Base58DecodeFailed(e.to_string()))
75}
76
77fn validate_multicodec_and_extract(decoded: &[u8]) -> Result<[u8; 32], DidKeyError> {
78 if decoded.len() != 34
79 || decoded[0] != ED25519_MULTICODEC[0]
80 || decoded[1] != ED25519_MULTICODEC[1]
81 {
82 if decoded.len() != 34 {
83 return Err(DidKeyError::InvalidKeyLength(
84 decoded.len().saturating_sub(2),
85 ));
86 }
87 return Err(DidKeyError::UnsupportedMulticodec);
88 }
89
90 let mut key = [0u8; 32];
91 key.copy_from_slice(&decoded[2..]);
92 Ok(key)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn roundtrip_encode_decode() {
101 let original = [42u8; 32];
102 let did = ed25519_pubkey_to_did_key(&original);
103 assert!(did.starts_with("did:key:z"));
104 let decoded = did_key_to_ed25519(&did).unwrap();
105 assert_eq!(decoded, original);
106 }
107
108 #[test]
109 fn rejects_invalid_prefix() {
110 let err = did_key_to_ed25519("did:web:example.com").unwrap_err();
111 assert!(matches!(err, DidKeyError::InvalidPrefix(_)));
112 }
113
114 #[test]
115 fn rejects_invalid_base58() {
116 let err = did_key_to_ed25519("did:key:z0OOO").unwrap_err();
117 assert!(matches!(err, DidKeyError::Base58DecodeFailed(_)));
118 }
119}