1pub mod credential;
16pub mod error;
17pub mod keys;
18pub mod lifecycle;
19pub mod predicates;
20pub mod proof;
21pub mod signing;
22
23pub use credential::{BbsClaim, BbsCredential, BbsDerivedProof};
24pub use keys::BbsKeyPair;
25pub use lifecycle::{verify_derived_proof, BbsLifecycle};
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30 use baseid_core::claims::{ClaimSet, DisclosureSelection, PredicateType};
31 use baseid_core::lifecycle::*;
32 use baseid_core::types::CredentialFormat;
33 use serde_json::json;
34
35 #[test]
36 fn key_generation() {
37 let kp = BbsKeyPair::generate().unwrap();
38 assert!(!kp.public_key.is_empty());
39 assert!(!kp.secret_key.is_empty());
40 }
41
42 #[test]
43 fn key_roundtrip() {
44 let kp = BbsKeyPair::generate().unwrap();
45 let restored = BbsKeyPair::from_bytes(&kp.secret_key, &kp.public_key).unwrap();
46 assert_eq!(kp.public_key, restored.public_key);
47 }
48
49 #[test]
50 fn sign_and_verify() {
51 let kp = BbsKeyPair::generate().unwrap();
52 let messages = vec![
53 b"claim1: Alice".to_vec(),
54 b"claim2: age=30".to_vec(),
55 b"claim3: CA".to_vec(),
56 ];
57
58 let sig = signing::bbs_sign(&kp, &messages, None).unwrap();
59 assert_eq!(sig.len(), 80);
60
61 let valid = signing::bbs_verify(&kp.public_key, &sig, &messages, None).unwrap();
62 assert!(valid, "signature should be valid");
63 }
64
65 #[test]
66 fn verify_wrong_messages_fails() {
67 let kp = BbsKeyPair::generate().unwrap();
68 let messages = vec![b"claim1".to_vec(), b"claim2".to_vec()];
69 let sig = signing::bbs_sign(&kp, &messages, None).unwrap();
70
71 let wrong = vec![b"claim1".to_vec(), b"TAMPERED".to_vec()];
72 let valid = signing::bbs_verify(&kp.public_key, &sig, &wrong, None).unwrap();
73 assert!(!valid, "tampered messages should fail");
74 }
75
76 #[test]
77 fn proof_selective_disclosure() {
78 let kp = BbsKeyPair::generate().unwrap();
79 let messages = vec![
80 b"name: Alice".to_vec(),
81 b"age: 30".to_vec(),
82 b"ssn: 123-45-6789".to_vec(),
83 ];
84 let sig = signing::bbs_sign(&kp, &messages, None).unwrap();
85
86 let disclosed = [0usize];
88 let proof_bytes =
89 proof::bbs_proof_gen(&kp.public_key, &sig, &messages, &disclosed, None, None).unwrap();
90 assert!(!proof_bytes.is_empty());
91
92 let disclosed_msgs = vec![(0, b"name: Alice".to_vec())];
94 let valid =
95 proof::bbs_proof_verify(&kp.public_key, &proof_bytes, &disclosed_msgs, None, None)
96 .unwrap();
97 assert!(valid, "selective disclosure proof should verify");
98 }
99
100 #[test]
101 fn proof_unlinkability() {
102 let kp = BbsKeyPair::generate().unwrap();
103 let messages = vec![b"claim1".to_vec(), b"claim2".to_vec()];
104 let sig = signing::bbs_sign(&kp, &messages, None).unwrap();
105
106 let proof1 =
107 proof::bbs_proof_gen(&kp.public_key, &sig, &messages, &[0], None, None).unwrap();
108 let proof2 =
109 proof::bbs_proof_gen(&kp.public_key, &sig, &messages, &[0], None, None).unwrap();
110
111 assert_ne!(
113 proof1, proof2,
114 "proofs should be unlinkable (different bytes)"
115 );
116
117 let disclosed = vec![(0, b"claim1".to_vec())];
119 assert!(proof::bbs_proof_verify(&kp.public_key, &proof1, &disclosed, None, None).unwrap());
120 assert!(proof::bbs_proof_verify(&kp.public_key, &proof2, &disclosed, None, None).unwrap());
121 }
122
123 #[test]
124 fn lifecycle_issue_verify() {
125 let kp = BbsKeyPair::generate().unwrap();
126 let lifecycle = BbsLifecycle::new(kp);
127
128 let mut claims = ClaimSet::new();
129 claims.insert("", "given_name", json!("Alice"));
130 claims.insert("", "family_name", json!("Smith"));
131 claims.insert("", "age", json!(30));
132
133 let options = IssuanceOptions {
134 credential_id: Some("urn:uuid:test".to_string()),
135 types: vec![
136 "VerifiableCredential".to_string(),
137 "IdentityCredential".to_string(),
138 ],
139 valid_from: None,
140 valid_until: None,
141 status: None,
142 };
143
144 let issued = lifecycle
145 .issue("did:key:issuer", Some("did:key:holder"), &claims, &options)
146 .unwrap();
147 assert_eq!(issued.format, CredentialFormat::Bbs);
148
149 let outcome = lifecycle.verify(&issued.data).unwrap();
150 assert!(outcome.valid, "issued credential should verify");
151 assert_eq!(outcome.claims.get("", "given_name"), Some(&json!("Alice")));
152 assert!(!outcome.unlinkable, "full verification is not unlinkable");
153 }
154
155 #[test]
156 fn lifecycle_present_selective_disclosure() {
157 let kp = BbsKeyPair::generate().unwrap();
158 let lifecycle = BbsLifecycle::new(kp);
159
160 let mut claims = ClaimSet::new();
161 claims.insert("", "given_name", json!("Alice"));
162 claims.insert("", "family_name", json!("Smith"));
163 claims.insert("", "birth_date", json!("1994-01-15"));
164 claims.insert("", "ssn", json!("123-45-6789"));
165
166 let options = IssuanceOptions::default();
167
168 let issued = lifecycle
169 .issue("did:key:issuer", None, &claims, &options)
170 .unwrap();
171
172 let disclosure = DisclosureSelection::new()
174 .reveal("given_name")
175 .reveal("family_name")
176 .hide("birth_date")
177 .hide("ssn");
178
179 let pres_options = PresentationOptions {
180 nonce: Some("verifier-nonce-123".to_string()),
181 audience: None,
182 holder_did: None,
183 };
184
185 let presented = lifecycle
186 .present(&issued.data, &disclosure, &pres_options)
187 .unwrap();
188 assert_eq!(presented.format, CredentialFormat::Bbs);
189 assert!(presented.unlinkable, "BBS+ presentations are unlinkable");
190
191 let outcome = verify_derived_proof(&presented.data).unwrap();
193 assert!(outcome.valid, "derived proof should verify");
194 assert!(outcome.unlinkable, "derived proof is unlinkable");
195 assert_eq!(outcome.claims.get("", "given_name"), Some(&json!("Alice")));
197 assert_eq!(outcome.claims.get("", "family_name"), Some(&json!("Smith")));
198 assert_eq!(outcome.claims.get("", "ssn"), None, "SSN should be hidden");
199 assert_eq!(
200 outcome.claims.get("", "birth_date"),
201 None,
202 "birth_date should be hidden"
203 );
204 }
205
206 #[test]
207 fn lifecycle_predicate_evaluation() {
208 let kp = BbsKeyPair::generate().unwrap();
209 let lifecycle = BbsLifecycle::new(kp);
210
211 let mut claims = ClaimSet::new();
212 claims.insert("", "name", json!("Alice"));
213 claims.insert("", "age", json!(25));
214 claims.insert("", "country", json!("CA"));
215
216 let options = IssuanceOptions::default();
217 let issued = lifecycle
218 .issue("did:key:issuer", None, &claims, &options)
219 .unwrap();
220
221 let disclosure = DisclosureSelection::new()
223 .reveal("name")
224 .predicate("age", PredicateType::GreaterThan(json!(18)))
225 .hide("country");
226
227 let pres_options = PresentationOptions {
228 nonce: None,
229 audience: None,
230 holder_did: None,
231 };
232 let presented = lifecycle
233 .present(&issued.data, &disclosure, &pres_options)
234 .unwrap();
235
236 let derived = BbsDerivedProof::from_bytes(&presented.data).unwrap();
237 assert_eq!(derived.predicate_results.len(), 1);
239 assert_eq!(derived.predicate_results[0].0, "age");
240 assert!(
241 derived.predicate_results[0].1,
242 "age > 18 should be true for age=25"
243 );
244 }
245
246 #[test]
247 fn predicate_age_over_18() {
248 assert!(predicates::evaluate_predicate(
249 &json!(25),
250 &PredicateType::GreaterThan(json!(18))
251 ));
252 assert!(!predicates::evaluate_predicate(
253 &json!(16),
254 &PredicateType::GreaterThan(json!(18))
255 ));
256 assert!(predicates::evaluate_predicate(
257 &json!(18),
258 &PredicateType::GreaterThanOrEqual(json!(18))
259 ));
260 }
261
262 #[test]
263 fn predicate_set_membership() {
264 let set = vec![json!("CA"), json!("US"), json!("MX")];
265 assert!(predicates::evaluate_predicate(
266 &json!("CA"),
267 &PredicateType::InSet(set.clone())
268 ));
269 assert!(!predicates::evaluate_predicate(
270 &json!("UK"),
271 &PredicateType::InSet(set.clone())
272 ));
273 assert!(predicates::evaluate_predicate(
274 &json!("UK"),
275 &PredicateType::NotInSet(set)
276 ));
277 }
278}