agentic_payments/ap2/
credentials.rs

1//! W3C Verifiable Credentials Implementation with Ed25519 Signatures
2
3use super::{Ap2Error, Result, AP2_CONTEXT, VC_CONTEXT};
4use crate::ap2::did::DidResolver;
5use chrono::{DateTime, Utc};
6use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use sha2::{Digest, Sha256};
10
11/// W3C Verifiable Credential
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct VerifiableCredential {
15    #[serde(rename = "@context")]
16    pub context: Vec<String>,
17    pub id: String,
18    #[serde(rename = "type")]
19    pub types: Vec<String>,
20    pub issuer: String,
21    pub issuance_date: DateTime<Utc>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub expiration_date: Option<DateTime<Utc>>,
24    pub credential_subject: CredentialSubject,
25    pub proof: Proof,
26}
27
28/// Credential Subject
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct CredentialSubject {
31    pub id: String,
32    #[serde(flatten)]
33    pub claims: Value,
34}
35
36/// Cryptographic Proof
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Proof {
40    #[serde(rename = "type")]
41    pub proof_type: String,
42    pub created: DateTime<Utc>,
43    pub proof_purpose: ProofPurpose,
44    pub verification_method: String,
45    pub jws: String, // JSON Web Signature
46}
47
48/// Proof Purpose
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub enum ProofPurpose {
52    AssertionMethod,
53    Authentication,
54    KeyAgreement,
55    CapabilityInvocation,
56    CapabilityDelegation,
57}
58
59/// Verification Method
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct VerificationMethod {
63    pub id: String,
64    #[serde(rename = "type")]
65    pub method_type: String,
66    pub controller: String,
67    pub public_key_multibase: String,
68}
69
70impl VerifiableCredential {
71    /// Create a new verifiable credential with Ed25519 signature
72    pub fn new(issuer: String, subject: CredentialSubject, private_key: &[u8]) -> Result<Self> {
73        let now = Utc::now();
74        let id = format!("urn:uuid:{}", uuid::Uuid::new_v4());
75
76        let mut credential = Self {
77            context: vec![VC_CONTEXT.to_string(), AP2_CONTEXT.to_string()],
78            id,
79            types: vec!["VerifiableCredential".to_string()],
80            issuer: issuer.clone(),
81            issuance_date: now,
82            expiration_date: Some(now + chrono::Duration::days(365)),
83            credential_subject: subject,
84            proof: Proof {
85                proof_type: "Ed25519Signature2020".to_string(),
86                created: now,
87                proof_purpose: ProofPurpose::AssertionMethod,
88                verification_method: format!("{}#key-1", issuer),
89                jws: String::new(), // Will be set after signing
90            },
91        };
92
93        // Sign the credential
94        credential.sign(private_key)?;
95
96        Ok(credential)
97    }
98
99    /// Sign the credential using Ed25519
100    fn sign(&mut self, private_key: &[u8]) -> Result<()> {
101        // Create signing key from bytes
102        let key_bytes: [u8; 32] = private_key
103            .try_into()
104            .map_err(|_| Ap2Error::CryptographicError("Invalid private key length".to_string()))?;
105        let signing_key = SigningKey::from_bytes(&key_bytes);
106
107        // Create canonical representation for signing (without proof)
108        let mut signing_input = self.clone();
109        signing_input.proof.jws = String::new();
110
111        let canonical = serde_json::to_string(&signing_input)
112            .map_err(|e| Ap2Error::SerializationError(e.to_string()))?;
113
114        // Hash the canonical representation
115        let mut hasher = Sha256::new();
116        hasher.update(canonical.as_bytes());
117        let hash = hasher.finalize();
118
119        // Sign the hash
120        let signature = signing_key.sign(&hash);
121
122        // Encode signature as base64url
123        self.proof.jws = base64_url::encode(&signature.to_bytes());
124
125        Ok(())
126    }
127
128    /// Verify the credential signature
129    pub fn verify(&self, did_resolver: &DidResolver) -> Result<bool> {
130        // Check expiration
131        if let Some(exp) = self.expiration_date {
132            if Utc::now() > exp {
133                return Err(Ap2Error::Expired);
134            }
135        }
136
137        // Resolve issuer DID to get public key
138        let did_doc = did_resolver.resolve(&self.issuer)?;
139        let public_key = did_doc
140            .verification_method
141            .first()
142            .ok_or_else(|| {
143                Ap2Error::DidResolutionFailed("No verification method found".to_string())
144            })?
145            .public_key_multibase
146            .clone();
147
148        // Decode public key from multibase
149        let public_key_bytes = base64_url::decode(&public_key)
150            .map_err(|e| Ap2Error::CryptographicError(format!("Invalid public key: {}", e)))?;
151
152        let verifying_key_bytes: [u8; 32] = public_key_bytes
153            .try_into()
154            .map_err(|_| Ap2Error::CryptographicError("Invalid public key length".to_string()))?;
155        let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)
156            .map_err(|e| Ap2Error::CryptographicError(e.to_string()))?;
157
158        // Reconstruct canonical form for verification
159        let mut verification_input = self.clone();
160        verification_input.proof.jws = String::new();
161
162        let canonical = serde_json::to_string(&verification_input)
163            .map_err(|e| Ap2Error::SerializationError(e.to_string()))?;
164
165        // Hash the canonical representation
166        let mut hasher = Sha256::new();
167        hasher.update(canonical.as_bytes());
168        let hash = hasher.finalize();
169
170        // Decode signature
171        let signature_bytes = base64_url::decode(&self.proof.jws)
172            .map_err(|e| Ap2Error::CryptographicError(format!("Invalid signature: {}", e)))?;
173
174        let signature = Signature::from_bytes(
175            signature_bytes
176                .as_slice()
177                .try_into()
178                .map_err(|_| Ap2Error::CryptographicError("Invalid signature length".to_string()))?,
179        );
180
181        // Verify signature
182        verifying_key
183            .verify(&hash, &signature)
184            .map_err(|e| Ap2Error::SignatureVerificationFailed(e.to_string()))?;
185
186        Ok(true)
187    }
188
189    /// Add credential type
190    pub fn add_type(&mut self, credential_type: String) {
191        if !self.types.contains(&credential_type) {
192            self.types.push(credential_type);
193        }
194    }
195
196    /// Set expiration date
197    pub fn with_expiration(mut self, expiration: DateTime<Utc>) -> Self {
198        self.expiration_date = Some(expiration);
199        self
200    }
201
202    /// Check if credential is expired
203    pub fn is_expired(&self) -> bool {
204        self.expiration_date
205            .map_or(false, |exp| Utc::now() > exp)
206    }
207
208    /// Get credential claims
209    pub fn get_claim(&self, key: &str) -> Option<&Value> {
210        self.credential_subject.claims.get(key)
211    }
212}
213
214/// Credential Builder for easier construction
215pub struct CredentialBuilder {
216    issuer: String,
217    subject_id: String,
218    claims: serde_json::Map<String, Value>,
219    types: Vec<String>,
220    expiration: Option<DateTime<Utc>>,
221}
222
223impl CredentialBuilder {
224    pub fn new(issuer: String, subject_id: String) -> Self {
225        Self {
226            issuer,
227            subject_id,
228            claims: serde_json::Map::new(),
229            types: vec!["VerifiableCredential".to_string()],
230            expiration: None,
231        }
232    }
233
234    pub fn add_claim(mut self, key: String, value: Value) -> Self {
235        self.claims.insert(key, value);
236        self
237    }
238
239    pub fn add_type(mut self, credential_type: String) -> Self {
240        if !self.types.contains(&credential_type) {
241            self.types.push(credential_type);
242        }
243        self
244    }
245
246    pub fn with_expiration(mut self, expiration: DateTime<Utc>) -> Self {
247        self.expiration = Some(expiration);
248        self
249    }
250
251    pub fn build(self, private_key: &[u8]) -> Result<VerifiableCredential> {
252        let subject = CredentialSubject {
253            id: self.subject_id,
254            claims: Value::Object(self.claims),
255        };
256
257        let mut credential = VerifiableCredential::new(self.issuer, subject, private_key)?;
258        credential.types = self.types;
259        if let Some(exp) = self.expiration {
260            credential.expiration_date = Some(exp);
261        }
262
263        // Re-sign after modifications
264        credential.sign(private_key)?;
265
266        Ok(credential)
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    fn generate_keypair() -> (SigningKey, VerifyingKey) {
275        let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
276        let verifying_key = signing_key.verifying_key();
277        (signing_key, verifying_key)
278    }
279
280    #[test]
281    fn test_credential_creation() {
282        let (signing_key, _) = generate_keypair();
283        let issuer = "did:example:issuer".to_string();
284        let subject = CredentialSubject {
285            id: "did:example:subject".to_string(),
286            claims: serde_json::json!({"name": "Test Agent"}),
287        };
288
289        let credential = VerifiableCredential::new(issuer, subject, signing_key.as_bytes());
290        assert!(credential.is_ok());
291
292        let vc = credential.unwrap();
293        assert_eq!(vc.types, vec!["VerifiableCredential"]);
294        assert!(!vc.proof.jws.is_empty());
295    }
296
297    #[test]
298    fn test_credential_builder() {
299        let (signing_key, _) = generate_keypair();
300
301        let credential = CredentialBuilder::new(
302            "did:example:issuer".to_string(),
303            "did:example:subject".to_string(),
304        )
305        .add_claim("role".to_string(), serde_json::json!("agent"))
306        .add_type("AgentCredential".to_string())
307        .build(signing_key.as_bytes());
308
309        assert!(credential.is_ok());
310        let vc = credential.unwrap();
311        assert!(vc.types.contains(&"AgentCredential".to_string()));
312    }
313
314    #[test]
315    fn test_credential_expiration() {
316        let (signing_key, _) = generate_keypair();
317        let past = Utc::now() - chrono::Duration::days(1);
318
319        let subject = CredentialSubject {
320            id: "did:example:subject".to_string(),
321            claims: serde_json::json!({}),
322        };
323
324        let mut credential =
325            VerifiableCredential::new("did:example:issuer".to_string(), subject, signing_key.as_bytes())
326                .unwrap();
327        credential.expiration_date = Some(past);
328
329        assert!(credential.is_expired());
330    }
331}