From 2446a459876ed673db9f2a0239d807f2e757b60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 27 Jun 2024 06:09:26 -0300 Subject: [PATCH] VCDM v2.0 (#549) * Data Model v2.0 traits. * VCDM v2.0 (JSON) syntax types. * Add `DataIntegrityDocument` type. * Add documentation, defaults & re-exports. --- README.md | 4 +- .../data-integrity/core/src/document.rs | 79 ++++++ .../crates/data-integrity/core/src/lib.rs | 2 + .../data-integrity/core/src/signing/jws.rs | 2 - .../claims/crates/data-integrity/src/lib.rs | 2 +- crates/claims/crates/jws/src/lib.rs | 11 +- .../syntax/entry_set/credential.rs | 4 +- .../syntax/status_list/credential.rs | 10 +- crates/claims/crates/vc/examples/sign.rs | 8 +- .../crates/vc/src/data_model/evidence.rs | 21 -- .../claims/crates/vc/src/data_model/issuer.rs | 11 - crates/claims/crates/vc/src/data_model/mod.rs | 15 -- .../vc/src/data_model/refresh_service.rs | 20 -- .../claims/crates/vc/src/data_model/status.rs | 17 -- .../crates/vc/src/data_model/terms_of_use.rs | 22 -- crates/claims/crates/vc/src/id.rs | 82 ++++++ crates/claims/crates/vc/src/lib.rs | 42 +-- crates/claims/crates/vc/src/schema/cred.ttl | 106 -------- crates/claims/crates/vc/src/schema/sec.ttl | 13 - crates/claims/crates/vc/src/syntax/context.rs | 108 +++++--- .../claims/crates/vc/src/syntax/credential.rs | 104 ++++++++ .../vc/src/syntax/json/credential/evidence.rs | 27 -- .../vc/src/syntax/json/credential/issuer.rs | 32 --- .../syntax/json/credential/refresh_service.rs | 26 -- .../vc/src/syntax/json/credential/schema.rs | 16 -- .../vc/src/syntax/json/credential/status.rs | 23 -- .../syntax/json/credential/terms_of_use.rs | 27 -- .../vc/src/syntax/json/credential/type.rs | 141 ---------- crates/claims/crates/vc/src/syntax/mod.rs | 91 ++++++- .../crates/vc/src/syntax/presentation.rs | 75 ++++++ .../{json/presentation/type.rs => types.rs} | 71 +++-- crates/claims/crates/vc/src/typed.rs | 29 +++ .../crates/vc/src/{ => v1}/data_integrity.rs | 5 +- .../vc/src/{ => v1}/data_model/credential.rs | 34 ++- .../src/{syntax/json => v1/data_model}/mod.rs | 1 - .../src/{ => v1}/data_model/presentation.rs | 13 +- .../vc/src/{syntax => v1}/jwt/decode.rs | 0 .../vc/src/{syntax => v1}/jwt/encode.rs | 0 .../crates/vc/src/{syntax => v1}/jwt/mod.rs | 4 +- crates/claims/crates/vc/src/v1/mod.rs | 31 +++ .../crates/vc/src/{ => v1}/revocation/mod.rs | 8 +- .../vc/src/{ => v1}/revocation/v2020.rs | 14 +- .../vc/src/{ => v1}/revocation/v2021.rs | 14 +- .../mod.rs => v1/syntax/credential.rs} | 107 ++++---- crates/claims/crates/vc/src/v1/syntax/mod.rs | 11 + .../mod.rs => v1/syntax/presentation.rs} | 22 +- .../crates/vc/src/v2/data_model/credential.rs | 216 +++++++++++++++ .../crates/vc/src/v2/data_model/language.rs | 27 ++ .../claims/crates/vc/src/v2/data_model/mod.rs | 11 + .../vc/src/v2/data_model/presentation.rs | 37 +++ .../vc/src/v2/data_model/related_resource.rs | 13 + crates/claims/crates/vc/src/v2/mod.rs | 24 ++ .../crates/vc/src/v2/syntax/credential.rs | 246 ++++++++++++++++++ .../crates/vc/src/v2/syntax/language.rs | 23 ++ crates/claims/crates/vc/src/v2/syntax/mod.rs | 15 ++ .../crates/vc/src/v2/syntax/presentation.rs | 160 ++++++++++++ .../vc/src/v2/syntax/related_resource.rs | 21 ++ crates/claims/crates/vc/src/verification.rs | 100 ------- crates/claims/crates/vc/src/vocab.rs | 7 - crates/claims/src/lib.rs | 9 +- crates/core/src/one_or_many.rs | 7 + crates/dids/methods/ethr/src/lib.rs | 8 +- crates/dids/methods/key/src/lib.rs | 2 +- crates/dids/methods/pkh/src/lib.rs | 8 +- crates/dids/methods/tz/tests/did.rs | 2 +- crates/dids/methods/web/src/lib.rs | 2 +- crates/json-ld/src/lib.rs | 12 +- crates/rdf/src/lib.rs | 4 +- .../src/methods/w3c/multikey.rs | 6 +- crates/zcap-ld/src/lib.rs | 10 +- examples/issue-revocation-list.rs | 2 +- examples/issue-status-list.rs | 2 +- examples/issue.rs | 4 +- examples/present.rs | 10 +- examples/vc_parse.rs | 10 +- examples/vc_verify.rs | 5 +- src/lib.rs | 4 +- src/prelude.rs | 9 +- tests/send.rs | 4 +- 79 files changed, 1635 insertions(+), 890 deletions(-) create mode 100644 crates/claims/crates/data-integrity/core/src/document.rs delete mode 100644 crates/claims/crates/vc/src/data_model/evidence.rs delete mode 100644 crates/claims/crates/vc/src/data_model/issuer.rs delete mode 100644 crates/claims/crates/vc/src/data_model/mod.rs delete mode 100644 crates/claims/crates/vc/src/data_model/refresh_service.rs delete mode 100644 crates/claims/crates/vc/src/data_model/status.rs delete mode 100644 crates/claims/crates/vc/src/data_model/terms_of_use.rs create mode 100644 crates/claims/crates/vc/src/id.rs delete mode 100644 crates/claims/crates/vc/src/schema/cred.ttl delete mode 100644 crates/claims/crates/vc/src/schema/sec.ttl create mode 100644 crates/claims/crates/vc/src/syntax/credential.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/evidence.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/issuer.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/refresh_service.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/schema.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/status.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/terms_of_use.rs delete mode 100644 crates/claims/crates/vc/src/syntax/json/credential/type.rs create mode 100644 crates/claims/crates/vc/src/syntax/presentation.rs rename crates/claims/crates/vc/src/syntax/{json/presentation/type.rs => types.rs} (57%) create mode 100644 crates/claims/crates/vc/src/typed.rs rename crates/claims/crates/vc/src/{ => v1}/data_integrity.rs (79%) rename crates/claims/crates/vc/src/{ => v1}/data_model/credential.rs (89%) rename crates/claims/crates/vc/src/{syntax/json => v1/data_model}/mod.rs (61%) rename crates/claims/crates/vc/src/{ => v1}/data_model/presentation.rs (82%) rename crates/claims/crates/vc/src/{syntax => v1}/jwt/decode.rs (100%) rename crates/claims/crates/vc/src/{syntax => v1}/jwt/encode.rs (100%) rename crates/claims/crates/vc/src/{syntax => v1}/jwt/mod.rs (97%) create mode 100644 crates/claims/crates/vc/src/v1/mod.rs rename crates/claims/crates/vc/src/{ => v1}/revocation/mod.rs (98%) rename crates/claims/crates/vc/src/{ => v1}/revocation/v2020.rs (95%) rename crates/claims/crates/vc/src/{ => v1}/revocation/v2021.rs (95%) rename crates/claims/crates/vc/src/{syntax/json/credential/mod.rs => v1/syntax/credential.rs} (72%) create mode 100644 crates/claims/crates/vc/src/v1/syntax/mod.rs rename crates/claims/crates/vc/src/{syntax/json/presentation/mod.rs => v1/syntax/presentation.rs} (86%) create mode 100644 crates/claims/crates/vc/src/v2/data_model/credential.rs create mode 100644 crates/claims/crates/vc/src/v2/data_model/language.rs create mode 100644 crates/claims/crates/vc/src/v2/data_model/mod.rs create mode 100644 crates/claims/crates/vc/src/v2/data_model/presentation.rs create mode 100644 crates/claims/crates/vc/src/v2/data_model/related_resource.rs create mode 100644 crates/claims/crates/vc/src/v2/mod.rs create mode 100644 crates/claims/crates/vc/src/v2/syntax/credential.rs create mode 100644 crates/claims/crates/vc/src/v2/syntax/language.rs create mode 100644 crates/claims/crates/vc/src/v2/syntax/mod.rs create mode 100644 crates/claims/crates/vc/src/v2/syntax/presentation.rs create mode 100644 crates/claims/crates/vc/src/v2/syntax/related_resource.rs delete mode 100644 crates/claims/crates/vc/src/verification.rs delete mode 100644 crates/claims/crates/vc/src/vocab.rs diff --git a/README.md b/README.md index f8dda945a..3ce30e862 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ any Data-Integrity proof supported by SSI. ```rust use ssi::prelude::*; -let vc = any_credential_from_json_str( +let vc = ssi::claims::vc::v1::data_integrity::any_credential_from_json_str( &std::fs::read_to_string("examples/files/vc.jsonld") .expect("unable to load VC") ).await.expect("invalid VC"); @@ -165,7 +165,7 @@ pub struct MyCredentialSubject { email: String } -let credential = SpecializedJsonCredential::::new( +let credential = ssi::claims::vc::v1::JsonCredential::::new( Some(uri!("https://example.org/#CredentialId").to_owned()), // id uri!("https://example.org/#Issuer").to_owned().into(), // issuer DateTime::now(), // issuance date diff --git a/crates/claims/crates/data-integrity/core/src/document.rs b/crates/claims/crates/data-integrity/core/src/document.rs new file mode 100644 index 000000000..46c21974c --- /dev/null +++ b/crates/claims/crates/data-integrity/core/src/document.rs @@ -0,0 +1,79 @@ +use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; + +use rdf_types::VocabularyMut; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::Validate; +use ssi_core::OneOrMany; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, Loader}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject, Vocabulary}; + +/// Any Data-Integrity-compatible document. +/// +/// The only assumption made by this type is that the JSON-LD `@type` attribute +/// is aliased to `type`, which is common practice (for instance with +/// Verifiable Credentials). +/// +/// Note that this type represents an *unsecured* document. +/// The type for any Data-Integrity-secured document (with the cryptosuite `S`) +/// is [`DataIntegrity`](crate::DataIntegrity). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DataIntegrityDocument { + #[serde(rename = "@context", skip_serializing_if = "Option::is_none")] + pub context: Option, + + #[serde( + rename = "type", + alias = "@type", + default, + skip_serializing_if = "OneOrMany::is_empty" + )] + pub types: OneOrMany, + + #[serde(flatten)] + pub properties: BTreeMap, +} + +impl ssi_json_ld::Expandable for DataIntegrityDocument { + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + #[allow(async_fn_in_trait)] + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand_with(ld, loader).await + } +} + +impl JsonLdObject for DataIntegrityDocument { + fn json_ld_context(&self) -> Option> { + self.context.as_ref().map(Cow::Borrowed) + } +} + +impl JsonLdNodeObject for DataIntegrityDocument { + fn json_ld_type(&self) -> ssi_json_ld::JsonLdTypes { + ssi_json_ld::JsonLdTypes::new(&[], Cow::Borrowed(self.types.as_slice())) + } +} + +impl Validate for DataIntegrityDocument { + fn validate(&self, _env: &E, _proof: &P) -> ssi_claims_core::ClaimsValidity { + Ok(()) + } +} diff --git a/crates/claims/crates/data-integrity/core/src/lib.rs b/crates/claims/crates/data-integrity/core/src/lib.rs index 9b8875eca..73c952a6f 100644 --- a/crates/claims/crates/data-integrity/core/src/lib.rs +++ b/crates/claims/crates/data-integrity/core/src/lib.rs @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut}; pub mod canonicalization; mod de; mod decode; +mod document; pub mod hashing; mod options; mod proof; @@ -23,6 +24,7 @@ pub use suite::{ DeserializeCryptographicSuite, SerializeCryptographicSuite, StandardCryptographicSuite, }; +pub use document::*; #[doc(hidden)] pub use ssi_rdf; diff --git a/crates/claims/crates/data-integrity/core/src/signing/jws.rs b/crates/claims/crates/data-integrity/core/src/signing/jws.rs index 04a59c5d6..7af19c855 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/jws.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/jws.rs @@ -181,8 +181,6 @@ where prepared_claims: S::PreparedClaims, proof: ProofRef, ) -> Result { - eprintln!("payload: {:?}", prepared_claims.as_ref()); - let JWS { header, signature, .. } = proof diff --git a/crates/claims/crates/data-integrity/src/lib.rs b/crates/claims/crates/data-integrity/src/lib.rs index da7be5977..ee705b957 100644 --- a/crates/claims/crates/data-integrity/src/lib.rs +++ b/crates/claims/crates/data-integrity/src/lib.rs @@ -11,4 +11,4 @@ pub type AnyProof = Proof; pub type AnyProofs = Proofs; /// Data-Integrity-secured claims with any cryptographic suite. -pub type AnyDataIntegrity = DataIntegrity; +pub type AnyDataIntegrity = DataIntegrity; diff --git a/crates/claims/crates/jws/src/lib.rs b/crates/claims/crates/jws/src/lib.rs index 0d248266f..825d24117 100644 --- a/crates/claims/crates/jws/src/lib.rs +++ b/crates/claims/crates/jws/src/lib.rs @@ -580,9 +580,7 @@ pub fn verify_bytes_warnable( signature: &[u8], ) -> Result { let mut warnings = VerificationWarnings::default(); - eprintln!("verify with algorithm: {algorithm}"); if let Some(key_algorithm) = key.algorithm { - eprintln!("key algorithm: {key_algorithm}"); if key_algorithm != algorithm && !(key_algorithm == Algorithm::EdDSA && algorithm == Algorithm::EdBlake2b) && !(key_algorithm == Algorithm::ES256 && algorithm == Algorithm::ESBlake2b) @@ -638,12 +636,9 @@ pub fn verify_bytes_warnable( return Err(ssi_jwk::Error::CurveNotImplemented(okp.curve.to_string()).into()); } let hash = match algorithm { - Algorithm::EdBlake2b => { - eprintln!("verifying EdBlake2b"); - as Digest>::new_with_prefix(data) - .finalize() - .to_vec() - } + Algorithm::EdBlake2b => as Digest>::new_with_prefix(data) + .finalize() + .to_vec(), _ => data.to_vec(), }; #[cfg(feature = "ring")] diff --git a/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/entry_set/credential.rs b/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/entry_set/credential.rs index 9b4b68282..df6359f5a 100644 --- a/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/entry_set/credential.rs +++ b/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/entry_set/credential.rs @@ -13,7 +13,7 @@ use ssi_data_integrity::{ }; use ssi_json_ld::{CompactJsonLd, Expandable, JsonLdError, JsonLdNodeObject, JsonLdObject, Loader}; use ssi_jws::{CompactJWS, InvalidCompactJWS, JWSVerifier, ValidateJWSHeader}; -use ssi_vc::{json::JsonCredentialTypes, Context, V2}; +use ssi_vc::v2::{syntax::JsonCredentialTypes, Context}; use ssi_verification_methods::ssi_core::OneOrMany; use crate::{ @@ -27,7 +27,7 @@ use super::BitstringStatusListEntry; pub struct BitstringStatusListEntrySetCredential { /// JSON-LD context. #[serde(rename = "@context")] - pub context: Context, + pub context: Context, /// Credential identifier. #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/status_list/credential.rs b/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/status_list/credential.rs index 6896d883e..1d07d52cc 100644 --- a/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/status_list/credential.rs +++ b/crates/claims/crates/status/src/impl/bitstream_status_list/syntax/status_list/credential.rs @@ -14,8 +14,8 @@ use ssi_data_integrity::{ use ssi_json_ld::{CompactJsonLd, Expandable, JsonLdError, JsonLdNodeObject, JsonLdObject, Loader}; use ssi_jws::{CompactJWS, InvalidCompactJWS, JWSVerifier, ValidateJWSHeader}; use ssi_vc::{ - json::{JsonCredentialTypes, RequiredCredentialType}, - Context, V2, + syntax::RequiredType, + v2::syntax::{Context, JsonCredentialTypes}, }; use crate::{EncodedStatusMap, FromBytes, FromBytesOptions}; @@ -27,8 +27,8 @@ pub const BITSTRING_STATUS_LIST_CREDENTIAL_TYPE: &str = "BitstringStatusListCred #[derive(Debug, Clone, Copy)] pub struct BitstringStatusListCredentialType; -impl RequiredCredentialType for BitstringStatusListCredentialType { - const REQUIRED_CREDENTIAL_TYPE: &'static str = BITSTRING_STATUS_LIST_CREDENTIAL_TYPE; +impl RequiredType for BitstringStatusListCredentialType { + const REQUIRED_TYPE: &'static str = BITSTRING_STATUS_LIST_CREDENTIAL_TYPE; } #[derive(Debug, Serialize, Deserialize)] @@ -36,7 +36,7 @@ impl RequiredCredentialType for BitstringStatusListCredentialType { pub struct BitstringStatusListCredential { /// JSON-LD context. #[serde(rename = "@context")] - pub context: Context, + pub context: Context, /// Credential identifier. #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/claims/crates/vc/examples/sign.rs b/crates/claims/crates/vc/examples/sign.rs index 9f6627c58..9469bb3b7 100644 --- a/crates/claims/crates/vc/examples/sign.rs +++ b/crates/claims/crates/vc/examples/sign.rs @@ -22,11 +22,11 @@ use xsd_types::DateTime; pub struct Credential { #[ld(ignore)] #[serde(rename = "@context")] - context: ssi_vc::Context, + context: ssi_vc::v1::Context, #[ld(ignore)] #[serde(rename = "type")] - type_: ssi_vc::json::JsonCredentialTypes, + type_: ssi_vc::v1::JsonCredentialTypes, #[ld("cred:credentialSubject")] credential_subject: CredentialSubject, @@ -55,11 +55,11 @@ where E: ssi_claims_core::DateTimeEnvironment, { fn validate(&self, env: &E, _proof: &P) -> ssi_claims_core::ClaimsValidity { - ssi_vc::Credential::validate_credential(self, env) + ssi_vc::v1::Credential::validate_credential(self, env) } } -impl ssi_vc::Credential for Credential { +impl ssi_vc::v1::Credential for Credential { type Subject = CredentialSubject; type Issuer = Uri; type Status = std::convert::Infallible; diff --git a/crates/claims/crates/vc/src/data_model/evidence.rs b/crates/claims/crates/vc/src/data_model/evidence.rs deleted file mode 100644 index b55503a63..000000000 --- a/crates/claims/crates/vc/src/data_model/evidence.rs +++ /dev/null @@ -1,21 +0,0 @@ -use iref::Uri; - -/// Evidence. -/// -/// Can be included by an issuer to provide the verifier with additional -/// supporting information in a verifiable credential. -pub trait Evidence { - fn id(&self) -> Option<&Uri>; - - fn type_(&self) -> &[String]; -} - -impl Evidence for std::convert::Infallible { - fn id(&self) -> Option<&Uri> { - unreachable!() - } - - fn type_(&self) -> &[String] { - unreachable!() - } -} diff --git a/crates/claims/crates/vc/src/data_model/issuer.rs b/crates/claims/crates/vc/src/data_model/issuer.rs deleted file mode 100644 index 0d6204e63..000000000 --- a/crates/claims/crates/vc/src/data_model/issuer.rs +++ /dev/null @@ -1,11 +0,0 @@ -use iref::Uri; - -pub trait Issuer { - fn id(&self) -> &Uri; -} - -impl Issuer for Uri { - fn id(&self) -> &Uri { - self - } -} diff --git a/crates/claims/crates/vc/src/data_model/mod.rs b/crates/claims/crates/vc/src/data_model/mod.rs deleted file mode 100644 index f6e3a7d72..000000000 --- a/crates/claims/crates/vc/src/data_model/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod credential; -mod evidence; -mod issuer; -mod presentation; -mod refresh_service; -mod status; -mod terms_of_use; - -pub use credential::*; -pub use evidence::*; -pub use issuer::*; -pub use presentation::*; -pub use refresh_service::*; -pub use status::*; -pub use terms_of_use::*; diff --git a/crates/claims/crates/vc/src/data_model/refresh_service.rs b/crates/claims/crates/vc/src/data_model/refresh_service.rs deleted file mode 100644 index 7e896e25f..000000000 --- a/crates/claims/crates/vc/src/data_model/refresh_service.rs +++ /dev/null @@ -1,20 +0,0 @@ -use iref::Uri; - -/// Refresh Service. -/// -/// See: -pub trait RefreshService { - fn id(&self) -> &Uri; - - fn type_(&self) -> &str; -} - -impl RefreshService for std::convert::Infallible { - fn id(&self) -> &Uri { - unreachable!() - } - - fn type_(&self) -> &str { - unreachable!() - } -} diff --git a/crates/claims/crates/vc/src/data_model/status.rs b/crates/claims/crates/vc/src/data_model/status.rs deleted file mode 100644 index 2a5cd5a9a..000000000 --- a/crates/claims/crates/vc/src/data_model/status.rs +++ /dev/null @@ -1,17 +0,0 @@ -use iref::Uri; - -pub trait CredentialStatus { - fn id(&self) -> &Uri; -} - -impl CredentialStatus for Uri { - fn id(&self) -> &Uri { - self - } -} - -impl CredentialStatus for std::convert::Infallible { - fn id(&self) -> &Uri { - unreachable!() - } -} diff --git a/crates/claims/crates/vc/src/data_model/terms_of_use.rs b/crates/claims/crates/vc/src/data_model/terms_of_use.rs deleted file mode 100644 index 3a9980c99..000000000 --- a/crates/claims/crates/vc/src/data_model/terms_of_use.rs +++ /dev/null @@ -1,22 +0,0 @@ -use iref::Uri; - -/// Terms of Use. -/// -/// Terms of use can be utilized by an issuer or a holder to communicate the -/// terms under which a verifiable credential or verifiable presentation was -/// issued. -pub trait TermsOfUse { - fn id(&self) -> Option<&Uri>; - - fn type_(&self) -> &str; -} - -impl TermsOfUse for std::convert::Infallible { - fn id(&self) -> Option<&Uri> { - unreachable!() - } - - fn type_(&self) -> &str { - unreachable!() - } -} diff --git a/crates/claims/crates/vc/src/id.rs b/crates/claims/crates/vc/src/id.rs new file mode 100644 index 000000000..4497f80ce --- /dev/null +++ b/crates/claims/crates/vc/src/id.rs @@ -0,0 +1,82 @@ +use iref::Uri; + +use crate::syntax::{ + IdOr, IdentifiedObject, IdentifiedTypedObject, MaybeIdentifiedObject, + MaybeIdentifiedTypedObject, +}; + +/// Object that *may* contain an `id` property. +/// +/// See: +pub trait MaybeIdentified { + fn id(&self) -> Option<&Uri> { + None + } +} + +impl MaybeIdentified for MaybeIdentifiedObject { + fn id(&self) -> Option<&Uri> { + self.id.as_deref() + } +} + +impl MaybeIdentified for IdOr { + fn id(&self) -> Option<&Uri> { + match self { + Self::Id(id) => Some(id), + Self::NotId(o) => o.id(), + } + } +} + +impl MaybeIdentified for MaybeIdentifiedTypedObject { + fn id(&self) -> Option<&Uri> { + self.id.as_deref() + } +} + +/// Object that contain an `id` property. +/// +/// See: +pub trait Identified { + fn id(&self) -> &Uri; +} + +impl MaybeIdentified for T { + fn id(&self) -> Option<&Uri> { + Some(Identified::id(self)) + } +} + +impl Identified for std::convert::Infallible { + fn id(&self) -> &Uri { + unreachable!() + } +} + +impl Identified for Uri { + fn id(&self) -> &Uri { + self + } +} + +impl Identified for IdentifiedObject { + fn id(&self) -> &Uri { + &self.id + } +} + +impl Identified for IdentifiedTypedObject { + fn id(&self) -> &Uri { + &self.id + } +} + +impl Identified for IdOr { + fn id(&self) -> &Uri { + match self { + Self::Id(id) => id, + Self::NotId(o) => &o.id, + } + } +} diff --git a/crates/claims/crates/vc/src/lib.rs b/crates/claims/crates/vc/src/lib.rs index cc4c26b66..4ac32f19f 100644 --- a/crates/claims/crates/vc/src/lib.rs +++ b/crates/claims/crates/vc/src/lib.rs @@ -1,35 +1,15 @@ -//! Verifiable Credential Data Model implementation. +//! Verifiable Credentials Data Model v1.1 and v2.0 implementation. //! //! See: -use iref::Iri; -mod data_integrity; -mod data_model; -pub mod datatype; -pub mod revocation; -mod syntax; -pub mod verification; -pub mod vocab; - -pub use data_integrity::*; -pub use data_model::*; -pub use syntax::*; - -pub const CREDENTIALS_V1_CONTEXT_IRI: &Iri = - static_iref::iri!("https://www.w3.org/2018/credentials/v1"); - -pub const CREDENTIALS_V2_CONTEXT_IRI: &Iri = - static_iref::iri!("https://www.w3.org/ns/credentials/v2"); +//! See: +mod id; +pub use id::*; +mod typed; +pub use typed::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct V1; - -impl RequiredContext for V1 { - const CONTEXT_IRI: &'static Iri = CREDENTIALS_V1_CONTEXT_IRI; -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct V2; +pub mod datatype; +pub mod syntax; +pub mod v1; +pub mod v2; -impl RequiredContext for V2 { - const CONTEXT_IRI: &'static Iri = CREDENTIALS_V2_CONTEXT_IRI; -} +pub use syntax::{AnyJsonCredential, AnyJsonPresentation, AnySpecializedJsonCredential}; diff --git a/crates/claims/crates/vc/src/schema/cred.ttl b/crates/claims/crates/vc/src/schema/cred.ttl deleted file mode 100644 index 45aaaea69..000000000 --- a/crates/claims/crates/vc/src/schema/cred.ttl +++ /dev/null @@ -1,106 +0,0 @@ -# @prefix dc: . -@prefix owl: . -@prefix rdf: . -@prefix rdfs: . -@prefix cred: . -@prefix odrl: . -@prefix xsd: . - -# # CSVM Ontology definition -# cred: a owl:Ontology; -# dc:title "Verifiable Credentials Vocabulary"@en; -# dc:description """This document describes the RDFS vocabulary description used for Verifiable Credentials [[VERIFIABLE-CREDENTIALS]] along with the default JSON-LD Context."""@en; -# dc:date "2019-05-26"^^xsd:date; -# dc:imports ; -# rdfs:seeAlso ; - -# Class definitions -cred:JsonSchemaValidator2018 a rdfs:Class; - rdfs:label ""@en; - rdfs:comment """A type of validator that can be used to syntactically validate JSON documents using the JSON Schema language."""@en; - rdfs:isDefinedBy cred: . -cred:ManualRefreshService2018 a rdfs:Class; - rdfs:label ""@en; - rdfs:comment """A type of refresh service that must be interacted with in a manual fashion."""@en; - rdfs:subClassOf cred:RefreshService; - rdfs:isDefinedBy cred: . -cred:RefreshService a rdfs:Class; - rdfs:label "Refresh Service"@en; - rdfs:comment """A refresh service is a mechanism that can be utilized by software agents to retrieve an updated copy of a `verifiable credential`."""@en; - rdfs:isDefinedBy cred: . -cred:CredentialSchema a rdfs:Class . -cred:CredentialStatus a rdfs:Class . -cred:CredentialEvidence a rdfs:Class . -cred:VerifiableCredential a rdfs:Class; - rdfs:label "Verifiable Credential"@en; - rdfs:comment """A `credential` is a set of one or more claims made by an issuer. A `verifiable credential` is a tamper-evident credential that has authorship that can be cryptographically verified. Verifiable credentials can be used to build `verifiable presentations`, which can also be cryptographically verified. The claims in a credential can be about different subjects."""@en; - rdfs:isDefinedBy cred: . -cred:VerifiablePresentation a rdfs:Class; - rdfs:label "Verifiable Presentation"@en; - rdfs:comment """A `presentation` is data derived from one or more `credentials`, issued by one or more `issuers`, that is shared with a specific `verifier`. A `verifiable presentation` is a tamper-evident `presentation` encoded in such a way that authorship of the data can be trusted after a process of cryptographic verification. Certain types of verifiable presentations might contain data that is synthesized from, but do not contain, the original verifiable credentials (for example, zero-knowledge proofs)."""@en; - rdfs:isDefinedBy cred: . - -# Property definitions -cred:credentialSchema a rdf:Property; - rdfs:label "Credential Schema"@en; - rdfs:comment """The value of the credentialSchema property MUST be one or more data schemas that provide verifiers with enough information to determine if the provided data conforms to the provided schema."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range cred:CredentialSchema; - rdfs:isDefinedBy cred: . -cred:credentialStatus a rdf:Property; - rdfs:label "Credential Status"@en; - rdfs:comment """The value of the credentialStatus property MUST include the id property, which MUST be a URL, and the type property, which expresses the credential status type (also referred to as the credential status scheme), which MUST provide enough information to determine the current status of the credential (for example, suspended or revoked)."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range cred:CredentialStatus; - rdfs:isDefinedBy cred: . -cred:credentialSubject a rdf:Property; - rdfs:label "Credential Subject"@en; - rdfs:comment """An entity about which claims are made."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:isDefinedBy cred: . -cred:evidence a rdf:Property; - rdfs:label "evidence"@en; - rdfs:comment """The value of the evidence property MUST be one or more evidence schemes providing enough information to a verifier to determine whether the evidence gathered meets their requirements for issuing a credential. The precise content of each evidence scheme is determined by the specific evidence type definition."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range cred:CredentialEvidence; - rdfs:isDefinedBy cred: . -cred:expirationDate a rdf:Property; - rdfs:label "expiration date"@en; - rdfs:comment """The value of the expirationDate property MUST be a string value of an ISO8601 combined date and time string representing the date and time the credential ceases to be valid."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range xsd:dateTime; - rdfs:isDefinedBy cred: . -cred:issuanceDate a rdf:Property; - rdfs:label "issuance date"@en; - rdfs:comment """The value of the issuanceDate property MUST be a string value of an ISO8601 combined date and time string representing the date and time the credential was issued. Note that this date represents the earliest date when the information associated with the credentialSubject property became valid."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range xsd:dateTime; - rdfs:isDefinedBy cred: . -cred:issuer a rdf:Property; - rdfs:label "issuer"@en; - rdfs:comment """The value of the issuer property MUST be a URI. It is RECOMMENDED that dereferencing the URI results in a document containing machine-readable information about the issuer that can be used to verify the information expressed in the credential."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:isDefinedBy cred: . -cred:refreshService a rdf:Property; - rdfs:label "refresh service"@en; - rdfs:comment """The value of the refreshService property MUST be one or more refresh services that provides enough information to the holder's software such that the holder can refresh the credential."""@en; - rdfs:domain cred:VerifiableCredential; - rdfs:range cred:RefreshService; - rdfs:isDefinedBy cred: . -cred:serviceEndpoint a rdf:Property; - rdfs:label "service endpoint"@en; - rdfs:comment """The value of the serviceEndpoint property MUST be a URL to the service endpoint associated with the subject."""@en; - rdfs:domain cred:RefreshService; - rdfs:isDefinedBy cred: . -cred:termsOfUse a rdf:Property; - rdfs:label "terms of use"@en; - rdfs:comment """If specified, the value of the termsOfUse property MUST specify one or more terms of use policies under which the creator issued the credential or presentation. If the recipient (a holder or verifier) is not willing to adhere to the specified terms of use, then they do so on their own responsibility and might incur legal liability if they violate the stated Terms of Use. Each termsOfUse MUST specify its type, for example, IssuerPolicy, and optionally, its instance id. The precise contents of each term of use is determined by the specific TermsOfUse type definition."""@en; -# rdfs:range odrl:Policy; - rdfs:domain cred:VerifiableCredential; - rdfs:isDefinedBy cred: . -cred:verifiableCredential a rdf:Property; - rdfs:label "verifiable credential"@en; - rdfs:comment """The value of the verifiableCredential property MUST describe a VerifiableCredential."""@en; - rdfs:domain cred:VerifiablePresentation; - rdfs:range cred:VerifiableCredential; - rdfs:isDefinedBy cred: . \ No newline at end of file diff --git a/crates/claims/crates/vc/src/schema/sec.ttl b/crates/claims/crates/vc/src/schema/sec.ttl deleted file mode 100644 index ceb0bd5eb..000000000 --- a/crates/claims/crates/vc/src/schema/sec.ttl +++ /dev/null @@ -1,13 +0,0 @@ -@prefix rdfs: . -@prefix xsd: . -@prefix owl: . -@prefix sec: . -@prefix tldr: . - -sec:multibase a rdfs:Datatype ; - owl:onDatatype xsd:string ; - owl:withRestrictions () ; - a tldr:Layout ; - tldr:name "Multibase" ; - tldr:derivedFrom tldr:String ; - tldr:withRestrictions () . \ No newline at end of file diff --git a/crates/claims/crates/vc/src/syntax/context.rs b/crates/claims/crates/vc/src/syntax/context.rs index 3e1f8682b..3e46caa34 100644 --- a/crates/claims/crates/vc/src/syntax/context.rs +++ b/crates/claims/crates/vc/src/syntax/context.rs @@ -5,34 +5,39 @@ use iref::{Iri, IriRef, IriRefBuf}; use serde::{Deserialize, Serialize}; use ssi_json_ld::syntax::ContextEntry; -use crate::V1; - -/// Verifiable Credential context. +/// JSON-LD context. /// /// This type represents the value of the `@context` property. /// -/// It is an ordered set where the first item is a URI with the value -/// `https://www.w3.org/2018/credentials/v1`. +/// It is an ordered set where the first item is a URI given by the `V` type +/// parameter implementing [`RequiredContext`], followed by a list of more +/// required context given by the type parameter `T`, implementing +/// [`RequiredContextList`]. #[derive(Educe, Serialize)] // FIXME serializing a single entry as a string breaks Tezos JCS cryptosuite. #[educe(Debug, Clone)] #[serde(transparent, bound = "")] -pub struct Context(ssi_json_ld::syntax::Context, PhantomData); +pub struct Context(ssi_json_ld::syntax::Context, PhantomData<(V, T)>); -impl Default for Context { +impl Default for Context { fn default() -> Self { Self( ssi_json_ld::syntax::Context::Many( - V::CONTEXT_IRIS - .iter() - .map(|&i| ssi_json_ld::syntax::ContextEntry::IriRef(i.as_iri_ref().to_owned())) - .collect(), + std::iter::once(ssi_json_ld::syntax::ContextEntry::IriRef( + V::CONTEXT_IRI.as_iri_ref().to_owned(), + )) + .chain( + T::CONTEXT_IRIS.iter().map(|&i| { + ssi_json_ld::syntax::ContextEntry::IriRef(i.as_iri_ref().to_owned()) + }), + ) + .collect(), ), PhantomData, ) } } -impl Context { +impl Context { pub fn contains_iri(&self, iri: &Iri) -> bool { self.contains_iri_ref(iri.as_iri_ref()) } @@ -48,27 +53,27 @@ impl Context { } } -impl AsRef for Context { +impl AsRef for Context { fn as_ref(&self) -> &ssi_json_ld::syntax::Context { &self.0 } } -impl Borrow for Context { +impl Borrow for Context { fn borrow(&self) -> &ssi_json_ld::syntax::Context { &self.0 } } -impl<'de, V: RequiredContextSet> Deserialize<'de> for Context { +impl<'de, V: RequiredContext, T: RequiredContextList> Deserialize<'de> for Context { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - struct Visitor(PhantomData); + struct Visitor(PhantomData<(V, E)>); - impl<'de, V: RequiredContextSet> serde::de::Visitor<'de> for Visitor { - type Value = Context; + impl<'de, V: RequiredContext, T: RequiredContextList> serde::de::Visitor<'de> for Visitor { + type Value = Context; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "presentation types") @@ -79,19 +84,28 @@ impl<'de, V: RequiredContextSet> Deserialize<'de> for Context { E: serde::de::Error, { match IriRefBuf::new(v.to_owned()) { - Ok(iri_ref) => { - let contexts = vec![ContextEntry::IriRef(iri_ref)]; - if contexts.len() < V::CONTEXT_IRIS.len() { - let required_iri = V::CONTEXT_IRIS[contexts.len()]; - Err(E::custom(format!( - "missing required context `{}`", - required_iri - ))) - } else { + Ok(v) => { + if v == V::CONTEXT_IRI { + let contexts = vec![ContextEntry::IriRef(v)]; + + for &required in T::CONTEXT_IRIS { + if required != V::CONTEXT_IRI { + return Err(E::custom(format!( + "expected required context `{}`", + required + ))); + } + } + Ok(Context( ssi_json_ld::syntax::Context::Many(contexts), PhantomData, )) + } else { + Err(E::custom(format!( + "expected required context `{}`", + V::CONTEXT_IRI + ))) } } Err(e) => Err(E::custom(format!("invalid context IRI `{v}`: {e}"))), @@ -104,12 +118,32 @@ impl<'de, V: RequiredContextSet> Deserialize<'de> for Context { { let mut contexts = Vec::new(); + match seq.next_element()? { + Some(entry) => match &entry { + ContextEntry::IriRef(iri) if iri == V::CONTEXT_IRI => contexts.push(entry), + _ => { + return Err(::custom(format!( + "missing required context `{}`", + V::CONTEXT_IRI + ))) + } + }, + None => { + return Err(::custom(format!( + "missing required context `{}`", + V::CONTEXT_IRI + ))) + } + } + while let Some(entry) = seq.next_element()? { - if contexts.len() < V::CONTEXT_IRIS.len() { - let required_iri = V::CONTEXT_IRIS[contexts.len()]; + let i = contexts.len() - 1; + + if i < T::CONTEXT_IRIS.len() { + let required_iri = T::CONTEXT_IRIS[i]; match &entry { - ContextEntry::IriRef(i) if required_iri.as_iri_ref() == i => {} + ContextEntry::IriRef(iri) if iri == required_iri.as_iri_ref() => {} _ => { return Err(::custom(format!( "missing required context `{}`", @@ -122,8 +156,8 @@ impl<'de, V: RequiredContextSet> Deserialize<'de> for Context { contexts.push(entry) } - if contexts.len() < V::CONTEXT_IRIS.len() { - let required_iri = V::CONTEXT_IRIS[contexts.len()]; + if contexts.len() - 1 < T::CONTEXT_IRIS.len() { + let required_iri = T::CONTEXT_IRIS[contexts.len() - 1]; Err(::custom(format!( "missing required context `{}`", required_iri @@ -146,17 +180,21 @@ pub trait RequiredContext { } /// Set of required contexts. -pub trait RequiredContextSet { +pub trait RequiredContextList { const CONTEXT_IRIS: &'static [&'static Iri]; } -impl RequiredContextSet for T { +impl RequiredContextList for () { + const CONTEXT_IRIS: &'static [&'static Iri] = &[]; +} + +impl RequiredContextList for T { const CONTEXT_IRIS: &'static [&'static Iri] = &[T::CONTEXT_IRI]; } macro_rules! required_context_tuple { ($($t:ident: $n:tt),*) => { - impl<$($t : RequiredContext),*> RequiredContextSet for ($($t),*,) { + impl<$($t : RequiredContext),*> RequiredContextList for ($($t),*,) { const CONTEXT_IRIS: &'static [&'static Iri] = &[ $($t::CONTEXT_IRI),* ]; diff --git a/crates/claims/crates/vc/src/syntax/credential.rs b/crates/claims/crates/vc/src/syntax/credential.rs new file mode 100644 index 000000000..9a45b0e04 --- /dev/null +++ b/crates/claims/crates/vc/src/syntax/credential.rs @@ -0,0 +1,104 @@ +use std::{borrow::Cow, hash::Hash}; + +use iref::Uri; +use rdf_types::VocabularyMut; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::{ClaimsValidity, DateTimeEnvironment, Validate}; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject, Vocabulary}; + +use super::{RequiredContextList, RequiredTypeSet}; +use crate::{v1, v2, MaybeIdentified}; + +/// Any JSON credential using VCDM v1 or v2. +/// +/// If you care about required context and/or type, use the +/// [`AnySpecializedJsonCredential`] type directly. +pub type AnyJsonCredential = AnySpecializedJsonCredential; + +/// Any JSON credential using VCDM v1 or v2 with custom required contexts and +/// types. +/// +/// If you don't care about required context and/or type, you can use the +/// [`AnyJsonCredential`] type alias instead. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde( + untagged, + bound( + serialize = "S: Serialize", + deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet" + ) +)] +pub enum AnySpecializedJsonCredential { + V1(v1::syntax::SpecializedJsonCredential), + V2(v2::syntax::SpecializedJsonCredential), +} + +impl JsonLdObject for AnySpecializedJsonCredential { + fn json_ld_context(&self) -> Option> { + match self { + Self::V1(c) => c.json_ld_context(), + Self::V2(c) => c.json_ld_context(), + } + } +} + +impl JsonLdNodeObject for AnySpecializedJsonCredential { + fn json_ld_type(&self) -> JsonLdTypes { + match self { + Self::V1(c) => c.json_ld_type(), + Self::V2(c) => c.json_ld_type(), + } + } +} + +impl Validate for AnySpecializedJsonCredential +where + E: DateTimeEnvironment, +{ + fn validate(&self, env: &E, proof: &P) -> ClaimsValidity { + match self { + Self::V1(c) => c.validate(env, proof), + Self::V2(c) => c.validate(env, proof), + } + } +} + +impl MaybeIdentified for AnySpecializedJsonCredential { + fn id(&self) -> Option<&Uri> { + match self { + Self::V1(c) => c.id(), + Self::V2(c) => c.id(), + } + } +} + +impl ssi_json_ld::Expandable for AnySpecializedJsonCredential +where + S: Serialize, +{ + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + #[allow(async_fn_in_trait)] + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand_with(ld, loader).await + } +} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/evidence.rs b/crates/claims/crates/vc/src/syntax/json/credential/evidence.rs deleted file mode 100644 index b9bf1beb1..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/evidence.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::BTreeMap; - -use iref::{Uri, UriBuf}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Evidence { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - - #[serde(rename = "type")] - pub type_: Vec, - - #[serde(flatten)] - pub property_set: Option>, -} - -impl crate::Evidence for Evidence { - fn id(&self) -> Option<&Uri> { - self.id.as_deref() - } - - fn type_(&self) -> &[String] { - &self.type_ - } -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/issuer.rs b/crates/claims/crates/vc/src/syntax/json/credential/issuer.rs deleted file mode 100644 index f944d3d99..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/issuer.rs +++ /dev/null @@ -1,32 +0,0 @@ -use iref::{Uri, UriBuf}; -use serde::{Deserialize, Serialize}; - -use super::ObjectWithId; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(untagged)] -pub enum Issuer { - Uri(UriBuf), - Object(ObjectWithId), -} - -impl Issuer { - pub fn id(&self) -> &Uri { - match self { - Self::Uri(uri) => uri, - Self::Object(object) => &object.id, - } - } -} - -impl From for Issuer { - fn from(value: UriBuf) -> Self { - Self::Uri(value) - } -} - -impl crate::Issuer for Issuer { - fn id(&self) -> &Uri { - self.id() - } -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/refresh_service.rs b/crates/claims/crates/vc/src/syntax/json/credential/refresh_service.rs deleted file mode 100644 index 0ba8a733b..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/refresh_service.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::collections::BTreeMap; - -use iref::{Uri, UriBuf}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct RefreshService { - pub id: UriBuf, - - #[serde(rename = "type")] - pub type_: String, - - #[serde(flatten)] - pub property_set: Option>, -} - -impl crate::RefreshService for RefreshService { - fn id(&self) -> &Uri { - &self.id - } - - fn type_(&self) -> &str { - &self.type_ - } -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/schema.rs b/crates/claims/crates/vc/src/syntax/json/credential/schema.rs deleted file mode 100644 index 6b763112c..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/schema.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::collections::BTreeMap; - -use iref::UriBuf; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Schema { - pub id: UriBuf, - - #[serde(rename = "type")] - pub type_: String, - - #[serde(flatten)] - pub property_set: Option>, -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/status.rs b/crates/claims/crates/vc/src/syntax/json/credential/status.rs deleted file mode 100644 index fd7d111fc..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/status.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::collections::BTreeMap; - -use iref::{Uri, UriBuf}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Status { - pub id: UriBuf, - - #[serde(rename = "type")] - pub type_: String, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(flatten)] - pub property_set: Option>, -} - -impl crate::CredentialStatus for Status { - fn id(&self) -> &Uri { - &self.id - } -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/terms_of_use.rs b/crates/claims/crates/vc/src/syntax/json/credential/terms_of_use.rs deleted file mode 100644 index 22ca9d1ee..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/terms_of_use.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::BTreeMap; - -use iref::{Uri, UriBuf}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct TermsOfUse { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - - #[serde(rename = "type")] - pub type_: String, - - #[serde(flatten)] - pub property_set: Option>, -} - -impl crate::TermsOfUse for TermsOfUse { - fn id(&self) -> Option<&Uri> { - self.id.as_deref() - } - - fn type_(&self) -> &str { - &self.type_ - } -} diff --git a/crates/claims/crates/vc/src/syntax/json/credential/type.rs b/crates/claims/crates/vc/src/syntax/json/credential/type.rs deleted file mode 100644 index aaece2f53..000000000 --- a/crates/claims/crates/vc/src/syntax/json/credential/type.rs +++ /dev/null @@ -1,141 +0,0 @@ -use serde::{ser::SerializeSeq, Deserialize, Serialize}; -use ssi_json_ld::JsonLdTypes; -use std::{borrow::Cow, marker::PhantomData}; - -use crate::VERIFIABLE_CREDENTIAL_TYPE; - -pub trait RequiredCredentialTypeSet { - const REQUIRED_CREDENTIAL_TYPES: &'static [&'static str]; -} - -impl RequiredCredentialTypeSet for () { - const REQUIRED_CREDENTIAL_TYPES: &'static [&'static str] = &[]; -} - -pub trait RequiredCredentialType { - const REQUIRED_CREDENTIAL_TYPE: &'static str; -} - -impl RequiredCredentialTypeSet for T { - const REQUIRED_CREDENTIAL_TYPES: &'static [&'static str] = &[T::REQUIRED_CREDENTIAL_TYPE]; -} - -#[derive(Debug, Clone)] -pub struct JsonCredentialTypes(Vec, PhantomData); - -impl Default for JsonCredentialTypes { - fn default() -> Self { - Self( - T::REQUIRED_CREDENTIAL_TYPES - .iter() - .filter_map(|&i| { - if i == VERIFIABLE_CREDENTIAL_TYPE { - None - } else { - Some(i.to_owned()) - } - }) - .collect(), - PhantomData, - ) - } -} - -impl JsonCredentialTypes { - pub fn additional_types(&self) -> &[String] { - &self.0 - } - - pub fn to_json_ld_types(&self) -> JsonLdTypes { - JsonLdTypes::new(&[VERIFIABLE_CREDENTIAL_TYPE], Cow::Borrowed(&self.0)) - } -} - -impl Serialize for JsonCredentialTypes { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut seq = serializer.serialize_seq(Some(1 + self.0.len()))?; - seq.serialize_element(VERIFIABLE_CREDENTIAL_TYPE)?; - for t in &self.0 { - seq.serialize_element(t)?; - } - seq.end() - } -} - -impl<'de, T: RequiredCredentialTypeSet> Deserialize<'de> for JsonCredentialTypes { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct Visitor(PhantomData); - - impl<'de, T: RequiredCredentialTypeSet> serde::de::Visitor<'de> for Visitor { - type Value = JsonCredentialTypes; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "credential types") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - if v == VERIFIABLE_CREDENTIAL_TYPE { - for &required in T::REQUIRED_CREDENTIAL_TYPES { - if required != VERIFIABLE_CREDENTIAL_TYPE { - return Err(E::custom(format!( - "expected required `{}` type", - required - ))); - } - } - - Ok(JsonCredentialTypes(Vec::new(), PhantomData)) - } else { - Err(E::custom(format!( - "expected required `{}` type", - VERIFIABLE_CREDENTIAL_TYPE - ))) - } - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut base_type = false; - let mut types = Vec::new(); - - while let Some(t) = seq.next_element()? { - if t == VERIFIABLE_CREDENTIAL_TYPE { - base_type = true - } else { - types.push(t) - } - } - - if !base_type { - return Err(::custom(format!( - "expected required `{}` type", - VERIFIABLE_CREDENTIAL_TYPE - ))); - } - - for &required in T::REQUIRED_CREDENTIAL_TYPES { - if !types.iter().any(|s| s == required) { - return Err(::custom(format!( - "expected required `{required}` type" - ))); - } - } - - Ok(JsonCredentialTypes(types, PhantomData)) - } - } - - deserializer.deserialize_any(Visitor(PhantomData)) - } -} diff --git a/crates/claims/crates/vc/src/syntax/mod.rs b/crates/claims/crates/vc/src/syntax/mod.rs index e4a135ed5..3c0bf2bd3 100644 --- a/crates/claims/crates/vc/src/syntax/mod.rs +++ b/crates/claims/crates/vc/src/syntax/mod.rs @@ -1,11 +1,92 @@ -//! Syntaxes for the VC data model. mod context; -pub mod json; -mod jwt; +mod credential; +mod presentation; +mod types; + +use std::collections::BTreeMap; pub use context::*; -pub use json::{JsonCredential, JsonPresentation, SpecializedJsonCredential}; -pub use jwt::*; +pub use credential::*; +use iref::{Uri, UriBuf}; +pub use presentation::*; +use serde::{Deserialize, Serialize}; +pub use types::*; + +use crate::Identified; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum IdOr { + Id(UriBuf), + NotId(T), +} + +impl IdOr { + pub fn id(&self) -> &Uri { + match self { + Self::Id(id) => id, + Self::NotId(t) => t.id(), + } + } +} + +impl From for IdOr { + fn from(value: UriBuf) -> Self { + Self::Id(value) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdentifiedObject { + pub id: UriBuf, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MaybeIdentifiedObject { + pub id: Option, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdentifiedTypedObject { + pub id: UriBuf, + + #[serde(rename = "type", with = "value_or_array")] + pub types: Vec, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MaybeIdentifiedTypedObject { + pub id: Option, + + #[serde(rename = "type", with = "value_or_array")] + pub types: Vec, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TypedObject { + #[serde(rename = "type", with = "value_or_array")] + pub types: Vec, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} pub(crate) mod value_or_array { use serde::{Deserialize, Serialize}; diff --git a/crates/claims/crates/vc/src/syntax/presentation.rs b/crates/claims/crates/vc/src/syntax/presentation.rs new file mode 100644 index 000000000..3c1ad3e89 --- /dev/null +++ b/crates/claims/crates/vc/src/syntax/presentation.rs @@ -0,0 +1,75 @@ +use std::{borrow::Cow, hash::Hash}; + +use rdf_types::VocabularyMut; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::{ClaimsValidity, Validate}; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject, Vocabulary}; + +use crate::{v1, v2}; + +/// Any JSON presentation using VCDM v1 or v2. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AnyJsonPresentation { + V1(v1::syntax::JsonPresentation), + V2(v2::syntax::JsonPresentation), +} + +impl JsonLdObject for AnyJsonPresentation { + fn json_ld_context(&self) -> Option> { + match self { + Self::V1(p) => p.json_ld_context(), + Self::V2(p) => p.json_ld_context(), + } + } +} + +impl JsonLdNodeObject for AnyJsonPresentation { + fn json_ld_type(&self) -> JsonLdTypes { + match self { + Self::V1(p) => p.json_ld_type(), + Self::V2(p) => p.json_ld_type(), + } + } +} + +impl Validate for AnyJsonPresentation { + fn validate(&self, env: &E, proof: &P) -> ClaimsValidity { + match self { + Self::V1(p) => Validate::::validate(p, env, proof), + Self::V2(p) => Validate::::validate(p, env, proof), + } + } +} + +impl ssi_json_ld::Expandable for AnyJsonPresentation +where + C1: Serialize, + C2: Serialize, +{ + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + #[allow(async_fn_in_trait)] + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand_with(ld, loader).await + } +} diff --git a/crates/claims/crates/vc/src/syntax/json/presentation/type.rs b/crates/claims/crates/vc/src/syntax/types.rs similarity index 57% rename from crates/claims/crates/vc/src/syntax/json/presentation/type.rs rename to crates/claims/crates/vc/src/syntax/types.rs index 877130164..9637fa1f3 100644 --- a/crates/claims/crates/vc/src/syntax/json/presentation/type.rs +++ b/crates/claims/crates/vc/src/syntax/types.rs @@ -1,40 +1,59 @@ +use educe::Educe; use serde::{ser::SerializeSeq, Deserialize, Serialize}; use ssi_json_ld::JsonLdTypes; use std::{borrow::Cow, marker::PhantomData}; -use crate::VERIFIABLE_PRESENTATION_TYPE; +pub trait RequiredType { + const REQUIRED_TYPE: &'static str; +} + +pub trait RequiredTypeSet { + const REQUIRED_TYPES: &'static [&'static str]; +} -pub trait RequiredPresentationTypeSet { - const REQUIRED_PRESENTATION_TYPES: &'static [&'static str]; +impl RequiredTypeSet for () { + const REQUIRED_TYPES: &'static [&'static str] = &[]; } -impl RequiredPresentationTypeSet for () { - const REQUIRED_PRESENTATION_TYPES: &'static [&'static str] = &[]; +impl RequiredTypeSet for T { + const REQUIRED_TYPES: &'static [&'static str] = &[T::REQUIRED_TYPE]; } -#[derive(Debug, Default, Clone)] -pub struct JsonPresentationTypes(Vec, PhantomData); +pub trait TypeSerializationPolicy { + const PREFER_ARRAY: bool; +} + +/// List of types. +/// +/// An unordered list of types that must include `B` (a base type) implementing +/// [`RequiredType`], and more required types given by `T` implementing +/// [`RequiredTypeSet`]. +#[derive(Educe)] +#[educe(Debug, Default, Clone)] +pub struct Types(Vec, PhantomData<(B, T)>); -impl JsonPresentationTypes { +impl Types { pub fn additional_types(&self) -> &[String] { &self.0 } +} +impl Types { pub fn to_json_ld_types(&self) -> JsonLdTypes { - JsonLdTypes::new(&[VERIFIABLE_PRESENTATION_TYPE], Cow::Borrowed(&self.0)) + JsonLdTypes::new(&[B::REQUIRED_TYPE], Cow::Borrowed(&self.0)) } } -impl Serialize for JsonPresentationTypes { +impl Serialize for Types { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - if self.0.is_empty() { - VERIFIABLE_PRESENTATION_TYPE.serialize(serializer) + if !B::PREFER_ARRAY && self.0.is_empty() { + B::REQUIRED_TYPE.serialize(serializer) } else { let mut seq = serializer.serialize_seq(Some(1 + self.0.len()))?; - seq.serialize_element(VERIFIABLE_PRESENTATION_TYPE)?; + seq.serialize_element(B::REQUIRED_TYPE)?; for t in &self.0 { seq.serialize_element(t)?; } @@ -43,15 +62,15 @@ impl Serialize for JsonPresentationTypes { } } -impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationTypes { +impl<'de, B: RequiredType, T: RequiredTypeSet> Deserialize<'de> for Types { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - struct Visitor(PhantomData); + struct Visitor(PhantomData<(B, T)>); - impl<'de, T: RequiredPresentationTypeSet> serde::de::Visitor<'de> for Visitor { - type Value = JsonPresentationTypes; + impl<'de, B: RequiredType, T: RequiredTypeSet> serde::de::Visitor<'de> for Visitor { + type Value = Types; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "credential types") @@ -61,9 +80,9 @@ impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationT where E: serde::de::Error, { - if v == VERIFIABLE_PRESENTATION_TYPE { - for &required in T::REQUIRED_PRESENTATION_TYPES { - if required != VERIFIABLE_PRESENTATION_TYPE { + if v == B::REQUIRED_TYPE { + for &required in T::REQUIRED_TYPES { + if required != B::REQUIRED_TYPE { return Err(E::custom(format!( "expected required `{}` type", required @@ -71,11 +90,11 @@ impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationT } } - Ok(JsonPresentationTypes(Vec::new(), PhantomData)) + Ok(Types(Vec::new(), PhantomData)) } else { Err(E::custom(format!( "expected required `{}` type", - VERIFIABLE_PRESENTATION_TYPE + B::REQUIRED_TYPE ))) } } @@ -88,7 +107,7 @@ impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationT let mut types = Vec::new(); while let Some(t) = seq.next_element()? { - if t == VERIFIABLE_PRESENTATION_TYPE { + if t == B::REQUIRED_TYPE { base_type = true } else { types.push(t) @@ -98,11 +117,11 @@ impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationT if !base_type { return Err(::custom(format!( "expected required `{}` type", - VERIFIABLE_PRESENTATION_TYPE + B::REQUIRED_TYPE ))); } - for &required in T::REQUIRED_PRESENTATION_TYPES { + for &required in T::REQUIRED_TYPES { if !types.iter().any(|s| s == required) { return Err(::custom(format!( "expected required `{required}` type" @@ -110,7 +129,7 @@ impl<'de, T: RequiredPresentationTypeSet> Deserialize<'de> for JsonPresentationT } } - Ok(JsonPresentationTypes(types, PhantomData)) + Ok(Types(types, PhantomData)) } } diff --git a/crates/claims/crates/vc/src/typed.rs b/crates/claims/crates/vc/src/typed.rs new file mode 100644 index 000000000..b8deb30eb --- /dev/null +++ b/crates/claims/crates/vc/src/typed.rs @@ -0,0 +1,29 @@ +use crate::syntax::{IdentifiedTypedObject, MaybeIdentifiedTypedObject, TypedObject}; + +pub trait Typed { + fn types(&self) -> &[String]; +} + +impl Typed for std::convert::Infallible { + fn types(&self) -> &[String] { + unreachable!() + } +} + +impl Typed for IdentifiedTypedObject { + fn types(&self) -> &[String] { + &self.types + } +} + +impl Typed for MaybeIdentifiedTypedObject { + fn types(&self) -> &[String] { + &self.types + } +} + +impl Typed for TypedObject { + fn types(&self) -> &[String] { + &self.types + } +} diff --git a/crates/claims/crates/vc/src/data_integrity.rs b/crates/claims/crates/vc/src/v1/data_integrity.rs similarity index 79% rename from crates/claims/crates/vc/src/data_integrity.rs rename to crates/claims/crates/vc/src/v1/data_integrity.rs index d33802ebe..e653afa78 100644 --- a/crates/claims/crates/vc/src/data_integrity.rs +++ b/crates/claims/crates/vc/src/v1/data_integrity.rs @@ -1,11 +1,12 @@ -use crate::{JsonCredential, SpecializedJsonCredential}; use ssi_data_integrity::{AnySuite, DataIntegrity, DecodeError}; +use super::JsonCredential; + /// Decodes a Data-Integrity credential or presentation from its JSON binary /// representation. pub fn any_credential_from_json_slice( json: &[u8], -) -> Result, DecodeError> { +) -> Result, DecodeError> { ssi_data_integrity::from_json_slice(json) } diff --git a/crates/claims/crates/vc/src/data_model/credential.rs b/crates/claims/crates/vc/src/v1/data_model/credential.rs similarity index 89% rename from crates/claims/crates/vc/src/data_model/credential.rs rename to crates/claims/crates/vc/src/v1/data_model/credential.rs index c9c2a4c10..980bca6b2 100644 --- a/crates/claims/crates/vc/src/data_model/credential.rs +++ b/crates/claims/crates/vc/src/v1/data_model/credential.rs @@ -1,11 +1,13 @@ -use iref::Uri; +use iref::{Iri, Uri}; use ssi_claims_core::{ClaimsValidity, DateTimeEnvironment, InvalidClaims, VerifiableClaims}; use ssi_data_integrity::{CryptographicSuite, DataIntegrity}; +use static_iref::iri; use xsd_types::DateTime; -use super::{CredentialStatus, Evidence, Issuer, RefreshService, TermsOfUse}; +use crate::{v1::syntax::VERIFIABLE_CREDENTIAL_TYPE, Identified, MaybeIdentified, Typed}; -pub const VERIFIABLE_CREDENTIAL_TYPE: &str = "VerifiableCredential"; +pub const VERIFIABLE_CREDENTIAL: &Iri = + iri!("https://www.w3.org/2018/credentials#VerifiableCredential"); /// Credential trait. pub trait Credential { @@ -13,33 +15,33 @@ pub trait Credential { type Subject; /// Issuer type. - type Issuer: ?Sized + Issuer; + type Issuer: ?Sized + Identified; /// Credential status type. - type Status: CredentialStatus; + type Status: Identified + Typed; /// Refresh service. /// /// See: - type RefreshService: RefreshService; + type RefreshService: Identified + Typed; /// Terms of Use type. /// /// Terms of use can be utilized by an issuer or a holder to communicate the /// terms under which a verifiable credential or verifiable presentation was /// issued. - type TermsOfUse: TermsOfUse; + type TermsOfUse: MaybeIdentified + Typed; /// Evidence type. /// /// Can be included by an issuer to provide the verifier with additional /// supporting information in a verifiable credential. - type Evidence: Evidence; + type Evidence: MaybeIdentified + Typed; /// Credential Schemas (Zero-Knowledge Proofs). /// /// See: - type Schema; + type Schema: Identified + Typed; /// Identifier. fn id(&self) -> Option<&Uri> { @@ -57,10 +59,7 @@ pub trait Credential { } fn types(&self) -> CredentialTypes { - CredentialTypes { - base_type: true, - additional_types: self.additional_types().iter(), - } + CredentialTypes::from_additional_types(self.additional_types()) } /// Credential subject. @@ -222,6 +221,15 @@ pub struct CredentialTypes<'a> { additional_types: std::slice::Iter<'a, String>, } +impl<'a> CredentialTypes<'a> { + pub fn from_additional_types(additional_types: &'a [String]) -> Self { + Self { + base_type: true, + additional_types: additional_types.iter(), + } + } +} + impl<'a> Iterator for CredentialTypes<'a> { type Item = &'a str; diff --git a/crates/claims/crates/vc/src/syntax/json/mod.rs b/crates/claims/crates/vc/src/v1/data_model/mod.rs similarity index 61% rename from crates/claims/crates/vc/src/syntax/json/mod.rs rename to crates/claims/crates/vc/src/v1/data_model/mod.rs index 17e4d379e..8e0289fa9 100644 --- a/crates/claims/crates/vc/src/syntax/json/mod.rs +++ b/crates/claims/crates/vc/src/v1/data_model/mod.rs @@ -1,4 +1,3 @@ -//! JSON syntax for Credentials and Presentations. mod credential; mod presentation; diff --git a/crates/claims/crates/vc/src/data_model/presentation.rs b/crates/claims/crates/vc/src/v1/data_model/presentation.rs similarity index 82% rename from crates/claims/crates/vc/src/data_model/presentation.rs rename to crates/claims/crates/vc/src/v1/data_model/presentation.rs index 3beb65c18..47771c747 100644 --- a/crates/claims/crates/vc/src/data_model/presentation.rs +++ b/crates/claims/crates/vc/src/v1/data_model/presentation.rs @@ -1,9 +1,9 @@ use iref::Uri; use ssi_claims_core::VerifiableClaims; -use super::Credential; +use crate::v1::syntax::VERIFIABLE_PRESENTATION_TYPE; -pub const VERIFIABLE_PRESENTATION_TYPE: &str = "VerifiablePresentation"; +use super::Credential; /// Presentation trait. pub trait Presentation { @@ -45,6 +45,15 @@ pub struct PresentationTypes<'a> { additional_types: std::slice::Iter<'a, String>, } +impl<'a> PresentationTypes<'a> { + pub fn from_additional_types(additional_types: &'a [String]) -> Self { + Self { + base_type: true, + additional_types: additional_types.iter(), + } + } +} + impl<'a> Iterator for PresentationTypes<'a> { type Item = &'a str; diff --git a/crates/claims/crates/vc/src/syntax/jwt/decode.rs b/crates/claims/crates/vc/src/v1/jwt/decode.rs similarity index 100% rename from crates/claims/crates/vc/src/syntax/jwt/decode.rs rename to crates/claims/crates/vc/src/v1/jwt/decode.rs diff --git a/crates/claims/crates/vc/src/syntax/jwt/encode.rs b/crates/claims/crates/vc/src/v1/jwt/encode.rs similarity index 100% rename from crates/claims/crates/vc/src/syntax/jwt/encode.rs rename to crates/claims/crates/vc/src/v1/jwt/encode.rs diff --git a/crates/claims/crates/vc/src/syntax/jwt/mod.rs b/crates/claims/crates/vc/src/v1/jwt/mod.rs similarity index 97% rename from crates/claims/crates/vc/src/syntax/jwt/mod.rs rename to crates/claims/crates/vc/src/v1/jwt/mod.rs index 191bd9422..2082ec780 100644 --- a/crates/claims/crates/vc/src/syntax/jwt/mod.rs +++ b/crates/claims/crates/vc/src/v1/jwt/mod.rs @@ -20,7 +20,7 @@ pub trait ToJwtClaims { fn to_jwt_claims(&self) -> Result; } -impl FromJwtClaims for DataIntegrity +impl FromJwtClaims for DataIntegrity where S: CryptographicSuite + TryFrom, S::VerificationMethod: DeserializeOwned, @@ -38,7 +38,7 @@ where } } -impl ToJwtClaims for DataIntegrity +impl ToJwtClaims for DataIntegrity where S::VerificationMethod: Serialize, S::ProofOptions: Serialize, diff --git a/crates/claims/crates/vc/src/v1/mod.rs b/crates/claims/crates/vc/src/v1/mod.rs new file mode 100644 index 000000000..9439536c3 --- /dev/null +++ b/crates/claims/crates/vc/src/v1/mod.rs @@ -0,0 +1,31 @@ +//! Verifiable Credentials Data Model v1.1 +//! +//! See: +use iref::Iri; + +pub mod data_integrity; +mod data_model; +mod jwt; +pub mod revocation; +pub mod syntax; + +pub use data_model::*; +pub use jwt::*; +pub use syntax::{ + Context, JsonCredential, JsonCredentialTypes, JsonPresentation, JsonPresentationTypes, + SpecializedJsonCredential, +}; + +use crate::syntax::RequiredContext; + +/// JSON-LD context IRI. +pub const CREDENTIALS_V1_CONTEXT_IRI: &Iri = + static_iref::iri!("https://www.w3.org/2018/credentials/v1"); + +/// JSON-LD context. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct V1; + +impl RequiredContext for V1 { + const CONTEXT_IRI: &'static Iri = CREDENTIALS_V1_CONTEXT_IRI; +} diff --git a/crates/claims/crates/vc/src/revocation/mod.rs b/crates/claims/crates/vc/src/v1/revocation/mod.rs similarity index 98% rename from crates/claims/crates/vc/src/revocation/mod.rs rename to crates/claims/crates/vc/src/v1/revocation/mod.rs index 4fe5eba45..e9783a24a 100644 --- a/crates/claims/crates/vc/src/revocation/mod.rs +++ b/crates/claims/crates/vc/src/v1/revocation/mod.rs @@ -18,7 +18,7 @@ mod v2021; pub use v2020::*; pub use v2021::*; -use crate::JsonCredential; +use super::SpecializedJsonCredential; /// Minimum length of a revocation list bit-string. /// @@ -232,7 +232,7 @@ pub trait CredentialStatus { #[allow(async_fn_in_trait)] async fn check( &self, - credential: &AnyDataIntegrity, + credential: &AnyDataIntegrity, resolver: &impl VerificationMethodResolver, ) -> Result; } @@ -374,11 +374,11 @@ mod tests { pub const EXAMPLE_REVOCATION_2020_LIST_URL: &str = "https://example.test/revocationList.json"; pub const EXAMPLE_REVOCATION_2020_LIST: &[u8] = - include_bytes!("../../../../../../tests/revocationList.json"); + include_bytes!("../../../../../../../tests/revocationList.json"); pub const EXAMPLE_STATUS_LIST_2021_URL: &str = "https://example.com/credentials/status/3"; pub const EXAMPLE_STATUS_LIST_2021: &[u8] = - include_bytes!("../../../../../../tests/statusList.json"); + include_bytes!("../../../../../../../tests/statusList.json"); #[test] fn default_list() { diff --git a/crates/claims/crates/vc/src/revocation/v2020.rs b/crates/claims/crates/vc/src/v1/revocation/v2020.rs similarity index 95% rename from crates/claims/crates/vc/src/revocation/v2020.rs rename to crates/claims/crates/vc/src/v1/revocation/v2020.rs index 042649538..4cfe71928 100644 --- a/crates/claims/crates/vc/src/revocation/v2020.rs +++ b/crates/claims/crates/vc/src/v1/revocation/v2020.rs @@ -13,9 +13,11 @@ use static_iref::iri; use std::collections::BTreeMap; use crate::{ - json::RequiredCredentialType, - revocation::{load_resource, Reason, StatusCheckError}, - JsonCredential, RequiredContext, SpecializedJsonCredential, + syntax::RequiredType, + v1::{ + revocation::{load_resource, Reason, StatusCheckError}, + RequiredContext, SpecializedJsonCredential, + }, }; use super::{ @@ -46,8 +48,8 @@ pub struct RevocationList2020Status { } pub struct RevocationList2020CredentialType; -impl RequiredCredentialType for RevocationList2020CredentialType { - const REQUIRED_CREDENTIAL_TYPE: &'static str = "RevocationList2020Credential"; +impl RequiredType for RevocationList2020CredentialType { + const REQUIRED_TYPE: &'static str = "RevocationList2020Credential"; } /// Verifiable Credential of type RevocationList2020Credential. @@ -108,7 +110,7 @@ impl CredentialStatus for RevocationList2020Status { /// [1]: https://w3c-ccg.github.io/vc-status-rl-2020/#validate-algorithm async fn check( &self, - credential: &AnyDataIntegrity, + credential: &AnyDataIntegrity, resolver: &impl VerificationMethodResolver, ) -> Result { use bitvec::prelude::*; diff --git a/crates/claims/crates/vc/src/revocation/v2021.rs b/crates/claims/crates/vc/src/v1/revocation/v2021.rs similarity index 95% rename from crates/claims/crates/vc/src/revocation/v2021.rs rename to crates/claims/crates/vc/src/v1/revocation/v2021.rs index baf93ec9e..614e294b8 100644 --- a/crates/claims/crates/vc/src/revocation/v2021.rs +++ b/crates/claims/crates/vc/src/v1/revocation/v2021.rs @@ -13,9 +13,11 @@ use static_iref::iri; use std::collections::BTreeMap; use crate::{ - json::RequiredCredentialType, - revocation::{load_resource, Reason}, - JsonCredential, RequiredContext, SpecializedJsonCredential, + syntax::RequiredType, + v1::{ + revocation::{load_resource, Reason}, + RequiredContext, SpecializedJsonCredential, + }, }; use super::{ @@ -64,8 +66,8 @@ pub enum StatusList2021Subject { pub struct StatusList2021CredentialType; -impl RequiredCredentialType for StatusList2021CredentialType { - const REQUIRED_CREDENTIAL_TYPE: &'static str = "StatusList2021Credential"; +impl RequiredType for StatusList2021CredentialType { + const REQUIRED_TYPE: &'static str = "StatusList2021Credential"; } /// Verifiable Credential of type RevocationList2020Credential. @@ -137,7 +139,7 @@ impl CredentialStatus for StatusList2021Entry { /// [1]: https://w3c-ccg.github.io/vc-status-list-2021/#validate-algorithm async fn check( &self, - credential: &AnyDataIntegrity, + credential: &AnyDataIntegrity, resolver: &impl VerificationMethodResolver, ) -> Result { use bitvec::prelude::*; diff --git a/crates/claims/crates/vc/src/syntax/json/credential/mod.rs b/crates/claims/crates/vc/src/v1/syntax/credential.rs similarity index 72% rename from crates/claims/crates/vc/src/syntax/json/credential/mod.rs rename to crates/claims/crates/vc/src/v1/syntax/credential.rs index 62a717443..36adfced5 100644 --- a/crates/claims/crates/vc/src/syntax/json/credential/mod.rs +++ b/crates/claims/crates/vc/src/v1/syntax/credential.rs @@ -8,35 +8,43 @@ use ssi_rdf::{Interpretation, LdEnvironment}; use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; use xsd_types::DateTime; -use super::super::value_or_array; -use crate::{Context, RequiredContextSet, V1}; - -mod evidence; -mod issuer; -mod refresh_service; -mod schema; -mod status; -mod terms_of_use; -mod r#type; - -pub use evidence::*; -pub use issuer::*; -pub use r#type::*; -pub use refresh_service::*; -pub use schema::*; -pub use status::*; -pub use terms_of_use::*; - -/// JSON Credential. -pub type JsonCredential = SpecializedJsonCredential; - -/// Specialized JSON Credential. +use crate::syntax::{ + value_or_array, IdOr, IdentifiedObject, IdentifiedTypedObject, MaybeIdentifiedTypedObject, + RequiredContextList, RequiredType, RequiredTypeSet, TypeSerializationPolicy, Types, +}; + +use super::Context; + +pub const VERIFIABLE_CREDENTIAL_TYPE: &str = "VerifiableCredential"; + +pub struct CredentialType; + +impl RequiredType for CredentialType { + const REQUIRED_TYPE: &'static str = VERIFIABLE_CREDENTIAL_TYPE; +} + +impl TypeSerializationPolicy for CredentialType { + const PREFER_ARRAY: bool = true; +} + +pub type JsonCredentialTypes = Types; + +/// JSON Credential, without required context nor type. +/// +/// If you care about required context and/or type, use the +/// [`SpecializedJsonCredential`] type directly. +pub type JsonCredential = SpecializedJsonCredential; + +/// Specialized JSON Credential with custom required context and type. +/// +/// If you don't care about required context and/or type, you can use the +/// [`JsonCredential`] type alias instead. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(bound( serialize = "S: Serialize", - deserialize = "S: Deserialize<'de>, C: RequiredContextSet, T: RequiredCredentialTypeSet" + deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet" ))] -pub struct SpecializedJsonCredential { +pub struct SpecializedJsonCredential { /// JSON-LD context. #[serde(rename = "@context")] pub context: Context, @@ -59,7 +67,7 @@ pub struct SpecializedJsonCredential { pub credential_subjects: Vec, /// Issuer. - pub issuer: Issuer, + pub issuer: IdOr, /// Issuance date. /// @@ -79,7 +87,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_status: Vec, + pub credential_status: Vec, /// Terms of use. #[serde(rename = "termsOfUse")] @@ -88,7 +96,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub terms_of_use: Vec, + pub terms_of_use: Vec, /// Evidences. #[serde(rename = "evidence")] @@ -97,7 +105,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub evidences: Vec, + pub evidences: Vec, #[serde(rename = "credentialSchema")] #[serde( @@ -105,7 +113,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_schema: Vec, + pub credential_schema: Vec, #[serde(rename = "refreshService")] #[serde( @@ -113,16 +121,17 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub refresh_services: Vec, + pub refresh_services: Vec, #[serde(flatten)] pub additional_properties: BTreeMap, } -impl SpecializedJsonCredential { +impl SpecializedJsonCredential { + /// Creates a new credential. pub fn new( id: Option, - issuer: Issuer, + issuer: IdOr, issuance_date: xsd_types::DateTime, credential_subjects: Vec, ) -> Self { @@ -161,18 +170,24 @@ where E: DateTimeEnvironment, { fn validate(&self, env: &E, _proof: &P) -> ClaimsValidity { - crate::Credential::validate_credential(self, env) + crate::v1::Credential::validate_credential(self, env) } } -impl crate::Credential for SpecializedJsonCredential { +impl crate::MaybeIdentified for SpecializedJsonCredential { + fn id(&self) -> Option<&Uri> { + self.id.as_deref() + } +} + +impl crate::v1::Credential for SpecializedJsonCredential { type Subject = S; - type Issuer = Issuer; - type Status = Status; - type RefreshService = RefreshService; - type TermsOfUse = TermsOfUse; - type Evidence = Evidence; - type Schema = Schema; + type Issuer = IdOr; + type Status = IdentifiedTypedObject; + type RefreshService = IdentifiedTypedObject; + type TermsOfUse = MaybeIdentifiedTypedObject; + type Evidence = MaybeIdentifiedTypedObject; + type Schema = IdentifiedTypedObject; fn id(&self) -> Option<&Uri> { self.id.as_deref() @@ -247,13 +262,3 @@ where json.expand_with(ld, loader).await } } - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ObjectWithId { - pub id: UriBuf, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(flatten)] - pub property_set: Option>, -} diff --git a/crates/claims/crates/vc/src/v1/syntax/mod.rs b/crates/claims/crates/vc/src/v1/syntax/mod.rs new file mode 100644 index 000000000..2d2b58f9c --- /dev/null +++ b/crates/claims/crates/vc/src/v1/syntax/mod.rs @@ -0,0 +1,11 @@ +//! Syntaxes for the VC data model. +//! JSON syntax for Credentials and Presentations. +use super::V1; + +mod credential; +mod presentation; + +pub use credential::*; +pub use presentation::*; + +pub type Context = crate::syntax::Context; diff --git a/crates/claims/crates/vc/src/syntax/json/presentation/mod.rs b/crates/claims/crates/vc/src/v1/syntax/presentation.rs similarity index 86% rename from crates/claims/crates/vc/src/syntax/json/presentation/mod.rs rename to crates/claims/crates/vc/src/v1/syntax/presentation.rs index a24a1f391..b4a40ab6c 100644 --- a/crates/claims/crates/vc/src/syntax/json/presentation/mod.rs +++ b/crates/claims/crates/vc/src/v1/syntax/presentation.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; -use crate::{Context, Credential}; +use crate::syntax::{value_or_array, RequiredType, TypeSerializationPolicy, Types}; +use crate::v1::{Context, Credential}; use iref::{Uri, UriBuf}; use linked_data::{LinkedDataResource, LinkedDataSubject}; use rdf_types::VocabularyMut; @@ -9,10 +10,21 @@ use ssi_claims_core::{ClaimsValidity, Validate}; use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader}; use ssi_rdf::{Interpretation, LdEnvironment}; -use super::{super::value_or_array, SpecializedJsonCredential}; +use super::SpecializedJsonCredential; -mod r#type; -pub use r#type::*; +pub const VERIFIABLE_PRESENTATION_TYPE: &str = "VerifiablePresentation"; + +pub struct PresentationType; + +impl RequiredType for PresentationType { + const REQUIRED_TYPE: &'static str = VERIFIABLE_PRESENTATION_TYPE; +} + +impl TypeSerializationPolicy for PresentationType { + const PREFER_ARRAY: bool = false; +} + +pub type JsonPresentationTypes = Types; /// JSON Presentation. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -95,7 +107,7 @@ impl Validate for JsonPresentation { } } -impl crate::Presentation for JsonPresentation { +impl crate::v1::Presentation for JsonPresentation { /// Verifiable credential type. type Credential = C; diff --git a/crates/claims/crates/vc/src/v2/data_model/credential.rs b/crates/claims/crates/vc/src/v2/data_model/credential.rs new file mode 100644 index 000000000..4bc1a36e1 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/data_model/credential.rs @@ -0,0 +1,216 @@ +use iref::Uri; +use ssi_claims_core::{ClaimsValidity, DateTimeEnvironment, InvalidClaims}; +use xsd_types::DateTimeStamp; + +use super::{InternationalString, RelatedResource}; + +pub use crate::v1::CredentialTypes; +use crate::{Identified, MaybeIdentified, Typed}; + +/// Verifiable Credential. +pub trait Credential: MaybeIdentified { + /// Description type. + type Description: InternationalString; + + /// Credential subject type. + type Subject; + + /// Return type of the [`issuer`](Credential::issuer) method. + /// + /// See: + type Issuer: Identified; + + type Status: MaybeIdentified + Typed; + + type Schema: Identified + Typed; + + type RelatedResource: RelatedResource; + + /// Refresh service. + /// + /// See: + type RefreshService: Typed; + + /// Terms of Use type. + /// + /// Terms of use can be utilized by an issuer or a holder to communicate the + /// terms under which a verifiable credential or verifiable presentation was + /// issued. + /// + /// See: + type TermsOfUse: MaybeIdentified + Typed; + + /// Evidence type. + /// + /// Can be included by an issuer to provide the verifier with additional + /// supporting information in a verifiable credential. + type Evidence: MaybeIdentified + Typed; + + /// Identifier. + fn id(&self) -> Option<&Uri> { + MaybeIdentified::id(self) + } + + /// Types that are **not** `VerifiableCredential`. + /// + /// Since the `VerifiableCredential` type is *required*, it is omitted from + /// the value returned by this function. If you need to iterate over + /// all the credential types, including `VerifiableCredential`, use the + /// [`Self::types`] method. + fn additional_types(&self) -> &[String] { + &[] + } + + fn types(&self) -> CredentialTypes { + CredentialTypes::from_additional_types(self.additional_types()) + } + + /// Name of the credential. + /// + /// Ideally, the name of a credential is concise, human-readable, and could + /// enable an individual to quickly differentiate one credential from any + /// other credentials that they might hold. + /// + /// See: + fn name(&self) -> Option<&str> { + None + } + + /// Details about the credential. + /// + /// Ideally, the description of a credential is no more than a few sentences + /// in length and conveys enough information about the credential to remind + /// an individual of its contents without their having to look through the + /// entirety of the claims. + /// + /// See: + fn description(&self) -> Option<&Self::Description> { + None + } + + /// Credential subject. + /// + /// See: + fn credential_subjects(&self) -> &[Self::Subject] { + &[] + } + + /// Issuer. + /// + /// It is *recommended* that the URL be one which, if dereferenced, results + /// in a controller document, as defined in [VC-DATA-INTEGRITY] or + /// [VC-JOSE-COSE], about the issuer that can be used to verify the + /// information expressed in the credential. + /// + /// See: + /// + /// [VC-DATA-INTEGRITY]: + /// [VC-JOSE-COSE]: + fn issuer(&self) -> &Self::Issuer; + + /// Date and time the credential becomes valid. + /// + /// Could be a date and time in the future or in the past. + /// Note that this value represents the earliest point in time at which the + /// information associated with the + /// [`credential_subject`](Credential::credential_subjects) property becomes + /// valid. + /// + /// If a [`valid_until`](Credential::valid_until) value also exists, the + /// [`valid_from`](Credential::valid_from) value *must* express a datetime + /// that is temporally the same or earlier than the datetime expressed by + /// the `valid_until` value. + /// + /// See: + fn valid_from(&self) -> Option { + None + } + + /// Date and time the credential ceases to be valid. + /// + /// Could be a date and time in the past or in the future. + /// Note that this value represents the latest point in time at which the + /// information associated with the + /// [`credential_subject`](Credential::credential_subjects) property is + /// valid. + /// + /// If a [`valid_from`](Credential::valid_from) value also exists, the + /// [`valid_until`](Credential::valid_until) value *must* express a datetime + /// that is temporally the same or later than the datetime expressed by the + /// `valid_from` value. + /// + /// See: + fn valid_until(&self) -> Option { + None + } + + /// Credential status. + /// + /// Helps discover information related to the status of the verifiable + /// credential, such as whether it is suspended or revoked. + /// + /// See: + fn credential_status(&self) -> &[Self::Status] { + &[] + } + + /// Data schemas. + /// + /// Data schemas are useful when enforcing a specific structure on a given + /// collection of data. + /// + /// See: + fn credential_schemas(&self) -> &[Self::Schema] { + &[] + } + + /// Integrity metadata about each resource referenced by the verifiable + /// credential. + fn related_resources(&self) -> &[Self::RelatedResource] { + &[] + } + + fn refresh_services(&self) -> &[Self::RefreshService] { + &[] + } + + fn terms_of_use(&self) -> &[Self::TermsOfUse] { + &[] + } + + fn evidences(&self) -> &[Self::Evidence] { + &[] + } + + /// Validates the credential. + /// + /// Validation consists in verifying that the claims themselves are + /// consistent and valid with regard to the verification environment. + /// For instance, checking that a credential's expiration date is not in the + /// past, or the issue date not in the future. + /// + /// Validation may fail even if the credential proof is successfully + /// verified. + fn validate_credential(&self, env: &E) -> ClaimsValidity + where + E: DateTimeEnvironment, + { + let now = env.date_time(); + + if let Some(valid_from) = self.valid_from().map(Into::into) { + if valid_from > now { + // Credential is issued in the future! + return Err(InvalidClaims::Premature { now, valid_from }); + } + } + + if let Some(valid_until) = self.valid_until().map(Into::into) { + if now >= valid_until { + // Credential has expired. + return Err(InvalidClaims::Expired { now, valid_until }); + } + } + + Ok(()) + } +} diff --git a/crates/claims/crates/vc/src/v2/data_model/language.rs b/crates/claims/crates/vc/src/v2/data_model/language.rs new file mode 100644 index 000000000..3e87d08dd --- /dev/null +++ b/crates/claims/crates/vc/src/v2/data_model/language.rs @@ -0,0 +1,27 @@ +use ssi_json_ld::{syntax::LangTag, Direction, LangString, LenientLangTag}; + +pub struct LanguageValue<'a> { + pub value: &'a str, + pub language: Option<&'a LangTag>, + pub direction: Option, +} + +impl<'a> From<&'a LangString> for LanguageValue<'a> { + fn from(value: &'a LangString) -> Self { + LanguageValue { + value: value.as_str(), + language: value.language().and_then(LenientLangTag::as_well_formed), + direction: value.direction(), + } + } +} + +pub trait InternationalString { + fn default_value(&self) -> Option; +} + +impl InternationalString for std::convert::Infallible { + fn default_value(&self) -> Option { + unreachable!() + } +} diff --git a/crates/claims/crates/vc/src/v2/data_model/mod.rs b/crates/claims/crates/vc/src/v2/data_model/mod.rs new file mode 100644 index 000000000..38b738859 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/data_model/mod.rs @@ -0,0 +1,11 @@ +mod language; +pub use language::*; + +mod related_resource; +pub use related_resource::*; + +mod credential; +pub use credential::*; + +mod presentation; +pub use presentation::*; diff --git a/crates/claims/crates/vc/src/v2/data_model/presentation.rs b/crates/claims/crates/vc/src/v2/data_model/presentation.rs new file mode 100644 index 000000000..1491b9416 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/data_model/presentation.rs @@ -0,0 +1,37 @@ +use iref::Uri; + +use super::Credential; + +pub use crate::v1::PresentationTypes; +use crate::{Identified, MaybeIdentified}; + +/// Verifiable Presentation. +pub trait Presentation: MaybeIdentified { + /// Verifiable credential type. + type Credential: Credential; + + /// Holder. + type Holder: Identified; + + /// Identifier. + fn id(&self) -> Option<&Uri> { + MaybeIdentified::id(self) + } + + /// Types, without the `VerifiablePresentation` type. + fn additional_types(&self) -> &[String] { + &[] + } + + fn types(&self) -> PresentationTypes { + PresentationTypes::from_additional_types(self.additional_types()) + } + + fn verifiable_credentials(&self) -> &[Self::Credential] { + &[] + } + + fn holders(&self) -> &[Self::Holder] { + &[] + } +} diff --git a/crates/claims/crates/vc/src/v2/data_model/related_resource.rs b/crates/claims/crates/vc/src/v2/data_model/related_resource.rs new file mode 100644 index 000000000..3e9064110 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/data_model/related_resource.rs @@ -0,0 +1,13 @@ +use crate::Identified; + +/// Integrity metadata about each resource referenced by the verifiable +/// credential. +pub trait RelatedResource: Identified { + fn digest_sri(&self) -> &str; +} + +impl RelatedResource for std::convert::Infallible { + fn digest_sri(&self) -> &str { + unreachable!() + } +} diff --git a/crates/claims/crates/vc/src/v2/mod.rs b/crates/claims/crates/vc/src/v2/mod.rs new file mode 100644 index 000000000..c4c6f73ff --- /dev/null +++ b/crates/claims/crates/vc/src/v2/mod.rs @@ -0,0 +1,24 @@ +//! Verifiable Credentials Data Model v2.0 +//! +//! See: +use iref::Iri; + +use crate::syntax::RequiredContext; + +mod data_model; +pub mod syntax; + +pub use data_model::*; +pub use syntax::{Context, JsonCredential, JsonCredentialTypes, SpecializedJsonCredential}; + +/// JSON-LD context IRI. +pub const CREDENTIALS_V2_CONTEXT_IRI: &Iri = + static_iref::iri!("https://www.w3.org/ns/credentials/v2"); + +/// JSON-LD context. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct V2; + +impl RequiredContext for V2 { + const CONTEXT_IRI: &'static Iri = CREDENTIALS_V2_CONTEXT_IRI; +} diff --git a/crates/claims/crates/vc/src/v2/syntax/credential.rs b/crates/claims/crates/vc/src/v2/syntax/credential.rs new file mode 100644 index 000000000..51da6c4e8 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/syntax/credential.rs @@ -0,0 +1,246 @@ +use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; + +use super::{Context, InternationalString, RelatedResource}; +use crate::syntax::{ + value_or_array, IdOr, IdentifiedObject, IdentifiedTypedObject, MaybeIdentifiedTypedObject, + RequiredContextList, RequiredTypeSet, TypedObject, +}; +use iref::{Uri, UriBuf}; +use rdf_types::VocabularyMut; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::{ClaimsValidity, DateTimeEnvironment, Validate}; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject}; +use xsd_types::DateTimeStamp; + +pub use crate::v1::syntax::{CredentialType, JsonCredentialTypes, VERIFIABLE_CREDENTIAL_TYPE}; + +/// JSON Credential, without required context nor type. +/// +/// If you care about required context and/or type, use the +/// [`SpecializedJsonCredential`] type directly. +pub type JsonCredential = SpecializedJsonCredential; + +/// Specialized JSON Credential with custom required context and type. +/// +/// If you don't care about required context and/or type, you can use the +/// [`JsonCredential`] type alias instead. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound( + serialize = "S: Serialize", + deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet" +))] +pub struct SpecializedJsonCredential { + /// JSON-LD context. + #[serde(rename = "@context")] + pub context: Context, + + /// Credential identifier. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + /// Credential type. + #[serde(rename = "type")] + pub types: JsonCredentialTypes, + + /// Credential subjects. + #[serde(rename = "credentialSubject")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub credential_subjects: Vec, + + /// Issuer. + pub issuer: IdOr, + + /// Issuance date. + #[serde(rename = "validFrom")] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub valid_from: Option, + + /// Expiration date. + #[serde(rename = "validUntil")] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub valid_until: Option, + + /// Credential status. + #[serde(rename = "credentialStatus")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub credential_status: Vec, + + /// Terms of use. + #[serde(rename = "termsOfUse")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub terms_of_use: Vec, + + /// Evidences. + #[serde(rename = "evidence")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub evidences: Vec, + + #[serde(rename = "credentialSchema")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub credential_schema: Vec, + + #[serde(rename = "refreshService")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub refresh_services: Vec, + + #[serde(flatten)] + pub extra_properties: BTreeMap, +} + +impl SpecializedJsonCredential { + /// Creates a new credential. + pub fn new( + id: Option, + issuer: IdOr, + credential_subjects: Vec, + ) -> Self { + Self { + context: Context::default(), + id, + types: JsonCredentialTypes::default(), + issuer, + credential_subjects, + valid_from: None, + valid_until: None, + credential_status: Vec::new(), + terms_of_use: Vec::new(), + evidences: Vec::new(), + credential_schema: Vec::new(), + refresh_services: Vec::new(), + extra_properties: BTreeMap::new(), + } + } +} + +impl JsonLdObject for SpecializedJsonCredential { + fn json_ld_context(&self) -> Option> { + Some(Cow::Borrowed(self.context.as_ref())) + } +} + +impl JsonLdNodeObject for SpecializedJsonCredential { + fn json_ld_type(&self) -> JsonLdTypes { + self.types.to_json_ld_types() + } +} + +impl Validate for SpecializedJsonCredential +where + E: DateTimeEnvironment, +{ + fn validate(&self, env: &E, _proof: &P) -> ClaimsValidity { + crate::v2::Credential::validate_credential(self, env) + } +} + +impl crate::MaybeIdentified for SpecializedJsonCredential { + fn id(&self) -> Option<&Uri> { + self.id.as_deref() + } +} + +impl crate::v2::Credential for SpecializedJsonCredential { + type Subject = S; + type Description = InternationalString; + type Issuer = IdOr; + type Status = MaybeIdentifiedTypedObject; + type RefreshService = TypedObject; + type TermsOfUse = MaybeIdentifiedTypedObject; + type Evidence = MaybeIdentifiedTypedObject; + type Schema = IdentifiedTypedObject; + type RelatedResource = RelatedResource; + + fn additional_types(&self) -> &[String] { + self.types.additional_types() + } + + fn credential_subjects(&self) -> &[Self::Subject] { + &self.credential_subjects + } + + fn issuer(&self) -> &Self::Issuer { + &self.issuer + } + + fn valid_from(&self) -> Option { + self.valid_from + } + + fn valid_until(&self) -> Option { + self.valid_until + } + + fn credential_status(&self) -> &[Self::Status] { + &self.credential_status + } + + fn refresh_services(&self) -> &[Self::RefreshService] { + &self.refresh_services + } + + fn terms_of_use(&self) -> &[Self::TermsOfUse] { + &self.terms_of_use + } + + fn evidences(&self) -> &[Self::Evidence] { + &self.evidences + } + + fn credential_schemas(&self) -> &[Self::Schema] { + &self.credential_schema + } +} + +impl ssi_json_ld::Expandable for SpecializedJsonCredential +where + S: Serialize, +{ + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand_with(ld, loader).await + } +} diff --git a/crates/claims/crates/vc/src/v2/syntax/language.rs b/crates/claims/crates/vc/src/v2/syntax/language.rs new file mode 100644 index 000000000..0d4404ca4 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/syntax/language.rs @@ -0,0 +1,23 @@ +use crate::v2::LanguageValue; +use ssi_json_ld::LangString; + +/// International string. +pub enum InternationalString { + String(String), + LanguageValue(LangString), + LanguageMap(Vec), +} + +impl crate::v2::InternationalString for InternationalString { + fn default_value(&self) -> Option { + match self { + Self::String(s) => Some(LanguageValue { + value: s, + language: None, + direction: None, + }), + Self::LanguageValue(l) => Some(l.into()), + Self::LanguageMap(_) => None, + } + } +} diff --git a/crates/claims/crates/vc/src/v2/syntax/mod.rs b/crates/claims/crates/vc/src/v2/syntax/mod.rs new file mode 100644 index 000000000..945124901 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/syntax/mod.rs @@ -0,0 +1,15 @@ +mod language; +pub use language::*; + +mod related_resource; +pub use related_resource::*; + +mod credential; +pub use credential::*; + +mod presentation; +pub use presentation::*; + +use super::V2; + +pub type Context = crate::syntax::Context; diff --git a/crates/claims/crates/vc/src/v2/syntax/presentation.rs b/crates/claims/crates/vc/src/v2/syntax/presentation.rs new file mode 100644 index 000000000..5a5286cea --- /dev/null +++ b/crates/claims/crates/vc/src/v2/syntax/presentation.rs @@ -0,0 +1,160 @@ +use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; + +use crate::syntax::{value_or_array, IdOr, IdentifiedObject}; +use crate::v2::{Context, Credential}; +use iref::{Uri, UriBuf}; +use rdf_types::VocabularyMut; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::{ClaimsValidity, Validate}; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject}; + +use super::JsonCredential; + +pub use crate::v1::syntax::{ + JsonPresentationTypes, PresentationType, VERIFIABLE_PRESENTATION_TYPE, +}; + +/// JSON Presentation. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound( + serialize = "C: serde::Serialize", + deserialize = "C: serde::Deserialize<'de>" +))] +pub struct JsonPresentation { + /// JSON-LD context. + #[serde(rename = "@context")] + pub context: Context, + + /// Presentation identifier. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + + /// Presentation type. + #[serde(rename = "type")] + pub types: JsonPresentationTypes, + + /// Holders. + #[serde(rename = "holder")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub holders: Vec>, + + /// Verifiable credentials. + #[serde(rename = "verifiableCredential")] + #[serde( + with = "value_or_array", + default, + skip_serializing_if = "Vec::is_empty" + )] + pub verifiable_credentials: Vec, + + #[serde(flatten)] + pub additional_properties: BTreeMap, +} + +impl Default for JsonPresentation { + fn default() -> Self { + Self { + context: Context::default(), + id: None, + types: JsonPresentationTypes::default(), + verifiable_credentials: Vec::new(), + holders: Vec::new(), + additional_properties: BTreeMap::new(), + } + } +} + +impl JsonPresentation { + pub fn new( + id: Option, + holders: Vec>, + verifiable_credentials: Vec, + ) -> Self { + Self { + context: Context::default(), + id, + types: JsonPresentationTypes::default(), + holders, + verifiable_credentials, + additional_properties: BTreeMap::new(), + } + } +} + +impl JsonLdObject for JsonPresentation { + fn json_ld_context(&self) -> Option> { + Some(Cow::Borrowed(self.context.as_ref())) + } +} + +impl JsonLdNodeObject for JsonPresentation { + fn json_ld_type(&self) -> JsonLdTypes { + self.types.to_json_ld_types() + } +} + +impl Validate for JsonPresentation { + fn validate(&self, _: &E, _: &P) -> ClaimsValidity { + Ok(()) + } +} + +impl crate::MaybeIdentified for JsonPresentation { + fn id(&self) -> Option<&Uri> { + self.id.as_deref() + } +} + +impl crate::v2::Presentation for JsonPresentation { + /// Verifiable credential type. + type Credential = C; + + type Holder = IdOr; + + /// Types, without the `VerifiablePresentation` type. + fn additional_types(&self) -> &[String] { + self.types.additional_types() + } + + fn verifiable_credentials(&self) -> &[Self::Credential] { + &self.verifiable_credentials + } + + fn holders(&self) -> &[Self::Holder] { + &self.holders + } +} + +impl ssi_json_ld::Expandable for JsonPresentation +where + C: Serialize, +{ + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand_with(ld, loader).await + } +} diff --git a/crates/claims/crates/vc/src/v2/syntax/related_resource.rs b/crates/claims/crates/vc/src/v2/syntax/related_resource.rs new file mode 100644 index 000000000..21b7d42a5 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/syntax/related_resource.rs @@ -0,0 +1,21 @@ +use iref::{Uri, UriBuf}; + +/// Integrity metadata about each resource referenced by the verifiable +/// credential. +pub struct RelatedResource { + pub id: UriBuf, + + pub digest_sri: String, +} + +impl crate::Identified for RelatedResource { + fn id(&self) -> &Uri { + &self.id + } +} + +impl crate::v2::RelatedResource for RelatedResource { + fn digest_sri(&self) -> &str { + &self.digest_sri + } +} diff --git a/crates/claims/crates/vc/src/verification.rs b/crates/claims/crates/vc/src/verification.rs deleted file mode 100644 index 4ff201578..000000000 --- a/crates/claims/crates/vc/src/verification.rs +++ /dev/null @@ -1,100 +0,0 @@ -// use std::{marker::PhantomData, ops::Deref}; - -// use educe::Educe; -// use linked_data::{LinkedData, LinkedDataGraph, LinkedDataPredicateObjects, LinkedDataSubject}; -// use rdf_types::{Interpretation, Vocabulary}; -// use ssi_claims_core::{Verifiable, VerifiableWith}; - -// use crate::{Credential, Presentation, Validate, VerifiableClaims}; - -// impl ssi_claims_core::Provable for Claims -// where -// P: ProofType, -// { -// type Proof = Vec; -// } - -// impl VerifiableWith for Claims -// where -// T: Validate, -// P: ProofType, -// P::Prepared: VerifyPreparedWith, -// { -// type Error = >::Error; - -// async fn verify_with<'a>( -// &'a self, -// verifier: &'a V, -// proof: &'a Self::Proof, -// ) -> Result { -// if !self.is_valid() { -// // The proof is invalidated by impossible claims. -// return Ok(ssi_claims_core::ProofValidity::Invalid); -// } - -// if proof.is_empty() { -// // No proof means no valid proof. -// return Ok(ssi_claims_core::ProofValidity::Invalid); -// } - -// for p in proof { -// if p.verify_prepared_with(verifier).await?.is_invalid() { -// return Ok(ssi_claims_core::ProofValidity::Invalid); -// } -// } - -// Ok(ssi_claims_core::ProofValidity::Valid) -// } -// } - -// impl, P> LinkedDataSubject -// for Claims -// { -// fn visit_subject(&self, serializer: S) -> Result -// where -// S: linked_data::SubjectVisitor, -// { -// self.value.visit_subject(serializer) -// } -// } - -// impl, P> -// LinkedDataPredicateObjects for Claims -// { -// fn visit_objects(&self, visitor: S) -> Result -// where -// S: linked_data::PredicateObjectsVisitor, -// { -// self.value.visit_objects(visitor) -// } -// } - -// impl, P> LinkedDataGraph -// for Claims -// { -// fn visit_graph(&self, visitor: S) -> Result -// where -// S: linked_data::GraphVisitor, -// { -// self.value.visit_graph(visitor) -// } -// } - -// impl, P> LinkedData for Claims { -// fn visit(&self, visitor: S) -> Result -// where -// S: linked_data::Visitor, -// { -// self.value.visit(visitor) -// } -// } - -// pub trait VerifyPreparedWith { -// type Error; - -// #[allow(async_fn_in_trait)] -// async fn verify_prepared_with<'a>( -// &'a self, -// verifier: &'a V, -// ) -> Result; -// } diff --git a/crates/claims/crates/vc/src/vocab.rs b/crates/claims/crates/vc/src/vocab.rs deleted file mode 100644 index 8329365c4..000000000 --- a/crates/claims/crates/vc/src/vocab.rs +++ /dev/null @@ -1,7 +0,0 @@ -use iref::Iri; -use static_iref::iri; - -pub const VERIFIABLE_CREDENTIAL: &Iri = - iri!("https://www.w3.org/2018/credentials#VerifiableCredential"); - -pub const PROOF: &Iri = iri!("https://w3id.org/security#proof"); diff --git a/crates/claims/src/lib.rs b/crates/claims/src/lib.rs index f46bd108b..eca914aa9 100644 --- a/crates/claims/src/lib.rs +++ b/crates/claims/src/lib.rs @@ -31,11 +31,6 @@ pub use ssi_sd_jwt as sd_jwt; /// See: pub use ssi_vc as vc; -pub use vc::{ - Credential, JsonCredential, JsonPresentation, Presentation, SpecializedJsonCredential, - VerifiableCredential, VerifiablePresentation, -}; - /// Data-Integrity Proofs. /// /// See: @@ -54,7 +49,7 @@ pub use ssi_data_integrity as data_integrity; #[educe(Debug(bound("S: DebugCryptographicSuite")))] pub enum JsonCredentialOrJws { /// JSON-like verifiable credential. - Credential(DataIntegrity), + Credential(DataIntegrity), /// JSON Web Signature. Jws(jws::CompactJWSString), @@ -73,7 +68,7 @@ pub enum JsonCredentialOrJws { #[educe(Debug(bound("S: DebugCryptographicSuite")))] pub enum JsonPresentationOrJws { /// JSON-like verifiable presentation. - Presentation(DataIntegrity), + Presentation(DataIntegrity), /// JSON Web Signature. Jws(jws::CompactJWSString), diff --git a/crates/core/src/one_or_many.rs b/crates/core/src/one_or_many.rs index f53d30679..e2ae5fcf1 100644 --- a/crates/core/src/one_or_many.rs +++ b/crates/core/src/one_or_many.rs @@ -56,6 +56,13 @@ impl OneOrMany { } } + pub fn as_slice(&self) -> &[T] { + match self { + Self::One(t) => std::slice::from_ref(t), + Self::Many(l) => l.as_slice(), + } + } + pub fn first(&self) -> Option<&T> { match self { Self::One(value) => Some(value), diff --git a/crates/dids/methods/ethr/src/lib.rs b/crates/dids/methods/ethr/src/lib.rs index b06af66d1..b70ac65fd 100644 --- a/crates/dids/methods/ethr/src/lib.rs +++ b/crates/dids/methods/ethr/src/lib.rs @@ -380,7 +380,7 @@ mod tests { signing::AlterSignature, AnyInputSuiteOptions, AnySuite, CryptographicSuite, ProofOptions, }, - vc::{JsonCredential, JsonPresentation}, + vc::v1::{JsonCredential, JsonPresentation}, VerifiableClaims, }; use ssi_dids_core::{did, DIDResolver}; @@ -592,8 +592,10 @@ mod tests { #[tokio::test] async fn credential_verify_eip712vm() { let didethr = DIDEthr.with_default_options(); - let vc = ssi_claims::vc::any_credential_from_json_str(include_str!("../tests/vc.jsonld")) - .unwrap(); + let vc = ssi_claims::vc::v1::data_integrity::any_credential_from_json_str(include_str!( + "../tests/vc.jsonld" + )) + .unwrap(); // eprintln!("vc {:?}", vc); assert!(vc.verify(&didethr).await.unwrap().is_ok()) } diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 0fe6cb077..5a91ceb32 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -449,7 +449,7 @@ mod tests { use ssi_claims::{ data_integrity::{AnyInputSuiteOptions, AnySuite}, jws::JWSVerifier, - vc::JsonCredential, + vc::v1::JsonCredential, VerifiableClaims, }; use ssi_data_integrity::{CryptographicSuite, ProofOptions as SuiteOptions}; diff --git a/crates/dids/methods/pkh/src/lib.rs b/crates/dids/methods/pkh/src/lib.rs index ab4dc2594..ab1547264 100644 --- a/crates/dids/methods/pkh/src/lib.rs +++ b/crates/dids/methods/pkh/src/lib.rs @@ -955,7 +955,7 @@ mod tests { data_integrity::{ signing::AlterSignature, AnyInputSuiteOptions, CryptographicSuite, ProofOptions, }, - vc::{JsonCredential, JsonPresentation}, + vc::v1::{JsonCredential, JsonPresentation}, }; use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner}; use static_iref::uri; @@ -998,6 +998,7 @@ mod tests { let signer = SingleSecretSigner::new(key.clone()).into_local(); eprintln!("key: {key}"); eprintln!("suite: {proof_suite:?}"); + println!("cred: {}", serde_json::to_string_pretty(&cred).unwrap()); let vc = proof_suite .sign(cred.clone(), &didpkh, &signer, issue_options.clone()) .await @@ -1082,8 +1083,7 @@ mod tests { data_integrity::{ signing::AlterSignature, AnyInputSuiteOptions, CryptographicSuite, ProofOptions, }, - vc::{JsonCredential, JsonPresentation}, - VerifiableClaims, + vc::v1::{JsonCredential, JsonPresentation}, }; use ssi_verification_methods_core::{ProofPurpose, SingleSecretSigner}; use static_iref::uri; @@ -1503,7 +1503,7 @@ mod tests { eprintln!("test verify vc `{name}`"); eprintln!("input: {vc_str}"); - let vc = ssi_claims::vc::any_credential_from_json_str(vc_str).unwrap(); + let vc = ssi_claims::vc::v1::data_integrity::any_credential_from_json_str(vc_str).unwrap(); let didpkh = VerificationMethodDIDResolver::new(DIDPKH); let verification_result = vc.verify(&didpkh).await.unwrap(); diff --git a/crates/dids/methods/tz/tests/did.rs b/crates/dids/methods/tz/tests/did.rs index 9baa24753..30c1a5e69 100644 --- a/crates/dids/methods/tz/tests/did.rs +++ b/crates/dids/methods/tz/tests/did.rs @@ -7,7 +7,7 @@ use ssi_claims::{ signing::AlterSignature, AnyInputSuiteOptions, AnySuite, CryptographicSuite, DataIntegrity, ProofOptions as SuiteOptions, }, - vc::{JsonCredential, JsonPresentation}, + vc::v1::{JsonCredential, JsonPresentation}, VerifiableClaims, }; use ssi_dids_core::{did, resolution::Options, DIDResolver, VerificationMethodDIDResolver}; diff --git a/crates/dids/methods/web/src/lib.rs b/crates/dids/methods/web/src/lib.rs index 8eda6220e..05fcd0e8e 100644 --- a/crates/dids/methods/web/src/lib.rs +++ b/crates/dids/methods/web/src/lib.rs @@ -138,7 +138,7 @@ impl DIDMethodResolver for DIDWeb { mod tests { use ssi_claims::{ data_integrity::{AnySuite, CryptographicSuite, ProofOptions}, - vc::JsonCredential, + vc::v1::JsonCredential, VerifiableClaims, }; use ssi_dids_core::{did, DIDResolver, Document, VerificationMethodDIDResolver}; diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index f3257dbed..16b3ed50d 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -6,12 +6,16 @@ use std::{borrow::Cow, hash::Hash}; pub use context::*; use json_ld::Expand; use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; -use rdf_types::interpretation::WithGenerator; -use rdf_types::{Vocabulary, VocabularyMut}; pub use json_ld; -pub use json_ld::{syntax, ExpandedDocument, Id, LoadError, Loader, Nullable, ToRdfError}; -use ssi_rdf::{generator, Interpretation, LdEnvironment}; +pub use json_ld::{ + syntax, Direction, ExpandedDocument, Id, LangString, LenientLangTag, LoadError, Loader, + Nullable, ToRdfError, +}; +use ssi_rdf::{ + generator, interpretation::WithGenerator, Interpretation, LdEnvironment, Vocabulary, + VocabularyMut, +}; /// Environment that provides a JSON-LD context loader. pub trait ContextLoaderEnvironment { diff --git a/crates/rdf/src/lib.rs b/crates/rdf/src/lib.rs index 5fc6fca59..d7c6b03ed 100644 --- a/crates/rdf/src/lib.rs +++ b/crates/rdf/src/lib.rs @@ -13,8 +13,8 @@ use rdf_types::{ }; pub use rdf_types::{ - generator, Interpretation, InterpretationMut, LexicalQuad, LexicalQuadRef, Vocabulary, - VocabularyMut, + generator, interpretation, vocabulary, Interpretation, InterpretationMut, LexicalQuad, + LexicalQuadRef, Vocabulary, VocabularyMut, }; pub use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 7099bb319..3b658fba9 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -90,9 +90,9 @@ impl Multikey { let algorithm = self .public_key .decoded - .algorithm + .get_algorithm() .ok_or(MessageSignatureError::MissingAlgorithm)?; - let key_algorithm = secret_key.algorithm.unwrap_or(algorithm); + let key_algorithm = secret_key.get_algorithm().unwrap_or(algorithm); if !algorithm.is_compatible_with(key_algorithm) { return Err(MessageSignatureError::InvalidSecretKey); } @@ -108,7 +108,7 @@ impl Multikey { let algorithm = self .public_key .decoded - .algorithm + .get_algorithm() .ok_or(ProofValidationError::MissingAlgorithm)?; Ok(ssi_jws::verify_bytes( algorithm, diff --git a/crates/zcap-ld/src/lib.rs b/crates/zcap-ld/src/lib.rs index 8c9e48871..cdc2dde04 100644 --- a/crates/zcap-ld/src/lib.rs +++ b/crates/zcap-ld/src/lib.rs @@ -16,9 +16,9 @@ use ssi_claims::{ AnyDataIntegrity, AnyProofs, AnySignatureAlgorithm, AnySuite, CryptographicSuite, DataIntegrity, Proof, Proofs, }, - vc::{Context, RequiredContext}, - ClaimsValidity, DateTimeEnvironment, Eip712TypesEnvironment, InvalidClaims, SignatureError, - Validate, VerificationEnvironment, + vc::syntax::{Context, RequiredContext}, + ClaimsValidity, DateTimeEnvironment, Eip712TypesEnvironment, InvalidClaims, + SignatureEnvironment, SignatureError, Validate, }; use ssi_json_ld::{ContextLoaderEnvironment, JsonLdError, JsonLdNodeObject, JsonLdObject, Loader}; use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject}; @@ -147,7 +147,7 @@ impl Delegation { { self.sign_with( suite, - VerificationEnvironment::default(), + SignatureEnvironment::default(), resolver, signer, proof_configuration, @@ -429,7 +429,7 @@ where #[cfg(test)] mod tests { use super::*; - use ssi_claims::VerifiableClaims; + use ssi_claims::{VerifiableClaims, VerificationEnvironment}; use ssi_data_integrity::DataIntegrity; use ssi_dids_core::{example::ExampleDIDResolver, VerificationMethodDIDResolver}; use ssi_jwk::JWK; diff --git a/examples/issue-revocation-list.rs b/examples/issue-revocation-list.rs index c4c6b8ddc..6ac5434d5 100644 --- a/examples/issue-revocation-list.rs +++ b/examples/issue-revocation-list.rs @@ -3,7 +3,7 @@ use ssi::{ claims::{ data_integrity::{AnySuite, CryptographicSuite, ProofOptions}, - vc::revocation::{ + vc::v1::revocation::{ RevocationList2020, RevocationList2020Credential, RevocationList2020Subject, }, VerifiableClaims, diff --git a/examples/issue-status-list.rs b/examples/issue-status-list.rs index fbb94c9a2..3ad06aec2 100644 --- a/examples/issue-status-list.rs +++ b/examples/issue-status-list.rs @@ -3,7 +3,7 @@ use ssi::{ claims::{ data_integrity::{AnySuite, CryptographicSuite, ProofOptions}, - vc::revocation::{StatusList2021, StatusList2021Credential, StatusList2021Subject}, + vc::v1::revocation::{StatusList2021, StatusList2021Credential, StatusList2021Subject}, VerifiableClaims, }, jwk::JWK, diff --git a/examples/issue.rs b/examples/issue.rs index c234aa812..b40404f53 100644 --- a/examples/issue.rs +++ b/examples/issue.rs @@ -6,7 +6,7 @@ use serde_json::json; use ssi_claims::{ data_integrity::{AnySuite, CryptographicSuite, ProofOptions}, jws::JWSPayload, - vc::ToJwtClaims, + vc::v1::ToJwtClaims, VerifiableClaims, }; use ssi_dids::DIDResolver; @@ -20,7 +20,7 @@ async fn main() { let resolver = ssi::dids::example::ExampleDIDResolver::default().with_default_options(); let signer = SingleSecretSigner::new(key.clone()).into_local(); - let vc: ssi::claims::vc::SpecializedJsonCredential = serde_json::from_value(json!({ + let vc: ssi::claims::vc::v1::SpecializedJsonCredential = serde_json::from_value(json!({ "@context": ["https://www.w3.org/2018/credentials/v1"], "type": "VerifiableCredential", "issuer": "did:example:foo", diff --git a/examples/present.rs b/examples/present.rs index 7448aa22a..55eba34f5 100644 --- a/examples/present.rs +++ b/examples/present.rs @@ -17,7 +17,10 @@ use ssi::{ }, verification_methods::{ProofPurpose, SingleSecretSigner}, }; -use ssi_claims::{data_integrity::AnyDataIntegrity, vc::ToJwtClaims, JsonCredential}; +use ssi_claims::{ + data_integrity::AnyDataIntegrity, + vc::v1::{SpecializedJsonCredential, ToJwtClaims}, +}; use ssi_dids::DIDResolver; use static_iref::{iri, uri}; @@ -35,7 +38,8 @@ async fn main() { let mut reader = std::io::BufReader::new(std::io::stdin()); let vc = match &proof_format_in[..] { "ldp" => { - let vc_ldp: AnyDataIntegrity = serde_json::from_reader(reader).unwrap(); + let vc_ldp: AnyDataIntegrity = + serde_json::from_reader(reader).unwrap(); ssi::claims::JsonCredentialOrJws::Credential(vc_ldp) } "jwt" => { @@ -53,7 +57,7 @@ async fn main() { format => panic!("unknown input proof format: {}", format), }; - let vp = ssi::claims::vc::JsonPresentation::new( + let vp = ssi::claims::vc::v1::JsonPresentation::new( None, Some(uri!("did:example:foo").to_owned()), vec![vc], diff --git a/examples/vc_parse.rs b/examples/vc_parse.rs index 3d38b43b5..4335ca514 100644 --- a/examples/vc_parse.rs +++ b/examples/vc_parse.rs @@ -1,7 +1,7 @@ //! This example shows how to parse a verifiable credential. use std::fs; -use ssi_claims::{data_integrity::AnyDataIntegrity, JsonCredential}; +use ssi_claims::{data_integrity::AnyDataIntegrity, vc::AnyJsonCredential}; #[async_std::main] async fn main() { @@ -9,12 +9,14 @@ async fn main() { let credential_content = fs::read_to_string("examples/files/vc.jsonld").unwrap(); // Parse the credential into a JSON VC with any Data-Integrity proof. - let vc: AnyDataIntegrity = serde_json::from_str(&credential_content).unwrap(); + let vc: AnyDataIntegrity = + serde_json::from_str(&credential_content).unwrap(); println!("{}", serde_json::to_string_pretty(&vc).unwrap()); - // The above can also be done with the following helper function. - let vc = ssi::claims::vc::any_credential_from_json_str(&credential_content).unwrap(); + // The above can be done with the following helper function. + let vc = ssi::claims::vc::v1::data_integrity::any_credential_from_json_str(&credential_content) + .unwrap(); // Print the same credential. println!("{}", serde_json::to_string_pretty(&vc).unwrap()); diff --git a/examples/vc_verify.rs b/examples/vc_verify.rs index 028db6af7..4e971aa40 100644 --- a/examples/vc_verify.rs +++ b/examples/vc_verify.rs @@ -9,8 +9,9 @@ async fn main() { // Load the credential textual representation from the file system. let credential_content = fs::read_to_string("examples/files/vc.jsonld").unwrap(); - // Parse the VC. - let vc = ssi::claims::vc::any_credential_from_json_str(&credential_content).unwrap(); + // All of the above can be done with the following helper function. + let vc = ssi::claims::vc::v1::data_integrity::any_credential_from_json_str(&credential_content) + .unwrap(); // Prepare our verifier. let verifier = create_verifier(); diff --git a/src/lib.rs b/src/lib.rs index a50c569bd..0d19c1098 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ //! # async fn main() { //! use ssi::prelude::*; //! -//! let vc = any_credential_from_json_str( +//! let vc = ssi::claims::vc::v1::data_integrity::any_credential_from_json_str( //! &std::fs::read_to_string("examples/files/vc.jsonld") //! .expect("unable to load VC") //! ).expect("invalid VC"); @@ -173,7 +173,7 @@ //! email: String //! } //! -//! let credential = SpecializedJsonCredential::::new( +//! let credential = ssi::claims::vc::v1::JsonCredential::::new( //! Some(uri!("https://example.org/#CredentialId").to_owned()), // id //! uri!("https://example.org/#Issuer").to_owned().into(), // issuer //! DateTime::now(), // issuance date diff --git a/src/prelude.rs b/src/prelude.rs index 4f26f05b1..c68b05118 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,13 +1,12 @@ pub use crate::{ claims::{ data_integrity::{ - AnyDataIntegrity, AnySuite, CryptographicSuite, DataIntegrity, ProofConfiguration, - ProofOptions, + AnyDataIntegrity, AnySuite, CryptographicSuite, DataIntegrity, DataIntegrityDocument, + ProofConfiguration, ProofOptions, }, - vc::{any_credential_from_json_slice, any_credential_from_json_str}, + vc::syntax::{AnyJsonCredential, AnyJsonPresentation}, CompactJWS, CompactJWSBuf, CompactJWSStr, CompactJWSString, JWSPayload, JWTClaims, - JsonCredential, JsonPresentation, SpecializedJsonCredential, VerifiableClaims, - VerificationEnvironment, + VerifiableClaims, }, dids::{DIDResolver, DIDJWK}, verification_methods::{AnyJwkMethod, AnyMethod, SingleSecretSigner}, diff --git a/tests/send.rs b/tests/send.rs index b029cff59..75e155e05 100644 --- a/tests/send.rs +++ b/tests/send.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use ssi::{ claims::{ data_integrity::{AnySuite, CryptographicSuite, ProofOptions}, - vc::SpecializedJsonCredential, + vc::v1::JsonCredential, }, dids::{DIDResolver, DIDJWK}, verification_methods::SingleSecretSigner, @@ -23,7 +23,7 @@ fn assert_send(f: impl Send + Future) { #[test] fn data_integrity_sign_is_send() { - let credential = SpecializedJsonCredential::::new( + let credential = JsonCredential::::new( Some(uri!("https://example.org/#CredentialId").to_owned()), // id uri!("https://example.org/#Issuer").to_owned().into(), // issuer xsd_types::DateTime::now(), // issuance date