1use std::sync::Arc;
24
25use aex_core::{AgentId, Error, IdScheme, IdentityProvider, Result, Signature, SignatureAlgorithm};
26use async_trait::async_trait;
27use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
28use tokio::sync::RwLock;
29
30const ED25519_MULTICODEC_PREFIX: [u8; 2] = [0xed, 0x01];
37
38const ED25519_PUBKEY_LEN: usize = 32;
40
41pub struct DidKeyProvider {
49 agent_id: AgentId,
50 signing_key: SigningKey,
51 peers: Arc<RwLock<std::collections::HashMap<AgentId, VerifyingKey>>>,
52}
53
54impl DidKeyProvider {
55 pub fn from_signing_key(signing_key: SigningKey) -> Result<Self> {
59 let verifying = signing_key.verifying_key();
60 let id_str = encode_did_key(&verifying);
61 let agent_id = AgentId::new(id_str)?;
62 Ok(Self {
63 agent_id,
64 signing_key,
65 peers: Arc::new(RwLock::new(Default::default())),
66 })
67 }
68
69 pub fn generate() -> Result<Self> {
71 let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
72 Self::from_signing_key(signing_key)
73 }
74
75 pub fn decode_pubkey(agent_id: &AgentId) -> Result<VerifyingKey> {
81 if agent_id.scheme() != IdScheme::DidKey {
82 return Err(Error::InvalidAgentId(format!(
83 "not a did:key agent_id: {}",
84 agent_id.as_str()
85 )));
86 }
87 let uri = agent_id.as_did_uri().ok_or_else(|| {
88 Error::InvalidAgentId(format!(
89 "did:key id is not a valid DID URI: {}",
90 agent_id.as_str()
91 ))
92 })?;
93 let msi = uri.method_specific_id;
94 let after_z = msi
97 .strip_prefix('z')
98 .ok_or_else(|| Error::InvalidAgentId("did:key must use base58btc (z prefix)".into()))?;
99
100 let bytes = bs58::decode(after_z)
101 .into_vec()
102 .map_err(|e| Error::InvalidAgentId(format!("base58 decode failed: {}", e)))?;
103
104 if bytes.len() != ED25519_MULTICODEC_PREFIX.len() + ED25519_PUBKEY_LEN {
105 return Err(Error::InvalidAgentId(format!(
106 "did:key length mismatch: got {} bytes, expected {}",
107 bytes.len(),
108 ED25519_MULTICODEC_PREFIX.len() + ED25519_PUBKEY_LEN
109 )));
110 }
111 if bytes[..2] != ED25519_MULTICODEC_PREFIX {
112 return Err(Error::InvalidAgentId(format!(
113 "did:key multicodec prefix mismatch: got {:02x?}, expected {:02x?} (Ed25519)",
114 &bytes[..2],
115 ED25519_MULTICODEC_PREFIX
116 )));
117 }
118 let pubkey_bytes: [u8; ED25519_PUBKEY_LEN] =
119 bytes[2..].try_into().expect("length checked just above");
120 VerifyingKey::from_bytes(&pubkey_bytes)
121 .map_err(|e| Error::InvalidAgentId(format!("invalid Ed25519 public key: {}", e)))
122 }
123
124 pub async fn register_peer(&self, peer_id: AgentId, pubkey: VerifyingKey) {
127 self.peers.write().await.insert(peer_id, pubkey);
128 }
129}
130
131fn encode_did_key(vk: &VerifyingKey) -> String {
133 let mut buf = Vec::with_capacity(ED25519_MULTICODEC_PREFIX.len() + ED25519_PUBKEY_LEN);
134 buf.extend_from_slice(&ED25519_MULTICODEC_PREFIX);
135 buf.extend_from_slice(vk.as_bytes());
136 format!("did:key:z{}", bs58::encode(buf).into_string())
137}
138
139#[async_trait]
140impl IdentityProvider for DidKeyProvider {
141 fn agent_id(&self) -> &AgentId {
142 &self.agent_id
143 }
144
145 async fn sign(&self, message: &[u8]) -> Result<Signature> {
146 let sig = self.signing_key.sign(message);
147 Ok(Signature {
148 algorithm: SignatureAlgorithm::Ed25519,
149 bytes: sig.to_bytes().to_vec(),
150 })
151 }
152
153 async fn verify_peer(
154 &self,
155 peer_id: &AgentId,
156 message: &[u8],
157 signature: &Signature,
158 ) -> Result<()> {
159 if signature.algorithm != SignatureAlgorithm::Ed25519 {
160 return Err(Error::SignatureFormat(format!(
161 "did:key requires Ed25519 signature, got {:?}",
162 signature.algorithm
163 )));
164 }
165
166 let cached = self.peers.read().await.get(peer_id).copied();
168 let pubkey = match cached {
169 Some(k) => k,
170 None => {
171 let pk = Self::decode_pubkey(peer_id)?;
174 self.peers.write().await.insert(peer_id.clone(), pk);
175 pk
176 }
177 };
178
179 use ed25519_dalek::Verifier;
180 let sig_bytes: [u8; 64] = signature.bytes.as_slice().try_into().map_err(|_| {
181 Error::SignatureFormat(format!(
182 "Ed25519 signature must be 64 bytes, got {}",
183 signature.bytes.len()
184 ))
185 })?;
186 let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
187 pubkey
188 .verify(message, &sig)
189 .map_err(|_| Error::SignatureInvalid)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn generate_yields_did_key_scheme() {
199 let p = DidKeyProvider::generate().unwrap();
200 assert_eq!(p.agent_id().scheme(), IdScheme::DidKey);
201 assert!(p.agent_id().as_str().starts_with("did:key:z"));
202 }
203
204 #[test]
205 fn from_signing_key_deterministic() {
206 let sk1 = SigningKey::from_bytes(&[7u8; 32]);
207 let sk2 = SigningKey::from_bytes(&[7u8; 32]);
208 let p1 = DidKeyProvider::from_signing_key(sk1).unwrap();
209 let p2 = DidKeyProvider::from_signing_key(sk2).unwrap();
210 assert_eq!(p1.agent_id(), p2.agent_id());
211 }
212
213 #[test]
214 fn roundtrip_encode_decode() {
215 let sk = SigningKey::from_bytes(&[42u8; 32]);
216 let original_vk = sk.verifying_key();
217 let p = DidKeyProvider::from_signing_key(sk).unwrap();
218 let decoded = DidKeyProvider::decode_pubkey(p.agent_id()).unwrap();
219 assert_eq!(decoded.as_bytes(), original_vk.as_bytes());
220 }
221
222 #[test]
223 fn reject_non_did_key_id() {
224 let id = AgentId::new("did:web:acme.com#agent").unwrap();
225 let err = DidKeyProvider::decode_pubkey(&id).unwrap_err();
226 assert!(matches!(err, Error::InvalidAgentId(_)));
227 }
228
229 #[test]
230 fn reject_wrong_multibase_prefix() {
231 let id = AgentId::new("did:key:fab12cd").unwrap();
233 let err = DidKeyProvider::decode_pubkey(&id).unwrap_err();
234 assert!(matches!(err, Error::InvalidAgentId(_)));
235 }
236
237 #[test]
238 fn reject_truncated_id() {
239 let id = AgentId::new("did:key:zabc").unwrap();
241 let err = DidKeyProvider::decode_pubkey(&id).unwrap_err();
242 assert!(matches!(err, Error::InvalidAgentId(_)));
243 }
244
245 #[test]
246 fn reject_wrong_multicodec_prefix() {
247 let mut buf: Vec<u8> = vec![0x12, 0x20];
249 buf.extend_from_slice(&[0u8; 32]);
250 let s = format!("did:key:z{}", bs58::encode(buf).into_string());
251 let id = AgentId::new(s).unwrap();
252 let err = DidKeyProvider::decode_pubkey(&id).unwrap_err();
253 assert!(matches!(err, Error::InvalidAgentId(_)));
254 }
255
256 #[tokio::test]
257 async fn sign_and_verify_self() {
258 let p = DidKeyProvider::generate().unwrap();
259 let msg = b"hello did:key";
260 let sig = p.sign(msg).await.unwrap();
261 p.verify_peer(p.agent_id(), msg, &sig).await.unwrap();
262 }
263
264 #[tokio::test]
265 async fn verify_peer_decodes_inline_on_cache_miss() {
266 let alice = DidKeyProvider::generate().unwrap();
267 let bob = DidKeyProvider::generate().unwrap();
268 let msg = b"from bob to alice";
269 let sig = bob.sign(msg).await.unwrap();
270 alice
272 .verify_peer(bob.agent_id(), msg, &sig)
273 .await
274 .expect("did:key peer verifies without prior registration");
275 }
276
277 #[tokio::test]
278 async fn rejects_wrong_signature_algorithm() {
279 let p = DidKeyProvider::generate().unwrap();
280 let bogus = Signature {
281 algorithm: SignatureAlgorithm::EcdsaSecp256k1,
282 bytes: vec![0u8; 64],
283 };
284 let err = p.verify_peer(p.agent_id(), b"x", &bogus).await.unwrap_err();
285 assert!(matches!(err, Error::SignatureFormat(_)));
286 }
287
288 #[tokio::test]
289 async fn rejects_tampered_signature() {
290 let p = DidKeyProvider::generate().unwrap();
291 let msg = b"x";
292 let mut sig = p.sign(msg).await.unwrap();
293 sig.bytes[0] ^= 0xff;
294 let err = p.verify_peer(p.agent_id(), msg, &sig).await.unwrap_err();
295 assert!(matches!(err, Error::SignatureInvalid));
296 }
297
298 #[tokio::test]
299 async fn rejects_wrong_signature_length() {
300 let p = DidKeyProvider::generate().unwrap();
301 let short = Signature {
302 algorithm: SignatureAlgorithm::Ed25519,
303 bytes: vec![0u8; 32], };
305 let err = p.verify_peer(p.agent_id(), b"x", &short).await.unwrap_err();
306 assert!(matches!(err, Error::SignatureFormat(_)));
307 }
308}