qualia_core_db/identity/credentials/
mod.rs1use std::collections::HashMap;
12
13use serde::{Deserialize, Serialize};
14
15use crate::crypto::fiduciary_crypto::{FiduciaryCrypto, MlDsaSignature};
16
17pub mod codecs;
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum VcError {
22 CryptoError(String),
23 SerializationError(String),
24 MissingProof,
25 VerificationFailed,
26 NotImplemented,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Credential {
32 #[serde(rename = "@context")]
33 pub context: Vec<String>,
34 pub id: String,
35 #[serde(rename = "type")]
36 pub types: Vec<String>,
37 pub issuer: String,
38 #[serde(rename = "issuanceDate")]
39 pub issuance_date: String,
40 #[serde(rename = "credentialSubject")]
41 pub credential_subject: HashMap<String, String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub proof: Option<Proof>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Presentation {
49 #[serde(rename = "@context")]
50 pub context: Vec<String>,
51 #[serde(rename = "type")]
52 pub types: Vec<String>,
53 #[serde(rename = "verifiableCredential")]
54 pub verifiable_credential: Vec<Credential>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub proof: Option<Proof>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Proof {
62 #[serde(rename = "type")]
63 pub proof_type: String,
64 pub created: String,
65 #[serde(rename = "verificationMethod")]
66 pub verification_method: String,
67 #[serde(rename = "proofPurpose")]
68 pub proof_purpose: String,
69 #[serde(rename = "proofValue")]
70 pub proof_value: MlDsaSignature,
71}
72
73pub trait SelectiveDisclosure {
75 fn generate_selective_presentation(
76 &self,
77 credential: &Credential,
78 disclosed_fields: &[String],
79 ) -> Result<Presentation, VcError>;
80 fn verify_selective_presentation(&self, presentation: &Presentation) -> Result<bool, VcError>;
81}
82
83pub trait ZkDisclosure {
85 fn generate_zk_proof(
86 &self,
87 credential: &Credential,
88 predicates: &[String],
89 ) -> Result<Presentation, VcError>;
90 fn verify_zk_proof(&self, presentation: &Presentation) -> Result<bool, VcError>;
91}
92
93pub trait CredentialStatus {
95 fn check_status(&self, credential_id: &str) -> Result<StatusResult, VcError>;
96}
97
98#[derive(Debug, Clone, PartialEq)]
99pub enum StatusResult {
100 Valid,
101 Revoked,
102 Suspended,
103}
104
105pub struct VcRuntime {
107 crypto: FiduciaryCrypto,
108}
109
110impl VcRuntime {
111 pub fn new(crypto: FiduciaryCrypto) -> Self {
112 Self { crypto }
113 }
114
115 pub fn issue(
117 &self,
118 mut credential: Credential,
119 key_id: Option<&str>,
120 ) -> Result<Credential, VcError> {
121 credential.proof = None;
122
123 let serialized = serde_json::to_vec(&credential)
124 .map_err(|e| VcError::SerializationError(e.to_string()))?;
125
126 let signature = self
127 .crypto
128 .sign(
129 &serialized,
130 key_id,
131 "vc-dm".to_string(),
132 "assertionMethod".to_string(),
133 )
134 .map_err(|e| VcError::CryptoError(format!("{:?}", e)))?;
135
136 credential.proof = Some(Proof {
137 proof_type: "MlDsaSignature2024".to_string(),
138 created: "2026-06-24T00:00:00Z".to_string(),
139 verification_method: key_id.unwrap_or("default").to_string(),
140 proof_purpose: "assertionMethod".to_string(),
141 proof_value: signature,
142 });
143 Ok(credential)
144 }
145
146 pub fn hold(&self, _credential: &Credential) -> Result<(), VcError> {
148 Ok(())
149 }
150
151 pub fn present(
153 &self,
154 credentials: Vec<Credential>,
155 key_id: Option<&str>,
156 ) -> Result<Presentation, VcError> {
157 let mut presentation = Presentation {
158 context: vec!["https://www.w3.org/2018/credentials/v1".to_string()],
159 types: vec!["VerifiablePresentation".to_string()],
160 verifiable_credential: credentials,
161 proof: None,
162 };
163
164 let serialized = serde_json::to_vec(&presentation)
165 .map_err(|e| VcError::SerializationError(e.to_string()))?;
166
167 let signature = self
168 .crypto
169 .sign(
170 &serialized,
171 key_id,
172 "vc-dm".to_string(),
173 "authentication".to_string(),
174 )
175 .map_err(|e| VcError::CryptoError(format!("{:?}", e)))?;
176
177 presentation.proof = Some(Proof {
178 proof_type: "MlDsaSignature2024".to_string(),
179 created: "2026-06-24T00:00:00Z".to_string(),
180 verification_method: key_id.unwrap_or("default").to_string(),
181 proof_purpose: "authentication".to_string(),
182 proof_value: signature,
183 });
184 Ok(presentation)
185 }
186
187 pub fn verify_credential(&self, credential: &Credential) -> Result<bool, VcError> {
189 let proof = credential.proof.as_ref().ok_or(VcError::MissingProof)?;
190
191 let mut bare = credential.clone();
192 bare.proof = None;
193 let serialized =
194 serde_json::to_vec(&bare).map_err(|e| VcError::SerializationError(e.to_string()))?;
195
196 let ok = self
197 .crypto
198 .verify(
199 &serialized,
200 &proof.proof_value,
201 Some(&proof.verification_method),
202 "vc-dm".to_string(),
203 proof.proof_purpose.clone(),
204 )
205 .map_err(|e| VcError::CryptoError(format!("{:?}", e)))?;
206
207 if !ok {
208 return Err(VcError::VerificationFailed);
209 }
210 Ok(true)
211 }
212
213 pub fn verify_presentation(&self, presentation: &Presentation) -> Result<bool, VcError> {
215 let proof = presentation.proof.as_ref().ok_or(VcError::MissingProof)?;
216
217 let mut bare = presentation.clone();
218 bare.proof = None;
219 let serialized =
220 serde_json::to_vec(&bare).map_err(|e| VcError::SerializationError(e.to_string()))?;
221
222 let ok = self
223 .crypto
224 .verify(
225 &serialized,
226 &proof.proof_value,
227 Some(&proof.verification_method),
228 "vc-dm".to_string(),
229 proof.proof_purpose.clone(),
230 )
231 .map_err(|e| VcError::CryptoError(format!("{:?}", e)))?;
232
233 if !ok {
234 return Err(VcError::VerificationFailed);
235 }
236
237 for cred in &presentation.verifiable_credential {
238 if !self.verify_credential(cred)? {
239 return Err(VcError::VerificationFailed);
240 }
241 }
242 Ok(true)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn test_credential() -> Credential {
251 let mut subject = HashMap::new();
252 subject.insert(
253 "id".to_string(),
254 "did:example:ebfeb1f712ebc6f1c276e12ec21".to_string(),
255 );
256 subject.insert(
257 "degree".to_string(),
258 "Bachelor of Science and Arts".to_string(),
259 );
260 Credential {
261 context: vec!["https://www.w3.org/2018/credentials/v1".to_string()],
262 id: "http://example.edu/credentials/3732".to_string(),
263 types: vec![
264 "VerifiableCredential".to_string(),
265 "UniversityDegreeCredential".to_string(),
266 ],
267 issuer: "https://example.edu/issuers/565049".to_string(),
268 issuance_date: "2010-01-01T19:23:24Z".to_string(),
269 credential_subject: subject,
270 proof: None,
271 }
272 }
273
274 #[test]
275 fn issue_verify_roundtrip() {
276 let mut crypto = FiduciaryCrypto::new();
277 crypto.generate_key("default".to_string()).unwrap();
278 let runtime = VcRuntime::new(crypto);
279
280 let issued = runtime
281 .issue(test_credential(), Some("default"))
282 .expect("issue");
283 assert!(issued.proof.is_some());
284 assert_eq!(runtime.verify_credential(&issued), Ok(true));
285 }
286
287 #[test]
288 fn tampered_credential_fails_closed() {
289 let mut crypto = FiduciaryCrypto::new();
290 crypto.generate_key("default".to_string()).unwrap();
291 let runtime = VcRuntime::new(crypto);
292
293 let mut issued = runtime.issue(test_credential(), Some("default")).unwrap();
294 issued.issuer = "https://hacker.com".to_string();
295 assert!(matches!(
296 runtime.verify_credential(&issued),
297 Err(VcError::VerificationFailed) | Err(VcError::CryptoError(_))
298 ));
299 }
300
301 #[test]
302 fn unsigned_credential_fails_closed() {
303 let runtime = VcRuntime::new(FiduciaryCrypto::new());
304 assert_eq!(
305 runtime.verify_credential(&test_credential()),
306 Err(VcError::MissingProof)
307 );
308 }
309
310 #[test]
311 fn present_verify_roundtrip() {
312 let mut crypto = FiduciaryCrypto::new();
313 crypto.generate_key("default".to_string()).unwrap();
314 let runtime = VcRuntime::new(crypto);
315
316 let issued = runtime.issue(test_credential(), Some("default")).unwrap();
317 let presentation = runtime.present(vec![issued], Some("default")).unwrap();
318 assert_eq!(runtime.verify_presentation(&presentation), Ok(true));
319 }
320}