Skip to main content

dpp_domain/credential/
passport.rs

1//! [`PassportCredential`] — the W3C VC 2.0 envelope around a signed passport.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::{Value, json};
6
7use super::subject::PassportCredentialSubject;
8
9/// A W3C Verifiable Credential 2.0 envelope binding a DPP passport to its signed payload.
10///
11/// The cryptographic proof is in [`SignedCredential::jws`](crate::credential::SignedCredential::jws); this struct provides
12/// the structured VC context required for EUDI/EBSI interoperability.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct PassportCredential {
16    #[serde(rename = "@context")]
17    pub context: Vec<Value>,
18    #[serde(rename = "type")]
19    pub credential_type: Vec<String>,
20    /// Unique credential ID (`urn:uuid:…`) — generated fresh per signing call.
21    pub id: String,
22    /// DID of the signing issuer (`did:web:…`).
23    pub issuer: String,
24    /// Credential issuance timestamp (W3C VC 2.0 `validFrom`).
25    pub valid_from: DateTime<Utc>,
26    pub credential_subject: PassportCredentialSubject,
27}
28
29impl PassportCredential {
30    /// W3C VCDM v2 base context — MUST be the first `@context` entry.
31    pub const VC_BASE_CONTEXT: &'static str = "https://www.w3.org/ns/credentials/v2";
32
33    /// Inline JSON-LD term map for the DPP-specific terms this credential adds
34    /// on top of the VCDM v2 base context: the credential type value and the
35    /// one custom subject property (`payloadHash`).
36    ///
37    /// Inlined rather than hosted at a URL — a string entry in `@context` is
38    /// fetched by the consumer at expansion time, and this crate does not host
39    /// a context document. Same reasoning and `dpp:` prefix as
40    /// `dpp_vc::jsonld::context::passport_context`; a prefix IRI names a
41    /// vocabulary and is never dereferenced during expansion, so it carries no
42    /// such obligation.
43    fn dpp_terms() -> Value {
44        json!({
45            "dpp": "https://schema.odal-node.io/dpp#",
46            "DppPassportCredential": "dpp:DppPassportCredential",
47            "payloadHash": "dpp:payloadHash",
48        })
49    }
50
51    /// Construct a passport credential with the VCDM v2 base context and the
52    /// `VerifiableCredential` base type guaranteed present, so a caller cannot
53    /// emit a VC missing `https://www.w3.org/ns/credentials/v2`. `id`
54    /// (`urn:uuid:` v7) and `valid_from` are generated fresh.
55    #[must_use]
56    pub fn new(issuer: String, credential_subject: PassportCredentialSubject) -> Self {
57        Self {
58            context: vec![json!(Self::VC_BASE_CONTEXT), Self::dpp_terms()],
59            credential_type: vec![
60                "VerifiableCredential".into(),
61                "DppPassportCredential".into(),
62            ],
63            id: format!("urn:uuid:{}", uuid::Uuid::now_v7()),
64            issuer,
65            valid_from: Utc::now(),
66            credential_subject,
67        }
68    }
69}