1#![allow(clippy::disallowed_methods)]
8
9const ED25519_MULTICODEC: [u8; 2] = [0xED, 0x01];
11
12const P256_MULTICODEC: [u8; 2] = [0x80, 0x24];
14
15#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum DidKeyError {
19 #[error("DID must start with 'did:key:z', got: {0}")]
20 InvalidPrefix(String),
21
22 #[error("Base58 decoding failed: {0}")]
23 Base58DecodeFailed(String),
24
25 #[error("Unsupported multicodec: expected Ed25519 [0xED, 0x01] or P-256 [0x80, 0x24]")]
26 UnsupportedMulticodec,
27
28 #[error("Invalid key length: got {0} bytes")]
29 InvalidKeyLength(usize),
30}
31
32impl crate::AuthsErrorInfo for DidKeyError {
33 fn error_code(&self) -> &'static str {
34 match self {
35 Self::InvalidPrefix(_) => "AUTHS-E1101",
36 Self::Base58DecodeFailed(_) => "AUTHS-E1102",
37 Self::UnsupportedMulticodec => "AUTHS-E1103",
38 Self::InvalidKeyLength(_) => "AUTHS-E1104",
39 }
40 }
41
42 fn suggestion(&self) -> Option<&'static str> {
43 match self {
44 Self::InvalidPrefix(_) => Some("DID must start with 'did:key:z'"),
45 Self::UnsupportedMulticodec => Some("Supported key types: Ed25519, P-256 (secp256r1)"),
46 _ => None,
47 }
48 }
49}
50
51pub fn ed25519_pubkey_to_did_keri(pk: &[u8]) -> String {
56 format!("did:keri:{}", bs58::encode(pk).into_string())
57}
58
59pub fn did_key_encode(curve: crate::CurveType, public_key: &[u8]) -> String {
72 let prefix = match curve {
73 crate::CurveType::Ed25519 => ED25519_MULTICODEC,
74 crate::CurveType::P256 => P256_MULTICODEC,
75 };
76 let mut bytes = Vec::with_capacity(2 + public_key.len());
77 bytes.extend_from_slice(&prefix);
78 bytes.extend_from_slice(public_key);
79 format!("did:key:z{}", bs58::encode(bytes).into_string())
80}
81
82pub fn did_key_to_p256(did: &str) -> Result<Vec<u8>, DidKeyError> {
93 let encoded = strip_did_key_prefix(did)?;
94 let decoded = decode_base58(encoded)?;
95 if decoded.len() < 2 {
96 return Err(DidKeyError::InvalidKeyLength(decoded.len()));
97 }
98 if decoded[0] != P256_MULTICODEC[0] || decoded[1] != P256_MULTICODEC[1] {
99 return Err(DidKeyError::UnsupportedMulticodec);
100 }
101 let key = decoded[2..].to_vec();
102 if key.len() != 33 {
103 return Err(DidKeyError::InvalidKeyLength(key.len()));
104 }
105 Ok(key)
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum DecodedDidKey {
111 Ed25519([u8; 32]),
113 P256(Vec<u8>),
115}
116
117impl DecodedDidKey {
118 pub fn curve(&self) -> crate::CurveType {
120 match self {
121 Self::Ed25519(_) => crate::CurveType::Ed25519,
122 Self::P256(_) => crate::CurveType::P256,
123 }
124 }
125
126 pub fn bytes(&self) -> &[u8] {
128 match self {
129 Self::Ed25519(b) => b,
130 Self::P256(b) => b,
131 }
132 }
133}
134
135pub fn did_key_decode(did: &str) -> Result<DecodedDidKey, DidKeyError> {
145 let encoded = strip_did_key_prefix(did)?;
146 let decoded = decode_base58(encoded)?;
147 if decoded.len() < 2 {
148 return Err(DidKeyError::InvalidKeyLength(decoded.len()));
149 }
150 if decoded[0] == ED25519_MULTICODEC[0] && decoded[1] == ED25519_MULTICODEC[1] {
151 let key = validate_multicodec_and_extract(&decoded)?;
152 Ok(DecodedDidKey::Ed25519(key))
153 } else if decoded[0] == P256_MULTICODEC[0] && decoded[1] == P256_MULTICODEC[1] {
154 let key = decoded[2..].to_vec();
155 if key.len() != 33 {
156 return Err(DidKeyError::InvalidKeyLength(key.len()));
157 }
158 Ok(DecodedDidKey::P256(key))
159 } else {
160 Err(DidKeyError::UnsupportedMulticodec)
161 }
162}
163
164fn strip_did_key_prefix(did: &str) -> Result<&str, DidKeyError> {
165 did.strip_prefix("did:key:z")
166 .ok_or_else(|| DidKeyError::InvalidPrefix(did.to_string()))
167}
168
169fn decode_base58(encoded: &str) -> Result<Vec<u8>, DidKeyError> {
170 bs58::decode(encoded)
171 .into_vec()
172 .map_err(|e| DidKeyError::Base58DecodeFailed(e.to_string()))
173}
174
175fn validate_multicodec_and_extract(decoded: &[u8]) -> Result<[u8; 32], DidKeyError> {
176 if decoded.len() != 34
177 || decoded[0] != ED25519_MULTICODEC[0]
178 || decoded[1] != ED25519_MULTICODEC[1]
179 {
180 if decoded.len() != 34 {
181 return Err(DidKeyError::InvalidKeyLength(
182 decoded.len().saturating_sub(2),
183 ));
184 }
185 return Err(DidKeyError::UnsupportedMulticodec);
186 }
187
188 let mut key = [0u8; 32];
189 key.copy_from_slice(&decoded[2..]);
190 Ok(key)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn rejects_invalid_prefix() {
199 let err = did_key_decode("did:web:example.com").unwrap_err();
200 assert!(matches!(err, DidKeyError::InvalidPrefix(_)));
201 }
202
203 #[test]
204 fn rejects_invalid_base58() {
205 let err = did_key_decode("did:key:z0OOO").unwrap_err();
206 assert!(matches!(err, DidKeyError::Base58DecodeFailed(_)));
207 }
208}