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}