diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index badd56b4..0e5e54fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,7 +242,7 @@ jobs: - name: Run Rust Tests run: | mkdir -p test-results - cargo nextest run --profile ci --config-file ./nextest.toml + cargo nextest run --profile ci --config-file ./nextest.toml -E 'test(test_vectors::)' - name: Modify testsuite name in XML for test runner consumption run: | sed -i '' 's/&1)" == *"rustup could not choose a version of cargo to run"* ]]; then - rustup default 1.78.0 + rustup default 1.80.0 rustup target add aarch64-apple-darwin fi diff --git a/bindings/web5_uniffi/src/lib.rs b/bindings/web5_uniffi/src/lib.rs index 98b3ee1a..26d98479 100644 --- a/bindings/web5_uniffi/src/lib.rs +++ b/bindings/web5_uniffi/src/lib.rs @@ -1,9 +1,7 @@ use web5_uniffi_wrapper::{ credentials::{ presentation_definition::PresentationDefinition, - verifiable_credential_1_1::{ - data::VerifiableCredential as VerifiableCredentialData, VerifiableCredential, - }, + verifiable_credential_1_1::{VerifiableCredential, VerifiableCredentialData}, }, crypto::{ dsa::{ @@ -25,10 +23,11 @@ use web5_uniffi_wrapper::{ portable_did::PortableDid, resolution::resolution_result::ResolutionResult, }, - errors::RustCoreError, + errors::Web5Error, }; use web5::{ + credentials::verifiable_credential_1_1::VerifiableCredentialCreateOptions as VerifiableCredentialCreateOptionsData, crypto::{dsa::Dsa, jwk::Jwk as JwkData}, dids::{ data_model::{ diff --git a/bindings/web5_uniffi/src/web5.udl b/bindings/web5_uniffi/src/web5.udl index 84594d20..6337b305 100644 --- a/bindings/web5_uniffi/src/web5.udl +++ b/bindings/web5_uniffi/src/web5.udl @@ -2,14 +2,14 @@ namespace web5 { JwkData ed25519_generator_generate(); ResolutionResult did_jwk_resolve([ByRef] string uri); - [Async, Throws=RustCoreError] + [Async, Throws=Web5Error] ResolutionResult did_web_resolve([ByRef] string uri); - [Throws=RustCoreError] + [Throws=Web5Error] ResolutionResult did_dht_resolve([ByRef] string uri); }; [Error] -interface RustCoreError { +interface Web5Error { Error(string type, string variant, string msg); }; @@ -24,15 +24,15 @@ dictionary JwkData { [Trait, WithForeign] interface KeyManager { - [Throws=RustCoreError] + [Throws=Web5Error] Signer get_signer(JwkData public_jwk); }; interface InMemoryKeyManager { constructor(); - [Throws=RustCoreError] + [Throws=Web5Error] Signer get_signer(JwkData public_jwk); - [Throws=RustCoreError] + [Throws=Web5Error] JwkData import_private_jwk(JwkData private_key); KeyManager get_as_key_manager(); }; @@ -43,25 +43,25 @@ enum Dsa { [Trait, WithForeign] interface Signer { - [Throws=RustCoreError] + [Throws=Web5Error] bytes sign(bytes payload); }; [Trait, WithForeign] interface Verifier { - [Throws=RustCoreError] + [Throws=Web5Error] boolean verify(bytes message, bytes signature); }; interface Ed25519Signer { constructor(JwkData private_key); - [Throws=RustCoreError] + [Throws=Web5Error] bytes sign(bytes payload); }; interface Ed25519Verifier { constructor(JwkData public_jwk); - [Throws=RustCoreError] + [Throws=Web5Error] boolean verify(bytes message, bytes signature); }; @@ -77,7 +77,7 @@ dictionary DidData { }; interface Did { - [Throws=RustCoreError] + [Throws=Web5Error] constructor([ByRef] string uri); DidData get_data(); }; @@ -112,7 +112,7 @@ dictionary ServiceData { interface Document { constructor(DocumentData data); DocumentData get_data(); - [Throws=RustCoreError] + [Throws=Web5Error] JwkData find_public_key_jwk(string key_id); }; @@ -159,9 +159,9 @@ dictionary DidJwkData { }; interface DidJwk { - [Name=from_public_jwk, Throws=RustCoreError] + [Name=from_public_jwk, Throws=Web5Error] constructor(JwkData public_jwk); - [Name=from_uri, Throws=RustCoreError] + [Name=from_uri, Throws=Web5Error] constructor([ByRef] string uri); DidJwkData get_data(); }; @@ -172,9 +172,9 @@ dictionary DidWebData { }; interface DidWeb { - [Name=from_public_jwk, Throws=RustCoreError] + [Name=from_public_jwk, Throws=Web5Error] constructor([ByRef] string domain, JwkData public_jwk); - [Async, Name=from_uri, Throws=RustCoreError] + [Async, Name=from_uri, Throws=Web5Error] constructor([ByRef] string uri); DidWebData get_data(); }; @@ -185,13 +185,13 @@ dictionary DidDhtData { }; interface DidDht { - [Name=from_identity_key, Throws=RustCoreError] + [Name=from_identity_key, Throws=Web5Error] constructor(JwkData identity_key); - [Name=from_uri, Throws=RustCoreError] + [Name=from_uri, Throws=Web5Error] constructor([ByRef] string uri); - [Throws=RustCoreError] + [Throws=Web5Error] void publish(Signer signer); - [Throws=RustCoreError] + [Throws=Web5Error] void deactivate(Signer signer); DidDhtData get_data(); }; @@ -203,7 +203,7 @@ dictionary PortableDidData { }; interface PortableDid { - [Throws=RustCoreError] + [Throws=Web5Error] constructor([ByRef] string json); PortableDidData get_data(); }; @@ -215,45 +215,52 @@ dictionary BearerDidData { }; interface BearerDid { - [Throws=RustCoreError] + [Throws=Web5Error] constructor([ByRef] string uri, KeyManager key_manager); - [Throws=RustCoreError, Name=from_portable_did] + [Throws=Web5Error, Name=from_portable_did] constructor(PortableDid portable_did); BearerDidData get_data(); - [Throws=RustCoreError] + [Throws=Web5Error] Signer get_signer(string key_id); }; -dictionary VerifiableCredentialData { - sequence context; - string id; - sequence type; - string json_serialized_issuer; - timestamp issuance_date; +interface PresentationDefinition { + [Throws=Web5Error] + constructor(string json_serialized_presentation_definition); + [Throws=Web5Error] + string get_json_serialized_presentation_definition(); + [Throws=Web5Error] + sequence select_credentials([ByRef] sequence vc_jwts); +}; + + + + + +dictionary VerifiableCredentialCreateOptionsData { + string? id; + sequence? context; + sequence? type; + timestamp? issuance_date; timestamp? expiration_date; - string json_serialized_credential_subject; }; interface VerifiableCredential { - [Throws=RustCoreError] - constructor(VerifiableCredentialData data); - [Name=verify, Throws=RustCoreError] - constructor([ByRef] string vcjwt); - [Name=verify_with_verifier, Throws=RustCoreError] - constructor([ByRef] string vcjwt, Verifier verifier); - [Throws=RustCoreError] - string sign(BearerDid bearer_did); - [Throws=RustCoreError] - string sign_with_signer([ByRef] string key_id, Signer signer); - [Throws=RustCoreError] + [Throws=Web5Error, Name=create] + constructor( + string json_serialized_issuer, + string json_serialized_credential_subject, + VerifiableCredentialCreateOptionsData? options + ); VerifiableCredentialData get_data(); }; -interface PresentationDefinition { - [Throws=RustCoreError] - constructor(string json_serialized_presentation_definition); - [Throws=RustCoreError] - string get_json_serialized_presentation_definition(); - [Throws=RustCoreError] - sequence select_credentials([ByRef] sequence vc_jwts); +dictionary VerifiableCredentialData { + sequence context; + sequence type; + string id; + string json_serialized_issuer; + string json_serialized_credential_subject; + timestamp issuance_date; + timestamp? expiration_date; }; \ No newline at end of file diff --git a/bindings/web5_uniffi_wrapper/src/credentials/verifiable_credential_1_1.rs b/bindings/web5_uniffi_wrapper/src/credentials/verifiable_credential_1_1.rs index 1d0f869a..0d38cb7e 100644 --- a/bindings/web5_uniffi_wrapper/src/credentials/verifiable_credential_1_1.rs +++ b/bindings/web5_uniffi_wrapper/src/credentials/verifiable_credential_1_1.rs @@ -1,103 +1,58 @@ -use crate::{ - crypto::dsa::{Signer, ToInnerSigner, ToInnerVerifier, Verifier}, - dids::bearer_did::BearerDid, - errors::{Result, RustCoreError}, +use crate::errors::Result; +use std::time::SystemTime; +use web5::{ + credentials::verifiable_credential_1_1::{ + CredentialSubject, Issuer, VerifiableCredential as InnerVerifiableCredential, + VerifiableCredentialCreateOptions, + }, + json::FromJson, }; -use std::sync::{Arc, RwLock}; -use web5::credentials::verifiable_credential_1_1::VerifiableCredential as InnerVerifiableCredential; -pub struct VerifiableCredential(pub Arc>); +pub struct VerifiableCredential { + inner_vc: InnerVerifiableCredential, + json_serialized_issuer: String, + json_serialized_credential_subject: String, +} impl VerifiableCredential { - pub fn new(verifiable_credential: data::VerifiableCredential) -> Result { - let inner_verifiable_credential = verifiable_credential.to_inner()?; - - Ok(Self(Arc::new(RwLock::new(inner_verifiable_credential)))) - } - - pub fn verify(vcjwt: &str) -> Result { - let inner_verifiable_credential = InnerVerifiableCredential::verify(vcjwt)?; - - Ok(Self(Arc::new(RwLock::new(inner_verifiable_credential)))) - } - - pub fn verify_with_verifier(vcjwt: &str, verifier: Arc) -> Result { - let inner_verifier = Arc::new(ToInnerVerifier(verifier)); - let inner_verifiable_credential = - InnerVerifiableCredential::verify_with_verifier(vcjwt, inner_verifier)?; - - Ok(Self(Arc::new(RwLock::new(inner_verifiable_credential)))) - } - - pub fn sign(&self, bearer_did: Arc) -> Result { - let inner_verifiable_credential = self - .0 - .read() - .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; - - Ok(inner_verifiable_credential.sign(&bearer_did.0)?) - } - - pub fn sign_with_signer(&self, key_id: &str, signer: Arc) -> Result { - let inner_verifiable_credential = self - .0 - .read() - .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; - - let inner_signer = Arc::new(ToInnerSigner(signer)); - Ok(inner_verifiable_credential.sign_with_signer(key_id, inner_signer)?) - } - - pub fn get_data(&self) -> Result { - let inner_verifiable_credential = self - .0 - .read() - .map_err(|e| RustCoreError::from_poison_error(e, "RwLockReadError"))?; - - data::VerifiableCredential::from_inner(inner_verifiable_credential.clone()) + pub fn create( + json_serialized_issuer: String, + json_serialized_credential_subject: String, + options: Option, + ) -> Result { + let issuer = Issuer::from_json_string(&json_serialized_issuer)?; + let credential_subject = + CredentialSubject::from_json_string(&json_serialized_credential_subject)?; + + let inner_vc = InnerVerifiableCredential::create(issuer, credential_subject, options)?; + + Ok(Self { + inner_vc, + json_serialized_issuer, + json_serialized_credential_subject, + }) + } + + pub fn get_data(&self) -> VerifiableCredentialData { + VerifiableCredentialData { + context: self.inner_vc.context.clone(), + id: self.inner_vc.id.clone(), + r#type: self.inner_vc.r#type.clone(), + json_serialized_issuer: self.json_serialized_issuer.clone(), + json_serialized_credential_subject: self.json_serialized_credential_subject.clone(), + issuance_date: self.inner_vc.issuance_date, + expiration_date: self.inner_vc.expiration_date, + } } } -pub mod data { - use super::*; - use std::time::SystemTime; - - #[derive(Clone)] - pub struct VerifiableCredential { - pub context: Vec, - pub id: String, - pub r#type: Vec, - pub json_serialized_issuer: String, // JSON serialized - pub issuance_date: SystemTime, - pub expiration_date: Option, - pub json_serialized_credential_subject: String, // JSON serialized - } - - impl VerifiableCredential { - pub fn from_inner(inner_verifiable_credential: InnerVerifiableCredential) -> Result { - Ok(Self { - context: inner_verifiable_credential.context.clone(), - id: inner_verifiable_credential.id.clone(), - r#type: inner_verifiable_credential.r#type.clone(), - json_serialized_issuer: serde_json::to_string(&inner_verifiable_credential.issuer)?, - issuance_date: inner_verifiable_credential.issuance_date, - expiration_date: inner_verifiable_credential.expiration_date, - json_serialized_credential_subject: serde_json::to_string( - &inner_verifiable_credential.credential_subject, - )?, - }) - } - - pub fn to_inner(&self) -> Result { - Ok(InnerVerifiableCredential { - context: self.context.clone(), - id: self.id.clone(), - r#type: self.r#type.clone(), - issuer: serde_json::from_str(&self.json_serialized_issuer)?, - issuance_date: self.issuance_date, - expiration_date: self.expiration_date, - credential_subject: serde_json::from_str(&self.json_serialized_credential_subject)?, - }) - } - } +#[derive(Clone)] +pub struct VerifiableCredentialData { + pub context: Vec, + pub id: String, + pub r#type: Vec, + pub json_serialized_issuer: String, + pub json_serialized_credential_subject: String, + pub issuance_date: SystemTime, + pub expiration_date: Option, } diff --git a/bindings/web5_uniffi_wrapper/src/errors.rs b/bindings/web5_uniffi_wrapper/src/errors.rs index 30cbf152..db489676 100644 --- a/bindings/web5_uniffi_wrapper/src/errors.rs +++ b/bindings/web5_uniffi_wrapper/src/errors.rs @@ -11,9 +11,10 @@ use web5::dids::data_model::DataModelError as DidDataModelError; use web5::dids::did::DidError; use web5::dids::methods::MethodError; use web5::dids::portable_did::PortableDidError; +use web5::errors::Web5Error as InnerWeb5Error; #[derive(Debug, Error)] -pub enum RustCoreError { +pub enum Web5Error { #[error("{msg}")] Error { r#type: String, @@ -22,9 +23,9 @@ pub enum RustCoreError { }, } -impl RustCoreError { +impl Web5Error { pub fn from_poison_error(error: PoisonError, error_type: &str) -> Self { - RustCoreError::Error { + Web5Error::Error { r#type: error_type.to_string(), variant: "PoisonError".to_string(), msg: error.to_string(), @@ -44,7 +45,7 @@ impl RustCoreError { pub fn r#type(&self) -> String { match self { - RustCoreError::Error { + Web5Error::Error { r#type: error_type, .. } => error_type.clone(), } @@ -52,7 +53,7 @@ impl RustCoreError { pub fn variant(&self) -> String { match self { - RustCoreError::Error { + Web5Error::Error { variant: error_variant, .. } => error_variant.clone(), @@ -61,7 +62,7 @@ impl RustCoreError { pub fn msg(&self) -> String { match self { - RustCoreError::Error { msg, .. } => msg.clone(), + Web5Error::Error { msg, .. } => msg.clone(), } } } @@ -79,74 +80,74 @@ where variant_name.to_string() } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: JwkError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: KeyManagerError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: DsaError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: DidError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: PortableDidError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: MethodError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: CredentialError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: PexError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: DidDataModelError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: BearerDidError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for RustCoreError { +impl From for Web5Error { fn from(error: SerdeJsonError) -> Self { - RustCoreError::new(error) + Web5Error::new(error) } } -impl From for KeyManagerError { - fn from(error: RustCoreError) -> Self { +impl From for KeyManagerError { + fn from(error: Web5Error) -> Self { let variant = error.variant(); let msg = error.msg(); @@ -170,8 +171,8 @@ impl From for KeyManagerError { } } -impl From for DsaError { - fn from(error: RustCoreError) -> Self { +impl From for DsaError { + fn from(error: Web5Error) -> Self { let variant = error.variant(); let msg = error.msg(); @@ -199,4 +200,11 @@ impl From for DsaError { } } -pub type Result = std::result::Result; +impl From for Web5Error { + fn from(error: InnerWeb5Error) -> Self { + Web5Error::new(error) + } +} + +pub type Result = std::result::Result; + diff --git a/bound/kt/pom.xml b/bound/kt/pom.xml index e1d62dc8..c271b81a 100644 --- a/bound/kt/pom.xml +++ b/bound/kt/pom.xml @@ -539,6 +539,14 @@ ${kotlin.jvm.target} + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + diff --git a/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt b/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt index a91f2e7f..ad87c9e1 100644 --- a/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt +++ b/bound/kt/src/main/kotlin/web5/sdk/rust/UniFFI.kt @@ -869,14 +869,6 @@ internal open class UniffiVTableCallbackInterfaceVerifier( - - - - - - - - @@ -1046,18 +1038,10 @@ internal interface UniffiLib : Library { ): Pointer fun uniffi_web5_uniffi_fn_free_verifiablecredential(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_web5_uniffi_fn_constructor_verifiablecredential_new(`data`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_web5_uniffi_fn_constructor_verifiablecredential_verify(`vcjwt`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_web5_uniffi_fn_constructor_verifiablecredential_verify_with_verifier(`vcjwt`: RustBuffer.ByValue,`verifier`: Pointer,uniffi_out_err: UniffiRustCallStatus, + fun uniffi_web5_uniffi_fn_constructor_verifiablecredential_create(`jsonSerializedIssuer`: RustBuffer.ByValue,`jsonSerializedCredentialSubject`: RustBuffer.ByValue,`options`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_web5_uniffi_fn_method_verifiablecredential_get_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_web5_uniffi_fn_method_verifiablecredential_sign(`ptr`: Pointer,`bearerDid`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_web5_uniffi_fn_method_verifiablecredential_sign_with_signer(`ptr`: Pointer,`keyId`: RustBuffer.ByValue,`signer`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue fun uniffi_web5_uniffi_fn_clone_verifier(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, ): Pointer fun uniffi_web5_uniffi_fn_free_verifier(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, @@ -1238,10 +1222,6 @@ internal interface UniffiLib : Library { ): Short fun uniffi_web5_uniffi_checksum_method_verifiablecredential_get_data( ): Short - fun uniffi_web5_uniffi_checksum_method_verifiablecredential_sign( - ): Short - fun uniffi_web5_uniffi_checksum_method_verifiablecredential_sign_with_signer( - ): Short fun uniffi_web5_uniffi_checksum_method_verifier_verify( ): Short fun uniffi_web5_uniffi_checksum_constructor_bearerdid_from_portable_did( @@ -1276,11 +1256,7 @@ internal interface UniffiLib : Library { ): Short fun uniffi_web5_uniffi_checksum_constructor_resolutionresult_new( ): Short - fun uniffi_web5_uniffi_checksum_constructor_verifiablecredential_new( - ): Short - fun uniffi_web5_uniffi_checksum_constructor_verifiablecredential_verify( - ): Short - fun uniffi_web5_uniffi_checksum_constructor_verifiablecredential_verify_with_verifier( + fun uniffi_web5_uniffi_checksum_constructor_verifiablecredential_create( ): Short fun ffi_web5_uniffi_uniffi_contract_version( ): Int @@ -1299,13 +1275,13 @@ private fun uniffiCheckContractApiVersion(lib: UniffiLib) { @Suppress("UNUSED_PARAMETER") private fun uniffiCheckApiChecksums(lib: UniffiLib) { - if (lib.uniffi_web5_uniffi_checksum_func_did_dht_resolve() != 52564.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_func_did_dht_resolve() != 54117.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_func_did_jwk_resolve() != 47278.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_func_did_web_resolve() != 2260.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_func_did_web_resolve() != 32907.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_func_ed25519_generator_generate() != 57849.toShort()) { @@ -1314,19 +1290,19 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_method_bearerdid_get_data() != 23985.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_bearerdid_get_signer() != 62154.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_bearerdid_get_signer() != 49175.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_did_get_data() != 55630.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_diddht_deactivate() != 44735.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_diddht_deactivate() != 8415.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_diddht_get_data() != 2858.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_diddht_publish() != 61119.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_diddht_publish() != 3488.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_didjwk_get_data() != 58319.toShort()) { @@ -1335,82 +1311,76 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_method_didweb_get_data() != 40916.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_document_find_public_key_jwk() != 5237.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_document_find_public_key_jwk() != 16969.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_document_get_data() != 16490.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_ed25519signer_sign() != 50886.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_ed25519signer_sign() != 7079.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_ed25519verifier_verify() != 14184.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_ed25519verifier_verify() != 48256.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_get_as_key_manager() != 57819.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_get_signer() != 37812.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_get_signer() != 64632.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_import_private_jwk() != 25100.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_inmemorykeymanager_import_private_jwk() != 54213.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_keymanager_get_signer() != 28362.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_keymanager_get_signer() != 27148.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_portabledid_get_data() != 27045.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_presentationdefinition_get_json_serialized_presentation_definition() != 49729.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_presentationdefinition_get_json_serialized_presentation_definition() != 52261.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_presentationdefinition_select_credentials() != 48916.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_presentationdefinition_select_credentials() != 27039.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_method_resolutionresult_get_data() != 57220.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_signer_sign() != 6486.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_web5_uniffi_checksum_method_verifiablecredential_get_data() != 36872.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_signer_sign() != 5738.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_verifiablecredential_sign() != 6102.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_verifiablecredential_get_data() != 34047.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_verifiablecredential_sign_with_signer() != 64852.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_method_verifier_verify() != 49443.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_method_verifier_verify() != 15654.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_bearerdid_from_portable_did() != 49122.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_bearerdid_from_portable_did() != 22241.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_bearerdid_new() != 7404.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_bearerdid_new() != 65090.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_did_new() != 60730.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_did_new() != 36469.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_diddht_from_identity_key() != 7094.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_diddht_from_identity_key() != 8336.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_diddht_from_uri() != 63936.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_diddht_from_uri() != 36592.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_public_jwk() != 39843.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_public_jwk() != 14130.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_uri() != 21472.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_didjwk_from_uri() != 10422.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_didweb_from_public_jwk() != 22173.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_didweb_from_public_jwk() != 58059.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_web5_uniffi_checksum_constructor_didweb_from_uri() != 44078.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_didweb_from_uri() != 39137.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_constructor_document_new() != 10173.toShort()) { @@ -1425,22 +1395,16 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_web5_uniffi_checksum_constructor_inmemorykeymanager_new() != 16598.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_portabledid_new() != 53732.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_portabledid_new() != 37852.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_presentationdefinition_new() != 37876.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_presentationdefinition_new() != 13282.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_web5_uniffi_checksum_constructor_resolutionresult_new() != 23836.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_web5_uniffi_checksum_constructor_verifiablecredential_new() != 49878.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_web5_uniffi_checksum_constructor_verifiablecredential_verify() != 34478.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_web5_uniffi_checksum_constructor_verifiablecredential_verify_with_verifier() != 49273.toShort()) { + if (lib.uniffi_web5_uniffi_checksum_constructor_verifiablecredential_create() != 31236.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } @@ -1839,7 +1803,7 @@ open class BearerDid: Disposable, AutoCloseable, BearerDidInterface { } constructor(`uri`: kotlin.String, `keyManager`: KeyManager) : this( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_bearerdid_new( FfiConverterString.lower(`uri`),FfiConverterTypeKeyManager.lower(`keyManager`),_status) } @@ -1921,10 +1885,10 @@ open class BearerDid: Disposable, AutoCloseable, BearerDidInterface { - @Throws(RustCoreException::class)override fun `getSigner`(`keyId`: kotlin.String): Signer { + @Throws(Web5Exception::class)override fun `getSigner`(`keyId`: kotlin.String): Signer { return FfiConverterTypeSigner.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_bearerdid_get_signer( it, FfiConverterString.lower(`keyId`),_status) } @@ -1938,9 +1902,9 @@ open class BearerDid: Disposable, AutoCloseable, BearerDidInterface { companion object { - @Throws(RustCoreException::class) fun `fromPortableDid`(`portableDid`: PortableDid): BearerDid { + @Throws(Web5Exception::class) fun `fromPortableDid`(`portableDid`: PortableDid): BearerDid { return FfiConverterTypeBearerDid.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_bearerdid_from_portable_did( FfiConverterTypePortableDid.lower(`portableDid`),_status) } @@ -2103,7 +2067,7 @@ open class Did: Disposable, AutoCloseable, DidInterface { } constructor(`uri`: kotlin.String) : this( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_did_new( FfiConverterString.lower(`uri`),_status) } @@ -2409,10 +2373,10 @@ open class DidDht: Disposable, AutoCloseable, DidDhtInterface { } - @Throws(RustCoreException::class)override fun `deactivate`(`signer`: Signer) + @Throws(Web5Exception::class)override fun `deactivate`(`signer`: Signer) = callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_diddht_deactivate( it, FfiConverterTypeSigner.lower(`signer`),_status) } @@ -2433,10 +2397,10 @@ open class DidDht: Disposable, AutoCloseable, DidDhtInterface { - @Throws(RustCoreException::class)override fun `publish`(`signer`: Signer) + @Throws(Web5Exception::class)override fun `publish`(`signer`: Signer) = callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_diddht_publish( it, FfiConverterTypeSigner.lower(`signer`),_status) } @@ -2449,9 +2413,9 @@ open class DidDht: Disposable, AutoCloseable, DidDhtInterface { companion object { - @Throws(RustCoreException::class) fun `fromIdentityKey`(`identityKey`: JwkData): DidDht { + @Throws(Web5Exception::class) fun `fromIdentityKey`(`identityKey`: JwkData): DidDht { return FfiConverterTypeDidDht.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_diddht_from_identity_key( FfiConverterTypeJwkData.lower(`identityKey`),_status) } @@ -2460,9 +2424,9 @@ open class DidDht: Disposable, AutoCloseable, DidDhtInterface { - @Throws(RustCoreException::class) fun `fromUri`(`uri`: kotlin.String): DidDht { + @Throws(Web5Exception::class) fun `fromUri`(`uri`: kotlin.String): DidDht { return FfiConverterTypeDidDht.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_diddht_from_uri( FfiConverterString.lower(`uri`),_status) } @@ -2704,9 +2668,9 @@ open class DidJwk: Disposable, AutoCloseable, DidJwkInterface { companion object { - @Throws(RustCoreException::class) fun `fromPublicJwk`(`publicJwk`: JwkData): DidJwk { + @Throws(Web5Exception::class) fun `fromPublicJwk`(`publicJwk`: JwkData): DidJwk { return FfiConverterTypeDidJwk.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_didjwk_from_public_jwk( FfiConverterTypeJwkData.lower(`publicJwk`),_status) } @@ -2715,9 +2679,9 @@ open class DidJwk: Disposable, AutoCloseable, DidJwkInterface { - @Throws(RustCoreException::class) fun `fromUri`(`uri`: kotlin.String): DidJwk { + @Throws(Web5Exception::class) fun `fromUri`(`uri`: kotlin.String): DidJwk { return FfiConverterTypeDidJwk.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_didjwk_from_uri( FfiConverterString.lower(`uri`),_status) } @@ -2959,9 +2923,9 @@ open class DidWeb: Disposable, AutoCloseable, DidWebInterface { companion object { - @Throws(RustCoreException::class) fun `fromPublicJwk`(`domain`: kotlin.String, `publicJwk`: JwkData): DidWeb { + @Throws(Web5Exception::class) fun `fromPublicJwk`(`domain`: kotlin.String, `publicJwk`: JwkData): DidWeb { return FfiConverterTypeDidWeb.lift( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_didweb_from_public_jwk( FfiConverterString.lower(`domain`),FfiConverterTypeJwkData.lower(`publicJwk`),_status) } @@ -2970,7 +2934,7 @@ open class DidWeb: Disposable, AutoCloseable, DidWebInterface { - @Throws(RustCoreException::class) + @Throws(Web5Exception::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") suspend fun `fromUri`(`uri`: kotlin.String) : DidWeb { return uniffiRustCallAsync( @@ -2981,7 +2945,7 @@ open class DidWeb: Disposable, AutoCloseable, DidWebInterface { // lift function { FfiConverterTypeDidWeb.lift(it) }, // Error FFI converter - RustCoreException.ErrorHandler, + Web5Exception.ErrorHandler, ) } @@ -3212,10 +3176,10 @@ open class Document: Disposable, AutoCloseable, DocumentInterface { } - @Throws(RustCoreException::class)override fun `findPublicKeyJwk`(`keyId`: kotlin.String): JwkData { + @Throws(Web5Exception::class)override fun `findPublicKeyJwk`(`keyId`: kotlin.String): JwkData { return FfiConverterTypeJwkData.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_document_find_public_key_jwk( it, FfiConverterString.lower(`keyId`),_status) } @@ -3464,10 +3428,10 @@ open class Ed25519Signer: Disposable, AutoCloseable, Ed25519SignerInterface { } - @Throws(RustCoreException::class)override fun `sign`(`payload`: kotlin.ByteArray): kotlin.ByteArray { + @Throws(Web5Exception::class)override fun `sign`(`payload`: kotlin.ByteArray): kotlin.ByteArray { return FfiConverterByteArray.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_ed25519signer_sign( it, FfiConverterByteArray.lower(`payload`),_status) } @@ -3704,10 +3668,10 @@ open class Ed25519Verifier: Disposable, AutoCloseable, Ed25519VerifierInterface } - @Throws(RustCoreException::class)override fun `verify`(`message`: kotlin.ByteArray, `signature`: kotlin.ByteArray): kotlin.Boolean { + @Throws(Web5Exception::class)override fun `verify`(`message`: kotlin.ByteArray, `signature`: kotlin.ByteArray): kotlin.Boolean { return FfiConverterBoolean.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_ed25519verifier_verify( it, FfiConverterByteArray.lower(`message`),FfiConverterByteArray.lower(`signature`),_status) } @@ -3960,10 +3924,10 @@ open class InMemoryKeyManager: Disposable, AutoCloseable, InMemoryKeyManagerInte - @Throws(RustCoreException::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { + @Throws(Web5Exception::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { return FfiConverterTypeSigner.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_inmemorykeymanager_get_signer( it, FfiConverterTypeJwkData.lower(`publicJwk`),_status) } @@ -3973,10 +3937,10 @@ open class InMemoryKeyManager: Disposable, AutoCloseable, InMemoryKeyManagerInte - @Throws(RustCoreException::class)override fun `importPrivateJwk`(`privateKey`: JwkData): JwkData { + @Throws(Web5Exception::class)override fun `importPrivateJwk`(`privateKey`: JwkData): JwkData { return FfiConverterTypeJwkData.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_inmemorykeymanager_import_private_jwk( it, FfiConverterTypeJwkData.lower(`privateKey`),_status) } @@ -4206,10 +4170,10 @@ open class KeyManagerImpl: Disposable, AutoCloseable, KeyManager { } - @Throws(RustCoreException::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { + @Throws(Web5Exception::class)override fun `getSigner`(`publicJwk`: JwkData): Signer { return FfiConverterTypeSigner.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_keymanager_get_signer( it, FfiConverterTypeJwkData.lower(`publicJwk`),_status) } @@ -4270,7 +4234,7 @@ internal object uniffiCallbackInterfaceKeyManager { uniffiCallStatus, makeCall, writeReturn, - { e: RustCoreException -> FfiConverterTypeRustCoreError.lower(e) } + { e: Web5Exception -> FfiConverterTypeWeb5Error.lower(e) } ) } } @@ -4444,7 +4408,7 @@ open class PortableDid: Disposable, AutoCloseable, PortableDidInterface { } constructor(`json`: kotlin.String) : this( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_portabledid_new( FfiConverterString.lower(`json`),_status) } @@ -4685,7 +4649,7 @@ open class PresentationDefinition: Disposable, AutoCloseable, PresentationDefini } constructor(`jsonSerializedPresentationDefinition`: kotlin.String) : this( - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_presentationdefinition_new( FfiConverterString.lower(`jsonSerializedPresentationDefinition`),_status) } @@ -4755,10 +4719,10 @@ open class PresentationDefinition: Disposable, AutoCloseable, PresentationDefini } - @Throws(RustCoreException::class)override fun `getJsonSerializedPresentationDefinition`(): kotlin.String { + @Throws(Web5Exception::class)override fun `getJsonSerializedPresentationDefinition`(): kotlin.String { return FfiConverterString.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_presentationdefinition_get_json_serialized_presentation_definition( it, _status) } @@ -4768,10 +4732,10 @@ open class PresentationDefinition: Disposable, AutoCloseable, PresentationDefini - @Throws(RustCoreException::class)override fun `selectCredentials`(`vcJwts`: List): List { + @Throws(Web5Exception::class)override fun `selectCredentials`(`vcJwts`: List): List { return FfiConverterSequenceString.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_presentationdefinition_select_credentials( it, FfiConverterSequenceString.lower(`vcJwts`),_status) } @@ -5240,10 +5204,10 @@ open class SignerImpl: Disposable, AutoCloseable, Signer { } - @Throws(RustCoreException::class)override fun `sign`(`payload`: kotlin.ByteArray): kotlin.ByteArray { + @Throws(Web5Exception::class)override fun `sign`(`payload`: kotlin.ByteArray): kotlin.ByteArray { return FfiConverterByteArray.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_signer_sign( it, FfiConverterByteArray.lower(`payload`),_status) } @@ -5276,7 +5240,7 @@ internal object uniffiCallbackInterfaceSigner { uniffiCallStatus, makeCall, writeReturn, - { e: RustCoreException -> FfiConverterTypeRustCoreError.lower(e) } + { e: Web5Exception -> FfiConverterTypeWeb5Error.lower(e) } ) } } @@ -5428,10 +5392,6 @@ public interface VerifiableCredentialInterface { fun `getData`(): VerifiableCredentialData - fun `sign`(`bearerDid`: BearerDid): kotlin.String - - fun `signWithSigner`(`keyId`: kotlin.String, `signer`: Signer): kotlin.String - companion object } @@ -5452,13 +5412,6 @@ open class VerifiableCredential: Disposable, AutoCloseable, VerifiableCredential this.pointer = null this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) } - constructor(`data`: VerifiableCredentialData) : - this( - uniffiRustCallWithError(RustCoreException) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_verifiablecredential_new( - FfiConverterTypeVerifiableCredentialData.lower(`data`),_status) -} - ) protected val pointer: Pointer? protected val cleanable: UniffiCleaner.Cleanable @@ -5523,11 +5476,10 @@ open class VerifiableCredential: Disposable, AutoCloseable, VerifiableCredential } } - - @Throws(RustCoreException::class)override fun `getData`(): VerifiableCredentialData { + override fun `getData`(): VerifiableCredentialData { return FfiConverterTypeVerifiableCredentialData.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCall() { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_verifiablecredential_get_data( it, _status) } @@ -5537,52 +5489,15 @@ open class VerifiableCredential: Disposable, AutoCloseable, VerifiableCredential - @Throws(RustCoreException::class)override fun `sign`(`bearerDid`: BearerDid): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_verifiablecredential_sign( - it, FfiConverterTypeBearerDid.lower(`bearerDid`),_status) -} - } - ) - } - - - - @Throws(RustCoreException::class)override fun `signWithSigner`(`keyId`: kotlin.String, `signer`: Signer): kotlin.String { - return FfiConverterString.lift( - callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_verifiablecredential_sign_with_signer( - it, FfiConverterString.lower(`keyId`),FfiConverterTypeSigner.lower(`signer`),_status) -} - } - ) - } - - - companion object { - @Throws(RustCoreException::class) fun `verify`(`vcjwt`: kotlin.String): VerifiableCredential { - return FfiConverterTypeVerifiableCredential.lift( - uniffiRustCallWithError(RustCoreException) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_verifiablecredential_verify( - FfiConverterString.lower(`vcjwt`),_status) -} - ) - } - - - - @Throws(RustCoreException::class) fun `verifyWithVerifier`(`vcjwt`: kotlin.String, `verifier`: Verifier): VerifiableCredential { + @Throws(Web5Exception::class) fun `create`(`jsonSerializedIssuer`: kotlin.String, `jsonSerializedCredentialSubject`: kotlin.String, `options`: VerifiableCredentialCreateOptionsData?): VerifiableCredential { return FfiConverterTypeVerifiableCredential.lift( - uniffiRustCallWithError(RustCoreException) { _status -> - UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_verifiablecredential_verify_with_verifier( - FfiConverterString.lower(`vcjwt`),FfiConverterTypeVerifier.lower(`verifier`),_status) + uniffiRustCallWithError(Web5Exception) { _status -> + UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_constructor_verifiablecredential_create( + FfiConverterString.lower(`jsonSerializedIssuer`),FfiConverterString.lower(`jsonSerializedCredentialSubject`),FfiConverterOptionalTypeVerifiableCredentialCreateOptionsData.lower(`options`),_status) } ) } @@ -5806,10 +5721,10 @@ open class VerifierImpl: Disposable, AutoCloseable, Verifier { } - @Throws(RustCoreException::class)override fun `verify`(`message`: kotlin.ByteArray, `signature`: kotlin.ByteArray): kotlin.Boolean { + @Throws(Web5Exception::class)override fun `verify`(`message`: kotlin.ByteArray, `signature`: kotlin.ByteArray): kotlin.Boolean { return FfiConverterBoolean.lift( callWithPointer { - uniffiRustCallWithError(RustCoreException) { _status -> + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_method_verifier_verify( it, FfiConverterByteArray.lower(`message`),FfiConverterByteArray.lower(`signature`),_status) } @@ -5843,7 +5758,7 @@ internal object uniffiCallbackInterfaceVerifier { uniffiCallStatus, makeCall, writeReturn, - { e: RustCoreException -> FfiConverterTypeRustCoreError.lower(e) } + { e: Web5Exception -> FfiConverterTypeWeb5Error.lower(e) } ) } } @@ -6363,14 +6278,55 @@ public object FfiConverterTypeServiceData: FfiConverterRustBuffer { +data class VerifiableCredentialCreateOptionsData ( + var `id`: kotlin.String?, + var `context`: List?, + var `type`: List?, + var `issuanceDate`: java.time.Instant?, + var `expirationDate`: java.time.Instant? +) { + + companion object +} + +public object FfiConverterTypeVerifiableCredentialCreateOptionsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): VerifiableCredentialCreateOptionsData { + return VerifiableCredentialCreateOptionsData( + FfiConverterOptionalString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalSequenceString.read(buf), + FfiConverterOptionalTimestamp.read(buf), + FfiConverterOptionalTimestamp.read(buf), + ) + } + + override fun allocationSize(value: VerifiableCredentialCreateOptionsData) = ( + FfiConverterOptionalString.allocationSize(value.`id`) + + FfiConverterOptionalSequenceString.allocationSize(value.`context`) + + FfiConverterOptionalSequenceString.allocationSize(value.`type`) + + FfiConverterOptionalTimestamp.allocationSize(value.`issuanceDate`) + + FfiConverterOptionalTimestamp.allocationSize(value.`expirationDate`) + ) + + override fun write(value: VerifiableCredentialCreateOptionsData, buf: ByteBuffer) { + FfiConverterOptionalString.write(value.`id`, buf) + FfiConverterOptionalSequenceString.write(value.`context`, buf) + FfiConverterOptionalSequenceString.write(value.`type`, buf) + FfiConverterOptionalTimestamp.write(value.`issuanceDate`, buf) + FfiConverterOptionalTimestamp.write(value.`expirationDate`, buf) + } +} + + + data class VerifiableCredentialData ( var `context`: List, - var `id`: kotlin.String, var `type`: List, + var `id`: kotlin.String, var `jsonSerializedIssuer`: kotlin.String, + var `jsonSerializedCredentialSubject`: kotlin.String, var `issuanceDate`: java.time.Instant, - var `expirationDate`: java.time.Instant?, - var `jsonSerializedCredentialSubject`: kotlin.String + var `expirationDate`: java.time.Instant? ) { companion object @@ -6380,33 +6336,33 @@ public object FfiConverterTypeVerifiableCredentialData: FfiConverterRustBuffer { - override fun lift(error_buf: RustBuffer.ByValue): RustCoreException = FfiConverterTypeRustCoreError.lift(error_buf) + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): Web5Exception = FfiConverterTypeWeb5Error.lift(error_buf) } } -public object FfiConverterTypeRustCoreError : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): RustCoreException { +public object FfiConverterTypeWeb5Error : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Web5Exception { return when(buf.getInt()) { - 1 -> RustCoreException.Exception( + 1 -> Web5Exception.Exception( FfiConverterString.read(buf), FfiConverterString.read(buf), FfiConverterString.read(buf), @@ -6546,9 +6502,9 @@ public object FfiConverterTypeRustCoreError : FfiConverterRustBuffer ( + is Web5Exception.Exception -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL + FfiConverterString.allocationSize(value.`type`) @@ -6558,9 +6514,9 @@ public object FfiConverterTypeRustCoreError : FfiConverterRustBuffer { + is Web5Exception.Exception -> { buf.putInt(1) FfiConverterString.write(value.`type`, buf) FfiConverterString.write(value.`variant`, buf) @@ -6720,6 +6676,35 @@ public object FfiConverterOptionalTypeDocumentMetadataData: FfiConverterRustBuff +public object FfiConverterOptionalTypeVerifiableCredentialCreateOptionsData: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): VerifiableCredentialCreateOptionsData? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeVerifiableCredentialCreateOptionsData.read(buf) + } + + override fun allocationSize(value: VerifiableCredentialCreateOptionsData?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeVerifiableCredentialCreateOptionsData.allocationSize(value) + } + } + + override fun write(value: VerifiableCredentialCreateOptionsData?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeVerifiableCredentialCreateOptionsData.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalTypeResolutionMetadataError: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): ResolutionMetadataError? { if (buf.get().toInt() == 0) { @@ -6975,9 +6960,9 @@ public object FfiConverterMapStringString: FfiConverterRustBuffer + uniffiRustCallWithError(Web5Exception) { _status -> UniffiLib.INSTANCE.uniffi_web5_uniffi_fn_func_did_dht_resolve( FfiConverterString.lower(`uri`),_status) } @@ -6994,7 +6979,7 @@ public object FfiConverterMapStringString: FfiConverterRustBuffer? = null, + var type: List? = null, + var issuanceDate: Date? = null, + var expirationDate: Date? = null +) + +class VerifiableCredential private constructor( + val context: List, + val type: List, + val id: String, + val issuer: Issuer, + val credentialSubject: CredentialSubject, + val issuanceDate: Date, + val expirationDate: Date? = null, + internal val rustCoreVerifiableCredential: RustCoreVerifiableCredential, +) { + companion object { + fun create( + issuer: Issuer, + credentialSubject: CredentialSubject, + options: VerifiableCredentialCreateOptions? = null): VerifiableCredential { + + val jsonSerializedIssuer = Json.stringify(issuer) + val jsonSerializedCredentialSubject = Json.stringify(credentialSubject) + + val rustCoreVerifiableCredential = RustCoreVerifiableCredential.create( + jsonSerializedIssuer, + jsonSerializedCredentialSubject, + RustCoreVerifiableCredentialCreateOptions( + options?.id, + options?.context, + options?.type, + options?.issuanceDate?.toInstant(), + options?.expirationDate?.toInstant(), + ) + ) + + val data = rustCoreVerifiableCredential.getData() + + return VerifiableCredential( + data.context, + data.type, + data.id, + issuer, + credentialSubject, + Date.from(data.issuanceDate), + data.expirationDate?.let { Date.from(it) }, + rustCoreVerifiableCredential, + ) + } + } +} + +sealed class Issuer { + data class StringIssuer(val value: String) : Issuer() { + @JsonValue + fun toJson(): String = value + } + + data class ObjectIssuer( + val id: String, + val name: String, + val additionalProperties: Map = emptyMap() + ) : Issuer() { + @JsonValue + fun toJson(): Map { + return mapOf("id" to id, "name" to name) + additionalProperties + } + } +} + +data class CredentialSubject( + val id: String, + val additionalProperties: Map = emptyMap() +) { + @JsonValue + fun toJson(): Map { + return mapOf( + "id" to id, + ) + additionalProperties + } +} \ No newline at end of file diff --git a/bound/kt/src/test/kotlin/web5/sdk/TestHelper.kt b/bound/kt/src/test/kotlin/web5/sdk/TestHelper.kt new file mode 100644 index 00000000..4b82f1c2 --- /dev/null +++ b/bound/kt/src/test/kotlin/web5/sdk/TestHelper.kt @@ -0,0 +1,22 @@ +package web5.sdk + +import java.nio.file.Files +import java.nio.file.Paths + +class UnitTestSuite(name: String) { + val tests: MutableList + + init { + val path = Paths.get("../../tests/unit_test_cases/$name.json") + val jsonString = Files.readString(path) + this.tests = Json.jsonMapper.readValue(jsonString, List::class.java) as MutableList + } + + fun include() { + val testMethodName = Thread.currentThread().stackTrace + .firstOrNull { it.methodName.startsWith("test") }?.methodName + ?: throw IllegalStateException("Unable to determine test method name") + + this.tests.remove(testMethodName) + } +} \ No newline at end of file diff --git a/bound/kt/src/test/kotlin/web5/sdk/crypto/keys/InMemoryKeyManagerTest.kt b/bound/kt/src/test/kotlin/web5/sdk/crypto/keys/InMemoryKeyManagerTest.kt index 81a90597..7176aef0 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/crypto/keys/InMemoryKeyManagerTest.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/crypto/keys/InMemoryKeyManagerTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test -import web5.sdk.rust.RustCoreException +import web5.sdk.rust.Web5Exception import web5.sdk.rust.ed25519GeneratorGenerate as rustCoreEd25519GeneratorGenerate @@ -26,7 +26,7 @@ class InMemoryKeyManagerTest { fun `test wrong jwk for key manager`() { val publicJwk = Jwk(alg="Ed25519", kty="OKP", crv="Ed25519", d=null, x="yxTpaqbGhLNMfOCu31znPNNei0OtDiQ_AS9DxC7Bstg", y=null) - assertThrows(RustCoreException::class.java) { + assertThrows(Web5Exception::class.java) { InMemoryKeyManager(listOf(publicJwk)) } } diff --git a/bound/kt/src/test/kotlin/web5/sdk/dids/PortableDidTest.kt b/bound/kt/src/test/kotlin/web5/sdk/dids/PortableDidTest.kt index e5753784..f5679ea5 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/dids/PortableDidTest.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/dids/PortableDidTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertEquals -import web5.sdk.rust.RustCoreException +import web5.sdk.rust.Web5Exception class PortableDidTest { @Test @@ -22,7 +22,7 @@ class PortableDidTest { @Test fun `instantiation from json string throws with invalid json string`() { val invalidJsonString = "something not valid" - assertThrows(RustCoreException::class.java) { + assertThrows(Web5Exception::class.java) { PortableDid(invalidJsonString) } } diff --git a/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt b/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt index 38acd0d2..f1ec04a3 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTests.kt @@ -2,7 +2,6 @@ package web5.sdk.dids.methods.jwk import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import web5.sdk.rust.RustCoreException import web5.sdk.rust.DidJwk as RustCoreDidJwk diff --git a/bound/kt/src/test/kotlin/web5/sdk/vc/VerifiableCredentialTest.kt b/bound/kt/src/test/kotlin/web5/sdk/vc/VerifiableCredentialTest.kt new file mode 100644 index 00000000..ac8acb3c --- /dev/null +++ b/bound/kt/src/test/kotlin/web5/sdk/vc/VerifiableCredentialTest.kt @@ -0,0 +1,253 @@ +package web5.sdk.vc + +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.fail +import web5.sdk.UnitTestSuite +import web5.sdk.rust.Web5Exception +import java.util.Date +import java.util.regex.Pattern + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class VerifiableCredentialTest { + companion object { + const val ISSUER_DID_URI = "did:web:tbd.website" + const val SUBJECT_DID_URI = "did:dht:qgmmpyjw5hwnqfgzn7wmrm33ady8gb8z9ideib6m9gj4ys6wny8y" + + val ISSUER = Issuer.StringIssuer(ISSUER_DID_URI) + val CREDENTIAL_SUBJECT = CredentialSubject(SUBJECT_DID_URI) + } + + private val testSuite = UnitTestSuite("verifiable_credential_1_1_create") + + @AfterAll + fun verifyAllTestsIncluded() { + if (testSuite.tests.isNotEmpty()) { + println("The following tests were not included or executed:") + testSuite.tests.forEach { println(it) } + fail("Not all tests were executed! ${this.testSuite.tests}") + } + } + + @Test + fun test_default_context_added_if_not_supplied() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT) + assertEquals(listOf("https://www.w3.org/2018/credentials/v1"), vc.context) + } + + @Test + fun test_default_context_not_duplicated_if_supplied() { + this.testSuite.include() + val options = VerifiableCredentialCreateOptions( + context = listOf("https://www.w3.org/2018/credentials/v1") + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(listOf("https://www.w3.org/2018/credentials/v1"), vc.context) + } + + @Test + fun test_developer_provided_context_appended_to_default() { + this.testSuite.include() + val customContext = "https://example.com/custom-context" + val options = VerifiableCredentialCreateOptions( + context = listOf(customContext) + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(listOf("https://www.w3.org/2018/credentials/v1", customContext), vc.context) + } + + @Test + fun test_default_type_added_if_not_supplied() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + assertEquals(listOf("VerifiableCredential"), vc.type) + } + + @Test + fun test_default_type_not_duplicated_if_supplied() { + this.testSuite.include() + val options = VerifiableCredentialCreateOptions( + type = listOf("VerifiableCredential") + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(listOf("VerifiableCredential"), vc.type) + } + + @Test + fun test_developer_provided_type_appended_to_default() { + this.testSuite.include() + val customType = "CustomType" + val options = VerifiableCredentialCreateOptions( + type = listOf(customType) + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(listOf("VerifiableCredential", customType), vc.type) + } + + @Test + fun test_id_generated_if_not_supplied() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + val uuidPattern = Pattern.compile("^urn:uuid:[0-9a-fA-F-]{36}$") + assertTrue(uuidPattern.matcher(vc.id).matches()) + } + + @Test + fun test_id_must_be_set_if_supplied() { + this.testSuite.include() + val customId = "custom-id" + val options = VerifiableCredentialCreateOptions( + id = customId + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(customId, vc.id) + } + + @Test + fun test_issuer_string_must_not_be_empty() { + this.testSuite.include() + val emptyIssuer = Issuer.StringIssuer("") + + val exception = assertThrows { + VerifiableCredential.create(emptyIssuer, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + } + + assertEquals("parameter error issuer id must not be empty", exception.msg) + } + + @Test + fun test_issuer_string_must_be_set() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + assertEquals(ISSUER, vc.issuer) + } + + @Test + fun test_issuer_object_id_must_not_be_empty() { + this.testSuite.include() + val issuer = Issuer.ObjectIssuer("", "Example Name") + + val exception = assertThrows { + VerifiableCredential.create(issuer, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + } + + assertEquals("parameter error issuer id must not be empty", exception.msg) + } + + @Test + fun test_issuer_object_name_must_not_be_empty() { + this.testSuite.include() + val issuer = Issuer.ObjectIssuer(ISSUER_DID_URI, "") + + val exception = assertThrows { + VerifiableCredential.create(issuer, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + } + + assertEquals("parameter error named issuer name must not be empty", exception.msg) + } + + @Test + fun test_issuer_object_must_be_set() { + this.testSuite.include() + val issuer = Issuer.ObjectIssuer(ISSUER_DID_URI, "Example Name") + + val vc = VerifiableCredential.create(issuer, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + assertEquals(issuer, vc.issuer) + } + + @Test + fun test_issuer_object_supports_additional_properties() { + this.testSuite.include() + val additionalProperties = mapOf("extra_key" to "extra_value") + + val issuer = Issuer.ObjectIssuer( + ISSUER_DID_URI, + "Example Name", + additionalProperties + ) + + val vc = VerifiableCredential.create(issuer, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + + if (vc.issuer is Issuer.ObjectIssuer) { + assertEquals(additionalProperties, (vc.issuer as Issuer.ObjectIssuer).additionalProperties) + } else { + fail("Issuer is not an ObjectIssuer") + } + } + + @Test + fun test_credential_subject_id_must_not_be_empty() { + this.testSuite.include() + val credentialSubject = CredentialSubject("") + + val exception = assertThrows { + VerifiableCredential.create(ISSUER, credentialSubject, VerifiableCredentialCreateOptions()) + } + + assertEquals("parameter error subject id must not be empty", exception.msg) + } + + @Test + fun test_credential_subject_must_be_set() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + assertEquals(CREDENTIAL_SUBJECT, vc.credentialSubject) + } + + @Test + fun test_credential_subject_supports_additional_properties() { + this.testSuite.include() + val additionalProperties = mapOf("extra_key" to "extra_value") + + val credentialSubject = CredentialSubject( + SUBJECT_DID_URI, + additionalProperties + ) + + val vc = VerifiableCredential.create(ISSUER, credentialSubject, VerifiableCredentialCreateOptions()) + assertEquals(additionalProperties, vc.credentialSubject.additionalProperties) + } + + @Test + fun test_issuance_date_must_be_set() { + this.testSuite.include() + val issuanceDate = Date() + + val options = VerifiableCredentialCreateOptions( + issuanceDate = issuanceDate + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(issuanceDate, vc.issuanceDate) + } + + @Test + fun test_issuance_date_must_be_now_if_not_supplied() { + this.testSuite.include() + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, VerifiableCredentialCreateOptions()) + + val now = Date() + val tenSecondsAgo = Date(now.time - 10000) + val tenSecondsAhead = Date(now.time + 10000) + + assertTrue(vc.issuanceDate.after(tenSecondsAgo) && vc.issuanceDate.before(tenSecondsAhead)) + } + + @Test + fun test_expiration_date_must_be_set_if_supplied() { + this.testSuite.include() + val expirationDate = Date() + val options = VerifiableCredentialCreateOptions( + expirationDate = expirationDate + ) + + val vc = VerifiableCredential.create(ISSUER, CREDENTIAL_SUBJECT, options) + assertEquals(expirationDate, vc.expirationDate) + } +} diff --git a/bound/kt/src/test/kotlin/web5/sdk/vcs/pex/PresentationDefinitionTest.kt b/bound/kt/src/test/kotlin/web5/sdk/vc/pex/PresentationDefinitionTest.kt similarity index 97% rename from bound/kt/src/test/kotlin/web5/sdk/vcs/pex/PresentationDefinitionTest.kt rename to bound/kt/src/test/kotlin/web5/sdk/vc/pex/PresentationDefinitionTest.kt index b66f214b..5314eca2 100644 --- a/bound/kt/src/test/kotlin/web5/sdk/vcs/pex/PresentationDefinitionTest.kt +++ b/bound/kt/src/test/kotlin/web5/sdk/vc/pex/PresentationDefinitionTest.kt @@ -1,11 +1,10 @@ -package web5.sdk.vcs.pex +package web5.sdk.vc.pex import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test -import web5.sdk.vc.pex.* class PresentationDefinitionTest { diff --git a/crates/web5/src/credentials/presentation_definition.rs b/crates/web5/src/credentials/presentation_definition.rs index 1861fc1b..f6a23e39 100644 --- a/crates/web5/src/credentials/presentation_definition.rs +++ b/crates/web5/src/credentials/presentation_definition.rs @@ -238,55 +238,3 @@ impl JsonSchemaBuilder { }) } } - -#[cfg(test)] -mod tests { - use std::collections::HashSet; - - use crate::test_helpers::TestVectorFile; - - use super::PresentationDefinition; - - #[derive(Debug, serde::Deserialize)] - struct VectorInput { - #[serde(rename = "presentationDefinition")] - pub presentation_definition: PresentationDefinition, - #[serde(rename = "credentialJwts")] - pub credential_jwts: Vec, - } - - #[derive(Debug, serde::Deserialize)] - struct VectorOutput { - #[serde(rename = "selectedCredentials")] - pub selected_credentials: Vec, - } - - #[test] - #[ignore] // TODO temporarily ignoring, because web5-spec test vectors use did:key which isn't supported - fn test_web5_spec_test_vectors() { - let path = "presentation_exchange/select_credentials.json"; - let vectors: TestVectorFile = - TestVectorFile::load_from_path(path); - - for vector in vectors.vectors { - let presentation_definition = vector.input.presentation_definition; - let vc_jwts = vector.input.credential_jwts; - let error_msg = format!( - "Selected Credential test vector ({}) should not have thrown error", - vector.description - ); - - let selected_credentials = presentation_definition - .select_credentials(&vc_jwts) - .expect(&error_msg); - - let set1: HashSet<_> = selected_credentials.iter().collect(); - let set2: HashSet<_> = vector.output.selected_credentials.iter().collect(); - assert_eq!( - set1, set2, - "Vectors do not contain the same elements: {}", - error_msg - ); - } - } -} diff --git a/crates/web5/src/credentials/verifiable_credential_1_1.rs b/crates/web5/src/credentials/verifiable_credential_1_1.rs index f96e7520..7b665983 100644 --- a/crates/web5/src/credentials/verifiable_credential_1_1.rs +++ b/crates/web5/src/credentials/verifiable_credential_1_1.rs @@ -1,4 +1,10 @@ -use super::{CredentialError, Result}; +use super::{CredentialError, Result as ResultOld}; +use crate::errors::{Result, Web5Error}; +use crate::json::{FromJson, JsonObject, ToJson}; +use crate::rfc3339::{ + deserialize_optional_system_time, deserialize_system_time, serialize_optional_system_time, + serialize_system_time, +}; use crate::{ crypto::dsa::{ed25519::Ed25519Verifier, DsaError, Signer, Verifier}, dids::{ @@ -9,7 +15,6 @@ use crate::{ }, }, }; -use chrono::{DateTime, Utc}; use core::fmt; use josekit::{ jws::{ @@ -20,30 +25,35 @@ use josekit::{ jwt::JwtPayload, JoseError as JosekitError, }; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, fmt::{Display, Formatter}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; +use uuid::Uuid; pub const BASE_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; pub const BASE_TYPE: &str = "VerifiableCredential"; #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] -pub struct NamedIssuer { +pub struct ObjectIssuer { pub id: String, pub name: String, + #[serde(flatten)] + pub additional_properties: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(untagged)] pub enum Issuer { String(String), - Object(NamedIssuer), + Object(ObjectIssuer), } +impl FromJson for Issuer {} +impl ToJson for Issuer {} + impl From for Issuer where I: Into, @@ -62,53 +72,31 @@ impl Display for Issuer { } } -fn serialize_system_time( - time: &SystemTime, - serializer: S, -) -> std::result::Result -where - S: Serializer, -{ - let datetime: chrono::DateTime = (*time).into(); - let s = datetime.to_rfc3339(); - serializer.serialize_str(&s) +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +pub struct CredentialSubject { + pub id: String, + #[serde(flatten)] + pub additional_properties: Option, } -fn deserialize_system_time<'de, D>(deserializer: D) -> std::result::Result -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - let datetime = chrono::DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; - Ok(datetime.with_timezone(&Utc).into()) -} +impl FromJson for CredentialSubject {} +impl ToJson for CredentialSubject {} -fn serialize_option_system_time( - time: &Option, - serializer: S, -) -> std::result::Result +impl From for CredentialSubject where - S: Serializer, + I: Into, { - match time { - Some(time) => serialize_system_time(time, serializer), - None => serializer.serialize_none(), + fn from(s: I) -> Self { + CredentialSubject { + id: s.into(), + ..Default::default() + } } } -fn deserialize_option_system_time<'de, D>( - deserializer: D, -) -> std::result::Result, D::Error> -where - D: Deserializer<'de>, -{ - let opt = Option::::deserialize(deserializer)?; - match opt { - Some(s) => { - let datetime = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; - Ok(Some(datetime.with_timezone(&Utc).into())) - } - None => Ok(None), +impl Display for CredentialSubject { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.id) } } @@ -120,6 +108,8 @@ pub struct VerifiableCredential { #[serde(rename = "type")] pub r#type: Vec, pub issuer: Issuer, + #[serde(rename = "credentialSubject")] + pub credential_subject: CredentialSubject, #[serde( rename = "issuanceDate", serialize_with = "serialize_system_time", @@ -128,77 +118,92 @@ pub struct VerifiableCredential { pub issuance_date: SystemTime, #[serde( rename = "expirationDate", - serialize_with = "serialize_option_system_time", - deserialize_with = "deserialize_option_system_time" + serialize_with = "serialize_optional_system_time", + deserialize_with = "deserialize_optional_system_time" )] pub expiration_date: Option, - #[serde(rename = "credentialSubject")] - pub credential_subject: CredentialSubject, } -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct CredentialSubject { - pub id: String, - #[serde(flatten)] - pub params: Option>, -} +impl FromJson for VerifiableCredential {} +impl ToJson for VerifiableCredential {} -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct JwtPayloadVerifiableCredential { - #[serde(rename = "@context")] - context: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(rename = "type")] - r#type: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - issuer: Option, - #[serde( - rename = "issuanceDate", - serialize_with = "serialize_option_system_time", - deserialize_with = "deserialize_option_system_time" - )] - issuance_date: Option, - #[serde( - rename = "expirationDate", - serialize_with = "serialize_option_system_time", - deserialize_with = "deserialize_option_system_time" - )] - expiration_date: Option, - #[serde(skip_serializing_if = "Option::is_none", rename = "credentialSubject")] - credential_subject: Option, +#[derive(Default)] +pub struct VerifiableCredentialCreateOptions { + pub id: Option, + pub context: Option>, + pub r#type: Option>, + pub issuance_date: Option, + pub expiration_date: Option, } impl VerifiableCredential { - pub fn new( - id: String, - context: Vec, - r#type: Vec, + pub fn create( issuer: Issuer, - issuance_date: SystemTime, - expiration_date: Option, credential_subject: CredentialSubject, - ) -> Self { - let context_with_base = std::iter::once(BASE_CONTEXT.to_string()) - .chain(context.into_iter().filter(|c| c != BASE_CONTEXT)) - .collect::>(); + options: Option, + ) -> Result { + if issuer.to_string().is_empty() { + return Err(Web5Error::Parameter(String::from( + "issuer id must not be empty", + ))); + } - let type_with_base = std::iter::once(BASE_TYPE.to_string()) - .chain(r#type.into_iter().filter(|t| t != BASE_TYPE)) - .collect::>(); + if let Issuer::Object(ref named_issuer) = issuer { + if named_issuer.name.is_empty() { + return Err(Web5Error::Parameter(String::from( + "named issuer name must not be empty", + ))); + } + } + + if credential_subject.to_string().is_empty() { + return Err(Web5Error::Parameter(String::from( + "subject id must not be empty", + ))); + } + + let options = options.unwrap_or_default(); + + let context = { + let mut contexts = options + .context + .unwrap_or_else(|| vec![BASE_CONTEXT.to_string()]); + + if !contexts.contains(&BASE_CONTEXT.to_string()) { + contexts.insert(0, BASE_CONTEXT.to_string()); + } + + contexts + }; + + let r#type = { + let mut types = options + .r#type + .unwrap_or_else(|| vec![BASE_TYPE.to_string()]); + + if !types.contains(&BASE_TYPE.to_string()) { + types.insert(0, BASE_TYPE.to_string()); + } - Self { - context: context_with_base, + types + }; + + let id = options + .id + .unwrap_or_else(|| format!("urn:uuid:{}", Uuid::new_v4())); + + Ok(Self { + context, id, - r#type: type_with_base, + r#type, issuer, - issuance_date, - expiration_date, + issuance_date: options.issuance_date.unwrap_or_else(SystemTime::now), + expiration_date: options.expiration_date, credential_subject, - } + }) } - pub fn sign(&self, bearer_did: &BearerDid) -> Result { + pub fn sign(&self, bearer_did: &BearerDid) -> ResultOld { // default to first VM let key_id = bearer_did.document.verification_method[0].id.clone(); let signer = bearer_did.get_signer(key_id.clone())?; @@ -206,7 +211,7 @@ impl VerifiableCredential { self.sign_with_signer(&key_id, signer) } - pub fn sign_with_signer(&self, key_id: &str, signer: Arc) -> Result { + pub fn sign_with_signer(&self, key_id: &str, signer: Arc) -> ResultOld { let mut payload = JwtPayload::new(); let vc_claim = JwtPayloadVerifiableCredential { context: self.context.clone(), @@ -218,7 +223,7 @@ impl VerifiableCredential { credential_subject: Some(self.credential_subject.clone()), }; payload.set_claim("vc", Some(serde_json::to_value(vc_claim)?))?; - payload.set_issuer(&self.issuer.to_string()); + payload.set_issuer(self.issuer.to_string()); payload.set_jwt_id(&self.id); payload.set_subject(&self.credential_subject.id); payload.set_not_before(&self.issuance_date); @@ -239,7 +244,7 @@ impl VerifiableCredential { Ok(vc_jwt) } - pub fn verify(vc_jwt: &str) -> Result { + pub fn verify(vc_jwt: &str) -> ResultOld { // this function currently only supports Ed25519 let header = josekit::jwt::decode_header(vc_jwt)?; @@ -268,7 +273,7 @@ impl VerifiableCredential { Self::verify_with_verifier(vc_jwt, Arc::new(verifier)) } - pub fn verify_with_verifier(vc_jwt: &str, verifier: Arc) -> Result { + pub fn verify_with_verifier(vc_jwt: &str, verifier: Arc) -> ResultOld { let header = josekit::jwt::decode_header(vc_jwt)?; let kid = header @@ -358,7 +363,7 @@ impl VerifiableCredential { let vc_credential_subject = vc_payload.credential_subject.unwrap_or(CredentialSubject { id: sub.to_string(), - params: None, + additional_properties: None, }); let vc = VerifiableCredential { @@ -377,7 +382,7 @@ impl VerifiableCredential { } } -fn validate_vc_data_model(vc: &VerifiableCredential) -> Result<()> { +fn validate_vc_data_model(vc: &VerifiableCredential) -> ResultOld<()> { // Required fields ["@context", "id", "type", "issuer", "issuanceDate", "credentialSubject"] if vc.id.is_empty() { return Err(CredentialError::VcDataModelValidationError( @@ -431,8 +436,34 @@ fn validate_vc_data_model(vc: &VerifiableCredential) -> Result<()> { Ok(()) } +#[derive(Serialize, Deserialize, Debug, Clone)] +struct JwtPayloadVerifiableCredential { + #[serde(rename = "@context")] + context: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(rename = "type")] + r#type: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + issuer: Option, + #[serde( + rename = "issuanceDate", + serialize_with = "serialize_optional_system_time", + deserialize_with = "deserialize_optional_system_time" + )] + issuance_date: Option, + #[serde( + rename = "expirationDate", + serialize_with = "serialize_optional_system_time", + deserialize_with = "deserialize_optional_system_time" + )] + expiration_date: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "credentialSubject")] + credential_subject: Option, +} + #[derive(Clone)] -pub struct JoseSigner { +struct JoseSigner { pub kid: String, pub signer: Arc, } @@ -512,41 +543,358 @@ impl core::fmt::Debug for JoseVerifier { #[cfg(test)] mod tests { use super::*; - use crate::{ - crypto::{ - dsa::ed25519::Ed25519Generator, key_managers::in_memory_key_manager::InMemoryKeyManager, - }, - dids::methods::did_jwk::DidJwk, - }; - use std::time::Duration; - use uuid::Uuid; - - #[test] - fn can_create_sign_and_verify() { - let key_manager = InMemoryKeyManager::new(); - let public_jwk = key_manager - .import_private_jwk(Ed25519Generator::generate()) - .unwrap(); - let did_jwk = DidJwk::from_public_jwk(public_jwk).unwrap(); - let bearer_did = BearerDid::new(&did_jwk.did.uri, Arc::new(key_manager)).unwrap(); + use crate::json::JsonValue; + use regex::Regex; + use std::collections::HashMap; + use std::sync::LazyLock; - let now = SystemTime::now(); - let vc = VerifiableCredential::new( - format!("urn:vc:uuid:{0}", Uuid::new_v4().to_string()), - vec![BASE_CONTEXT.to_string()], - vec![BASE_TYPE.to_string()], - Issuer::String(bearer_did.did.uri.clone()), - now, - Some(now + Duration::from_secs(20 * 365 * 24 * 60 * 60)), // now + 20 years - CredentialSubject { - id: bearer_did.did.uri.clone(), + const ISSUER_DID_URI: &str = "did:web:tbd.website"; + const SUBJECT_DID_URI: &str = "did:dht:qgmmpyjw5hwnqfgzn7wmrm33ady8gb8z9ideib6m9gj4ys6wny8y"; + + static ISSUER: LazyLock = LazyLock::new(|| Issuer::from(ISSUER_DID_URI)); + static CREDENTIAL_SUBJECT: LazyLock = + LazyLock::new(|| CredentialSubject::from(SUBJECT_DID_URI)); + + mod create { + use super::*; + use crate::{test_helpers::UnitTestSuite, test_name}; + + static TEST_SUITE: LazyLock = + LazyLock::new(|| UnitTestSuite::new("verifiable_credential_1_1_create")); + + #[test] + fn z_assert_all_suite_cases_covered() { + // fn name prefixed with `z_*` b/c rust test harness executes in alphabetical order, + // unless intentionally executed with "shuffle" https://doc.rust-lang.org/rustc/tests/index.html#--shuffle + // this may not work if shuffled or if test list grows to the extent of 100ms being insufficient wait time + + // wait 100ms to be last-in-queue of mutex lock + std::thread::sleep(std::time::Duration::from_millis(100)); + + TEST_SUITE.assert_coverage() + } + + #[test] + fn test_default_context_added_if_not_supplied() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + assert_eq!(vc.context, vec![BASE_CONTEXT]); + } + + #[test] + fn test_default_context_not_duplicated_if_supplied() { + TEST_SUITE.include(test_name!()); + + let options = Some(VerifiableCredentialCreateOptions { + context: Some(vec![BASE_CONTEXT.to_string()]), ..Default::default() - }, - ); + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.context, vec![BASE_CONTEXT]); + } + + #[test] + fn test_developer_provided_context_appended_to_default() { + TEST_SUITE.include(test_name!()); + + let custom_context = "https://example.com/custom-context"; + let options = Some(VerifiableCredentialCreateOptions { + context: Some(vec![custom_context.to_string()]), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.context, vec![BASE_CONTEXT, custom_context]); + } + + #[test] + fn test_default_type_added_if_not_supplied() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + assert_eq!(vc.r#type, vec![BASE_TYPE]); + } + + #[test] + fn test_default_type_not_duplicated_if_supplied() { + TEST_SUITE.include(test_name!()); + + let options = Some(VerifiableCredentialCreateOptions { + r#type: Some(vec![BASE_TYPE.to_string()]), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.r#type, vec![BASE_TYPE]); + } + + #[test] + fn test_developer_provided_type_appended_to_default() { + TEST_SUITE.include(test_name!()); + + let custom_type = "CustomType"; + let options = Some(VerifiableCredentialCreateOptions { + r#type: Some(vec![custom_type.to_string()]), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.r#type, vec![BASE_TYPE, custom_type]); + } + + #[test] + fn test_id_generated_if_not_supplied() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + let uuid_regex = Regex::new(r"^urn:uuid:[0-9a-fA-F-]{36}$").unwrap(); + assert!(uuid_regex.is_match(&vc.id)); + } + + #[test] + fn test_id_must_be_set_if_supplied() { + TEST_SUITE.include(test_name!()); + + let custom_id = "custom-id"; + let options = Some(VerifiableCredentialCreateOptions { + id: Some(custom_id.to_string()), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.id, custom_id); + } + + #[test] + fn test_issuer_string_must_not_be_empty() { + TEST_SUITE.include(test_name!()); + + let empty_issuer = Issuer::from(""); + let result = + VerifiableCredential::create(empty_issuer, CREDENTIAL_SUBJECT.clone(), None); + + match result { + Err(Web5Error::Parameter(err_msg)) => { + assert_eq!(err_msg, "issuer id must not be empty"); + } + _ => panic!("Expected Web5Error::Parameter with specific error message"), + }; + } + + #[test] + fn test_issuer_string_must_be_set() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + assert_eq!(vc.issuer, ISSUER.clone()); + } - let vc_jwt = vc.sign(&bearer_did).unwrap(); - assert_ne!(String::default(), vc_jwt); + #[test] + fn test_issuer_object_id_must_not_be_empty() { + TEST_SUITE.include(test_name!()); - VerifiableCredential::verify(&vc_jwt).unwrap(); + let issuer = Issuer::Object(ObjectIssuer { + id: "".to_string(), + name: "Example Name".to_string(), + additional_properties: None, + }); + + let result = VerifiableCredential::create(issuer, CREDENTIAL_SUBJECT.clone(), None); + + match result { + Err(Web5Error::Parameter(err_msg)) => { + assert_eq!(err_msg, "issuer id must not be empty"); + } + _ => panic!("Expected Web5Error::Parameter with specific error message"), + }; + } + + #[test] + fn test_issuer_object_name_must_not_be_empty() { + TEST_SUITE.include(test_name!()); + + let issuer = Issuer::Object(ObjectIssuer { + id: ISSUER_DID_URI.to_string(), + name: "".to_string(), + additional_properties: None, + }); + + let result = VerifiableCredential::create(issuer, CREDENTIAL_SUBJECT.clone(), None); + + match result { + Err(Web5Error::Parameter(err_msg)) => { + assert_eq!(err_msg, "named issuer name must not be empty"); + } + _ => panic!("Expected Web5Error::Parameter with specific error message"), + }; + } + + #[test] + fn test_issuer_object_must_be_set() { + TEST_SUITE.include(test_name!()); + + let issuer = Issuer::Object(ObjectIssuer { + id: ISSUER_DID_URI.to_string(), + name: "Example Name".to_string(), + additional_properties: None, + }); + + let vc = VerifiableCredential::create(issuer.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + assert_eq!(vc.issuer, issuer); + } + + #[test] + fn test_issuer_object_supports_additional_properties() { + TEST_SUITE.include(test_name!()); + + let additional_properties = JsonObject { + properties: HashMap::from([( + "extra_key".to_string(), + JsonValue::String("extra_value".to_string()), + )]), + }; + + let issuer = Issuer::Object(ObjectIssuer { + id: ISSUER_DID_URI.to_string(), + name: "Example Name".to_string(), + additional_properties: Some(additional_properties.clone()), + }); + + let vc = VerifiableCredential::create(issuer.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + match vc.issuer { + Issuer::Object(ref obj) => { + assert_eq!(obj.additional_properties, Some(additional_properties)); + } + _ => panic!("Issuer is not an ObjectIssuer"), + }; + } + + #[test] + fn test_credential_subject_id_must_not_be_empty() { + TEST_SUITE.include(test_name!()); + + let credential_subject = CredentialSubject::from(""); + + let result = VerifiableCredential::create(ISSUER.clone(), credential_subject, None); + + match result { + Err(Web5Error::Parameter(err_msg)) => { + assert_eq!(err_msg, "subject id must not be empty"); + } + _ => panic!("Expected Web5Error::Parameter with specific error message"), + }; + } + + #[test] + fn test_credential_subject_must_be_set() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + assert_eq!(vc.credential_subject, CREDENTIAL_SUBJECT.clone()); + } + + #[test] + fn test_credential_subject_supports_additional_properties() { + TEST_SUITE.include(test_name!()); + + let additional_properties = JsonObject { + properties: HashMap::from([( + "extra_key".to_string(), + JsonValue::String("extra_value".to_string()), + )]), + }; + + let credential_subject = CredentialSubject { + id: SUBJECT_DID_URI.to_string(), + additional_properties: Some(additional_properties.clone()), + }; + + let vc = VerifiableCredential::create(ISSUER.clone(), credential_subject.clone(), None) + .unwrap(); + + assert_eq!( + vc.credential_subject.additional_properties, + Some(additional_properties) + ); + } + + #[test] + fn test_issuance_date_must_be_set() { + TEST_SUITE.include(test_name!()); + + let issuance_date = SystemTime::now(); + + let options = Some(VerifiableCredentialCreateOptions { + issuance_date: Some(issuance_date), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.issuance_date, issuance_date); + } + + #[test] + fn test_issuance_date_must_be_now_if_not_supplied() { + TEST_SUITE.include(test_name!()); + + let vc = VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), None) + .unwrap(); + + let now = SystemTime::now(); + let hundred_millis_ago = now - std::time::Duration::from_millis(100); + + assert!(vc.issuance_date >= hundred_millis_ago && vc.issuance_date <= now); + } + + #[test] + fn test_expiration_date_must_be_set_if_supplied() { + TEST_SUITE.include(test_name!()); + + let expiration_date = SystemTime::now(); + let options = Some(VerifiableCredentialCreateOptions { + expiration_date: Some(expiration_date), + ..Default::default() + }); + + let vc = + VerifiableCredential::create(ISSUER.clone(), CREDENTIAL_SUBJECT.clone(), options) + .unwrap(); + + assert_eq!(vc.expiration_date, Some(expiration_date)); + } } } diff --git a/crates/web5/src/dids/methods/did_dht/mod.rs b/crates/web5/src/dids/methods/did_dht/mod.rs index 4ba76c18..08bf8bc6 100644 --- a/crates/web5/src/dids/methods/did_dht/mod.rs +++ b/crates/web5/src/dids/methods/did_dht/mod.rs @@ -311,65 +311,3 @@ mod tests { assert_eq!(resolved_document, did_dht.document) } } - -#[cfg(test)] -mod web5_test_vectors_did_dht { - use crate::dids::resolution::resolution_metadata::ResolutionMetadataError; - use crate::{ - dids::resolution::resolution_metadata::ResolutionMetadata, test_helpers::TestVectorFile, - }; - - #[derive(Debug, PartialEq, serde::Deserialize)] - struct VectorInput { - #[serde(rename = "didUri")] - did_uri: String, - } - - #[derive(Debug, PartialEq, serde::Deserialize)] - struct VectorOutput { - #[serde(rename = "didResolutionMetadata")] - did_resolution_metadata: ResolutionMetadata, - } - - #[test] - fn resolve() { - let path = "did_dht/resolve.json"; - let vectors: TestVectorFile = - TestVectorFile::load_from_path(path); - - for vector in vectors.vectors { - let vector_input = vector.input; - let vector_output = &vector.output; - - // As a replay attack protection protocol, if the same DID is doing a GET request within 5 minutes of each other, instead of a 404 it will start returning a 429. - // to get around this for our test we just create a new DID that is not published to get a fresh 404 for this error code - if let Some(ResolutionMetadataError::NotFound) = - vector_output.did_resolution_metadata.error - { - // TODO: According to the did dht spec a 404 should be returned when trying to resolve a DID that does not exists. Currently it incorrectly returns a 429 even on the first call. - // Uncomment this code block when resolved - https://github.com/TBD54566975/web5-rs/issues/286 - continue; - - // let private_jwk = Ed25519Generator::generate(); - // let identity_key = ed25519::to_public_jwk(&private_jwk); - // let did_dht = - // DidDht::from_identity_key(identity_key.clone()).expect("Should create did:dht"); - // - // vector_input = VectorInput{ - // did_uri: did_dht.did.uri, - // }; - } - - let resolution_result = super::DidDht::resolve(&vector_input.did_uri); - - let metadata_error = resolution_result.resolution_metadata.error.as_ref(); - let expected_error = vector_output.did_resolution_metadata.error.as_ref(); - - assert_eq!( - metadata_error, expected_error, - "Document resolution metadata does not match. Expected '{:?}' but got '{:?}'.", - expected_error, metadata_error - ); - } - } -} diff --git a/crates/web5/src/dids/methods/did_jwk.rs b/crates/web5/src/dids/methods/did_jwk.rs index a7076d8b..d59e6d1e 100644 --- a/crates/web5/src/dids/methods/did_jwk.rs +++ b/crates/web5/src/dids/methods/did_jwk.rs @@ -115,71 +115,3 @@ impl DidJwk { } } } - -#[cfg(test)] -mod web5_test_vectors_did_jwk { - use crate::{ - dids::{ - data_model::document::Document, - resolution::{ - document_metadata::DocumentMetadata, resolution_metadata::ResolutionMetadata, - }, - }, - test_helpers::TestVectorFile, - }; - - #[derive(Debug, PartialEq, serde::Deserialize)] - struct VectorOutput { - #[serde(rename = "@context")] - context: String, - #[serde(rename = "didDocument")] - did_document: Option, - #[serde(rename = "didDocumentMetadata")] - did_document_metadata: DocumentMetadata, - #[serde(rename = "didResolutionMetadata")] - did_resolution_metadata: ResolutionMetadata, - } - - #[test] - fn resolve() { - let path = "did_jwk/resolve.json"; - let vectors: TestVectorFile = TestVectorFile::load_from_path(path); - - for vector in vectors.vectors { - let did_uri = vector.input; - let resolution_result = super::DidJwk::resolve(&did_uri); - - let all_none = vector.output.did_document_metadata.created.is_none() - && vector.output.did_document_metadata.updated.is_none() - && vector.output.did_document_metadata.deactivated.is_none() - && vector.output.did_document_metadata.next_update.is_none() - && vector.output.did_document_metadata.version_id.is_none() - && vector - .output - .did_document_metadata - .next_version_id - .is_none() - && vector.output.did_document_metadata.equivalent_id.is_none() - && vector.output.did_document_metadata.canonical_id.is_none(); - - let vector_document_metadata = if all_none { - None - } else { - Some(vector.output.did_document_metadata.clone()) - }; - - assert_eq!( - resolution_result.resolution_metadata, vector.output.did_resolution_metadata, - "Resolution metadata does not match." - ); - assert_eq!( - resolution_result.document, vector.output.did_document, - "DID Document does not match." - ); - assert_eq!( - resolution_result.document_metadata, vector_document_metadata, - "Document metadata does not match." - ); - } - } -} diff --git a/crates/web5/src/errors.rs b/crates/web5/src/errors.rs new file mode 100644 index 00000000..59d31ec4 --- /dev/null +++ b/crates/web5/src/errors.rs @@ -0,0 +1,17 @@ +use serde_json::Error as SerdeJsonError; + +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum Web5Error { + #[error("json error {0}")] + Json(String), + #[error("parameter error {0}")] + Parameter(String), +} + +impl From for Web5Error { + fn from(err: SerdeJsonError) -> Self { + Web5Error::Json(err.to_string()) + } +} + +pub(crate) type Result = std::result::Result; diff --git a/crates/web5/src/json.rs b/crates/web5/src/json.rs new file mode 100644 index 00000000..7f4a3aa3 --- /dev/null +++ b/crates/web5/src/json.rs @@ -0,0 +1,54 @@ +use crate::errors::{Result, Web5Error}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::HashMap; + +pub trait FromJson: Sized + DeserializeOwned { + fn from_json_string(json: &str) -> Result { + serde_json::from_str(json).map_err(Web5Error::from) + } +} + +pub trait ToJson: Serialize { + fn to_json_string(&self) -> Result { + serde_json::to_string(self).map_err(Web5Error::from) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(untagged)] +pub enum JsonValue { + Null, + Bool(bool), + Number(f64), + String(String), + Array(Vec), + Object(HashMap), +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct JsonObject { + #[serde(flatten)] + pub properties: HashMap, +} + +impl JsonObject { + pub fn new() -> Self { + Self { + properties: HashMap::new(), + } + } + + pub fn insert(&mut self, key: String, value: JsonValue) { + self.properties.insert(key, value); + } + + pub fn get(&self, key: &str) -> Option<&JsonValue> { + self.properties.get(key) + } +} + +impl Default for JsonObject { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/web5/src/lib.rs b/crates/web5/src/lib.rs index f4d67188..e0b15c9a 100644 --- a/crates/web5/src/lib.rs +++ b/crates/web5/src/lib.rs @@ -2,5 +2,11 @@ pub mod credentials; pub mod crypto; pub mod dids; +pub mod errors; +pub mod json; +pub mod rfc3339; + #[cfg(test)] mod test_helpers; +#[cfg(test)] +mod test_vectors; \ No newline at end of file diff --git a/crates/web5/src/rfc3339.rs b/crates/web5/src/rfc3339.rs new file mode 100644 index 00000000..9025ffad --- /dev/null +++ b/crates/web5/src/rfc3339.rs @@ -0,0 +1,55 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Deserializer, Serializer}; +use std::time::SystemTime; + +pub(crate) fn serialize_system_time( + time: &SystemTime, + serializer: S, +) -> std::result::Result +where + S: Serializer, +{ + let datetime: chrono::DateTime = (*time).into(); + let s = datetime.to_rfc3339(); + serializer.serialize_str(&s) +} + +pub(crate) fn deserialize_system_time<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let datetime = chrono::DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; + Ok(datetime.with_timezone(&Utc).into()) +} + +pub(crate) fn serialize_optional_system_time( + time: &Option, + serializer: S, +) -> std::result::Result +where + S: Serializer, +{ + match time { + Some(time) => serialize_system_time(time, serializer), + None => serializer.serialize_none(), + } +} + +pub(crate) fn deserialize_optional_system_time<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt = Option::::deserialize(deserializer)?; + match opt { + Some(s) => { + let datetime = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; + Ok(Some(datetime.with_timezone(&Utc).into())) + } + None => Ok(None), + } +} diff --git a/crates/web5/src/test_helpers.rs b/crates/web5/src/test_helpers.rs index 0a6271bc..224e0162 100644 --- a/crates/web5/src/test_helpers.rs +++ b/crates/web5/src/test_helpers.rs @@ -1,29 +1,55 @@ -use std::{fs, path::PathBuf}; +use std::{fs, sync::Mutex}; -use serde::de::DeserializeOwned; +#[macro_export] +macro_rules! test_name { + () => {{ + let current_fn = { + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] // Strip off "::f" + }; -#[derive(Debug, serde::Deserialize)] -pub struct TestVector { - pub description: String, - pub input: I, - pub output: O, + let test_name = current_fn.split("::").last().unwrap(); + test_name + }}; } -#[derive(Debug, serde::Deserialize)] -pub struct TestVectorFile { - pub description: String, - pub vectors: Vec>, +pub(crate) struct UnitTestSuite { + tests: Mutex>, } -impl TestVectorFile { - pub fn load_from_path(file_path: &str) -> TestVectorFile - where - I: DeserializeOwned, - O: DeserializeOwned, - { - let mut vector_path = PathBuf::from("../../web5-spec/test-vectors/"); - vector_path.push(file_path); - let data = fs::read_to_string(vector_path).unwrap(); - serde_json::from_str(&data).unwrap() +impl UnitTestSuite { + pub(crate) fn new(name: &str) -> Self { + let file_path = format!( + "{}/../../tests/unit_test_cases/{}.json", + env!("CARGO_MANIFEST_DIR"), + name + ); + let file_content = + fs::read_to_string(file_path).expect("Failed to read test cases JSON file"); + + Self { + tests: Mutex::new( + serde_json::from_str::>(&file_content) + .expect("Failed to parse test cases JSON file"), + ), + } + } + + pub(crate) fn include(&self, test_name: &str) { + let mut tests = self.tests.lock().unwrap(); + if let Some(pos) = tests.iter().position(|x| *x == test_name) { + tests.remove(pos); + } + } + + pub(crate) fn assert_coverage(&self) { + let tests = self.tests.lock().unwrap(); + if !tests.is_empty() { + panic!("The following test cases were not covered: {:?}", *tests); + } } } diff --git a/crates/web5/src/test_vectors.rs b/crates/web5/src/test_vectors.rs new file mode 100644 index 00000000..09d51d15 --- /dev/null +++ b/crates/web5/src/test_vectors.rs @@ -0,0 +1,210 @@ +use serde::de::DeserializeOwned; +use std::{fs, path::PathBuf}; + +#[derive(Debug, serde::Deserialize)] +pub struct TestVector { + pub description: String, + pub input: I, + pub output: O, +} + +#[derive(Debug, serde::Deserialize)] +pub struct TestVectorFile { + pub vectors: Vec>, +} + +impl TestVectorFile { + pub fn load_from_path(file_path: &str) -> TestVectorFile + where + I: DeserializeOwned, + O: DeserializeOwned, + { + let mut vector_path = PathBuf::from("../../web5-spec/test-vectors/"); + vector_path.push(file_path); + let data = fs::read_to_string(vector_path).unwrap(); + serde_json::from_str(&data).unwrap() + } +} + +#[cfg(test)] +mod test_vectors { + use super::*; + + mod did_jwk { + use super::*; + use crate::dids::{ + data_model::document::Document, + methods::did_jwk::DidJwk, + resolution::{ + document_metadata::DocumentMetadata, resolution_metadata::ResolutionMetadata, + }, + }; + + #[derive(Debug, PartialEq, serde::Deserialize)] + struct VectorOutput { + #[serde(rename = "@context")] + context: String, + #[serde(rename = "didDocument")] + did_document: Option, + #[serde(rename = "didDocumentMetadata")] + did_document_metadata: DocumentMetadata, + #[serde(rename = "didResolutionMetadata")] + did_resolution_metadata: ResolutionMetadata, + } + + #[test] + fn resolve() { + let path = "did_jwk/resolve.json"; + let vectors: TestVectorFile = + TestVectorFile::load_from_path(path); + + for vector in vectors.vectors { + let did_uri = vector.input; + let resolution_result = DidJwk::resolve(&did_uri); + + let all_none = vector.output.did_document_metadata.created.is_none() + && vector.output.did_document_metadata.updated.is_none() + && vector.output.did_document_metadata.deactivated.is_none() + && vector.output.did_document_metadata.next_update.is_none() + && vector.output.did_document_metadata.version_id.is_none() + && vector + .output + .did_document_metadata + .next_version_id + .is_none() + && vector.output.did_document_metadata.equivalent_id.is_none() + && vector.output.did_document_metadata.canonical_id.is_none(); + + let vector_document_metadata = if all_none { + None + } else { + Some(vector.output.did_document_metadata.clone()) + }; + + assert_eq!( + resolution_result.resolution_metadata, vector.output.did_resolution_metadata, + "Resolution metadata does not match." + ); + assert_eq!( + resolution_result.document, vector.output.did_document, + "DID Document does not match." + ); + assert_eq!( + resolution_result.document_metadata, vector_document_metadata, + "Document metadata does not match." + ); + } + } + } + + mod did_dht { + use super::*; + use crate::dids::{ + methods::did_dht::DidDht, + resolution::resolution_metadata::{ResolutionMetadata, ResolutionMetadataError}, + }; + + #[derive(Debug, PartialEq, serde::Deserialize)] + struct VectorInput { + #[serde(rename = "didUri")] + did_uri: String, + } + + #[derive(Debug, PartialEq, serde::Deserialize)] + struct VectorOutput { + #[serde(rename = "didResolutionMetadata")] + did_resolution_metadata: ResolutionMetadata, + } + + #[test] + fn resolve() { + let path = "did_dht/resolve.json"; + let vectors: TestVectorFile = + TestVectorFile::load_from_path(path); + + for vector in vectors.vectors { + let vector_input = vector.input; + let vector_output = &vector.output; + + // As a replay attack protection protocol, if the same DID is doing a GET request within 5 minutes of each other, instead of a 404 it will start returning a 429. + // to get around this for our test we just create a new DID that is not published to get a fresh 404 for this error code + if let Some(ResolutionMetadataError::NotFound) = + vector_output.did_resolution_metadata.error + { + // TODO: According to the did dht spec a 404 should be returned when trying to resolve a DID that does not exists. Currently it incorrectly returns a 429 even on the first call. + // Uncomment this code block when resolved - https://github.com/TBD54566975/web5-rs/issues/286 + continue; + + // let private_jwk = Ed25519Generator::generate(); + // let identity_key = ed25519::to_public_jwk(&private_jwk); + // let did_dht = + // DidDht::from_identity_key(identity_key.clone()).expect("Should create did:dht"); + // + // vector_input = VectorInput{ + // did_uri: did_dht.did.uri, + // }; + } + + let resolution_result = DidDht::resolve(&vector_input.did_uri); + + let metadata_error = resolution_result.resolution_metadata.error.as_ref(); + let expected_error = vector_output.did_resolution_metadata.error.as_ref(); + + assert_eq!( + metadata_error, expected_error, + "Document resolution metadata does not match. Expected '{:?}' but got '{:?}'.", + expected_error, metadata_error + ); + } + } + } + + mod presentation_exchange { + use super::*; + use crate::credentials::presentation_definition::PresentationDefinition; + use std::collections::HashSet; + + #[derive(Debug, serde::Deserialize)] + struct VectorInput { + #[serde(rename = "presentationDefinition")] + pub presentation_definition: PresentationDefinition, + #[serde(rename = "credentialJwts")] + pub credential_jwts: Vec, + } + + #[derive(Debug, serde::Deserialize)] + struct VectorOutput { + #[serde(rename = "selectedCredentials")] + pub selected_credentials: Vec, + } + + #[test] + #[ignore] // TODO temporarily ignoring, because web5-spec test vectors use did:key which isn't supported + fn select_credentials() { + let path = "presentation_exchange/select_credentials.json"; + let vectors: TestVectorFile = + TestVectorFile::load_from_path(path); + + for vector in vectors.vectors { + let presentation_definition = vector.input.presentation_definition; + let vc_jwts = vector.input.credential_jwts; + let error_msg = format!( + "Selected Credential test vector ({}) should not have thrown error", + vector.description + ); + + let selected_credentials = presentation_definition + .select_credentials(&vc_jwts) + .expect(&error_msg); + + let set1: HashSet<_> = selected_credentials.iter().collect(); + let set2: HashSet<_> = vector.output.selected_credentials.iter().collect(); + assert_eq!( + set1, set2, + "Vectors do not contain the same elements: {}", + error_msg + ); + } + } + } +} diff --git a/crates/web5_cli/src/vcs.rs b/crates/web5_cli/src/vcs.rs index fd0e32dc..f453f9f0 100644 --- a/crates/web5_cli/src/vcs.rs +++ b/crates/web5_cli/src/vcs.rs @@ -1,12 +1,12 @@ use chrono::{DateTime, Utc}; use clap::Subcommand; use std::time::SystemTime; -use uuid::Uuid; use web5::{ credentials::verifiable_credential_1_1::{ - CredentialSubject, Issuer, VerifiableCredential, BASE_CONTEXT, BASE_TYPE, + CredentialSubject, Issuer, VerifiableCredential, VerifiableCredentialCreateOptions, }, dids::{bearer_did::BearerDid, portable_did::PortableDid}, + json::ToJson, }; #[derive(Subcommand, Debug)] @@ -49,7 +49,7 @@ impl Commands { Some(i) => i.to_string(), None => match &portable_did { Some(p) => p.did_uri.to_string(), - None => String::default(), + None => panic!("either --issuer or --portable-did must be supplied"), }, }); let expiration_date = match expiration_date { @@ -62,22 +62,21 @@ impl Commands { }, }; - let now = SystemTime::now(); - let vc = VerifiableCredential::new( - format!("urn:vc:uuid:{0}", Uuid::new_v4()), - vec![BASE_CONTEXT.to_string()], - vec![BASE_TYPE.to_string()], + let vc = VerifiableCredential::create( issuer, - now, - expiration_date, CredentialSubject { id: credential_subject_id.to_string(), ..Default::default() }, - ); + Some(VerifiableCredentialCreateOptions { + expiration_date, + ..Default::default() + }), + ) + .unwrap(); let mut output_str = match no_indent { - true => serde_json::to_string(&vc).unwrap(), + true => vc.to_json_string().unwrap(), false => serde_json::to_string_pretty(&vc).unwrap(), }; diff --git a/docs/API_DESIGN.md b/docs/API_DESIGN.md index 1720d566..8d1a740e 100644 --- a/docs/API_DESIGN.md +++ b/docs/API_DESIGN.md @@ -4,15 +4,15 @@ **Version** 2.1.0 -**[Custom DSL](./CUSTOM_DSL.md) Version**: 0.1.0 +**[Custom DSL](./CUSTOM_DSL.md) Version**: 0.2.0 - [Credentials](#credentials) - [Verifiable Credentials (VCs)](#verifiable-credentials-vcs) - [Data Model 1.1](#data-model-11) - - [`NamedIssuer`](#namedissuer) - [`VerifiableCredential`](#verifiablecredential) - - [Example: Create a VC \& sign](#example-create-a-vc--sign) - - [Example: Verify a VC-JWT](#example-verify-a-vc-jwt) + - [`CredentialSubject`](#credentialsubject) + - [`Issuer`](#issuer) + - [`CreateOptions`](#createoptions) - [Presentation Exchange (PEX)](#presentation-exchange-pex) - [`PresentationDefinition`](#presentationdefinition) - [`InputDescriptor`](#inputdescriptor) @@ -72,67 +72,46 @@ ### Data Model 1.1 -#### `NamedIssuer` - -```pseudocode! -CLASS NamedIssuer - PUBLIC DATA id: string - PUBLIC DATA name: string -``` - -#### `VerifiableCredential` - > [!WARNING] -> The following is incomplete in that an `Object` is not currently supported in the Custom DSL; the matter of the `Object` below is a placeholder and expected to be completed in a subsequent version. +> We are currently missing `credentialStatus`, `credentialSchema` and `evidence` -> [!WARNING] -> We need to consider default behaviors such as always including the base `@context` and `type` +#### `VerifiableCredential` ```pseudocode! CLASS VerifiableCredential PUBLIC DATA @context: []string PUBLIC DATA id: string PUBLIC DATA type: []string - PUBLIC DATA issuer: string | NamedIssuer # 🚧 Multitype should be re-considered, perhaps should just be Object - PUBLIC DATA issuanceDate: string - PUBLIC DATA expirationDate: string? - PUBLIC DATA credentialSubject: Object # 🚧 `Object` not supported 🚧 - CONSTRUCTOR(context: []string, id: string, type: []string, issuer: string | NamedIssuer, issuanceDate: string, expirationDate: string?) + PUBLIC DATA issuer: Issuer + PUBLIC DATA issuance_date: datetime + PUBLIC DATA expiration_date: datetime? + PUBLIC DATA credentialSubject: CredentialSubject + + CONSTRUCTOR create(issuer: Issuer, credential_subject: CredentialSubject, options: CreateOptions) + CONSTRUCTOR(vcjwt: string) CONSTRUCTOR(vcjwt: string, verifier: Verifier) METHOD sign(bearer_did: BearerDid): string METHOD sign_with_signer(key_id: string, signer: Signer): string ``` -> [!NOTE] -> -> `CONSTRUCTOR(vcjwt: string)` and `CONSTRUCTOR(vcjwt: string, verifier: Verifier)` both execute cryprographic verification and assume `vcjwt` is a compact serialized JWS wherein the `kid` JOSE Header is equal to a DID URI which can be dereferenced to fetch the [`publicKeyJwk`](./did.md#data-models). - -##### Example: Create a VC & sign - -```pseudocode! -key_manager = new InMemoryKeyManager() -public_jwk = key_manager.import_private_jwk(Ed25519Generator::generate()) - -did_jwk = new DidJwk(public_jwk) +##### `CredentialSubject` -context = ["https://www.w3.org/2018/credentials/v1"] -id = "urn:vc:uuid:123456" -type = ["VerifiableCredential"] -issuer = did_jwk.did.uri -issuance_date = DateTime.now() -vc = new VerifiableCredential(context, id, type, issuer, issuance_date, null) +`Object` with at least a non-empty `id: string` data member. -signer = key_manager.get_signer(public_jwk) -vcjwt = vc.sign(signer) -``` +##### `Issuer` -##### Example: Verify a VC-JWT +`Object` or `string`, and if `Object` then at least non-empty `id: string` and `name: string` data members. -```pseudocode! -vcjwt = "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaU5XOUNaRmhNTjNSRFdDMWlXbXd3Tm5VNVdXUlNXakJhYWxKTExVcHhWV1poWmtWM1owMHRUR0ptYXlKOSMwIiwidHlwIjoiSldUIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJpZCI6InVybjp2Yzp1dWlkOmUzMDc0OWVhLTg4YjctNDkwMi05ZTRlLWYwYjk1MTRjZmU1OSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmp3azpleUpoYkdjaU9pSkZaRVJUUVNJc0ltTnlkaUk2SWtWa01qVTFNVGtpTENKcmRIa2lPaUpQUzFBaUxDSjRJam9pTlc5Q1pGaE1OM1JEV0MxaVdtd3dOblU1V1dSU1dqQmFhbEpMTFVweFZXWmhaa1YzWjAwdFRHSm1heUo5IiwiaXNzdWFuY2VEYXRlIjoxNzE2MzEyNDU3LCJleHBpcmF0aW9uRGF0ZSI6MjM0NzQ2NDQ1NywiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlpFUlRRU0lzSW1OeWRpSTZJa1ZrTWpVMU1Ua2lMQ0pyZEhraU9pSlBTMUFpTENKNElqb2lOVzlDWkZoTU4zUkRXQzFpV213d05uVTVXV1JTV2pCYWFsSkxMVXB4VldaaFprVjNaMDB0VEdKbWF5SjkifX0sImlzcyI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaU5XOUNaRmhNTjNSRFdDMWlXbXd3Tm5VNVdXUlNXakJhYWxKTExVcHhWV1poWmtWM1owMHRUR0ptYXlKOSIsInN1YiI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaU5XOUNaRmhNTjNSRFdDMWlXbXd3Tm5VNVdXUlNXakJhYWxKTExVcHhWV1poWmtWM1owMHRUR0ptYXlKOSIsImV4cCI6MjM0NzQ2NDQ1NywibmJmIjoxNzE2MzEyNDU3LCJqdGkiOiJ1cm46dmM6dXVpZDplMzA3NDllYS04OGI3LTQ5MDItOWU0ZS1mMGI5NTE0Y2ZlNTkifQ.a8ciqXyNgqttWPKl76CFwDTRvEoJEq5nndfM1UMkClvzhPOUWSUtE0wNHOxQFwUBBSbwozScBNe-dc-mWQFqAQ" +##### `CreateOptions` -vc = VerifiableCredential.verify(vcjwt) +```psuedocode! +CLASS CreateOptions + PUBLIC DATA id: string? + PUBLIC DATA context: []string? + PUBLIC DATA type: []string? + PUBLIC DATA issuance_date: datetime? + PUBLIC DATA expiration_date: datetime? ``` ## Presentation Exchange (PEX) diff --git a/docs/CUSTOM_DSL.md b/docs/CUSTOM_DSL.md index 7a94a076..dff8fd45 100644 --- a/docs/CUSTOM_DSL.md +++ b/docs/CUSTOM_DSL.md @@ -2,7 +2,7 @@ **Last Updated:** May 30, 2024 -**Version:** 0.1.0 +**Version:** 0.2.0 The design definitions within the Custom DSL are intended to span any programming language, so long as the given programming language supports the [High-Level Concepts](#high-level-concepts) and [Primitive Concepts](#primitive-concepts) in one form or another. The instantiations of these concepts will be unique to the given idioms of the target programming language. @@ -12,6 +12,7 @@ The design definitions within the Custom DSL are intended to span any programmin - [Polymorphic Base Class](#polymorphic-base-class) - [Class](#class) - [Enumeration](#enumeration) + - [Function](#function) # Limitations @@ -19,13 +20,9 @@ In order to achieve the goal of defining concrete design definitions which span The Custom DSL does not assert requirements as to the artifact makeup (i.e. npm packages, rust crates, go modules, etc.) of the API. It is recommended to implement the entirety of an API design in a single artifact, but each implementation may choose to create multiple artifacts. However, the APID makes no regards for the matter of circular dependencies, and so it may become unviable to implement the APID in it's completeness across multiple artifacts. -The Custom DSL does not offer a primitive type conceptually equivalent to a JSON Object wherein the concept is a hash map with N-number of distinct value types, where N > 1. In other words, the Custom DSL offers a hash map primitive -concept, but the key and value types are considered to uniform over the entirety of the hash map. - > [!WARNING] > Concepts required but missing: > - Errors. -> - JSON Object (see above paragraph). > - JSON serialization naming. > - Namespacing. @@ -42,6 +39,8 @@ concept, but the key and value types are considered to uniform over the entirety | hash map | `Map` | | function | `func_name(param1: T1, param2: T2): T3` | | mixed type | `T1 \| T2` | +| object | `Object` | +| datetime | `datetime` | # High-Level Concepts @@ -52,7 +51,7 @@ concept, but the key and value types are considered to uniform over the entirety **Example** -```psuedocode +```psuedocode! INTERFACE Shape METHOD area(): int METHOD perimeter(): int @@ -72,7 +71,7 @@ INTERFACE Shape **Example** -```psuedocode +```psuedocode! CLASS Circle IMPLEMENTS Shape PUBLIC DATA radius: int CONSTRUCTOR(radius: int) @@ -90,9 +89,19 @@ CLASS Circle IMPLEMENTS Shape **Example:** -```psuedocode +```psuedocode! ENUM Color RED GREEN BLUE ``` + +## Function + +- `FUNCTION functionName(param: T1): T2`: Defines a function + +**Example:** + +```pseudocode! +FUNCTION someFunction(p1: string, p2: Object): bool +``` \ No newline at end of file diff --git a/tests/unit_test_cases/verifiable_credential_1_1_create.json b/tests/unit_test_cases/verifiable_credential_1_1_create.json new file mode 100644 index 00000000..1d6064da --- /dev/null +++ b/tests/unit_test_cases/verifiable_credential_1_1_create.json @@ -0,0 +1,22 @@ +[ + "test_default_context_added_if_not_supplied", + "test_default_context_not_duplicated_if_supplied", + "test_developer_provided_context_appended_to_default", + "test_default_type_added_if_not_supplied", + "test_default_type_not_duplicated_if_supplied", + "test_developer_provided_type_appended_to_default", + "test_id_generated_if_not_supplied", + "test_id_must_be_set_if_supplied", + "test_issuer_string_must_not_be_empty", + "test_issuer_string_must_be_set", + "test_issuer_object_id_must_not_be_empty", + "test_issuer_object_name_must_not_be_empty", + "test_issuer_object_must_be_set", + "test_issuer_object_supports_additional_properties", + "test_credential_subject_id_must_not_be_empty", + "test_credential_subject_must_be_set", + "test_credential_subject_supports_additional_properties", + "test_issuance_date_must_be_set", + "test_issuance_date_must_be_now_if_not_supplied", + "test_expiration_date_must_be_set_if_supplied" +]