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}