agentic_payments/crypto/
identity.rs

1//! Agent identity and DID (Decentralized Identifier) support
2
3use crate::crypto::{generate_keypair, sign_message, verify_signature, Signature};
4use crate::error::{CryptoError, Error, Result};
5use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8use zeroize::{Zeroize, ZeroizeOnDrop};
9use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
10
11/// Agent identity with Ed25519 keypair and DID support
12#[derive(Clone, Serialize, Deserialize)]
13pub struct AgentIdentity {
14    /// Unique agent identifier
15    id: Uuid,
16    /// Ed25519 signing key (zeroized on drop)
17    #[serde(serialize_with = "serialize_signing_key", deserialize_with = "deserialize_signing_key")]
18    signing_key: SigningKey,
19    /// Ed25519 verifying (public) key
20    #[serde(serialize_with = "serialize_verifying_key", deserialize_with = "deserialize_verifying_key")]
21    verifying_key: VerifyingKey,
22    /// DID (Decentralized Identifier)
23    did: String,
24}
25
26// Custom serialization for SigningKey
27fn serialize_signing_key<S>(key: &SigningKey, serializer: S) -> std::result::Result<S::Ok, S::Error>
28where
29    S: serde::Serializer,
30{
31    let bytes = key.to_bytes();
32    serializer.serialize_bytes(&bytes)
33}
34
35fn deserialize_signing_key<'de, D>(deserializer: D) -> std::result::Result<SigningKey, D::Error>
36where
37    D: serde::Deserializer<'de>,
38{
39    let bytes: Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
40    let key_bytes: [u8; 32] = bytes.as_slice().try_into()
41        .map_err(|_| serde::de::Error::custom("Invalid signing key length"))?;
42    Ok(SigningKey::from_bytes(&key_bytes))
43}
44
45// Custom serialization for VerifyingKey
46fn serialize_verifying_key<S>(key: &VerifyingKey, serializer: S) -> std::result::Result<S::Ok, S::Error>
47where
48    S: serde::Serializer,
49{
50    let bytes = key.to_bytes();
51    serializer.serialize_bytes(&bytes)
52}
53
54fn deserialize_verifying_key<'de, D>(deserializer: D) -> std::result::Result<VerifyingKey, D::Error>
55where
56    D: serde::Deserializer<'de>,
57{
58    let bytes: Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
59    let key_bytes: [u8; 32] = bytes.as_slice().try_into()
60        .map_err(|_| serde::de::Error::custom("Invalid verifying key length"))?;
61    VerifyingKey::from_bytes(&key_bytes)
62        .map_err(|e| serde::de::Error::custom(format!("Invalid verifying key: {}", e)))
63}
64
65impl AgentIdentity {
66    /// Generate a new agent identity with a fresh keypair
67    pub fn generate() -> Result<Self> {
68        let id = Uuid::new_v4();
69        let (signing_key, verifying_key) = generate_keypair()?;
70        let did = Self::compute_did(&verifying_key);
71
72        Ok(Self {
73            id,
74            signing_key,
75            verifying_key,
76            did,
77        })
78    }
79
80    /// Create an agent identity from an existing signing key
81    pub fn from_signing_key(signing_key: SigningKey) -> Result<Self> {
82        let id = Uuid::new_v4();
83        let verifying_key = signing_key.verifying_key();
84        let did = Self::compute_did(&verifying_key);
85
86        Ok(Self {
87            id,
88            signing_key,
89            verifying_key,
90            did,
91        })
92    }
93
94    /// Create an agent identity from raw key bytes
95    pub fn from_bytes(signing_key_bytes: &[u8]) -> Result<Self> {
96        if signing_key_bytes.len() != SECRET_KEY_LENGTH {
97            return Err(Error::Crypto(CryptoError::InvalidPrivateKey {
98                details: format!("Expected {} bytes, got {}", SECRET_KEY_LENGTH, signing_key_bytes.len()),
99            }));
100        }
101
102        let signing_key = SigningKey::from_bytes(
103            signing_key_bytes.try_into().map_err(|_| {
104                Error::Crypto(CryptoError::InvalidPrivateKey {
105                    details: "Invalid key length".to_string(),
106                })
107            })?
108        );
109
110        Self::from_signing_key(signing_key)
111    }
112
113    /// Get the agent's unique identifier
114    pub fn id(&self) -> &Uuid {
115        &self.id
116    }
117
118    /// Get the DID (Decentralized Identifier)
119    pub fn did(&self) -> &str {
120        &self.did
121    }
122
123    /// Get a reference to the signing key
124    pub fn signing_key(&self) -> &SigningKey {
125        &self.signing_key
126    }
127
128    /// Get a reference to the verifying key
129    pub fn verifying_key(&self) -> &VerifyingKey {
130        &self.verifying_key
131    }
132
133    /// Get the verifying key as bytes
134    pub fn verifying_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] {
135        self.verifying_key.to_bytes()
136    }
137
138    /// Sign a message with this identity
139    pub fn sign(&self, message: &[u8]) -> Result<Signature> {
140        sign_message(&self.signing_key, message)
141    }
142
143    /// Verify a signature against this identity's public key
144    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<bool> {
145        verify_signature(&self.verifying_key, message, signature)
146    }
147
148    /// Export the signing key as bytes (use with caution!)
149    pub fn to_bytes(&self) -> [u8; SECRET_KEY_LENGTH] {
150        self.signing_key.to_bytes()
151    }
152
153    /// Create a DID document for this identity
154    pub fn to_did_document(&self) -> DidDocument {
155        DidDocument::new(self.did.clone(), self.verifying_key)
156    }
157
158    /// Compute DID from a verifying key
159    fn compute_did(verifying_key: &VerifyingKey) -> String {
160        let key_bytes = verifying_key.to_bytes();
161        let encoded = URL_SAFE_NO_PAD.encode(&key_bytes);
162        format!("did:key:z{}", encoded)
163    }
164}
165
166impl Drop for AgentIdentity {
167    fn drop(&mut self) {
168        // The SigningKey type already implements Zeroize internally
169        // This is an additional safeguard
170    }
171}
172
173impl std::fmt::Debug for AgentIdentity {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        f.debug_struct("AgentIdentity")
176            .field("id", &self.id)
177            .field("did", &self.did)
178            .field("verifying_key", &hex::encode(self.verifying_key.to_bytes()))
179            .finish_non_exhaustive()
180    }
181}
182
183/// DID Document following W3C DID specification
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct DidDocument {
186    /// The DID identifier
187    pub id: String,
188
189    /// Verification methods
190    #[serde(rename = "verificationMethod")]
191    pub verification_method: Vec<VerificationMethod>,
192
193    /// Authentication methods
194    pub authentication: Vec<String>,
195
196    /// Assertion methods (for signing)
197    #[serde(rename = "assertionMethod")]
198    pub assertion_method: Vec<String>,
199}
200
201impl DidDocument {
202    /// Create a new DID document from a DID and verifying key
203    pub fn new(did: String, verifying_key: VerifyingKey) -> Self {
204        let key_id = format!("{}#key-1", did);
205        let verification_method = vec![
206            VerificationMethod {
207                id: key_id.clone(),
208                type_: "Ed25519VerificationKey2020".to_string(),
209                controller: did.clone(),
210                public_key_multibase: format!("z{}",
211                    URL_SAFE_NO_PAD.encode(&verifying_key.to_bytes())
212                ),
213            }
214        ];
215
216        Self {
217            id: did,
218            verification_method,
219            authentication: vec![key_id.clone()],
220            assertion_method: vec![key_id],
221        }
222    }
223
224    /// Validate the DID document structure
225    pub fn validate(&self) -> Result<()> {
226        if self.id.is_empty() {
227            return Err(Error::Did("DID cannot be empty".to_string()));
228        }
229
230        if !self.id.starts_with("did:") {
231            return Err(Error::Did("DID must start with 'did:'".to_string()));
232        }
233
234        if self.verification_method.is_empty() {
235            return Err(Error::Did("DID document must have at least one verification method".to_string()));
236        }
237
238        Ok(())
239    }
240}
241
242/// Verification method in a DID document
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct VerificationMethod {
245    /// Method identifier
246    pub id: String,
247
248    /// Key type
249    #[serde(rename = "type")]
250    pub type_: String,
251
252    /// Controller DID
253    pub controller: String,
254
255    /// Public key in multibase format
256    #[serde(rename = "publicKeyMultibase")]
257    pub public_key_multibase: String,
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_generate_identity() {
266        let identity = AgentIdentity::generate().unwrap();
267        assert!(identity.did().starts_with("did:key:z"));
268        assert!(!identity.id().is_nil());
269    }
270
271    #[test]
272    fn test_sign_and_verify() {
273        let identity = AgentIdentity::generate().unwrap();
274        let message = b"test message";
275
276        let signature = identity.sign(message).unwrap();
277        let is_valid = identity.verify(message, &signature).unwrap();
278
279        assert!(is_valid);
280    }
281
282    #[test]
283    fn test_verify_wrong_message() {
284        let identity = AgentIdentity::generate().unwrap();
285        let message = b"test message";
286        let wrong_message = b"wrong message";
287
288        let signature = identity.sign(message).unwrap();
289        let is_valid = identity.verify(wrong_message, &signature).unwrap();
290
291        assert!(!is_valid);
292    }
293
294    #[test]
295    fn test_identity_from_bytes() {
296        let identity1 = AgentIdentity::generate().unwrap();
297        let key_bytes = identity1.to_bytes();
298
299        let identity2 = AgentIdentity::from_bytes(&key_bytes).unwrap();
300
301        assert_eq!(
302            identity1.verifying_key_bytes(),
303            identity2.verifying_key_bytes()
304        );
305    }
306
307    #[test]
308    fn test_invalid_key_bytes() {
309        let invalid_bytes = vec![0u8; 16];
310        let result = AgentIdentity::from_bytes(&invalid_bytes);
311        assert!(result.is_err());
312    }
313
314    #[test]
315    fn test_did_document() {
316        let identity = AgentIdentity::generate().unwrap();
317        let doc = identity.to_did_document();
318
319        assert_eq!(doc.id, identity.did());
320        assert!(!doc.verification_method.is_empty());
321        assert!(!doc.authentication.is_empty());
322        assert!(!doc.assertion_method.is_empty());
323
324        doc.validate().unwrap();
325    }
326
327    #[test]
328    fn test_did_document_validation() {
329        let identity = AgentIdentity::generate().unwrap();
330        let mut doc = identity.to_did_document();
331
332        // Valid document
333        assert!(doc.validate().is_ok());
334
335        // Invalid: empty ID
336        doc.id = String::new();
337        assert!(doc.validate().is_err());
338
339        // Invalid: wrong DID format
340        doc.id = "not-a-did".to_string();
341        assert!(doc.validate().is_err());
342    }
343
344    #[test]
345    fn test_multiple_identities() {
346        let identity1 = AgentIdentity::generate().unwrap();
347        let identity2 = AgentIdentity::generate().unwrap();
348
349        assert_ne!(identity1.id(), identity2.id());
350        assert_ne!(identity1.did(), identity2.did());
351        assert_ne!(
352            identity1.verifying_key_bytes(),
353            identity2.verifying_key_bytes()
354        );
355    }
356
357    #[test]
358    fn test_cross_identity_verification() {
359        let identity1 = AgentIdentity::generate().unwrap();
360        let identity2 = AgentIdentity::generate().unwrap();
361
362        let message = b"test message";
363        let signature = identity1.sign(message).unwrap();
364
365        // Should fail to verify with different identity
366        let is_valid = identity2.verify(message, &signature).unwrap();
367        assert!(!is_valid);
368    }
369}