baseid_core/lifecycle.rs
1//! Unified credential lifecycle traits.
2//!
3//! These traits define the common operations across all credential formats:
4//! issuance, verification, and presentation. Each format crate (baseid-vc,
5//! baseid-sd-jwt, baseid-mdl, baseid-bbs) provides its own implementation.
6//!
7//! The trait design is forward-compatible with zero-knowledge proofs:
8//! `DisclosureSelection` supports predicates from day one, even though
9//! only BBS+ and AnonCreds implementations will support them. Formats
10//! that don't support predicates return `Err(UnsupportedPredicate)`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::claims::{ClaimPath, ClaimSet, DisclosureSelection, PredicateType};
15use crate::types::{CredentialFormat, DateTime, Uri};
16
17/// Options controlling credential issuance.
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct IssuanceOptions {
20 /// Credential identifier (URI).
21 pub credential_id: Option<String>,
22 /// Credential type(s) beyond the base type.
23 pub types: Vec<String>,
24 /// Validity start time (RFC 3339). Defaults to now if not set.
25 pub valid_from: Option<DateTime>,
26 /// Expiration time (RFC 3339).
27 pub valid_until: Option<DateTime>,
28 /// Credential status endpoint for revocation.
29 pub status: Option<serde_json::Value>,
30}
31
32/// An issued credential in its serialized format.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct IssuedCredential {
35 /// The serialized credential bytes (JWT string, CBOR bytes, etc.).
36 pub data: Vec<u8>,
37 /// The credential format.
38 pub format: CredentialFormat,
39 /// Credential identifier, if assigned.
40 pub id: Option<String>,
41 /// Issuer DID.
42 pub issuer: Uri,
43 /// Subject DID, if bound to a holder.
44 pub subject: Option<Uri>,
45}
46
47/// Options controlling credential presentation.
48#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49pub struct PresentationOptions {
50 /// Verifier-provided nonce for freshness.
51 pub nonce: Option<String>,
52 /// Intended audience (verifier DID or URL).
53 pub audience: Option<String>,
54 /// Holder DID (for key binding).
55 pub holder_did: Option<String>,
56}
57
58/// A credential presentation ready to be sent to a verifier.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct PresentedCredential {
61 /// The serialized presentation bytes.
62 pub data: Vec<u8>,
63 /// The credential format.
64 pub format: CredentialFormat,
65 /// Whether this presentation is unlinkable (true for BBS+ derived proofs).
66 pub unlinkable: bool,
67}
68
69/// Status of revocation checking for a credential.
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
71pub enum RevocationStatus {
72 /// Revocation was not checked (no status mechanism configured).
73 NotChecked,
74 /// Revocation was checked and the credential is valid (not revoked).
75 Valid,
76 /// The credential has been revoked.
77 Revoked,
78 /// Revocation check failed (e.g., status endpoint unreachable).
79 CheckFailed,
80}
81
82/// The outcome of credential verification.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct VerificationOutcome {
85 /// Whether the credential signature is valid.
86 pub valid: bool,
87 /// The credential format that was verified.
88 pub format: CredentialFormat,
89 /// Issuer DID extracted from the credential.
90 pub issuer: Uri,
91 /// Subject DID, if present.
92 pub subject: Option<Uri>,
93 /// The disclosed claims.
94 pub claims: ClaimSet,
95 /// Whether the presentation provides unlinkability (BBS+ derived proofs).
96 pub unlinkable: bool,
97 /// Predicates that were verified (only for ZK-capable formats).
98 pub predicates_verified: Vec<(ClaimPath, PredicateType)>,
99 /// Credential expiration, if present.
100 pub valid_until: Option<DateTime>,
101 /// Revocation status of the credential.
102 pub revocation_status: RevocationStatus,
103}
104
105/// Trait for issuing credentials in a specific format.
106///
107/// Implementors: `SdJwtLifecycle`, `VcJwtLifecycle`, `MdocLifecycle`, `BbsLifecycle`.
108pub trait CredentialIssuer: Send + Sync {
109 /// The credential format produced by this issuer.
110 fn format(&self) -> CredentialFormat;
111
112 /// Issue a credential.
113 ///
114 /// # Arguments
115 /// * `issuer_did` - The DID of the issuer
116 /// * `subject_did` - The DID of the subject (holder), if holder-bound
117 /// * `claims` - The claims to include in the credential
118 /// * `options` - Additional issuance options (types, validity, status)
119 fn issue(
120 &self,
121 issuer_did: &str,
122 subject_did: Option<&str>,
123 claims: &ClaimSet,
124 options: &IssuanceOptions,
125 ) -> crate::Result<IssuedCredential>;
126}
127
128/// Trait for verifying credentials in a specific format.
129pub trait CredentialVerifier: Send + Sync {
130 /// The credential format this verifier handles.
131 fn format(&self) -> CredentialFormat;
132
133 /// Verify a credential and return the verification outcome.
134 ///
135 /// # Arguments
136 /// * `credential_data` - The serialized credential bytes
137 fn verify(&self, credential_data: &[u8]) -> crate::Result<VerificationOutcome>;
138}
139
140/// Trait for creating credential presentations with selective disclosure.
141pub trait CredentialPresenter: Send + Sync {
142 /// The credential format this presenter handles.
143 fn format(&self) -> CredentialFormat;
144
145 /// Create a presentation from a credential with selective disclosure.
146 ///
147 /// Formats that don't support predicates (SD-JWT, JWT-VC, mdoc) must
148 /// return `Err(CredentialError::UnsupportedPredicate)` if the disclosure
149 /// selection contains any `ClaimDisclosure::Predicate` entries.
150 ///
151 /// # Arguments
152 /// * `credential_data` - The serialized credential bytes
153 /// * `disclosure` - Which claims to reveal, hide, or prove predicates about
154 /// * `options` - Presentation options (nonce, audience, holder DID)
155 fn present(
156 &self,
157 credential_data: &[u8],
158 disclosure: &DisclosureSelection,
159 options: &PresentationOptions,
160 ) -> crate::Result<PresentedCredential>;
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn issuance_options_defaults() {
169 let opts = IssuanceOptions::default();
170 assert!(opts.credential_id.is_none());
171 assert!(opts.types.is_empty());
172 assert!(opts.valid_from.is_none());
173 assert!(opts.valid_until.is_none());
174 assert!(opts.status.is_none());
175 }
176
177 #[test]
178 fn presentation_options_defaults() {
179 let opts = PresentationOptions::default();
180 assert!(opts.nonce.is_none());
181 assert!(opts.audience.is_none());
182 assert!(opts.holder_did.is_none());
183 }
184
185 #[test]
186 fn issued_credential_serializes() {
187 let cred = IssuedCredential {
188 data: b"test-jwt".to_vec(),
189 format: CredentialFormat::SdJwtVc,
190 id: Some("urn:uuid:1234".to_string()),
191 issuer: "did:key:issuer".to_string(),
192 subject: Some("did:key:holder".to_string()),
193 };
194 let json = serde_json::to_string(&cred).unwrap();
195 let decoded: IssuedCredential = serde_json::from_str(&json).unwrap();
196 assert_eq!(decoded.format, CredentialFormat::SdJwtVc);
197 assert_eq!(decoded.issuer, "did:key:issuer");
198 }
199
200 #[test]
201 fn verification_outcome_serializes() {
202 let outcome = VerificationOutcome {
203 valid: true,
204 format: CredentialFormat::W3cVc,
205 issuer: "did:key:issuer".to_string(),
206 subject: Some("did:key:holder".to_string()),
207 claims: ClaimSet::new(),
208 unlinkable: false,
209 predicates_verified: vec![],
210 valid_until: None,
211 revocation_status: RevocationStatus::NotChecked,
212 };
213 let json = serde_json::to_string(&outcome).unwrap();
214 let decoded: VerificationOutcome = serde_json::from_str(&json).unwrap();
215 assert!(decoded.valid);
216 assert!(!decoded.unlinkable);
217 }
218}