From c78c1dad3277baf67be6875945959f9f0af14261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Mon, 3 Jun 2024 17:00:17 +0200 Subject: [PATCH 01/34] Add bbs2023 suite. --- .../data-integrity/suites/src/suites/w3c.rs | 3 + .../suites/src/suites/w3c/bbs_2023.rs | 136 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs index 1af461aa4..cd89fa5b1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs @@ -47,3 +47,6 @@ pub use ecdsa_secp256r1_signature_2019::EcdsaSecp256r1Signature2019; pub mod json_web_signature_2020; pub use json_web_signature_2020::JsonWebSignature2020; + +pub mod bbs_2023; +pub use bbs_2023::Bbs2023; \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs new file mode 100644 index 000000000..e3b6a3e76 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs @@ -0,0 +1,136 @@ +//! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. +//! +//! See: +use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_verification_methods::Multikey; +use ssi_data_integrity_core::{suite::{standard::{HashingAlgorithm, HashingError, SignatureAlgorithm, SignatureAndVerificationAlgorithm, TransformationAlgorithm, TransformationError, TransformedData, TypedTransformationAlgorithm, VerificationAlgorithm}, ConfigurationAlgorithm, ConfigurationError, InputOptions}, ProofConfiguration, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef}; + +/// The `bbs-2023` cryptographic suite. +#[derive(Debug, Clone, Copy)] +pub struct Bbs2023; + +impl StandardCryptographicSuite for Bbs2023 { + type Configuration = Bbs2023Configuration; + + type Transformation = Bbs2023Transformation; + + type Hashing = Bbs2023Hashing; + + type VerificationMethod = Multikey; + + type ProofOptions = Bbs2023Options; + + type SignatureAlgorithm = Bbs2023SignatureAlgorithm; + + fn type_(&self) -> TypeRef { + TypeRef::DataIntegrityProof("bbs-2023") + } +} + +pub struct Bbs2023InputOptions { + pub mandatory_pointers: Vec, + + pub feature_option: FeatureOption, + + pub commitment_with_proof: Option>, + + pub proof_options: Bbs2023Options +} + +pub struct MandatoryPointer; + +#[derive(Debug, Default, Clone, Copy)] +pub enum FeatureOption { + #[default] + Baseline, + AnonymousHolderBinding, + PseudonymIssuerPid, + PseudonymHidderPid +} + +/// Base Proof Configuration. +/// +/// See: +pub struct Bbs2023Configuration; + +impl ConfigurationAlgorithm for Bbs2023Configuration { + type InputSuiteOptions = Bbs2023InputOptions; + type InputVerificationMethod = Multikey; + + fn configure( + _: &Bbs2023, + options: InputOptions, + ) -> Result, ConfigurationError> { + todo!() + } +} + +pub struct Bbs2023Transformation; + +impl TransformationAlgorithm for Bbs2023Transformation { + type Output = Transformed; +} + +impl TypedTransformationAlgorithm for Bbs2023Transformation { +async fn transform( + context: &mut C, + data: &T, + options: ProofConfigurationRef<'_, Bbs2023>, + ) -> Result { + todo!() + } +} + +pub struct Transformed; + +pub struct Bbs2023Hashing; + +impl HashingAlgorithm for Bbs2023Hashing { + type Output = Hashed; + + fn hash( + input: TransformedData, + proof_configuration: ProofConfigurationRef, + ) -> Result { + todo!() + } +} + +pub struct Hashed; + +pub struct Bbs2023Options; + +pub struct Bbs2023SignatureAlgorithm; + +impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { + type Signature = Bbs2023Signature; +} + +impl SignatureAlgorithm for Bbs2023SignatureAlgorithm { + async fn sign( + verification_method: &Multikey, + signer: T, + prepared_claims: &Hashed, + proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + ) -> Result { + todo!() + } +} + +impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { + fn verify( + method: &Multikey, + prepared_claims: &Hashed, + proof: ProofRef, + ) -> Result { + todo!() + } +} + +pub struct Bbs2023Signature; + +impl AsRef for Bbs2023Signature { + fn as_ref(&self) -> &str { + todo!() + } +} \ No newline at end of file From 2ed5b1056344264674bb7dc3d284634ae5194202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 5 Jun 2024 17:46:27 +0200 Subject: [PATCH 02/34] BBS transformation algorithm done. --- Cargo.toml | 1 + .../core/src/canonicalization.rs | 3 +- .../core/src/suite/configuration.rs | 46 +- .../data-integrity/core/src/suite/mod.rs | 19 +- .../core/src/suite/signature.rs | 3 +- .../core/src/suite/standard/mod.rs | 23 +- .../core/src/suite/standard/transformation.rs | 9 +- .../crates/data-integrity/src/any/macros.rs | 38 +- .../crates/data-integrity/suites/Cargo.toml | 6 + .../unspecified/eip712_signature_2021.rs | 1 + .../data-integrity/suites/src/suites/w3c.rs | 2 +- .../suites/src/suites/w3c/bbs_2023.rs | 136 ---- .../suites/src/suites/w3c/bbs_2023/hashing.rs | 64 ++ .../src/suites/w3c/bbs_2023/json_pointer.rs | 155 ++++ .../suites/src/suites/w3c/bbs_2023/mod.rs | 138 ++++ .../src/suites/w3c/bbs_2023/signature.rs | 144 ++++ .../src/suites/w3c/bbs_2023/transformation.rs | 730 ++++++++++++++++++ .../src/suites/w3c/bbs_2023/verification.rs | 29 + .../w3c/ethereum_eip712_signature_2021.rs | 3 +- crates/crypto/Cargo.toml | 4 +- .../json-ld/src/{context.rs => contexts.rs} | 1 - crates/json-ld/src/lib.rs | 11 +- crates/jwk/Cargo.toml | 2 +- crates/rdf/src/expand.rs | 9 + crates/verification-methods/core/src/lib.rs | 2 +- crates/zcap-ld/src/lib.rs | 18 +- 26 files changed, 1401 insertions(+), 196 deletions(-) delete mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs rename crates/json-ld/src/{context.rs => contexts.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index 61c35c746..246997578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ futures = "0.3.28" linked-data = "0.1.2" rand = "0.8" rand_chacha = "0.3.1" +getrandom = "0.2" contextual = "0.1.6" lazy_static = "1.4.0" diff --git a/crates/claims/crates/data-integrity/core/src/canonicalization.rs b/crates/claims/crates/data-integrity/core/src/canonicalization.rs index 87902e48b..0c41007a6 100644 --- a/crates/claims/crates/data-integrity/core/src/canonicalization.rs +++ b/crates/claims/crates/data-integrity/core/src/canonicalization.rs @@ -6,7 +6,7 @@ use ssi_rdf::{AnyLdEnvironment, LdEnvironment}; use crate::{ hashing::ConcatOutputSize, - suite::standard::{self, HashingAlgorithm, TransformationAlgorithm, TransformationError}, + suite::{standard::{self, HashingAlgorithm, TransformationAlgorithm, TransformationError}, TransformationOptions}, CryptographicSuite, ProofConfigurationRef, SerializeCryptographicSuite, StandardCryptographicSuite, }; @@ -36,6 +36,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, + _transformation_options: Option>, ) -> Result { let mut ld = LdEnvironment::default(); diff --git a/crates/claims/crates/data-integrity/core/src/suite/configuration.rs b/crates/claims/crates/data-integrity/core/src/suite/configuration.rs index 9c9de1dfd..a0eb2ed1e 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/configuration.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/configuration.rs @@ -8,9 +8,15 @@ use crate::{CryptographicSuite, ProofConfiguration, ProofOptions}; pub type InputVerificationMethod = <::Configuration as ConfigurationAlgorithm>::InputVerificationMethod; pub type InputSuiteOptions = - <::Configuration as ConfigurationAlgorithm>::InputSuiteOptions; + <::Configuration as ConfigurationAlgorithm>::InputProofOptions; -pub type InputOptions = ProofOptions, InputSuiteOptions>; +pub type InputProofOptions = ProofOptions, InputSuiteOptions>; + +pub type InputSignatureOptions = + <::Configuration as ConfigurationAlgorithm>::InputSignatureOptions; + +pub type TransformationOptions = + <::Configuration as ConfigurationAlgorithm>::TransformationOptions; #[derive(Debug, thiserror::Error)] pub enum ConfigurationError { @@ -43,26 +49,39 @@ impl From for SignatureError { } pub trait ConfigurationAlgorithm { + /// Input type for the verification method. type InputVerificationMethod; - type InputSuiteOptions; + + /// Input suite-specific proof options. + type InputProofOptions; + + /// Input signature options. + type InputSignatureOptions; + + /// Document transformation options. + type TransformationOptions; fn configure( suite: &S, - options: ProofOptions, - ) -> Result, ConfigurationError>; + proof_options: ProofOptions, + signature_options: Self::InputSignatureOptions, + ) -> Result<(ProofConfiguration, Self::TransformationOptions), ConfigurationError>; } pub struct NoConfiguration; impl ConfigurationAlgorithm for NoConfiguration { type InputVerificationMethod = S::VerificationMethod; - type InputSuiteOptions = S::ProofOptions; + type InputProofOptions = S::ProofOptions; + type InputSignatureOptions = (); + type TransformationOptions = (); fn configure( suite: &S, - options: ProofOptions, - ) -> Result, ConfigurationError> { - options.into_configuration(suite.clone()) + proof_options: ProofOptions, + _: (), + ) -> Result<(ProofConfiguration, ()), ConfigurationError> { + Ok((proof_options.into_configuration(suite.clone())?, ())) } } @@ -73,12 +92,15 @@ where C: Default + Into, { type InputVerificationMethod = S::VerificationMethod; - type InputSuiteOptions = S::ProofOptions; + type InputProofOptions = S::ProofOptions; + type InputSignatureOptions = (); + type TransformationOptions = (); fn configure( suite: &S, options: ProofOptions, - ) -> Result, ConfigurationError> { + _: (), + ) -> Result<(ProofConfiguration, ()), ConfigurationError> { let mut result = options.into_configuration(suite.clone())?; result.context = match result.context { None => Some(C::default().into()), @@ -86,6 +108,6 @@ where c.into_iter().chain(C::default().into()).collect(), )), }; - Ok(result) + Ok((result, ())) } } diff --git a/crates/claims/crates/data-integrity/core/src/suite/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/mod.rs index 294e32ea8..4be7a301a 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -60,9 +60,10 @@ pub trait CryptographicSuite: Clone { /// Generates a proof configuration from input options. fn configure( &self, - options: InputOptions, - ) -> Result, ConfigurationError> { - Self::Configuration::configure(self, options) + proof_options: InputProofOptions, + signature_options: InputSignatureOptions, + ) -> Result<(ProofConfiguration, TransformationOptions), ConfigurationError> { + Self::Configuration::configure(self, proof_options, signature_options) } /// Generates a verifiable document secured with this cryptographic suite. @@ -73,12 +74,13 @@ pub trait CryptographicSuite: Clone { unsecured_document: T, resolver: R, signer: S, - options: InputOptions, + proof_options: InputProofOptions, + signature_options: InputSignatureOptions, ) -> Result, SignatureError> where Self: CryptographicSuiteSigning, { - let proof_configuration = self.configure(options)?; + let (proof_configuration, transformation_options) = self.configure(proof_options, signature_options)?; let proof_configuration_ref = proof_configuration.borrowed(); let signature = self .generate_signature( @@ -87,6 +89,7 @@ pub trait CryptographicSuite: Clone { signer, &unsecured_document, proof_configuration_ref, + transformation_options ) .await?; @@ -101,17 +104,19 @@ pub trait CryptographicSuite: Clone { unsecured_document: T, resolver: R, signer: S, - options: InputOptions, + proof_options: InputProofOptions, ) -> Result, SignatureError> where Self: CryptographicSuiteSigning, + InputSignatureOptions: Default, { self.sign_with( SignatureEnvironment::default(), unsecured_document, resolver, signer, - options, + proof_options, + Default::default() ) .await } diff --git a/crates/claims/crates/data-integrity/core/src/suite/signature.rs b/crates/claims/crates/data-integrity/core/src/suite/signature.rs index 2df7a3635..fe7e4a2cb 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/signature.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/signature.rs @@ -1,7 +1,7 @@ use crate::ProofConfigurationRef; use ssi_claims_core::SignatureError; -use super::CryptographicSuite; +use super::{CryptographicSuite, TransformationOptions}; pub trait CryptographicSuiteSigning: CryptographicSuite { #[allow(async_fn_in_trait)] @@ -12,5 +12,6 @@ pub trait CryptographicSuiteSigning: CryptographicSuite { signer: S, claims: &T, proof_configuration: ProofConfigurationRef<'_, Self>, + transformation_options: TransformationOptions ) -> Result; } diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs index c2cfb92ea..0559824dc 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs @@ -18,7 +18,7 @@ pub use signature::*; mod verification; pub use verification::*; -use super::{ConfigurationAlgorithm, CryptographicSuiteSigning, CryptographicSuiteVerification}; +use super::{ConfigurationAlgorithm, CryptographicSuiteSigning, CryptographicSuiteVerification, TransformationOptions}; // mod test_bbs; @@ -55,12 +55,19 @@ pub trait StandardCryptographicSuite: Clone { &self, context: &C, unsecured_document: &T, - options: ProofConfigurationRef<'_, Self>, + proof_configuration: ProofConfigurationRef<'_, Self>, + transformation_options: Option>, ) -> Result, TransformationError> where Self::Transformation: TypedTransformationAlgorithm, { - Self::Transformation::transform(context, unsecured_document, options).await + Self::Transformation::transform( + context, + unsecured_document, + proof_configuration, + transformation_options, + ) + .await } fn hash( @@ -117,6 +124,7 @@ where signers: T, claims: &C, proof_configuration: ProofConfigurationRef<'_, Self>, + transformation_options: TransformationOptions ) -> Result { let options = ssi_verification_methods_core::ResolutionOptions { accept: Some(Box::new(Self::VerificationMethod::type_set())), @@ -131,7 +139,12 @@ where ) .await?; - let transformed = self.transform(context, claims, proof_configuration).await?; + let transformed = self.transform( + context, + claims, + proof_configuration, + Some(transformation_options) + ).await?; let hashed = self.hash(transformed, proof_configuration, &method)?; @@ -171,7 +184,7 @@ where let proof_configuration = proof.configuration(); let transformed = self - .transform(verifier, claims, proof_configuration) + .transform(verifier, claims, proof_configuration, None) .await?; let hashed = self.hash(transformed, proof_configuration, &method)?; diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs index f4929f735..bcfa8f928 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs @@ -2,7 +2,10 @@ use linked_data::IntoQuadsError; use serde::Serialize; use ssi_claims_core::{ProofValidationError, SignatureError}; -use crate::{ConfigurationExpansionError, CryptographicSuite, ProofConfigurationRef}; +use crate::{ + suite::TransformationOptions, ConfigurationExpansionError, CryptographicSuite, + ProofConfigurationRef, +}; use super::StandardCryptographicSuite; @@ -64,7 +67,8 @@ pub trait TypedTransformationAlgorithm: async fn transform( context: &C, data: &T, - options: ProofConfigurationRef, + proof_configuration: ProofConfigurationRef, + transformation_options: Option>, ) -> Result; } @@ -81,6 +85,7 @@ impl TypedTransformationAlgorithm, + _transformation_options: Option>, ) -> Result { json_syntax::to_value(data) .map_err(TransformationError::JsonSerialization)? diff --git a/crates/claims/crates/data-integrity/src/any/macros.rs b/crates/claims/crates/data-integrity/src/any/macros.rs index 81cc6093d..f53bf8706 100644 --- a/crates/claims/crates/data-integrity/src/any/macros.rs +++ b/crates/claims/crates/data-integrity/src/any/macros.rs @@ -49,8 +49,8 @@ macro_rules! crypto_suites { #[allow(unused)] trait Project: ssi_data_integrity_core::CryptographicSuite { fn project_input_options( - options: ssi_data_integrity_core::suite::InputOptions - ) -> Result, ssi_data_integrity_core::suite::ConfigurationError>; + options: ssi_data_integrity_core::suite::InputProofOptions + ) -> Result, ssi_data_integrity_core::suite::ConfigurationError>; fn project_prepared_claims( prepared_claims: &Self::PreparedClaims @@ -143,6 +143,7 @@ macro_rules! crypto_suites { signer: S, claims: &T, proof_configuration: ssi_data_integrity_core::ProofConfigurationRef<'_, Self>, + transformation_options: ssi_data_integrity_core::suite::TransformationOptions ) -> Result { match self { $( @@ -157,7 +158,8 @@ macro_rules! crypto_suites { resolver, signer, claims, - Self::project_proof_configuration(proof_configuration) + Self::project_proof_configuration(proof_configuration), + transformation_options ).await.map(AnySignature::$name) }, )* @@ -396,9 +398,15 @@ macro_rules! crypto_suites { #[allow(unused_variables)] impl ssi_data_integrity_core::suite::ConfigurationAlgorithm for AnyConfigurationAlgorithm { type InputVerificationMethod = ssi_verification_methods::AnyMethod; - type InputSuiteOptions = crate::AnyInputSuiteOptions; + type InputProofOptions = crate::AnyInputSuiteOptions; + type InputSignatureOptions = (); + type TransformationOptions = (); - fn configure(suite: &AnySuite, options: ssi_data_integrity_core::suite::InputOptions) -> Result, ssi_data_integrity_core::suite::ConfigurationError> { + fn configure( + suite: &AnySuite, + options: ssi_data_integrity_core::suite::InputProofOptions, + signature_options: () + ) -> Result<(ssi_data_integrity_core::ProofConfiguration, ()), ssi_data_integrity_core::suite::ConfigurationError> { match suite { $( $(#[cfg($($t)*)])? @@ -407,22 +415,26 @@ macro_rules! crypto_suites { options )?; - let proof_configuration = ::configure( + let (proof_configuration, transformation_options) = ::configure( &ssi_data_integrity_suites::$name, - options + options, + signature_options )?; - Ok(proof_configuration.map( + Ok((proof_configuration.map( |_| AnySuite::$name, |m| AnySuiteVerificationMethod::$name(m), |o| AnyProofOptions::$name(o) - )) + ), transformation_options)) }, )* - AnySuite::Unknown(_) => options.map( - |m| AnySuiteVerificationMethod::Unknown(m.into()), - |_| AnyProofOptions::Unknown - ).into_configuration(suite.clone()) + AnySuite::Unknown(_) => Ok(( + options.map( + |m| AnySuiteVerificationMethod::Unknown(m.into()), + |_| AnyProofOptions::Unknown + ).into_configuration(suite.clone())?, + () + )) } } } diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index b89035e13..04bb11b7a 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -127,6 +127,8 @@ futures.workspace = true self_cell = "1.0.1" contextual.workspace = true lazy_static.workspace = true +rand.workspace = true +getrandom.workspace = true bs58 = { workspace = true, optional = true } base64.workspace = true @@ -142,6 +144,10 @@ json-syntax = { workspace = true, features = ["canonicalize"] } serde_json = { workspace = true, optional = true } serde_jcs = { workspace = true, optional = true } +# BBS +bbs_plus = "0.20.0" +hmac = "0.12.1" + [dev-dependencies] async-std = { version = "1.9", features = ["attributes"] } serde_json.workspace = true diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs index d340ae3b8..60dfdb26a 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs @@ -112,6 +112,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, Eip712Signature2021>, + _transformation_options: Option<()>, ) -> Result { let mut ld = LdEnvironment::default(); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs index cd89fa5b1..9467994ac 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs @@ -49,4 +49,4 @@ pub mod json_web_signature_2020; pub use json_web_signature_2020::JsonWebSignature2020; pub mod bbs_2023; -pub use bbs_2023::Bbs2023; \ No newline at end of file +pub use bbs_2023::Bbs2023; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs deleted file mode 100644 index e3b6a3e76..000000000 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. -//! -//! See: -use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; -use ssi_verification_methods::Multikey; -use ssi_data_integrity_core::{suite::{standard::{HashingAlgorithm, HashingError, SignatureAlgorithm, SignatureAndVerificationAlgorithm, TransformationAlgorithm, TransformationError, TransformedData, TypedTransformationAlgorithm, VerificationAlgorithm}, ConfigurationAlgorithm, ConfigurationError, InputOptions}, ProofConfiguration, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef}; - -/// The `bbs-2023` cryptographic suite. -#[derive(Debug, Clone, Copy)] -pub struct Bbs2023; - -impl StandardCryptographicSuite for Bbs2023 { - type Configuration = Bbs2023Configuration; - - type Transformation = Bbs2023Transformation; - - type Hashing = Bbs2023Hashing; - - type VerificationMethod = Multikey; - - type ProofOptions = Bbs2023Options; - - type SignatureAlgorithm = Bbs2023SignatureAlgorithm; - - fn type_(&self) -> TypeRef { - TypeRef::DataIntegrityProof("bbs-2023") - } -} - -pub struct Bbs2023InputOptions { - pub mandatory_pointers: Vec, - - pub feature_option: FeatureOption, - - pub commitment_with_proof: Option>, - - pub proof_options: Bbs2023Options -} - -pub struct MandatoryPointer; - -#[derive(Debug, Default, Clone, Copy)] -pub enum FeatureOption { - #[default] - Baseline, - AnonymousHolderBinding, - PseudonymIssuerPid, - PseudonymHidderPid -} - -/// Base Proof Configuration. -/// -/// See: -pub struct Bbs2023Configuration; - -impl ConfigurationAlgorithm for Bbs2023Configuration { - type InputSuiteOptions = Bbs2023InputOptions; - type InputVerificationMethod = Multikey; - - fn configure( - _: &Bbs2023, - options: InputOptions, - ) -> Result, ConfigurationError> { - todo!() - } -} - -pub struct Bbs2023Transformation; - -impl TransformationAlgorithm for Bbs2023Transformation { - type Output = Transformed; -} - -impl TypedTransformationAlgorithm for Bbs2023Transformation { -async fn transform( - context: &mut C, - data: &T, - options: ProofConfigurationRef<'_, Bbs2023>, - ) -> Result { - todo!() - } -} - -pub struct Transformed; - -pub struct Bbs2023Hashing; - -impl HashingAlgorithm for Bbs2023Hashing { - type Output = Hashed; - - fn hash( - input: TransformedData, - proof_configuration: ProofConfigurationRef, - ) -> Result { - todo!() - } -} - -pub struct Hashed; - -pub struct Bbs2023Options; - -pub struct Bbs2023SignatureAlgorithm; - -impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { - type Signature = Bbs2023Signature; -} - -impl SignatureAlgorithm for Bbs2023SignatureAlgorithm { - async fn sign( - verification_method: &Multikey, - signer: T, - prepared_claims: &Hashed, - proof_configuration: ProofConfigurationRef<'_, Bbs2023>, - ) -> Result { - todo!() - } -} - -impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { - fn verify( - method: &Multikey, - prepared_claims: &Hashed, - proof: ProofRef, - ) -> Result { - todo!() - } -} - -pub struct Bbs2023Signature; - -impl AsRef for Bbs2023Signature { - fn as_ref(&self) -> &str { - todo!() - } -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs new file mode 100644 index 000000000..205a49615 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs @@ -0,0 +1,64 @@ +use k256::sha2::{Digest, Sha256}; +use ssi_data_integrity_core::{ + suite::standard::{HashingAlgorithm, HashingError, TransformedData}, + ProofConfigurationRef, +}; +use ssi_rdf::IntoNQuads; + +use crate::Bbs2023; + +use super::{ + transformation::{TransformedBase, TransformedDerived}, + Transformed, +}; + +pub struct Bbs2023Hashing; + +impl HashingAlgorithm for Bbs2023Hashing { + type Output = HashData; + + fn hash( + input: TransformedData, + _proof_configuration: ProofConfigurationRef, + ) -> Result { + match input { + Transformed::Base(t) => { + // Base Proof Hashing algorithm. + // See: + let proof_hash = t + .canonical_configuration + .iter() + .fold(Sha256::new(), |h, line| h.chain_update(line.as_bytes())) + .finalize() + .into(); + + let mandatory_hash = t + .mandatory + .iter() + .into_nquads_lines() + .into_iter() + .fold(Sha256::new(), |h, line| h.chain_update(line.as_bytes())) + .finalize() + .into(); + + Ok(HashData::Base(BaseHashData { + transformed_document: t, + proof_hash, + mandatory_hash, + })) + } + Transformed::Derived(t) => Ok(HashData::Derived(t)), + } + } +} + +pub enum HashData { + Base(BaseHashData), + Derived(TransformedDerived), +} + +pub struct BaseHashData { + pub transformed_document: TransformedBase, + pub proof_hash: [u8; 32], + pub mandatory_hash: [u8; 32], +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs new file mode 100644 index 000000000..1258a15ce --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs @@ -0,0 +1,155 @@ +use std::{borrow::Cow, ops::Deref}; + +use serde::Serialize; + +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("invalid JSON pointer `{0}`")] +pub struct InvalidJsonPointer(pub T); + +/// JSON Pointer. +/// +/// See: +#[derive(Debug, Serialize)] +pub struct JsonPointer(str); + +impl JsonPointer { + pub fn new(s: &str) -> Result<&Self, InvalidJsonPointer<&str>> { + if Self::validate(s) { + Ok(unsafe { Self::new_unchecked(s) }) + } else { + Err(InvalidJsonPointer(s)) + } + } + + pub unsafe fn new_unchecked(s: &str) -> &Self { + std::mem::transmute(s) + } + + pub fn validate(str: &str) -> bool { + let mut chars = str.chars(); + while let Some(c) = chars.next() { + if c == '~' && !matches!(chars.next(), Some('0' | '1')) { + return false; + } + } + + true + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn split_first(&self) -> Option<(&ReferenceToken, &Self)> { + if self.is_empty() { + None + } else { + let after_sep = &self.0[1..]; + let (token, rest) = after_sep.split_once('/').unwrap_or((after_sep, "")); + Some(unsafe { + ( + ReferenceToken::new_unchecked(token), + Self::new_unchecked(rest), + ) + }) + } + } + + pub fn iter(&self) -> JsonPointerIter { + let mut tokens = self.0.split('/'); + tokens.next(); + JsonPointerIter(tokens) + } +} + +impl<'a> IntoIterator for &'a JsonPointer { + type Item = &'a ReferenceToken; + type IntoIter = JsonPointerIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct JsonPointerIter<'a>(std::str::Split<'a, char>); + +impl<'a> Iterator for JsonPointerIter<'a> { + type Item = &'a ReferenceToken; + + fn next(&mut self) -> Option { + self.0.next().map(|s| unsafe { std::mem::transmute(s) }) + } +} + +/// JSON Pointer buffer. +/// +/// See: +#[derive(Debug, Clone, Serialize)] +pub struct JsonPointerBuf(String); + +impl Deref for JsonPointerBuf { + type Target = JsonPointer; + + fn deref(&self) -> &Self::Target { + unsafe { JsonPointer::new_unchecked(&self.0) } + } +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct ReferenceToken(str); + +impl ReferenceToken { + pub unsafe fn new_unchecked(s: &str) -> &Self { + std::mem::transmute(s) + } + + pub fn is_escaped(&self) -> bool { + self.0.contains('~') + } + + pub fn to_str(&self) -> Cow { + if self.is_escaped() { + Cow::Owned(self.decode()) + } else { + Cow::Borrowed(&self.0) + } + } + + pub fn decode(&self) -> String { + let mut result = String::new(); + let mut chars = self.0.chars(); + while let Some(c) = chars.next() { + let decoded_c = match c { + '~' => match chars.next() { + Some('0') => '~', + Some('1') => '/', + _ => unreachable!(), + }, + c => c, + }; + + result.push(decoded_c); + } + + result + } + + pub fn as_array_index(&self) -> Option { + let mut chars = self.0.chars(); + let mut i = chars.next()?.to_digit(10)? as usize; + if i == 0 { + match chars.next() { + Some(_) => None, + None => Some(0), + } + } else { + for c in chars { + let d = c.to_digit(10)? as usize; + i = i * 10 + d; + } + + Some(i) + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs new file mode 100644 index 000000000..3aa64832a --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -0,0 +1,138 @@ +//! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. +//! +//! See: +use std::{ + borrow::Cow, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, + fmt, + hash::Hash, + ops::Deref, +}; + +use getrandom::getrandom; +use hmac::{digest::KeyInit, Hmac}; +use iref::IriBuf; +use ssi_json_ld::{ + context_processing::ProcessedOwned, syntax::Value, Compact, JsonLdProcessor, Process, Profile, + RemoteDocument, +}; +use k256::sha2::{Digest, Sha256}; +use linked_data::IntoQuadsError; +use rdf_types::{ + generator, + vocabulary::{ExtractFromVocabulary, IriVocabulary, IriVocabularyMut}, + BlankId, BlankIdBuf, Id, LexicalQuad, Term, Vocabulary, VocabularyMut, +}; +use serde::{Deserialize, Serialize}; +use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_data_integrity_core::{ + suite::{ + standard::{ + HashingAlgorithm, HashingError, SignatureAlgorithm, SignatureAndVerificationAlgorithm, + TransformationAlgorithm, TransformationError, TransformedData, + TypedTransformationAlgorithm, VerificationAlgorithm, + }, + ConfigurationAlgorithm, ConfigurationError, InputProofOptions, + }, + ProofConfiguration, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef, +}; +use ssi_json_ld::{JsonLdObject, Expandable}; +use ssi_rdf::LdEnvironment; +use ssi_verification_methods::Multikey; + +mod json_pointer; +pub use json_pointer::*; + +mod transformation; +use transformation::TransformedDerived; +pub use transformation::{Bbs2023Transformation, Bbs2023TransformationOptions, Transformed}; + +mod hashing; +use hashing::BaseHashData; +pub use hashing::{Bbs2023Hashing, HashData}; + +mod signature; +pub use signature::Bbs2023SignatureAlgorithm; + +mod verification; + +/// The `bbs-2023` cryptographic suite. +#[derive(Debug, Clone, Copy)] +pub struct Bbs2023; + +impl StandardCryptographicSuite for Bbs2023 { + type Configuration = Bbs2023Configuration; + + type Transformation = Bbs2023Transformation; + + type Hashing = Bbs2023Hashing; + + type VerificationMethod = Multikey; + + type ProofOptions = Bbs2023Options; + + type SignatureAlgorithm = Bbs2023SignatureAlgorithm; + + fn type_(&self) -> TypeRef { + TypeRef::DataIntegrityProof("bbs-2023") + } +} + +pub struct Bbs2023InputOptions { + pub mandatory_pointers: Vec, + + pub feature_option: FeatureOption, + + pub commitment_with_proof: Option>, + + pub proof_options: Bbs2023Options, +} + +#[derive(Debug, Default, Clone, Copy)] +pub enum FeatureOption { + #[default] + Baseline, + AnonymousHolderBinding, + PseudonymIssuerPid, + PseudonymHiddenPid, +} + +/// Base Proof Configuration. +/// +/// See: +pub struct Bbs2023Configuration; + +impl ConfigurationAlgorithm for Bbs2023Configuration { + /// Input type for the verification method. + type InputVerificationMethod = Multikey; + + /// Input suite-specific proof options. + type InputProofOptions = (); + + /// Input signature options. + type InputSignatureOptions = Bbs2023InputOptions; + + /// Document transformation options. + type TransformationOptions = Bbs2023TransformationOptions; + + fn configure( + _: &Bbs2023, + options: InputProofOptions, + signature_options: Bbs2023InputOptions, + ) -> Result<(ProofConfiguration, Bbs2023TransformationOptions), ConfigurationError> + { + todo!() + } +} + +#[derive(Serialize)] +pub struct Bbs2023Options; + +#[derive(Serialize)] +pub struct Bbs2023Signature; + +impl AsRef for Bbs2023Signature { + fn as_ref(&self) -> &str { + todo!() + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs new file mode 100644 index 000000000..fa58adfd4 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs @@ -0,0 +1,144 @@ +use ssi_claims_core::SignatureError; +use ssi_data_integrity_core::{ + suite::standard::{SignatureAlgorithm, SignatureAndVerificationAlgorithm}, + ProofConfigurationRef, +}; +use ssi_rdf::IntoNQuads; +use ssi_verification_methods::{Multikey, SigningMethod}; + +use crate::Bbs2023; + +use super::{Bbs2023Signature, FeatureOption, HashData}; + +pub enum Bbs { + Baseline { + header: [u8; 64], + }, + Blind { + header: [u8; 64], + signer_blind: Option, + }, + Pseudonym1 { + header: [u8; 64], + pid: u32, + }, + Pseudonym2 { + header: [u8; 64], + commitment_with_proof: String, + signer_blind: Option, + }, +} + +pub trait MultiSigner { + type MessageSigner: MultiMessageSigner; + + async fn for_verification_method( + &self, + method: &M, + ) -> Result; +} + +pub trait MultiMessageSigner { + async fn multi_sign( + self, + algorithm: A, + messages: &[Vec], + ) -> Result, SignatureError>; +} + +pub struct Bbs2023SignatureAlgorithm; + +impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { + type Signature = Bbs2023Signature; +} + +impl SignatureAlgorithm for Bbs2023SignatureAlgorithm +where + T: MultiSigner, +{ + async fn sign( + verification_method: &Multikey, + signer: T, + prepared_claims: HashData, + proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + ) -> Result { + match prepared_claims { + HashData::Base(hash_data) => { + // See: + let feature_option = hash_data.transformed_document.feature_option; + let proof_hash = &hash_data.proof_hash; + let mandatory_pointers = &hash_data.transformed_document.mandatory_pointers; + let mandatory_hash = &hash_data.mandatory_hash; + let non_mandatory = &hash_data.transformed_document.non_mandatory; + let hmap_key = hash_data.transformed_document.hmac_key; + + let mut bbs_header = [0; 64]; + bbs_header[..32].copy_from_slice(proof_hash); + bbs_header[32..].copy_from_slice(mandatory_hash); + + let mut bbs_messages: Vec<_> = non_mandatory + .into_nquads_lines() + .into_iter() + .map(String::into_bytes) + .collect(); + + let message_signer = signer.for_verification_method(verification_method).await?; + + let bbs_signature = match feature_option { + FeatureOption::Baseline => { + message_signer + .multi_sign(Bbs::Baseline { header: bbs_header }, &bbs_messages) + .await? + } + FeatureOption::AnonymousHolderBinding => { + message_signer + .multi_sign( + Bbs::Blind { + header: bbs_header, + signer_blind: None, + }, + &bbs_messages, + ) + .await? + } + FeatureOption::PseudonymIssuerPid => { + let mut pid_buffer = [0u8; 4]; + getrandom::getrandom(&mut pid_buffer); + let pid = u32::from_ne_bytes(pid_buffer); + message_signer + .multi_sign( + Bbs::Pseudonym1 { + header: bbs_header, + pid, + }, + &bbs_messages, + ) + .await? + } + FeatureOption::PseudonymHiddenPid => { + todo!() + } + }; + + // Ok(Bbs2023Signature::new(description)) + todo!() + } + HashData::Derived(_) => { + todo!() + } + } + } +} + +struct BbsSecretKey; + +impl SigningMethod for Multikey { + fn sign_bytes( + &self, + secret: &BbsSecretKey, + algorithm: Bbs, + bytes: &[u8], + ) -> Result, ssi_verification_methods::MessageSignatureError> { + todo!() + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs new file mode 100644 index 000000000..70f708fd1 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs @@ -0,0 +1,730 @@ +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + fmt, + hash::Hash, +}; + +use getrandom::getrandom; +use hmac::{Hmac, Mac}; +use iref::IriBuf; +use ssi_json_ld::{ + context_processing::{Process, ProcessedOwned}, Compact, ContextLoaderEnvironment, JsonLdProcessor, Profile, RemoteDocument +}; +use json_syntax::Value; +use k256::sha2::Sha256; +use linked_data::{IntoQuadsError, LinkedDataResource, LinkedDataSubject}; +use rdf_types::{ + generator, + interpretation::ReverseTermInterpretation, + vocabulary::{ExtractFromVocabulary, IriVocabularyMut}, + BlankId, BlankIdBuf, Id, Interpretation, InterpretationMut, LexicalQuad, Term, Vocabulary, + VocabularyMut, +}; +use ssi_data_integrity_core::{ + suite::standard::{TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm}, + ProofConfigurationRef, +}; +use ssi_json_ld::{JsonLdNodeObject, JsonLdObject, Expandable}; +use ssi_rdf::interpretation::WithGenerator; + +use crate::Bbs2023; + +use super::{FeatureOption, JsonPointer, JsonPointerBuf}; + +pub struct Bbs2023TransformationOptions { + feature_options: FeatureOption, + mandatory_pointers: Vec, +} + +pub struct Bbs2023Transformation; + +impl TransformationAlgorithm for Bbs2023Transformation { + type Output = Transformed; +} + +impl TypedTransformationAlgorithm for Bbs2023Transformation +where + C: ContextLoaderEnvironment, + T: JsonLdNodeObject + Expandable, + T::Expanded, ()>: Into +{ + async fn transform( + context: &C, + unsecured_document: &T, + proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + transformation_options: Option, + ) -> Result { + let canonical_configuration = proof_configuration + .expand(context, unsecured_document) + .await + .map_err(TransformationError::ProofConfigurationExpansion)? + .nquads_lines(); + + match transformation_options { + Some(transform_options) => { + // Base Proof Transformation algorithm. + // See: + // Generate a random key + let mut hmac_key = HmacKey::default(); + getrandom(&mut hmac_key).map_err(TransformationError::internal)?; + let hmac = Hmac::::new_from_slice(&hmac_key); + + let mut group_definitions = HashMap::new(); + group_definitions.insert(Mandatory, transform_options.mandatory_pointers.clone()); + + let label_map_factory_function = || todo!(); + + let mut groups = canonicalize_and_group( + context.loader(), + label_map_factory_function, + group_definitions, + unsecured_document, + ) + .await? + .groups; + + let mandatory_group = groups.remove(&Mandatory).unwrap(); + let mandatory = mandatory_group.matching.into_values().collect(); + let non_mandatory = mandatory_group.non_matching.into_values().collect(); + + Ok(Transformed::Base(TransformedBase { + feature_option: transform_options.feature_options, + mandatory_pointers: transform_options.mandatory_pointers, + mandatory, + non_mandatory, + hmac_key, + canonical_configuration, + })) + } + None => { + // createVerifyData, step 1, 3, 4 + // canonicalize input document into N-Quads. + Ok(Transformed::Derived(todo!())) + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Mandatory; + +fn hmac_id_label_map_function(hmac: &mut Hmac) -> impl '_ + FnMut(&BlankId) -> BlankIdBuf { + use hmac::Mac; + let mut map: HashMap<&BlankId, BlankIdBuf> = HashMap::new(); + move |blank_id| { + hmac.update(blank_id.as_bytes()); + let digest = hmac.finalize_reset().into_bytes(); + let b64_url_digest = format!( + "u{}", + base64::encode_config(&digest, base64::URL_SAFE_NO_PAD) + ); + todo!() + } +} + +/// Canonicalize and group. +/// +/// See: +async fn canonicalize_and_group( + loader: &impl ssi_json_ld::Loader, + label_factory: impl FnMut() -> BlankIdBuf, + group_definitions: HashMap>, + document: &T, +) -> Result, TransformationError> +where + T: JsonLdObject + Expandable, + T::Expanded, ()>: Into, + N: Eq + Hash, +{ + let mut skolemize = Skolemize { + urn_scheme: String::new(), + random_string: String::new(), + count: 0, + }; + + let (skolemized_expanded_document, skolemized_compact_document) = + skolemize.compact_document(loader, document).await?; + + let deskolemized_quads = + expanded_to_deskolemized_nquads(&skolemize.urn_scheme, &skolemized_expanded_document)?; + + let (quads, label_map) = + label_replacement_canonicalize_nquads(label_factory, &deskolemized_quads); + + let mut selection = HashMap::new(); + for (name, pointers) in group_definitions { + selection.insert( + name, + select_canonical_nquads( + loader, + &skolemize.urn_scheme, + pointers, + &label_map, + &skolemized_compact_document, + ) + .await?, + ); + } + + let mut groups = HashMap::new(); + + for (name, selection_result) in selection { + let mut matching = HashMap::new(); + let mut non_matching = HashMap::new(); + + let selected_quads: HashSet<_> = selection_result.quads.into_iter().collect(); + let selected_deskolemized_quads = selection_result.deskolemized_quads; + + for (i, nq) in quads.iter().enumerate() { + if selected_quads.contains(nq) { + matching.insert(i, nq.clone()); + } else { + non_matching.insert(i, nq.clone()); + } + } + + groups.insert( + name, + Group { + matching, + non_matching, + deskolemized_quads: selected_deskolemized_quads, + }, + ); + } + + Ok(CanonicalizedAndGrouped { + groups, + skolemized_expanded_document, + skolemized_compact_document, + deskolemized_quads, + label_map, + quads, + }) +} + +struct CanonicalizedAndGrouped { + groups: HashMap, + skolemized_expanded_document: ssi_json_ld::ExpandedDocument, + skolemized_compact_document: ssi_json_ld::syntax::Object, + deskolemized_quads: Vec, + label_map: HashMap, + quads: Vec, +} + +async fn select_canonical_nquads( + loader: &impl ssi_json_ld::Loader, + urn_scheme: &str, + pointers: Vec, + label_map: &HashMap, + skolemized_compact_document: &ssi_json_ld::syntax::Object, +) -> Result { + let selection_document = select_json_ld(pointers, skolemized_compact_document) + .map_err(TransformationError::internal)?; + + let deskolemized_quads = match selection_document.clone() { + Some(selection_document) => { + compact_to_deskolemized_nquads(loader, urn_scheme, selection_document).await? + } + None => Vec::new(), + }; + + let quads = relabel_blank_nodes(label_map, &deskolemized_quads); + + Ok(CanonicalNquadsSelection { + selection_document, + deskolemized_quads, + quads, + }) +} + +struct CanonicalNquadsSelection { + selection_document: Option, + deskolemized_quads: Vec, + quads: Vec, +} + +fn relabel_blank_nodes( + label_map: &HashMap, + quads: &[LexicalQuad], +) -> Vec { + todo!() +} + +/// See: +fn select_json_ld( + pointers: Vec, + document: &ssi_json_ld::syntax::Object, +) -> Result, DanglingJsonPointer> { + if pointers.is_empty() { + return Ok(None); + } + + let mut selection_document = create_initial_selection_object(document); + if let Some(context) = document.get("@context").next() { + selection_document.insert("@context".into(), SparseValue::from_dense(context)); + } + + for pointer in pointers { + document.select(&pointer, &mut selection_document)?; + } + + Ok(Some(selection_document.into_dense())) +} + +fn create_initial_selection(source: &Value) -> SparseValue { + match source { + Value::Null => SparseValue::Null, + Value::Boolean(b) => SparseValue::Boolean(*b), + Value::Number(n) => SparseValue::Number(n.clone()), + Value::String(s) => SparseValue::String(s.clone()), + Value::Array(_) => SparseValue::Array(SparseArray::default()), + Value::Object(object) => SparseValue::Object(create_initial_selection_object(object)), + } +} + +fn create_initial_selection_object(source: &ssi_json_ld::syntax::Object) -> SparseObject { + let mut selection = SparseObject::new(); + + if let Some(Value::String(id)) = source.get("id").next() { + if BlankId::new(id).is_err() { + selection.insert("id".into(), SparseValue::String(id.to_owned())); + } + } + + if let Some(type_) = source.get("type").next() { + selection.insert("type".into(), SparseValue::from_dense(type_)); + } + + selection +} + +struct JsonPath; + +fn select_paths( + pointer: &JsonPointer, + mut value: &ssi_json_ld::syntax::Value, + selection_document: &mut ssi_json_ld::syntax::Object, +) -> Result<(), DanglingJsonPointer> { + for token in pointer { + value + .as_object() + .and_then(|o| o.get(token.to_str().as_ref()).next()) + .ok_or(DanglingJsonPointer); + + // ... + } + + todo!() +} + +#[derive(Debug)] +pub enum SparseValue { + Null, + Boolean(bool), + String(ssi_json_ld::syntax::String), + Number(ssi_json_ld::syntax::NumberBuf), + Array(SparseArray), + Object(SparseObject), +} + +impl SparseValue { + pub fn from_dense(value: &Value) -> Self { + match value { + Value::Null => Self::Null, + Value::Boolean(b) => Self::Boolean(*b), + Value::String(s) => Self::String(s.clone()), + Value::Number(n) => Self::Number(n.clone()), + Value::Array(a) => Self::Array(SparseArray::from_dense(a)), + Value::Object(o) => Self::Object(SparseObject::from_dense(o)), + } + } + + pub fn into_dense(self) -> Value { + match self { + Self::Null => Value::Null, + Self::Boolean(b) => Value::Boolean(b), + Self::Number(n) => Value::Number(n), + Self::String(s) => Value::String(s), + Self::Array(a) => Value::Array(a.into_dense()), + Self::Object(o) => Value::Object(o.into_dense()), + } + } +} + +#[derive(Debug, Default)] +pub struct SparseArray(BTreeMap); + +impl SparseArray { + pub fn from_dense(value: &Vec) -> Self { + Self( + value + .iter() + .enumerate() + .map(|(i, item)| (i, SparseValue::from_dense(item))) + .collect(), + ) + } + + pub fn get_mut_or_insert_with( + &mut self, + i: usize, + f: impl FnOnce() -> SparseValue, + ) -> &mut SparseValue { + todo!() + } + + pub fn into_dense(self) -> Vec { + self.0.into_values().map(SparseValue::into_dense).collect() + } +} + +#[derive(Debug, Default)] +pub struct SparseObject(BTreeMap); + +impl SparseObject { + pub fn new() -> Self { + Self::default() + } + + pub fn from_dense(value: &ssi_json_ld::syntax::Object) -> Self { + Self( + value + .iter() + .map(|entry| { + ( + entry.key.as_str().to_owned(), + SparseValue::from_dense(&entry.value), + ) + }) + .collect(), + ) + } + + pub fn get_mut_or_insert_with( + &mut self, + key: &str, + f: impl FnOnce() -> SparseValue, + ) -> &mut SparseValue { + todo!() + } + + pub fn insert(&mut self, key: String, value: SparseValue) { + self.0.insert(key, value); + } + + pub fn into_dense(self) -> ssi_json_ld::syntax::Object { + self.0 + .into_iter() + .map(|(key, value)| (key.into(), value.into_dense())) + .collect() + } +} + +trait Select { + type Sparse; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer>; +} + +impl Select for Value { + type Sparse = SparseValue; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match (self, selection) { + (Self::Array(a), SparseValue::Array(b)) => a.select(pointer, b), + (Self::Object(a), SparseValue::Object(b)) => a.select(pointer, b), + _ => { + if pointer.is_empty() { + Ok(()) + } else { + Err(DanglingJsonPointer) + } + } + } + } +} + +impl Select for Vec { + type Sparse = SparseArray; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match pointer.split_first() { + Some((token, rest)) => { + let i = token.as_array_index().ok_or(DanglingJsonPointer)?; + let a_item = self.get(i).ok_or(DanglingJsonPointer)?; + let b_item = + selection.get_mut_or_insert_with(i, || create_initial_selection(a_item)); + a_item.select(rest, b_item) + } + None => { + *selection = SparseArray::from_dense(self); + Ok(()) + } + } + } +} + +impl Select for ssi_json_ld::syntax::Object { + type Sparse = SparseObject; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match pointer.split_first() { + Some((token, rest)) => { + let key = token.to_str(); + let a_item = self.get(key.as_ref()).next().ok_or(DanglingJsonPointer)?; + let b_item = + selection.get_mut_or_insert_with(&key, || create_initial_selection(a_item)); + a_item.select(rest, b_item) + } + None => { + *selection = SparseObject::from_dense(self); + Ok(()) + } + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("dangling JSON pointer")] +pub struct DanglingJsonPointer; + +fn label_replacement_canonicalize_nquads( + mut label_factory: impl FnMut() -> BlankIdBuf, + quads: &[LexicalQuad], +) -> (Vec, HashMap) { + let mut label_map: HashMap = HashMap::new(); + let mut relabel = |b: &mut BlankIdBuf| match label_map.get(b.as_blank_id_ref()).cloned() { + Some(c) => *b = c, + None => { + let c = label_factory(); + label_map.insert(b.clone(), c); + } + }; + + let mut relabel_id = |id: &mut Id| { + if let Id::Blank(b) = id { + relabel(b) + } + }; + + let mut canonical_quads: Vec = + ssi_rdf::urdna2015::normalize(quads.iter().map(LexicalQuad::as_lexical_quad_ref)).collect(); + for quad in &mut canonical_quads { + relabel_id(&mut quad.0); + if let Term::Id(id) = &mut quad.2 { + relabel_id(id) + } + if let Some(g) = &mut quad.3 { + relabel_id(g) + } + } + + (canonical_quads, label_map) +} + +fn expanded_to_deskolemized_nquads( + urn_scheme: &str, + document: &ssi_json_ld::ExpandedDocument, +) -> Result, IntoQuadsError> { + let mut quads = linked_data::to_lexical_quads(generator::Blank::new(), &document)?; + + deskolemize_nquads(urn_scheme, &mut quads); + + Ok(quads) +} + +async fn compact_to_deskolemized_nquads( + loader: &impl ssi_json_ld::Loader, + urn_scheme: &str, + document: ssi_json_ld::syntax::Object, +) -> Result, TransformationError> { + let mut quads: Vec = RemoteDocument::new(None, None, Value::Object(document)) + .to_rdf(&mut generator::Blank::new(), loader) + .await + .map_err(TransformationError::internal)? + .cloned_quads() + .map(|quad| quad.map_predicate(|p| p.into_iri().unwrap())) + .collect(); + + deskolemize_nquads(urn_scheme, &mut quads); + + Ok(quads) +} + +fn deskolemize_nquads(urn_scheme: &str, quads: &mut [LexicalQuad]) { + for quad in quads { + deskolemize_id(urn_scheme, &mut quad.0); + deskolemize_term(urn_scheme, &mut quad.2); + + if let Some(g) = quad.graph_mut() { + deskolemize_id(urn_scheme, g); + } + } +} + +fn deskolemize_id(urn_scheme: &str, id: &mut Id) { + if let Id::Iri(iri) = id { + if iri.scheme().as_str() == "urn" { + let path = iri.path(); + if let Some((prefix, suffix)) = path.split_once(':') { + if prefix == urn_scheme { + let blank_id = BlankIdBuf::from_suffix(suffix).unwrap(); + *id = Id::Blank(blank_id) + } + } + } + } +} + +fn deskolemize_term(urn_scheme: &str, term: &mut Term) { + if let Term::Id(id) = term { + deskolemize_id(urn_scheme, id) + } +} + +struct Skolemize { + urn_scheme: String, + random_string: String, + count: u32, +} + +impl rdf_types::Generator for Skolemize { + fn next(&mut self, vocabulary: &mut ()) -> Id { + Id::Iri(self.fresh_blank_id()) + } +} + +impl Skolemize { + pub fn fresh_blank_id(&mut self) -> IriBuf { + let id = IriBuf::new(format!( + "urn:{}:{}_{}", + self.urn_scheme, self.random_string, self.count + )) + .unwrap(); + self.count += 1; + id + } + + pub fn blank_id(&mut self, blank_id: &BlankId) -> IriBuf { + IriBuf::new(format!("urn:{}:{}", self.urn_scheme, blank_id.suffix())).unwrap() + } + + /// See: + pub async fn compact_document( + &mut self, + loader: &impl ssi_json_ld::Loader, + document: &T, + ) -> Result<(ssi_json_ld::ExpandedDocument, ssi_json_ld::syntax::Object), TransformationError> + where + T: JsonLdObject + Expandable, + T::Expanded, ()>: Into + { + let expanded = document + .expand(loader) + .await + .map_err(|e| TransformationError::JsonLdExpansion(e.to_string()))?; + + let skolemized_expanded_document = self.expanded_document(expanded.into()); + + let processed_context: ProcessedOwned = match document.json_ld_context() + { + Some(ld_context) => { + let processed = ld_context + .process(&mut (), loader, None) + .await + .map_err(TransformationError::internal)? + .processed; + + ProcessedOwned::new(ld_context.into_owned(), processed) + } + None => ProcessedOwned::new( + ssi_json_ld::syntax::Context::default(), + ssi_json_ld::Context::default(), + ), + }; + + let skolemized_compact_document = skolemized_expanded_document + .compact(processed_context.as_ref(), loader) + .await + .map_err(TransformationError::internal)? + .into_object() + .ok_or_else(|| TransformationError::internal("expected JSON object"))?; + + Ok((skolemized_expanded_document, skolemized_compact_document)) + } + + /// See: + pub fn expanded_document( + &mut self, + expanded: ssi_json_ld::ExpandedDocument, + ) -> ssi_json_ld::ExpandedDocument { + let mut result = expanded.map_ids( + |i| i, + |id| match id { + ssi_json_ld::Id::Valid(id) => match id { + Id::Blank(blank_id) => { + ssi_json_ld::Id::Valid(Id::Iri(self.blank_id(&blank_id))) + } + Id::Iri(iri) => { + ssi_json_ld::Id::Valid(Id::Iri(iri)) + } + }, + ssi_json_ld::Id::Invalid(s) => ssi_json_ld::Id::Invalid(s), + }, + ); + + result.identify_all(self); + result + } +} + +struct Group { + pub matching: HashMap, + pub non_matching: HashMap, + pub deskolemized_quads: Vec, +} + +struct GroupDefinitions { + mandatory: Vec, +} + +pub enum Transformed { + Base(TransformedBase), + Derived(TransformedDerived), +} + +/// Result of the Base Proof Transformation algorithm. +/// +/// See: +pub struct TransformedBase { + pub feature_option: FeatureOption, + pub mandatory_pointers: Vec, + pub mandatory: Vec, + pub non_mandatory: Vec, + pub hmac_key: HmacKey, + pub canonical_configuration: Vec, +} + +pub struct TransformedDerived { + pub proof_hash: String, + pub nquads: Vec, +} + +type HmacKey = [u8; 32]; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs new file mode 100644 index 000000000..0ee703f60 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -0,0 +1,29 @@ +use ssi_claims_core::{ProofValidationError, ProofValidity}; +use ssi_data_integrity_core::{suite::standard::VerificationAlgorithm, ProofRef}; +use ssi_verification_methods::Multikey; + +use crate::Bbs2023; + +use super::{Bbs2023SignatureAlgorithm, HashData}; + +impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { + fn verify( + method: &Multikey, + prepared_claims: HashData, + proof: ProofRef, + ) -> Result { + match prepared_claims { + HashData::Base(_) => { + todo!() + } + HashData::Derived(t) => { + // Verify Derived Proof algorithm. + // See: + // let proof_hash, mandatory_hash = createVerifyData, step 2, 5, 6, 7, 8, 9 + // let bbs_header = proof_hash + mandatory_hash + // let disclosed_messages = ... + todo!() + } + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs index 07bf14f53..68cd4620f 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs @@ -10,7 +10,7 @@ use ssi_data_integrity_core::{ SignatureAlgorithm, SignatureAndVerificationAlgorithm, TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm, VerificationAlgorithm, }, - AddProofContext, + AddProofContext, TransformationOptions, }, CryptographicSuite, ProofConfigurationRef, ProofRef, SerializeCryptographicSuite, StandardCryptographicSuite, TypeRef, @@ -275,6 +275,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, + _transformation_options: Option>, ) -> Result { let types = match proof_configuration.options.types() { Some(TypesOrURI::Object(types)) => Some(types.clone()), diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 6d8b54a63..51d2fee78 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -24,7 +24,7 @@ k256 = { workspace = true, optional = true, features = ["ecdsa"] } p256 = { workspace = true, optional = true, features = ["ecdsa"] } hkdf = { version = "0.8", optional = true } rand_old = { package = "rand", version = "0.7", optional = true } -getrandom = { version = "0.2", optional = true } # Required for wasm targets. +getrandom = { workspace = true, optional = true } # Required for wasm targets. sha2_old = { package = "sha2", version = "0.8", optional = true } keccak-hash = { version = "0.7", optional = true } ed25519-dalek = { workspace = true, optional = true } @@ -42,7 +42,7 @@ pin-project.workspace = true hex.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["js"], optional = true } +getrandom = { workspace = true, features = ["js"], optional = true } [package.metadata.docs.rs] all-features = true diff --git a/crates/json-ld/src/context.rs b/crates/json-ld/src/contexts.rs similarity index 99% rename from crates/json-ld/src/context.rs rename to crates/json-ld/src/contexts.rs index 267cca509..a50bc0d41 100644 --- a/crates/json-ld/src/context.rs +++ b/crates/json-ld/src/contexts.rs @@ -2,7 +2,6 @@ use iref::{Iri, IriBuf}; use json_ld::{ syntax::TryFromJson, LoadError, Loader, RemoteContext, RemoteContextReference, RemoteDocument, }; -pub use json_ld::{Options, RemoteDocumentReference}; use json_syntax::Parse; use static_iref::iri; use std::collections::HashMap; diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index 0e3ffd438..d20762031 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -1,23 +1,20 @@ //! Linked-Data types. -mod context; - use std::{borrow::Cow, hash::Hash}; -pub use context::*; use json_ld::expansion::Action; use json_ld::Expand; use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; pub use json_ld; -pub use json_ld::{ - syntax, Direction, ExpandedDocument, Id, LangString, LenientLangTag, LoadError, Loader, - Nullable, ToRdfError, -}; +pub use json_ld::*; use ssi_rdf::{ generator, interpretation::WithGenerator, Interpretation, LdEnvironment, Vocabulary, VocabularyMut, }; +mod contexts; +pub use contexts::*; + /// Type that provides a JSON-LD document loader. pub trait JsonLdLoaderProvider { type Loader: json_ld::Loader; diff --git a/crates/jwk/Cargo.toml b/crates/jwk/Cargo.toml index 0b1ef6c20..0501e0161 100644 --- a/crates/jwk/Cargo.toml +++ b/crates/jwk/Cargo.toml @@ -82,7 +82,7 @@ snarkvm-utilities = { version = "0.7.9", optional = true } snarkvm-parameters = { version = "0.7.9", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["js"], optional = true } +getrandom = { workspace = true, features = ["js"], optional = true } [dev-dependencies] hex.workspace = true diff --git a/crates/rdf/src/expand.rs b/crates/rdf/src/expand.rs index bc15d2483..4289a85ed 100644 --- a/crates/rdf/src/expand.rs +++ b/crates/rdf/src/expand.rs @@ -11,6 +11,8 @@ pub trait AnyLdEnvironment { type Interpretation: Interpretation; + fn as_ld_environment(&self) -> LdEnvironment<&Self::Vocabulary, &Self::Interpretation>; + fn as_ld_environment_mut( &mut self, ) -> LdEnvironment<&mut Self::Vocabulary, &mut Self::Interpretation>; @@ -104,6 +106,13 @@ impl AnyLdEnvironment for LdEnvironment { type Vocabulary = V; type Interpretation = I; + fn as_ld_environment(&self) -> LdEnvironment<&Self::Vocabulary, &Self::Interpretation> { + LdEnvironment { + vocabulary: &self.vocabulary, + interpretation: &self.interpretation, + } + } + fn as_ld_environment_mut( &mut self, ) -> LdEnvironment<&mut Self::Vocabulary, &mut Self::Interpretation> { diff --git a/crates/verification-methods/core/src/lib.rs b/crates/verification-methods/core/src/lib.rs index 93189508a..a4d004f0b 100644 --- a/crates/verification-methods/core/src/lib.rs +++ b/crates/verification-methods/core/src/lib.rs @@ -167,7 +167,7 @@ impl<'t, T: VerificationMethodResolver> VerificationMethodResolver for &'t T { } } -pub trait SigningMethod: VerificationMethod { +pub trait SigningMethod: VerificationMethod { // fn sign( // &self, // secret: &S, diff --git a/crates/zcap-ld/src/lib.rs b/crates/zcap-ld/src/lib.rs index bce0b06b7..02d845178 100644 --- a/crates/zcap-ld/src/lib.rs +++ b/crates/zcap-ld/src/lib.rs @@ -13,7 +13,7 @@ use serde_json::Value; use ssi_claims::{ chrono::{DateTime, Utc}, data_integrity::{ - suite::{CryptographicSuiteSigning, InputOptions}, + suite::{CryptographicSuiteSigning, InputProofOptions, InputSignatureOptions}, AnyDataIntegrity, AnyProofs, AnySignatureAlgorithm, AnySuite, CryptographicSuite, DataIntegrity, Proof, Proofs, }, @@ -137,7 +137,7 @@ impl Delegation { suite: AnySuite, resolver: &impl VerificationMethodResolver, signer: S, - proof_configuration: InputOptions, + proof_configuration: InputProofOptions, capability_chain: &[&str], ) -> Result, SignatureError> where @@ -164,11 +164,12 @@ impl Delegation { environment: E, resolver: R, signer: S, - mut proof_configuration: InputOptions, + mut proof_configuration: InputProofOptions, capability_chain: &[&str], ) -> Result, SignatureError> where D: CryptographicSuiteSigning, + InputSignatureOptions: Default, { proof_configuration.extra_properties.insert( "capabilityChain".into(), @@ -180,7 +181,14 @@ impl Delegation { } suite - .sign_with(environment, self, resolver, signer, proof_configuration) + .sign_with( + environment, + self, + resolver, + signer, + proof_configuration, + Default::default() + ) .await } } @@ -386,7 +394,7 @@ impl

Invocation

{ suite: AnySuite, resolver: impl VerificationMethodResolver, signer: S, - mut proof_configuration: InputOptions, + mut proof_configuration: InputProofOptions, target: &Uri, ) -> Result>, SignatureError> where From 834da2b3aefc86924bc26e746b35a4e8a58dbdc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 7 Jun 2024 18:09:32 +0200 Subject: [PATCH 03/34] Complete base proof generation. Now testing... --- Cargo.toml | 7 +- crates/bbs/Cargo.toml | 13 + crates/bbs/src/lib.rs | 68 ++ crates/claims/core/src/signature.rs | 7 + .../crates/data-integrity/core/Cargo.toml | 2 +- .../data-integrity/sd-primitives/Cargo.toml | 29 + .../sd-primitives/src/canonicalize.rs | 80 +++ .../data-integrity/sd-primitives/src/group.rs | 254 +++++++ .../src}/json_pointer.rs | 76 +- .../data-integrity/sd-primitives/src/lib.rs | 12 + .../sd-primitives/src/select.rs | 311 ++++++++ .../sd-primitives/src/skolemize.rs | 214 ++++++ .../crates/data-integrity/suites/Cargo.toml | 5 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 136 ++-- .../src/suites/w3c/bbs_2023/signature.rs | 148 ++-- .../suites/src/suites/w3c/bbs_2023/tests.rs | 191 +++++ .../src/suites/w3c/bbs_2023/transformation.rs | 664 +----------------- crates/eip712/Cargo.toml | 2 +- crates/json-ld/Cargo.toml | 2 +- crates/json-ld/src/lib.rs | 9 + crates/rdf/Cargo.toml | 1 + crates/rdf/src/lib.rs | 4 +- crates/rdf/src/urdna2015.rs | 26 +- crates/verification-methods/core/src/lib.rs | 16 +- .../core/src/signature/signer/mod.rs | 19 + 25 files changed, 1493 insertions(+), 803 deletions(-) create mode 100644 crates/bbs/Cargo.toml create mode 100644 crates/bbs/src/lib.rs create mode 100644 crates/claims/crates/data-integrity/sd-primitives/Cargo.toml create mode 100644 crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs create mode 100644 crates/claims/crates/data-integrity/sd-primitives/src/group.rs rename crates/claims/crates/data-integrity/{suites/src/suites/w3c/bbs_2023 => sd-primitives/src}/json_pointer.rs (69%) create mode 100644 crates/claims/crates/data-integrity/sd-primitives/src/lib.rs create mode 100644 crates/claims/crates/data-integrity/sd-primitives/src/select.rs create mode 100644 crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 246997578..794232a0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ ssi-ucan = { path = "./crates/ucan", version = "0.2", default-features = false } ssi-zcap-ld = { path = "./crates/zcap-ld", version = "0.2", default-features = false } ssi-ssh = { path = "./crates/ssh", version = "0.2", default-features = false } ssi-status = { path = "./crates/status", version = "0.1", default-features = false } +ssi-bbs = { path = "./crates/bbs", version = "0.1", default-features = false } # Verifiable Claims ssi-claims-core = { path = "./crates/claims/core", version = "0.1", default-features = false } @@ -48,6 +49,7 @@ ssi-jwt = { path = "./crates/claims/crates/jwt", version = "0.2", default-featur ssi-sd-jwt = { path = "./crates/claims/crates/sd-jwt", version = "0.2", default-features = false } ssi-vc = { path = "./crates/claims/crates/vc", version = "0.3", default-features = false } ssi-data-integrity-core = { path = "./crates/claims/crates/data-integrity/core", version = "0.1", default-features = false } +ssi-di-sd-primitives = { path = "./crates/claims/crates/data-integrity/sd-primitives", version = "0.1", default-features = false } ssi-data-integrity-suites = { path = "./crates/claims/crates/data-integrity/suites", version = "0.1", default-features = false } ssi-data-integrity = { path = "./crates/claims/crates/data-integrity", version = "0.1", default-features = false } ssi-claims = { path = "./crates/claims", version = "0.1", default-features = false } @@ -64,6 +66,7 @@ did-web = { path = "./crates/dids/methods/web", version = "0.3", default-feature ssi-dids = { path = "./crates/dids", version = "0.2", default-features = false } # crypto +digest = "0.10" k256 = "0.13.1" p256 = "0.13.2" p384 = "0.13.0" @@ -103,6 +106,8 @@ rand_chacha = "0.3.1" getrandom = "0.2" contextual = "0.1.6" lazy_static = "1.4.0" +indexmap = "2.0.0" +uuid = "0.8" [features] default = ["w3c", "rsa", "ed25519", "secp256k1", "secp256r1", "ripemd-160", "eip712"] @@ -230,7 +235,7 @@ document-features = "0.2" [dev-dependencies] async-std = { workspace = true, features = ["attributes"] } -uuid = { version = "0.8", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } iref.workspace = true static-iref.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/crates/bbs/Cargo.toml b/crates/bbs/Cargo.toml new file mode 100644 index 000000000..0ca1c55fc --- /dev/null +++ b/crates/bbs/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ssi-bbs" +version = "0.1.0" +edition = "2021" +authors = ["Spruce Systems, Inc."] +license = "Apache-2.0" +description = "The BBS Signature Scheme implementation for SSI" +repository = "https://github.com/spruceid/ssi/" +documentation = "https://docs.rs/ssi-bbs/" + +[dependencies] +ssi-verification-methods = { workspace = true, features = ["ed25519"] } +zkryptium = "0.2.2" \ No newline at end of file diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs new file mode 100644 index 000000000..735199d7f --- /dev/null +++ b/crates/bbs/src/lib.rs @@ -0,0 +1,68 @@ +use ssi_verification_methods::{MessageSignatureError, MultiSigningMethod, Multikey}; +use zkryptium::bbsplus::commitment::BlindFactor; +pub use zkryptium::bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}; + +pub enum Bbs { + Baseline { + header: [u8; 64], + }, + Blind { + header: [u8; 64], + commitment_with_proof: Option>, + signer_blind: Option<[u8; 32]>, + }, +} + +pub fn bbs_public_key_from_multikey(multikey: &Multikey) -> BBSplusPublicKey { + todo!() +} + +impl MultiSigningMethod for Multikey { + fn sign_bytes_multi( + &self, + secret: &BBSplusSecretKey, + algorithm: Bbs, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + use zkryptium::{ + bbsplus::ciphersuites::Bls12381Sha256, + schemes::{ + algorithms::BBSplus, + generics::{BlindSignature, Signature}, + }, + }; + + let pk = bbs_public_key_from_multikey(self); + let signature = match algorithm { + Bbs::Baseline { header } => Signature::>::sign( + Some(messages), + secret, + &pk, + Some(&header), + ) + .map_err(MessageSignatureError::signature_failed)? + .to_bytes() + .to_vec(), + Bbs::Blind { + header, + commitment_with_proof, + signer_blind, + } => { + let signer_blind = signer_blind.map(|b| BlindFactor::from_bytes(&b).unwrap()); + BlindSignature::>::blind_sign( + secret, + &pk, + commitment_with_proof.as_deref(), + Some(&header), + Some(messages), + signer_blind.as_ref(), + ) + .map_err(MessageSignatureError::signature_failed)? + .to_bytes() + .to_vec() + } + }; + + Ok(signature) + } +} diff --git a/crates/claims/core/src/signature.rs b/crates/claims/core/src/signature.rs index 4df3922e6..db076554a 100644 --- a/crates/claims/core/src/signature.rs +++ b/crates/claims/core/src/signature.rs @@ -26,6 +26,9 @@ pub enum SignatureError { #[error("claims: {0}")] Claims(String), + #[error("missing required option `{0}`")] + MissingRequiredOption(String), + #[error("missing signer")] MissingSigner, @@ -43,6 +46,10 @@ impl SignatureError { pub fn other(e: impl fmt::Display) -> Self { Self::Other(e.to_string()) } + + pub fn missing_required_option(name: &str) -> Self { + Self::MissingRequiredOption(name.to_string()) + } } impl From for SignatureError { diff --git a/crates/claims/crates/data-integrity/core/Cargo.toml b/crates/claims/crates/data-integrity/core/Cargo.toml index b11f972d3..feb090cd7 100644 --- a/crates/claims/crates/data-integrity/core/Cargo.toml +++ b/crates/claims/crates/data-integrity/core/Cargo.toml @@ -36,7 +36,7 @@ futures.workspace = true self_cell = "1.0.1" contextual.workspace = true multibase.workspace = true -digest = "0.10" +digest.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml b/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml new file mode 100644 index 000000000..0e0c099d6 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ssi-di-sd-primitives" +version = "0.1.0" +edition = "2021" +authors = ["Spruce Systems, Inc."] +license = "Apache-2.0" +description = "Selective Disclosure primitive for Data-Integrity suites" +repository = "https://github.com/spruceid/ssi/" +documentation = "https://docs.rs/ssi-di-sd-primitives/" + +[dependencies] +ssi-rdf.workspace = true +ssi-json-ld.workspace = true +linked-data.workspace = true +iref.workspace = true +hmac = { version = "0.12.1", features = ["reset"] } +sha2.workspace = true +rdf-types.workspace = true +base64.workspace = true +digest.workspace = true +serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true +uuid = { workspace = true, features = ["v4"] } + +[dev-dependencies] +async-std = { workspace = true, features = ["attributes"] } +lazy_static.workspace = true +json-syntax.workspace = true +hex.workspace = true \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs new file mode 100644 index 000000000..c24b7c486 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs @@ -0,0 +1,80 @@ +use hmac::Mac; +use iref::Iri; +use rdf_types::{BlankId, BlankIdBuf, Id, LexicalQuad, LexicalQuadRef, Literal, Quad, Term}; +use ssi_rdf::urdna2015::NormalizingSubstitution; +use std::collections::HashMap; + +use crate::HmacSha256; + +pub fn create_hmac_id_label_map_function( + hmac: &mut HmacSha256, +) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { + move |canonical_map| { + canonical_map + .iter() + .map(|(key, value)| { + hmac.update(value.suffix().as_bytes()); + let digest = hmac.finalize_reset().into_bytes(); + let b64_url_digest = BlankIdBuf::new(format!( + "_:u{}", + base64::encode_config(&digest, base64::URL_SAFE_NO_PAD) + )) + .unwrap(); + (key.clone(), b64_url_digest) + }) + .collect() + } +} + +pub fn label_replacement_canonicalize_nquads( + mut label_map_factory: impl FnMut(&NormalizingSubstitution) -> HashMap, + quads: &[LexicalQuad], +) -> (Vec, HashMap) { + let quads_ref = quads.iter().map(LexicalQuad::as_lexical_quad_ref); + let bnode_identifier_map = ssi_rdf::urdna2015::normalize(quads_ref).into_substitution(); + + let label_map = label_map_factory(&bnode_identifier_map); + + let canonical_quads = quads + .iter() + .map(|quad| relabel_quad(&label_map, quad.as_lexical_quad_ref())) + .collect(); + + (canonical_quads, label_map) +} + +pub fn relabel_quads( + label_map: &HashMap, + quads: &[LexicalQuad], +) -> Vec { + quads + .iter() + .map(|quad| relabel_quad(label_map, quad.as_lexical_quad_ref())) + .collect() +} + +fn relabel_quad(label_map: &HashMap, quad: LexicalQuadRef) -> LexicalQuad { + Quad( + relabel_id(label_map, quad.0), + quad.1.to_owned(), + relabel_term(label_map, quad.2), + quad.3.map(|g| relabel_id(label_map, g)), + ) +} + +fn relabel_id(label_map: &HashMap, id: Id<&Iri, &BlankId>) -> Id { + match id { + Id::Iri(i) => Id::Iri(i.to_owned()), + Id::Blank(b) => Id::Blank(label_map.get(b).unwrap().to_owned()), + } +} + +fn relabel_term( + label_map: &HashMap, + term: Term, &Literal>, +) -> Term { + match term { + Term::Id(id) => Term::Id(relabel_id(label_map, id)), + Term::Literal(l) => Term::Literal(l.clone()), + } +} diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs new file mode 100644 index 000000000..3ffcdc653 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs @@ -0,0 +1,254 @@ +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, +}; + +use linked_data::IntoQuadsError; +use rdf_types::{BlankIdBuf, LexicalQuad}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdObject}; +use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; + +use crate::{ + canonicalize::label_replacement_canonicalize_nquads, + select::{select_canonical_nquads, SelectError}, + skolemize::{expanded_to_deskolemized_nquads, SkolemError, Skolemize}, + JsonPointerBuf, +}; + +#[derive(Debug, thiserror::Error)] +pub enum GroupError { + #[error(transparent)] + Skolem(#[from] SkolemError), + + #[error(transparent)] + NQuads(#[from] IntoQuadsError), + + #[error(transparent)] + Select(#[from] SelectError), +} + +/// Canonicalize and group. +/// +/// See: +pub async fn canonicalize_and_group( + loader: &impl ssi_json_ld::Loader, + label_map_factory_function: impl FnMut(&NormalizingSubstitution) -> HashMap, + group_definitions: HashMap>, + document: &T, +) -> Result, GroupError> +where + T: JsonLdObject + Expandable, + T::Expanded: Into, + N: Eq + Hash, +{ + let mut skolemize = Skolemize::default(); + + let (skolemized_expanded_document, skolemized_compact_document) = + skolemize.compact_document(loader, document).await?; + + let deskolemized_quads = + expanded_to_deskolemized_nquads(&skolemize.urn_scheme, &skolemized_expanded_document)?; + + let (quads, label_map) = + label_replacement_canonicalize_nquads(label_map_factory_function, &deskolemized_quads); + + let mut selection = HashMap::new(); + for (name, pointers) in group_definitions { + selection.insert( + name, + select_canonical_nquads( + loader, + &skolemize.urn_scheme, + pointers, + &label_map, + &skolemized_compact_document, + ) + .await?, + ); + } + + let mut groups = HashMap::new(); + + for (name, selection_result) in selection { + let mut matching = HashMap::new(); + let mut non_matching = HashMap::new(); + + let selected_quads: HashSet<_> = selection_result.quads.into_iter().collect(); + // let selected_deskolemized_quads = selection_result.deskolemized_quads; + + for (i, nq) in quads.iter().enumerate() { + if selected_quads.contains(nq) { + matching.insert(i, nq.clone()); + } else { + non_matching.insert(i, nq.clone()); + } + } + + groups.insert( + name, + Group { + matching, + non_matching, + // deskolemized_quads: selected_deskolemized_quads, + }, + ); + } + + Ok(CanonicalizedAndGrouped { + groups, + // skolemized_expanded_document, + // skolemized_compact_document, + // deskolemized_quads, + label_map, + quads, + }) +} + +pub struct CanonicalizedAndGrouped { + pub groups: HashMap, + // skolemized_expanded_document: json_ld::ExpandedDocument, + // skolemized_compact_document: json_ld::syntax::Object, + // deskolemized_quads: Vec, + pub label_map: HashMap, + pub quads: Vec, +} + +pub struct Group { + pub matching: HashMap, + pub non_matching: HashMap, + // pub deskolemized_quads: Vec, +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use hmac::{Hmac, Mac}; + use lazy_static::lazy_static; + use ssi_json_ld::CompactJsonLd; + use ssi_rdf::IntoNQuads; + + use crate::{canonicalize::create_hmac_id_label_map_function, JsonPointerBuf}; + + use super::canonicalize_and_group; + + const HMAC_KEY_STRING: &str = + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + + lazy_static! { + pub static ref CREDENTIAL: CompactJsonLd = CompactJsonLd(json_syntax::json!({ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7.0, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + } + })); + pub static ref MANDATORY_POINTERS: Vec = vec![ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; + } + + #[derive(PartialEq, Eq, Hash)] + struct Mandatory; + + #[async_std::test] + async fn test_canonicalize_and_group() { + let loader = ssi_json_ld::ContextLoader::default(); + + let hmac_key = hex::decode(HMAC_KEY_STRING).unwrap(); + let mut hmac = Hmac::new_from_slice(&hmac_key).unwrap(); + let label_map_factory_function = create_hmac_id_label_map_function(&mut hmac); + + let mut group_definitions = HashMap::new(); + group_definitions.insert(Mandatory, MANDATORY_POINTERS.clone()); + + let result = canonicalize_and_group( + &loader, + label_map_factory_function, + group_definitions, + &CREDENTIAL.clone(), + ) + .await + .unwrap(); + + const EXPECTED_NQUADS: [&str; 28] = [ + "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY .\n", + "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY _:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 .\n", + "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY .\n", + "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"Lahaina\" .\n", + "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"7.8E0\"^^ .\n", + "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"2023\"^^ .\n", + "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"CompFoil170\" .\n", + "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"Wailea\" .\n", + "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"2022\"^^ .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 \"Earth101\" .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk .\n", + "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc .\n", + "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"Kanaha Custom\" .\n", + "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"Wailea\" .\n", + "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"2019\"^^ .\n", + "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"Kihei\" .\n", + "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"5.5E0\"^^ .\n", + "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"2023\"^^ .\n", + "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"Lahaina\" .\n", + "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"6.1E0\"^^ .\n", + "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"2023\"^^ .\n", + "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"Lahaina\" .\n", + "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"7\"^^ .\n", + "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"2020\"^^ .\n" + ]; + + assert_eq!(result.quads.into_nquads_lines(), EXPECTED_NQUADS); + // let group = result.groups.get(&Mandatory).unwrap(); + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs similarity index 69% rename from crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs rename to crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs index 1258a15ce..2bfea62a4 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/json_pointer.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs @@ -1,10 +1,11 @@ -use std::{borrow::Cow, ops::Deref}; +use core::fmt; +use std::{borrow::Cow, ops::Deref, str::FromStr}; use serde::Serialize; #[derive(Debug, Clone, Copy, thiserror::Error)] #[error("invalid JSON pointer `{0}`")] -pub struct InvalidJsonPointer(pub T); +pub struct InvalidJsonPointer(pub T); /// JSON Pointer. /// @@ -36,25 +37,42 @@ impl JsonPointer { true } + pub fn as_str(&self) -> &str { + &self.0 + } + pub fn is_empty(&self) -> bool { self.0.is_empty() } - pub fn split_first(&self) -> Option<(&ReferenceToken, &Self)> { + fn token_end(&self) -> Option { if self.is_empty() { None } else { - let after_sep = &self.0[1..]; - let (token, rest) = after_sep.split_once('/').unwrap_or((after_sep, "")); - Some(unsafe { - ( - ReferenceToken::new_unchecked(token), - Self::new_unchecked(rest), - ) - }) + let mut i = 1; + + let bytes = self.0.as_bytes(); + while i < bytes.len() { + if bytes[i] == b'/' { + break; + } + + i += 1 + } + + Some(i) } } + pub fn split_first(&self) -> Option<(&ReferenceToken, &Self)> { + self.token_end().map(|i| unsafe { + ( + ReferenceToken::new_unchecked(&self.0[1..i]), + Self::new_unchecked(&self.0[i..]), + ) + }) + } + pub fn iter(&self) -> JsonPointerIter { let mut tokens = self.0.split('/'); tokens.next(); @@ -62,6 +80,12 @@ impl JsonPointer { } } +impl fmt::Display for JsonPointer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + impl<'a> IntoIterator for &'a JsonPointer { type Item = &'a ReferenceToken; type IntoIter = JsonPointerIter<'a>; @@ -87,6 +111,16 @@ impl<'a> Iterator for JsonPointerIter<'a> { #[derive(Debug, Clone, Serialize)] pub struct JsonPointerBuf(String); +impl JsonPointerBuf { + pub fn new(value: String) -> Result { + if JsonPointer::validate(&value) { + Ok(Self(value)) + } else { + Err(InvalidJsonPointer(value)) + } + } +} + impl Deref for JsonPointerBuf { type Target = JsonPointer; @@ -95,6 +129,20 @@ impl Deref for JsonPointerBuf { } } +impl FromStr for JsonPointerBuf { + type Err = InvalidJsonPointer; + + fn from_str(s: &str) -> Result { + Self::new(s.to_owned()) + } +} + +impl fmt::Display for JsonPointerBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + #[derive(Debug)] #[repr(transparent)] pub struct ReferenceToken(str); @@ -153,3 +201,9 @@ impl ReferenceToken { } } } + +impl fmt::Display for ReferenceToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs b/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs new file mode 100644 index 000000000..b4be06032 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs @@ -0,0 +1,12 @@ +pub use hmac::Hmac; +use sha2::Sha256; + +pub type HmacSha256 = Hmac; + +pub mod canonicalize; +pub mod group; +pub mod json_pointer; +pub mod select; +pub mod skolemize; + +pub use json_pointer::{JsonPointer, JsonPointerBuf}; diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs new file mode 100644 index 000000000..a5b4755c2 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs @@ -0,0 +1,311 @@ +use std::collections::{BTreeMap, HashMap}; + +use ssi_json_ld::{syntax::Value, Print}; +use rdf_types::{BlankId, BlankIdBuf, LexicalQuad}; + +use crate::{ + canonicalize::relabel_quads, + skolemize::{compact_to_deskolemized_nquads, SkolemError}, + JsonPointer, JsonPointerBuf, +}; + +#[derive(Debug, thiserror::Error)] +pub enum SelectError { + #[error("dangling JSON pointer")] + DanglingJsonPointer, + + #[error(transparent)] + Skolem(#[from] SkolemError), +} + +impl From for SelectError { + fn from(_: DanglingJsonPointer) -> Self { + Self::DanglingJsonPointer + } +} + +pub async fn select_canonical_nquads( + loader: &impl ssi_json_ld::Loader, + urn_scheme: &str, + pointers: Vec, + label_map: &HashMap, + skolemized_compact_document: &ssi_json_ld::syntax::Object, +) -> Result { + eprintln!("select pointers"); + for pointer in &pointers { + eprintln!("{pointer}") + } + eprintln!( + "in {}", + Value::Object(skolemized_compact_document.clone()).pretty_print() + ); + + let selection_document = select_json_ld(pointers, skolemized_compact_document)?; + + let deskolemized_quads = match selection_document.clone() { + Some(selection_document) => { + compact_to_deskolemized_nquads(loader, urn_scheme, selection_document).await? + } + None => Vec::new(), + }; + + let quads = relabel_quads(label_map, &deskolemized_quads); + + Ok(CanonicalNquadsSelection { + // selection_document, + // deskolemized_quads, + quads, + }) +} + +pub struct CanonicalNquadsSelection { + // selection_document: Option, + // deskolemized_quads: Vec, + pub quads: Vec, +} + +/// See: +pub fn select_json_ld( + pointers: Vec, + document: &ssi_json_ld::syntax::Object, +) -> Result, DanglingJsonPointer> { + if pointers.is_empty() { + return Ok(None); + } + + let mut selection_document = create_initial_selection_object(document); + if let Some(context) = document.get("@context").next() { + selection_document.insert("@context".into(), SparseValue::from_dense(context)); + } + + for pointer in pointers { + document.select(&pointer, &mut selection_document)?; + } + + Ok(Some(selection_document.into_dense())) +} + +fn create_initial_selection(source: &Value) -> SparseValue { + match source { + Value::Null => SparseValue::Null, + Value::Boolean(b) => SparseValue::Boolean(*b), + Value::Number(n) => SparseValue::Number(n.clone()), + Value::String(s) => SparseValue::String(s.clone()), + Value::Array(_) => SparseValue::Array(SparseArray::default()), + Value::Object(object) => SparseValue::Object(create_initial_selection_object(object)), + } +} + +fn create_initial_selection_object(source: &ssi_json_ld::syntax::Object) -> SparseObject { + let mut selection = SparseObject::new(); + + if let Some(Value::String(id)) = source.get("id").next() { + if BlankId::new(id).is_err() { + selection.insert("id".into(), SparseValue::String(id.to_owned())); + } + } + + if let Some(type_) = source.get("type").next() { + selection.insert("type".into(), SparseValue::from_dense(type_)); + } + + selection +} + +#[derive(Debug)] +pub enum SparseValue { + Null, + Boolean(bool), + String(ssi_json_ld::syntax::String), + Number(ssi_json_ld::syntax::NumberBuf), + Array(SparseArray), + Object(SparseObject), +} + +impl SparseValue { + pub fn from_dense(value: &Value) -> Self { + match value { + Value::Null => Self::Null, + Value::Boolean(b) => Self::Boolean(*b), + Value::String(s) => Self::String(s.clone()), + Value::Number(n) => Self::Number(n.clone()), + Value::Array(a) => Self::Array(SparseArray::from_dense(a)), + Value::Object(o) => Self::Object(SparseObject::from_dense(o)), + } + } + + pub fn into_dense(self) -> Value { + match self { + Self::Null => Value::Null, + Self::Boolean(b) => Value::Boolean(b), + Self::Number(n) => Value::Number(n), + Self::String(s) => Value::String(s), + Self::Array(a) => Value::Array(a.into_dense()), + Self::Object(o) => Value::Object(o.into_dense()), + } + } +} + +#[derive(Debug, Default)] +pub struct SparseArray(BTreeMap); + +impl SparseArray { + pub fn from_dense(value: &Vec) -> Self { + Self( + value + .iter() + .enumerate() + .map(|(i, item)| (i, SparseValue::from_dense(item))) + .collect(), + ) + } + + pub fn get_mut_or_insert_with( + &mut self, + i: usize, + f: impl FnOnce() -> SparseValue, + ) -> &mut SparseValue { + if !self.0.contains_key(&i) { + self.0.insert(i, f()); + } + + self.0.get_mut(&i).unwrap() + } + + pub fn into_dense(self) -> Vec { + self.0.into_values().map(SparseValue::into_dense).collect() + } +} + +#[derive(Debug, Default)] +pub struct SparseObject(BTreeMap); + +impl SparseObject { + pub fn new() -> Self { + Self::default() + } + + pub fn from_dense(value: &ssi_json_ld::syntax::Object) -> Self { + Self( + value + .iter() + .map(|entry| { + ( + entry.key.as_str().to_owned(), + SparseValue::from_dense(&entry.value), + ) + }) + .collect(), + ) + } + + pub fn get_mut_or_insert_with( + &mut self, + key: &str, + f: impl FnOnce() -> SparseValue, + ) -> &mut SparseValue { + if !self.0.contains_key(key) { + self.0.insert(key.to_owned(), f()); + } + + self.0.get_mut(key).unwrap() + } + + pub fn insert(&mut self, key: String, value: SparseValue) { + self.0.insert(key, value); + } + + pub fn into_dense(self) -> ssi_json_ld::syntax::Object { + self.0 + .into_iter() + .map(|(key, value)| (key.into(), value.into_dense())) + .collect() + } +} + +trait Select { + type Sparse; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer>; +} + +impl Select for Value { + type Sparse = SparseValue; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match (self, selection) { + (Self::Array(a), SparseValue::Array(b)) => a.select(pointer, b), + (Self::Object(a), SparseValue::Object(b)) => a.select(pointer, b), + _ => { + if pointer.is_empty() { + Ok(()) + } else { + eprintln!("tail pointer = {pointer}"); + Err(DanglingJsonPointer) + } + } + } + } +} + +impl Select for Vec { + type Sparse = SparseArray; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match pointer.split_first() { + Some((token, rest)) => { + eprintln!("array token = {token}"); + let i = token.as_array_index().ok_or(DanglingJsonPointer)?; + let a_item = self.get(i).ok_or(DanglingJsonPointer)?; + let b_item = + selection.get_mut_or_insert_with(i, || create_initial_selection(a_item)); + a_item.select(rest, b_item) + } + None => { + *selection = SparseArray::from_dense(self); + Ok(()) + } + } + } +} + +impl Select for ssi_json_ld::syntax::Object { + type Sparse = SparseObject; + + fn select( + &self, + pointer: &JsonPointer, + selection: &mut Self::Sparse, + ) -> Result<(), DanglingJsonPointer> { + match pointer.split_first() { + Some((token, rest)) => { + eprintln!("object token = {token}"); + let key = token.to_str(); + let a_item = self.get(key.as_ref()).next().ok_or(DanglingJsonPointer)?; + let b_item = + selection.get_mut_or_insert_with(&key, || create_initial_selection(a_item)); + a_item.select(rest, b_item) + } + None => { + *selection = SparseObject::from_dense(self); + Ok(()) + } + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("dangling JSON pointer")] +pub struct DanglingJsonPointer; diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs new file mode 100644 index 000000000..df52cb606 --- /dev/null +++ b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs @@ -0,0 +1,214 @@ +use iref::IriBuf; +use ssi_json_ld::{ + context_processing::{Process, ProcessedOwned}, syntax::Value, Compact, ExpandedDocument, JsonLdProcessor, RemoteDocument +}; +use linked_data::IntoQuadsError; +use rdf_types::{ + generator, + BlankId, BlankIdBuf, Id, LexicalQuad, Term, +}; +use ssi_json_ld::{Expandable, JsonLdObject}; +use ssi_rdf::LexicalInterpretation; +use uuid::Uuid; + +#[derive(Debug, thiserror::Error)] +pub enum SkolemError { + #[error("RDF serialization failed: {0}")] + ToRdf(String), + + #[error("JSON-LD expansion failed: {0}")] + JsonLdExpansion(String), + + #[error("JSON-LD context processing failed: {0}")] + ContextProcessing(String), + + #[error("JSON-LD compaction failed: {0}")] + Compaction(String), + + #[error("expected JSON object")] + ExpectedJsonObject, +} + +impl SkolemError { + pub fn to_rdf(e: impl ToString) -> Self { + Self::ToRdf(e.to_string()) + } + + pub fn json_ld_expansion(e: impl ToString) -> Self { + Self::JsonLdExpansion(e.to_string()) + } + + pub fn context_processing(e: impl ToString) -> Self { + Self::ContextProcessing(e.to_string()) + } + + pub fn compaction(e: impl ToString) -> Self { + Self::Compaction(e.to_string()) + } +} + +pub fn expanded_to_deskolemized_nquads( + urn_scheme: &str, + document: &ssi_json_ld::ExpandedDocument, +) -> Result, IntoQuadsError> { + let mut quads = linked_data::to_lexical_quads(generator::Blank::new(), &document)?; + + deskolemize_nquads(urn_scheme, &mut quads); + + Ok(quads) +} + +pub async fn compact_to_deskolemized_nquads( + loader: &impl ssi_json_ld::Loader, + urn_scheme: &str, + document: ssi_json_ld::syntax::Object, +) -> Result, SkolemError> { + let mut quads: Vec = RemoteDocument::new(None, None, Value::Object(document)) + .to_rdf(&mut generator::Blank::new(), loader) + .await + .map_err(SkolemError::to_rdf)? + .cloned_quads() + .map(|quad| quad.map_predicate(|p| p.into_iri().unwrap())) + .collect(); + + deskolemize_nquads(urn_scheme, &mut quads); + + Ok(quads) +} + +pub fn deskolemize_nquads(urn_scheme: &str, quads: &mut [LexicalQuad]) { + for quad in quads { + deskolemize_id(urn_scheme, &mut quad.0); + deskolemize_term(urn_scheme, &mut quad.2); + + if let Some(g) = quad.graph_mut() { + deskolemize_id(urn_scheme, g); + } + } +} + +pub fn deskolemize_id(urn_scheme: &str, id: &mut Id) { + if let Id::Iri(iri) = id { + if iri.scheme().as_str() == "urn" { + let path = iri.path(); + if let Some((prefix, suffix)) = path.split_once(':') { + if prefix == urn_scheme { + let blank_id = BlankIdBuf::from_suffix(suffix).unwrap(); + *id = Id::Blank(blank_id) + } + } + } + } +} + +pub fn deskolemize_term(urn_scheme: &str, term: &mut Term) { + if let Term::Id(id) = term { + deskolemize_id(urn_scheme, id) + } +} + +pub struct Skolemize { + pub urn_scheme: String, + pub random_string: String, + pub count: u32, +} + +impl Default for Skolemize { + fn default() -> Self { + Self { + urn_scheme: "bnid".to_owned(), + random_string: Uuid::new_v4().to_string(), + count: 0, + } + } +} + +impl rdf_types::Generator for Skolemize { + fn next(&mut self, _vocabulary: &mut ()) -> Id { + Id::Iri(self.fresh_blank_id()) + } +} + +impl Skolemize { + pub fn fresh_blank_id(&mut self) -> IriBuf { + let id = IriBuf::new(format!( + "urn:{}:{}_{}", + self.urn_scheme, self.random_string, self.count + )) + .unwrap(); + self.count += 1; + id + } + + pub fn blank_id(&mut self, blank_id: &BlankId) -> IriBuf { + IriBuf::new(format!("urn:{}:{}", self.urn_scheme, blank_id.suffix())).unwrap() + } + + /// See: + pub async fn compact_document( + &mut self, + loader: &impl ssi_json_ld::Loader, + document: &T, + ) -> Result<(ssi_json_ld::ExpandedDocument, ssi_json_ld::syntax::Object), SkolemError> + where + T: JsonLdObject + Expandable, + T::Expanded: Into, + { + let expanded = document + .expand(loader) + .await + .map_err(SkolemError::json_ld_expansion)?; + + let skolemized_expanded_document = self.expanded_document(expanded.into()); + + let processed_context: ProcessedOwned = match document.json_ld_context() + { + Some(ld_context) => { + let processed = ld_context + .process(&mut (), loader, None) + .await + .map_err(SkolemError::context_processing)? + .processed; + + ProcessedOwned::new(ld_context.into_owned(), processed) + } + None => ProcessedOwned::new( + ssi_json_ld::syntax::Context::default(), + ssi_json_ld::Context::default(), + ), + }; + + let skolemized_compact_document = skolemized_expanded_document + .compact(processed_context.as_ref(), loader) + .await + .map_err(SkolemError::compaction)? + .into_object() + .ok_or(SkolemError::ExpectedJsonObject)?; + + Ok((skolemized_expanded_document, skolemized_compact_document)) + } + + /// See: + pub fn expanded_document( + &mut self, + expanded: ssi_json_ld::ExpandedDocument, + ) -> ssi_json_ld::ExpandedDocument { + let mut result = expanded.map_ids( + |i| i, + |id| match id { + ssi_json_ld::Id::Valid(id) => match id { + Id::Blank(b) => { + ssi_json_ld::Id::Valid(Id::Iri(self.blank_id(&b))) + } + Id::Iri(i) => { + ssi_json_ld::Id::Valid(Id::Iri(i)) + } + }, + ssi_json_ld::Id::Invalid(s) => ssi_json_ld::Id::Invalid(s), + }, + ); + + result.identify_all(self); + result + } +} diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 04bb11b7a..c4942281d 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -145,8 +145,11 @@ serde_json = { workspace = true, optional = true } serde_jcs = { workspace = true, optional = true } # BBS -bbs_plus = "0.20.0" +ssi-di-sd-primitives.workspace = true +ssi-bbs.workspace = true hmac = "0.12.1" +serde_cbor = "0.11.2" +# rand_chacha.workspace = true [dev-dependencies] async-std = { version = "1.9", features = ["attributes"] } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 3aa64832a..6b7589c26 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,54 +1,20 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: -use std::{ - borrow::Cow, - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, - fmt, - hash::Hash, - ops::Deref, -}; - -use getrandom::getrandom; -use hmac::{digest::KeyInit, Hmac}; -use iref::IriBuf; -use ssi_json_ld::{ - context_processing::ProcessedOwned, syntax::Value, Compact, JsonLdProcessor, Process, Profile, - RemoteDocument, -}; -use k256::sha2::{Digest, Sha256}; -use linked_data::IntoQuadsError; -use rdf_types::{ - generator, - vocabulary::{ExtractFromVocabulary, IriVocabulary, IriVocabularyMut}, - BlankId, BlankIdBuf, Id, LexicalQuad, Term, Vocabulary, VocabularyMut, -}; -use serde::{Deserialize, Serialize}; -use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use serde::Serialize; +use ssi_bbs::BBSplusPublicKey; use ssi_data_integrity_core::{ - suite::{ - standard::{ - HashingAlgorithm, HashingError, SignatureAlgorithm, SignatureAndVerificationAlgorithm, - TransformationAlgorithm, TransformationError, TransformedData, - TypedTransformationAlgorithm, VerificationAlgorithm, - }, - ConfigurationAlgorithm, ConfigurationError, InputProofOptions, - }, - ProofConfiguration, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef, + suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, + ProofConfiguration, StandardCryptographicSuite, TypeRef, }; -use ssi_json_ld::{JsonLdObject, Expandable}; -use ssi_rdf::LdEnvironment; +use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_security::MultibaseBuf; use ssi_verification_methods::Multikey; -mod json_pointer; -pub use json_pointer::*; - -mod transformation; -use transformation::TransformedDerived; -pub use transformation::{Bbs2023Transformation, Bbs2023TransformationOptions, Transformed}; +pub(crate) mod transformation; +pub use transformation::{Bbs2023Transformation, Transformed}; mod hashing; -use hashing::BaseHashData; pub use hashing::{Bbs2023Hashing, HashData}; mod signature; @@ -56,6 +22,9 @@ pub use signature::Bbs2023SignatureAlgorithm; mod verification; +#[cfg(test)] +mod tests; + /// The `bbs-2023` cryptographic suite. #[derive(Debug, Clone, Copy)] pub struct Bbs2023; @@ -69,7 +38,7 @@ impl StandardCryptographicSuite for Bbs2023 { type VerificationMethod = Multikey; - type ProofOptions = Bbs2023Options; + type ProofOptions = (); type SignatureAlgorithm = Bbs2023SignatureAlgorithm; @@ -84,8 +53,6 @@ pub struct Bbs2023InputOptions { pub feature_option: FeatureOption, pub commitment_with_proof: Option>, - - pub proof_options: Bbs2023Options, } #[derive(Debug, Default, Clone, Copy)] @@ -97,6 +64,8 @@ pub enum FeatureOption { PseudonymHiddenPid, } +pub type HmacKey = [u8; 32]; + /// Base Proof Configuration. /// /// See: @@ -113,26 +82,85 @@ impl ConfigurationAlgorithm for Bbs2023Configuration { type InputSignatureOptions = Bbs2023InputOptions; /// Document transformation options. - type TransformationOptions = Bbs2023TransformationOptions; + type TransformationOptions = Bbs2023InputOptions; fn configure( - _: &Bbs2023, + type_: &Bbs2023, options: InputProofOptions, signature_options: Bbs2023InputOptions, - ) -> Result<(ProofConfiguration, Bbs2023TransformationOptions), ConfigurationError> - { - todo!() + ) -> Result<(ProofConfiguration, Bbs2023InputOptions), ConfigurationError> { + let proof_configuration = options.into_configuration(type_.clone())?; + Ok((proof_configuration, signature_options)) } } #[derive(Serialize)] -pub struct Bbs2023Options; +pub struct Bbs2023Signature { + pub proof_value: MultibaseBuf, +} -#[derive(Serialize)] -pub struct Bbs2023Signature; +impl Bbs2023Signature { + pub fn encode( + signature_bytes: &[u8], + bbs_header: [u8; 64], + public_key: &BBSplusPublicKey, + hmac_key: [u8; 32], + mandatory_pointers: &[JsonPointerBuf], + description: Bbs2023SignatureDescription, + ) -> Self { + let mut components = vec![ + serde_cbor::Value::Bytes(signature_bytes.to_vec()), + serde_cbor::Value::Bytes(bbs_header.to_vec()), + serde_cbor::Value::Bytes(public_key.to_bytes().to_vec()), + serde_cbor::Value::Bytes(hmac_key.to_vec()), + serde_cbor::Value::Array( + mandatory_pointers + .iter() + .map(|p| p.as_str().to_owned().into()) + .collect(), + ), + ]; + + let tag = match description { + Bbs2023SignatureDescription::Baseline => 0x02, + Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind } => { + components.push(match signer_blind { + Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), + None => serde_cbor::Value::Null, + }); + 0x04 + } + Bbs2023SignatureDescription::PseudonymIssuerPid { pid } => { + components.push(serde_cbor::Value::Bytes(pid.to_vec())); + 0x06 + } + Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind } => { + components.push(match signer_blind { + Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), + None => serde_cbor::Value::Null, + }); + 0x08 + } + }; + + let mut proof_value = vec![0xd9, 0x5d, tag]; + serde_cbor::to_writer(&mut proof_value, &components).unwrap(); + + Self { + proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), + } + } +} impl AsRef for Bbs2023Signature { fn as_ref(&self) -> &str { - todo!() + self.proof_value.as_str() } } + +pub enum Bbs2023SignatureDescription { + Baseline, + AnonymousHolderBinding { signer_blind: Option<[u8; 32]> }, + PseudonymIssuerPid { pid: [u8; 32] }, + PseudonymHiddenPid { signer_blind: Option<[u8; 32]> }, +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs index fa58adfd4..5c3539468 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs @@ -1,51 +1,16 @@ +use ssi_bbs::{bbs_public_key_from_multikey, Bbs}; use ssi_claims_core::SignatureError; use ssi_data_integrity_core::{ suite::standard::{SignatureAlgorithm, SignatureAndVerificationAlgorithm}, ProofConfigurationRef, }; use ssi_rdf::IntoNQuads; -use ssi_verification_methods::{Multikey, SigningMethod}; +use ssi_verification_methods::{MultiMessageSigner, MultiSigner, Multikey}; -use crate::Bbs2023; +use crate::{bbs_2023::Bbs2023SignatureDescription, Bbs2023}; use super::{Bbs2023Signature, FeatureOption, HashData}; -pub enum Bbs { - Baseline { - header: [u8; 64], - }, - Blind { - header: [u8; 64], - signer_blind: Option, - }, - Pseudonym1 { - header: [u8; 64], - pid: u32, - }, - Pseudonym2 { - header: [u8; 64], - commitment_with_proof: String, - signer_blind: Option, - }, -} - -pub trait MultiSigner { - type MessageSigner: MultiMessageSigner; - - async fn for_verification_method( - &self, - method: &M, - ) -> Result; -} - -pub trait MultiMessageSigner { - async fn multi_sign( - self, - algorithm: A, - messages: &[Vec], - ) -> Result, SignatureError>; -} - pub struct Bbs2023SignatureAlgorithm; impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { @@ -60,23 +25,24 @@ where verification_method: &Multikey, signer: T, prepared_claims: HashData, - proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + _proof_configuration: ProofConfigurationRef<'_, Bbs2023>, ) -> Result { match prepared_claims { HashData::Base(hash_data) => { // See: - let feature_option = hash_data.transformed_document.feature_option; + let public_key = bbs_public_key_from_multikey(verification_method); + let feature_option = hash_data.transformed_document.options.feature_option; let proof_hash = &hash_data.proof_hash; - let mandatory_pointers = &hash_data.transformed_document.mandatory_pointers; + let mandatory_pointers = &hash_data.transformed_document.options.mandatory_pointers; let mandatory_hash = &hash_data.mandatory_hash; let non_mandatory = &hash_data.transformed_document.non_mandatory; - let hmap_key = hash_data.transformed_document.hmac_key; + let hmac_key = hash_data.transformed_document.hmac_key; let mut bbs_header = [0; 64]; bbs_header[..32].copy_from_slice(proof_hash); bbs_header[32..].copy_from_slice(mandatory_hash); - let mut bbs_messages: Vec<_> = non_mandatory + let mut messages: Vec<_> = non_mandatory .into_nquads_lines() .into_iter() .map(String::into_bytes) @@ -84,44 +50,63 @@ where let message_signer = signer.for_verification_method(verification_method).await?; - let bbs_signature = match feature_option { - FeatureOption::Baseline => { - message_signer - .multi_sign(Bbs::Baseline { header: bbs_header }, &bbs_messages) - .await? - } - FeatureOption::AnonymousHolderBinding => { - message_signer - .multi_sign( - Bbs::Blind { - header: bbs_header, - signer_blind: None, - }, - &bbs_messages, - ) - .await? - } + let (algorithm, description) = match feature_option { + FeatureOption::Baseline => ( + Bbs::Baseline { header: bbs_header }, + Bbs2023SignatureDescription::Baseline, + ), + FeatureOption::AnonymousHolderBinding => ( + Bbs::Blind { + header: bbs_header, + commitment_with_proof: None, + signer_blind: None, + }, + Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind: None }, + ), FeatureOption::PseudonymIssuerPid => { - let mut pid_buffer = [0u8; 4]; - getrandom::getrandom(&mut pid_buffer); - let pid = u32::from_ne_bytes(pid_buffer); - message_signer - .multi_sign( - Bbs::Pseudonym1 { - header: bbs_header, - pid, - }, - &bbs_messages, - ) - .await? + // See: + let mut pid = [0u8; 32]; + getrandom::getrandom(&mut pid).map_err(SignatureError::other)?; + + messages.push(pid.to_vec()); + + ( + Bbs::Baseline { header: bbs_header }, + Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, + ) } FeatureOption::PseudonymHiddenPid => { - todo!() + // See: + let commitment_with_proof = hash_data + .transformed_document + .options + .commitment_with_proof + .clone() + .ok_or_else(|| { + SignatureError::missing_required_option("commitment_with_proof") + })?; + + ( + Bbs::Blind { + header: bbs_header, + commitment_with_proof: Some(commitment_with_proof), + signer_blind: None, + }, + Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind: None }, + ) } }; - // Ok(Bbs2023Signature::new(description)) - todo!() + let signature = message_signer.sign_multi(algorithm, &messages).await?; + + Ok(Bbs2023Signature::encode( + &signature, + bbs_header, + &public_key, + hmac_key, + &mandatory_pointers, + description, + )) } HashData::Derived(_) => { todo!() @@ -129,16 +114,3 @@ where } } } - -struct BbsSecretKey; - -impl SigningMethod for Multikey { - fn sign_bytes( - &self, - secret: &BbsSecretKey, - algorithm: Bbs, - bytes: &[u8], - ) -> Result, ssi_verification_methods::MessageSignatureError> { - todo!() - } -} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs new file mode 100644 index 000000000..cf8c97195 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs @@ -0,0 +1,191 @@ +use ssi_json_ld::{JsonLdProcessor, RemoteDocument}; +use lazy_static::lazy_static; +use linked_data::to_lexical_quads; +use rdf_types::{generator, LexicalQuad, Quad}; +use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; +use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_rdf::{urdna2015, IntoNQuads}; + +const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; +const SECRET_KEY_HEX: &str = "66d36e118832af4c5e28b2dfe1b9577857e57b042a33e06bdea37b811ed09ee0"; +const HMAC_KEY_STRING: &str = "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + +lazy_static! { + pub static ref PUBLIC_KEY: BBSplusPublicKey = + BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); + pub static ref SECRET_KEY: BBSplusSecretKey = + BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap(); + pub static ref CREDENTIAL: json_ld::syntax::Value = json_syntax::json!({ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7.0, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + } + }); + pub static ref MANDATORY_POINTERS: Vec = vec![ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; +} + +async fn to_rdf(document: json_ld::syntax::Value) -> Vec { + let document: RemoteDocument = RemoteDocument::new(None, None, document); + let expanded_document = document + .expand(&mut ssi_json_ld::ContextLoader::default()) + .await + .unwrap(); + + to_lexical_quads(&mut generator::Blank::new(), &expanded_document).unwrap() +} + +async fn canonicalize_document(document: json_ld::syntax::Value) -> Vec { + let quads = to_rdf(document).await; + urdna2015::normalize(quads.iter().map(Quad::as_lexical_quad_ref)).collect() +} + +#[async_std::test] +async fn bbs_canonicalization() { + const EXPECTED: [&str; 28] = [ + "_:c14n0 \"CompFoil170\" .\n", + "_:c14n0 \"Wailea\" .\n", + "_:c14n0 \"2022\"^^ .\n", + "_:c14n1 \"Lahaina\" .\n", + "_:c14n1 \"7.8E0\"^^ .\n", + "_:c14n1 \"2023\"^^ .\n", + "_:c14n2 \"Kanaha Custom\" .\n", + "_:c14n2 \"Wailea\" .\n", + "_:c14n2 \"2019\"^^ .\n", + "_:c14n3 \"Lahaina\" .\n", + "_:c14n3 \"7\"^^ .\n", + "_:c14n3 \"2020\"^^ .\n", + "_:c14n4 \"Kihei\" .\n", + "_:c14n4 \"5.5E0\"^^ .\n", + "_:c14n4 \"2023\"^^ .\n", + "_:c14n5 _:c14n0 .\n", + "_:c14n5 _:c14n2 .\n", + "_:c14n5 \"Earth101\" .\n", + "_:c14n5 _:c14n1 .\n", + "_:c14n5 _:c14n3 .\n", + "_:c14n5 _:c14n4 .\n", + "_:c14n5 _:c14n6 .\n", + "_:c14n6 \"Lahaina\" .\n", + "_:c14n6 \"6.1E0\"^^ .\n", + "_:c14n6 \"2023\"^^ .\n", + "_:c14n7 .\n", + "_:c14n7 _:c14n5 .\n", + "_:c14n7 .\n" + ]; + + let canonical_quads = canonicalize_document(CREDENTIAL.clone()) + .await + .into_nquads_lines(); + assert_eq!(canonical_quads, EXPECTED); +} + +// #[async_std::test] +// async fn bbs_hmac_canonicalization() { +// const EXPECTED: [&str; 28] = [ +// "_:b0 .\n", +// "_:b0 _:b3 .\n", +// "_:b0 .\n", +// "_:b1 \"Lahaina\" .\n", +// "_:b1 \"7.8E0\"^^ .\n", +// "_:b1 \"2023\"^^ .\n", +// "_:b2 \"CompFoil170\" .\n", +// "_:b2 \"Wailea\" .\n", +// "_:b2 \"2022\"^^ .\n", +// "_:b3 _:b2 .\n", +// "_:b3 _:b4 .\n", +// "_:b3 \"Earth101\" .\n", +// "_:b3 _:b1 .\n", +// "_:b3 _:b5 .\n", +// "_:b3 _:b6 .\n", +// "_:b3 _:b7 .\n", +// "_:b4 \"Kanaha Custom\" .\n", +// "_:b4 \"Wailea\" .\n", +// "_:b4 \"2019\"^^ .\n", +// "_:b5 \"Kihei\" .\n", +// "_:b5 \"5.5E0\"^^ .\n", +// "_:b5 \"2023\"^^ .\n", +// "_:b6 \"Lahaina\" .\n", +// "_:b6 \"6.1E0\"^^ .\n", +// "_:b6 \"2023\"^^ .\n", +// "_:b7 \"Lahaina\" .\n", +// "_:b7 \"7\"^^ .\n", +// "_:b7 \"2020\"^^ .\n" +// ]; + +// let mut hmac = Hmac::new_from_slice(HMAC_KEY_STRING.as_bytes()).unwrap(); + +// let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); + +// // let mut environment = ssi_json_ld::JsonLdEnvironment::default(); +// // let canonical = canonicalize( +// // &mut environment, +// // label_map_factory_function, +// // &ssi_json_ld::CompactJsonLd(CREDENTIAL.clone()) +// // ).await.unwrap(); + +// // let canonical_nquads = canonical.quads.into_nquads_lines(); + +// let quads = to_rdf(CREDENTIAL.clone()).await; +// let hmac_nquads = label_replacement_canonicalize_nquads(label_map_factory_function, &quads).0.into_nquads_lines(); + +// for line in &hmac_nquads { +// print!("{}", line) +// } + +// assert_eq!(hmac_nquads, EXPECTED); +// } + +// fn key_pair() -> (BBSplusPublicKey, BBSplusSecretKey) { +// ( +// BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(), +// BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap() +// ) +// } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs index 70f708fd1..10f3b7295 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs @@ -1,40 +1,24 @@ -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - fmt, - hash::Hash, -}; +use std::{collections::HashMap, hash::Hash}; use getrandom::getrandom; use hmac::{Hmac, Mac}; -use iref::IriBuf; -use ssi_json_ld::{ - context_processing::{Process, ProcessedOwned}, Compact, ContextLoaderEnvironment, JsonLdProcessor, Profile, RemoteDocument -}; -use json_syntax::Value; use k256::sha2::Sha256; -use linked_data::{IntoQuadsError, LinkedDataResource, LinkedDataSubject}; use rdf_types::{ - generator, - interpretation::ReverseTermInterpretation, - vocabulary::{ExtractFromVocabulary, IriVocabularyMut}, - BlankId, BlankIdBuf, Id, Interpretation, InterpretationMut, LexicalQuad, Term, Vocabulary, - VocabularyMut, + BlankIdBuf, LexicalQuad }; use ssi_data_integrity_core::{ suite::standard::{TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm}, ProofConfigurationRef, }; -use ssi_json_ld::{JsonLdNodeObject, JsonLdObject, Expandable}; -use ssi_rdf::interpretation::WithGenerator; +use ssi_di_sd_primitives::{ + canonicalize::create_hmac_id_label_map_function, group::canonicalize_and_group, +}; +use ssi_json_ld::{ContextLoaderEnvironment, Expandable, JsonLdNodeObject}; +use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; use crate::Bbs2023; -use super::{FeatureOption, JsonPointer, JsonPointerBuf}; - -pub struct Bbs2023TransformationOptions { - feature_options: FeatureOption, - mandatory_pointers: Vec, -} +use super::{Bbs2023InputOptions, HmacKey}; pub struct Bbs2023Transformation; @@ -46,13 +30,13 @@ impl TypedTransformationAlgorithm for Bbs2023Transformation where C: ContextLoaderEnvironment, T: JsonLdNodeObject + Expandable, - T::Expanded, ()>: Into + T::Expanded: Into { async fn transform( context: &C, unsecured_document: &T, proof_configuration: ProofConfigurationRef<'_, Bbs2023>, - transformation_options: Option, + transformation_options: Option, ) -> Result { let canonical_configuration = proof_configuration .expand(context, unsecured_document) @@ -67,12 +51,12 @@ where // Generate a random key let mut hmac_key = HmacKey::default(); getrandom(&mut hmac_key).map_err(TransformationError::internal)?; - let hmac = Hmac::::new_from_slice(&hmac_key); + let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); let mut group_definitions = HashMap::new(); group_definitions.insert(Mandatory, transform_options.mandatory_pointers.clone()); - let label_map_factory_function = || todo!(); + let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); let mut groups = canonicalize_and_group( context.loader(), @@ -80,7 +64,8 @@ where group_definitions, unsecured_document, ) - .await? + .await + .map_err(TransformationError::internal)? .groups; let mandatory_group = groups.remove(&Mandatory).unwrap(); @@ -88,8 +73,7 @@ where let non_mandatory = mandatory_group.non_matching.into_values().collect(); Ok(Transformed::Base(TransformedBase { - feature_option: transform_options.feature_options, - mandatory_pointers: transform_options.mandatory_pointers, + options: transform_options, mandatory, non_mandatory, hmac_key, @@ -99,7 +83,8 @@ where None => { // createVerifyData, step 1, 3, 4 // canonicalize input document into N-Quads. - Ok(Transformed::Derived(todo!())) + // Ok(Transformed::Derived(todo!())) + todo!() } } } @@ -108,603 +93,31 @@ where #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Mandatory; -fn hmac_id_label_map_function(hmac: &mut Hmac) -> impl '_ + FnMut(&BlankId) -> BlankIdBuf { - use hmac::Mac; - let mut map: HashMap<&BlankId, BlankIdBuf> = HashMap::new(); - move |blank_id| { - hmac.update(blank_id.as_bytes()); - let digest = hmac.finalize_reset().into_bytes(); - let b64_url_digest = format!( - "u{}", - base64::encode_config(&digest, base64::URL_SAFE_NO_PAD) - ); - todo!() - } -} - -/// Canonicalize and group. +/// Creates a label map factory function that uses an HMAC to shuffle canonical +/// blank node identifiers. /// -/// See: -async fn canonicalize_and_group( - loader: &impl ssi_json_ld::Loader, - label_factory: impl FnMut() -> BlankIdBuf, - group_definitions: HashMap>, - document: &T, -) -> Result, TransformationError> -where - T: JsonLdObject + Expandable, - T::Expanded, ()>: Into, - N: Eq + Hash, -{ - let mut skolemize = Skolemize { - urn_scheme: String::new(), - random_string: String::new(), - count: 0, - }; - - let (skolemized_expanded_document, skolemized_compact_document) = - skolemize.compact_document(loader, document).await?; - - let deskolemized_quads = - expanded_to_deskolemized_nquads(&skolemize.urn_scheme, &skolemized_expanded_document)?; - - let (quads, label_map) = - label_replacement_canonicalize_nquads(label_factory, &deskolemized_quads); - - let mut selection = HashMap::new(); - for (name, pointers) in group_definitions { - selection.insert( - name, - select_canonical_nquads( - loader, - &skolemize.urn_scheme, - pointers, - &label_map, - &skolemized_compact_document, - ) - .await?, - ); - } - - let mut groups = HashMap::new(); - - for (name, selection_result) in selection { - let mut matching = HashMap::new(); - let mut non_matching = HashMap::new(); - - let selected_quads: HashSet<_> = selection_result.quads.into_iter().collect(); - let selected_deskolemized_quads = selection_result.deskolemized_quads; - - for (i, nq) in quads.iter().enumerate() { - if selected_quads.contains(nq) { - matching.insert(i, nq.clone()); - } else { - non_matching.insert(i, nq.clone()); - } - } - - groups.insert( - name, - Group { - matching, - non_matching, - deskolemized_quads: selected_deskolemized_quads, - }, - ); - } - - Ok(CanonicalizedAndGrouped { - groups, - skolemized_expanded_document, - skolemized_compact_document, - deskolemized_quads, - label_map, - quads, - }) -} - -struct CanonicalizedAndGrouped { - groups: HashMap, - skolemized_expanded_document: ssi_json_ld::ExpandedDocument, - skolemized_compact_document: ssi_json_ld::syntax::Object, - deskolemized_quads: Vec, - label_map: HashMap, - quads: Vec, -} - -async fn select_canonical_nquads( - loader: &impl ssi_json_ld::Loader, - urn_scheme: &str, - pointers: Vec, - label_map: &HashMap, - skolemized_compact_document: &ssi_json_ld::syntax::Object, -) -> Result { - let selection_document = select_json_ld(pointers, skolemized_compact_document) - .map_err(TransformationError::internal)?; - - let deskolemized_quads = match selection_document.clone() { - Some(selection_document) => { - compact_to_deskolemized_nquads(loader, urn_scheme, selection_document).await? - } - None => Vec::new(), - }; - - let quads = relabel_blank_nodes(label_map, &deskolemized_quads); - - Ok(CanonicalNquadsSelection { - selection_document, - deskolemized_quads, - quads, - }) -} - -struct CanonicalNquadsSelection { - selection_document: Option, - deskolemized_quads: Vec, - quads: Vec, -} - -fn relabel_blank_nodes( - label_map: &HashMap, - quads: &[LexicalQuad], -) -> Vec { - todo!() -} - -/// See: -fn select_json_ld( - pointers: Vec, - document: &ssi_json_ld::syntax::Object, -) -> Result, DanglingJsonPointer> { - if pointers.is_empty() { - return Ok(None); - } - - let mut selection_document = create_initial_selection_object(document); - if let Some(context) = document.get("@context").next() { - selection_document.insert("@context".into(), SparseValue::from_dense(context)); - } - - for pointer in pointers { - document.select(&pointer, &mut selection_document)?; - } - - Ok(Some(selection_document.into_dense())) -} - -fn create_initial_selection(source: &Value) -> SparseValue { - match source { - Value::Null => SparseValue::Null, - Value::Boolean(b) => SparseValue::Boolean(*b), - Value::Number(n) => SparseValue::Number(n.clone()), - Value::String(s) => SparseValue::String(s.clone()), - Value::Array(_) => SparseValue::Array(SparseArray::default()), - Value::Object(object) => SparseValue::Object(create_initial_selection_object(object)), - } -} - -fn create_initial_selection_object(source: &ssi_json_ld::syntax::Object) -> SparseObject { - let mut selection = SparseObject::new(); - - if let Some(Value::String(id)) = source.get("id").next() { - if BlankId::new(id).is_err() { - selection.insert("id".into(), SparseValue::String(id.to_owned())); - } - } - - if let Some(type_) = source.get("type").next() { - selection.insert("type".into(), SparseValue::from_dense(type_)); - } - - selection -} - -struct JsonPath; - -fn select_paths( - pointer: &JsonPointer, - mut value: &ssi_json_ld::syntax::Value, - selection_document: &mut ssi_json_ld::syntax::Object, -) -> Result<(), DanglingJsonPointer> { - for token in pointer { - value - .as_object() - .and_then(|o| o.get(token.to_str().as_ref()).next()) - .ok_or(DanglingJsonPointer); - - // ... - } - - todo!() -} - -#[derive(Debug)] -pub enum SparseValue { - Null, - Boolean(bool), - String(ssi_json_ld::syntax::String), - Number(ssi_json_ld::syntax::NumberBuf), - Array(SparseArray), - Object(SparseObject), -} - -impl SparseValue { - pub fn from_dense(value: &Value) -> Self { - match value { - Value::Null => Self::Null, - Value::Boolean(b) => Self::Boolean(*b), - Value::String(s) => Self::String(s.clone()), - Value::Number(n) => Self::Number(n.clone()), - Value::Array(a) => Self::Array(SparseArray::from_dense(a)), - Value::Object(o) => Self::Object(SparseObject::from_dense(o)), - } - } - - pub fn into_dense(self) -> Value { - match self { - Self::Null => Value::Null, - Self::Boolean(b) => Value::Boolean(b), - Self::Number(n) => Value::Number(n), - Self::String(s) => Value::String(s), - Self::Array(a) => Value::Array(a.into_dense()), - Self::Object(o) => Value::Object(o.into_dense()), - } - } -} - -#[derive(Debug, Default)] -pub struct SparseArray(BTreeMap); - -impl SparseArray { - pub fn from_dense(value: &Vec) -> Self { - Self( - value - .iter() - .enumerate() - .map(|(i, item)| (i, SparseValue::from_dense(item))) - .collect(), - ) - } - - pub fn get_mut_or_insert_with( - &mut self, - i: usize, - f: impl FnOnce() -> SparseValue, - ) -> &mut SparseValue { - todo!() - } - - pub fn into_dense(self) -> Vec { - self.0.into_values().map(SparseValue::into_dense).collect() - } -} - -#[derive(Debug, Default)] -pub struct SparseObject(BTreeMap); - -impl SparseObject { - pub fn new() -> Self { - Self::default() - } - - pub fn from_dense(value: &ssi_json_ld::syntax::Object) -> Self { - Self( - value - .iter() - .map(|entry| { - ( - entry.key.as_str().to_owned(), - SparseValue::from_dense(&entry.value), - ) - }) - .collect(), - ) - } - - pub fn get_mut_or_insert_with( - &mut self, - key: &str, - f: impl FnOnce() -> SparseValue, - ) -> &mut SparseValue { - todo!() - } - - pub fn insert(&mut self, key: String, value: SparseValue) { - self.0.insert(key, value); - } - - pub fn into_dense(self) -> ssi_json_ld::syntax::Object { - self.0 - .into_iter() - .map(|(key, value)| (key.into(), value.into_dense())) - .collect() - } -} - -trait Select { - type Sparse; - - fn select( - &self, - pointer: &JsonPointer, - selection: &mut Self::Sparse, - ) -> Result<(), DanglingJsonPointer>; -} - -impl Select for Value { - type Sparse = SparseValue; - - fn select( - &self, - pointer: &JsonPointer, - selection: &mut Self::Sparse, - ) -> Result<(), DanglingJsonPointer> { - match (self, selection) { - (Self::Array(a), SparseValue::Array(b)) => a.select(pointer, b), - (Self::Object(a), SparseValue::Object(b)) => a.select(pointer, b), - _ => { - if pointer.is_empty() { - Ok(()) - } else { - Err(DanglingJsonPointer) - } - } - } - } -} - -impl Select for Vec { - type Sparse = SparseArray; - - fn select( - &self, - pointer: &JsonPointer, - selection: &mut Self::Sparse, - ) -> Result<(), DanglingJsonPointer> { - match pointer.split_first() { - Some((token, rest)) => { - let i = token.as_array_index().ok_or(DanglingJsonPointer)?; - let a_item = self.get(i).ok_or(DanglingJsonPointer)?; - let b_item = - selection.get_mut_or_insert_with(i, || create_initial_selection(a_item)); - a_item.select(rest, b_item) - } - None => { - *selection = SparseArray::from_dense(self); - Ok(()) - } - } - } -} - -impl Select for ssi_json_ld::syntax::Object { - type Sparse = SparseObject; - - fn select( - &self, - pointer: &JsonPointer, - selection: &mut Self::Sparse, - ) -> Result<(), DanglingJsonPointer> { - match pointer.split_first() { - Some((token, rest)) => { - let key = token.to_str(); - let a_item = self.get(key.as_ref()).next().ok_or(DanglingJsonPointer)?; - let b_item = - selection.get_mut_or_insert_with(&key, || create_initial_selection(a_item)); - a_item.select(rest, b_item) - } - None => { - *selection = SparseObject::from_dense(self); - Ok(()) - } - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("dangling JSON pointer")] -pub struct DanglingJsonPointer; - -fn label_replacement_canonicalize_nquads( - mut label_factory: impl FnMut() -> BlankIdBuf, - quads: &[LexicalQuad], -) -> (Vec, HashMap) { - let mut label_map: HashMap = HashMap::new(); - let mut relabel = |b: &mut BlankIdBuf| match label_map.get(b.as_blank_id_ref()).cloned() { - Some(c) => *b = c, - None => { - let c = label_factory(); - label_map.insert(b.clone(), c); - } - }; - - let mut relabel_id = |id: &mut Id| { - if let Id::Blank(b) = id { - relabel(b) - } - }; - - let mut canonical_quads: Vec = - ssi_rdf::urdna2015::normalize(quads.iter().map(LexicalQuad::as_lexical_quad_ref)).collect(); - for quad in &mut canonical_quads { - relabel_id(&mut quad.0); - if let Term::Id(id) = &mut quad.2 { - relabel_id(id) - } - if let Some(g) = &mut quad.3 { - relabel_id(g) - } - } - - (canonical_quads, label_map) -} - -fn expanded_to_deskolemized_nquads( - urn_scheme: &str, - document: &ssi_json_ld::ExpandedDocument, -) -> Result, IntoQuadsError> { - let mut quads = linked_data::to_lexical_quads(generator::Blank::new(), &document)?; - - deskolemize_nquads(urn_scheme, &mut quads); - - Ok(quads) -} - -async fn compact_to_deskolemized_nquads( - loader: &impl ssi_json_ld::Loader, - urn_scheme: &str, - document: ssi_json_ld::syntax::Object, -) -> Result, TransformationError> { - let mut quads: Vec = RemoteDocument::new(None, None, Value::Object(document)) - .to_rdf(&mut generator::Blank::new(), loader) - .await - .map_err(TransformationError::internal)? - .cloned_quads() - .map(|quad| quad.map_predicate(|p| p.into_iri().unwrap())) - .collect(); - - deskolemize_nquads(urn_scheme, &mut quads); - - Ok(quads) -} - -fn deskolemize_nquads(urn_scheme: &str, quads: &mut [LexicalQuad]) { - for quad in quads { - deskolemize_id(urn_scheme, &mut quad.0); - deskolemize_term(urn_scheme, &mut quad.2); - - if let Some(g) = quad.graph_mut() { - deskolemize_id(urn_scheme, g); - } - } -} - -fn deskolemize_id(urn_scheme: &str, id: &mut Id) { - if let Id::Iri(iri) = id { - if iri.scheme().as_str() == "urn" { - let path = iri.path(); - if let Some((prefix, suffix)) = path.split_once(':') { - if prefix == urn_scheme { - let blank_id = BlankIdBuf::from_suffix(suffix).unwrap(); - *id = Id::Blank(blank_id) - } - } +/// See: +pub(crate) fn create_shuffled_id_label_map_function( + hmac: &mut Hmac, +) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { + |canonical_map| { + let mut map = create_hmac_id_label_map_function(hmac)(canonical_map); + + let mut hmac_ids: Vec<_> = map.values().cloned().collect(); + hmac_ids.sort(); + + let mut bnode_keys: Vec<_> = map.keys().cloned().collect(); + bnode_keys.sort(); + + for key in bnode_keys { + let i = hmac_ids.binary_search(&map[&key]).unwrap(); + map.insert(key, BlankIdBuf::new(format!("_:b{}", i)).unwrap()); } - } -} -fn deskolemize_term(urn_scheme: &str, term: &mut Term) { - if let Term::Id(id) = term { - deskolemize_id(urn_scheme, id) + map } } -struct Skolemize { - urn_scheme: String, - random_string: String, - count: u32, -} - -impl rdf_types::Generator for Skolemize { - fn next(&mut self, vocabulary: &mut ()) -> Id { - Id::Iri(self.fresh_blank_id()) - } -} - -impl Skolemize { - pub fn fresh_blank_id(&mut self) -> IriBuf { - let id = IriBuf::new(format!( - "urn:{}:{}_{}", - self.urn_scheme, self.random_string, self.count - )) - .unwrap(); - self.count += 1; - id - } - - pub fn blank_id(&mut self, blank_id: &BlankId) -> IriBuf { - IriBuf::new(format!("urn:{}:{}", self.urn_scheme, blank_id.suffix())).unwrap() - } - - /// See: - pub async fn compact_document( - &mut self, - loader: &impl ssi_json_ld::Loader, - document: &T, - ) -> Result<(ssi_json_ld::ExpandedDocument, ssi_json_ld::syntax::Object), TransformationError> - where - T: JsonLdObject + Expandable, - T::Expanded, ()>: Into - { - let expanded = document - .expand(loader) - .await - .map_err(|e| TransformationError::JsonLdExpansion(e.to_string()))?; - - let skolemized_expanded_document = self.expanded_document(expanded.into()); - - let processed_context: ProcessedOwned = match document.json_ld_context() - { - Some(ld_context) => { - let processed = ld_context - .process(&mut (), loader, None) - .await - .map_err(TransformationError::internal)? - .processed; - - ProcessedOwned::new(ld_context.into_owned(), processed) - } - None => ProcessedOwned::new( - ssi_json_ld::syntax::Context::default(), - ssi_json_ld::Context::default(), - ), - }; - - let skolemized_compact_document = skolemized_expanded_document - .compact(processed_context.as_ref(), loader) - .await - .map_err(TransformationError::internal)? - .into_object() - .ok_or_else(|| TransformationError::internal("expected JSON object"))?; - - Ok((skolemized_expanded_document, skolemized_compact_document)) - } - - /// See: - pub fn expanded_document( - &mut self, - expanded: ssi_json_ld::ExpandedDocument, - ) -> ssi_json_ld::ExpandedDocument { - let mut result = expanded.map_ids( - |i| i, - |id| match id { - ssi_json_ld::Id::Valid(id) => match id { - Id::Blank(blank_id) => { - ssi_json_ld::Id::Valid(Id::Iri(self.blank_id(&blank_id))) - } - Id::Iri(iri) => { - ssi_json_ld::Id::Valid(Id::Iri(iri)) - } - }, - ssi_json_ld::Id::Invalid(s) => ssi_json_ld::Id::Invalid(s), - }, - ); - - result.identify_all(self); - result - } -} - -struct Group { - pub matching: HashMap, - pub non_matching: HashMap, - pub deskolemized_quads: Vec, -} - -struct GroupDefinitions { - mandatory: Vec, -} - pub enum Transformed { Base(TransformedBase), Derived(TransformedDerived), @@ -714,8 +127,7 @@ pub enum Transformed { /// /// See: pub struct TransformedBase { - pub feature_option: FeatureOption, - pub mandatory_pointers: Vec, + pub options: Bbs2023InputOptions, pub mandatory: Vec, pub non_mandatory: Vec, pub hmac_key: HmacKey, @@ -726,5 +138,3 @@ pub struct TransformedDerived { pub proof_hash: String, pub nquads: Vec, } - -type HmacKey = [u8; 32]; diff --git a/crates/eip712/Cargo.toml b/crates/eip712/Cargo.toml index f83c903c5..2fae3882a 100644 --- a/crates/eip712/Cargo.toml +++ b/crates/eip712/Cargo.toml @@ -16,7 +16,7 @@ thiserror.workspace = true keccak-hash = "0.7" hex.workspace = true rdf-types.workspace = true -indexmap = { version = "2.0.0", features = ["serde"] } +indexmap = { workspace = true, features = ["serde"] } json-syntax = { workspace = true, features = ["serde"] } linked-data.workspace = true iref.workspace = true diff --git a/crates/json-ld/Cargo.toml b/crates/json-ld/Cargo.toml index cd2af22c9..da65fa8e2 100644 --- a/crates/json-ld/Cargo.toml +++ b/crates/json-ld/Cargo.toml @@ -17,7 +17,7 @@ static-iref.workspace = true rdf-types.workspace = true xsd-types.workspace = true locspan.workspace = true -json-syntax.workspace = true +json-syntax = { workspace = true, features = ["serde"] } linked-data.workspace = true futures = "0.3" lazy_static = "1.4" diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index d20762031..28e1a1423 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -40,6 +40,7 @@ pub enum JsonLdError { } #[repr(transparent)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CompactJsonLd(pub json_syntax::Value); impl CompactJsonLd { @@ -132,6 +133,14 @@ pub trait JsonLdObject { } } +impl JsonLdObject for CompactJsonLd { + fn json_ld_context(&self) -> Option> { + json_syntax::from_value(self.0.as_object()?.get("@context").next()?.clone()) + .map(Cow::Owned) + .ok() + } +} + pub trait JsonLdNodeObject: JsonLdObject { fn json_ld_type(&self) -> JsonLdTypes { JsonLdTypes::default() diff --git a/crates/rdf/Cargo.toml b/crates/rdf/Cargo.toml index 9523e7ffc..3dbc20697 100644 --- a/crates/rdf/Cargo.toml +++ b/crates/rdf/Cargo.toml @@ -14,6 +14,7 @@ rdf-types.workspace = true ssi-crypto.workspace = true linked-data.workspace = true serde.workspace = true +indexmap.workspace = true combination = "0.1" [dev-dependencies] diff --git a/crates/rdf/src/lib.rs b/crates/rdf/src/lib.rs index d7c6b03ed..f07efbe04 100644 --- a/crates/rdf/src/lib.rs +++ b/crates/rdf/src/lib.rs @@ -8,7 +8,7 @@ pub mod urdna2015; pub use expand::*; use rdf_types::{ dataset::IndexedBTreeDataset, - interpretation::ReverseTermInterpretation, + interpretation::{ReverseTermInterpretation, WithGenerator}, vocabulary::{ByRef, ExtractFromVocabulary, Predicate}, }; @@ -19,6 +19,8 @@ pub use rdf_types::{ pub use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; +pub type LexicalInterpretation = WithGenerator; + /// Interpreted RDF dataset with an entry point. pub struct DatasetWithEntryPoint<'a, V, I: Interpretation> { pub vocabulary: &'a V, diff --git a/crates/rdf/src/urdna2015.rs b/crates/rdf/src/urdna2015.rs index 01aed423a..80a13d482 100644 --- a/crates/rdf/src/urdna2015.rs +++ b/crates/rdf/src/urdna2015.rs @@ -6,6 +6,7 @@ use std::fmt; #[derive(Debug)] pub struct MissingChosenIssuer; +use indexmap::IndexMap; use rdf_types::BlankId; use rdf_types::BlankIdBuf; use rdf_types::Id; @@ -108,7 +109,7 @@ pub struct NormalizationState<'a> { pub struct IdentifierIssuer { pub identifier_prefix: String, pub identifier_counter: u64, - pub issued_identifiers_list: Vec<(BlankIdBuf, BlankIdBuf)>, + pub issued_identifiers: IndexMap, } impl IdentifierIssuer { @@ -116,15 +117,13 @@ impl IdentifierIssuer { Self { identifier_prefix: prefix, identifier_counter: 0, - issued_identifiers_list: Vec::new(), + issued_identifiers: IndexMap::new(), } } pub fn find_issued_identifier(&self, existing_identifier: &BlankId) -> Option<&BlankId> { - // TODO(optimize): index issued_identifiers_list by existing_identifier - self.issued_identifiers_list - .iter() - .find(|(_, existing_id)| existing_id == existing_identifier) - .map(|(issued_identifier, _)| issued_identifier.as_ref()) + self.issued_identifiers + .get(existing_identifier) + .map(BlankIdBuf::as_blank_id_ref) } } @@ -290,7 +289,7 @@ where for result in hash_path_list { // 6.3.1 let identifier_issuer = result.issuer; - for (_, existing_identifier) in identifier_issuer.issued_identifiers_list { + for (existing_identifier, _) in identifier_issuer.issued_identifiers { issue_identifier( &mut normalization_state.canonical_issuer, &existing_identifier, @@ -311,6 +310,8 @@ pub struct NormalizedQuads<'a, Q> { normalization_state: NormalizationState<'a>, } +pub type NormalizingSubstitution = IndexMap; + impl<'a, Q: Iterator>> NormalizedQuads<'a, Q> { pub fn into_nquads_lines(self) -> Vec { IntoNQuads::into_nquads_lines(self) @@ -319,6 +320,11 @@ impl<'a, Q: Iterator>> NormalizedQuads<'a, Q> { pub fn into_nquads(self) -> String { IntoNQuads::into_nquads(self) } + + pub fn into_substitution(mut self) -> NormalizingSubstitution { + (&mut self).last(); // make sure all the quads have been normalized. + self.normalization_state.canonical_issuer.issued_identifiers + } } impl<'a, Q: Iterator>> Iterator for NormalizedQuads<'a, Q> { @@ -360,8 +366,8 @@ pub fn issue_identifier( .unwrap(); // 3 identifier_issuer - .issued_identifiers_list - .push((issued_identifier.clone(), existing_identifier.to_owned())); + .issued_identifiers + .insert(existing_identifier.to_owned(), issued_identifier.clone()); // 4 identifier_issuer.identifier_counter += 1; // 5 diff --git a/crates/verification-methods/core/src/lib.rs b/crates/verification-methods/core/src/lib.rs index a4d004f0b..cde360c94 100644 --- a/crates/verification-methods/core/src/lib.rs +++ b/crates/verification-methods/core/src/lib.rs @@ -168,13 +168,6 @@ impl<'t, T: VerificationMethodResolver> VerificationMethodResolver for &'t T { } pub trait SigningMethod: VerificationMethod { - // fn sign( - // &self, - // secret: &S, - // algorithm: A, - // bytes: &[u8], - // ) -> Result, MessageSignatureError>; - fn sign_bytes( &self, secret: &S, @@ -183,6 +176,15 @@ pub trait SigningMethod: VerificationMethod { ) -> Result, MessageSignatureError>; } +pub trait MultiSigningMethod { + fn sign_bytes_multi( + &self, + secret: &S, + algorithm: A, + messages: &[Vec], + ) -> Result, MessageSignatureError>; +} + pub struct MethodWithSecret { pub method: M, pub secret: Arc, diff --git a/crates/verification-methods/core/src/signature/signer/mod.rs b/crates/verification-methods/core/src/signature/signer/mod.rs index 5093631ac..73b9f7084 100644 --- a/crates/verification-methods/core/src/signature/signer/mod.rs +++ b/crates/verification-methods/core/src/signature/signer/mod.rs @@ -32,6 +32,16 @@ impl<'s, M: VerificationMethod, S: Signer> Signer for &'s S { } } +pub trait MultiSigner { + type MessageSigner: MultiMessageSigner; + + #[allow(async_fn_in_trait)] + async fn for_verification_method( + &self, + method: &M, + ) -> Result; +} + #[derive(Debug, thiserror::Error)] pub enum MessageSignatureError { #[error("0")] @@ -102,6 +112,15 @@ impl> MessageSigner for JWK { } } +pub trait MultiMessageSigner { + #[allow(async_fn_in_trait)] + async fn sign_multi( + self, + algorithm: A, + messages: &[Vec], + ) -> Result, MessageSignatureError>; +} + pub struct MessageSignerAdapter { // Underlying signer. signer: S, From 0ced82f4f158317458aab2566cd6de9e53fcce7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Mon, 10 Jun 2024 12:18:05 +0200 Subject: [PATCH 04/34] Tested base transformation. --- .../sd-primitives/src/canonicalize.rs | 4 +- .../data-integrity/sd-primitives/src/group.rs | 70 ++++- .../crates/data-integrity/suites/Cargo.toml | 1 + .../suites/src/suites/w3c/bbs_2023/mod.rs | 2 + .../src/suites/w3c/bbs_2023/signature.rs | 75 ++++++ .../suites/src/suites/w3c/bbs_2023/tests.rs | 76 +++--- .../src/suites/w3c/bbs_2023/transformation.rs | 250 +++++++++++++++++- 7 files changed, 430 insertions(+), 48 deletions(-) diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs index c24b7c486..6e293827b 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs @@ -35,10 +35,12 @@ pub fn label_replacement_canonicalize_nquads( let label_map = label_map_factory(&bnode_identifier_map); - let canonical_quads = quads + let mut canonical_quads: Vec<_> = quads .iter() .map(|quad| relabel_quad(&label_map, quad.as_lexical_quad_ref())) .collect(); + canonical_quads.sort(); + canonical_quads.dedup(); (canonical_quads, label_map) } diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs index 3ffcdc653..a04e2163b 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, hash::Hash, }; @@ -70,14 +70,15 @@ where let mut groups = HashMap::new(); for (name, selection_result) in selection { - let mut matching = HashMap::new(); - let mut non_matching = HashMap::new(); + let mut matching = BTreeMap::new(); + let mut non_matching = BTreeMap::new(); let selected_quads: HashSet<_> = selection_result.quads.into_iter().collect(); // let selected_deskolemized_quads = selection_result.deskolemized_quads; for (i, nq) in quads.iter().enumerate() { if selected_quads.contains(nq) { + eprintln!("matching {i} => {nq} ."); matching.insert(i, nq.clone()); } else { non_matching.insert(i, nq.clone()); @@ -114,8 +115,8 @@ pub struct CanonicalizedAndGrouped { } pub struct Group { - pub matching: HashMap, - pub non_matching: HashMap, + pub matching: BTreeMap, + pub non_matching: BTreeMap, // pub deskolemized_quads: Vec, } @@ -249,6 +250,63 @@ mod tests { ]; assert_eq!(result.quads.into_nquads_lines(), EXPECTED_NQUADS); - // let group = result.groups.get(&Mandatory).unwrap(); + + let expected_mandatory = [ + (0, "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY .\n".to_string()), + (1, "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY _:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 .\n".to_string()), + (2, "_:u2IE-HtO6PyHQsGnuqhO1mX6V7RkRREhF0d0sWZlxNOY .\n".to_string()), + (8, "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"2022\"^^ .\n".to_string()), + (9, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 .\n".to_string()), + (11, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 \"Earth101\" .\n".to_string()), + (14, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk .\n".to_string()), + (15, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc .\n".to_string()), + (22, "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"Lahaina\" .\n".to_string()), + (23, "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"6.1E0\"^^ .\n".to_string()), + (24, "_:uk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDk \"2023\"^^ .\n".to_string()), + (25, "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"Lahaina\" .\n".to_string()), + (26, "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"7\"^^ .\n".to_string()), + (27, "_:ukR2991GJuy_Tkjem_x7pLVpS4C4GkZAcuGtiPhBfSSc \"2020\"^^ .\n".to_string()) + ]; + + let mut mandatory: Vec<_> = result + .groups + .get(&Mandatory) + .unwrap() + .matching + .iter() + .map(|(i, quad)| (*i, format!("{quad} .\n"))) + .collect(); + mandatory.sort_by_key(|(i, _)| *i); + + assert_eq!(mandatory, expected_mandatory); + + let expected_non_mandatory = [ + (3, "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"Lahaina\" .\n".to_string()), + (4, "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"7.8E0\"^^ .\n".to_string()), + (5, "_:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg \"2023\"^^ .\n".to_string()), + (6, "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"CompFoil170\" .\n".to_string()), + (7, "_:u4YIOZn1MHES1Z4Ij2hWZG3R4dEYBqg5fHTyDEvYhC38 \"Wailea\" .\n".to_string()), + (10, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw .\n".to_string()), + (12, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:u3Lv2QpFgo-YAegc1cQQKWJFW2sEjQF6FfuZ0VEoMKHg .\n".to_string()), + (13, "_:uQ-qOZUDlozRsGk46ux9gp9fjT28Fy3g3nctmMoqi_U0 _:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ .\n".to_string()), + (16, "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"Kanaha Custom\" .\n".to_string()), + (17, "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"Wailea\" .\n".to_string()), + (18, "_:uVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKw \"2019\"^^ .\n".to_string()), + (19, "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"Kihei\" .\n".to_string()), + (20, "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"5.5E0\"^^ .\n".to_string()), + (21, "_:ufUWJRHQ9j1jmUKHLL8k6m0CZ8g4v73gOpaM5kL3ZACQ \"2023\"^^ .\n".to_string()) + ]; + + let mut non_mandatory: Vec<_> = result + .groups + .get(&Mandatory) + .unwrap() + .non_matching + .iter() + .map(|(i, quad)| (*i, format!("{quad} .\n"))) + .collect(); + non_mandatory.sort_by_key(|(i, _)| *i); + + assert_eq!(non_mandatory, expected_non_mandatory); } } diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index c4942281d..2a0fc1d38 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -158,6 +158,7 @@ static-iref.workspace = true rand = "0.7" hashbrown = "0.13.0" iref = { workspace = true, features = ["hashbrown"] } +ssi-vc.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 6b7589c26..9ce57d7c1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -53,6 +53,8 @@ pub struct Bbs2023InputOptions { pub feature_option: FeatureOption, pub commitment_with_proof: Option>, + + pub hmac_key: Option, } #[derive(Debug, Default, Clone, Copy)] diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs index 5c3539468..ee031cc17 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs @@ -114,3 +114,78 @@ where } } } + +#[cfg(test)] +mod tests { + use lazy_static::lazy_static; + use ssi_data_integrity_core::suite::standard::SignatureAlgorithm; + use ssi_di_sd_primitives::JsonPointerBuf; + + use crate::bbs_2023::{ + hashing::BaseHashData, transformation::TransformedBase, Bbs2023InputOptions, FeatureOption, + HashData, HmacKey, + }; + + use super::Bbs2023SignatureAlgorithm; + + lazy_static! { + pub static ref MANDATORY_POINTERS: Vec = vec![ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; + } + + const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; + const SECRET_KEY_HEX: &str = "66d36e118832af4c5e28b2dfe1b9577857e57b042a33e06bdea37b811ed09ee0"; + const HMAC_KEY_STRING: &str = + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + + fn test_base_proof_serialization() { + let mut proof_hash = [0; 32]; + hex::decode_to_slice( + b"3a5bbf25d34d90b18c35cd2357be6a6f42301e94fc9e52f77e93b773c5614bdf", + &mut proof_hash, + ) + .unwrap(); + + let mut mandatory_hash = [0; 32]; + hex::decode_to_slice( + b"555de05f898817e31301bac187d0c3ff2b03e2cbdb4adb4d568c17de961f9a18", + &mut mandatory_hash, + ) + .unwrap(); + + let mut hmac_key = HmacKey::default(); + hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); + + let mandatory = Vec::new(); + let non_mandatory = Vec::new(); + + Bbs2023SignatureAlgorithm::sign( + verification_method, + signer, + &HashData::Base(BaseHashData { + transformed_document: TransformedBase { + options: Bbs2023InputOptions { + mandatory_pointers: MANDATORY_POINTERS.clone(), + feature_option: FeatureOption::Baseline, + commitment_with_proof: None, + hmac_key: None, + }, + mandatory, + non_mandatory, + hmac_key, + canonical_configuration, + }, + proof_hash, + mandatory_hash, + }), + proof_configuration, + ); + + todo!() + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs index cf8c97195..5a9b216f1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs @@ -88,44 +88,44 @@ async fn canonicalize_document(document: json_ld::syntax::Value) -> Vec \"CompFoil170\" .\n", - "_:c14n0 \"Wailea\" .\n", - "_:c14n0 \"2022\"^^ .\n", - "_:c14n1 \"Lahaina\" .\n", - "_:c14n1 \"7.8E0\"^^ .\n", - "_:c14n1 \"2023\"^^ .\n", - "_:c14n2 \"Kanaha Custom\" .\n", - "_:c14n2 \"Wailea\" .\n", - "_:c14n2 \"2019\"^^ .\n", - "_:c14n3 \"Lahaina\" .\n", - "_:c14n3 \"7\"^^ .\n", - "_:c14n3 \"2020\"^^ .\n", - "_:c14n4 \"Kihei\" .\n", - "_:c14n4 \"5.5E0\"^^ .\n", - "_:c14n4 \"2023\"^^ .\n", - "_:c14n5 _:c14n0 .\n", - "_:c14n5 _:c14n2 .\n", - "_:c14n5 \"Earth101\" .\n", - "_:c14n5 _:c14n1 .\n", - "_:c14n5 _:c14n3 .\n", - "_:c14n5 _:c14n4 .\n", - "_:c14n5 _:c14n6 .\n", - "_:c14n6 \"Lahaina\" .\n", - "_:c14n6 \"6.1E0\"^^ .\n", - "_:c14n6 \"2023\"^^ .\n", - "_:c14n7 .\n", - "_:c14n7 _:c14n5 .\n", - "_:c14n7 .\n" - ]; - - let canonical_quads = canonicalize_document(CREDENTIAL.clone()) - .await - .into_nquads_lines(); - assert_eq!(canonical_quads, EXPECTED); -} +// #[async_std::test] +// async fn bbs_canonicalization() { +// const EXPECTED: [&str; 28] = [ +// "_:c14n0 \"CompFoil170\" .\n", +// "_:c14n0 \"Wailea\" .\n", +// "_:c14n0 \"2022\"^^ .\n", +// "_:c14n1 \"Lahaina\" .\n", +// "_:c14n1 \"7.8E0\"^^ .\n", +// "_:c14n1 \"2023\"^^ .\n", +// "_:c14n2 \"Kanaha Custom\" .\n", +// "_:c14n2 \"Wailea\" .\n", +// "_:c14n2 \"2019\"^^ .\n", +// "_:c14n3 \"Lahaina\" .\n", +// "_:c14n3 \"7\"^^ .\n", +// "_:c14n3 \"2020\"^^ .\n", +// "_:c14n4 \"Kihei\" .\n", +// "_:c14n4 \"5.5E0\"^^ .\n", +// "_:c14n4 \"2023\"^^ .\n", +// "_:c14n5 _:c14n0 .\n", +// "_:c14n5 _:c14n2 .\n", +// "_:c14n5 \"Earth101\" .\n", +// "_:c14n5 _:c14n1 .\n", +// "_:c14n5 _:c14n3 .\n", +// "_:c14n5 _:c14n4 .\n", +// "_:c14n5 _:c14n6 .\n", +// "_:c14n6 \"Lahaina\" .\n", +// "_:c14n6 \"6.1E0\"^^ .\n", +// "_:c14n6 \"2023\"^^ .\n", +// "_:c14n7 .\n", +// "_:c14n7 _:c14n5 .\n", +// "_:c14n7 .\n" +// ]; + +// let canonical_quads = canonicalize_document(CREDENTIAL.clone()) +// .await +// .into_nquads_lines(); +// assert_eq!(canonical_quads, EXPECTED); +// } // #[async_std::test] // async fn bbs_hmac_canonicalization() { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs index 10f3b7295..37006ff06 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs @@ -48,9 +48,16 @@ where Some(transform_options) => { // Base Proof Transformation algorithm. // See: - // Generate a random key - let mut hmac_key = HmacKey::default(); - getrandom(&mut hmac_key).map_err(TransformationError::internal)?; + let hmac_key = match transform_options.hmac_key { + Some(key) => key, + None => { + // Generate a random key + let mut key = HmacKey::default(); + getrandom(&mut key).map_err(TransformationError::internal)?; + key + } + }; + let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); let mut group_definitions = HashMap::new(); @@ -123,6 +130,15 @@ pub enum Transformed { Derived(TransformedDerived), } +impl Transformed { + pub fn into_base(self) -> Option { + match self { + Self::Base(b) => Some(b), + _ => None, + } + } +} + /// Result of the Base Proof Transformation algorithm. /// /// See: @@ -138,3 +154,231 @@ pub struct TransformedDerived { pub proof_hash: String, pub nquads: Vec, } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use hmac::{Hmac, Mac}; + use k256::sha2::Sha256; + use lazy_static::lazy_static; + use ssi_data_integrity_core::{ + suite::standard::TypedTransformationAlgorithm, ProofConfiguration, + }; + use ssi_di_sd_primitives::{group::canonicalize_and_group, JsonPointerBuf}; + use ssi_json_ld::JsonLdEnvironment; + use ssi_rdf::IntoNQuads; + use ssi_vc::v2::syntax::JsonCredential; + use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; + + use crate::{ + bbs_2023::{Bbs2023InputOptions, FeatureOption, HmacKey}, + Bbs2023, + }; + + use super::{create_shuffled_id_label_map_function, Bbs2023Transformation, Mandatory}; + + const HMAC_KEY_STRING: &str = + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + + lazy_static! { + pub static ref CREDENTIAL: JsonCredential = json_syntax::from_value(json_syntax::json!({ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7.0, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + } + })) + .unwrap(); + pub static ref MANDATORY_POINTERS: Vec = vec![ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; + } + + #[async_std::test] + async fn hmac_canonicalize_and_group() { + let mut context = JsonLdEnvironment::default(); + + let mut hmac_key = HmacKey::default(); + hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); + let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); + + let mut group_definitions = HashMap::new(); + group_definitions.insert(Mandatory, MANDATORY_POINTERS.clone()); + + let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); + + let canonical = canonicalize_and_group( + &mut context, + label_map_factory_function, + group_definitions, + &*CREDENTIAL, + ) + .await + .unwrap(); + + const EXPECTED_NQUADS: [&str; 28] = [ + "_:b0 .\n", + "_:b0 _:b3 .\n", + "_:b0 .\n", + "_:b1 \"Lahaina\" .\n", + "_:b1 \"7.8E0\"^^ .\n", + "_:b1 \"2023\"^^ .\n", + "_:b2 \"CompFoil170\" .\n", + "_:b2 \"Wailea\" .\n", + "_:b2 \"2022\"^^ .\n", + "_:b3 _:b2 .\n", + "_:b3 _:b4 .\n", + "_:b3 \"Earth101\" .\n", + "_:b3 _:b1 .\n", + "_:b3 _:b5 .\n", + "_:b3 _:b6 .\n", + "_:b3 _:b7 .\n", + "_:b4 \"Kanaha Custom\" .\n", + "_:b4 \"Wailea\" .\n", + "_:b4 \"2019\"^^ .\n", + "_:b5 \"Kihei\" .\n", + "_:b5 \"5.5E0\"^^ .\n", + "_:b5 \"2023\"^^ .\n", + "_:b6 \"Lahaina\" .\n", + "_:b6 \"6.1E0\"^^ .\n", + "_:b6 \"2023\"^^ .\n", + "_:b7 \"Lahaina\" .\n", + "_:b7 \"7\"^^ .\n", + "_:b7 \"2020\"^^ .\n" + ]; + + for quad in &canonical.quads { + eprintln!("{quad} ."); + } + + assert_eq!(canonical.quads.into_nquads_lines(), EXPECTED_NQUADS) + } + + #[async_std::test] + async fn transform_test() { + let mut context = JsonLdEnvironment::default(); + + let proof_configuration = ProofConfiguration::new( + Bbs2023, + xsd_types::DateTime::now_ms(), + ReferenceOrOwned::Reference("did:method:test".parse().unwrap()), + ProofPurpose::Assertion, + (), + ); + + let mut hmac_key = HmacKey::default(); + hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); + + let transformed = Bbs2023Transformation::transform( + &mut context, + &*CREDENTIAL, + proof_configuration.borrowed(), + Some(Bbs2023InputOptions { + mandatory_pointers: MANDATORY_POINTERS.clone(), + feature_option: FeatureOption::Baseline, + commitment_with_proof: None, + hmac_key: Some(hmac_key), + }), + ) + .await + .unwrap() + .into_base() + .unwrap(); + + let expected_mandatory = [ + (0, "_:b0 .\n".to_string()), + (1, "_:b0 _:b3 .\n".to_string()), + (2, "_:b0 .\n".to_string()), + (8, "_:b2 \"2022\"^^ .\n".to_string()), + (9, "_:b3 _:b2 .\n".to_string()), + (11, "_:b3 \"Earth101\" .\n".to_string()), + (14, "_:b3 _:b6 .\n".to_string()), + (15, "_:b3 _:b7 .\n".to_string()), + (22, "_:b6 \"Lahaina\" .\n".to_string()), + (23, "_:b6 \"6.1E0\"^^ .\n".to_string()), + (24, "_:b6 \"2023\"^^ .\n".to_string()), + (25, "_:b7 \"Lahaina\" .\n".to_string()), + (26, "_:b7 \"7\"^^ .\n".to_string()), + (27, "_:b7 \"2020\"^^ .\n".to_string()) + ]; + + let expected_non_mandatory = [ + (3, "_:b1 \"Lahaina\" .\n".to_string()), + (4, "_:b1 \"7.8E0\"^^ .\n".to_string()), + (5, "_:b1 \"2023\"^^ .\n".to_string()), + (6, "_:b2 \"CompFoil170\" .\n".to_string()), + (7, "_:b2 \"Wailea\" .\n".to_string()), + (10, "_:b3 _:b4 .\n".to_string()), + (12, "_:b3 _:b1 .\n".to_string()), + (13, "_:b3 _:b5 .\n".to_string()), + (16, "_:b4 \"Kanaha Custom\" .\n".to_string()), + (17, "_:b4 \"Wailea\" .\n".to_string()), + (18, "_:b4 \"2019\"^^ .\n".to_string()), + (19, "_:b5 \"Kihei\" .\n".to_string()), + (20, "_:b5 \"5.5E0\"^^ .\n".to_string()), + (21, "_:b5 \"2023\"^^ .\n".to_string()) + ]; + + assert_eq!(transformed.mandatory.len(), expected_mandatory.len()); + for (a, (_, b)) in transformed.mandatory.iter().zip(expected_mandatory) { + let a = format!("{a} .\n"); + assert_eq!(a, b) + } + + assert_eq!( + transformed.non_mandatory.len(), + expected_non_mandatory.len() + ); + for (a, (_, b)) in transformed.non_mandatory.iter().zip(expected_non_mandatory) { + let a = format!("{a} .\n"); + assert_eq!(a, b) + } + } +} From 92beba1020b2cbedc3a53433758491b99212c9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Mon, 10 Jun 2024 18:12:41 +0200 Subject: [PATCH 05/34] Proof deserialization. --- Cargo.toml | 1 + crates/bbs/Cargo.toml | 2 +- crates/bbs/src/lib.rs | 13 +- .../sd-primitives/src/json_pointer.rs | 13 + .../crates/data-integrity/suites/Cargo.toml | 5 + .../suites/src/suites/w3c/bbs_2023/mod.rs | 206 ++++++++++++- .../src/suites/w3c/bbs_2023/signature.rs | 135 +++++++-- .../suites/src/suites/w3c/bbs_2023/tests.rs | 191 ------------ .../base.rs} | 184 ++++-------- .../w3c/bbs_2023/transformation/derived.rs | 18 ++ .../suites/w3c/bbs_2023/transformation/mod.rs | 110 +++++++ crates/claims/crates/jws/src/lib.rs | 18 +- crates/dids/methods/ethr/src/lib.rs | 2 +- crates/dids/methods/ion/src/ion.rs | 6 +- crates/dids/methods/ion/src/sidetree/mod.rs | 14 +- crates/dids/methods/key/src/lib.rs | 14 +- crates/dids/methods/pkh/src/lib.rs | 2 +- crates/dids/methods/tz/tests/did.rs | 4 +- crates/jwk/src/lib.rs | 130 ++++---- crates/multicodec/Cargo.toml | 15 + crates/multicodec/src/codec/bls12_381.rs | 17 ++ crates/multicodec/src/codec/ed25519.rs | 16 + crates/multicodec/src/codec/k256.rs | 15 + crates/multicodec/src/codec/mod.rs | 46 +++ crates/multicodec/src/codec/p256.rs | 15 + crates/multicodec/src/codec/p384.rs | 15 + crates/multicodec/src/lib.rs | 34 ++- crates/verification-methods/Cargo.toml | 5 +- crates/verification-methods/core/src/lib.rs | 15 +- .../core/src/signature/signer/mod.rs | 13 +- crates/verification-methods/src/methods.rs | 2 +- .../w3c/ed25519_verification_key_2020.rs | 2 +- .../src/methods/w3c/multikey.rs | 281 ++++++++++-------- 33 files changed, 977 insertions(+), 582 deletions(-) delete mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs rename crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/{transformation.rs => transformation/base.rs} (74%) create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs create mode 100644 crates/multicodec/src/codec/bls12_381.rs create mode 100644 crates/multicodec/src/codec/ed25519.rs create mode 100644 crates/multicodec/src/codec/k256.rs create mode 100644 crates/multicodec/src/codec/mod.rs create mode 100644 crates/multicodec/src/codec/p256.rs create mode 100644 crates/multicodec/src/codec/p384.rs diff --git a/Cargo.toml b/Cargo.toml index 794232a0d..b003edf76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ blake2 = "0.10" sha2 = "0.10.0" sha3 = "0.10.8" rsa = "0.6" +zkryptium = "0.2.2" # BBS # other common dependencies log = "0.4.21" diff --git a/crates/bbs/Cargo.toml b/crates/bbs/Cargo.toml index 0ca1c55fc..71f210f61 100644 --- a/crates/bbs/Cargo.toml +++ b/crates/bbs/Cargo.toml @@ -9,5 +9,5 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-bbs/" [dependencies] -ssi-verification-methods = { workspace = true, features = ["ed25519"] } +ssi-verification-methods = { workspace = true, features = ["ed25519", "bls12-381"] } zkryptium = "0.2.2" \ No newline at end of file diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 735199d7f..20741c13a 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -1,4 +1,6 @@ -use ssi_verification_methods::{MessageSignatureError, MultiSigningMethod, Multikey}; +use ssi_verification_methods::{ + multikey::DecodedMultikey, MessageSignatureError, MultiSigningMethod, Multikey, +}; use zkryptium::bbsplus::commitment::BlindFactor; pub use zkryptium::bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}; @@ -13,10 +15,6 @@ pub enum Bbs { }, } -pub fn bbs_public_key_from_multikey(multikey: &Multikey) -> BBSplusPublicKey { - todo!() -} - impl MultiSigningMethod for Multikey { fn sign_bytes_multi( &self, @@ -32,7 +30,10 @@ impl MultiSigningMethod for Multikey { }, }; - let pk = bbs_public_key_from_multikey(self); + let DecodedMultikey::Bls12_381(pk) = self.decode()? else { + return Err(MessageSignatureError::InvalidPublicKey); + }; + let signature = match algorithm { Bbs::Baseline { header } => Signature::>::sign( Some(messages), diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs index 2bfea62a4..87840e0c6 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs @@ -119,6 +119,19 @@ impl JsonPointerBuf { Err(InvalidJsonPointer(value)) } } + + pub fn from_bytes(value: Vec) -> Result>> { + match String::from_utf8(value) { + Ok(value) => { + if JsonPointer::validate(&value) { + Ok(Self(value)) + } else { + Err(InvalidJsonPointer(value.into_bytes())) + } + } + Err(err) => Err(InvalidJsonPointer(err.into_bytes())), + } + } } impl Deref for JsonPointerBuf { diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 2a0fc1d38..dd3268284 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-data-integrity/" [features] +default = ["bbs"] ## Signature suites specified by the W3C. ## ## This includes: @@ -91,6 +92,9 @@ solana = ["ssi-verification-methods/solana", "k256"] ## - `Eip712Signature2021` (requires `eip712`) ethereum = ["serde_json"] +## BBS cryptographic suites. +bbs = ["ssi-verification-methods/bls12-381"] + [dependencies] ssi-data-integrity-core.workspace = true ssi-verification-methods.workspace = true @@ -159,6 +163,7 @@ rand = "0.7" hashbrown = "0.13.0" iref = { workspace = true, features = ["hashbrown"] } ssi-vc.workspace = true +nquads-syntax.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 9ce57d7c1..1c087bdec 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,11 +1,15 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: -use serde::Serialize; +use multibase::Base; +use serde::{Deserialize, Serialize}; use ssi_bbs::BBSplusPublicKey; use ssi_data_integrity_core::{ - suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, - ProofConfiguration, StandardCryptographicSuite, TypeRef, + suite::{ + standard::TransformationError, ConfigurationAlgorithm, ConfigurationError, + InputProofOptions, + }, + Proof, ProofConfiguration, StandardCryptographicSuite, TypeRef, }; use ssi_di_sd_primitives::JsonPointerBuf; use ssi_security::MultibaseBuf; @@ -22,9 +26,6 @@ pub use signature::Bbs2023SignatureAlgorithm; mod verification; -#[cfg(test)] -mod tests; - /// The `bbs-2023` cryptographic suite. #[derive(Debug, Clone, Copy)] pub struct Bbs2023; @@ -47,7 +48,12 @@ impl StandardCryptographicSuite for Bbs2023 { } } -pub struct Bbs2023InputOptions { +pub enum Bbs2023InputOptions { + Base(Bbs2023BaseInputOptions), + Derived(Bbs2023DerivedInputOptions), +} + +pub struct Bbs2023BaseInputOptions { pub mandatory_pointers: Vec, pub feature_option: FeatureOption, @@ -57,6 +63,16 @@ pub struct Bbs2023InputOptions { pub hmac_key: Option, } +pub struct Bbs2023DerivedInputOptions { + pub proof: Proof, + + pub selective_pointers: Vec, + + pub feature_option: DerivedFeatureOption, + + pub presentation_header: Option>, +} + #[derive(Debug, Default, Clone, Copy)] pub enum FeatureOption { #[default] @@ -66,6 +82,24 @@ pub enum FeatureOption { PseudonymHiddenPid, } +#[derive(Serialize, Deserialize)] +#[serde(tag = "featureOption")] +pub enum DerivedFeatureOption { + Baseline, + AnonymousHolderBinding { + holder_secret: String, + prover_blind: String, + }, + PseudonymIssuerPid { + verifier_id: String, + }, + PseudonymHiddenPid { + pid: String, + prover_blind: String, + verifier_id: String, + }, +} + pub type HmacKey = [u8; 32]; /// Base Proof Configuration. @@ -96,6 +130,15 @@ impl ConfigurationAlgorithm for Bbs2023Configuration { } } +pub struct DecodedBaseProof { + pub signature_bytes: Vec, + pub bbs_header: [u8; 64], + pub public_key: BBSplusPublicKey, + pub hmac_key: [u8; 32], + pub mandatory_pointers: Vec, + pub description: Bbs2023SignatureDescription, +} + #[derive(Serialize)] pub struct Bbs2023Signature { pub proof_value: MultibaseBuf, @@ -152,6 +195,155 @@ impl Bbs2023Signature { proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), } } + + /// Parses the components of a bbs-2023 selective disclosure base proof + /// value. + /// + /// See: + pub fn decode_base_proof(&self) -> Result { + let (base, decoded_proof_value) = self + .proof_value + .decode() + .map_err(|_| TransformationError::InvalidInput)?; + + if base != Base::Base64Url || decoded_proof_value.len() < 3 { + return Err(TransformationError::InvalidInput); + } + + let header = [ + decoded_proof_value[0], + decoded_proof_value[1], + decoded_proof_value[2], + ]; + + let mut components = + serde_cbor::from_slice::>(&decoded_proof_value[3..]) + .map_err(|_| TransformationError::InvalidInput)? + .into_iter(); + + let Some(serde_cbor::Value::Bytes(signature_bytes)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let Some(serde_cbor::Value::Bytes(bbs_header)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let bbs_header: [u8; 64] = bbs_header + .try_into() + .map_err(|_| TransformationError::InvalidInput)?; + + let Some(serde_cbor::Value::Bytes(public_key)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let public_key = BBSplusPublicKey::from_bytes(&public_key) + .map_err(|_| TransformationError::InvalidInput)?; + + let Some(serde_cbor::Value::Bytes(hmac_key)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let hmac_key: [u8; 32] = hmac_key + .try_into() + .map_err(|_| TransformationError::InvalidInput)?; + + let Some(serde_cbor::Value::Array(mandatory_pointers_values)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let mut mandatory_pointers = Vec::with_capacity(mandatory_pointers_values.len()); + for value in mandatory_pointers_values { + let serde_cbor::Value::Bytes(bytes) = value else { + return Err(TransformationError::InvalidInput); + }; + + let pointer = + JsonPointerBuf::from_bytes(bytes).map_err(|_| TransformationError::InvalidInput)?; + + mandatory_pointers.push(pointer); + } + + match header { + [0xd9, 0x5d, 0x02] => { + // baseline + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::Baseline, + }) + } + [0xd9, 0x5d, 0x04] => { + // anonymous_holder_binding + let signer_blind = match components.next() { + Some(serde_cbor::Value::Bytes(signer_blind)) => Some( + signer_blind + .try_into() + .map_err(|_| TransformationError::InvalidInput)?, + ), + Some(serde_cbor::Value::Null) => None, + _ => return Err(TransformationError::InvalidInput), + }; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::AnonymousHolderBinding { + signer_blind, + }, + }) + } + [0xd9, 0x5d, 0x06] => { + // pseudonym_issuer_pid + let Some(serde_cbor::Value::Bytes(pid)) = components.next() else { + return Err(TransformationError::InvalidInput); + }; + + let pid: [u8; 32] = pid + .try_into() + .map_err(|_| TransformationError::InvalidInput)?; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, + }) + } + [0xd9, 0x5d, 0x08] => { + // pseudonym_hidden_pid + let signer_blind = match components.next() { + Some(serde_cbor::Value::Bytes(signer_blind)) => Some( + signer_blind + .try_into() + .map_err(|_| TransformationError::InvalidInput)?, + ), + Some(serde_cbor::Value::Null) => None, + _ => return Err(TransformationError::InvalidInput), + }; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::AnonymousHolderBinding { + signer_blind, + }, + }) + } + _ => return Err(TransformationError::InvalidInput), + } + } } impl AsRef for Bbs2023Signature { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs index ee031cc17..438849228 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs @@ -1,11 +1,13 @@ -use ssi_bbs::{bbs_public_key_from_multikey, Bbs}; +use std::borrow::Cow; + +use ssi_bbs::Bbs; use ssi_claims_core::SignatureError; use ssi_data_integrity_core::{ suite::standard::{SignatureAlgorithm, SignatureAndVerificationAlgorithm}, ProofConfigurationRef, }; use ssi_rdf::IntoNQuads; -use ssi_verification_methods::{MultiMessageSigner, MultiSigner, Multikey}; +use ssi_verification_methods::{multikey::DecodedMultikey, MultiMessageSigner, Multikey, Signer}; use crate::{bbs_2023::Bbs2023SignatureDescription, Bbs2023}; @@ -19,7 +21,8 @@ impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { impl SignatureAlgorithm for Bbs2023SignatureAlgorithm where - T: MultiSigner, + T: Signer, + T::MessageSigner: MultiMessageSigner, { async fn sign( verification_method: &Multikey, @@ -30,7 +33,9 @@ where match prepared_claims { HashData::Base(hash_data) => { // See: - let public_key = bbs_public_key_from_multikey(verification_method); + let DecodedMultikey::Bls12_381(public_key) = verification_method.decode()? else { + return Err(SignatureError::InvalidPublicKey); + }; let feature_option = hash_data.transformed_document.options.feature_option; let proof_hash = &hash_data.proof_hash; let mandatory_pointers = &hash_data.transformed_document.options.mandatory_pointers; @@ -48,7 +53,10 @@ where .map(String::into_bytes) .collect(); - let message_signer = signer.for_verification_method(verification_method).await?; + let message_signer = signer + .for_method(Cow::Borrowed(verification_method)) + .await + .ok_or(SignatureError::MissingSigner)?; let (algorithm, description) = match feature_option { FeatureOption::Baseline => ( @@ -117,13 +125,21 @@ where #[cfg(test)] mod tests { + use iref::Iri; use lazy_static::lazy_static; - use ssi_data_integrity_core::suite::standard::SignatureAlgorithm; + use nquads_syntax::Parse; + use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; + use ssi_data_integrity_core::{suite::standard::SignatureAlgorithm, ProofConfiguration}; use ssi_di_sd_primitives::JsonPointerBuf; + use ssi_verification_methods::{Multikey, ProofPurpose, ReferenceOrOwned, SingleSecretSigner}; + use static_iref::{iri, uri}; - use crate::bbs_2023::{ - hashing::BaseHashData, transformation::TransformedBase, Bbs2023InputOptions, FeatureOption, - HashData, HmacKey, + use crate::{ + bbs_2023::{ + hashing::BaseHashData, transformation::TransformedBase, Bbs2023BaseInputOptions, + FeatureOption, HashData, HmacKey, + }, + Bbs2023, }; use super::Bbs2023SignatureAlgorithm; @@ -143,7 +159,44 @@ mod tests { const HMAC_KEY_STRING: &str = "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; - fn test_base_proof_serialization() { + const DID: &Iri = iri!("did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ"); + + const MANDATORY: &str = +"_:b0 . +_:b0 _:b3 . +_:b0 . +_:b2 \"2022\"^^ . +_:b3 _:b2 . +_:b3 \"Earth101\" . +_:b3 _:b6 . +_:b3 _:b7 . +_:b6 \"Lahaina\" . +_:b6 \"6.1E0\"^^ . +_:b6 \"2023\"^^ . +_:b7 \"Lahaina\" . +_:b7 \"7\"^^ . +_:b7 \"2020\"^^ . +"; + + const NON_MANDATORY: &str = +"_:b1 \"Lahaina\" . +_:b1 \"7.8E0\"^^ . +_:b1 \"2023\"^^ . +_:b2 \"CompFoil170\" . +_:b2 \"Wailea\" . +_:b3 _:b4 . +_:b3 _:b1 . +_:b3 _:b5 . +_:b4 \"Kanaha Custom\" . +_:b4 \"Wailea\" . +_:b4 \"2019\"^^ . +_:b5 \"Kihei\" . +_:b5 \"5.5E0\"^^ . +_:b5 \"2023\"^^ . +"; + + #[async_std::test] + async fn test_base_proof_serialization() { let mut proof_hash = [0; 32]; hex::decode_to_slice( b"3a5bbf25d34d90b18c35cd2357be6a6f42301e94fc9e52f77e93b773c5614bdf", @@ -161,15 +214,50 @@ mod tests { let mut hmac_key = HmacKey::default(); hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); - let mandatory = Vec::new(); - let non_mandatory = Vec::new(); + let mandatory = nquads_syntax::Document::parse_str(MANDATORY) + .unwrap() + .0 + .into_iter() + .map(|q| nquads_syntax::strip_quad(q.0)) + .collect(); + let non_mandatory = nquads_syntax::Document::parse_str(NON_MANDATORY) + .unwrap() + .0 + .into_iter() + .map(|q| nquads_syntax::strip_quad(q.0)) + .collect(); + + let (public_key, secret_key) = key_pair(); + + let verification_method = Multikey::from_public_key( + DID.to_owned(), + uri!("did:method:test").to_owned(), + &public_key, + ); + + let signer = SingleSecretSigner::new(secret_key); - Bbs2023SignatureAlgorithm::sign( - verification_method, + let canonical_configuration = vec![ + "_:c14n0 \"2023-08-15T23:36:38Z\"^^ .\n".to_string(), + "_:c14n0 .\n".to_string(), + "_:c14n0 \"bbs-2023\"^^ .\n".to_string(), + "_:c14n0 .\n".to_string(), + "_:c14n0 .\n".to_string() + ]; + let proof_configuration = ProofConfiguration::new( + Bbs2023, + xsd_types::DateTime::now_ms(), + ReferenceOrOwned::Reference("did:method:test".parse().unwrap()), + ProofPurpose::Assertion, + (), + ); + + let signature = Bbs2023SignatureAlgorithm::sign( + &verification_method, signer, - &HashData::Base(BaseHashData { + HashData::Base(BaseHashData { transformed_document: TransformedBase { - options: Bbs2023InputOptions { + options: Bbs2023BaseInputOptions { mandatory_pointers: MANDATORY_POINTERS.clone(), feature_option: FeatureOption::Baseline, commitment_with_proof: None, @@ -183,9 +271,18 @@ mod tests { proof_hash, mandatory_hash, }), - proof_configuration, - ); + proof_configuration.borrowed(), + ) + .await + .unwrap(); + + assert_eq!(signature.proof_value.as_str(), "u2V0ChVhQhruAY3aNS3CPmmWCHub-Qms9T2_lwsXJpfgMqlc_2MIMvfF4Jv5OGmJAcLpfIB2SAqD861WELqnmGnKnqgSJFDf8Nfarnvi_jsMATMRslFhYQDpbvyXTTZCxjDXNI1e-am9CMB6U_J5S936Tt3PFYUvfVV3gX4mIF-MTAbrBh9DD_ysD4svbSttNVowX3pYfmhhYYKTvGvo9pXVJbxIrm3i4wkdhUxqKCTIGrnxFuAdZwWi6T3omD5wzZ7bAGbRneEEQSxBmXtvnC6Pr59nPv_v3HrAW9wq_uxYzF_NyaX3GPv0h_FV2T2OSao8C6uoyWiqIj1ggABEiM0RVZneImaq7zN3u_wARIjNEVWZ3iJmqu8zd7v-FZy9pc3N1ZXJ4HS9jcmVkZW50aWFsU3ViamVjdC9zYWlsTnVtYmVyeBovY3JlZGVudGlhbFN1YmplY3Qvc2FpbHMvMXggL2NyZWRlbnRpYWxTdWJqZWN0L2JvYXJkcy8wL3llYXJ4Gi9jcmVkZW50aWFsU3ViamVjdC9zYWlscy8y") + } - todo!() + fn key_pair() -> (BBSplusPublicKey, BBSplusSecretKey) { + ( + BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(), + BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap(), + ) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs deleted file mode 100644 index 5a9b216f1..000000000 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests.rs +++ /dev/null @@ -1,191 +0,0 @@ -use ssi_json_ld::{JsonLdProcessor, RemoteDocument}; -use lazy_static::lazy_static; -use linked_data::to_lexical_quads; -use rdf_types::{generator, LexicalQuad, Quad}; -use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; -use ssi_di_sd_primitives::JsonPointerBuf; -use ssi_rdf::{urdna2015, IntoNQuads}; - -const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; -const SECRET_KEY_HEX: &str = "66d36e118832af4c5e28b2dfe1b9577857e57b042a33e06bdea37b811ed09ee0"; -const HMAC_KEY_STRING: &str = "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; - -lazy_static! { - pub static ref PUBLIC_KEY: BBSplusPublicKey = - BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); - pub static ref SECRET_KEY: BBSplusSecretKey = - BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap(); - pub static ref CREDENTIAL: json_ld::syntax::Value = json_syntax::json!({ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - { - "@vocab": "https://windsurf.grotto-networking.com/selective#" - } - ], - "type": [ - "VerifiableCredential" - ], - "issuer": "https://vc.example/windsurf/racecommittee", - "credentialSubject": { - "sailNumber": "Earth101", - "sails": [ - { - "size": 5.5, - "sailName": "Kihei", - "year": 2023 - }, - { - "size": 6.1, - "sailName": "Lahaina", - "year": 2023 - }, - { - "size": 7.0, - "sailName": "Lahaina", - "year": 2020 - }, - { - "size": 7.8, - "sailName": "Lahaina", - "year": 2023 - } - ], - "boards": [ - { - "boardName": "CompFoil170", - "brand": "Wailea", - "year": 2022 - }, - { - "boardName": "Kanaha Custom", - "brand": "Wailea", - "year": 2019 - } - ] - } - }); - pub static ref MANDATORY_POINTERS: Vec = vec![ - "/issuer".parse().unwrap(), - "/credentialSubject/sailNumber".parse().unwrap(), - "/credentialSubject/sails/1".parse().unwrap(), - "/credentialSubject/boards/0/year".parse().unwrap(), - "/credentialSubject/sails/2".parse().unwrap() - ]; -} - -async fn to_rdf(document: json_ld::syntax::Value) -> Vec { - let document: RemoteDocument = RemoteDocument::new(None, None, document); - let expanded_document = document - .expand(&mut ssi_json_ld::ContextLoader::default()) - .await - .unwrap(); - - to_lexical_quads(&mut generator::Blank::new(), &expanded_document).unwrap() -} - -async fn canonicalize_document(document: json_ld::syntax::Value) -> Vec { - let quads = to_rdf(document).await; - urdna2015::normalize(quads.iter().map(Quad::as_lexical_quad_ref)).collect() -} - -// #[async_std::test] -// async fn bbs_canonicalization() { -// const EXPECTED: [&str; 28] = [ -// "_:c14n0 \"CompFoil170\" .\n", -// "_:c14n0 \"Wailea\" .\n", -// "_:c14n0 \"2022\"^^ .\n", -// "_:c14n1 \"Lahaina\" .\n", -// "_:c14n1 \"7.8E0\"^^ .\n", -// "_:c14n1 \"2023\"^^ .\n", -// "_:c14n2 \"Kanaha Custom\" .\n", -// "_:c14n2 \"Wailea\" .\n", -// "_:c14n2 \"2019\"^^ .\n", -// "_:c14n3 \"Lahaina\" .\n", -// "_:c14n3 \"7\"^^ .\n", -// "_:c14n3 \"2020\"^^ .\n", -// "_:c14n4 \"Kihei\" .\n", -// "_:c14n4 \"5.5E0\"^^ .\n", -// "_:c14n4 \"2023\"^^ .\n", -// "_:c14n5 _:c14n0 .\n", -// "_:c14n5 _:c14n2 .\n", -// "_:c14n5 \"Earth101\" .\n", -// "_:c14n5 _:c14n1 .\n", -// "_:c14n5 _:c14n3 .\n", -// "_:c14n5 _:c14n4 .\n", -// "_:c14n5 _:c14n6 .\n", -// "_:c14n6 \"Lahaina\" .\n", -// "_:c14n6 \"6.1E0\"^^ .\n", -// "_:c14n6 \"2023\"^^ .\n", -// "_:c14n7 .\n", -// "_:c14n7 _:c14n5 .\n", -// "_:c14n7 .\n" -// ]; - -// let canonical_quads = canonicalize_document(CREDENTIAL.clone()) -// .await -// .into_nquads_lines(); -// assert_eq!(canonical_quads, EXPECTED); -// } - -// #[async_std::test] -// async fn bbs_hmac_canonicalization() { -// const EXPECTED: [&str; 28] = [ -// "_:b0 .\n", -// "_:b0 _:b3 .\n", -// "_:b0 .\n", -// "_:b1 \"Lahaina\" .\n", -// "_:b1 \"7.8E0\"^^ .\n", -// "_:b1 \"2023\"^^ .\n", -// "_:b2 \"CompFoil170\" .\n", -// "_:b2 \"Wailea\" .\n", -// "_:b2 \"2022\"^^ .\n", -// "_:b3 _:b2 .\n", -// "_:b3 _:b4 .\n", -// "_:b3 \"Earth101\" .\n", -// "_:b3 _:b1 .\n", -// "_:b3 _:b5 .\n", -// "_:b3 _:b6 .\n", -// "_:b3 _:b7 .\n", -// "_:b4 \"Kanaha Custom\" .\n", -// "_:b4 \"Wailea\" .\n", -// "_:b4 \"2019\"^^ .\n", -// "_:b5 \"Kihei\" .\n", -// "_:b5 \"5.5E0\"^^ .\n", -// "_:b5 \"2023\"^^ .\n", -// "_:b6 \"Lahaina\" .\n", -// "_:b6 \"6.1E0\"^^ .\n", -// "_:b6 \"2023\"^^ .\n", -// "_:b7 \"Lahaina\" .\n", -// "_:b7 \"7\"^^ .\n", -// "_:b7 \"2020\"^^ .\n" -// ]; - -// let mut hmac = Hmac::new_from_slice(HMAC_KEY_STRING.as_bytes()).unwrap(); - -// let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); - -// // let mut environment = ssi_json_ld::JsonLdEnvironment::default(); -// // let canonical = canonicalize( -// // &mut environment, -// // label_map_factory_function, -// // &ssi_json_ld::CompactJsonLd(CREDENTIAL.clone()) -// // ).await.unwrap(); - -// // let canonical_nquads = canonical.quads.into_nquads_lines(); - -// let quads = to_rdf(CREDENTIAL.clone()).await; -// let hmac_nquads = label_replacement_canonicalize_nquads(label_map_factory_function, &quads).0.into_nquads_lines(); - -// for line in &hmac_nquads { -// print!("{}", line) -// } - -// assert_eq!(hmac_nquads, EXPECTED); -// } - -// fn key_pair() -> (BBSplusPublicKey, BBSplusSecretKey) { -// ( -// BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(), -// BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap() -// ) -// } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs similarity index 74% rename from crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs rename to crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs index 37006ff06..6b0241e53 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs @@ -3,98 +3,68 @@ use std::{collections::HashMap, hash::Hash}; use getrandom::getrandom; use hmac::{Hmac, Mac}; use k256::sha2::Sha256; -use rdf_types::{ - BlankIdBuf, LexicalQuad -}; -use ssi_data_integrity_core::{ - suite::standard::{TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm}, - ProofConfigurationRef, -}; +use rdf_types::BlankIdBuf; +use ssi_data_integrity_core::suite::standard::TransformationError; use ssi_di_sd_primitives::{ canonicalize::create_hmac_id_label_map_function, group::canonicalize_and_group, }; -use ssi_json_ld::{ContextLoaderEnvironment, Expandable, JsonLdNodeObject}; +use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument}; use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; -use crate::Bbs2023; - -use super::{Bbs2023InputOptions, HmacKey}; +use crate::bbs_2023::{Bbs2023BaseInputOptions, HmacKey}; -pub struct Bbs2023Transformation; +use super::TransformedBase; -impl TransformationAlgorithm for Bbs2023Transformation { - type Output = Transformed; -} - -impl TypedTransformationAlgorithm for Bbs2023Transformation +pub async fn base_proof_transformation( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + canonical_configuration: Vec, + transform_options: Bbs2023BaseInputOptions, +) -> Result where - C: ContextLoaderEnvironment, T: JsonLdNodeObject + Expandable, - T::Expanded: Into + T::Expanded: Into { - async fn transform( - context: &C, - unsecured_document: &T, - proof_configuration: ProofConfigurationRef<'_, Bbs2023>, - transformation_options: Option, - ) -> Result { - let canonical_configuration = proof_configuration - .expand(context, unsecured_document) - .await - .map_err(TransformationError::ProofConfigurationExpansion)? - .nquads_lines(); - - match transformation_options { - Some(transform_options) => { - // Base Proof Transformation algorithm. - // See: - let hmac_key = match transform_options.hmac_key { - Some(key) => key, - None => { - // Generate a random key - let mut key = HmacKey::default(); - getrandom(&mut key).map_err(TransformationError::internal)?; - key - } - }; - - let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); - - let mut group_definitions = HashMap::new(); - group_definitions.insert(Mandatory, transform_options.mandatory_pointers.clone()); - - let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); - - let mut groups = canonicalize_and_group( - context.loader(), - label_map_factory_function, - group_definitions, - unsecured_document, - ) - .await - .map_err(TransformationError::internal)? - .groups; - - let mandatory_group = groups.remove(&Mandatory).unwrap(); - let mandatory = mandatory_group.matching.into_values().collect(); - let non_mandatory = mandatory_group.non_matching.into_values().collect(); - - Ok(Transformed::Base(TransformedBase { - options: transform_options, - mandatory, - non_mandatory, - hmac_key, - canonical_configuration, - })) - } - None => { - // createVerifyData, step 1, 3, 4 - // canonicalize input document into N-Quads. - // Ok(Transformed::Derived(todo!())) - todo!() - } + // Base Proof Transformation algorithm. + // See: + let hmac_key = match transform_options.hmac_key { + Some(key) => key, + None => { + // Generate a random key + let mut key = HmacKey::default(); + getrandom(&mut key).map_err(TransformationError::internal)?; + key } - } + }; + + let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); + + let mut group_definitions = HashMap::new(); + group_definitions.insert(Mandatory, transform_options.mandatory_pointers.clone()); + + let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); + + let mut groups = canonicalize_and_group( + loader, + label_map_factory_function, + group_definitions, + unsecured_document, + ) + .await + .map_err(TransformationError::internal)? + .groups; + + let mandatory_group = groups.remove(&Mandatory).unwrap(); + let mandatory = mandatory_group.matching.into_values().collect(); + let non_mandatory = mandatory_group.non_matching.into_values().collect(); + + Ok(TransformedBase { + options: transform_options, + mandatory, + non_mandatory, + hmac_key, + canonical_configuration, + }) } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -104,7 +74,7 @@ struct Mandatory; /// blank node identifiers. /// /// See: -pub(crate) fn create_shuffled_id_label_map_function( +pub fn create_shuffled_id_label_map_function( hmac: &mut Hmac, ) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { |canonical_map| { @@ -125,36 +95,6 @@ pub(crate) fn create_shuffled_id_label_map_function( } } -pub enum Transformed { - Base(TransformedBase), - Derived(TransformedDerived), -} - -impl Transformed { - pub fn into_base(self) -> Option { - match self { - Self::Base(b) => Some(b), - _ => None, - } - } -} - -/// Result of the Base Proof Transformation algorithm. -/// -/// See: -pub struct TransformedBase { - pub options: Bbs2023InputOptions, - pub mandatory: Vec, - pub non_mandatory: Vec, - pub hmac_key: HmacKey, - pub canonical_configuration: Vec, -} - -pub struct TransformedDerived { - pub proof_hash: String, - pub nquads: Vec, -} - #[cfg(test)] mod tests { use std::collections::HashMap; @@ -166,17 +106,19 @@ mod tests { suite::standard::TypedTransformationAlgorithm, ProofConfiguration, }; use ssi_di_sd_primitives::{group::canonicalize_and_group, JsonPointerBuf}; - use ssi_json_ld::JsonLdEnvironment; use ssi_rdf::IntoNQuads; use ssi_vc::v2::syntax::JsonCredential; use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use crate::{ - bbs_2023::{Bbs2023InputOptions, FeatureOption, HmacKey}, + bbs_2023::{ + Bbs2023BaseInputOptions, Bbs2023InputOptions, Bbs2023Transformation, FeatureOption, + HmacKey, + }, Bbs2023, }; - use super::{create_shuffled_id_label_map_function, Bbs2023Transformation, Mandatory}; + use super::{create_shuffled_id_label_map_function, Mandatory}; const HMAC_KEY_STRING: &str = "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; @@ -243,7 +185,7 @@ mod tests { #[async_std::test] async fn hmac_canonicalize_and_group() { - let mut context = JsonLdEnvironment::default(); + let loader = ssi_json_ld::ContextLoader::default(); let mut hmac_key = HmacKey::default(); hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); @@ -255,7 +197,7 @@ mod tests { let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); let canonical = canonicalize_and_group( - &mut context, + &loader, label_map_factory_function, group_definitions, &*CREDENTIAL, @@ -303,7 +245,7 @@ mod tests { #[async_std::test] async fn transform_test() { - let mut context = JsonLdEnvironment::default(); + let context = ssi_claims_core::SignatureEnvironment::default(); let proof_configuration = ProofConfiguration::new( Bbs2023, @@ -317,15 +259,15 @@ mod tests { hex::decode_to_slice(HMAC_KEY_STRING.as_bytes(), &mut hmac_key).unwrap(); let transformed = Bbs2023Transformation::transform( - &mut context, + &context, &*CREDENTIAL, proof_configuration.borrowed(), - Some(Bbs2023InputOptions { + Some(Bbs2023InputOptions::Base(Bbs2023BaseInputOptions { mandatory_pointers: MANDATORY_POINTERS.clone(), feature_option: FeatureOption::Baseline, commitment_with_proof: None, hmac_key: Some(hmac_key), - }), + })), ) .await .unwrap() diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs new file mode 100644 index 000000000..0162ecbe4 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs @@ -0,0 +1,18 @@ +use ssi_data_integrity_core::Proof; +use ssi_di_sd_primitives::JsonPointerBuf; + +use crate::{bbs_2023::DerivedFeatureOption, Bbs2023}; + +/// Creates data to be used to generate a derived proof. +/// +/// See: +pub async fn create_disclosure_data( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + proof: &Proof, + selective_pointers: &[JsonPointerBuf], + presentation_header: Option<&[u8]>, + transform_options: &DerivedFeatureOption, +) { + // ... +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs new file mode 100644 index 000000000..a5ba774c0 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs @@ -0,0 +1,110 @@ +use super::{Bbs2023BaseInputOptions, Bbs2023InputOptions, HmacKey}; +use crate::Bbs2023; +use linked_data::{LinkedDataResource, LinkedDataSubject}; +use rdf_types::{ + interpretation::ReverseTermInterpretation, BlankIdBuf, InterpretationMut, LexicalQuad, + VocabularyMut, +}; +use ssi_data_integrity_core::{ + suite::standard::{TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm}, + ProofConfigurationRef, +}; +use ssi_di_sd_primitives::{ + canonicalize::create_hmac_id_label_map_function, group::canonicalize_and_group, +}; +use ssi_rdf::LexicalInterpretation; +use ssi_json_ld::{Expandable, JsonLdNodeObject, ContextLoaderEnvironment, ExpandedDocument}; +use std::{fmt, hash::Hash}; + +mod base; +mod derived; + +pub struct Bbs2023Transformation; + +impl TransformationAlgorithm for Bbs2023Transformation { + type Output = Transformed; +} + +impl TypedTransformationAlgorithm for Bbs2023Transformation +where + C: ContextLoaderEnvironment, + T: JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + async fn transform( + context: &C, + unsecured_document: &T, + proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + transformation_options: Option, + ) -> Result { + match transformation_options { + Some(Bbs2023InputOptions::Base(transform_options)) => { + let canonical_configuration = proof_configuration + .expand(context, unsecured_document) + .await + .map_err(TransformationError::ProofConfigurationExpansion)? + .nquads_lines(); + + base::base_proof_transformation( + context.loader(), + unsecured_document, + canonical_configuration, + transform_options, + ) + .await + .map(Transformed::Base) + } + Some(Bbs2023InputOptions::Derived(transform_options)) => { + // https://www.w3.org/TR/vc-di-bbs/#add-derived-proof-bbs-2023 + + derived::create_disclosure_data( + context.loader(), + unsecured_document, + &transform_options.proof, + &transform_options.selective_pointers, + transform_options.presentation_header.as_deref(), + &transform_options.feature_option, + ) + .await; + + todo!() + } + None => { + // createVerifyData, step 1, 3, 4 + // canonicalize input document into N-Quads. + // Ok(Transformed::Derived(todo!())) + todo!() + } + } + } +} + +pub enum Transformed { + Base(TransformedBase), + Derived(TransformedDerived), +} + +impl Transformed { + pub fn into_base(self) -> Option { + match self { + Self::Base(b) => Some(b), + _ => None, + } + } +} + +/// Result of the Base Proof Transformation algorithm. +/// +/// See: +pub struct TransformedBase { + pub options: Bbs2023BaseInputOptions, + pub mandatory: Vec, + pub non_mandatory: Vec, + pub hmac_key: HmacKey, + pub canonical_configuration: Vec, +} + +pub struct TransformedDerived { + pub proof_hash: String, + pub nquads: Vec, +} diff --git a/crates/claims/crates/jws/src/lib.rs b/crates/claims/crates/jws/src/lib.rs index 255e66b4b..877f561ad 100644 --- a/crates/claims/crates/jws/src/lib.rs +++ b/crates/claims/crates/jws/src/lib.rs @@ -893,10 +893,10 @@ pub fn recover(algorithm: Algorithm, data: &[u8], signature: &[u8]) -> Result Result { @@ -1142,7 +1142,7 @@ mod tests { #[test] #[cfg(feature = "secp256k1")] fn secp256k1_sign_verify() { - let key = JWK::generate_secp256k1().unwrap(); + let key = JWK::generate_secp256k1(); let data = b"asdf"; let bad_data = b"no"; let sig = sign_bytes(Algorithm::ES256K, data, &key).unwrap(); @@ -1163,14 +1163,14 @@ mod tests { verify_bytes(Algorithm::ES256KR, bad_data, &key, &sig).unwrap_err(); let recovered_key = recover(Algorithm::ES256KR, data, &sig).unwrap(); verify_bytes(Algorithm::ES256KR, data, &recovered_key, &sig).unwrap(); - let other_key = JWK::generate_secp256k1().unwrap(); + let other_key = JWK::generate_secp256k1(); verify_bytes(Algorithm::ES256KR, data, &other_key, &sig).unwrap_err(); } #[test] #[cfg(feature = "eip")] fn keccak_sign_verify() { - let key = JWK::generate_secp256k1().unwrap(); + let key = JWK::generate_secp256k1(); let data = b"asdf"; let bad_data = b"no"; // ESKeccakK-R @@ -1180,7 +1180,7 @@ mod tests { }; let sig = sign_bytes(Algorithm::ES256KR, data, &key).unwrap(); - let other_key = JWK::generate_secp256k1().unwrap(); + let other_key = JWK::generate_secp256k1(); // TODO check the error type verify_bytes(Algorithm::ESKeccakKR, data, &key, &sig).unwrap_err(); verify_bytes(Algorithm::ESKeccakKR, bad_data, &key, &sig).unwrap_err(); @@ -1217,7 +1217,7 @@ mod tests { #[test] #[cfg(feature = "secp384r1")] fn p384_sign_verify() { - let key = JWK::generate_p384().unwrap(); + let key = JWK::generate_p384(); let data = b"asdf"; let bad_data = b"no"; let sig = sign_bytes(Algorithm::ES384, data, &key).unwrap(); diff --git a/crates/dids/methods/ethr/src/lib.rs b/crates/dids/methods/ethr/src/lib.rs index ad93e97bc..3f6c30a1f 100644 --- a/crates/dids/methods/ethr/src/lib.rs +++ b/crates/dids/methods/ethr/src/lib.rs @@ -536,7 +536,7 @@ mod tests { assert!(vc_bad_issuer.verify(&verifier).await.unwrap().is_err()); // Check that proof JWK must match proof verificationMethod - let wrong_key = JWK::generate_secp256k1().unwrap(); + let wrong_key = JWK::generate_secp256k1(); let wrong_signer = SingleSecretSigner::new(wrong_key.clone()).into_local(); let vc_wrong_key = suite .sign( diff --git a/crates/dids/methods/ion/src/ion.rs b/crates/dids/methods/ion/src/ion.rs index 6619c3c80..61b67711d 100644 --- a/crates/dids/methods/ion/src/ion.rs +++ b/crates/dids/methods/ion/src/ion.rs @@ -1,13 +1,13 @@ use ssi_jwk::{Algorithm, JWK}; -use crate::sidetree::{KeyGenerationFailed, Sidetree}; +use crate::sidetree::Sidetree; #[derive(Default, Clone)] pub struct ION; impl Sidetree for ION { - fn generate_key() -> Result { - JWK::generate_secp256k1().map_err(|_| KeyGenerationFailed) + fn generate_key() -> JWK { + JWK::generate_secp256k1() } fn validate_key(key: &JWK) -> bool { diff --git a/crates/dids/methods/ion/src/sidetree/mod.rs b/crates/dids/methods/ion/src/sidetree/mod.rs index 389f3d080..351f27777 100644 --- a/crates/dids/methods/ion/src/sidetree/mod.rs +++ b/crates/dids/methods/ion/src/sidetree/mod.rs @@ -203,7 +203,7 @@ pub trait Sidetree { /// Generate a new keypair ([KEY_ALGORITHM][ka]) /// /// [ka]: https://identity.foundation/sidetree/spec/v1.0.0/#key-algorithm - fn generate_key() -> Result; + fn generate_key() -> JWK; /// Ensure that a keypair is valid for this Sidetree DID Method /// @@ -336,8 +336,8 @@ pub trait Sidetree { /// /// [create]: https://identity.foundation/sidetree/spec/v1.0.0/#create fn create(patches: Vec) -> Result<(Operation, JWK, JWK), CreateError> { - let update_keypair = Self::generate_key()?; - let recovery_keypair = Self::generate_key()?; + let update_keypair = Self::generate_key(); + let recovery_keypair = Self::generate_key(); let update_pk = PublicKeyJwk::try_from(update_keypair.to_public()) .map_err(|_| CreateError::InvalidUpdateKey)?; let recovery_pk = PublicKeyJwk::try_from(recovery_keypair.to_public()) @@ -464,10 +464,10 @@ pub trait Sidetree { recovery_key: &JWK, patches: Vec, ) -> Result<(Operation, JWK, JWK), RecoverError> { - let new_update_keypair = Self::generate_key()?; + let new_update_keypair = Self::generate_key(); let new_update_pk = PublicKeyJwk::try_from(new_update_keypair.to_public()).unwrap(); - let new_recovery_keypair = Self::generate_key()?; + let new_recovery_keypair = Self::generate_key(); let new_recovery_pk = PublicKeyJwk::try_from(new_recovery_keypair.to_public()).unwrap(); let recover_op = Self::recover_existing( @@ -974,8 +974,8 @@ mod tests { struct Example; impl Sidetree for Example { - fn generate_key() -> Result { - JWK::generate_secp256k1().map_err(|_| KeyGenerationFailed) + fn generate_key() -> JWK { + JWK::generate_secp256k1() } fn validate_key(key: &JWK) -> bool { is_secp256k1(key) diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index b53b56f0f..cdd1811e1 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -44,12 +44,12 @@ impl DIDKey { Params::OKP(ref params) => match ¶ms.curve[..] { "Ed25519" => multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode(ssi_multicodec::ED25519_PUB, ¶ms.public_key.0) + MultiEncodedBuf::encode_bytes(ssi_multicodec::ED25519_PUB, ¶ms.public_key.0) .into_bytes(), ), "Bls12381G2" => multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode(ssi_multicodec::BLS12_381_G2_PUB, ¶ms.public_key.0) + MultiEncodedBuf::encode_bytes(ssi_multicodec::BLS12_381_G2_PUB, ¶ms.public_key.0) .into_bytes(), ), _ => return Err(GenerateError::UnsupportedCurve(params.curve.clone())), @@ -71,7 +71,7 @@ impl DIDKey { multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode( + MultiEncodedBuf::encode_bytes( ssi_multicodec::SECP256K1_PUB, pk.to_encoded_point(true).as_bytes(), ) @@ -88,7 +88,7 @@ impl DIDKey { multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode( + MultiEncodedBuf::encode_bytes( ssi_multicodec::P256_PUB, pk.to_encoded_point(true).as_bytes(), ) @@ -116,7 +116,7 @@ impl DIDKey { .map_err(|_| GenerateError::InvalidInputKey)?; multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode(ssi_multicodec::RSA_PUB, &der).into_bytes(), + MultiEncodedBuf::encode_bytes(ssi_multicodec::RSA_PUB, &der).into_bytes(), ) } _ => return Err(GenerateError::UnsupportedKeyType), @@ -705,7 +705,7 @@ mod tests { let params = VerificationParameters::from_resolver(&didkey); let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(2); - let key = JWK::generate_secp256k1_from(&mut rng).unwrap(); + let key = JWK::generate_secp256k1_from(&mut rng); let did = DIDKey::generate(&key).unwrap(); let cred = JsonCredential::new( @@ -827,7 +827,7 @@ mod tests { #[async_std::test] #[cfg(feature = "secp256k1")] async fn fetch_jwk_secp256k1() { - let jwk = JWK::generate_secp256k1().unwrap(); + let jwk = JWK::generate_secp256k1(); fetch_jwk(jwk).await; } diff --git a/crates/dids/methods/pkh/src/lib.rs b/crates/dids/methods/pkh/src/lib.rs index 4215f251e..7b6ae7842 100644 --- a/crates/dids/methods/pkh/src/lib.rs +++ b/crates/dids/methods/pkh/src/lib.rs @@ -1221,7 +1221,7 @@ mod tests { "../../../../../tests/secp256r1-2021-03-18.json" )) .unwrap(); - let other_key_secp256k1 = JWK::generate_secp256k1().unwrap(); + let other_key_secp256k1 = JWK::generate_secp256k1(); let mut other_key_ed25519 = JWK::generate_ed25519().unwrap(); let mut other_key_p256 = JWK::generate_p256(); diff --git a/crates/dids/methods/tz/tests/did.rs b/crates/dids/methods/tz/tests/did.rs index 01ecddcf4..59b2e4c43 100644 --- a/crates/dids/methods/tz/tests/did.rs +++ b/crates/dids/methods/tz/tests/did.rs @@ -318,7 +318,7 @@ async fn credential_prove_verify_did_tz2() { // use ssi_claims::{Credential, Issuer, LinkedDataProofOptions, URI}; let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(2); - let mut key = JWK::generate_secp256k1_from(&mut rng).unwrap(); + let mut key = JWK::generate_secp256k1_from(&mut rng); // mark this key as being for use with key recovery key.algorithm = Some(Algorithm::ES256KR); let did = DIDTZ.generate(&key).unwrap(); @@ -359,7 +359,7 @@ async fn credential_prove_verify_did_tz2() { // Check that proof JWK must match proof verificationMethod let wrong_signer = - SingleSecretSigner::new(JWK::generate_secp256k1_from(&mut rng).unwrap()).into_local(); + SingleSecretSigner::new(JWK::generate_secp256k1_from(&mut rng)).into_local(); let vc_wrong_key = suite .sign( vc.claims.clone(), diff --git a/crates/jwk/src/lib.rs b/crates/jwk/src/lib.rs index 6365ae016..5c076d953 100644 --- a/crates/jwk/src/lib.rs +++ b/crates/jwk/src/lib.rs @@ -308,7 +308,7 @@ impl JWK { } #[cfg(feature = "secp256k1")] - pub fn generate_secp256k1() -> Result { + pub fn generate_secp256k1() -> JWK { let mut rng = rand::rngs::OsRng {}; Self::generate_secp256k1_from(&mut rng) } @@ -316,13 +316,13 @@ impl JWK { #[cfg(feature = "secp256k1")] pub fn generate_secp256k1_from( rng: &mut (impl rand::CryptoRng + rand::RngCore), - ) -> Result { + ) -> JWK { let secret_key = k256::SecretKey::random(rng); let sk_bytes = zeroize::Zeroizing::new(secret_key.to_bytes().to_vec()); let public_key = secret_key.public_key(); - let mut ec_params = ECParams::try_from(&public_key)?; + let mut ec_params = ECParams::from(&public_key); ec_params.ecc_private_key = Some(Base64urlUInt(sk_bytes.to_vec())); - Ok(JWK::from(Params::EC(ec_params))) + JWK::from(Params::EC(ec_params)) } #[cfg(feature = "secp256r1")] @@ -342,14 +342,14 @@ impl JWK { } #[cfg(feature = "secp384r1")] - pub fn generate_p384() -> Result { + pub fn generate_p384() -> JWK { let mut rng = rand::rngs::OsRng {}; let secret_key = p384::SecretKey::random(&mut rng); let sk_bytes = zeroize::Zeroizing::new(secret_key.to_bytes().to_vec()); let public_key: p384::PublicKey = secret_key.public_key(); - let mut ec_params = ECParams::try_from(&public_key)?; + let mut ec_params = ECParams::from(&public_key); ec_params.ecc_private_key = Some(Base64urlUInt(sk_bytes.to_vec())); - Ok(JWK::from(Params::EC(ec_params))) + JWK::from(Params::EC(ec_params)) } #[cfg(feature = "aleo")] @@ -951,25 +951,30 @@ fn ed25519_parse_private(data: &[u8]) -> Result { #[cfg(feature = "secp256k1")] pub fn secp256k1_parse(data: &[u8]) -> Result { let pk = k256::PublicKey::from_sec1_bytes(data)?; - let jwk = JWK { - params: Params::EC(ECParams::try_from(&pk)?), - public_key_use: None, - key_operations: None, - algorithm: None, - key_id: None, - x509_url: None, - x509_certificate_chain: None, - x509_thumbprint_sha1: None, - x509_thumbprint_sha256: None, - }; - Ok(jwk) + Ok(pk.into()) +} + +impl From for JWK { + fn from(value: k256::PublicKey) -> Self { + JWK { + params: Params::EC(ECParams::from(&value)), + public_key_use: None, + key_operations: None, + algorithm: None, + key_id: None, + x509_url: None, + x509_certificate_chain: None, + x509_thumbprint_sha1: None, + x509_thumbprint_sha256: None, + } + } } #[cfg(feature = "secp256k1")] pub fn secp256k1_parse_private(data: &[u8]) -> Result { let k = k256::SecretKey::from_sec1_der(data)?; let jwk = JWK { - params: Params::EC(ECParams::try_from(&k)?), + params: Params::EC(ECParams::from(&k)), public_key_use: None, key_operations: None, algorithm: None, @@ -1026,7 +1031,7 @@ fn p256_parse_private(data: &[u8]) -> Result { pub fn p384_parse(pk_bytes: &[u8]) -> Result { let pk = p384::PublicKey::from_sec1_bytes(pk_bytes)?; let jwk = JWK { - params: Params::EC(ECParams::try_from(&pk)?), + params: Params::EC(ECParams::from(&pk)), public_key_use: None, key_operations: None, algorithm: None, @@ -1039,11 +1044,28 @@ pub fn p384_parse(pk_bytes: &[u8]) -> Result { Ok(jwk) } +#[cfg(feature = "secp384r1")] +impl From for JWK { + fn from(value: p384::PublicKey) -> Self { + JWK { + params: Params::EC(ECParams::from(&value)), + public_key_use: None, + key_operations: None, + algorithm: None, + key_id: None, + x509_url: None, + x509_certificate_chain: None, + x509_thumbprint_sha1: None, + x509_thumbprint_sha256: None, + } + } +} + #[cfg(feature = "secp384r1")] fn p384_parse_private(data: &[u8]) -> Result { let k = p384::SecretKey::from_bytes(data.into())?; let jwk = JWK { - params: Params::EC(ECParams::try_from(&k)?), + params: Params::EC(ECParams::from(&k)), public_key_use: None, key_operations: None, algorithm: None, @@ -1249,39 +1271,33 @@ impl TryFrom<&ECParams> for p384::PublicKey { } #[cfg(feature = "secp256k1")] -impl TryFrom<&k256::PublicKey> for ECParams { - type Error = Error; - fn try_from(pk: &k256::PublicKey) -> Result { +impl From<&k256::PublicKey> for ECParams { + fn from(pk: &k256::PublicKey) -> Self { use k256::elliptic_curve::sec1::ToEncodedPoint; let ec_points = pk.to_encoded_point(false); - let x = ec_points.x().ok_or(Error::MissingPoint)?; - let y = ec_points.y().ok_or(Error::MissingPoint)?; - Ok(ECParams { + ECParams { // TODO according to https://tools.ietf.org/id/draft-jones-webauthn-secp256k1-00.html#rfc.section.2 it should be P-256K? curve: Some("secp256k1".to_string()), - x_coordinate: Some(Base64urlUInt(x.to_vec())), - y_coordinate: Some(Base64urlUInt(y.to_vec())), + x_coordinate: ec_points.x().map(|x| Base64urlUInt(x.to_vec())), + y_coordinate: ec_points.y().map(|y| Base64urlUInt(y.to_vec())), ecc_private_key: None, - }) + } } } #[cfg(feature = "secp256k1")] -impl TryFrom<&k256::SecretKey> for ECParams { - type Error = Error; - fn try_from(k: &k256::SecretKey) -> Result { +impl From<&k256::SecretKey> for ECParams { + fn from(k: &k256::SecretKey) -> Self { let pk = k.public_key(); use k256::elliptic_curve::sec1::ToEncodedPoint; let ec_points = pk.to_encoded_point(false); - let x = ec_points.x().ok_or(Error::MissingPoint)?; - let y = ec_points.y().ok_or(Error::MissingPoint)?; - Ok(ECParams { + ECParams { // TODO according to https://tools.ietf.org/id/draft-jones-webauthn-secp256k1-00.html#rfc.section.2 it should be P-256K? curve: Some("secp256k1".to_string()), - x_coordinate: Some(Base64urlUInt(x.to_vec())), - y_coordinate: Some(Base64urlUInt(y.to_vec())), + x_coordinate: ec_points.x().map(|x| Base64urlUInt(x.to_vec())), + y_coordinate: ec_points.y().map(|y| Base64urlUInt(y.to_vec())), ecc_private_key: Some(Base64urlUInt(k.to_bytes().to_vec())), - }) + } } } @@ -1315,37 +1331,31 @@ impl From<&p256::SecretKey> for ECParams { } #[cfg(feature = "secp384r1")] -impl TryFrom<&p384::PublicKey> for ECParams { - type Error = Error; - fn try_from(pk: &p384::PublicKey) -> Result { +impl From<&p384::PublicKey> for ECParams { + fn from(pk: &p384::PublicKey) -> Self { use p384::elliptic_curve::sec1::ToEncodedPoint; let encoded_point = pk.to_encoded_point(false); - let x = encoded_point.x().ok_or(Error::MissingPoint)?; - let y = encoded_point.y().ok_or(Error::MissingPoint)?; - Ok(ECParams { + ECParams { curve: Some("P-384".to_string()), - x_coordinate: Some(Base64urlUInt(x.to_vec())), - y_coordinate: Some(Base64urlUInt(y.to_vec())), + x_coordinate: encoded_point.x().map(|x| Base64urlUInt(x.to_vec())), + y_coordinate: encoded_point.y().map(|y| Base64urlUInt(y.to_vec())), ecc_private_key: None, - }) + } } } #[cfg(feature = "secp384r1")] -impl TryFrom<&p384::SecretKey> for ECParams { - type Error = Error; - fn try_from(k: &p384::SecretKey) -> Result { +impl From<&p384::SecretKey> for ECParams { + fn from(k: &p384::SecretKey) -> Self { let pk = k.public_key(); use p384::elliptic_curve::sec1::ToEncodedPoint; let encoded_point = pk.to_encoded_point(false); - let x = encoded_point.x().ok_or(Error::MissingPoint)?; - let y = encoded_point.y().ok_or(Error::MissingPoint)?; - Ok(ECParams { + ECParams { curve: Some("P-384".to_string()), - x_coordinate: Some(Base64urlUInt(x.to_vec())), - y_coordinate: Some(Base64urlUInt(y.to_vec())), + x_coordinate: encoded_point.x().map(|x| Base64urlUInt(x.to_vec())), + y_coordinate: encoded_point.y().map(|y| Base64urlUInt(y.to_vec())), ecc_private_key: Some(Base64urlUInt(k.to_bytes().to_vec())), - }) + } } } @@ -1409,7 +1419,7 @@ mod tests { #[test] #[cfg(feature = "secp256k1")] fn secp256k1_generate() { - let _jwk = JWK::generate_secp256k1().unwrap(); + let _jwk = JWK::generate_secp256k1(); } #[test] @@ -1421,7 +1431,7 @@ mod tests { #[test] #[cfg(feature = "secp384r1")] fn p384_generate() { - let _jwk = JWK::generate_p384().unwrap(); + let _jwk = JWK::generate_p384(); } #[test] diff --git a/crates/multicodec/Cargo.toml b/crates/multicodec/Cargo.toml index e73f40ea2..4d1439287 100644 --- a/crates/multicodec/Cargo.toml +++ b/crates/multicodec/Cargo.toml @@ -8,8 +8,23 @@ description = "Implementation of the Multicodec specification for the ssi librar repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-multicodec/" +[features] +ed25519 = ["ed25519-dalek", "k256", "p256", "p384"] +k256 = ["dep:k256"] +p256 = ["dep:p256"] +p384 = ["dep:p384"] +bls12-381 = ["zkryptium"] + [dependencies] unsigned-varint = { version = "0.7.1", features = ["std"] } +thiserror.workspace = true + +# Codecs +ed25519-dalek = { workspace = true, optional = true } +k256 = { workspace = true, optional = true } +p256 = { workspace = true, optional = true } +p384 = { workspace = true, optional = true } +zkryptium = { version = "0.2.2", optional = true } [build-dependencies] csv = "1.2.2" diff --git a/crates/multicodec/src/codec/bls12_381.rs b/crates/multicodec/src/codec/bls12_381.rs new file mode 100644 index 000000000..ab204dbcd --- /dev/null +++ b/crates/multicodec/src/codec/bls12_381.rs @@ -0,0 +1,17 @@ +use std::borrow::Cow; + +pub use zkryptium::bbsplus::keys::BBSplusPublicKey; + +use crate::{Codec, Error, BLS12_381_G2_PUB}; + +impl Codec for BBSplusPublicKey { + const CODEC: u64 = BLS12_381_G2_PUB; + + fn from_bytes(bytes: &[u8]) -> Result { + BBSplusPublicKey::from_bytes(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Cow::Owned(self.to_bytes().to_vec()) + } +} diff --git a/crates/multicodec/src/codec/ed25519.rs b/crates/multicodec/src/codec/ed25519.rs new file mode 100644 index 000000000..1330bceb6 --- /dev/null +++ b/crates/multicodec/src/codec/ed25519.rs @@ -0,0 +1,16 @@ +use std::borrow::Cow; + +use crate::{Codec, Error, ED25519_PUB}; +use ed25519_dalek::VerifyingKey; + +impl Codec for VerifyingKey { + const CODEC: u64 = ED25519_PUB; + + fn from_bytes(bytes: &[u8]) -> Result { + Self::try_from(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Borrowed(self.as_bytes().as_slice()) + } +} diff --git a/crates/multicodec/src/codec/k256.rs b/crates/multicodec/src/codec/k256.rs new file mode 100644 index 000000000..08fea90bf --- /dev/null +++ b/crates/multicodec/src/codec/k256.rs @@ -0,0 +1,15 @@ +use std::borrow::Cow; + +use crate::{Codec, Error, SECP256K1_PUB}; + +impl Codec for k256::PublicKey { + const CODEC: u64 = SECP256K1_PUB; + + fn from_bytes(bytes: &[u8]) -> Result { + Self::from_sec1_bytes(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(self.to_sec1_bytes().into_vec()) + } +} diff --git a/crates/multicodec/src/codec/mod.rs b/crates/multicodec/src/codec/mod.rs new file mode 100644 index 000000000..5c90bec7a --- /dev/null +++ b/crates/multicodec/src/codec/mod.rs @@ -0,0 +1,46 @@ +use std::borrow::Cow; + +use crate::Error; + +pub trait Codec: Sized { + const CODEC: u64; + + fn to_bytes(&self) -> Cow<[u8]>; + + fn from_bytes(bytes: &[u8]) -> Result; +} + +pub trait MultiCodec: Sized { + fn to_codec_and_bytes(&self) -> (u64, Cow<[u8]>); + + fn from_codec_and_bytes(codec: u64, bytes: &[u8]) -> Result; +} + +impl MultiCodec for C { + fn to_codec_and_bytes(&self) -> (u64, Cow<[u8]>) { + (Self::CODEC, self.to_bytes()) + } + + fn from_codec_and_bytes(codec: u64, bytes: &[u8]) -> Result { + if codec == Self::CODEC { + Self::from_bytes(bytes) + } else { + Err(Error::UnexpectedCodec(codec)) + } + } +} + +#[cfg(feature = "ed25519")] +mod ed25519; + +#[cfg(feature = "k256")] +mod k256; + +#[cfg(feature = "p256")] +mod p256; + +#[cfg(feature = "p384")] +mod p384; + +#[cfg(feature = "bls12-381")] +mod bls12_381; diff --git a/crates/multicodec/src/codec/p256.rs b/crates/multicodec/src/codec/p256.rs new file mode 100644 index 000000000..8dd46509c --- /dev/null +++ b/crates/multicodec/src/codec/p256.rs @@ -0,0 +1,15 @@ +use std::borrow::Cow; + +use crate::{Codec, Error, P256_PUB}; + +impl Codec for p256::PublicKey { + const CODEC: u64 = P256_PUB; + + fn from_bytes(bytes: &[u8]) -> Result { + Self::from_sec1_bytes(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(self.to_sec1_bytes().into_vec()) + } +} diff --git a/crates/multicodec/src/codec/p384.rs b/crates/multicodec/src/codec/p384.rs new file mode 100644 index 000000000..566d149b7 --- /dev/null +++ b/crates/multicodec/src/codec/p384.rs @@ -0,0 +1,15 @@ +use std::borrow::Cow; + +use crate::{Codec, Error, P384_PUB}; + +impl Codec for p384::PublicKey { + const CODEC: u64 = P384_PUB; + + fn from_bytes(bytes: &[u8]) -> Result { + Self::from_sec1_bytes(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(self.to_sec1_bytes().into_vec()) + } +} \ No newline at end of file diff --git a/crates/multicodec/src/lib.rs b/crates/multicodec/src/lib.rs index 87194a6ce..b64adf68b 100644 --- a/crates/multicodec/src/lib.rs +++ b/crates/multicodec/src/lib.rs @@ -2,8 +2,23 @@ use std::ops::Deref; pub use unsigned_varint::decode::Error; +mod codec; +pub use codec::*; + include!(concat!(env!("OUT_DIR"), "/table.rs")); +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Varint(#[from] unsigned_varint::decode::Error), + + #[error("unexpected codec {0}")] + UnexpectedCodec(u64), + + #[error("invalid data")] + InvalidData, +} + /// Multi-encoded byte slice. pub struct MultiEncoded([u8]); @@ -56,6 +71,12 @@ impl MultiEncoded { pub fn as_bytes(&self) -> &[u8] { &self.0 } + + #[inline(always)] + pub fn decode(&self) -> Result { + let (codec, bytes) = self.parts(); + T::from_codec_and_bytes(codec, bytes) + } } pub struct MultiEncodedBuf(Vec); @@ -74,7 +95,7 @@ impl MultiEncodedBuf { Ok(Self(bytes)) } - pub fn encode(codec: u64, bytes: &[u8]) -> Self { + pub fn encode_bytes(codec: u64, bytes: &[u8]) -> Self { let mut codec_buffer = [0u8; 10]; let encoded_codec = unsigned_varint::encode::u64(codec, &mut codec_buffer); let mut result = Vec::with_capacity(encoded_codec.len() + bytes.len()); @@ -83,6 +104,11 @@ impl MultiEncodedBuf { Self(result) } + pub fn encode(value: &T) -> Self { + let (codec, bytes) = value.to_codec_and_bytes(); + Self::encode_bytes(codec, &bytes) + } + /// Creates a new multi-encoded slice from the given `bytes` without /// checking the codec. /// @@ -117,3 +143,9 @@ impl Deref for MultiEncodedBuf { self.as_multi_encoded() } } + +impl AsRef<[u8]> for MultiEncodedBuf { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} diff --git a/crates/verification-methods/Cargo.toml b/crates/verification-methods/Cargo.toml index 25b682cd9..c17aa3dab 100644 --- a/crates/verification-methods/Cargo.toml +++ b/crates/verification-methods/Cargo.toml @@ -15,7 +15,7 @@ default = ["ed25519", "rsa", "secp256k1", "secp256r1", "secp384r1", "tezos", "ei rsa = [] ## enable ed25519 keys -ed25519 = ["ed25519-dalek", "rand_core", "ssi-jws/ed25519"] +ed25519 = ["ed25519-dalek", "rand_core", "ssi-jws/ed25519", "ssi-multicodec/ed25519"] ## enable secp256k1 keys secp256k1 = ["k256", "sha2", "ssi-jws/secp256k1"] @@ -37,6 +37,8 @@ aleo = ["ssi-caips/aleo"] solana = [] +bls12-381 = ["ssi-multicodec/bls12-381", "zkryptium"] + [dependencies] ssi-core.workspace = true ssi-crypto.workspace = true @@ -71,6 +73,7 @@ sha3 = { workspace = true, optional = true } k256 = { workspace = true, optional = true, features = ["ecdsa", "sha256"] } p256 = { workspace = true, optional = true, features = ["ecdsa"] } p384 = { workspace = true, optional = true, features = ["ecdsa"] } +zkryptium = { workspace = true, optional = true } # rand_core_0_5 = { version = "0.5", optional = true, package = "rand_core" } rand_core = { version = "0.6.4", optional = true } \ No newline at end of file diff --git a/crates/verification-methods/core/src/lib.rs b/crates/verification-methods/core/src/lib.rs index cde360c94..f1b6e9f1d 100644 --- a/crates/verification-methods/core/src/lib.rs +++ b/crates/verification-methods/core/src/lib.rs @@ -176,7 +176,7 @@ pub trait SigningMethod: VerificationMethod { ) -> Result, MessageSignatureError>; } -pub trait MultiSigningMethod { +pub trait MultiSigningMethod: VerificationMethod { fn sign_bytes_multi( &self, secret: &S, @@ -196,12 +196,23 @@ impl MethodWithSecret { } } -impl, S> MessageSigner for MethodWithSecret { +impl, S> MessageSigner for MethodWithSecret { async fn sign(self, algorithm: A, message: &[u8]) -> Result, MessageSignatureError> { self.method.sign_bytes(&self.secret, algorithm, message) } } +impl, S> MultiMessageSigner for MethodWithSecret { + async fn sign_multi( + self, + algorithm: A, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + self.method + .sign_bytes_multi(&self.secret, algorithm, messages) + } +} + pub trait TypedVerificationMethod: VerificationMethod { fn expected_type() -> Option; diff --git a/crates/verification-methods/core/src/signature/signer/mod.rs b/crates/verification-methods/core/src/signature/signer/mod.rs index 73b9f7084..b036f2a3e 100644 --- a/crates/verification-methods/core/src/signature/signer/mod.rs +++ b/crates/verification-methods/core/src/signature/signer/mod.rs @@ -32,16 +32,6 @@ impl<'s, M: VerificationMethod, S: Signer> Signer for &'s S { } } -pub trait MultiSigner { - type MessageSigner: MultiMessageSigner; - - #[allow(async_fn_in_trait)] - async fn for_verification_method( - &self, - method: &M, - ) -> Result; -} - #[derive(Debug, thiserror::Error)] pub enum MessageSignatureError { #[error("0")] @@ -53,6 +43,9 @@ pub enum MessageSignatureError { #[error("invalid signer response")] InvalidResponse, + #[error("invalid public key")] + InvalidPublicKey, + #[error("invalid secret key")] InvalidSecretKey, diff --git a/crates/verification-methods/src/methods.rs b/crates/verification-methods/src/methods.rs index 026e0a212..0799ced6f 100644 --- a/crates/verification-methods/src/methods.rs +++ b/crates/verification-methods/src/methods.rs @@ -83,7 +83,7 @@ impl AnyMethod { Self::EcdsaSecp256r1VerificationKey2019(m) => Some(Cow::Owned(m.public_key_jwk())), Self::JsonWebKey2020(m) => Some(Cow::Borrowed(m.public_key_jwk())), #[cfg(feature = "ed25519")] - Self::Multikey(m) => Some(Cow::Owned(m.public_key_jwk())), + Self::Multikey(m) => m.public_key_jwk().map(Cow::Owned), #[cfg(all(feature = "tezos", feature = "ed25519"))] Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021(_) => None, #[cfg(all(feature = "tezos", feature = "secp256r1"))] diff --git a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs index b0a5fc7e9..ee7d55897 100644 --- a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs +++ b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs @@ -269,7 +269,7 @@ pub struct PublicKey { impl PublicKey { pub fn encode(decoded: ed25519_dalek::VerifyingKey) -> Self { let multi_encoded = - MultiEncodedBuf::encode(ssi_multicodec::ED25519_PUB, decoded.as_bytes()); + MultiEncodedBuf::encode_bytes(ssi_multicodec::ED25519_PUB, decoded.as_bytes()); Self { encoded: MultibaseBuf::encode(multibase::Base::Base58Btc, multi_encoded.as_bytes()), diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 09e2f6ac8..365ac603a 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -1,25 +1,22 @@ -use std::{borrow::Cow, hash::Hash, str::FromStr}; - use iref::{Iri, IriBuf, UriBuf}; -use rdf_types::{Interpretation, Vocabulary}; +use multibase::Base; +use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity, SignatureError}; use ssi_jwk::JWK; -use ssi_multicodec::MultiEncodedBuf; -use ssi_security::{Multibase, MultibaseBuf}; +use ssi_multicodec::{Codec, MultiCodec, MultiEncodedBuf}; +use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, SigningMethod, VerificationMethodSet, VerifyBytes, + MaybeJwkVerificationMethod, MessageSignatureError, SigningMethod, VerificationMethodSet, VerifyBytes }; use static_iref::iri; +use std::{borrow::Cow, hash::Hash}; use crate::{ ExpectedType, GenericVerificationMethod, InvalidVerificationMethod, TypedVerificationMethod, VerificationMethod, }; -// /// IRI of the Multikey type. -// pub const MULTIKEY_IRI: &Iri = iri!("https://w3id.org/security#Multikey"); - /// Multikey type name. pub const MULTIKEY_TYPE: &str = "Multikey"; @@ -56,7 +53,7 @@ pub struct Multikey { /// [MULTIBASE]: #[serde(rename = "publicKeyMultibase")] #[ld("sec:publicKeyMultibase")] - pub public_key: PublicKey, + pub public_key: MultibaseBuf, } #[derive(Debug, thiserror::Error)] @@ -66,20 +63,75 @@ pub enum InvalidPublicKey { #[error(transparent)] Multicodec(#[from] ssi_multicodec::Error), +} + +impl From for ProofValidationError { + fn from(_value: InvalidPublicKey) -> Self { + ProofValidationError::InvalidKey + } +} - #[error("unsupported key type: `{0}`")] - UnsupportedKeyType(String), +impl From for MessageSignatureError { + fn from(_value: InvalidPublicKey) -> Self { + MessageSignatureError::InvalidPublicKey + } +} - #[error(transparent)] - Jwk(#[from] ssi_jwk::Error), +impl From for SignatureError { + fn from(_value: InvalidPublicKey) -> Self { + SignatureError::InvalidPublicKey + } } impl Multikey { pub const NAME: &'static str = MULTIKEY_TYPE; pub const IRI: &'static Iri = iri!("https://w3id.org/security#Multikey"); - pub fn public_key_jwk(&self) -> JWK { - self.public_key.to_jwk() + pub fn public_key_jwk(&self) -> Option { + self.decode().ok()?.to_jwk() + } + + pub fn decode(&self) -> Result { + let pk_multi_encoded = MultiEncodedBuf::new(self.public_key.decode()?.1)?; + pk_multi_encoded.decode().map_err(Into::into) + } + + #[cfg(feature = "ed25519")] + pub fn generate_ed25519_key_pair( + id: IriBuf, + controller: UriBuf, + csprng: &mut (impl RngCore + CryptoRng), + ) -> (Self, ed25519_dalek::SigningKey) { + let key = ed25519_dalek::SigningKey::generate(csprng); + ( + Self::from_public_key(id, controller, &key.verifying_key()), + key, + ) + } + + pub fn from_public_key(id: IriBuf, controller: UriBuf, public_key: &K) -> Self { + Self { + id, + controller, + public_key: MultibaseBuf::encode(Base::Base58Btc, &MultiEncodedBuf::encode(public_key)), + } + } +} + +pub enum SecretKeyRef<'a> { + Ed25519(&'a ed25519_dalek::SigningKey), + Jwk(&'a JWK), +} + +impl<'a> From<&'a ed25519_dalek::SigningKey> for SecretKeyRef<'a> { + fn from(value: &'a ed25519_dalek::SigningKey) -> Self { + Self::Ed25519(value) + } +} + +impl<'a> From<&'a JWK> for SecretKeyRef<'a> { + fn from(value: &'a JWK) -> Self { + Self::Jwk(value) } } @@ -108,7 +160,9 @@ impl> VerifyBytes for Multikey { signing_bytes: &[u8], signature: &[u8], ) -> Result { - let key = self.public_key_jwk(); + let key = self + .public_key_jwk() + .ok_or(ProofValidationError::UnknownKey)?; Ok( ssi_jws::verify_bytes(algorithm.into(), signing_bytes, &key, signature) .map_err(|_| InvalidProof::Signature), @@ -116,6 +170,29 @@ impl> VerifyBytes for Multikey { } } +#[cfg(feature = "ed25519")] +impl VerifyBytes for Multikey { + fn verify_bytes( + &self, + _algorithm: ssi_jwk::algorithm::EdDSA, + signing_bytes: &[u8], + signature: &[u8], + ) -> Result { + #[allow(unreachable_patterns)] + match self.decode()? { + DecodedMultikey::Ed25519(public_key) => { + use ed25519_dalek::Verifier; + let signature = ed25519_dalek::Signature::try_from(signature) + .map_err(|_| ProofValidationError::InvalidSignature)?; + Ok(public_key + .verify(signing_bytes, &signature) + .map_err(|_| InvalidProof::Signature)) + } + _ => Err(ProofValidationError::InvalidKey), + } + } +} + impl SigningMethod for Multikey { fn sign_bytes( &self, @@ -156,9 +233,9 @@ impl TypedVerificationMethod for Multikey { } } -impl JwkVerificationMethod for Multikey { - fn to_jwk(&self) -> Cow { - Cow::Owned(self.public_key_jwk()) +impl MaybeJwkVerificationMethod for Multikey { + fn try_to_jwk(&self) -> Option> { + self.public_key_jwk().map(Cow::Owned) } } @@ -189,128 +266,70 @@ impl TryFrom for Multikey { } } -#[derive(Debug, Clone)] -pub struct PublicKey { - /// Multibase-encoded public key. - encoded: MultibaseBuf, +#[non_exhaustive] +pub enum DecodedMultikey { + #[cfg(feature = "ed25519")] + Ed25519(ed25519_dalek::VerifyingKey), - codec: u64, + #[cfg(feature = "secp256k1")] + Secp256k1(k256::PublicKey), - /// Decoded public key. - decoded: JWK, -} + #[cfg(feature = "secp256r1")] + P256(p256::PublicKey), -impl PublicKey { - pub fn decode(encoded: MultibaseBuf) -> Result { - let pk_multi_encoded = MultiEncodedBuf::new(encoded.decode()?.1)?; + #[cfg(feature = "secp384r1")] + P384(p384::PublicKey), + + #[cfg(feature = "bls12-381")] + Bls12_381(zkryptium::bbsplus::keys::BBSplusPublicKey), +} - let (codec, pk_data) = pk_multi_encoded.parts(); - let decoded = match codec { +impl DecodedMultikey { + pub fn to_jwk(&self) -> Option { + #[allow(unreachable_patterns)] + match self { #[cfg(feature = "ed25519")] - ssi_multicodec::ED25519_PUB => ssi_jwk::ed25519_parse(pk_data)?, + Self::Ed25519(key) => Some(key.clone().into()), #[cfg(feature = "secp256k1")] - ssi_multicodec::SECP256K1_PUB => ssi_jwk::secp256k1_parse(pk_data)?, + Self::Secp256k1(key) => Some(key.clone().into()), #[cfg(feature = "secp256r1")] - ssi_multicodec::P256_PUB => ssi_jwk::p256_parse(pk_data)?, + Self::P256(key) => Some(key.clone().into()), #[cfg(feature = "secp384r1")] - ssi_multicodec::P384_PUB => ssi_jwk::p384_parse(pk_data)?, - c => return Err(InvalidPublicKey::UnsupportedKeyType(format!("{c:#x}")))?, - }; - Ok(Self { - encoded, - codec, - decoded, - }) - } - - pub fn codec(&self) -> u64 { - self.codec - } - - pub fn encoded(&self) -> &Multibase { - &self.encoded - } - - pub fn to_jwk(&self) -> JWK { - self.decoded.clone() - } -} - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.encoded.serialize(serializer) - } -} - -impl<'a> Deserialize<'a> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'a>, - { - use serde::de::Error; - let encoded = MultibaseBuf::deserialize(deserializer)?; - Self::decode(encoded).map_err(D::Error::custom) - } -} - -impl FromStr for PublicKey { - type Err = InvalidPublicKey; - - fn from_str(s: &str) -> Result { - Self::decode(MultibaseBuf::new(s.to_owned())) - } -} - -impl PartialEq for PublicKey { - fn eq(&self, other: &Self) -> bool { - self.decoded.eq(&other.decoded) - } -} - -impl Eq for PublicKey {} - -impl Hash for PublicKey { - fn hash(&self, state: &mut H) { - self.encoded.hash(state) - } -} - -impl linked_data::LinkedDataResource for PublicKey -where - MultibaseBuf: linked_data::LinkedDataResource, -{ - fn interpretation( - &self, - vocabulary: &mut V, - interpretation: &mut I, - ) -> linked_data::ResourceInterpretation { - self.encoded.interpretation(vocabulary, interpretation) + Self::P384(key) => Some(key.clone().into()), + _ => None, + } } } -impl linked_data::LinkedDataSubject for PublicKey -where - MultibaseBuf: linked_data::LinkedDataSubject, -{ - fn visit_subject(&self, serializer: S) -> Result - where - S: linked_data::SubjectVisitor, - { - self.encoded.visit_subject(serializer) +impl MultiCodec for DecodedMultikey { + fn from_codec_and_bytes(codec: u64, bytes: &[u8]) -> Result { + match codec { + #[cfg(feature = "ed25519")] + ssi_multicodec::ED25519_PUB => Codec::from_bytes(bytes).map(Self::Ed25519), + #[cfg(feature = "secp256k1")] + ssi_multicodec::SECP256K1_PUB => Codec::from_bytes(bytes).map(Self::Secp256k1), + #[cfg(feature = "secp256r1")] + ssi_multicodec::P256_PUB => Codec::from_bytes(bytes).map(Self::P256), + #[cfg(feature = "secp384r1")] + ssi_multicodec::P384_PUB => Codec::from_bytes(bytes).map(Self::P384), + #[cfg(feature = "bls12-381")] + ssi_multicodec::BLS12_381_G2_PUB => Codec::from_bytes(bytes).map(Self::Bls12_381), + _ => Err(ssi_multicodec::Error::UnexpectedCodec(codec)), + } } -} -impl linked_data::LinkedDataPredicateObjects for PublicKey -where - MultibaseBuf: linked_data::LinkedDataPredicateObjects, -{ - fn visit_objects(&self, visitor: S) -> Result - where - S: linked_data::PredicateObjectsVisitor, - { - self.encoded.visit_objects(visitor) + fn to_codec_and_bytes(&self) -> (u64, Cow<[u8]>) { + match self { + #[cfg(feature = "ed25519")] + Self::Ed25519(k) => k.to_codec_and_bytes(), + #[cfg(feature = "secp256k1")] + Self::Secp256k1(k) => k.to_codec_and_bytes(), + #[cfg(feature = "secp256r1")] + Self::P256(k) => k.to_codec_and_bytes(), + #[cfg(feature = "secp384r1")] + Self::P384(k) => k.to_codec_and_bytes(), + #[cfg(feature = "bls12-381")] + Self::Bls12_381(k) => k.to_codec_and_bytes(), + } } } From cf741a63b79478b3cbee75b33d463375cbc8bee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 12 Jun 2024 11:00:40 +0200 Subject: [PATCH 06/34] Implement derived proof verification. --- crates/bbs/Cargo.toml | 1 + crates/bbs/src/lib.rs | 55 +++- .../core/src/suite/standard/transformation.rs | 4 + .../sd-primitives/src/canonicalize.rs | 22 +- .../data-integrity/sd-primitives/src/group.rs | 19 +- .../sd-primitives/src/select.rs | 19 +- .../suites/src/suites/w3c/bbs_2023/derive.rs | 290 ++++++++++++++++ .../suites/src/suites/w3c/bbs_2023/hashing.rs | 34 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 281 +--------------- .../src/suites/w3c/bbs_2023/signature/base.rs | 310 ++++++++++++++++++ .../suites/w3c/bbs_2023/signature/derived.rs | 204 ++++++++++++ .../{signature.rs => signature/mod.rs} | 138 +++----- .../w3c/bbs_2023/transformation/base.rs | 58 +--- .../w3c/bbs_2023/transformation/derived.rs | 51 ++- .../suites/w3c/bbs_2023/transformation/mod.rs | 105 +++--- .../src/suites/w3c/bbs_2023/verification.rs | 108 +++++- 16 files changed, 1188 insertions(+), 511 deletions(-) create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/derived.rs rename crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/{signature.rs => signature/mod.rs} (67%) diff --git a/crates/bbs/Cargo.toml b/crates/bbs/Cargo.toml index 71f210f61..affcccdcc 100644 --- a/crates/bbs/Cargo.toml +++ b/crates/bbs/Cargo.toml @@ -9,5 +9,6 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-bbs/" [dependencies] +ssi-claims-core.workspace = true ssi-verification-methods = { workspace = true, features = ["ed25519", "bls12-381"] } zkryptium = "0.2.2" \ No newline at end of file diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 20741c13a..ccecfb8d5 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -1,9 +1,62 @@ +use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; use ssi_verification_methods::{ multikey::DecodedMultikey, MessageSignatureError, MultiSigningMethod, Multikey, }; -use zkryptium::bbsplus::commitment::BlindFactor; pub use zkryptium::bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}; +use zkryptium::{ + bbsplus::{ciphersuites::Bls12381Sha256, commitment::BlindFactor}, + schemes::algorithms::BBSplus, +}; + +#[derive(Debug)] +pub struct ProofGenFailed; + +pub fn proof_gen( + pk: &BBSplusPublicKey, + signature: &[u8], + header: &[u8], + ph: Option<&[u8]>, + messages: &[Vec], + disclosed_indexes: &[usize], +) -> Result, ProofGenFailed> { + Ok( + zkryptium::schemes::generics::PoKSignature::>::proof_gen( + pk, + signature, + Some(header), + ph, + Some(messages), + Some(disclosed_indexes), + ) + .map_err(|_| ProofGenFailed)? + .to_bytes(), + ) +} + +pub fn proof_verify( + pk: &BBSplusPublicKey, + signature: &[u8], + header: &[u8], + ph: Option<&[u8]>, + disclosed_messages: &[Vec], + disclosed_indexes: &[usize], +) -> Result { + let signature = + zkryptium::schemes::generics::PoKSignature::>::from_bytes( + signature, + ) + .map_err(|_| ProofValidationError::InvalidSignature)?; + Ok(signature + .proof_verify( + pk, + Some(disclosed_messages), + Some(disclosed_indexes), + Some(header), + ph, + ) + .map_err(|_| InvalidProof::Signature)) +} pub enum Bbs { Baseline { header: [u8; 64], diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs index bcfa8f928..44c3ca90f 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs @@ -40,6 +40,10 @@ impl TransformationError { pub fn internal(e: impl ToString) -> Self { Self::Internal(e.to_string()) } + + pub fn json_ld_expansion(e: impl ToString) -> Self { + Self::JsonLdExpansion(e.to_string()) + } } impl From for SignatureError { diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs index 6e293827b..b07a789da 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs @@ -26,6 +26,23 @@ pub fn create_hmac_id_label_map_function( } } +/// See: +pub fn create_label_map_function( + label_map: &HashMap, +) -> impl '_ + Fn(&NormalizingSubstitution) -> HashMap { + |canonical_id_map| { + let mut bnode_id_map = HashMap::new(); + + for (key, value) in canonical_id_map { + if let Some(new_label) = label_map.get(value) { + bnode_id_map.insert(key.clone(), new_label.clone()); + } + } + + bnode_id_map + } +} + pub fn label_replacement_canonicalize_nquads( mut label_map_factory: impl FnMut(&NormalizingSubstitution) -> HashMap, quads: &[LexicalQuad], @@ -67,7 +84,10 @@ fn relabel_quad(label_map: &HashMap, quad: LexicalQuadRe fn relabel_id(label_map: &HashMap, id: Id<&Iri, &BlankId>) -> Id { match id { Id::Iri(i) => Id::Iri(i.to_owned()), - Id::Blank(b) => Id::Blank(label_map.get(b).unwrap().to_owned()), + Id::Blank(b) => Id::Blank(match label_map.get(b) { + Some(c) => c.clone(), + None => b.to_owned(), + }), } } diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs index a04e2163b..94e4b0265 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, collections::{BTreeMap, HashMap, HashSet}, hash::Hash, }; @@ -33,7 +34,7 @@ pub enum GroupError { pub async fn canonicalize_and_group( loader: &impl ssi_json_ld::Loader, label_map_factory_function: impl FnMut(&NormalizingSubstitution) -> HashMap, - group_definitions: HashMap>, + group_definitions: HashMap>, document: &T, ) -> Result, GroupError> where @@ -59,7 +60,7 @@ where select_canonical_nquads( loader, &skolemize.urn_scheme, - pointers, + &pointers, &label_map, &skolemized_compact_document, ) @@ -74,7 +75,7 @@ where let mut non_matching = BTreeMap::new(); let selected_quads: HashSet<_> = selection_result.quads.into_iter().collect(); - // let selected_deskolemized_quads = selection_result.deskolemized_quads; + let selected_deskolemized_quads = selection_result.deskolemized_quads; for (i, nq) in quads.iter().enumerate() { if selected_quads.contains(nq) { @@ -90,7 +91,7 @@ where Group { matching, non_matching, - // deskolemized_quads: selected_deskolemized_quads, + deskolemized_quads: selected_deskolemized_quads, }, ); } @@ -99,7 +100,7 @@ where groups, // skolemized_expanded_document, // skolemized_compact_document, - // deskolemized_quads, + deskolemized_quads, label_map, quads, }) @@ -109,7 +110,7 @@ pub struct CanonicalizedAndGrouped { pub groups: HashMap, // skolemized_expanded_document: json_ld::ExpandedDocument, // skolemized_compact_document: json_ld::syntax::Object, - // deskolemized_quads: Vec, + deskolemized_quads: Vec, pub label_map: HashMap, pub quads: Vec, } @@ -117,12 +118,12 @@ pub struct CanonicalizedAndGrouped { pub struct Group { pub matching: BTreeMap, pub non_matching: BTreeMap, - // pub deskolemized_quads: Vec, + pub deskolemized_quads: Vec, } #[cfg(test)] mod tests { - use std::collections::HashMap; + use std::{borrow::Cow, collections::HashMap}; use hmac::{Hmac, Mac}; use lazy_static::lazy_static; @@ -207,7 +208,7 @@ mod tests { let label_map_factory_function = create_hmac_id_label_map_function(&mut hmac); let mut group_definitions = HashMap::new(); - group_definitions.insert(Mandatory, MANDATORY_POINTERS.clone()); + group_definitions.insert(Mandatory, Cow::Borrowed(MANDATORY_POINTERS.as_slice())); let result = canonicalize_and_group( &loader, diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs index a5b4755c2..bdac7f954 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, HashMap}; -use ssi_json_ld::{syntax::Value, Print}; +use ssi_json_ld::syntax::Value; use rdf_types::{BlankId, BlankIdBuf, LexicalQuad}; use crate::{ @@ -27,19 +27,10 @@ impl From for SelectError { pub async fn select_canonical_nquads( loader: &impl ssi_json_ld::Loader, urn_scheme: &str, - pointers: Vec, + pointers: &[JsonPointerBuf], label_map: &HashMap, skolemized_compact_document: &ssi_json_ld::syntax::Object, ) -> Result { - eprintln!("select pointers"); - for pointer in &pointers { - eprintln!("{pointer}") - } - eprintln!( - "in {}", - Value::Object(skolemized_compact_document.clone()).pretty_print() - ); - let selection_document = select_json_ld(pointers, skolemized_compact_document)?; let deskolemized_quads = match selection_document.clone() { @@ -53,20 +44,20 @@ pub async fn select_canonical_nquads( Ok(CanonicalNquadsSelection { // selection_document, - // deskolemized_quads, + deskolemized_quads, quads, }) } pub struct CanonicalNquadsSelection { // selection_document: Option, - // deskolemized_quads: Vec, + pub deskolemized_quads: Vec, pub quads: Vec, } /// See: pub fn select_json_ld( - pointers: Vec, + pointers: &[JsonPointerBuf], document: &ssi_json_ld::syntax::Object, ) -> Result, DanglingJsonPointer> { if pointers.is_empty() { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs new file mode 100644 index 000000000..be417e042 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -0,0 +1,290 @@ +use std::{borrow::Cow, collections::HashMap, hash::Hash}; + +use hmac::{digest::KeyInit, Hmac}; +use k256::sha2::Sha256; +use rdf_types::{ + BlankIdBuf, Quad +}; +use serde::{Deserialize, Serialize}; +use ssi_bbs::{proof_gen, ProofGenFailed}; +use ssi_data_integrity_core::{DataIntegrity, Proof}; +use ssi_di_sd_primitives::{ + group::{canonicalize_and_group, GroupError}, + select::{select_json_ld, DanglingJsonPointer}, + JsonPointerBuf, +}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; +use ssi_rdf::LexicalInterpretation; +use ssi_verification_methods::{ + multikey::{self, DecodedMultikey}, + Multikey, +}; + +use crate::{bbs_2023::transformation::create_shuffled_id_label_map_function, Bbs2023}; + +use super::{ + Bbs2023Signature, Bbs2023SignatureDescription, InvalidBbs2023Signature, + UnsupportedBbs2023Signature, +}; + +#[derive(Debug, thiserror::Error)] +pub enum DeriveError { + #[error("JSON serialization failed: {0}")] + JsonSerialization(#[from] json_syntax::SerializeError), + + #[error("expected JSON object")] + ExpectedJsonObject, + + #[error("invalid public key")] + InvalidPublicKey, + + #[error("invalid base signature")] + InvalidBaseSignature, + + #[error(transparent)] + Group(#[from] GroupError), + + #[error("proof generation failed")] + ProofGen, + + #[error("dangling JSON pointer")] + DanglingJsonPointer, + + #[error("unsupported feature")] + UnsupportedFeature, +} + +impl From for DeriveError { + fn from(_value: InvalidBbs2023Signature) -> Self { + Self::InvalidBaseSignature + } +} + +impl From for DeriveError { + fn from(_value: multikey::InvalidPublicKey) -> Self { + Self::InvalidPublicKey + } +} + +impl From for DeriveError { + fn from(_value: ProofGenFailed) -> Self { + Self::ProofGen + } +} + +impl From for DeriveError { + fn from(_value: DanglingJsonPointer) -> Self { + Self::DanglingJsonPointer + } +} + +impl From for DeriveError { + fn from(_value: UnsupportedBbs2023Signature) -> Self { + Self::UnsupportedFeature + } +} + +pub struct DeriveOptions { + pub base_proof: Proof, + pub selective_pointers: Vec, + pub presentation_header: Option>, + pub feature_option: DerivedFeatureOption, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(tag = "featureOption")] +pub enum DerivedFeatureOption { + Baseline, + AnonymousHolderBinding { + holder_secret: String, + prover_blind: String, + }, + PseudonymIssuerPid { + verifier_id: String, + }, + PseudonymHiddenPid { + pid: String, + prover_blind: String, + verifier_id: String, + }, +} + +/// See: +pub async fn add_derived_proof( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + verification_method: &Multikey, + options: DeriveOptions, +) -> Result, DeriveError> +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let data = create_disclosure_data( + loader, + unsecured_document, + verification_method, + &options.base_proof.signature, + options.selective_pointers, + options.presentation_header.as_deref(), + &options.feature_option, + ) + .await?; + + let mut new_proof = options.base_proof; + new_proof.signature = Bbs2023Signature::encode_derived( + &data.bbs_proof, + &data.label_map, + &data.mandatory_indexes, + &data.selective_indexes, + options.presentation_header.as_deref(), + &options.feature_option, + )?; + + Ok(DataIntegrity::new(data.reveal_document, new_proof.into())) +} + +struct DisclosureData { + pub bbs_proof: Vec, + pub label_map: HashMap, + pub mandatory_indexes: Vec, + pub selective_indexes: Vec, + pub reveal_document: json_syntax::Object, +} + +/// Creates data to be used to generate a derived proof. +/// +/// See: +async fn create_disclosure_data( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + verification_method: &Multikey, + base_signature: &Bbs2023Signature, + selective_pointers: Vec, + presentation_header: Option<&[u8]>, + feature_option: &DerivedFeatureOption, +) -> Result +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let document = json_syntax::to_value(unsecured_document)? + .into_object() + .ok_or(DeriveError::ExpectedJsonObject)?; + + let decoded_base_proof = base_signature.decode_base()?; + + let mut hmac = Hmac::::new_from_slice(&decoded_base_proof.hmac_key).unwrap(); + + let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); + + let mut combined_pointers = decoded_base_proof.mandatory_pointers.clone(); + combined_pointers.extend(selective_pointers.iter().cloned()); + + let mut group_definitions = HashMap::new(); + group_definitions.insert( + Group::Mandatory, + Cow::Borrowed(decoded_base_proof.mandatory_pointers.as_slice()), + ); + group_definitions.insert( + Group::Selective, + Cow::Borrowed(selective_pointers.as_slice()), + ); + group_definitions.insert(Group::Combined, Cow::Borrowed(&combined_pointers)); + + let canonical = canonicalize_and_group( + loader, + label_map_factory_function, + group_definitions, + unsecured_document, + ) + .await?; + + let combined_group = canonical.groups.get(&Group::Combined).unwrap(); + let mandatory_group = canonical.groups.get(&Group::Mandatory).unwrap(); + let selective_group = canonical.groups.get(&Group::Selective).unwrap(); + + let mandatory_match = &mandatory_group.matching; + let combined_match = &combined_group.matching; + let combined_indexes: Vec<_> = combined_match.keys().copied().collect(); + let mut mandatory_indexes = Vec::with_capacity(mandatory_match.len()); + for i in mandatory_match.keys() { + let offset = combined_indexes.binary_search(i).unwrap(); + mandatory_indexes.push(offset); + } + + let selective_match = &selective_group.matching; + let mandatory_non_match = &mandatory_group.non_matching; + let non_mandatory_indexes: Vec<_> = mandatory_non_match.keys().copied().collect(); + let mut selective_indexes = Vec::with_capacity(mandatory_match.len()); + for i in selective_match.keys() { + let offset = non_mandatory_indexes.binary_search(i).unwrap(); + selective_indexes.push(offset); + } + + let bbs_messages: Vec<_> = mandatory_non_match + .values() + .map(|quad| format!("{quad} .\n").into_bytes()) + .collect(); + + let DecodedMultikey::Bls12_381(pk) = verification_method.decode()? else { + return Err(DeriveError::InvalidPublicKey); + }; + + let bbs_proof = match (&feature_option, &decoded_base_proof.description) { + (DerivedFeatureOption::Baseline, Bbs2023SignatureDescription::Baseline) => proof_gen( + &pk, + &decoded_base_proof.signature_bytes, + &decoded_base_proof.bbs_header, + presentation_header.as_deref(), + &bbs_messages, + &selective_indexes, + )?, + ( + DerivedFeatureOption::AnonymousHolderBinding { .. }, + Bbs2023SignatureDescription::AnonymousHolderBinding { .. }, + ) => return Err(DeriveError::UnsupportedFeature), + ( + DerivedFeatureOption::PseudonymIssuerPid { .. }, + Bbs2023SignatureDescription::PseudonymIssuerPid { .. }, + ) => return Err(DeriveError::UnsupportedFeature), + ( + DerivedFeatureOption::PseudonymHiddenPid { .. }, + Bbs2023SignatureDescription::PseudonymHiddenPid { .. }, + ) => return Err(DeriveError::UnsupportedFeature), + _ => return Err(DeriveError::InvalidBaseSignature), + }; + + let reveal_document = select_json_ld(&combined_pointers, &document)?.unwrap_or_default(); + + let normalizer = ssi_rdf::urdna2015::normalize( + combined_group + .deskolemized_quads + .iter() + .map(Quad::as_lexical_quad_ref), + ); + let canonical_id_map = normalizer.into_substitution(); + + let mut verifier_label_map = HashMap::new(); + for (input_label, canonical_label) in canonical_id_map { + verifier_label_map.insert( + canonical_label, + canonical.label_map.get(&input_label).unwrap().clone(), + ); + } + + Ok(DisclosureData { + bbs_proof, + label_map: verifier_label_map, + mandatory_indexes, + selective_indexes, + reveal_document, + }) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Group { + Mandatory, + Selective, + Combined, +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs index 205a49615..33dff3474 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs @@ -1,9 +1,10 @@ use k256::sha2::{Digest, Sha256}; +use rdf_types::LexicalQuad; use ssi_data_integrity_core::{ suite::standard::{HashingAlgorithm, HashingError, TransformedData}, ProofConfigurationRef, }; -use ssi_rdf::IntoNQuads; +use ssi_rdf::{urdna2015::NormalizingSubstitution, IntoNQuads}; use crate::Bbs2023; @@ -47,18 +48,45 @@ impl HashingAlgorithm for Bbs2023Hashing { mandatory_hash, })) } - Transformed::Derived(t) => Ok(HashData::Derived(t)), + Transformed::Derived(t) => Ok(HashData::Derived(create_verify_data2(t))), } } } +// See: https://www.w3.org/TR/vc-di-bbs/#createverifydata +fn create_verify_data2(t: TransformedDerived) -> DerivedHashData { + let proof_hash = t + .canonical_configuration + .iter() + .fold(Sha256::new(), |h, line| h.chain_update(line.as_bytes())) + .finalize() + .into(); + + DerivedHashData { + canonical_configuration: t.canonical_configuration, + quads: t.quads, + canonical_id_map: t.canonical_id_map, + proof_hash, + } +} + +#[derive(Clone)] pub enum HashData { Base(BaseHashData), - Derived(TransformedDerived), + Derived(DerivedHashData), } +#[derive(Clone)] pub struct BaseHashData { pub transformed_document: TransformedBase, pub proof_hash: [u8; 32], pub mandatory_hash: [u8; 32], } + +#[derive(Clone)] +pub struct DerivedHashData { + pub canonical_configuration: Vec, + pub quads: Vec, + pub canonical_id_map: NormalizingSubstitution, + pub proof_hash: [u8; 32], +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 1c087bdec..d9cb0fce6 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,18 +1,11 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: -use multibase::Base; -use serde::{Deserialize, Serialize}; -use ssi_bbs::BBSplusPublicKey; use ssi_data_integrity_core::{ - suite::{ - standard::TransformationError, ConfigurationAlgorithm, ConfigurationError, - InputProofOptions, - }, - Proof, ProofConfiguration, StandardCryptographicSuite, TypeRef, + suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, + ProofConfiguration, StandardCryptographicSuite, TypeRef, }; use ssi_di_sd_primitives::JsonPointerBuf; -use ssi_security::MultibaseBuf; use ssi_verification_methods::Multikey; pub(crate) mod transformation; @@ -22,7 +15,10 @@ mod hashing; pub use hashing::{Bbs2023Hashing, HashData}; mod signature; -pub use signature::Bbs2023SignatureAlgorithm; +pub use signature::*; + +mod derive; +pub use derive::*; mod verification; @@ -48,12 +44,8 @@ impl StandardCryptographicSuite for Bbs2023 { } } -pub enum Bbs2023InputOptions { - Base(Bbs2023BaseInputOptions), - Derived(Bbs2023DerivedInputOptions), -} - -pub struct Bbs2023BaseInputOptions { +#[derive(Clone)] +pub struct Bbs2023InputOptions { pub mandatory_pointers: Vec, pub feature_option: FeatureOption, @@ -63,16 +55,6 @@ pub struct Bbs2023BaseInputOptions { pub hmac_key: Option, } -pub struct Bbs2023DerivedInputOptions { - pub proof: Proof, - - pub selective_pointers: Vec, - - pub feature_option: DerivedFeatureOption, - - pub presentation_header: Option>, -} - #[derive(Debug, Default, Clone, Copy)] pub enum FeatureOption { #[default] @@ -82,24 +64,6 @@ pub enum FeatureOption { PseudonymHiddenPid, } -#[derive(Serialize, Deserialize)] -#[serde(tag = "featureOption")] -pub enum DerivedFeatureOption { - Baseline, - AnonymousHolderBinding { - holder_secret: String, - prover_blind: String, - }, - PseudonymIssuerPid { - verifier_id: String, - }, - PseudonymHiddenPid { - pid: String, - prover_blind: String, - verifier_id: String, - }, -} - pub type HmacKey = [u8; 32]; /// Base Proof Configuration. @@ -129,232 +93,3 @@ impl ConfigurationAlgorithm for Bbs2023Configuration { Ok((proof_configuration, signature_options)) } } - -pub struct DecodedBaseProof { - pub signature_bytes: Vec, - pub bbs_header: [u8; 64], - pub public_key: BBSplusPublicKey, - pub hmac_key: [u8; 32], - pub mandatory_pointers: Vec, - pub description: Bbs2023SignatureDescription, -} - -#[derive(Serialize)] -pub struct Bbs2023Signature { - pub proof_value: MultibaseBuf, -} - -impl Bbs2023Signature { - pub fn encode( - signature_bytes: &[u8], - bbs_header: [u8; 64], - public_key: &BBSplusPublicKey, - hmac_key: [u8; 32], - mandatory_pointers: &[JsonPointerBuf], - description: Bbs2023SignatureDescription, - ) -> Self { - let mut components = vec![ - serde_cbor::Value::Bytes(signature_bytes.to_vec()), - serde_cbor::Value::Bytes(bbs_header.to_vec()), - serde_cbor::Value::Bytes(public_key.to_bytes().to_vec()), - serde_cbor::Value::Bytes(hmac_key.to_vec()), - serde_cbor::Value::Array( - mandatory_pointers - .iter() - .map(|p| p.as_str().to_owned().into()) - .collect(), - ), - ]; - - let tag = match description { - Bbs2023SignatureDescription::Baseline => 0x02, - Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind } => { - components.push(match signer_blind { - Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), - None => serde_cbor::Value::Null, - }); - 0x04 - } - Bbs2023SignatureDescription::PseudonymIssuerPid { pid } => { - components.push(serde_cbor::Value::Bytes(pid.to_vec())); - 0x06 - } - Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind } => { - components.push(match signer_blind { - Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), - None => serde_cbor::Value::Null, - }); - 0x08 - } - }; - - let mut proof_value = vec![0xd9, 0x5d, tag]; - serde_cbor::to_writer(&mut proof_value, &components).unwrap(); - - Self { - proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), - } - } - - /// Parses the components of a bbs-2023 selective disclosure base proof - /// value. - /// - /// See: - pub fn decode_base_proof(&self) -> Result { - let (base, decoded_proof_value) = self - .proof_value - .decode() - .map_err(|_| TransformationError::InvalidInput)?; - - if base != Base::Base64Url || decoded_proof_value.len() < 3 { - return Err(TransformationError::InvalidInput); - } - - let header = [ - decoded_proof_value[0], - decoded_proof_value[1], - decoded_proof_value[2], - ]; - - let mut components = - serde_cbor::from_slice::>(&decoded_proof_value[3..]) - .map_err(|_| TransformationError::InvalidInput)? - .into_iter(); - - let Some(serde_cbor::Value::Bytes(signature_bytes)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let Some(serde_cbor::Value::Bytes(bbs_header)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let bbs_header: [u8; 64] = bbs_header - .try_into() - .map_err(|_| TransformationError::InvalidInput)?; - - let Some(serde_cbor::Value::Bytes(public_key)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let public_key = BBSplusPublicKey::from_bytes(&public_key) - .map_err(|_| TransformationError::InvalidInput)?; - - let Some(serde_cbor::Value::Bytes(hmac_key)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let hmac_key: [u8; 32] = hmac_key - .try_into() - .map_err(|_| TransformationError::InvalidInput)?; - - let Some(serde_cbor::Value::Array(mandatory_pointers_values)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let mut mandatory_pointers = Vec::with_capacity(mandatory_pointers_values.len()); - for value in mandatory_pointers_values { - let serde_cbor::Value::Bytes(bytes) = value else { - return Err(TransformationError::InvalidInput); - }; - - let pointer = - JsonPointerBuf::from_bytes(bytes).map_err(|_| TransformationError::InvalidInput)?; - - mandatory_pointers.push(pointer); - } - - match header { - [0xd9, 0x5d, 0x02] => { - // baseline - Ok(DecodedBaseProof { - signature_bytes, - bbs_header, - public_key, - hmac_key, - mandatory_pointers, - description: Bbs2023SignatureDescription::Baseline, - }) - } - [0xd9, 0x5d, 0x04] => { - // anonymous_holder_binding - let signer_blind = match components.next() { - Some(serde_cbor::Value::Bytes(signer_blind)) => Some( - signer_blind - .try_into() - .map_err(|_| TransformationError::InvalidInput)?, - ), - Some(serde_cbor::Value::Null) => None, - _ => return Err(TransformationError::InvalidInput), - }; - - Ok(DecodedBaseProof { - signature_bytes, - bbs_header, - public_key, - hmac_key, - mandatory_pointers, - description: Bbs2023SignatureDescription::AnonymousHolderBinding { - signer_blind, - }, - }) - } - [0xd9, 0x5d, 0x06] => { - // pseudonym_issuer_pid - let Some(serde_cbor::Value::Bytes(pid)) = components.next() else { - return Err(TransformationError::InvalidInput); - }; - - let pid: [u8; 32] = pid - .try_into() - .map_err(|_| TransformationError::InvalidInput)?; - - Ok(DecodedBaseProof { - signature_bytes, - bbs_header, - public_key, - hmac_key, - mandatory_pointers, - description: Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, - }) - } - [0xd9, 0x5d, 0x08] => { - // pseudonym_hidden_pid - let signer_blind = match components.next() { - Some(serde_cbor::Value::Bytes(signer_blind)) => Some( - signer_blind - .try_into() - .map_err(|_| TransformationError::InvalidInput)?, - ), - Some(serde_cbor::Value::Null) => None, - _ => return Err(TransformationError::InvalidInput), - }; - - Ok(DecodedBaseProof { - signature_bytes, - bbs_header, - public_key, - hmac_key, - mandatory_pointers, - description: Bbs2023SignatureDescription::AnonymousHolderBinding { - signer_blind, - }, - }) - } - _ => return Err(TransformationError::InvalidInput), - } - } -} - -impl AsRef for Bbs2023Signature { - fn as_ref(&self) -> &str { - self.proof_value.as_str() - } -} - -pub enum Bbs2023SignatureDescription { - Baseline, - AnonymousHolderBinding { signer_blind: Option<[u8; 32]> }, - PseudonymIssuerPid { pid: [u8; 32] }, - PseudonymHiddenPid { signer_blind: Option<[u8; 32]> }, -} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs new file mode 100644 index 000000000..0c2b42eb4 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -0,0 +1,310 @@ +use std::borrow::Cow; + +use multibase::Base; +use ssi_bbs::{BBSplusPublicKey, Bbs}; +use ssi_claims_core::SignatureError; +use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_rdf::IntoNQuads; +use ssi_security::MultibaseBuf; +use ssi_verification_methods::{multikey::DecodedMultikey, MultiMessageSigner, Multikey, Signer}; + +use crate::bbs_2023::{hashing::BaseHashData, FeatureOption}; + +use super::{Bbs2023Signature, Bbs2023SignatureDescription, InvalidBbs2023Signature}; + +pub async fn generate_base_proof( + verification_method: &Multikey, + signer: T, + hash_data: BaseHashData, +) -> Result +where + T: Signer, + T::MessageSigner: MultiMessageSigner, +{ + // See: + let DecodedMultikey::Bls12_381(public_key) = verification_method.decode()? else { + return Err(SignatureError::InvalidPublicKey); + }; + let feature_option = hash_data.transformed_document.options.feature_option; + let proof_hash = &hash_data.proof_hash; + let mandatory_pointers = &hash_data.transformed_document.options.mandatory_pointers; + let mandatory_hash = &hash_data.mandatory_hash; + let non_mandatory = &hash_data.transformed_document.non_mandatory; + let hmac_key = hash_data.transformed_document.hmac_key; + + let mut bbs_header = [0; 64]; + bbs_header[..32].copy_from_slice(proof_hash); + bbs_header[32..].copy_from_slice(mandatory_hash); + + let mut messages: Vec<_> = non_mandatory + .into_nquads_lines() + .into_iter() + .map(String::into_bytes) + .collect(); + + let message_signer = signer + .for_method(Cow::Borrowed(verification_method)) + .await + .ok_or(SignatureError::MissingSigner)?; + + let (algorithm, description) = match feature_option { + FeatureOption::Baseline => ( + Bbs::Baseline { header: bbs_header }, + Bbs2023SignatureDescription::Baseline, + ), + FeatureOption::AnonymousHolderBinding => ( + Bbs::Blind { + header: bbs_header, + commitment_with_proof: None, + signer_blind: None, + }, + Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind: None }, + ), + FeatureOption::PseudonymIssuerPid => { + // See: + let mut pid = [0u8; 32]; + getrandom::getrandom(&mut pid).map_err(SignatureError::other)?; + + messages.push(pid.to_vec()); + + ( + Bbs::Baseline { header: bbs_header }, + Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, + ) + } + FeatureOption::PseudonymHiddenPid => { + // See: + let commitment_with_proof = hash_data + .transformed_document + .options + .commitment_with_proof + .clone() + .ok_or_else(|| SignatureError::missing_required_option("commitment_with_proof"))?; + + ( + Bbs::Blind { + header: bbs_header, + commitment_with_proof: Some(commitment_with_proof), + signer_blind: None, + }, + Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind: None }, + ) + } + }; + + let signature = message_signer.sign_multi(algorithm, &messages).await?; + + Ok(Bbs2023Signature::encode_base( + &signature, + bbs_header, + &public_key, + hmac_key, + &mandatory_pointers, + description, + )) +} + +impl Bbs2023Signature { + pub fn encode_base( + signature_bytes: &[u8], + bbs_header: [u8; 64], + public_key: &BBSplusPublicKey, + hmac_key: [u8; 32], + mandatory_pointers: &[JsonPointerBuf], + description: Bbs2023SignatureDescription, + ) -> Self { + let mut components = vec![ + serde_cbor::Value::Bytes(signature_bytes.to_vec()), + serde_cbor::Value::Bytes(bbs_header.to_vec()), + serde_cbor::Value::Bytes(public_key.to_bytes().to_vec()), + serde_cbor::Value::Bytes(hmac_key.to_vec()), + serde_cbor::Value::Array( + mandatory_pointers + .iter() + .map(|p| p.as_str().to_owned().into()) + .collect(), + ), + ]; + + let tag = match description { + Bbs2023SignatureDescription::Baseline => 0x02, + Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind } => { + components.push(match signer_blind { + Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), + None => serde_cbor::Value::Null, + }); + 0x04 + } + Bbs2023SignatureDescription::PseudonymIssuerPid { pid } => { + components.push(serde_cbor::Value::Bytes(pid.to_vec())); + 0x06 + } + Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind } => { + components.push(match signer_blind { + Some(signer_blind) => serde_cbor::Value::Bytes(signer_blind.to_vec()), + None => serde_cbor::Value::Null, + }); + 0x08 + } + }; + + let mut proof_value = vec![0xd9, 0x5d, tag]; + serde_cbor::to_writer(&mut proof_value, &components).unwrap(); + + Self { + proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), + } + } + + /// Parses the components of a bbs-2023 selective disclosure base proof + /// value. + /// + /// See: + pub fn decode_base(&self) -> Result { + let (base, decoded_proof_value) = self + .proof_value + .decode() + .map_err(|_| InvalidBbs2023Signature)?; + + if base != Base::Base64Url || decoded_proof_value.len() < 3 { + return Err(InvalidBbs2023Signature); + } + + let header = [ + decoded_proof_value[0], + decoded_proof_value[1], + decoded_proof_value[2], + ]; + + let mut components = + serde_cbor::from_slice::>(&decoded_proof_value[3..]) + .map_err(|_| InvalidBbs2023Signature)? + .into_iter(); + + let Some(serde_cbor::Value::Bytes(signature_bytes)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let Some(serde_cbor::Value::Bytes(bbs_header)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let bbs_header: [u8; 64] = bbs_header.try_into().map_err(|_| InvalidBbs2023Signature)?; + + let Some(serde_cbor::Value::Bytes(public_key)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let public_key = + BBSplusPublicKey::from_bytes(&public_key).map_err(|_| InvalidBbs2023Signature)?; + + let Some(serde_cbor::Value::Bytes(hmac_key)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let hmac_key: [u8; 32] = hmac_key.try_into().map_err(|_| InvalidBbs2023Signature)?; + + let Some(serde_cbor::Value::Array(mandatory_pointers_values)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let mut mandatory_pointers = Vec::with_capacity(mandatory_pointers_values.len()); + for value in mandatory_pointers_values { + let serde_cbor::Value::Bytes(bytes) = value else { + return Err(InvalidBbs2023Signature); + }; + + let pointer = JsonPointerBuf::from_bytes(bytes).map_err(|_| InvalidBbs2023Signature)?; + + mandatory_pointers.push(pointer); + } + + match header { + [0xd9, 0x5d, 0x02] => { + // baseline + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::Baseline, + }) + } + [0xd9, 0x5d, 0x04] => { + // anonymous_holder_binding + let signer_blind = match components.next() { + Some(serde_cbor::Value::Bytes(signer_blind)) => Some( + signer_blind + .try_into() + .map_err(|_| InvalidBbs2023Signature)?, + ), + Some(serde_cbor::Value::Null) => None, + _ => return Err(InvalidBbs2023Signature), + }; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::AnonymousHolderBinding { + signer_blind, + }, + }) + } + [0xd9, 0x5d, 0x06] => { + // pseudonym_issuer_pid + let Some(serde_cbor::Value::Bytes(pid)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let pid: [u8; 32] = pid.try_into().map_err(|_| InvalidBbs2023Signature)?; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, + }) + } + [0xd9, 0x5d, 0x08] => { + // pseudonym_hidden_pid + let signer_blind = match components.next() { + Some(serde_cbor::Value::Bytes(signer_blind)) => Some( + signer_blind + .try_into() + .map_err(|_| InvalidBbs2023Signature)?, + ), + Some(serde_cbor::Value::Null) => None, + _ => return Err(InvalidBbs2023Signature), + }; + + Ok(DecodedBaseProof { + signature_bytes, + bbs_header, + public_key, + hmac_key, + mandatory_pointers, + description: Bbs2023SignatureDescription::AnonymousHolderBinding { + signer_blind, + }, + }) + } + _ => return Err(InvalidBbs2023Signature), + } + } +} + +#[derive(Clone)] +pub struct DecodedBaseProof { + pub signature_bytes: Vec, + pub bbs_header: [u8; 64], + pub public_key: BBSplusPublicKey, + pub hmac_key: [u8; 32], + pub mandatory_pointers: Vec, + pub description: Bbs2023SignatureDescription, +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/derived.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/derived.rs new file mode 100644 index 000000000..62b6aa6a7 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/derived.rs @@ -0,0 +1,204 @@ +use std::collections::{BTreeMap, HashMap}; + +use crate::bbs_2023::DerivedFeatureOption; +use multibase::Base; +use rdf_types::BlankIdBuf; +use ssi_security::MultibaseBuf; + +use super::{Bbs2023Signature, InvalidBbs2023Signature, UnsupportedBbs2023Signature}; + +impl Bbs2023Signature { + pub fn encode_derived( + bbs_proof: &[u8], + label_map: &HashMap, + mandatory_indexes: &[usize], + selective_indexes: &[usize], + presentation_header: Option<&[u8]>, + feature_option: &DerivedFeatureOption, + ) -> Result { + let comperessed_label_map = compress_label_map(label_map); + + let components = vec![ + serde_cbor::Value::Bytes(bbs_proof.to_vec()), + serde_cbor::Value::Map(comperessed_label_map), + serde_cbor::Value::Array( + mandatory_indexes + .iter() + .map(|i| serde_cbor::Value::Integer(*i as i128)) + .collect(), + ), + serde_cbor::Value::Array( + selective_indexes + .iter() + .map(|i| serde_cbor::Value::Integer(*i as i128)) + .collect(), + ), + match presentation_header { + Some(presentation_header) => serde_cbor::Value::Bytes(presentation_header.to_vec()), + None => serde_cbor::Value::Null, + }, + ]; + + let tag = match feature_option { + DerivedFeatureOption::Baseline => 0x03, + DerivedFeatureOption::AnonymousHolderBinding { .. } => { + return Err(UnsupportedBbs2023Signature) + } + DerivedFeatureOption::PseudonymIssuerPid { .. } => { + return Err(UnsupportedBbs2023Signature) + } + DerivedFeatureOption::PseudonymHiddenPid { .. } => { + return Err(UnsupportedBbs2023Signature) + } + }; + + let mut proof_value = vec![0xd9, 0x5d, tag]; + serde_cbor::to_writer(&mut proof_value, &components).unwrap(); + + Ok(Self { + proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), + }) + } + + pub fn decode_derived(&self) -> Result { + let (base, decoded_proof_value) = self + .proof_value + .decode() + .map_err(|_| InvalidBbs2023Signature)?; + + if base != Base::Base64Url || decoded_proof_value.len() < 3 { + return Err(InvalidBbs2023Signature); + } + + let header = [ + decoded_proof_value[0], + decoded_proof_value[1], + decoded_proof_value[2], + ]; + + let mut components = + serde_cbor::from_slice::>(&decoded_proof_value[3..]) + .map_err(|_| InvalidBbs2023Signature)? + .into_iter(); + + let Some(serde_cbor::Value::Bytes(signature_bytes)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let Some(serde_cbor::Value::Map(compressed_label_map)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let label_map = decompress_label_map(&compressed_label_map)?; + + let Some(serde_cbor::Value::Array(mandatory_indexes)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let mandatory_indexes = decode_indexes(mandatory_indexes)?; + + let Some(serde_cbor::Value::Array(selective_indexes)) = components.next() else { + return Err(InvalidBbs2023Signature); + }; + + let selective_indexes = decode_indexes(selective_indexes)?; + + let presentation_header = match components.next() { + Some(serde_cbor::Value::Null) => None, + Some(serde_cbor::Value::Bytes(presentation_header)) => Some(presentation_header), + _ => return Err(InvalidBbs2023Signature), + }; + + match header { + [0xd9, 0x5d, 0x03] => { + // baseline + Ok(DecodedDerivedProof { + bbs_proof: signature_bytes, + label_map, + mandatory_indexes, + selective_indexes, + presentation_header, + feature_option: DerivedFeatureOption::Baseline, + }) + } + [0xd9, 0x5d, 0x05] => { + // anonymous_holder_binding + Err(InvalidBbs2023Signature) + } + [0xd9, 0x5d, 0x07] => { + // pseudonym_issuer_pid + Err(InvalidBbs2023Signature) + } + [0xd9, 0x5d, 0x09] => { + // pseudonym_hidden_pid + Err(InvalidBbs2023Signature) + } + _ => Err(InvalidBbs2023Signature), + } + } +} + +pub struct DecodedDerivedProof { + pub bbs_proof: Vec, + pub label_map: HashMap, + pub mandatory_indexes: Vec, + pub selective_indexes: Vec, + pub presentation_header: Option>, + pub feature_option: DerivedFeatureOption, +} + +fn compress_label_map( + label_map: &HashMap, +) -> BTreeMap { + let mut map = BTreeMap::new(); + + for (k, v) in label_map { + let ki: i128 = k.strip_prefix("_:c14n").unwrap().parse().unwrap(); + let vi: i128 = v.strip_prefix("_:b").unwrap().parse().unwrap(); + map.insert( + serde_cbor::Value::Integer(ki), + serde_cbor::Value::Integer(vi), + ); + } + + map +} + +fn decompress_label_map( + compressed_label_map: &BTreeMap, +) -> Result, InvalidBbs2023Signature> { + let mut map = HashMap::new(); + + for (ki, vi) in compressed_label_map { + let serde_cbor::Value::Integer(ki) = ki else { + return Err(InvalidBbs2023Signature); + }; + + let serde_cbor::Value::Integer(vi) = vi else { + return Err(InvalidBbs2023Signature); + }; + + let k = BlankIdBuf::new(format!("_:c14n{ki}")).unwrap(); + let v = BlankIdBuf::new(format!("_:b{vi}")).unwrap(); + + map.insert(k, v); + } + + Ok(map) +} + +fn decode_indexes( + encoded_indexes: Vec, +) -> Result, InvalidBbs2023Signature> { + let mut indexes = Vec::with_capacity(encoded_indexes.len()); + + for v in encoded_indexes { + let serde_cbor::Value::Integer(i) = v else { + return Err(InvalidBbs2023Signature); + }; + + indexes.push(i.try_into().map_err(|_| InvalidBbs2023Signature)?) + } + + Ok(indexes) +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs similarity index 67% rename from crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs rename to crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs index 438849228..6353972fc 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs @@ -1,17 +1,47 @@ -use std::borrow::Cow; - +use crate::Bbs2023; +use serde::Serialize; use ssi_bbs::Bbs; -use ssi_claims_core::SignatureError; +use ssi_claims_core::{ProofValidationError, SignatureError}; use ssi_data_integrity_core::{ suite::standard::{SignatureAlgorithm, SignatureAndVerificationAlgorithm}, ProofConfigurationRef, }; -use ssi_rdf::IntoNQuads; -use ssi_verification_methods::{multikey::DecodedMultikey, MultiMessageSigner, Multikey, Signer}; +use ssi_security::MultibaseBuf; +use ssi_verification_methods::{MultiMessageSigner, Multikey, Signer}; + +use super::HashData; + +mod base; +mod derived; + +pub struct InvalidBbs2023Signature; + +impl From for ProofValidationError { + fn from(_value: InvalidBbs2023Signature) -> Self { + ProofValidationError::InvalidSignature + } +} + +pub struct UnsupportedBbs2023Signature; + +#[derive(Clone, Serialize)] +pub struct Bbs2023Signature { + pub proof_value: MultibaseBuf, +} -use crate::{bbs_2023::Bbs2023SignatureDescription, Bbs2023}; +impl AsRef for Bbs2023Signature { + fn as_ref(&self) -> &str { + self.proof_value.as_str() + } +} -use super::{Bbs2023Signature, FeatureOption, HashData}; +#[derive(Clone)] +pub enum Bbs2023SignatureDescription { + Baseline, + AnonymousHolderBinding { signer_blind: Option<[u8; 32]> }, + PseudonymIssuerPid { pid: [u8; 32] }, + PseudonymHiddenPid { signer_blind: Option<[u8; 32]> }, +} pub struct Bbs2023SignatureAlgorithm; @@ -32,93 +62,11 @@ where ) -> Result { match prepared_claims { HashData::Base(hash_data) => { - // See: - let DecodedMultikey::Bls12_381(public_key) = verification_method.decode()? else { - return Err(SignatureError::InvalidPublicKey); - }; - let feature_option = hash_data.transformed_document.options.feature_option; - let proof_hash = &hash_data.proof_hash; - let mandatory_pointers = &hash_data.transformed_document.options.mandatory_pointers; - let mandatory_hash = &hash_data.mandatory_hash; - let non_mandatory = &hash_data.transformed_document.non_mandatory; - let hmac_key = hash_data.transformed_document.hmac_key; - - let mut bbs_header = [0; 64]; - bbs_header[..32].copy_from_slice(proof_hash); - bbs_header[32..].copy_from_slice(mandatory_hash); - - let mut messages: Vec<_> = non_mandatory - .into_nquads_lines() - .into_iter() - .map(String::into_bytes) - .collect(); - - let message_signer = signer - .for_method(Cow::Borrowed(verification_method)) - .await - .ok_or(SignatureError::MissingSigner)?; - - let (algorithm, description) = match feature_option { - FeatureOption::Baseline => ( - Bbs::Baseline { header: bbs_header }, - Bbs2023SignatureDescription::Baseline, - ), - FeatureOption::AnonymousHolderBinding => ( - Bbs::Blind { - header: bbs_header, - commitment_with_proof: None, - signer_blind: None, - }, - Bbs2023SignatureDescription::AnonymousHolderBinding { signer_blind: None }, - ), - FeatureOption::PseudonymIssuerPid => { - // See: - let mut pid = [0u8; 32]; - getrandom::getrandom(&mut pid).map_err(SignatureError::other)?; - - messages.push(pid.to_vec()); - - ( - Bbs::Baseline { header: bbs_header }, - Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, - ) - } - FeatureOption::PseudonymHiddenPid => { - // See: - let commitment_with_proof = hash_data - .transformed_document - .options - .commitment_with_proof - .clone() - .ok_or_else(|| { - SignatureError::missing_required_option("commitment_with_proof") - })?; - - ( - Bbs::Blind { - header: bbs_header, - commitment_with_proof: Some(commitment_with_proof), - signer_blind: None, - }, - Bbs2023SignatureDescription::PseudonymHiddenPid { signer_blind: None }, - ) - } - }; - - let signature = message_signer.sign_multi(algorithm, &messages).await?; - - Ok(Bbs2023Signature::encode( - &signature, - bbs_header, - &public_key, - hmac_key, - &mandatory_pointers, - description, - )) - } - HashData::Derived(_) => { - todo!() + base::generate_base_proof(verification_method, signer, hash_data).await } + HashData::Derived(_) => Err(SignatureError::other( + "unable to sign derived claims without a base proof", + )), } } } @@ -136,7 +84,7 @@ mod tests { use crate::{ bbs_2023::{ - hashing::BaseHashData, transformation::TransformedBase, Bbs2023BaseInputOptions, + hashing::BaseHashData, transformation::TransformedBase, Bbs2023InputOptions, FeatureOption, HashData, HmacKey, }, Bbs2023, @@ -257,7 +205,7 @@ _:b5 \"2023\"^^( loader: &impl ssi_json_ld::Loader, unsecured_document: &T, canonical_configuration: Vec, - transform_options: Bbs2023BaseInputOptions, + transform_options: Bbs2023InputOptions, ) -> Result where T: JsonLdNodeObject + Expandable, @@ -40,7 +37,10 @@ where let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); let mut group_definitions = HashMap::new(); - group_definitions.insert(Mandatory, transform_options.mandatory_pointers.clone()); + group_definitions.insert( + Mandatory, + Cow::Borrowed(transform_options.mandatory_pointers.as_slice()), + ); let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); @@ -70,34 +70,9 @@ where #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Mandatory; -/// Creates a label map factory function that uses an HMAC to shuffle canonical -/// blank node identifiers. -/// -/// See: -pub fn create_shuffled_id_label_map_function( - hmac: &mut Hmac, -) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { - |canonical_map| { - let mut map = create_hmac_id_label_map_function(hmac)(canonical_map); - - let mut hmac_ids: Vec<_> = map.values().cloned().collect(); - hmac_ids.sort(); - - let mut bnode_keys: Vec<_> = map.keys().cloned().collect(); - bnode_keys.sort(); - - for key in bnode_keys { - let i = hmac_ids.binary_search(&map[&key]).unwrap(); - map.insert(key, BlankIdBuf::new(format!("_:b{}", i)).unwrap()); - } - - map - } -} - #[cfg(test)] mod tests { - use std::collections::HashMap; + use std::{borrow::Cow, collections::HashMap}; use hmac::{Hmac, Mac}; use k256::sha2::Sha256; @@ -111,10 +86,7 @@ mod tests { use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use crate::{ - bbs_2023::{ - Bbs2023BaseInputOptions, Bbs2023InputOptions, Bbs2023Transformation, FeatureOption, - HmacKey, - }, + bbs_2023::{Bbs2023InputOptions, Bbs2023Transformation, FeatureOption, HmacKey}, Bbs2023, }; @@ -192,7 +164,7 @@ mod tests { let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); let mut group_definitions = HashMap::new(); - group_definitions.insert(Mandatory, MANDATORY_POINTERS.clone()); + group_definitions.insert(Mandatory, Cow::Borrowed(MANDATORY_POINTERS.as_slice())); let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); @@ -262,12 +234,12 @@ mod tests { &context, &*CREDENTIAL, proof_configuration.borrowed(), - Some(Bbs2023InputOptions::Base(Bbs2023BaseInputOptions { + Some(Bbs2023InputOptions { mandatory_pointers: MANDATORY_POINTERS.clone(), feature_option: FeatureOption::Baseline, commitment_with_proof: None, hmac_key: Some(hmac_key), - })), + }), ) .await .unwrap() diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs index 0162ecbe4..eafcd6ae1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs @@ -1,18 +1,41 @@ -use ssi_data_integrity_core::Proof; -use ssi_di_sd_primitives::JsonPointerBuf; +use super::TransformedDerived; +use rdf_types::Quad; +use serde::Serialize; +use ssi_data_integrity_core::suite::standard::TransformationError; +use ssi_rdf::{LdEnvironment, LexicalInterpretation}; +use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument}; -use crate::{bbs_2023::DerivedFeatureOption, Bbs2023}; - -/// Creates data to be used to generate a derived proof. -/// -/// See: -pub async fn create_disclosure_data( +/// See: +pub async fn create_verify_data1( loader: &impl ssi_json_ld::Loader, unsecured_document: &T, - proof: &Proof, - selective_pointers: &[JsonPointerBuf], - presentation_header: Option<&[u8]>, - transform_options: &DerivedFeatureOption, -) { - // ... + canonical_configuration: Vec, +) -> Result +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let mut ld = LdEnvironment::default(); + + let expanded: ExpandedDocument = unsecured_document + .expand_with(&mut ld, loader) + .await + .map_err(TransformationError::json_ld_expansion)? + .into(); + + let quads = linked_data::to_lexical_quads_with( + &mut ld.vocabulary, + &mut ld.interpretation, + &expanded + )?; + + let canonical_id_map = + ssi_rdf::urdna2015::normalize(quads.iter().map(Quad::as_lexical_quad_ref)) + .into_substitution(); + + Ok(TransformedDerived { + canonical_configuration, + quads, + canonical_id_map, + }) } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs index a5ba774c0..aff572859 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs @@ -1,20 +1,17 @@ -use super::{Bbs2023BaseInputOptions, Bbs2023InputOptions, HmacKey}; +use super::{Bbs2023InputOptions, HmacKey}; use crate::Bbs2023; -use linked_data::{LinkedDataResource, LinkedDataSubject}; -use rdf_types::{ - interpretation::ReverseTermInterpretation, BlankIdBuf, InterpretationMut, LexicalQuad, - VocabularyMut, -}; +use hmac::Hmac; +use k256::sha2::Sha256; +use rdf_types::{BlankIdBuf, LexicalQuad}; +use serde::Serialize; use ssi_data_integrity_core::{ suite::standard::{TransformationAlgorithm, TransformationError, TypedTransformationAlgorithm}, ProofConfigurationRef, }; -use ssi_di_sd_primitives::{ - canonicalize::create_hmac_id_label_map_function, group::canonicalize_and_group, -}; -use ssi_rdf::LexicalInterpretation; -use ssi_json_ld::{Expandable, JsonLdNodeObject, ContextLoaderEnvironment, ExpandedDocument}; -use std::{fmt, hash::Hash}; +use ssi_di_sd_primitives::canonicalize::create_hmac_id_label_map_function; +use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument, ContextLoaderEnvironment}; +use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; +use std::collections::HashMap; mod base; mod derived; @@ -28,7 +25,7 @@ impl TransformationAlgorithm for Bbs2023Transformation { impl TypedTransformationAlgorithm for Bbs2023Transformation where C: ContextLoaderEnvironment, - T: JsonLdNodeObject + Expandable, + T: Serialize + JsonLdNodeObject + Expandable, T::Expanded: Into, { async fn transform( @@ -37,45 +34,52 @@ where proof_configuration: ProofConfigurationRef<'_, Bbs2023>, transformation_options: Option, ) -> Result { + let canonical_configuration = proof_configuration + .expand(context, unsecured_document) + .await + .map_err(TransformationError::ProofConfigurationExpansion)? + .nquads_lines(); + match transformation_options { - Some(Bbs2023InputOptions::Base(transform_options)) => { - let canonical_configuration = proof_configuration - .expand(context, unsecured_document) + Some(transform_options) => base::base_proof_transformation( + context.loader(), + unsecured_document, + canonical_configuration, + transform_options, + ) + .await + .map(Transformed::Base), + None => { + derived::create_verify_data1(context.loader(), unsecured_document, canonical_configuration) .await - .map_err(TransformationError::ProofConfigurationExpansion)? - .nquads_lines(); - - base::base_proof_transformation( - context.loader(), - unsecured_document, - canonical_configuration, - transform_options, - ) - .await - .map(Transformed::Base) + .map(Transformed::Derived) } - Some(Bbs2023InputOptions::Derived(transform_options)) => { - // https://www.w3.org/TR/vc-di-bbs/#add-derived-proof-bbs-2023 + } + } +} - derived::create_disclosure_data( - context.loader(), - unsecured_document, - &transform_options.proof, - &transform_options.selective_pointers, - transform_options.presentation_header.as_deref(), - &transform_options.feature_option, - ) - .await; +/// Creates a label map factory function that uses an HMAC to shuffle canonical +/// blank node identifiers. +/// +/// See: +pub fn create_shuffled_id_label_map_function( + hmac: &mut Hmac, +) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { + |canonical_map| { + let mut map = create_hmac_id_label_map_function(hmac)(canonical_map); - todo!() - } - None => { - // createVerifyData, step 1, 3, 4 - // canonicalize input document into N-Quads. - // Ok(Transformed::Derived(todo!())) - todo!() - } + let mut hmac_ids: Vec<_> = map.values().cloned().collect(); + hmac_ids.sort(); + + let mut bnode_keys: Vec<_> = map.keys().cloned().collect(); + bnode_keys.sort(); + + for key in bnode_keys { + let i = hmac_ids.binary_search(&map[&key]).unwrap(); + map.insert(key, BlankIdBuf::new(format!("_:b{}", i)).unwrap()); } + + map } } @@ -96,15 +100,18 @@ impl Transformed { /// Result of the Base Proof Transformation algorithm. /// /// See: +#[derive(Clone)] pub struct TransformedBase { - pub options: Bbs2023BaseInputOptions, + pub options: Bbs2023InputOptions, pub mandatory: Vec, pub non_mandatory: Vec, pub hmac_key: HmacKey, pub canonical_configuration: Vec, } +#[derive(Clone)] pub struct TransformedDerived { - pub proof_hash: String, - pub nquads: Vec, + pub canonical_configuration: Vec, + pub quads: Vec, + pub canonical_id_map: NormalizingSubstitution, } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 0ee703f60..5e6bda323 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -1,10 +1,15 @@ +use k256::sha2::{Digest, Sha256}; +use rdf_types::LexicalQuad; +use ssi_bbs::proof_verify; use ssi_claims_core::{ProofValidationError, ProofValidity}; use ssi_data_integrity_core::{suite::standard::VerificationAlgorithm, ProofRef}; -use ssi_verification_methods::Multikey; +use ssi_di_sd_primitives::canonicalize::{create_label_map_function, relabel_quads}; +use ssi_rdf::{urdna2015::NormalizingSubstitution, IntoNQuads}; +use ssi_verification_methods::{multikey::DecodedMultikey, Multikey}; use crate::Bbs2023; -use super::{Bbs2023SignatureAlgorithm, HashData}; +use super::{Bbs2023SignatureAlgorithm, DerivedFeatureOption, HashData}; impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { fn verify( @@ -13,17 +18,102 @@ impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { proof: ProofRef, ) -> Result { match prepared_claims { - HashData::Base(_) => { - todo!() - } + HashData::Base(_) => Err(ProofValidationError::other( + "selective disclosure base proof", + )), HashData::Derived(t) => { // Verify Derived Proof algorithm. // See: - // let proof_hash, mandatory_hash = createVerifyData, step 2, 5, 6, 7, 8, 9 - // let bbs_header = proof_hash + mandatory_hash - // let disclosed_messages = ... - todo!() + + let DecodedMultikey::Bls12_381(public_key) = method.decode()? else { + return Err(ProofValidationError::InvalidKey); + }; + + let data = + create_verify_data3(&t.proof_hash, &t.canonical_id_map, &t.quads, proof)?; + + let mut bbs_header = [0; 64]; + bbs_header[..32].copy_from_slice(data.proof_hash); + bbs_header[32..].copy_from_slice(&data.mandatory_hash); + + let disclosed_messages: Vec<_> = data + .non_mandatory + .iter() + .map(|quad| format!("{quad} .\n").into_bytes()) + .collect(); + + match data.feature_option { + DerivedFeatureOption::Baseline => proof_verify( + &public_key, + &data.base_signature, + &bbs_header, + data.presentation_header.as_deref(), + &disclosed_messages, + &data.selective_indexes, + ), + _ => Err(ProofValidationError::other("unimplemented feature option")), + } } } } } + +struct VerifyData<'a> { + base_signature: Vec, + proof_hash: &'a [u8; 32], + non_mandatory: Vec, + mandatory_hash: [u8; 32], + selective_indexes: Vec, + feature_option: DerivedFeatureOption, + presentation_header: Option>, +} + +/// See: +fn create_verify_data3<'a>( + proof_hash: &'a [u8; 32], + canonical_id_map: &NormalizingSubstitution, + quads: &[LexicalQuad], + proof: ProofRef, +) -> Result, ProofValidationError> { + let decoded_signature = proof.signature.decode_derived()?; + + let label_map_factory_function = create_label_map_function(&decoded_signature.label_map); + + let label_map = label_map_factory_function(canonical_id_map); + let mut canonical_quads = relabel_quads(&label_map, quads); + canonical_quads.sort_unstable(); + canonical_quads.dedup(); + + let mut mandatory = Vec::new(); + let mut non_mandatory = Vec::new(); + + for (i, quad) in canonical_quads.into_iter().enumerate() { + if decoded_signature + .mandatory_indexes + .binary_search(&i) + .is_ok() + { + mandatory.push(quad) + } else { + non_mandatory.push(quad) + } + } + + let mandatory_hash: [u8; 32] = mandatory + .iter() + .into_nquads_lines() + .into_iter() + .fold(Sha256::new(), |h, line| h.chain_update(line.as_bytes())) + .finalize() + .into(); + + Ok(VerifyData { + base_signature: decoded_signature.bbs_proof, + proof_hash, + non_mandatory, + mandatory_hash, + selective_indexes: decoded_signature.selective_indexes, + feature_option: decoded_signature.feature_option, + presentation_header: decoded_signature.presentation_header, + }) +} From 3d7277559f8f387fb541a842fb54c8bc2c04aa97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 12 Jun 2024 17:19:59 +0200 Subject: [PATCH 07/34] Testing bbs-2023 deriving/verification --- crates/bbs/Cargo.toml | 2 +- .../crates/data-integrity/core/src/de.rs | 36 +++- .../data-integrity/core/src/proof/de/mod.rs | 7 + .../data-integrity/core/src/proof/mod.rs | 1 + .../data-integrity/sd-primitives/src/group.rs | 5 +- .../crates/data-integrity/suites/Cargo.toml | 4 +- .../suites/src/suites/w3c/bbs_2023/derive.rs | 158 +++++++++++++++++- .../suites/src/suites/w3c/bbs_2023/mod.rs | 19 ++- .../src/suites/w3c/bbs_2023/signature/base.rs | 5 +- .../src/suites/w3c/bbs_2023/signature/mod.rs | 7 +- .../tests/signed-base-document.jsonld | 57 +++++++ .../tests/signed-derived-document.jsonld | 47 ++++++ .../tests/unsigned-reveal-document.jsonld | 39 +++++ .../src/suites/w3c/bbs_2023/verification.rs | 42 +++++ crates/json-ld/src/lib.rs | 4 +- crates/verification-methods/core/src/lib.rs | 22 ++- .../src/methods/w3c/multikey.rs | 5 +- 17 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-base-document.jsonld create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-derived-document.jsonld create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-reveal-document.jsonld diff --git a/crates/bbs/Cargo.toml b/crates/bbs/Cargo.toml index affcccdcc..b08cb8f81 100644 --- a/crates/bbs/Cargo.toml +++ b/crates/bbs/Cargo.toml @@ -10,5 +10,5 @@ documentation = "https://docs.rs/ssi-bbs/" [dependencies] ssi-claims-core.workspace = true -ssi-verification-methods = { workspace = true, features = ["ed25519", "bls12-381"] } +ssi-verification-methods = { workspace = true, features = ["bls12-381"] } zkryptium = "0.2.2" \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/core/src/de.rs b/crates/claims/crates/data-integrity/core/src/de.rs index bfad2c87e..4b32483fc 100644 --- a/crates/claims/crates/data-integrity/core/src/de.rs +++ b/crates/claims/crates/data-integrity/core/src/de.rs @@ -33,22 +33,41 @@ where where A: serde::de::MapAccess<'de>, { - let mut proofs = Proofs::default(); - let claims = T::deserialize(Deserializer { + let mut deserializer = Deserializer { inner: map, - proofs: &mut proofs, - })?; + proofs: Proofs::default(), + de: PhantomData, + }; + + let claims = T::deserialize(&mut deserializer)?; + let proofs = deserializer.into_proofs()?; Ok(DataIntegrity { claims, proofs }) } } -struct Deserializer<'a, D, S: CryptographicSuite> { +struct Deserializer<'de, D, S: CryptographicSuite> { inner: D, - proofs: &'a mut Proofs, + proofs: Proofs, + de: PhantomData<&'de ()>, +} + +impl<'de, D: serde::de::MapAccess<'de>, S: DeserializeCryptographicSuite<'de>> + Deserializer<'de, D, S> +{ + fn into_proofs(mut self) -> Result, D::Error> { + while let Some(key) = self.inner.next_key::()? { + if key == "proof" { + let proofs: Proofs = self.inner.next_value()?; + self.proofs.extend(proofs) + } + } + + Ok(self.proofs) + } } -impl<'a, 'de, D: serde::de::MapAccess<'de>, S> serde::de::MapAccess<'de> for Deserializer<'a, D, S> +impl<'de, D: serde::de::MapAccess<'de>, S> serde::de::MapAccess<'de> for Deserializer<'de, D, S> where S: DeserializeCryptographicSuite<'de>, { @@ -82,7 +101,8 @@ where } } -impl<'a, 'de, D: serde::de::MapAccess<'de>, S> serde::Deserializer<'de> for Deserializer<'a, D, S> +impl<'a, 'de, D: serde::de::MapAccess<'de>, S> serde::Deserializer<'de> + for &'a mut Deserializer<'de, D, S> where S: DeserializeCryptographicSuite<'de>, { diff --git a/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs index 80d994e95..29b8f1f99 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs @@ -131,6 +131,8 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro let mut cryptosuite = None; let mut data_integrity_proof = false; + eprintln!("start deserializing proof type"); + while let Some(key) = map.next_key::()? { match key { TypeField::Type => { @@ -139,6 +141,7 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro if name == "DataIntegrityProof" { match cryptosuite.take() { Some(c) => { + eprintln!("success"); return Proof::::deserialize_with_type( Type::DataIntegrityProof(c), ReplayMap::new(keep, map), @@ -149,6 +152,7 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro } } } else { + eprintln!("success"); return Proof::::deserialize_with_type( Type::Other(name), ReplayMap::new(keep, map), @@ -158,6 +162,7 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro TypeField::Cryptosuite => { let name = map.next_value::()?; if data_integrity_proof { + eprintln!("success"); return Proof::::deserialize_with_type( Type::DataIntegrityProof(name), ReplayMap::new(keep, map), @@ -172,6 +177,8 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro } } + eprintln!("failure"); + Err(serde::de::Error::custom("missing type")) } } diff --git a/crates/claims/crates/data-integrity/core/src/proof/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/mod.rs index 18a8e1ae6..f2b50ac2f 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/mod.rs @@ -392,6 +392,7 @@ impl<'de, S: DeserializeCryptographicSuite<'de>> Deserialize<'de> for Proofs where D: serde::Deserializer<'de>, { + eprintln!("start deserializing proofs"); match OneOrMany::>::deserialize(deserializer)? { OneOrMany::One(proof) => Ok(Self(vec![proof])), OneOrMany::Many(proofs) => Ok(Self(proofs)), diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs index 94e4b0265..125e39da1 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs @@ -79,7 +79,6 @@ where for (i, nq) in quads.iter().enumerate() { if selected_quads.contains(nq) { - eprintln!("matching {i} => {nq} ."); matching.insert(i, nq.clone()); } else { non_matching.insert(i, nq.clone()); @@ -100,7 +99,7 @@ where groups, // skolemized_expanded_document, // skolemized_compact_document, - deskolemized_quads, + // deskolemized_quads, label_map, quads, }) @@ -110,7 +109,7 @@ pub struct CanonicalizedAndGrouped { pub groups: HashMap, // skolemized_expanded_document: json_ld::ExpandedDocument, // skolemized_compact_document: json_ld::syntax::Object, - deskolemized_quads: Vec, + // deskolemized_quads: Vec, pub label_map: HashMap, pub quads: Vec, } diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index dd3268284..74c5a9dcb 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -143,7 +143,7 @@ p256 = { workspace = true, optional = true, features = ["ecdsa"] } p384 = { workspace = true, optional = true, features = ["ecdsa"] } sha2 = { workspace = true, optional = true } -# only needed by non LD suites and EIP-712. +# only needed by non LD suites, EIP-712 and BBS. json-syntax = { workspace = true, features = ["canonicalize"] } serde_json = { workspace = true, optional = true } serde_jcs = { workspace = true, optional = true } @@ -164,6 +164,8 @@ hashbrown = "0.13.0" iref = { workspace = true, features = ["hashbrown"] } ssi-vc.workspace = true nquads-syntax.workspace = true +serde_json.workspace = true +json-syntax = { workspace = true, features = ["serde_json"] } [package.metadata.docs.rs] all-features = true diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index be417e042..16a0623f3 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -216,10 +216,11 @@ where let selective_match = &selective_group.matching; let mandatory_non_match = &mandatory_group.non_matching; let non_mandatory_indexes: Vec<_> = mandatory_non_match.keys().copied().collect(); - let mut selective_indexes = Vec::with_capacity(mandatory_match.len()); + let mut selective_indexes = Vec::with_capacity(mandatory_non_match.len()); for i in selective_match.keys() { - let offset = non_mandatory_indexes.binary_search(i).unwrap(); - selective_indexes.push(offset); + if let Ok(offset) = non_mandatory_indexes.binary_search(i) { + selective_indexes.push(offset); + } } let bbs_messages: Vec<_> = mandatory_non_match @@ -288,3 +289,154 @@ enum Group { Selective, Combined, } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use json_syntax::{Parse, UnorderedPartialEq}; + use lazy_static::lazy_static; + use rdf_types::BlankIdBuf; + use ssi_bbs::BBSplusPublicKey; + use ssi_data_integrity_core::DataIntegrity; + use ssi_di_sd_primitives::{select::select_json_ld, JsonPointerBuf}; + use ssi_json_ld::JsonLdEnvironment; + use ssi_vc::v2::JsonCredential; + use ssi_verification_methods::Multikey; + use static_iref::{iri, uri}; + + use crate::{bbs_2023::Bbs2023Signature, Bbs2023}; + + use super::{create_disclosure_data, DerivedFeatureOption, DisclosureData}; + + const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; + const PRESENTATION_HEADER_HEX: &str = "113377aa"; + + // TODO this can't be used because the BBS API (based on zkryptium) does not + // allow providing a seed. + // const PSEUDO_RANDOM_SEED_HEX: &str = "332e313431353932363533353839373933323338343632363433333833323739"; + + const BBS_PROOF_HEX: &str = "85b72d74b55aae76e4f8e986387352f6d3e13f19387f5935a9f34c59aa3af77885501ef1dba67576bd24e6dab1b1c5b3891671112c26982c441d4f352e1bc8f5451127fbda2ad240d9a5d6933f455db741cc3e79d3281bc5b611118b363461f2b6a5ecdd423f6b76711680824665f50eec1f5cbaf219ee90e66ceac575146d1a8935f770be6d29a376b00e4e39a4fa7755ecf4eb42aa3babfd6e48bb23e91081f08d0d259b4029683d01c25378be3c61213a097750b8ce2a3c0915061a547405b3ce587d1d8299269fad29103509b3e53067f7b6078e9dc66a5112192aede3662e6dac5876d945fd05863fb249b0fca02e10ab5173650ef665e92c3ea72eaba94fca860cd6c639538e5156f8cbc3b4d222f7a11f837bb9e76ba54d58c1b4ac834ef338a3db4bf645b4622153c897f477255f40e4fcc7919348ae5bf9032a9f7c0876e47a6666ca9f178673ac7a41b864480d8e84c6655cd2f0e1866dedc467590a2ba76c28cb41f3d5582e0773b737914b8353fea4df918a022aa5aa92f490f0b3c2edf4a4d5538b8d07aa2530f118863e654eeaaac69c2c020509c24294c13bda721c73b8610bbce7e7030d1710dd5148731a5026c741d1da9e0693d32b90d09bb58a8e4a295a32fb27f654a03c31c56e6c3afb1aa3f2fa240f5095f31fe8b95f8179bc4408cf96713f3aec6a06409a6f1486a99d9923befdb274d3e04f6faa9bf316ce9a2c4f5e1bc6db031593323b"; + + lazy_static! { + static ref MANDATORY_POINTERS: [JsonPointerBuf; 5] = [ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; + static ref SELECTIVE_POINTERS: [JsonPointerBuf; 2] = [ + "/credentialSubject/boards/0".parse().unwrap(), + "/credentialSubject/boards/1".parse().unwrap() + ]; + static ref SIGNED_BASE_DOCUMENT: json_syntax::Object = + json_syntax::Value::parse_str(include_str!("tests/signed-base-document.jsonld")) + .unwrap() + .0 + .into_object() + .unwrap(); + static ref UNSIGNED_REVEAL_DOCUMENT: json_syntax::Object = + json_syntax::Value::parse_str(include_str!("tests/unsigned-reveal-document.jsonld")) + .unwrap() + .0 + .into_object() + .unwrap(); + static ref PUBLIC_KEY: BBSplusPublicKey = + BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); + static ref PRESENTATION_HEADER: Vec = hex::decode(PRESENTATION_HEADER_HEX).unwrap(); + static ref LABEL_MAP: HashMap = [ + ("_:c14n0".parse().unwrap(), "_:b2".parse().unwrap()), + ("_:c14n1".parse().unwrap(), "_:b4".parse().unwrap()), + ("_:c14n2".parse().unwrap(), "_:b3".parse().unwrap()), + ("_:c14n3".parse().unwrap(), "_:b7".parse().unwrap()), + ("_:c14n4".parse().unwrap(), "_:b6".parse().unwrap()), + ("_:c14n5".parse().unwrap(), "_:b0".parse().unwrap()) + ] + .into_iter() + .collect(); + static ref BBS_PROOF: Vec = hex::decode(BBS_PROOF_HEX).unwrap(); + } + + #[test] + fn reveal_document() { + let mut combined_pointers = MANDATORY_POINTERS.to_vec(); + combined_pointers.extend(SELECTIVE_POINTERS.iter().cloned()); + + let mut document = SIGNED_BASE_DOCUMENT.clone(); + document.remove("proof"); + + let reveal_document = select_json_ld(&combined_pointers, &document) + .unwrap() + .unwrap_or_default(); + + assert!(reveal_document.unordered_eq(&UNSIGNED_REVEAL_DOCUMENT)) + } + + #[async_std::test] + async fn disclosure_data() { + let signed_base: DataIntegrity = + json_syntax::from_value(json_syntax::Value::Object(SIGNED_BASE_DOCUMENT.clone())) + .unwrap(); + + let verification_method = Multikey::from_public_key( + iri!("did:method:id").to_owned(), + uri!("did:method:controller").to_owned(), + &*PUBLIC_KEY, + ); + + let mut context = JsonLdEnvironment::default(); + + eprintln!( + "signature = {}", + signed_base.proofs.first().unwrap().signature.proof_value + ); + + let data = create_disclosure_data( + &mut context, + &signed_base.claims, + &verification_method, + &signed_base.proofs.first().unwrap().signature, + SELECTIVE_POINTERS.to_vec(), + Some(&PRESENTATION_HEADER), + &DerivedFeatureOption::Baseline, + ) + .await + .unwrap(); + + assert_eq!( + data.mandatory_indexes, + [0, 1, 2, 5, 6, 8, 9, 10, 14, 15, 16, 17, 18, 19] + ); + assert_eq!(data.selective_indexes, [3, 4, 5, 8, 9, 10]); + + // TODO this can't be tested because the BBS API (based on zkryptium) + // does not allow providing a seed. + // assert_eq!(data.bbs_proof, *BBS_PROOF); + + assert_eq!(data.label_map, *LABEL_MAP); + } + + #[test] + fn encode_derived() { + let data = DisclosureData { + bbs_proof: BBS_PROOF.clone(), + label_map: LABEL_MAP.clone(), + mandatory_indexes: [0, 1, 2, 5, 6, 8, 9, 10, 14, 15, 16, 17, 18, 19].to_vec(), + selective_indexes: [3, 4, 5, 8, 9, 10].to_vec(), + reveal_document: UNSIGNED_REVEAL_DOCUMENT.clone(), + }; + + let signature = Bbs2023Signature::encode_derived( + &data.bbs_proof, + &data.label_map, + &data.mandatory_indexes, + &data.selective_indexes, + Some(&PRESENTATION_HEADER), + &DerivedFeatureOption::Baseline, + ) + .unwrap(); + + assert_eq!(signature.proof_value.as_str(), "u2V0DhVkCEIW3LXS1Wq525PjphjhzUvbT4T8ZOH9ZNanzTFmqOvd4hVAe8dumdXa9JObasbHFs4kWcREsJpgsRB1PNS4byPVFESf72irSQNml1pM_RV23Qcw-edMoG8W2ERGLNjRh8ral7N1CP2t2cRaAgkZl9Q7sH1y68hnukOZs6sV1FG0aiTX3cL5tKaN2sA5OOaT6d1Xs9OtCqjur_W5IuyPpEIHwjQ0lm0ApaD0BwlN4vjxhIToJd1C4zio8CRUGGlR0BbPOWH0dgpkmn60pEDUJs-UwZ_e2B46dxmpREhkq7eNmLm2sWHbZRf0Fhj-ySbD8oC4Qq1FzZQ72ZeksPqcuq6lPyoYM1sY5U45RVvjLw7TSIvehH4N7uedrpU1YwbSsg07zOKPbS_ZFtGIhU8iX9HclX0Dk_MeRk0iuW_kDKp98CHbkemZmyp8XhnOsekG4ZEgNjoTGZVzS8OGGbe3EZ1kKK6dsKMtB89VYLgdztzeRS4NT_qTfkYoCKqWqkvSQ8LPC7fSk1VOLjQeqJTDxGIY-ZU7qqsacLAIFCcJClME72nIcc7hhC7zn5wMNFxDdUUhzGlAmx0HR2p4Gk9MrkNCbtYqOSilaMvsn9lSgPDHFbmw6-xqj8vokD1CV8x_ouV-BebxECM-WcT867GoGQJpvFIapnZkjvv2ydNPgT2-qm_MWzposT14bxtsDFZMyO6YAAgEEAgMDBwQGBQCOAAECBQYICQoODxAREhOGAwQFCAkKRBEzd6o"); + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index d9cb0fce6..f6b202954 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,11 +1,13 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: +use ssi_claims_core::DefaultEnvironment; use ssi_data_integrity_core::{ suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, - ProofConfiguration, StandardCryptographicSuite, TypeRef, + ProofConfiguration, StandardCryptographicSuite, Type, TypeRef, UnsupportedProofSuite, }; use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_json_ld::JsonLdEnvironment; use ssi_verification_methods::Multikey; pub(crate) mod transformation; @@ -44,6 +46,21 @@ impl StandardCryptographicSuite for Bbs2023 { } } +impl TryFrom for Bbs2023 { + type Error = UnsupportedProofSuite; + + fn try_from(value: Type) -> Result { + match value { + Type::DataIntegrityProof(c) if c == "bbs-2023" => Ok(Self), + ty => Err(UnsupportedProofSuite::Compact(ty)), + } + } +} + +impl DefaultEnvironment for Bbs2023 { + type Environment = JsonLdEnvironment; +} + #[derive(Clone)] pub struct Bbs2023InputOptions { pub mandatory_pointers: Vec, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index 0c2b42eb4..d603e406e 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -209,12 +209,13 @@ impl Bbs2023Signature { }; let mut mandatory_pointers = Vec::with_capacity(mandatory_pointers_values.len()); + for value in mandatory_pointers_values { - let serde_cbor::Value::Bytes(bytes) = value else { + let serde_cbor::Value::Text(text) = value else { return Err(InvalidBbs2023Signature); }; - let pointer = JsonPointerBuf::from_bytes(bytes).map_err(|_| InvalidBbs2023Signature)?; + let pointer = JsonPointerBuf::new(text).map_err(|_| InvalidBbs2023Signature)?; mandatory_pointers.push(pointer); } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs index 6353972fc..df932f057 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs @@ -1,5 +1,5 @@ use crate::Bbs2023; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use ssi_bbs::Bbs; use ssi_claims_core::{ProofValidationError, SignatureError}; use ssi_data_integrity_core::{ @@ -22,9 +22,12 @@ impl From for ProofValidationError { } } +#[derive(Debug, thiserror::Error)] +#[error("unsupported bbs-2023 signature type")] pub struct UnsupportedBbs2023Signature; -#[derive(Clone, Serialize)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Bbs2023Signature { pub proof_value: MultibaseBuf, } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-base-document.jsonld b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-base-document.jsonld new file mode 100644 index 000000000..2c807508f --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-base-document.jsonld @@ -0,0 +1,57 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "bbs-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0ChVhQhruAY3aNS3CPmmWCHub-Qms9T2_lwsXJpfgMqlc_2MIMvfF4Jv5OGmJAcLpfIB2SAqD861WELqnmGnKnqgSJFDf8Nfarnvi_jsMATMRslFhYQDpbvyXTTZCxjDXNI1e-am9CMB6U_J5S936Tt3PFYUvfVV3gX4mIF-MTAbrBh9DD_ysD4svbSttNVowX3pYfmhhYYKTvGvo9pXVJbxIrm3i4wkdhUxqKCTIGrnxFuAdZwWi6T3omD5wzZ7bAGbRneEEQSxBmXtvnC6Pr59nPv_v3HrAW9wq_uxYzF_NyaX3GPv0h_FV2T2OSao8C6uoyWiqIj1ggABEiM0RVZneImaq7zN3u_wARIjNEVWZ3iJmqu8zd7v-FZy9pc3N1ZXJ4HS9jcmVkZW50aWFsU3ViamVjdC9zYWlsTnVtYmVyeBovY3JlZGVudGlhbFN1YmplY3Qvc2FpbHMvMXggL2NyZWRlbnRpYWxTdWJqZWN0L2JvYXJkcy8wL3llYXJ4Gi9jcmVkZW50aWFsU3ViamVjdC9zYWlscy8y" + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-derived-document.jsonld b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-derived-document.jsonld new file mode 100644 index 000000000..9ab8fcde0 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/signed-derived-document.jsonld @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7, + "sailName": "Lahaina", + "year": 2020 + } + ], + "boards": [ + { + "year": 2022, + "boardName": "CompFoil170", + "brand": "Wailea" + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "bbs-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0DhVkCEIW3LXS1Wq525PjphjhzUvbT4T8ZOH9ZNanzTFmqOvd4hVAe8dumdXa9JObasbHFs4kWcREsJpgsRB1PNS4byPVFESf72irSQNml1pM_RV23Qcw-edMoG8W2ERGLNjRh8ral7N1CP2t2cRaAgkZl9Q7sH1y68hnukOZs6sV1FG0aiTX3cL5tKaN2sA5OOaT6d1Xs9OtCqjur_W5IuyPpEIHwjQ0lm0ApaD0BwlN4vjxhIToJd1C4zio8CRUGGlR0BbPOWH0dgpkmn60pEDUJs-UwZ_e2B46dxmpREhkq7eNmLm2sWHbZRf0Fhj-ySbD8oC4Qq1FzZQ72ZeksPqcuq6lPyoYM1sY5U45RVvjLw7TSIvehH4N7uedrpU1YwbSsg07zOKPbS_ZFtGIhU8iX9HclX0Dk_MeRk0iuW_kDKp98CHbkemZmyp8XhnOsekG4ZEgNjoTGZVzS8OGGbe3EZ1kKK6dsKMtB89VYLgdztzeRS4NT_qTfkYoCKqWqkvSQ8LPC7fSk1VOLjQeqJTDxGIY-ZU7qqsacLAIFCcJClME72nIcc7hhC7zn5wMNFxDdUUhzGlAmx0HR2p4Gk9MrkNCbtYqOSilaMvsn9lSgPDHFbmw6-xqj8vokD1CV8x_ouV-BebxECM-WcT867GoGQJpvFIapnZkjvv2ydNPgT2-qm_MWzposT14bxtsDFZMyO6YAAgEEAgMDBwQGBQCOAAECBQYICQoODxAREhOGAwQFCAkKRBEzd6o" + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-reveal-document.jsonld b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-reveal-document.jsonld new file mode 100644 index 000000000..b268950da --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-reveal-document.jsonld @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7, + "sailName": "Lahaina", + "year": 2020 + } + ], + "boards": [ + { + "year": 2022, + "boardName": "CompFoil170", + "brand": "Wailea" + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 5e6bda323..1195cf954 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -117,3 +117,45 @@ fn create_verify_data3<'a>( presentation_header: decoded_signature.presentation_header, }) } + +#[cfg(test)] +mod tests { + use iref::Iri; + use lazy_static::lazy_static; + use ssi_bbs::BBSplusPublicKey; + use ssi_claims_core::VerifiableClaims; + use ssi_data_integrity_core::DataIntegrity; + use ssi_vc::v2::JsonCredential; + use ssi_verification_methods::Multikey; + use static_iref::{iri, uri}; + use std::collections::HashMap; + + use crate::Bbs2023; + + const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; + + const VERIFICATION_METHOD_IRI: &Iri = iri!("did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ"); + + lazy_static! { + static ref PUBLIC_KEY: BBSplusPublicKey = + BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); + } + + #[async_std::test] + async fn verify() { + let document: DataIntegrity = + serde_json::from_str(include_str!("tests/signed-derived-document.jsonld")).unwrap(); + + let verification_method = Multikey::from_public_key( + VERIFICATION_METHOD_IRI.to_owned(), + uri!("did:method:controller").to_owned(), + &*PUBLIC_KEY, + ); + + let mut methods = HashMap::new(); + methods.insert(VERIFICATION_METHOD_IRI.to_owned(), verification_method); + + let vc = document.into_verifiable().await.unwrap(); + vc.verify(&methods).await.unwrap().unwrap(); + } +} diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index 28e1a1423..f561a11a8 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -11,6 +11,7 @@ use ssi_rdf::{ generator, interpretation::WithGenerator, Interpretation, LdEnvironment, Vocabulary, VocabularyMut, }; +use serde::{Deserialize, Serialize}; mod contexts; pub use contexts::*; @@ -40,7 +41,8 @@ pub enum JsonLdError { } #[repr(transparent)] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] pub struct CompactJsonLd(pub json_syntax::Value); impl CompactJsonLd { diff --git a/crates/verification-methods/core/src/lib.rs b/crates/verification-methods/core/src/lib.rs index f1b6e9f1d..cc7b6f6df 100644 --- a/crates/verification-methods/core/src/lib.rs +++ b/crates/verification-methods/core/src/lib.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, collections::HashMap, sync::Arc}; use iref::{Iri, IriBuf}; use ssi_claims_core::{ProofValidationError, SignatureError}; @@ -167,6 +167,26 @@ impl<'t, T: VerificationMethodResolver> VerificationMethodResolver for &'t T { } } +impl VerificationMethodResolver for HashMap { + type Method = M; + + async fn resolve_verification_method_with( + &self, + _issuer: Option<&Iri>, + method: Option>, + _options: ResolutionOptions, + ) -> Result, VerificationMethodResolutionError> { + match method { + Some(ReferenceOrOwnedRef::Owned(method)) => Ok(Cow::Owned(method.clone())), + Some(ReferenceOrOwnedRef::Reference(iri)) => match self.get(iri) { + Some(method) => Ok(Cow::Borrowed(method)), + None => Err(VerificationMethodResolutionError::UnknownKey), + }, + None => Err(VerificationMethodResolutionError::MissingVerificationMethod), + } + } +} + pub trait SigningMethod: VerificationMethod { fn sign_bytes( &self, diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 365ac603a..fa6bdd630 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -1,6 +1,5 @@ use iref::{Iri, IriBuf, UriBuf}; use multibase::Base; -use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity, SignatureError}; use ssi_jwk::JWK; @@ -100,7 +99,7 @@ impl Multikey { pub fn generate_ed25519_key_pair( id: IriBuf, controller: UriBuf, - csprng: &mut (impl RngCore + CryptoRng), + csprng: &mut (impl rand_core::RngCore + rand_core::CryptoRng), ) -> (Self, ed25519_dalek::SigningKey) { let key = ed25519_dalek::SigningKey::generate(csprng); ( @@ -119,10 +118,12 @@ impl Multikey { } pub enum SecretKeyRef<'a> { + #[cfg(feature = "ed25519")] Ed25519(&'a ed25519_dalek::SigningKey), Jwk(&'a JWK), } +#[cfg(feature = "ed25519")] impl<'a> From<&'a ed25519_dalek::SigningKey> for SecretKeyRef<'a> { fn from(value: &'a ed25519_dalek::SigningKey) -> Self { Self::Ed25519(value) From bf6a6a04f4ff74c06c3aeaa1bd57853ce96ed56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 13 Jun 2024 12:12:55 +0200 Subject: [PATCH 08/34] Successful bbs-2023 verification. --- .../data-integrity/core/src/proof/de/mod.rs | 11 +- .../data-integrity/core/src/proof/mod.rs | 1 - .../data-integrity/core/src/proof/type.rs | 275 +++++++++++++++++- .../crates/data-integrity/suites/Cargo.toml | 3 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 5 +- .../src/suites/w3c/bbs_2023/verification.rs | 2 +- .../suites/src/suites/w3c/eddsa_2022.rs | 4 +- 7 files changed, 277 insertions(+), 24 deletions(-) diff --git a/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs index 29b8f1f99..6c74f4554 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/de/mod.rs @@ -8,7 +8,7 @@ //! proof. use crate::{ suite::bounds::{OptionsOf, SignatureOf, VerificationMethodOf}, - DeserializeCryptographicSuite, Proof, Type, + CryptosuiteString, DeserializeCryptographicSuite, Proof, Type, }; use serde::{ de::{DeserializeSeed, MapAccess}, @@ -131,8 +131,6 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro let mut cryptosuite = None; let mut data_integrity_proof = false; - eprintln!("start deserializing proof type"); - while let Some(key) = map.next_key::()? { match key { TypeField::Type => { @@ -141,7 +139,6 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro if name == "DataIntegrityProof" { match cryptosuite.take() { Some(c) => { - eprintln!("success"); return Proof::::deserialize_with_type( Type::DataIntegrityProof(c), ReplayMap::new(keep, map), @@ -152,7 +149,6 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro } } } else { - eprintln!("success"); return Proof::::deserialize_with_type( Type::Other(name), ReplayMap::new(keep, map), @@ -160,9 +156,8 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro } } TypeField::Cryptosuite => { - let name = map.next_value::()?; + let name = map.next_value::()?; if data_integrity_proof { - eprintln!("success"); return Proof::::deserialize_with_type( Type::DataIntegrityProof(name), ReplayMap::new(keep, map), @@ -177,8 +172,6 @@ impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> for Pro } } - eprintln!("failure"); - Err(serde::de::Error::custom("missing type")) } } diff --git a/crates/claims/crates/data-integrity/core/src/proof/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/mod.rs index f2b50ac2f..18a8e1ae6 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/mod.rs @@ -392,7 +392,6 @@ impl<'de, S: DeserializeCryptographicSuite<'de>> Deserialize<'de> for Proofs where D: serde::Deserializer<'de>, { - eprintln!("start deserializing proofs"); match OneOrMany::>::deserialize(deserializer)? { OneOrMany::One(proof) => Ok(Self(vec![proof])), OneOrMany::Many(proofs) => Ok(Self(proofs)), diff --git a/crates/claims/crates/data-integrity/core/src/proof/type.rs b/crates/claims/crates/data-integrity/core/src/proof/type.rs index 6e2932f46..31987716d 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/type.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/type.rs @@ -1,7 +1,18 @@ -use iref::IriBuf; +use iref::{Iri, IriBuf}; +use linked_data::{ + FromLinkedDataError, LinkedDataDeserializeSubject, LinkedDataPredicateObjects, + LinkedDataSubject, RdfLiteral, +}; +use rdf_types::{ + dataset::PatternMatchingDataset, + interpretation::{ReverseIriInterpretation, ReverseLiteralInterpretation}, + vocabulary::{IriVocabularyMut, LiteralVocabulary}, + Interpretation, LiteralType, LiteralTypeRef, Vocabulary, RDF_LANG_STRING, +}; use serde::{Deserialize, Serialize}; use ssi_claims_core::ProofPreparationError; -use std::fmt; +use static_iref::iri; +use std::{fmt, ops::Deref, str::FromStr}; #[derive(Debug, Clone, Copy, thiserror::Error)] #[error("missing `cryptosuite` parameter")] @@ -10,12 +21,15 @@ pub struct MissingCryptosuite; /// Proof type. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Type { - DataIntegrityProof(String), + DataIntegrityProof(CryptosuiteString), Other(String), } impl Type { - pub fn new(type_: String, cryptosuite: Option) -> Result { + pub fn new( + type_: String, + cryptosuite: Option, + ) -> Result { if type_ == "DataIntegrityProof" { cryptosuite .ok_or(MissingCryptosuite) @@ -47,7 +61,7 @@ impl fmt::Display for Type { impl<'a> PartialEq> for Type { fn eq(&self, other: &TypeRef<'a>) -> bool { match (self, other) { - (Self::DataIntegrityProof(a), TypeRef::DataIntegrityProof(b)) => a == b, + (Self::DataIntegrityProof(a), TypeRef::DataIntegrityProof(b)) => **a == **b, (Self::Other(a), TypeRef::Other(b)) => a == b, _ => false, } @@ -56,7 +70,7 @@ impl<'a> PartialEq> for Type { /// Proof type reference. pub enum TypeRef<'a> { - DataIntegrityProof(&'a str), + DataIntegrityProof(&'a CryptosuiteStr), Other(&'a str), } @@ -107,7 +121,7 @@ struct CompactType { default, skip_serializing_if = "Option::is_none" )] - pub cryptosuite: Option, + pub cryptosuite: Option, } /// Expanded proof type. @@ -145,3 +159,250 @@ impl From for ProofPreparationError { Self::Proof(value.to_string()) } } + +#[derive(Debug, thiserror::Error)] +#[error("invalid cryptosuite string `{0}`")] +pub struct InvalidCryptosuiteString(pub T); + +pub const CRYPTOSUITE_STRING: &Iri = + iri!("https://www.w3.org/TR/vc-data-integrity/#cryptosuiteString"); + +/// Cryptographic suite identifier. +/// +/// Must be an ASCII string. +/// +/// See: +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct CryptosuiteStr(str); + +impl CryptosuiteStr { + pub fn validate(bytes: &[u8]) -> bool { + bytes.iter().all(u8::is_ascii) + } + + pub fn new(s: &str) -> Result<&Self, InvalidCryptosuiteString<&str>> { + if Self::validate(s.as_bytes()) { + Ok(unsafe { Self::new_unchecked(s) }) + } else { + Err(InvalidCryptosuiteString(s)) + } + } + + pub unsafe fn new_unchecked(s: &str) -> &Self { + std::mem::transmute(s) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl Deref for CryptosuiteStr { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + +impl AsRef for CryptosuiteStr { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for CryptosuiteStr { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl fmt::Display for CryptosuiteStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Owned cryptographic suite identifier. +/// +/// Must be an ASCII string. +/// +/// See: +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct CryptosuiteString(String); + +impl CryptosuiteString { + pub fn new(s: String) -> Result { + if CryptosuiteStr::validate(s.as_bytes()) { + Ok(Self(s)) + } else { + Err(InvalidCryptosuiteString(s)) + } + } + + pub unsafe fn new_unchecked(s: String) -> Self { + Self(s) + } + + pub fn as_cryptosuite_str(&self) -> &CryptosuiteStr { + unsafe { CryptosuiteStr::new_unchecked(self.0.as_str()) } + } +} + +impl Deref for CryptosuiteString { + type Target = CryptosuiteStr; + + fn deref(&self) -> &CryptosuiteStr { + &self.as_cryptosuite_str() + } +} + +impl AsRef for CryptosuiteString { + fn as_ref(&self) -> &CryptosuiteStr { + self.as_cryptosuite_str() + } +} + +impl AsRef for CryptosuiteString { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for CryptosuiteString { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl PartialEq for CryptosuiteString { + fn eq(&self, other: &CryptosuiteStr) -> bool { + self.as_cryptosuite_str() == other + } +} + +impl<'a> PartialEq<&'a CryptosuiteStr> for CryptosuiteString { + fn eq(&self, other: &&'a CryptosuiteStr) -> bool { + self.as_cryptosuite_str() == *other + } +} + +impl PartialEq for CryptosuiteString { + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl<'a> PartialEq<&'a str> for CryptosuiteString { + fn eq(&self, other: &&'a str) -> bool { + self.as_str() == *other + } +} + +impl fmt::Display for CryptosuiteString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for CryptosuiteString { + type Err = InvalidCryptosuiteString; + + fn from_str(s: &str) -> Result { + Self::new(s.to_owned()) + } +} + +impl linked_data::LinkedDataResource for CryptosuiteString +where + V: IriVocabularyMut, +{ + fn interpretation( + &self, + vocabulary: &mut V, + _interpretation: &mut I, + ) -> linked_data::ResourceInterpretation { + use linked_data::{rdf_types::Term, CowRdfTerm, ResourceInterpretation}; + ResourceInterpretation::Uninterpreted(Some(CowRdfTerm::Owned(Term::Literal( + RdfLiteral::Any( + self.0.to_owned(), + LiteralType::Any(vocabulary.insert(CRYPTOSUITE_STRING)), + ), + )))) + } +} + +impl LinkedDataPredicateObjects for CryptosuiteString +where + V: IriVocabularyMut, +{ + fn visit_objects(&self, mut visitor: S) -> Result + where + S: linked_data::PredicateObjectsVisitor, + { + visitor.object(self)?; + visitor.end() + } +} + +impl LinkedDataSubject for CryptosuiteString { + fn visit_subject(&self, visitor: S) -> Result + where + S: linked_data::SubjectVisitor, + { + visitor.end() + } +} + +impl LinkedDataDeserializeSubject for CryptosuiteString +where + V: LiteralVocabulary, + I: ReverseIriInterpretation + ReverseLiteralInterpretation, +{ + fn deserialize_subject_in( + vocabulary: &V, + interpretation: &I, + _dataset: &D, + _graph: Option<&I::Resource>, + resource: &::Resource, + context: linked_data::Context, + ) -> Result + where + D: PatternMatchingDataset, + { + let mut literal_ty = None; + for l in interpretation.literals_of(resource) { + let l = vocabulary.literal(l).unwrap(); + match l.type_ { + LiteralTypeRef::Any(ty_iri) => { + let ty_iri = vocabulary.iri(ty_iri).unwrap(); + if ty_iri == CRYPTOSUITE_STRING { + return match l.value.parse() { + Ok(value) => Ok(value), + Err(_) => Err(FromLinkedDataError::InvalidLiteral( + context.into_iris(vocabulary, interpretation), + )), + }; + } + + literal_ty = Some(ty_iri) + } + LiteralTypeRef::LangString(_) => literal_ty = Some(RDF_LANG_STRING), + } + } + + match literal_ty { + Some(ty) => Err(FromLinkedDataError::LiteralTypeMismatch { + context: context.into_iris(vocabulary, interpretation), + expected: Some(CRYPTOSUITE_STRING.to_owned()), + found: ty.to_owned(), + }), + None => Err(FromLinkedDataError::ExpectedLiteral( + context.into_iris(vocabulary, interpretation), + )), + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 74c5a9dcb..9e3c68f1a 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -164,8 +164,7 @@ hashbrown = "0.13.0" iref = { workspace = true, features = ["hashbrown"] } ssi-vc.workspace = true nquads-syntax.workspace = true -serde_json.workspace = true -json-syntax = { workspace = true, features = ["serde_json"] } +serde_json = { workspace = true, features = ["arbitrary_precision"] } [package.metadata.docs.rs] all-features = true diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index f6b202954..3de3ec027 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -4,7 +4,8 @@ use ssi_claims_core::DefaultEnvironment; use ssi_data_integrity_core::{ suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, - ProofConfiguration, StandardCryptographicSuite, Type, TypeRef, UnsupportedProofSuite, + CryptosuiteStr, ProofConfiguration, StandardCryptographicSuite, Type, TypeRef, + UnsupportedProofSuite, }; use ssi_di_sd_primitives::JsonPointerBuf; use ssi_json_ld::JsonLdEnvironment; @@ -42,7 +43,7 @@ impl StandardCryptographicSuite for Bbs2023 { type SignatureAlgorithm = Bbs2023SignatureAlgorithm; fn type_(&self) -> TypeRef { - TypeRef::DataIntegrityProof("bbs-2023") + TypeRef::DataIntegrityProof(CryptosuiteStr::new("bbs-2023").unwrap()) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 1195cf954..85fd2b939 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -156,6 +156,6 @@ mod tests { methods.insert(VERIFICATION_METHOD_IRI.to_owned(), verification_method); let vc = document.into_verifiable().await.unwrap(); - vc.verify(&methods).await.unwrap().unwrap(); + assert!(vc.verify(&methods).await.unwrap().is_ok()) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs index 24e9e4d6d..3d6c907c8 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs @@ -8,7 +8,7 @@ use ssi_data_integrity_core::{ canonicalization::{CanonicalizeClaimsAndConfiguration, HashCanonicalClaimsAndConfiguration}, signing::{Base58Btc, MultibaseSigning}, suite::NoConfiguration, - StandardCryptographicSuite, TypeRef, + CryptosuiteStr, StandardCryptographicSuite, TypeRef, }; use ssi_verification_methods::Multikey; use static_iref::iri; @@ -42,6 +42,6 @@ impl StandardCryptographicSuite for EdDsa2022 { type ProofOptions = (); fn type_(&self) -> TypeRef { - TypeRef::DataIntegrityProof("eddsa-2022") + TypeRef::DataIntegrityProof(CryptosuiteStr::new("eddsa-2022").unwrap()) } } From ac9da0f79f4053bb18d6fcc8fa138f281c43e431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 13 Jun 2024 12:29:57 +0200 Subject: [PATCH 09/34] Cleanup code. --- .../crates/data-integrity/core/src/proof/type.rs | 16 +++++++++++++++- .../sd-primitives/src/canonicalize.rs | 2 +- .../sd-primitives/src/json_pointer.rs | 14 ++++++++++++++ .../data-integrity/sd-primitives/src/select.rs | 13 +++---------- .../suites/src/suites/w3c/bbs_2023/derive.rs | 7 +------ .../suites/src/suites/w3c/bbs_2023/mod.rs | 2 +- .../src/suites/w3c/bbs_2023/signature/base.rs | 4 ++-- .../suites/w3c/bbs_2023/transformation/base.rs | 4 ---- .../src/methods/w3c/multikey.rs | 10 +++++----- 9 files changed, 42 insertions(+), 30 deletions(-) diff --git a/crates/claims/crates/data-integrity/core/src/proof/type.rs b/crates/claims/crates/data-integrity/core/src/proof/type.rs index 31987716d..b5179f102 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/type.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/type.rs @@ -182,6 +182,7 @@ impl CryptosuiteStr { bytes.iter().all(u8::is_ascii) } + /// Converts the given string into a cryptographic suite identifier. pub fn new(s: &str) -> Result<&Self, InvalidCryptosuiteString<&str>> { if Self::validate(s.as_bytes()) { Ok(unsafe { Self::new_unchecked(s) }) @@ -190,6 +191,12 @@ impl CryptosuiteStr { } } + /// Converts the given string into a cryptographic suite identifier without + /// validation. + /// + /// # Safety + /// + /// The input string *must* be a valid cryptographic suite identifier. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) } @@ -235,6 +242,7 @@ impl fmt::Display for CryptosuiteStr { pub struct CryptosuiteString(String); impl CryptosuiteString { + /// Converts the given string into an owned cryptographic suite identifier. pub fn new(s: String) -> Result { if CryptosuiteStr::validate(s.as_bytes()) { Ok(Self(s)) @@ -243,6 +251,12 @@ impl CryptosuiteString { } } + /// Converts the given string into an owned cryptographic suite identifier + /// without validation. + /// + /// # Safety + /// + /// The input string *must* be a valid cryptographic suite identifier. pub unsafe fn new_unchecked(s: String) -> Self { Self(s) } @@ -256,7 +270,7 @@ impl Deref for CryptosuiteString { type Target = CryptosuiteStr; fn deref(&self) -> &CryptosuiteStr { - &self.as_cryptosuite_str() + self.as_cryptosuite_str() } } diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs index b07a789da..8b938b878 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs @@ -17,7 +17,7 @@ pub fn create_hmac_id_label_map_function( let digest = hmac.finalize_reset().into_bytes(); let b64_url_digest = BlankIdBuf::new(format!( "_:u{}", - base64::encode_config(&digest, base64::URL_SAFE_NO_PAD) + base64::encode_config(digest, base64::URL_SAFE_NO_PAD) )) .unwrap(); (key.clone(), b64_url_digest) diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs index 87840e0c6..3f6ce138c 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs @@ -14,6 +14,7 @@ pub struct InvalidJsonPointer(pub T); pub struct JsonPointer(str); impl JsonPointer { + /// Converts the given string into a JSON pointer. pub fn new(s: &str) -> Result<&Self, InvalidJsonPointer<&str>> { if Self::validate(s) { Ok(unsafe { Self::new_unchecked(s) }) @@ -22,6 +23,11 @@ impl JsonPointer { } } + /// Converts the given string into a JSON pointer without validation. + /// + /// # Safety + /// + /// The input string *must* be a valid JSON pointer. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) } @@ -112,6 +118,7 @@ impl<'a> Iterator for JsonPointerIter<'a> { pub struct JsonPointerBuf(String); impl JsonPointerBuf { + /// Converts the given string into an owned JSON pointer. pub fn new(value: String) -> Result { if JsonPointer::validate(&value) { Ok(Self(value)) @@ -120,6 +127,7 @@ impl JsonPointerBuf { } } + /// Converts the given byte string into an owned JSON pointer. pub fn from_bytes(value: Vec) -> Result>> { match String::from_utf8(value) { Ok(value) => { @@ -161,6 +169,12 @@ impl fmt::Display for JsonPointerBuf { pub struct ReferenceToken(str); impl ReferenceToken { + /// Converts the given string into a JSON pointer reference token without + /// validation. + /// + /// # Safety + /// + /// The input string *must* be a valid JSON pointer reference token. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) } diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs index bdac7f954..f829bd8fa 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs @@ -70,7 +70,7 @@ pub fn select_json_ld( } for pointer in pointers { - document.select(&pointer, &mut selection_document)?; + document.select(pointer, &mut selection_document)?; } Ok(Some(selection_document.into_dense())) @@ -141,7 +141,7 @@ impl SparseValue { pub struct SparseArray(BTreeMap); impl SparseArray { - pub fn from_dense(value: &Vec) -> Self { + pub fn from_dense(value: &[Value]) -> Self { Self( value .iter() @@ -156,11 +156,7 @@ impl SparseArray { i: usize, f: impl FnOnce() -> SparseValue, ) -> &mut SparseValue { - if !self.0.contains_key(&i) { - self.0.insert(i, f()); - } - - self.0.get_mut(&i).unwrap() + self.0.entry(i).or_insert_with(f) } pub fn into_dense(self) -> Vec { @@ -239,7 +235,6 @@ impl Select for Value { if pointer.is_empty() { Ok(()) } else { - eprintln!("tail pointer = {pointer}"); Err(DanglingJsonPointer) } } @@ -257,7 +252,6 @@ impl Select for Vec { ) -> Result<(), DanglingJsonPointer> { match pointer.split_first() { Some((token, rest)) => { - eprintln!("array token = {token}"); let i = token.as_array_index().ok_or(DanglingJsonPointer)?; let a_item = self.get(i).ok_or(DanglingJsonPointer)?; let b_item = @@ -282,7 +276,6 @@ impl Select for ssi_json_ld::syntax::Object { ) -> Result<(), DanglingJsonPointer> { match pointer.split_first() { Some((token, rest)) => { - eprintln!("object token = {token}"); let key = token.to_str(); let a_item = self.get(key.as_ref()).next().ok_or(DanglingJsonPointer)?; let b_item = diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 16a0623f3..8646da153 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -237,7 +237,7 @@ where &pk, &decoded_base_proof.signature_bytes, &decoded_base_proof.bbs_header, - presentation_header.as_deref(), + presentation_header, &bbs_messages, &selective_indexes, )?, @@ -387,11 +387,6 @@ mod tests { let mut context = JsonLdEnvironment::default(); - eprintln!( - "signature = {}", - signed_base.proofs.first().unwrap().signature.proof_value - ); - let data = create_disclosure_data( &mut context, &signed_base.claims, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 3de3ec027..76d4012b2 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -107,7 +107,7 @@ impl ConfigurationAlgorithm for Bbs2023Configuration { options: InputProofOptions, signature_options: Bbs2023InputOptions, ) -> Result<(ProofConfiguration, Bbs2023InputOptions), ConfigurationError> { - let proof_configuration = options.into_configuration(type_.clone())?; + let proof_configuration = options.into_configuration(*type_)?; Ok((proof_configuration, signature_options)) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index d603e406e..4dfecd258 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -99,7 +99,7 @@ where bbs_header, &public_key, hmac_key, - &mandatory_pointers, + mandatory_pointers, description, )) } @@ -295,7 +295,7 @@ impl Bbs2023Signature { }, }) } - _ => return Err(InvalidBbs2023Signature), + _ => Err(InvalidBbs2023Signature) } } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs index 2c61ded56..6d2649b2d 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs @@ -208,10 +208,6 @@ mod tests { "_:b7 \"2020\"^^ .\n" ]; - for quad in &canonical.quads { - eprintln!("{quad} ."); - } - assert_eq!(canonical.quads.into_nquads_lines(), EXPECTED_NQUADS) } diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index fa6bdd630..e92d8e5dd 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -112,7 +112,7 @@ impl Multikey { Self { id, controller, - public_key: MultibaseBuf::encode(Base::Base58Btc, &MultiEncodedBuf::encode(public_key)), + public_key: MultibaseBuf::encode(Base::Base58Btc, MultiEncodedBuf::encode(public_key)), } } } @@ -290,13 +290,13 @@ impl DecodedMultikey { #[allow(unreachable_patterns)] match self { #[cfg(feature = "ed25519")] - Self::Ed25519(key) => Some(key.clone().into()), + Self::Ed25519(key) => Some((*key).into()), #[cfg(feature = "secp256k1")] - Self::Secp256k1(key) => Some(key.clone().into()), + Self::Secp256k1(key) => Some((*key).into()), #[cfg(feature = "secp256r1")] - Self::P256(key) => Some(key.clone().into()), + Self::P256(key) => Some((*key).into()), #[cfg(feature = "secp384r1")] - Self::P384(key) => Some(key.clone().into()), + Self::P384(key) => Some((*key).into()), _ => None, } } From a71015c11ce576701bdd7dcd357388d24f411eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 13 Jun 2024 12:38:48 +0200 Subject: [PATCH 10/34] Feature gate. --- Cargo.toml | 2 +- crates/claims/Cargo.toml | 3 +++ crates/claims/crates/data-integrity/Cargo.toml | 3 +++ crates/claims/crates/data-integrity/suites/Cargo.toml | 1 - crates/claims/crates/data-integrity/suites/src/suites/w3c.rs | 2 ++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b003edf76..d6a5b8724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,7 @@ ethereum = ["ssi-claims/ethereum", "ssi-jwk/eip", "ssi-dids/eip"] ripemd-160 = ["ssi-jwk/ripemd-160", "ssi-dids/ripemd-160"] ## Enable bbs. -bbs = ["ssi-crypto/bbs"] +bbs = ["ssi-crypto/bbs", "ssi-claims/bbs"] ## Use the Ring crate for crypto operations ring = ["ssi-jwk/ring", "ssi-jws/ring", "ssi-crypto/ring"] diff --git a/crates/claims/Cargo.toml b/crates/claims/Cargo.toml index cc197b46c..6e39c7660 100644 --- a/crates/claims/Cargo.toml +++ b/crates/claims/Cargo.toml @@ -56,6 +56,9 @@ solana = ["ssi-data-integrity/solana", "ssi-verification-methods/solana"] # Enables `EthereumPersonalSignature2021` ethereum = ["ssi-jws/eip", "ssi-data-integrity/ethereum"] +# Enables `Bbs2023` +bbs = ["ssi-data-integrity/bbs"] + [dependencies] ssi-core.workspace = true ssi-crypto.workspace = true diff --git a/crates/claims/crates/data-integrity/Cargo.toml b/crates/claims/crates/data-integrity/Cargo.toml index f82aca318..05df8193a 100644 --- a/crates/claims/crates/data-integrity/Cargo.toml +++ b/crates/claims/crates/data-integrity/Cargo.toml @@ -93,6 +93,9 @@ solana = ["ssi-data-integrity-suites/solana"] ## - `Eip712Signature2021` (requires `eip712`) ethereum = ["ssi-data-integrity-suites/ethereum"] +## BBS cryptographic suites. +bbs = ["ssi-data-integrity-suites/bbs"] + [dependencies] ssi-data-integrity-core.workspace = true ssi-data-integrity-suites.workspace = true diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 9e3c68f1a..1d988087c 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -9,7 +9,6 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-data-integrity/" [features] -default = ["bbs"] ## Signature suites specified by the W3C. ## ## This includes: diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs index 9467994ac..f4fe60639 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs @@ -48,5 +48,7 @@ pub use ecdsa_secp256r1_signature_2019::EcdsaSecp256r1Signature2019; pub mod json_web_signature_2020; pub use json_web_signature_2020::JsonWebSignature2020; +#[cfg(feature = "bbs")] pub mod bbs_2023; +#[cfg(feature = "bbs")] pub use bbs_2023::Bbs2023; From 58903071856c9864f2ff1086383c8d6a4a448424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 13 Jun 2024 16:52:06 +0200 Subject: [PATCH 11/34] Factorize tests, ensure JSON canonicalization. --- .../core/src/canonicalization.rs | 5 +- .../core/src/proof/configuration/expansion.rs | 6 +- .../data-integrity/core/src/proof/type.rs | 8 +- .../data-integrity/core/src/suite/mod.rs | 7 +- .../core/src/suite/signature.rs | 2 +- .../core/src/suite/standard/mod.rs | 7 +- .../sd-primitives/src/json_pointer.rs | 8 +- .../sd-primitives/src/select.rs | 2 +- .../sd-primitives/src/skolemize.rs | 27 ++-- .../suites/src/suites/w3c/bbs_2023/derive.rs | 65 +-------- .../suites/src/suites/w3c/bbs_2023/mod.rs | 3 + .../src/suites/w3c/bbs_2023/signature/base.rs | 2 +- .../src/suites/w3c/bbs_2023/signature/mod.rs | 40 +----- .../src/suites/w3c/bbs_2023/tests/mod.rs | 128 ++++++++++++++++++ .../tests/unsigned-base-document.jsonld | 49 +++++++ .../w3c/bbs_2023/transformation/base.rs | 75 +--------- .../w3c/bbs_2023/transformation/derived.rs | 12 +- .../suites/w3c/bbs_2023/transformation/mod.rs | 14 +- .../src/suites/w3c/bbs_2023/verification.rs | 15 +- crates/dids/methods/key/src/lib.rs | 14 +- crates/dids/methods/tz/tests/did.rs | 3 +- crates/json-ld/src/lib.rs | 2 +- crates/jwk/src/lib.rs | 4 +- crates/multicodec/src/codec/p384.rs | 2 +- .../src/methods/w3c/multikey.rs | 3 +- crates/zcap-ld/src/lib.rs | 4 +- 26 files changed, 266 insertions(+), 241 deletions(-) create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-base-document.jsonld diff --git a/crates/claims/crates/data-integrity/core/src/canonicalization.rs b/crates/claims/crates/data-integrity/core/src/canonicalization.rs index 0c41007a6..2c6596c8e 100644 --- a/crates/claims/crates/data-integrity/core/src/canonicalization.rs +++ b/crates/claims/crates/data-integrity/core/src/canonicalization.rs @@ -6,7 +6,10 @@ use ssi_rdf::{AnyLdEnvironment, LdEnvironment}; use crate::{ hashing::ConcatOutputSize, - suite::{standard::{self, HashingAlgorithm, TransformationAlgorithm, TransformationError}, TransformationOptions}, + suite::{ + standard::{self, HashingAlgorithm, TransformationAlgorithm, TransformationError}, + TransformationOptions, + }, CryptographicSuite, ProofConfigurationRef, SerializeCryptographicSuite, StandardCryptographicSuite, }; diff --git a/crates/claims/crates/data-integrity/core/src/proof/configuration/expansion.rs b/crates/claims/crates/data-integrity/core/src/proof/configuration/expansion.rs index 973d9ebf7..818945801 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/configuration/expansion.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/configuration/expansion.rs @@ -115,9 +115,9 @@ impl<'d, 'a, S: SerializeCryptographicSuite> Expandable V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, { let json = json_syntax::to_value(self).unwrap(); - Ok(ExpandedEmbeddedProofConfiguration( - CompactJsonLd(json).expand_with(ld, loader).await?, - )) + let mut expanded = CompactJsonLd(json).expand_with(ld, loader).await?; + expanded.canonicalize(); + Ok(ExpandedEmbeddedProofConfiguration(expanded)) } } diff --git a/crates/claims/crates/data-integrity/core/src/proof/type.rs b/crates/claims/crates/data-integrity/core/src/proof/type.rs index b5179f102..8db7ab3a1 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/type.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/type.rs @@ -193,9 +193,9 @@ impl CryptosuiteStr { /// Converts the given string into a cryptographic suite identifier without /// validation. - /// + /// /// # Safety - /// + /// /// The input string *must* be a valid cryptographic suite identifier. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) @@ -253,9 +253,9 @@ impl CryptosuiteString { /// Converts the given string into an owned cryptographic suite identifier /// without validation. - /// + /// /// # Safety - /// + /// /// The input string *must* be a valid cryptographic suite identifier. pub unsafe fn new_unchecked(s: String) -> Self { Self(s) diff --git a/crates/claims/crates/data-integrity/core/src/suite/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/mod.rs index 4be7a301a..c90ab5204 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -80,7 +80,8 @@ pub trait CryptographicSuite: Clone { where Self: CryptographicSuiteSigning, { - let (proof_configuration, transformation_options) = self.configure(proof_options, signature_options)?; + let (proof_configuration, transformation_options) = + self.configure(proof_options, signature_options)?; let proof_configuration_ref = proof_configuration.borrowed(); let signature = self .generate_signature( @@ -89,7 +90,7 @@ pub trait CryptographicSuite: Clone { signer, &unsecured_document, proof_configuration_ref, - transformation_options + transformation_options, ) .await?; @@ -116,7 +117,7 @@ pub trait CryptographicSuite: Clone { resolver, signer, proof_options, - Default::default() + Default::default(), ) .await } diff --git a/crates/claims/crates/data-integrity/core/src/suite/signature.rs b/crates/claims/crates/data-integrity/core/src/suite/signature.rs index fe7e4a2cb..2da5327f7 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/signature.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/signature.rs @@ -12,6 +12,6 @@ pub trait CryptographicSuiteSigning: CryptographicSuite { signer: S, claims: &T, proof_configuration: ProofConfigurationRef<'_, Self>, - transformation_options: TransformationOptions + transformation_options: TransformationOptions, ) -> Result; } diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs index 0559824dc..e616796cb 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs @@ -18,7 +18,10 @@ pub use signature::*; mod verification; pub use verification::*; -use super::{ConfigurationAlgorithm, CryptographicSuiteSigning, CryptographicSuiteVerification, TransformationOptions}; +use super::{ + ConfigurationAlgorithm, CryptographicSuiteSigning, CryptographicSuiteVerification, + TransformationOptions, +}; // mod test_bbs; @@ -124,7 +127,7 @@ where signers: T, claims: &C, proof_configuration: ProofConfigurationRef<'_, Self>, - transformation_options: TransformationOptions + transformation_options: TransformationOptions, ) -> Result { let options = ssi_verification_methods_core::ResolutionOptions { accept: Some(Box::new(Self::VerificationMethod::type_set())), diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs index 3f6ce138c..83c099e16 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/json_pointer.rs @@ -24,9 +24,9 @@ impl JsonPointer { } /// Converts the given string into a JSON pointer without validation. - /// + /// /// # Safety - /// + /// /// The input string *must* be a valid JSON pointer. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) @@ -171,9 +171,9 @@ pub struct ReferenceToken(str); impl ReferenceToken { /// Converts the given string into a JSON pointer reference token without /// validation. - /// + /// /// # Safety - /// + /// /// The input string *must* be a valid JSON pointer reference token. pub unsafe fn new_unchecked(s: &str) -> &Self { std::mem::transmute(s) diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs index f829bd8fa..4af4df26a 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/select.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/select.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, HashMap}; -use ssi_json_ld::syntax::Value; use rdf_types::{BlankId, BlankIdBuf, LexicalQuad}; +use ssi_json_ld::syntax::Value; use crate::{ canonicalize::relabel_quads, diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs index df52cb606..aedc9b2a6 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs @@ -1,11 +1,10 @@ use iref::IriBuf; -use ssi_json_ld::{ - context_processing::{Process, ProcessedOwned}, syntax::Value, Compact, ExpandedDocument, JsonLdProcessor, RemoteDocument -}; use linked_data::IntoQuadsError; -use rdf_types::{ - generator, - BlankId, BlankIdBuf, Id, LexicalQuad, Term, +use rdf_types::{generator, BlankId, BlankIdBuf, Id, LexicalQuad, Term}; +use ssi_json_ld::{ + context_processing::{Process, ProcessedOwned}, + syntax::Value, + Compact, ExpandedDocument, JsonLdProcessor, RemoteDocument, }; use ssi_json_ld::{Expandable, JsonLdObject}; use ssi_rdf::LexicalInterpretation; @@ -154,12 +153,14 @@ impl Skolemize { T: JsonLdObject + Expandable, T::Expanded: Into, { - let expanded = document + let mut expanded = document .expand(loader) .await - .map_err(SkolemError::json_ld_expansion)?; + .map_err(SkolemError::json_ld_expansion)? + .into(); + expanded.canonicalize(); - let skolemized_expanded_document = self.expanded_document(expanded.into()); + let skolemized_expanded_document = self.expanded_document(expanded); let processed_context: ProcessedOwned = match document.json_ld_context() { @@ -197,12 +198,8 @@ impl Skolemize { |i| i, |id| match id { ssi_json_ld::Id::Valid(id) => match id { - Id::Blank(b) => { - ssi_json_ld::Id::Valid(Id::Iri(self.blank_id(&b))) - } - Id::Iri(i) => { - ssi_json_ld::Id::Valid(Id::Iri(i)) - } + Id::Blank(b) => ssi_json_ld::Id::Valid(Id::Iri(self.blank_id(&b))), + Id::Iri(i) => ssi_json_ld::Id::Valid(Id::Iri(i)), }, ssi_json_ld::Id::Invalid(s) => ssi_json_ld::Id::Invalid(s), }, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 8646da153..9233ac59d 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -2,9 +2,7 @@ use std::{borrow::Cow, collections::HashMap, hash::Hash}; use hmac::{digest::KeyInit, Hmac}; use k256::sha2::Sha256; -use rdf_types::{ - BlankIdBuf, Quad -}; +use rdf_types::{BlankIdBuf, Quad}; use serde::{Deserialize, Serialize}; use ssi_bbs::{proof_gen, ProofGenFailed}; use ssi_data_integrity_core::{DataIntegrity, Proof}; @@ -292,71 +290,16 @@ enum Group { #[cfg(test)] mod tests { - use std::collections::HashMap; - - use json_syntax::{Parse, UnorderedPartialEq}; - use lazy_static::lazy_static; - use rdf_types::BlankIdBuf; - use ssi_bbs::BBSplusPublicKey; + use json_syntax::UnorderedPartialEq; use ssi_data_integrity_core::DataIntegrity; - use ssi_di_sd_primitives::{select::select_json_ld, JsonPointerBuf}; + use ssi_di_sd_primitives::select::select_json_ld; use ssi_json_ld::JsonLdEnvironment; - use ssi_vc::v2::JsonCredential; use ssi_verification_methods::Multikey; use static_iref::{iri, uri}; use crate::{bbs_2023::Bbs2023Signature, Bbs2023}; - use super::{create_disclosure_data, DerivedFeatureOption, DisclosureData}; - - const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; - const PRESENTATION_HEADER_HEX: &str = "113377aa"; - - // TODO this can't be used because the BBS API (based on zkryptium) does not - // allow providing a seed. - // const PSEUDO_RANDOM_SEED_HEX: &str = "332e313431353932363533353839373933323338343632363433333833323739"; - - const BBS_PROOF_HEX: &str = "85b72d74b55aae76e4f8e986387352f6d3e13f19387f5935a9f34c59aa3af77885501ef1dba67576bd24e6dab1b1c5b3891671112c26982c441d4f352e1bc8f5451127fbda2ad240d9a5d6933f455db741cc3e79d3281bc5b611118b363461f2b6a5ecdd423f6b76711680824665f50eec1f5cbaf219ee90e66ceac575146d1a8935f770be6d29a376b00e4e39a4fa7755ecf4eb42aa3babfd6e48bb23e91081f08d0d259b4029683d01c25378be3c61213a097750b8ce2a3c0915061a547405b3ce587d1d8299269fad29103509b3e53067f7b6078e9dc66a5112192aede3662e6dac5876d945fd05863fb249b0fca02e10ab5173650ef665e92c3ea72eaba94fca860cd6c639538e5156f8cbc3b4d222f7a11f837bb9e76ba54d58c1b4ac834ef338a3db4bf645b4622153c897f477255f40e4fcc7919348ae5bf9032a9f7c0876e47a6666ca9f178673ac7a41b864480d8e84c6655cd2f0e1866dedc467590a2ba76c28cb41f3d5582e0773b737914b8353fea4df918a022aa5aa92f490f0b3c2edf4a4d5538b8d07aa2530f118863e654eeaaac69c2c020509c24294c13bda721c73b8610bbce7e7030d1710dd5148731a5026c741d1da9e0693d32b90d09bb58a8e4a295a32fb27f654a03c31c56e6c3afb1aa3f2fa240f5095f31fe8b95f8179bc4408cf96713f3aec6a06409a6f1486a99d9923befdb274d3e04f6faa9bf316ce9a2c4f5e1bc6db031593323b"; - - lazy_static! { - static ref MANDATORY_POINTERS: [JsonPointerBuf; 5] = [ - "/issuer".parse().unwrap(), - "/credentialSubject/sailNumber".parse().unwrap(), - "/credentialSubject/sails/1".parse().unwrap(), - "/credentialSubject/boards/0/year".parse().unwrap(), - "/credentialSubject/sails/2".parse().unwrap() - ]; - static ref SELECTIVE_POINTERS: [JsonPointerBuf; 2] = [ - "/credentialSubject/boards/0".parse().unwrap(), - "/credentialSubject/boards/1".parse().unwrap() - ]; - static ref SIGNED_BASE_DOCUMENT: json_syntax::Object = - json_syntax::Value::parse_str(include_str!("tests/signed-base-document.jsonld")) - .unwrap() - .0 - .into_object() - .unwrap(); - static ref UNSIGNED_REVEAL_DOCUMENT: json_syntax::Object = - json_syntax::Value::parse_str(include_str!("tests/unsigned-reveal-document.jsonld")) - .unwrap() - .0 - .into_object() - .unwrap(); - static ref PUBLIC_KEY: BBSplusPublicKey = - BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); - static ref PRESENTATION_HEADER: Vec = hex::decode(PRESENTATION_HEADER_HEX).unwrap(); - static ref LABEL_MAP: HashMap = [ - ("_:c14n0".parse().unwrap(), "_:b2".parse().unwrap()), - ("_:c14n1".parse().unwrap(), "_:b4".parse().unwrap()), - ("_:c14n2".parse().unwrap(), "_:b3".parse().unwrap()), - ("_:c14n3".parse().unwrap(), "_:b7".parse().unwrap()), - ("_:c14n4".parse().unwrap(), "_:b6".parse().unwrap()), - ("_:c14n5".parse().unwrap(), "_:b0".parse().unwrap()) - ] - .into_iter() - .collect(); - static ref BBS_PROOF: Vec = hex::decode(BBS_PROOF_HEX).unwrap(); - } + use super::{super::tests::*, create_disclosure_data, DerivedFeatureOption, DisclosureData}; #[test] fn reveal_document() { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 76d4012b2..85ae26e22 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -25,6 +25,9 @@ pub use derive::*; mod verification; +#[cfg(test)] +mod tests; + /// The `bbs-2023` cryptographic suite. #[derive(Debug, Clone, Copy)] pub struct Bbs2023; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index 4dfecd258..eae672f66 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -295,7 +295,7 @@ impl Bbs2023Signature { }, }) } - _ => Err(InvalidBbs2023Signature) + _ => Err(InvalidBbs2023Signature), } } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs index df932f057..b6dd98fff 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs @@ -76,14 +76,10 @@ where #[cfg(test)] mod tests { - use iref::Iri; - use lazy_static::lazy_static; use nquads_syntax::Parse; - use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; use ssi_data_integrity_core::{suite::standard::SignatureAlgorithm, ProofConfiguration}; - use ssi_di_sd_primitives::JsonPointerBuf; use ssi_verification_methods::{Multikey, ProofPurpose, ReferenceOrOwned, SingleSecretSigner}; - use static_iref::{iri, uri}; + use static_iref::uri; use crate::{ bbs_2023::{ @@ -93,24 +89,7 @@ mod tests { Bbs2023, }; - use super::Bbs2023SignatureAlgorithm; - - lazy_static! { - pub static ref MANDATORY_POINTERS: Vec = vec![ - "/issuer".parse().unwrap(), - "/credentialSubject/sailNumber".parse().unwrap(), - "/credentialSubject/sails/1".parse().unwrap(), - "/credentialSubject/boards/0/year".parse().unwrap(), - "/credentialSubject/sails/2".parse().unwrap() - ]; - } - - const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; - const SECRET_KEY_HEX: &str = "66d36e118832af4c5e28b2dfe1b9577857e57b042a33e06bdea37b811ed09ee0"; - const HMAC_KEY_STRING: &str = - "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; - - const DID: &Iri = iri!("did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ"); + use super::{super::tests::*, Bbs2023SignatureAlgorithm}; const MANDATORY: &str = "_:b0 . @@ -178,15 +157,13 @@ _:b5 \"2023\"^^ \"2023-08-15T23:36:38Z\"^^ .\n".to_string(), @@ -229,11 +206,4 @@ _:b5 \"2023\"^^ (BBSplusPublicKey, BBSplusSecretKey) { - ( - BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(), - BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap(), - ) - } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs new file mode 100644 index 000000000..0e8bd332e --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs @@ -0,0 +1,128 @@ +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, + hash::Hash, +}; + +use iref::Iri; +use json_syntax::Parse; +use lazy_static::lazy_static; +use rdf_types::{BlankIdBuf, VocabularyMut}; +use serde::{Deserialize, Serialize}; +use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; +use ssi_claims_core::{ClaimsValidity, Proof, Validate}; +use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_json_ld::{AnyJsonLdEnvironment, JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes}; +use static_iref::iri; + +/// JSON Credential. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JsonCredential { + /// JSON-LD context. + #[serde(rename = "@context")] + pub context: json_ld::syntax::Context, + + /// Credential type. + #[serde(rename = "type")] + pub types: Vec, + + #[serde(flatten)] + pub properties: BTreeMap, +} + +impl JsonLdObject for JsonCredential { + fn json_ld_context(&self) -> Option> { + Some(Cow::Borrowed(&self.context)) + } +} + +impl JsonLdNodeObject for JsonCredential { + fn json_ld_type(&self) -> JsonLdTypes { + JsonLdTypes::new(&[], Cow::Borrowed(&self.types)) + } +} + +impl Validate for JsonCredential { + fn validate(&self, _env: &E, _proof: &P::Prepared) -> ClaimsValidity { + Ok(()) + } +} + +impl ssi_rdf::Expandable for JsonCredential +where + E: AnyJsonLdEnvironment, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash, + V::BlankId: Clone + Eq + Hash, + L: json_ld::Loader, + L::Error: std::fmt::Display, +{ + type Error = JsonLdError; + + type Expanded = json_ld::ExpandedDocument; + + async fn expand(&self, environment: &mut E) -> Result { + let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); + json.expand(environment).await + } +} + +pub const VERIFICATION_METHOD_IRI: &Iri = iri!("did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ"); + +pub const HMAC_KEY_STRING: &str = + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + +pub const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; +pub const SECRET_KEY_HEX: &str = "66d36e118832af4c5e28b2dfe1b9577857e57b042a33e06bdea37b811ed09ee0"; + +pub const PRESENTATION_HEADER_HEX: &str = "113377aa"; + +// TODO this can't be used because the BBS API (based on zkryptium) does not +// allow providing a seed. +// pub const PSEUDO_RANDOM_SEED_HEX: &str = "332e313431353932363533353839373933323338343632363433333833323739"; + +pub const BBS_PROOF_HEX: &str = "85b72d74b55aae76e4f8e986387352f6d3e13f19387f5935a9f34c59aa3af77885501ef1dba67576bd24e6dab1b1c5b3891671112c26982c441d4f352e1bc8f5451127fbda2ad240d9a5d6933f455db741cc3e79d3281bc5b611118b363461f2b6a5ecdd423f6b76711680824665f50eec1f5cbaf219ee90e66ceac575146d1a8935f770be6d29a376b00e4e39a4fa7755ecf4eb42aa3babfd6e48bb23e91081f08d0d259b4029683d01c25378be3c61213a097750b8ce2a3c0915061a547405b3ce587d1d8299269fad29103509b3e53067f7b6078e9dc66a5112192aede3662e6dac5876d945fd05863fb249b0fca02e10ab5173650ef665e92c3ea72eaba94fca860cd6c639538e5156f8cbc3b4d222f7a11f837bb9e76ba54d58c1b4ac834ef338a3db4bf645b4622153c897f477255f40e4fcc7919348ae5bf9032a9f7c0876e47a6666ca9f178673ac7a41b864480d8e84c6655cd2f0e1866dedc467590a2ba76c28cb41f3d5582e0773b737914b8353fea4df918a022aa5aa92f490f0b3c2edf4a4d5538b8d07aa2530f118863e654eeaaac69c2c020509c24294c13bda721c73b8610bbce7e7030d1710dd5148731a5026c741d1da9e0693d32b90d09bb58a8e4a295a32fb27f654a03c31c56e6c3afb1aa3f2fa240f5095f31fe8b95f8179bc4408cf96713f3aec6a06409a6f1486a99d9923befdb274d3e04f6faa9bf316ce9a2c4f5e1bc6db031593323b"; + +lazy_static! { + pub static ref MANDATORY_POINTERS: Vec = vec![ + "/issuer".parse().unwrap(), + "/credentialSubject/sailNumber".parse().unwrap(), + "/credentialSubject/sails/1".parse().unwrap(), + "/credentialSubject/boards/0/year".parse().unwrap(), + "/credentialSubject/sails/2".parse().unwrap() + ]; + pub static ref SELECTIVE_POINTERS: [JsonPointerBuf; 2] = [ + "/credentialSubject/boards/0".parse().unwrap(), + "/credentialSubject/boards/1".parse().unwrap() + ]; + pub static ref UNSIGNED_BASE_DOCUMENT: JsonCredential = + serde_json::from_str(include_str!("unsigned-base-document.jsonld")).unwrap(); + pub static ref SIGNED_BASE_DOCUMENT: json_syntax::Object = + json_syntax::Value::parse_str(include_str!("signed-base-document.jsonld")) + .unwrap() + .0 + .into_object() + .unwrap(); + pub static ref UNSIGNED_REVEAL_DOCUMENT: json_syntax::Object = + json_syntax::Value::parse_str(include_str!("unsigned-reveal-document.jsonld")) + .unwrap() + .0 + .into_object() + .unwrap(); + pub static ref PUBLIC_KEY: BBSplusPublicKey = + BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); + pub static ref SECRET_KEY: BBSplusSecretKey = + BBSplusSecretKey::from_bytes(&hex::decode(SECRET_KEY_HEX).unwrap()).unwrap(); + pub static ref PRESENTATION_HEADER: Vec = hex::decode(PRESENTATION_HEADER_HEX).unwrap(); + pub static ref LABEL_MAP: HashMap = [ + ("_:c14n0".parse().unwrap(), "_:b2".parse().unwrap()), + ("_:c14n1".parse().unwrap(), "_:b4".parse().unwrap()), + ("_:c14n2".parse().unwrap(), "_:b3".parse().unwrap()), + ("_:c14n3".parse().unwrap(), "_:b7".parse().unwrap()), + ("_:c14n4".parse().unwrap(), "_:b6".parse().unwrap()), + ("_:c14n5".parse().unwrap(), "_:b0".parse().unwrap()) + ] + .into_iter() + .collect(); + pub static ref BBS_PROOF: Vec = hex::decode(BBS_PROOF_HEX).unwrap(); +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-base-document.jsonld b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-base-document.jsonld new file mode 100644 index 000000000..4678731b5 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/unsigned-base-document.jsonld @@ -0,0 +1,49 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7.0, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs index 6d2649b2d..99d6cf2e9 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs @@ -5,7 +5,7 @@ use hmac::{Hmac, Mac}; use k256::sha2::Sha256; use ssi_data_integrity_core::suite::standard::TransformationError; use ssi_di_sd_primitives::group::canonicalize_and_group; -use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; use ssi_rdf::LexicalInterpretation; use crate::bbs_2023::{Bbs2023InputOptions, HmacKey}; @@ -20,7 +20,7 @@ pub async fn base_proof_transformation( ) -> Result where T: JsonLdNodeObject + Expandable, - T::Expanded: Into + T::Expanded: Into, { // Base Proof Transformation algorithm. // See: @@ -76,13 +76,11 @@ mod tests { use hmac::{Hmac, Mac}; use k256::sha2::Sha256; - use lazy_static::lazy_static; use ssi_data_integrity_core::{ suite::standard::TypedTransformationAlgorithm, ProofConfiguration, }; use ssi_di_sd_primitives::{group::canonicalize_and_group, JsonPointerBuf}; use ssi_rdf::IntoNQuads; - use ssi_vc::v2::syntax::JsonCredential; use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use crate::{ @@ -90,70 +88,7 @@ mod tests { Bbs2023, }; - use super::{create_shuffled_id_label_map_function, Mandatory}; - - const HMAC_KEY_STRING: &str = - "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; - - lazy_static! { - pub static ref CREDENTIAL: JsonCredential = json_syntax::from_value(json_syntax::json!({ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - { - "@vocab": "https://windsurf.grotto-networking.com/selective#" - } - ], - "type": [ - "VerifiableCredential" - ], - "issuer": "https://vc.example/windsurf/racecommittee", - "credentialSubject": { - "sailNumber": "Earth101", - "sails": [ - { - "size": 5.5, - "sailName": "Kihei", - "year": 2023 - }, - { - "size": 6.1, - "sailName": "Lahaina", - "year": 2023 - }, - { - "size": 7.0, - "sailName": "Lahaina", - "year": 2020 - }, - { - "size": 7.8, - "sailName": "Lahaina", - "year": 2023 - } - ], - "boards": [ - { - "boardName": "CompFoil170", - "brand": "Wailea", - "year": 2022 - }, - { - "boardName": "Kanaha Custom", - "brand": "Wailea", - "year": 2019 - } - ] - } - })) - .unwrap(); - pub static ref MANDATORY_POINTERS: Vec = vec![ - "/issuer".parse().unwrap(), - "/credentialSubject/sailNumber".parse().unwrap(), - "/credentialSubject/sails/1".parse().unwrap(), - "/credentialSubject/boards/0/year".parse().unwrap(), - "/credentialSubject/sails/2".parse().unwrap() - ]; - } + use super::{super::super::tests::*, create_shuffled_id_label_map_function, Mandatory}; #[async_std::test] async fn hmac_canonicalize_and_group() { @@ -172,7 +107,7 @@ mod tests { &loader, label_map_factory_function, group_definitions, - &*CREDENTIAL, + &*UNSIGNED_BASE_DOCUMENT, ) .await .unwrap(); @@ -228,7 +163,7 @@ mod tests { let transformed = Bbs2023Transformation::transform( &context, - &*CREDENTIAL, + &*UNSIGNED_BASE_DOCUMENT, proof_configuration.borrowed(), Some(Bbs2023InputOptions { mandatory_pointers: MANDATORY_POINTERS.clone(), diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs index eafcd6ae1..84adcfca7 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/derived.rs @@ -2,8 +2,8 @@ use super::TransformedDerived; use rdf_types::Quad; use serde::Serialize; use ssi_data_integrity_core::suite::standard::TransformationError; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; use ssi_rdf::{LdEnvironment, LexicalInterpretation}; -use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument}; /// See: pub async fn create_verify_data1( @@ -17,17 +17,15 @@ where { let mut ld = LdEnvironment::default(); - let expanded: ExpandedDocument = unsecured_document + let mut expanded: ExpandedDocument = unsecured_document .expand_with(&mut ld, loader) .await .map_err(TransformationError::json_ld_expansion)? .into(); + expanded.canonicalize(); - let quads = linked_data::to_lexical_quads_with( - &mut ld.vocabulary, - &mut ld.interpretation, - &expanded - )?; + let quads = + linked_data::to_lexical_quads_with(&mut ld.vocabulary, &mut ld.interpretation, &expanded)?; let canonical_id_map = ssi_rdf::urdna2015::normalize(quads.iter().map(Quad::as_lexical_quad_ref)) diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs index aff572859..c76450094 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs @@ -9,7 +9,7 @@ use ssi_data_integrity_core::{ ProofConfigurationRef, }; use ssi_di_sd_primitives::canonicalize::create_hmac_id_label_map_function; -use ssi_json_ld::{Expandable, JsonLdNodeObject, ExpandedDocument, ContextLoaderEnvironment}; +use ssi_json_ld::{ContextLoaderEnvironment, Expandable, ExpandedDocument, JsonLdNodeObject}; use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; use std::collections::HashMap; @@ -49,11 +49,13 @@ where ) .await .map(Transformed::Base), - None => { - derived::create_verify_data1(context.loader(), unsecured_document, canonical_configuration) - .await - .map(Transformed::Derived) - } + None => derived::create_verify_data1( + context.loader(), + unsecured_document, + canonical_configuration, + ) + .await + .map(Transformed::Derived), } } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 85fd2b939..a1a8525e8 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -120,26 +120,15 @@ fn create_verify_data3<'a>( #[cfg(test)] mod tests { - use iref::Iri; - use lazy_static::lazy_static; - use ssi_bbs::BBSplusPublicKey; use ssi_claims_core::VerifiableClaims; use ssi_data_integrity_core::DataIntegrity; - use ssi_vc::v2::JsonCredential; use ssi_verification_methods::Multikey; - use static_iref::{iri, uri}; + use static_iref::uri; use std::collections::HashMap; use crate::Bbs2023; - const PUBLIC_KEY_HEX: &str = "a4ef1afa3da575496f122b9b78b8c24761531a8a093206ae7c45b80759c168ba4f7a260f9c3367b6c019b4677841104b10665edbe70ba3ebe7d9cfbffbf71eb016f70abfbb163317f372697dc63efd21fc55764f63926a8f02eaea325a2a888f"; - - const VERIFICATION_METHOD_IRI: &Iri = iri!("did:key:zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ#zUC7DerdEmfZ8f4pFajXgGwJoMkV1ofMTmEG5UoNvnWiPiLuGKNeqgRpLH2TV4Xe5mJ2cXV76gRN7LFQwapF1VFu6x2yrr5ci1mXqC1WNUrnHnLgvfZfMH7h6xP6qsf9EKRQrPQ"); - - lazy_static! { - static ref PUBLIC_KEY: BBSplusPublicKey = - BBSplusPublicKey::from_bytes(&hex::decode(PUBLIC_KEY_HEX).unwrap()).unwrap(); - } + use super::super::tests::*; #[async_std::test] async fn verify() { diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index cdd1811e1..7c98fd367 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -44,13 +44,19 @@ impl DIDKey { Params::OKP(ref params) => match ¶ms.curve[..] { "Ed25519" => multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes(ssi_multicodec::ED25519_PUB, ¶ms.public_key.0) - .into_bytes(), + MultiEncodedBuf::encode_bytes( + ssi_multicodec::ED25519_PUB, + ¶ms.public_key.0, + ) + .into_bytes(), ), "Bls12381G2" => multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes(ssi_multicodec::BLS12_381_G2_PUB, ¶ms.public_key.0) - .into_bytes(), + MultiEncodedBuf::encode_bytes( + ssi_multicodec::BLS12_381_G2_PUB, + ¶ms.public_key.0, + ) + .into_bytes(), ), _ => return Err(GenerateError::UnsupportedCurve(params.curve.clone())), }, diff --git a/crates/dids/methods/tz/tests/did.rs b/crates/dids/methods/tz/tests/did.rs index 59b2e4c43..d51f7ffb4 100644 --- a/crates/dids/methods/tz/tests/did.rs +++ b/crates/dids/methods/tz/tests/did.rs @@ -358,8 +358,7 @@ async fn credential_prove_verify_did_tz2() { assert!(vc_bad_issuer.verify(¶ms).await.unwrap().is_err()); // Check that proof JWK must match proof verificationMethod - let wrong_signer = - SingleSecretSigner::new(JWK::generate_secp256k1_from(&mut rng)).into_local(); + let wrong_signer = SingleSecretSigner::new(JWK::generate_secp256k1_from(&mut rng)).into_local(); let vc_wrong_key = suite .sign( vc.claims.clone(), diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index f561a11a8..2b1d5e1fe 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -7,11 +7,11 @@ use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; pub use json_ld; pub use json_ld::*; +use serde::{Deserialize, Serialize}; use ssi_rdf::{ generator, interpretation::WithGenerator, Interpretation, LdEnvironment, Vocabulary, VocabularyMut, }; -use serde::{Deserialize, Serialize}; mod contexts; pub use contexts::*; diff --git a/crates/jwk/src/lib.rs b/crates/jwk/src/lib.rs index 5c076d953..2636545a4 100644 --- a/crates/jwk/src/lib.rs +++ b/crates/jwk/src/lib.rs @@ -314,9 +314,7 @@ impl JWK { } #[cfg(feature = "secp256k1")] - pub fn generate_secp256k1_from( - rng: &mut (impl rand::CryptoRng + rand::RngCore), - ) -> JWK { + pub fn generate_secp256k1_from(rng: &mut (impl rand::CryptoRng + rand::RngCore)) -> JWK { let secret_key = k256::SecretKey::random(rng); let sk_bytes = zeroize::Zeroizing::new(secret_key.to_bytes().to_vec()); let public_key = secret_key.public_key(); diff --git a/crates/multicodec/src/codec/p384.rs b/crates/multicodec/src/codec/p384.rs index 566d149b7..7d89842ba 100644 --- a/crates/multicodec/src/codec/p384.rs +++ b/crates/multicodec/src/codec/p384.rs @@ -12,4 +12,4 @@ impl Codec for p384::PublicKey { fn to_bytes(&self) -> Cow<[u8]> { Cow::Owned(self.to_sec1_bytes().into_vec()) } -} \ No newline at end of file +} diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index e92d8e5dd..44cd5e76c 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -6,7 +6,8 @@ use ssi_jwk::JWK; use ssi_multicodec::{Codec, MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ - MaybeJwkVerificationMethod, MessageSignatureError, SigningMethod, VerificationMethodSet, VerifyBytes + MaybeJwkVerificationMethod, MessageSignatureError, SigningMethod, VerificationMethodSet, + VerifyBytes, }; use static_iref::iri; use std::{borrow::Cow, hash::Hash}; diff --git a/crates/zcap-ld/src/lib.rs b/crates/zcap-ld/src/lib.rs index 02d845178..19de4750d 100644 --- a/crates/zcap-ld/src/lib.rs +++ b/crates/zcap-ld/src/lib.rs @@ -182,12 +182,12 @@ impl Delegation { suite .sign_with( - environment, + environment, self, resolver, signer, proof_configuration, - Default::default() + Default::default(), ) .await } From 9a2a8c53a47b807d2adfc959acfabc27a1cc8970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Mon, 24 Jun 2024 13:57:39 +0200 Subject: [PATCH 12/34] Fix formatting & clippy warnings. --- .../suites/src/suites/w3c/bbs_2023/mod.rs | 6 ------ crates/dids/methods/key/src/lib.rs | 2 +- .../src/methods/unspecified/eip712_method_2021.rs | 9 +++------ .../w3c/ecdsa_secp_256k1_recovery_method_2020.rs | 12 +----------- .../w3c/ecdsa_secp_256k1_verification_key_2019.rs | 12 +----------- 5 files changed, 6 insertions(+), 35 deletions(-) diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 85ae26e22..28fb0315a 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,14 +1,12 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: -use ssi_claims_core::DefaultEnvironment; use ssi_data_integrity_core::{ suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, CryptosuiteStr, ProofConfiguration, StandardCryptographicSuite, Type, TypeRef, UnsupportedProofSuite, }; use ssi_di_sd_primitives::JsonPointerBuf; -use ssi_json_ld::JsonLdEnvironment; use ssi_verification_methods::Multikey; pub(crate) mod transformation; @@ -61,10 +59,6 @@ impl TryFrom for Bbs2023 { } } -impl DefaultEnvironment for Bbs2023 { - type Environment = JsonLdEnvironment; -} - #[derive(Clone)] pub struct Bbs2023InputOptions { pub mandatory_pointers: Vec, diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 7c98fd367..4f94d85f3 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -110,7 +110,7 @@ impl DIDKey { multibase::encode( multibase::Base::Base58Btc, - MultiEncodedBuf::encode(ssi_multicodec::P384_PUB, &pk_bytes) + MultiEncodedBuf::encode_bytes(ssi_multicodec::P384_PUB, &pk_bytes) .into_bytes(), ) } diff --git a/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs b/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs index 288adc85c..fe8477d8a 100644 --- a/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs @@ -111,12 +111,9 @@ impl Eip712Method2021 { // Check the signing key. let jwk = JWK { - params: ssi_jwk::Params::EC( - ssi_jwk::ECParams::try_from( - &k256::PublicKey::from_sec1_bytes(&recovered_key.to_sec1_bytes()).unwrap(), - ) - .unwrap(), - ), + params: ssi_jwk::Params::EC(ssi_jwk::ECParams::from( + &k256::PublicKey::from_sec1_bytes(&recovered_key.to_sec1_bytes()).unwrap(), + )), public_key_use: None, key_operations: None, algorithm: None, diff --git a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs index 45fe977c6..8bd9021ac 100644 --- a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs +++ b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs @@ -281,17 +281,7 @@ impl PublicKeyHex { } pub fn to_jwk(&self) -> JWK { - JWK { - params: ssi_jwk::Params::EC(ssi_jwk::ECParams::try_from(&self.decoded).unwrap()), - public_key_use: None, - key_operations: None, - algorithm: None, - key_id: None, - x509_url: None, - x509_certificate_chain: None, - x509_thumbprint_sha1: None, - x509_thumbprint_sha256: None, - } + self.decoded.into() } } diff --git a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs index 002210a02..9976f4a60 100644 --- a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs +++ b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs @@ -248,17 +248,7 @@ impl PublicKeyHex { } pub fn to_jwk(&self) -> JWK { - JWK { - params: ssi_jwk::Params::EC(ssi_jwk::ECParams::try_from(&self.decoded).unwrap()), - public_key_use: None, - key_operations: None, - algorithm: None, - key_id: None, - x509_url: None, - x509_certificate_chain: None, - x509_thumbprint_sha1: None, - x509_thumbprint_sha256: None, - } + self.decoded.into() } } From 9f3871cca8381411e62ba4420a342c7ff72935c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 27 Jun 2024 16:38:07 +0200 Subject: [PATCH 13/34] Fix tests. --- .../suites/src/suites/w3c/bbs_2023/derive.rs | 5 +- .../src/suites/w3c/bbs_2023/tests/mod.rs | 51 +++++++++++-------- .../w3c/bbs_2023/transformation/base.rs | 2 +- .../src/suites/w3c/bbs_2023/verification.rs | 3 +- crates/jwk/src/lib.rs | 1 + 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 9233ac59d..bd7c5e1c5 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -293,7 +293,6 @@ mod tests { use json_syntax::UnorderedPartialEq; use ssi_data_integrity_core::DataIntegrity; use ssi_di_sd_primitives::select::select_json_ld; - use ssi_json_ld::JsonLdEnvironment; use ssi_verification_methods::Multikey; use static_iref::{iri, uri}; @@ -328,10 +327,10 @@ mod tests { &*PUBLIC_KEY, ); - let mut context = JsonLdEnvironment::default(); + let loader = ssi_json_ld::ContextLoader::default(); let data = create_disclosure_data( - &mut context, + &loader, &signed_base.claims, &verification_method, &signed_base.proofs.first().unwrap().signature, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs index 0e8bd332e..cf621a69b 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs @@ -10,9 +10,10 @@ use lazy_static::lazy_static; use rdf_types::{BlankIdBuf, VocabularyMut}; use serde::{Deserialize, Serialize}; use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; -use ssi_claims_core::{ClaimsValidity, Proof, Validate}; +use ssi_claims_core::{ClaimsValidity, Validate}; use ssi_di_sd_primitives::JsonPointerBuf; -use ssi_json_ld::{AnyJsonLdEnvironment, JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes}; +use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes}; +use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject}; use static_iref::iri; /// JSON Credential. @@ -20,7 +21,7 @@ use static_iref::iri; pub struct JsonCredential { /// JSON-LD context. #[serde(rename = "@context")] - pub context: json_ld::syntax::Context, + pub context: ssi_json_ld::syntax::Context, /// Credential type. #[serde(rename = "type")] @@ -31,7 +32,7 @@ pub struct JsonCredential { } impl JsonLdObject for JsonCredential { - fn json_ld_context(&self) -> Option> { + fn json_ld_context(&self) -> Option> { Some(Cow::Borrowed(&self.context)) } } @@ -42,28 +43,36 @@ impl JsonLdNodeObject for JsonCredential { } } -impl Validate for JsonCredential { - fn validate(&self, _env: &E, _proof: &P::Prepared) -> ClaimsValidity { +impl Validate for JsonCredential { + fn validate(&self, _env: &E, _proof: &P) -> ClaimsValidity { Ok(()) } } -impl ssi_rdf::Expandable for JsonCredential -where - E: AnyJsonLdEnvironment, - V: VocabularyMut, - V::Iri: Clone + Eq + Hash, - V::BlankId: Clone + Eq + Hash, - L: json_ld::Loader, - L::Error: std::fmt::Display, -{ - type Error = JsonLdError; - - type Expanded = json_ld::ExpandedDocument; - - async fn expand(&self, environment: &mut E) -> Result { +impl ssi_json_ld::Expandable for JsonCredential { + type Error = JsonLdError; + + type Expanded = ssi_json_ld::ExpandedDocument + where + I: Interpretation, + V: VocabularyMut, + V::Iri: LinkedDataResource + LinkedDataSubject, + V::BlankId: LinkedDataResource + LinkedDataSubject; + + #[allow(async_fn_in_trait)] + async fn expand_with( + &self, + ld: &mut LdEnvironment, + loader: &impl ssi_json_ld::Loader, + ) -> Result, Self::Error> + where + I: Interpretation, + V: VocabularyMut, + V::Iri: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + V::BlankId: Clone + Eq + Hash + LinkedDataResource + LinkedDataSubject, + { let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap()); - json.expand(environment).await + json.expand_with(ld, loader).await } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs index 99d6cf2e9..f2e5d9b9c 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/base.rs @@ -79,7 +79,7 @@ mod tests { use ssi_data_integrity_core::{ suite::standard::TypedTransformationAlgorithm, ProofConfiguration, }; - use ssi_di_sd_primitives::{group::canonicalize_and_group, JsonPointerBuf}; + use ssi_di_sd_primitives::group::canonicalize_and_group; use ssi_rdf::IntoNQuads; use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index a1a8525e8..7acd43d33 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -144,7 +144,6 @@ mod tests { let mut methods = HashMap::new(); methods.insert(VERIFICATION_METHOD_IRI.to_owned(), verification_method); - let vc = document.into_verifiable().await.unwrap(); - assert!(vc.verify(&methods).await.unwrap().is_ok()) + assert!(document.verify(&methods).await.unwrap().is_ok()) } } diff --git a/crates/jwk/src/lib.rs b/crates/jwk/src/lib.rs index 2636545a4..08383895d 100644 --- a/crates/jwk/src/lib.rs +++ b/crates/jwk/src/lib.rs @@ -952,6 +952,7 @@ pub fn secp256k1_parse(data: &[u8]) -> Result { Ok(pk.into()) } +#[cfg(feature = "secp256k1")] impl From for JWK { fn from(value: k256::PublicKey) -> Self { JWK { From 1231a7570eccba0c92ee87cd4f6f8285812c593b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 27 Jun 2024 16:49:16 +0200 Subject: [PATCH 14/34] Fix redundant imports. --- crates/bbs/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index ccecfb8d5..5412af411 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -75,13 +75,7 @@ impl MultiSigningMethod for Multikey { algorithm: Bbs, messages: &[Vec], ) -> Result, MessageSignatureError> { - use zkryptium::{ - bbsplus::ciphersuites::Bls12381Sha256, - schemes::{ - algorithms::BBSplus, - generics::{BlindSignature, Signature}, - }, - }; + use zkryptium::schemes::generics::{BlindSignature, Signature}; let DecodedMultikey::Bls12_381(pk) = self.decode()? else { return Err(MessageSignatureError::InvalidPublicKey); From fcd7339bec797d42df7490032f0153160642f6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 27 Jun 2024 16:53:51 +0200 Subject: [PATCH 15/34] Fix non-exhaustive pattern in multikey. --- crates/verification-methods/src/methods/w3c/multikey.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 44cd5e76c..3a9685517 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -332,6 +332,8 @@ impl MultiCodec for DecodedMultikey { Self::P384(k) => k.to_codec_and_bytes(), #[cfg(feature = "bls12-381")] Self::Bls12_381(k) => k.to_codec_and_bytes(), + #[allow(unreachable_patterns)] + _ => unreachable!(), // references are always considered inhabited. } } } From 80857a9db98873d2fcd429185561ea6f8c030d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 3 Jul 2024 11:20:48 +0200 Subject: [PATCH 16/34] Upgrade `uuid`to 1.9 --- Cargo.toml | 2 +- crates/dids/methods/key/src/lib.rs | 2 +- crates/verification-methods/Cargo.toml | 6 +++--- examples/issue.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d6a5b8724..1e18501d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ getrandom = "0.2" contextual = "0.1.6" lazy_static = "1.4.0" indexmap = "2.0.0" -uuid = "0.8" +uuid = "1.9" [features] default = ["w3c", "rsa", "ed25519", "secp256k1", "secp256r1", "ripemd-160", "eip712"] diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 4f94d85f3..763be07f3 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -847,7 +847,7 @@ mod tests { #[async_std::test] #[cfg(feature = "secp384r1")] async fn fetch_jwk_secp384r1() { - let jwk = JWK::generate_p384().unwrap(); + let jwk = JWK::generate_p384(); fetch_jwk(jwk).await; } } diff --git a/crates/verification-methods/Cargo.toml b/crates/verification-methods/Cargo.toml index c17aa3dab..d8289f076 100644 --- a/crates/verification-methods/Cargo.toml +++ b/crates/verification-methods/Cargo.toml @@ -18,13 +18,13 @@ rsa = [] ed25519 = ["ed25519-dalek", "rand_core", "ssi-jws/ed25519", "ssi-multicodec/ed25519"] ## enable secp256k1 keys -secp256k1 = ["k256", "sha2", "ssi-jws/secp256k1"] +secp256k1 = ["k256", "sha2", "ssi-jws/secp256k1", "ssi-multicodec/k256"] ## enable secp256r1 (p256) keys -secp256r1 = ["p256", "ssi-jws/secp256r1"] +secp256r1 = ["p256", "ssi-jws/secp256r1", "ssi-multicodec/p256"] ## enable secp384r1 (p384) keys -secp384r1 = ["p384", "ssi-jws/secp384r1"] +secp384r1 = ["p384", "ssi-jws/secp384r1", "ssi-multicodec/p384"] tezos = ["ssi-tzkey", "ssi-caips/tezos"] diff --git a/examples/issue.rs b/examples/issue.rs index 1f1497640..4d53be8fb 100644 --- a/examples/issue.rs +++ b/examples/issue.rs @@ -27,7 +27,7 @@ async fn issue(proof_format: &str) { "issuer": "did:example:foo", "issuanceDate": ssi::xsd_types::DateTime::now(), "credentialSubject": { - "id": uuid::Uuid::new_v4().to_urn().to_string() + "id": uuid::Uuid::new_v4().urn().to_string() } })) .unwrap(); From 224a54c0f5b93fa31ba64cb524b5249f2e8ebf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 3 Jul 2024 11:21:07 +0200 Subject: [PATCH 17/34] Fix multicodec imports. --- crates/multicodec/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/multicodec/src/lib.rs b/crates/multicodec/src/lib.rs index b64adf68b..0c36d4122 100644 --- a/crates/multicodec/src/lib.rs +++ b/crates/multicodec/src/lib.rs @@ -1,7 +1,5 @@ use std::ops::Deref; -pub use unsigned_varint::decode::Error; - mod codec; pub use codec::*; From 892ed01f4ec2a5d059df9d5c8c270c9bc8867ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 3 Jul 2024 11:25:44 +0200 Subject: [PATCH 18/34] Remove some unused files. --- .../data-integrity/core/src/suite/error.rs | 136 ------------------ .../data-integrity/core/src/suite/input.rs | 66 --------- .../data-integrity/core/src/suite/mod.rs | 57 -------- 3 files changed, 259 deletions(-) delete mode 100644 crates/claims/crates/data-integrity/core/src/suite/error.rs delete mode 100644 crates/claims/crates/data-integrity/core/src/suite/input.rs diff --git a/crates/claims/crates/data-integrity/core/src/suite/error.rs b/crates/claims/crates/data-integrity/core/src/suite/error.rs deleted file mode 100644 index b8dddc5b7..000000000 --- a/crates/claims/crates/data-integrity/core/src/suite/error.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::convert::Infallible; - -use ssi_claims_core::{ProofPreparationError, ProofValidationError, SignatureError}; -use ssi_verification_methods_core::InvalidVerificationMethod; - -use crate::ProofConfigurationCastError; - -#[derive(Debug, thiserror::Error)] -pub enum ProofConfigurationError {} - -impl From for SignatureError { - fn from(value: ProofConfigurationError) -> Self { - match value {} - } -} - -#[derive(Debug, thiserror::Error)] -pub enum TransformError { - #[error("Expansion failed: {0}")] - ExpansionFailed(String), - - #[error("RDF deserialization failed: {0}")] - LinkedData(#[from] linked_data::IntoQuadsError), - - #[error("JSON serialization failed: {0}")] - JsonSerialization(json_syntax::SerializeError), - - #[error("expected JSON object")] // TODO merge it with `InvalidData`. - ExpectedJsonObject, - - #[error("invalid data")] - InvalidData, - - #[error("unsupported input format")] - UnsupportedInputFormat, - - #[error("invalid proof options: {0}")] - InvalidProofOptions(InvalidOptions), - - #[error("invalid verification method: {0}")] - InvalidVerificationMethod(InvalidVerificationMethod), - - #[error("internal error: `{0}`")] - Internal(String), -} - -impl TransformError { - pub fn internal(e: impl ToString) -> Self { - Self::Internal(e.to_string()) - } -} - -impl From> - for TransformError -{ - fn from(value: ProofConfigurationCastError) -> Self { - match value { - ProofConfigurationCastError::VerificationMethod(e) => { - Self::InvalidVerificationMethod(e) - } - ProofConfigurationCastError::Options(e) => Self::InvalidProofOptions(e), - } - } -} - -impl From> for TransformError { - fn from(value: ProofConfigurationCastError) -> Self { - match value { - ProofConfigurationCastError::VerificationMethod(e) => { - Self::InvalidVerificationMethod(e) - } - ProofConfigurationCastError::Options(_) => unreachable!(), - } - } -} - -impl From for ProofPreparationError { - fn from(value: TransformError) -> Self { - ProofPreparationError::Claims(value.to_string()) - } -} - -impl From for SignatureError { - fn from(value: TransformError) -> Self { - SignatureError::Claims(value.to_string()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum HashError { - #[error("invalid verification method")] - InvalidVerificationMethod, - - #[error("message is too long")] - TooLong, - - #[error("invalid message: {0}")] - InvalidMessage(String), - - #[error("invalid transformed input")] - InvalidTransformedInput, -} - -impl From for ProofPreparationError { - fn from(value: HashError) -> Self { - Self::Claims(value.to_string()) - } -} - -impl From for SignatureError { - fn from(value: HashError) -> Self { - Self::Claims(value.to_string()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum InvalidOptions { - #[error("missing public key")] - MissingPublicKey, -} - -impl From for ProofValidationError { - fn from(value: InvalidOptions) -> Self { - match value { - InvalidOptions::MissingPublicKey => ProofValidationError::MissingPublicKey, - } - } -} - -impl From for SignatureError { - fn from(value: InvalidOptions) -> Self { - match value { - InvalidOptions::MissingPublicKey => SignatureError::MissingPublicKey, - } - } -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/core/src/suite/input.rs b/crates/claims/crates/data-integrity/core/src/suite/input.rs deleted file mode 100644 index 03b9c919e..000000000 --- a/crates/claims/crates/data-integrity/core/src/suite/input.rs +++ /dev/null @@ -1,66 +0,0 @@ -use ssi_claims_core::{SignatureError, Verifiable}; -use ssi_json_ld::JsonLdNodeObject; -use ssi_verification_methods_core::{Signer, VerificationMethodResolver}; - -use crate::{sign, signing::sign_single, ExpandedConfigurationRef, Proof, ProofConfigurationRefExpansion, Proofs}; - -use super::{CryptographicSuite, TransformError}; - -pub trait CryptographicSuiteInput: CryptographicSuite { - /// Transformation algorithm. - #[allow(async_fn_in_trait)] - async fn transform<'a, 'c: 'a>( - &'a self, - data: &'a T, - context: &'a mut C, - params: ExpandedConfigurationRef<'c, Self>, - ) -> Result - where - C: 'a; - - #[allow(async_fn_in_trait)] - async fn sign<'max, R, S>( - &self, - input: T, - context: C, - resolver: &'max R, - signer: &'max S, - params: Self::InputOptions, - ) -> Result>, SignatureError> - where - Self::VerificationMethod: 'max, - T: JsonLdNodeObject, - R: 'max + VerificationMethodResolver, - S: 'max - + Signer< - Self::VerificationMethod, - Self::MessageSignatureAlgorithm - >, - C: for<'a> ProofConfigurationRefExpansion<'a, Self>, - { - sign(input, context, resolver, signer, self, params).await - } - - #[allow(async_fn_in_trait)] - async fn sign_single<'max, R, S>( - &self, - input: T, - context: C, - resolver: &'max R, - signer: &'max S, - params: Self::InputOptions, - ) -> Result>, SignatureError> - where - Self::VerificationMethod: 'max, - T: JsonLdNodeObject, - R: 'max + VerificationMethodResolver, - S: 'max - + Signer< - Self::VerificationMethod, - Self::MessageSignatureAlgorithm - >, - C: for<'a> ProofConfigurationRefExpansion<'a, Self>, - { - sign_single(input, context, resolver, signer, self, params).await - } -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/core/src/suite/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/mod.rs index c90ab5204..d740f4065 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -123,63 +123,6 @@ pub trait CryptographicSuite: Clone { } } -// /// Cryptographic suite instance. -// /// -// /// See: -// pub trait CryptographicSuiteInstance: CryptographicSuite { -// /// Prepare the input claims for signing or verification. -// #[allow(async_fn_in_trait)] -// async fn prepare_claims( -// &self, -// context: &mut C, -// unsecured_document: &T, -// proof_configuration: ProofConfigurationRef, -// ) -> Result; - -// /// Create a proof by signing the given unsecured document. -// #[allow(async_fn_in_trait)] -// async fn create_proof( -// &self, -// context: &mut C, -// resolver: R, -// signer: S, -// unsecured_document: &T, -// options: InputOptions, -// ) -> Result, SignatureError> -// where -// Self: CryptographicSuiteSigning, -// { -// let proof_configuration = self.configure(options)?; -// let proof_configuration_ref = proof_configuration.borrowed(); -// let prepared_claims = self -// .prepare_claims(context, unsecured_document, proof_configuration_ref) -// .await?; -// let signature = self -// .sign_prepared_claims(resolver, signer, &prepared_claims, proof_configuration_ref) -// .await?; -// Ok(proof_configuration.into_proof(signature)) -// } - -// /// Verify a proof of the given unsecured document. -// #[allow(async_fn_in_trait)] -// async fn verify_proof( -// &self, -// context: &mut C, -// verifier: &V, -// unsecured_document: &T, -// proof: ProofRef<'_, Self>, -// ) -> Result -// where -// Self: CryptographicSuiteVerification, -// { -// let prepared_claims = self -// .prepare_claims(context, unsecured_document, proof.configuration()) -// .await?; -// self.verify_prepared_claims(verifier, &prepared_claims, proof) -// .await -// } -// } - #[derive(Debug, thiserror::Error)] pub enum ClaimsPreparationError { #[error("proof configuration failed: {0}")] From 28adfb3e42b85cc82009f5d7e467ee825193469f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 5 Jul 2024 13:32:57 +0200 Subject: [PATCH 19/34] Remove duplicate `serde_json` dev dependency. --- crates/claims/crates/data-integrity/suites/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 1d988087c..419618684 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -156,14 +156,13 @@ serde_cbor = "0.11.2" [dev-dependencies] async-std = { version = "1.9", features = ["attributes"] } -serde_json.workspace = true +serde_json = { workspace = true, features = ["arbitrary_precision"] } static-iref.workspace = true rand = "0.7" hashbrown = "0.13.0" iref = { workspace = true, features = ["hashbrown"] } ssi-vc.workspace = true nquads-syntax.workspace = true -serde_json = { workspace = true, features = ["arbitrary_precision"] } [package.metadata.docs.rs] all-features = true From a155b72189682a976162800cfa439770e9f72024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 5 Jul 2024 13:33:17 +0200 Subject: [PATCH 20/34] Fix formatting. --- .../data-integrity/core/src/suite/standard/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs index e616796cb..395d039f7 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs @@ -142,12 +142,14 @@ where ) .await?; - let transformed = self.transform( - context, - claims, - proof_configuration, - Some(transformation_options) - ).await?; + let transformed = self + .transform( + context, + claims, + proof_configuration, + Some(transformation_options), + ) + .await?; let hashed = self.hash(transformed, proof_configuration, &method)?; From 8d754aaf1dcf11d761ab3b0e0736f6bb7e939037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 5 Jul 2024 14:27:34 +0200 Subject: [PATCH 21/34] Lazy `Multikey` decoding. --- crates/bbs/src/lib.rs | 2 +- .../suites/src/suites/w3c/bbs_2023/derive.rs | 2 +- .../suites/src/suites/w3c/bbs_2023/hashing.rs | 2 + .../src/suites/w3c/bbs_2023/signature/base.rs | 4 +- .../src/suites/w3c/bbs_2023/tests/mod.rs | 6 +- .../suites/w3c/bbs_2023/transformation/mod.rs | 4 +- .../src/suites/w3c/bbs_2023/verification.rs | 7 +- .../suites/src/suites/w3c/ecdsa_rdfc_2019.rs | 56 ++++-- .../data-integrity/suites/tests/suite.rs | 5 +- crates/multicodec/src/lib.rs | 2 +- .../src/methods/w3c/multikey.rs | 181 ++++++++++++++---- 11 files changed, 200 insertions(+), 71 deletions(-) diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 5412af411..2ce0b6c70 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -77,7 +77,7 @@ impl MultiSigningMethod for Multikey { ) -> Result, MessageSignatureError> { use zkryptium::schemes::generics::{BlindSignature, Signature}; - let DecodedMultikey::Bls12_381(pk) = self.decode()? else { + let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? else { return Err(MessageSignatureError::InvalidPublicKey); }; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index bd7c5e1c5..8845cd749 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -226,7 +226,7 @@ where .map(|quad| format!("{quad} .\n").into_bytes()) .collect(); - let DecodedMultikey::Bls12_381(pk) = verification_method.decode()? else { + let DecodedMultikey::Bls12_381(pk) = verification_method.public_key.decode()? else { return Err(DeriveError::InvalidPublicKey); }; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs index 33dff3474..6ceeae295 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs @@ -5,6 +5,7 @@ use ssi_data_integrity_core::{ ProofConfigurationRef, }; use ssi_rdf::{urdna2015::NormalizingSubstitution, IntoNQuads}; +use ssi_verification_methods::Multikey; use crate::Bbs2023; @@ -21,6 +22,7 @@ impl HashingAlgorithm for Bbs2023Hashing { fn hash( input: TransformedData, _proof_configuration: ProofConfigurationRef, + _verification_method: &Multikey, ) -> Result { match input { Transformed::Base(t) => { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index eae672f66..0bccda0e6 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -22,7 +22,7 @@ where T::MessageSigner: MultiMessageSigner, { // See: - let DecodedMultikey::Bls12_381(public_key) = verification_method.decode()? else { + let DecodedMultikey::Bls12_381(public_key) = verification_method.public_key.decode()? else { return Err(SignatureError::InvalidPublicKey); }; let feature_option = hash_data.transformed_document.options.feature_option; @@ -44,7 +44,7 @@ where let message_signer = signer .for_method(Cow::Borrowed(verification_method)) - .await + .await? .ok_or(SignatureError::MissingSigner)?; let (algorithm, description) = match feature_option { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs index cf621a69b..d3c4b34e1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/tests/mod.rs @@ -10,7 +10,7 @@ use lazy_static::lazy_static; use rdf_types::{BlankIdBuf, VocabularyMut}; use serde::{Deserialize, Serialize}; use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; -use ssi_claims_core::{ClaimsValidity, Validate}; +use ssi_claims_core::{ClaimsValidity, ValidateClaims}; use ssi_di_sd_primitives::JsonPointerBuf; use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes}; use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject}; @@ -43,8 +43,8 @@ impl JsonLdNodeObject for JsonCredential { } } -impl Validate for JsonCredential { - fn validate(&self, _env: &E, _proof: &P) -> ClaimsValidity { +impl ValidateClaims for JsonCredential { + fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity { Ok(()) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs index c76450094..c1f4f8cf6 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs @@ -9,7 +9,7 @@ use ssi_data_integrity_core::{ ProofConfigurationRef, }; use ssi_di_sd_primitives::canonicalize::create_hmac_id_label_map_function; -use ssi_json_ld::{ContextLoaderEnvironment, Expandable, ExpandedDocument, JsonLdNodeObject}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; use std::collections::HashMap; @@ -24,7 +24,7 @@ impl TransformationAlgorithm for Bbs2023Transformation { impl TypedTransformationAlgorithm for Bbs2023Transformation where - C: ContextLoaderEnvironment, + C: JsonLdLoaderProvider, T: Serialize + JsonLdNodeObject + Expandable, T::Expanded: Into, { diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 7acd43d33..477f7b1c9 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -25,7 +25,7 @@ impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { // Verify Derived Proof algorithm. // See: - let DecodedMultikey::Bls12_381(public_key) = method.decode()? else { + let DecodedMultikey::Bls12_381(public_key) = method.public_key.decode()? else { return Err(ProofValidationError::InvalidKey); }; @@ -120,7 +120,7 @@ fn create_verify_data3<'a>( #[cfg(test)] mod tests { - use ssi_claims_core::VerifiableClaims; + use ssi_claims_core::VerificationParameters; use ssi_data_integrity_core::DataIntegrity; use ssi_verification_methods::Multikey; use static_iref::uri; @@ -144,6 +144,7 @@ mod tests { let mut methods = HashMap::new(); methods.insert(VERIFICATION_METHOD_IRI.to_owned(), verification_method); - assert!(document.verify(&methods).await.unwrap().is_ok()) + let params = VerificationParameters::from_resolver(methods); + assert!(document.verify(params).await.unwrap().is_ok()) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs index 2d71463eb..9de444c6d 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs @@ -2,8 +2,6 @@ //! //! See: use core::fmt; - -use k256::sha2::{Sha256, Sha384}; use ssi_data_integrity_core::{ canonicalization::{ CanonicalClaimsAndConfiguration, CanonicalizeClaimsAndConfiguration, @@ -14,9 +12,9 @@ use ssi_data_integrity_core::{ standard::{HashingAlgorithm, HashingError}, NoConfiguration, }, - ProofConfigurationRef, StandardCryptographicSuite, TypeRef, + CryptosuiteStr, ProofConfigurationRef, StandardCryptographicSuite, TypeRef, }; -use ssi_verification_methods::Multikey; +use ssi_verification_methods::{multikey::DecodedMultikey, Multikey}; use static_iref::iri; /// The `ecdsa-rdfc-2019` cryptosuite. @@ -45,7 +43,7 @@ impl StandardCryptographicSuite for EcdsaRdfc2019 { type ProofOptions = (); fn type_(&self) -> TypeRef { - TypeRef::DataIntegrityProof("ecdsa-rdfc-2019") + TypeRef::DataIntegrityProof(CryptosuiteStr::new("ecdsa-rdfc-2019").unwrap()) } } @@ -59,19 +57,29 @@ impl HashingAlgorithm for EcdsaRdfc2019HashingAlgorithm { proof_configuration: ProofConfigurationRef, verification_method: &Multikey, ) -> Result { - match verification_method.public_key.codec() { - ssi_multicodec::P256_PUB => HashCanonicalClaimsAndConfiguration::::hash( - input, - proof_configuration, - verification_method, - ) - .map(EcdsaRdfc2019Hash::Sha256), - ssi_multicodec::P384_PUB => HashCanonicalClaimsAndConfiguration::::hash( - input, - proof_configuration, - verification_method, - ) - .map(EcdsaRdfc2019Hash::Sha384), + match verification_method + .public_key + .decode() + .map_err(|_| HashingError::InvalidKey)? + { + #[cfg(feature = "secp256r1")] + DecodedMultikey::P256(_) => { + HashCanonicalClaimsAndConfiguration::::hash( + input, + proof_configuration, + verification_method, + ) + .map(EcdsaRdfc2019Hash::Sha256) + } + #[cfg(feature = "secp384r1")] + DecodedMultikey::P384(_) => { + HashCanonicalClaimsAndConfiguration::::hash( + input, + proof_configuration, + verification_method, + ) + .map(EcdsaRdfc2019Hash::Sha384) + } _ => Err(HashingError::InvalidKey), } } @@ -117,9 +125,15 @@ impl AlgorithmSelection for ES256OrES384 { verification_method: &Multikey, _options: &O, ) -> Result { - match verification_method.public_key.codec() { - ssi_multicodec::P256_PUB => Ok(Self::ES256), - ssi_multicodec::P384_PUB => Ok(Self::ES384), + match verification_method + .public_key + .decode() + .map_err(|_| AlgorithmSelectionError::InvalidKey)? + { + #[cfg(feature = "secp256r1")] + DecodedMultikey::P256(_) => Ok(Self::ES256), + #[cfg(feature = "secp384r1")] + DecodedMultikey::P384(_) => Ok(Self::ES384), _ => Err(AlgorithmSelectionError::InvalidKey), } } diff --git a/crates/claims/crates/data-integrity/suites/tests/suite.rs b/crates/claims/crates/data-integrity/suites/tests/suite.rs index a05682022..bb21be74e 100644 --- a/crates/claims/crates/data-integrity/suites/tests/suite.rs +++ b/crates/claims/crates/data-integrity/suites/tests/suite.rs @@ -85,10 +85,7 @@ impl VerificationMethodResolver for MultikeyRing { Ok(Cow::Owned(ssi_verification_methods::Multikey { id: id.to_owned(), controller: UriBuf::new(controller.to_owned().into_bytes()).unwrap(), - public_key: ssi_verification_methods::multikey::PublicKey::decode( - public_key, - ) - .unwrap(), + public_key: public_key.into(), })) } None => Err(VerificationMethodResolutionError::UnknownKey), diff --git a/crates/multicodec/src/lib.rs b/crates/multicodec/src/lib.rs index 0c36d4122..981645bae 100644 --- a/crates/multicodec/src/lib.rs +++ b/crates/multicodec/src/lib.rs @@ -5,7 +5,7 @@ pub use codec::*; include!(concat!(env!("OUT_DIR"), "/table.rs")); -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error(transparent)] Varint(#[from] unsigned_varint::decode::Error), diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 3a9685517..88e2a1e95 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -1,5 +1,11 @@ use iref::{Iri, IriBuf, UriBuf}; use multibase::Base; +use rdf_types::{ + dataset::PatternMatchingDataset, + interpretation::{ReverseIriInterpretation, ReverseLiteralInterpretation}, + vocabulary::{IriVocabularyMut, LiteralVocabulary}, + Interpretation, Vocabulary, +}; use serde::{Deserialize, Serialize}; use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity, SignatureError}; use ssi_jwk::JWK; @@ -10,7 +16,7 @@ use ssi_verification_methods_core::{ VerifyBytes, }; use static_iref::iri; -use std::{borrow::Cow, hash::Hash}; +use std::{borrow::Cow, hash::Hash, str::FromStr, sync::OnceLock}; use crate::{ ExpectedType, GenericVerificationMethod, InvalidVerificationMethod, TypedVerificationMethod, @@ -53,10 +59,10 @@ pub struct Multikey { /// [MULTIBASE]: #[serde(rename = "publicKeyMultibase")] #[ld("sec:publicKeyMultibase")] - pub public_key: MultibaseBuf, + pub public_key: PublicKey, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum InvalidPublicKey { #[error(transparent)] Multibase(#[from] multibase::Error), @@ -88,12 +94,7 @@ impl Multikey { pub const IRI: &'static Iri = iri!("https://w3id.org/security#Multikey"); pub fn public_key_jwk(&self) -> Option { - self.decode().ok()?.to_jwk() - } - - pub fn decode(&self) -> Result { - let pk_multi_encoded = MultiEncodedBuf::new(self.public_key.decode()?.1)?; - pk_multi_encoded.decode().map_err(Into::into) + self.public_key.decode().ok()?.to_jwk() } #[cfg(feature = "ed25519")] @@ -113,7 +114,7 @@ impl Multikey { Self { id, controller, - public_key: MultibaseBuf::encode(Base::Base58Btc, MultiEncodedBuf::encode(public_key)), + public_key: PublicKey::new(public_key), } } } @@ -172,29 +173,6 @@ impl> VerifyBytes for Multikey { } } -#[cfg(feature = "ed25519")] -impl VerifyBytes for Multikey { - fn verify_bytes( - &self, - _algorithm: ssi_jwk::algorithm::EdDSA, - signing_bytes: &[u8], - signature: &[u8], - ) -> Result { - #[allow(unreachable_patterns)] - match self.decode()? { - DecodedMultikey::Ed25519(public_key) => { - use ed25519_dalek::Verifier; - let signature = ed25519_dalek::Signature::try_from(signature) - .map_err(|_| ProofValidationError::InvalidSignature)?; - Ok(public_key - .verify(signing_bytes, &signature) - .map_err(|_| InvalidProof::Signature)) - } - _ => Err(ProofValidationError::InvalidKey), - } - } -} - impl SigningMethod for Multikey { fn sign_bytes( &self, @@ -268,6 +246,143 @@ impl TryFrom for Multikey { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PublicKey { + pub encoded: MultibaseBuf, + + #[serde(skip)] + decoded: OnceLock>, +} + +impl PublicKey { + pub fn new(public_key: &impl MultiCodec) -> Self { + Self::from_multibase(MultibaseBuf::encode( + Base::Base58Btc, + MultiEncodedBuf::encode(public_key), + )) + } + + pub fn from_multibase(encoded: MultibaseBuf) -> Self { + Self { + encoded, + decoded: OnceLock::new(), + } + } + + pub fn decode(&self) -> Result<&DecodedMultikey, InvalidPublicKey> { + self.decoded + .get_or_init(|| { + let pk_multi_encoded = MultiEncodedBuf::new(self.encoded.decode()?.1)?; + pk_multi_encoded.decode().map_err(Into::into) + }) + .as_ref() + .map_err(Clone::clone) + } +} + +impl From for PublicKey { + fn from(value: MultibaseBuf) -> Self { + Self::from_multibase(value) + } +} + +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.encoded == other.encoded + } +} + +impl Eq for PublicKey {} + +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PublicKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.encoded.cmp(&other.encoded) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.encoded.hash(state) + } +} + +impl FromStr for PublicKey { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result { + MultibaseBuf::from_str(s).map(Self::from_multibase) + } +} + +impl linked_data::LinkedDataResource for PublicKey +where + V: IriVocabularyMut, +{ + fn interpretation( + &self, + vocabulary: &mut V, + interpretation: &mut I, + ) -> linked_data::ResourceInterpretation { + self.encoded.interpretation(vocabulary, interpretation) + } +} + +impl linked_data::LinkedDataPredicateObjects for PublicKey +where + V: IriVocabularyMut, +{ + fn visit_objects(&self, visitor: S) -> Result + where + S: linked_data::PredicateObjectsVisitor, + { + self.encoded.visit_objects(visitor) + } +} + +impl linked_data::LinkedDataSubject for PublicKey { + fn visit_subject(&self, visitor: S) -> Result + where + S: linked_data::SubjectVisitor, + { + self.encoded.visit_subject(visitor) + } +} + +impl linked_data::LinkedDataDeserializeSubject for PublicKey +where + V: LiteralVocabulary, + I: ReverseIriInterpretation + ReverseLiteralInterpretation, +{ + fn deserialize_subject_in( + vocabulary: &V, + interpretation: &I, + dataset: &D, + graph: Option<&I::Resource>, + resource: &::Resource, + context: linked_data::Context, + ) -> Result + where + D: PatternMatchingDataset, + { + Ok(Self::from_multibase(MultibaseBuf::deserialize_subject_in( + vocabulary, + interpretation, + dataset, + graph, + resource, + context, + )?)) + } +} + +#[derive(Debug, Clone)] #[non_exhaustive] pub enum DecodedMultikey { #[cfg(feature = "ed25519")] From 8fdf9047edb62c7081da7809a5d2f159324b3844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 5 Jul 2024 17:17:48 +0200 Subject: [PATCH 22/34] A few renamings. Unification of the signature options and verification options interface. --- crates/bbs/src/lib.rs | 4 +- crates/claims/core/src/verification/mod.rs | 13 ++++ .../core/src/canonicalization.rs | 2 +- .../data-integrity/core/src/proof/mod.rs | 13 +++- .../core/src/suite/configuration.rs | 72 +++++++++++++++---- .../data-integrity/core/src/suite/mod.rs | 14 +++- .../core/src/suite/standard/mod.rs | 25 ++++--- .../core/src/suite/standard/transformation.rs | 6 +- .../core/src/suite/standard/verification.rs | 2 +- .../core/src/suite/verification.rs | 3 +- .../crates/data-integrity/src/any/macros.rs | 18 +++-- .../unspecified/eip712_signature_2021.rs | 2 +- .../suites/src/suites/w3c/bbs_2023/derive.rs | 2 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 31 +++++--- .../src/suites/w3c/bbs_2023/signature/base.rs | 2 +- .../src/suites/w3c/bbs_2023/signature/mod.rs | 4 +- .../w3c/bbs_2023/transformation/base.rs | 11 +-- .../suites/w3c/bbs_2023/transformation/mod.rs | 32 +++++---- .../src/suites/w3c/bbs_2023/verification.rs | 2 +- .../w3c/ethereum_eip712_signature_2021.rs | 2 +- 20 files changed, 184 insertions(+), 76 deletions(-) diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 2ce0b6c70..9d5d89b65 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -85,7 +85,7 @@ impl MultiSigningMethod for Multikey { Bbs::Baseline { header } => Signature::>::sign( Some(messages), secret, - &pk, + pk, Some(&header), ) .map_err(MessageSignatureError::signature_failed)? @@ -99,7 +99,7 @@ impl MultiSigningMethod for Multikey { let signer_blind = signer_blind.map(|b| BlindFactor::from_bytes(&b).unwrap()); BlindSignature::>::blind_sign( secret, - &pk, + pk, commitment_with_proof.as_deref(), Some(&header), Some(messages), diff --git a/crates/claims/core/src/verification/mod.rs b/crates/claims/core/src/verification/mod.rs index 430dd73d4..0a34f9a56 100644 --- a/crates/claims/core/src/verification/mod.rs +++ b/crates/claims/core/src/verification/mod.rs @@ -104,6 +104,19 @@ pub enum Invalid { Proof(#[from] InvalidProof), } +/// Arbitrary resource provider. +pub trait ResourceProvider { + /// Returns a reference to the resource of type `T`. + fn get_resource(&self) -> &T; +} + +/// Anything can return the unit resource. +impl ResourceProvider<()> for T { + fn get_resource(&self) -> &() { + &() + } +} + /// Type that provides a public key resolver. pub trait ResolverProvider { /// Public key resolver. diff --git a/crates/claims/crates/data-integrity/core/src/canonicalization.rs b/crates/claims/crates/data-integrity/core/src/canonicalization.rs index 2c6596c8e..449f3dbb3 100644 --- a/crates/claims/crates/data-integrity/core/src/canonicalization.rs +++ b/crates/claims/crates/data-integrity/core/src/canonicalization.rs @@ -39,7 +39,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, - _transformation_options: Option>, + _transformation_options: TransformationOptions, ) -> Result { let mut ld = LdEnvironment::default(); diff --git a/crates/claims/crates/data-integrity/core/src/proof/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/mod.rs index 18a8e1ae6..ee77a36c9 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/mod.rs @@ -1,12 +1,14 @@ use crate::suite::bounds::{OptionsRefOf, SignatureRefOf, VerificationMethodRefOf}; -use crate::suite::{CryptographicSuiteVerification, SerializeCryptographicSuite}; +use crate::suite::{ + CryptographicSuiteVerification, InputVerificationOptions, SerializeCryptographicSuite, +}; use crate::{ CloneCryptographicSuite, CryptographicSuite, DataIntegrity, DebugCryptographicSuite, DeserializeCryptographicSuite, }; use educe::Educe; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{AttachProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{AttachProof, ProofValidationError, ProofValidity, ResourceProvider}; use ssi_core::{one_or_many::OneOrManyRef, OneOrMany}; use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwned}; use std::collections::BTreeMap; @@ -221,14 +223,18 @@ impl fmt::Debug for Proof { impl ssi_claims_core::ValidateProof for Proof where S: CryptographicSuiteVerification, + V: ResourceProvider>, { async fn validate_proof<'a>( &'a self, verifier: &'a V, claims: &'a T, ) -> Result { + let transformation_options = self + .suite() + .configure_verification(verifier.get_resource())?; self.suite() - .verify_proof(verifier, claims, self.borrowed()) + .verify_proof(verifier, claims, self.borrowed(), transformation_options) .await } } @@ -327,6 +333,7 @@ impl From>> for Proofs { impl ssi_claims_core::ValidateProof for Proofs where S: CryptographicSuiteVerification, + V: ResourceProvider>, { async fn validate_proof<'a>( &'a self, diff --git a/crates/claims/crates/data-integrity/core/src/suite/configuration.rs b/crates/claims/crates/data-integrity/core/src/suite/configuration.rs index a0eb2ed1e..dc62e7ae6 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/configuration.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/configuration.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use ssi_claims_core::SignatureError; +use ssi_claims_core::{ProofValidationError, SignatureError}; use ssi_json_ld::syntax::Context; use crate::{CryptographicSuite, ProofConfiguration, ProofOptions}; @@ -8,13 +8,16 @@ use crate::{CryptographicSuite, ProofConfiguration, ProofOptions}; pub type InputVerificationMethod = <::Configuration as ConfigurationAlgorithm>::InputVerificationMethod; pub type InputSuiteOptions = - <::Configuration as ConfigurationAlgorithm>::InputProofOptions; + <::Configuration as ConfigurationAlgorithm>::InputSuiteOptions; pub type InputProofOptions = ProofOptions, InputSuiteOptions>; pub type InputSignatureOptions = <::Configuration as ConfigurationAlgorithm>::InputSignatureOptions; +pub type InputVerificationOptions = + <::Configuration as ConfigurationAlgorithm>::InputVerificationOptions; + pub type TransformationOptions = <::Configuration as ConfigurationAlgorithm>::TransformationOptions; @@ -48,41 +51,72 @@ impl From for SignatureError { } } +impl From for ProofValidationError { + fn from(value: ConfigurationError) -> Self { + Self::other(value) + } +} + pub trait ConfigurationAlgorithm { /// Input type for the verification method. type InputVerificationMethod; /// Input suite-specific proof options. - type InputProofOptions; + /// + /// These options are stored in the `proof` object. + type InputSuiteOptions; - /// Input signature options. + /// Input suite-specific signature options. + /// + /// These options do not appear in the `proof` object. type InputSignatureOptions; + /// Input suite-specific verification options. + /// + /// These options do not appear in the `proof` object. + type InputVerificationOptions; + /// Document transformation options. type TransformationOptions; - fn configure( + fn configure_signature( suite: &S, - proof_options: ProofOptions, - signature_options: Self::InputSignatureOptions, + proof_options: ProofOptions, + signature_options: InputSignatureOptions, ) -> Result<(ProofConfiguration, Self::TransformationOptions), ConfigurationError>; + + fn configure_verification( + suite: &S, + verification_options: &InputVerificationOptions, + ) -> Result; } pub struct NoConfiguration; impl ConfigurationAlgorithm for NoConfiguration { type InputVerificationMethod = S::VerificationMethod; - type InputProofOptions = S::ProofOptions; + type InputSuiteOptions = S::ProofOptions; + type InputSignatureOptions = (); + + type InputVerificationOptions = (); + type TransformationOptions = (); - fn configure( + fn configure_signature( suite: &S, proof_options: ProofOptions, - _: (), - ) -> Result<(ProofConfiguration, ()), ConfigurationError> { + _: InputSignatureOptions, + ) -> Result<(ProofConfiguration, Self::TransformationOptions), ConfigurationError> { Ok((proof_options.into_configuration(suite.clone())?, ())) } + + fn configure_verification( + _suite: &S, + _verification_options: &InputVerificationOptions, + ) -> Result { + Ok(()) + } } pub struct AddProofContext(PhantomData); @@ -92,15 +126,16 @@ where C: Default + Into, { type InputVerificationMethod = S::VerificationMethod; - type InputProofOptions = S::ProofOptions; + type InputSuiteOptions = S::ProofOptions; type InputSignatureOptions = (); + type InputVerificationOptions = (); type TransformationOptions = (); - fn configure( + fn configure_signature( suite: &S, options: ProofOptions, - _: (), - ) -> Result<(ProofConfiguration, ()), ConfigurationError> { + _: InputSignatureOptions, + ) -> Result<(ProofConfiguration, Self::TransformationOptions), ConfigurationError> { let mut result = options.into_configuration(suite.clone())?; result.context = match result.context { None => Some(C::default().into()), @@ -110,4 +145,11 @@ where }; Ok((result, ())) } + + fn configure_verification( + _suite: &S, + _verification_options: &InputVerificationOptions, + ) -> Result { + Ok(()) + } } diff --git a/crates/claims/crates/data-integrity/core/src/suite/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/mod.rs index d740f4065..26e078be3 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -58,12 +58,20 @@ pub trait CryptographicSuite: Clone { fn type_(&self) -> TypeRef; /// Generates a proof configuration from input options. - fn configure( + fn configure_signature( &self, proof_options: InputProofOptions, signature_options: InputSignatureOptions, ) -> Result<(ProofConfiguration, TransformationOptions), ConfigurationError> { - Self::Configuration::configure(self, proof_options, signature_options) + Self::Configuration::configure_signature(self, proof_options, signature_options) + } + + /// Generates a proof configuration from input options. + fn configure_verification( + &self, + verification_options: &InputVerificationOptions, + ) -> Result, ConfigurationError> { + Self::Configuration::configure_verification(self, verification_options) } /// Generates a verifiable document secured with this cryptographic suite. @@ -81,7 +89,7 @@ pub trait CryptographicSuite: Clone { Self: CryptographicSuiteSigning, { let (proof_configuration, transformation_options) = - self.configure(proof_options, signature_options)?; + self.configure_signature(proof_options, signature_options)?; let proof_configuration_ref = proof_configuration.borrowed(); let signature = self .generate_signature( diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs index 395d039f7..952ca6f3c 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/mod.rs @@ -1,7 +1,9 @@ //! Cryptographic suites. use std::borrow::Cow; -use ssi_claims_core::{ProofValidationError, ProofValidity, ResolverProvider, SignatureError}; +use ssi_claims_core::{ + ProofValidationError, ProofValidity, ResolverProvider, ResourceProvider, SignatureError, +}; use ssi_verification_methods_core::{Signer, VerificationMethodResolver, VerificationMethodSet}; use crate::{CryptographicSuite, ProofConfigurationRef, ProofRef, TypeRef}; @@ -20,7 +22,7 @@ pub use verification::*; use super::{ ConfigurationAlgorithm, CryptographicSuiteSigning, CryptographicSuiteVerification, - TransformationOptions, + InputVerificationOptions, TransformationOptions, }; // mod test_bbs; @@ -59,7 +61,7 @@ pub trait StandardCryptographicSuite: Clone { context: &C, unsecured_document: &T, proof_configuration: ProofConfigurationRef<'_, Self>, - transformation_options: Option>, + transformation_options: TransformationOptions, ) -> Result, TransformationError> where Self::Transformation: TypedTransformationAlgorithm, @@ -143,12 +145,7 @@ where .await?; let transformed = self - .transform( - context, - claims, - proof_configuration, - Some(transformation_options), - ) + .transform(context, claims, proof_configuration, transformation_options) .await?; let hashed = self.hash(transformed, proof_configuration, &method)?; @@ -165,7 +162,7 @@ where impl CryptographicSuiteVerification for S where - V: ResolverProvider, + V: ResolverProvider + ResourceProvider>, V::Resolver: VerificationMethodResolver, S::Transformation: TypedTransformationAlgorithm, S::SignatureAlgorithm: VerificationAlgorithm, @@ -175,6 +172,7 @@ where verifier: &V, claims: &C, proof: ProofRef<'_, Self>, + transformation_options: TransformationOptions, ) -> Result { let options = ssi_verification_methods_core::ResolutionOptions { accept: Some(Box::new(Self::VerificationMethod::type_set())), @@ -189,7 +187,12 @@ where let proof_configuration = proof.configuration(); let transformed = self - .transform(verifier, claims, proof_configuration, None) + .transform( + verifier, + claims, + proof_configuration, + transformation_options, + ) .await?; let hashed = self.hash(transformed, proof_configuration, &method)?; diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs index 44c3ca90f..94c1d653c 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/transformation.rs @@ -72,7 +72,7 @@ pub trait TypedTransformationAlgorithm: context: &C, data: &T, proof_configuration: ProofConfigurationRef, - transformation_options: Option>, + transformation_options: TransformationOptions, ) -> Result; } @@ -82,14 +82,14 @@ impl TransformationAlgorithm for JsonObjectTransformat type Output = json_syntax::Object; } -impl TypedTransformationAlgorithm +impl TypedTransformationAlgorithm for JsonObjectTransformation { async fn transform( _context: &C, data: &T, _options: ProofConfigurationRef<'_, S>, - _transformation_options: Option>, + _transformation_options: TransformationOptions, ) -> Result { json_syntax::to_value(data) .map_err(TransformationError::JsonSerialization)? diff --git a/crates/claims/crates/data-integrity/core/src/suite/standard/verification.rs b/crates/claims/crates/data-integrity/core/src/suite/standard/verification.rs index 8b9da6ee6..8537660ea 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/standard/verification.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/standard/verification.rs @@ -5,7 +5,7 @@ use crate::{CryptographicSuite, ProofRef}; pub trait VerificationAlgorithm { fn verify( method: &S::VerificationMethod, - prepared_claims: S::PreparedClaims, + prepared_claims: ::PreparedClaims, proof: ProofRef, ) -> Result; } diff --git a/crates/claims/crates/data-integrity/core/src/suite/verification.rs b/crates/claims/crates/data-integrity/core/src/suite/verification.rs index 599a2e47b..b0106ca58 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/verification.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/verification.rs @@ -1,4 +1,4 @@ -use super::CryptographicSuite; +use super::{CryptographicSuite, TransformationOptions}; use crate::ProofRef; use ssi_claims_core::{ProofValidationError, ProofValidity}; @@ -9,5 +9,6 @@ pub trait CryptographicSuiteVerification: CryptographicSuite { verifier: &V, claims: &T, proof: ProofRef<'_, Self>, + transformation_options: TransformationOptions, ) -> Result; } diff --git a/crates/claims/crates/data-integrity/src/any/macros.rs b/crates/claims/crates/data-integrity/src/any/macros.rs index f53bf8706..26bcfb827 100644 --- a/crates/claims/crates/data-integrity/src/any/macros.rs +++ b/crates/claims/crates/data-integrity/src/any/macros.rs @@ -182,6 +182,7 @@ macro_rules! crypto_suites { verifier: &V, claims: &T, proof: ssi_data_integrity_core::ProofRef<'_, Self>, + transformation_options: () ) -> Result { match self { $( @@ -197,7 +198,8 @@ macro_rules! crypto_suites { &ssi_data_integrity_suites::$name, &verifier, claims, - Self::project_proof(proof) + Self::project_proof(proof), + transformation_options ).await }, )* @@ -398,11 +400,12 @@ macro_rules! crypto_suites { #[allow(unused_variables)] impl ssi_data_integrity_core::suite::ConfigurationAlgorithm for AnyConfigurationAlgorithm { type InputVerificationMethod = ssi_verification_methods::AnyMethod; - type InputProofOptions = crate::AnyInputSuiteOptions; + type InputSuiteOptions = crate::AnyInputSuiteOptions; type InputSignatureOptions = (); + type InputVerificationOptions = (); type TransformationOptions = (); - fn configure( + fn configure_signature( suite: &AnySuite, options: ssi_data_integrity_core::suite::InputProofOptions, signature_options: () @@ -415,7 +418,7 @@ macro_rules! crypto_suites { options )?; - let (proof_configuration, transformation_options) = ::configure( + let (proof_configuration, transformation_options) = ::configure_signature( &ssi_data_integrity_suites::$name, options, signature_options @@ -437,6 +440,13 @@ macro_rules! crypto_suites { )) } } + + fn configure_verification( + _suite: &AnySuite, + _verification_options: &() + ) -> Result<(), ssi_data_integrity_core::suite::ConfigurationError> { + Ok(()) + } } #[derive(Debug, Clone)] diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs index 60dfdb26a..443b3b730 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs @@ -112,7 +112,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, Eip712Signature2021>, - _transformation_options: Option<()>, + _transformation_options: (), ) -> Result { let mut ld = LdEnvironment::default(); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 8845cd749..0d1972fe0 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -232,7 +232,7 @@ where let bbs_proof = match (&feature_option, &decoded_base_proof.description) { (DerivedFeatureOption::Baseline, Bbs2023SignatureDescription::Baseline) => proof_gen( - &pk, + pk, &decoded_base_proof.signature_bytes, &decoded_base_proof.bbs_header, presentation_header, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 28fb0315a..cd4929498 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -10,7 +10,7 @@ use ssi_di_sd_primitives::JsonPointerBuf; use ssi_verification_methods::Multikey; pub(crate) mod transformation; -pub use transformation::{Bbs2023Transformation, Transformed}; +pub use transformation::{Bbs2023Transformation, Bbs2023TransformationOptions, Transformed}; mod hashing; pub use hashing::{Bbs2023Hashing, HashData}; @@ -60,7 +60,7 @@ impl TryFrom for Bbs2023 { } #[derive(Clone)] -pub struct Bbs2023InputOptions { +pub struct Bbs2023SignatureOptions { pub mandatory_pointers: Vec, pub feature_option: FeatureOption, @@ -91,20 +91,33 @@ impl ConfigurationAlgorithm for Bbs2023Configuration { type InputVerificationMethod = Multikey; /// Input suite-specific proof options. - type InputProofOptions = (); + type InputSuiteOptions = (); /// Input signature options. - type InputSignatureOptions = Bbs2023InputOptions; + type InputSignatureOptions = Bbs2023SignatureOptions; + + type InputVerificationOptions = (); /// Document transformation options. - type TransformationOptions = Bbs2023InputOptions; + type TransformationOptions = Bbs2023TransformationOptions; - fn configure( + fn configure_signature( type_: &Bbs2023, options: InputProofOptions, - signature_options: Bbs2023InputOptions, - ) -> Result<(ProofConfiguration, Bbs2023InputOptions), ConfigurationError> { + signature_options: Bbs2023SignatureOptions, + ) -> Result<(ProofConfiguration, Bbs2023TransformationOptions), ConfigurationError> + { let proof_configuration = options.into_configuration(*type_)?; - Ok((proof_configuration, signature_options)) + Ok(( + proof_configuration, + Bbs2023TransformationOptions::BaseSignature(signature_options), + )) + } + + fn configure_verification( + _suite: &Bbs2023, + _verification_options: &ssi_data_integrity_core::suite::InputVerificationOptions, + ) -> Result { + Ok(Bbs2023TransformationOptions::DerivedVerification) } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index 0bccda0e6..6bcdf60da 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -97,7 +97,7 @@ where Ok(Bbs2023Signature::encode_base( &signature, bbs_header, - &public_key, + public_key, hmac_key, mandatory_pointers, description, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs index b6dd98fff..cfb9a5458 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs @@ -83,7 +83,7 @@ mod tests { use crate::{ bbs_2023::{ - hashing::BaseHashData, transformation::TransformedBase, Bbs2023InputOptions, + hashing::BaseHashData, transformation::TransformedBase, Bbs2023SignatureOptions, FeatureOption, HashData, HmacKey, }, Bbs2023, @@ -185,7 +185,7 @@ _:b5 \"2023\"^^( loader: &impl ssi_json_ld::Loader, unsecured_document: &T, canonical_configuration: Vec, - transform_options: Bbs2023InputOptions, + transform_options: Bbs2023SignatureOptions, ) -> Result where T: JsonLdNodeObject + Expandable, @@ -84,7 +84,10 @@ mod tests { use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use crate::{ - bbs_2023::{Bbs2023InputOptions, Bbs2023Transformation, FeatureOption, HmacKey}, + bbs_2023::{ + Bbs2023SignatureOptions, Bbs2023Transformation, Bbs2023TransformationOptions, + FeatureOption, HmacKey, + }, Bbs2023, }; @@ -165,7 +168,7 @@ mod tests { &context, &*UNSIGNED_BASE_DOCUMENT, proof_configuration.borrowed(), - Some(Bbs2023InputOptions { + Bbs2023TransformationOptions::BaseSignature(Bbs2023SignatureOptions { mandatory_pointers: MANDATORY_POINTERS.clone(), feature_option: FeatureOption::Baseline, commitment_with_proof: None, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs index c1f4f8cf6..66a5a5af1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/transformation/mod.rs @@ -1,4 +1,4 @@ -use super::{Bbs2023InputOptions, HmacKey}; +use super::{Bbs2023SignatureOptions, HmacKey}; use crate::Bbs2023; use hmac::Hmac; use k256::sha2::Sha256; @@ -32,7 +32,7 @@ where context: &C, unsecured_document: &T, proof_configuration: ProofConfigurationRef<'_, Bbs2023>, - transformation_options: Option, + transformation_options: Bbs2023TransformationOptions, ) -> Result { let canonical_configuration = proof_configuration .expand(context, unsecured_document) @@ -41,15 +41,17 @@ where .nquads_lines(); match transformation_options { - Some(transform_options) => base::base_proof_transformation( - context.loader(), - unsecured_document, - canonical_configuration, - transform_options, - ) - .await - .map(Transformed::Base), - None => derived::create_verify_data1( + Bbs2023TransformationOptions::BaseSignature(transform_options) => { + base::base_proof_transformation( + context.loader(), + unsecured_document, + canonical_configuration, + transform_options, + ) + .await + .map(Transformed::Base) + } + Bbs2023TransformationOptions::DerivedVerification => derived::create_verify_data1( context.loader(), unsecured_document, canonical_configuration, @@ -104,7 +106,7 @@ impl Transformed { /// See: #[derive(Clone)] pub struct TransformedBase { - pub options: Bbs2023InputOptions, + pub options: Bbs2023SignatureOptions, pub mandatory: Vec, pub non_mandatory: Vec, pub hmac_key: HmacKey, @@ -117,3 +119,9 @@ pub struct TransformedDerived { pub quads: Vec, pub canonical_id_map: NormalizingSubstitution, } + +#[derive(Clone)] +pub enum Bbs2023TransformationOptions { + BaseSignature(Bbs2023SignatureOptions), + DerivedVerification, +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs index 477f7b1c9..f7ff118b8 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/verification.rs @@ -44,7 +44,7 @@ impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { match data.feature_option { DerivedFeatureOption::Baseline => proof_verify( - &public_key, + public_key, &data.base_signature, &bbs_header, data.presentation_header.as_deref(), diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs index 68cd4620f..58fd527c1 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs @@ -275,7 +275,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, - _transformation_options: Option>, + _transformation_options: TransformationOptions, ) -> Result { let types = match proof_configuration.options.types() { Some(TypesOrURI::Object(types)) => Some(types.clone()), From 8ddd4d6cf22d58bfe38dd712cbadd7587ef7a683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Mon, 8 Jul 2024 15:24:55 +0200 Subject: [PATCH 23/34] Add `SelectiveCryptographicSuite` trait, providing a nice interface to SD suites. Add `DataIntegrity::select` method. --- .../crates/data-integrity/core/src/lib.rs | 28 +++++++++++ .../data-integrity/core/src/suite/mod.rs | 3 ++ .../data-integrity/core/src/suite/sd.rs | 49 ++++++++++++++++++ .../suites/src/suites/w3c/bbs_2023/derive.rs | 36 ++++++++----- .../suites/src/suites/w3c/bbs_2023/mod.rs | 50 +++++++++++++++++-- crates/json-ld/src/lib.rs | 1 - 6 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 crates/claims/crates/data-integrity/core/src/suite/sd.rs diff --git a/crates/claims/crates/data-integrity/core/src/lib.rs b/crates/claims/crates/data-integrity/core/src/lib.rs index ef54e663c..463f1a32f 100644 --- a/crates/claims/crates/data-integrity/core/src/lib.rs +++ b/crates/claims/crates/data-integrity/core/src/lib.rs @@ -25,6 +25,7 @@ pub use suite::{ CloneCryptographicSuite, CryptographicSuite, DebugCryptographicSuite, DeserializeCryptographicSuite, SerializeCryptographicSuite, StandardCryptographicSuite, }; +use suite::{CryptographicSuiteSelect, SelectionError}; pub use document::*; #[doc(hidden)] @@ -72,6 +73,33 @@ impl DataIntegrity { { VerifiableClaims::verify(self, params).await } + + /// Select a subset of claims to disclose. + /// + /// The `params` argument is similar to the verification parameters of the + /// `verify` function. It must provides resources necessary to the selection + /// of claims. This depends on the cryptosuite type `S`, but probably + /// includes a verification method resolver. + /// Using `ssi::claims::VerificationParameters` will work in most cases. + pub async fn select

( + &self, + params: P, + options: S::SelectionOptions, + ) -> Result, SelectionError> + where + S: CryptographicSuiteSelect, + { + match self.proofs.split_first() { + Some((proof, [])) => { + proof + .suite() + .select(&self.claims, proof.borrowed(), params, options) + .await + } + Some(_) => Err(SelectionError::AmbiguousProof), + None => Err(SelectionError::MissingProof), + } + } } // impl DataIntegrity diff --git a/crates/claims/crates/data-integrity/core/src/suite/mod.rs b/crates/claims/crates/data-integrity/core/src/suite/mod.rs index 26e078be3..a96b1d834 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -25,6 +25,9 @@ use self::standard::{HashingError, TransformationError}; pub mod standard; pub use standard::StandardCryptographicSuite; +mod sd; +pub use sd::*; + /// Cryptographic suite. /// /// See: diff --git a/crates/claims/crates/data-integrity/core/src/suite/sd.rs b/crates/claims/crates/data-integrity/core/src/suite/sd.rs new file mode 100644 index 000000000..c7dc8275e --- /dev/null +++ b/crates/claims/crates/data-integrity/core/src/suite/sd.rs @@ -0,0 +1,49 @@ +use ssi_verification_methods_core::VerificationMethodResolutionError; + +use crate::{CryptographicSuite, DataIntegrity, ProofRef}; + +#[derive(Debug, thiserror::Error)] +pub enum SelectionError { + #[error("missing proof")] + MissingProof, + + #[error("ambiguous proof")] + AmbiguousProof, + + #[error(transparent)] + VerificationMethodResolution(#[from] VerificationMethodResolutionError), + + #[error("proof derivation failed: {0}")] + ProofDerivation(String), + + #[error("non-selective cryptographic suite")] + NonSelectiveSuite, +} + +impl SelectionError { + pub fn proof_derivation(e: impl ToString) -> Self { + Self::ProofDerivation(e.to_string()) + } +} + +/// Cryptographic suite with selective disclosure capabilities. +pub trait SelectiveCryptographicSuite: CryptographicSuite { + /// Options specifying what claims to select and how. + type SelectionOptions; +} + +/// Cryptographic suite with selective disclosure capabilities on a given type +/// `T`. +/// +/// Provides the `select` method on the cryptosuite. +pub trait CryptographicSuiteSelect: SelectiveCryptographicSuite { + /// Select a subset of claims to disclose. + #[allow(async_fn_in_trait)] + async fn select( + &self, + unsecured_document: &T, + proof: ProofRef<'_, Self>, + params: P, + options: Self::SelectionOptions, + ) -> Result, SelectionError>; +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 0d1972fe0..0466815d3 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -5,7 +5,7 @@ use k256::sha2::Sha256; use rdf_types::{BlankIdBuf, Quad}; use serde::{Deserialize, Serialize}; use ssi_bbs::{proof_gen, ProofGenFailed}; -use ssi_data_integrity_core::{DataIntegrity, Proof}; +use ssi_data_integrity_core::{DataIntegrity, Proof, ProofRef}; use ssi_di_sd_primitives::{ group::{canonicalize_and_group, GroupError}, select::{select_json_ld, DanglingJsonPointer}, @@ -83,7 +83,6 @@ impl From for DeriveError { } pub struct DeriveOptions { - pub base_proof: Proof, pub selective_pointers: Vec, pub presentation_header: Option>, pub feature_option: DerivedFeatureOption, @@ -113,6 +112,7 @@ pub async fn add_derived_proof( unsecured_document: &T, verification_method: &Multikey, options: DeriveOptions, + base_proof: ProofRef<'_, Bbs2023>, ) -> Result, DeriveError> where T: Serialize + JsonLdNodeObject + Expandable, @@ -122,22 +122,34 @@ where loader, unsecured_document, verification_method, - &options.base_proof.signature, + base_proof.signature, options.selective_pointers, options.presentation_header.as_deref(), &options.feature_option, ) .await?; - let mut new_proof = options.base_proof; - new_proof.signature = Bbs2023Signature::encode_derived( - &data.bbs_proof, - &data.label_map, - &data.mandatory_indexes, - &data.selective_indexes, - options.presentation_header.as_deref(), - &options.feature_option, - )?; + let new_proof = Proof { + context: base_proof.context.cloned(), + type_: Bbs2023, + created: base_proof.created, + verification_method: base_proof.verification_method.cloned(), + proof_purpose: base_proof.proof_purpose, + expires: base_proof.expires, + domains: base_proof.domains.to_vec(), + challenge: base_proof.challenge.map(ToOwned::to_owned), + nonce: base_proof.nonce.map(ToOwned::to_owned), + options: *base_proof.options, + signature: Bbs2023Signature::encode_derived( + &data.bbs_proof, + &data.label_map, + &data.mandatory_indexes, + &data.selective_indexes, + options.presentation_header.as_deref(), + &options.feature_option, + )?, + extra_properties: base_proof.extra_properties.clone(), + }; Ok(DataIntegrity::new(data.reveal_document, new_proof.into())) } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index cd4929498..8b94a0b57 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -1,13 +1,20 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: +use serde::Serialize; +use ssi_claims_core::ResolverProvider; use ssi_data_integrity_core::{ - suite::{ConfigurationAlgorithm, ConfigurationError, InputProofOptions}, - CryptosuiteStr, ProofConfiguration, StandardCryptographicSuite, Type, TypeRef, - UnsupportedProofSuite, + suite::{ + ConfigurationAlgorithm, ConfigurationError, CryptographicSuiteSelect, InputProofOptions, + SelectionError, SelectiveCryptographicSuite, + }, + CryptosuiteStr, DataIntegrity, ProofConfiguration, ProofRef, StandardCryptographicSuite, Type, + TypeRef, UnsupportedProofSuite, }; use ssi_di_sd_primitives::JsonPointerBuf; -use ssi_verification_methods::Multikey; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; +use ssi_rdf::LexicalInterpretation; +use ssi_verification_methods::{Multikey, VerificationMethodResolver}; pub(crate) mod transformation; pub use transformation::{Bbs2023Transformation, Bbs2023TransformationOptions, Transformed}; @@ -30,6 +37,41 @@ mod tests; #[derive(Debug, Clone, Copy)] pub struct Bbs2023; +impl SelectiveCryptographicSuite for Bbs2023 { + type SelectionOptions = DeriveOptions; +} + +impl CryptographicSuiteSelect for Bbs2023 +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, + P: JsonLdLoaderProvider + ResolverProvider, + P::Resolver: VerificationMethodResolver, +{ + async fn select( + &self, + document: &T, + proof: ProofRef<'_, Self>, + params: P, + options: DeriveOptions, + ) -> Result, SelectionError> { + let verification_method = params + .resolver() + .resolve_verification_method(None, Some(proof.verification_method)) + .await?; + + add_derived_proof( + params.loader(), + document, + &verification_method, + options, + proof, + ) + .await + .map_err(SelectionError::proof_derivation) + } +} + impl StandardCryptographicSuite for Bbs2023 { type Configuration = Bbs2023Configuration; diff --git a/crates/json-ld/src/lib.rs b/crates/json-ld/src/lib.rs index 2b1d5e1fe..9554881e2 100644 --- a/crates/json-ld/src/lib.rs +++ b/crates/json-ld/src/lib.rs @@ -2,7 +2,6 @@ use std::{borrow::Cow, hash::Hash}; use json_ld::expansion::Action; -use json_ld::Expand; use linked_data::{LinkedData, LinkedDataResource, LinkedDataSubject}; pub use json_ld; From ec9b7639820c6cfdf305b2787c31f2a8b9767b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 17:09:01 +0200 Subject: [PATCH 24/34] Add `Bbs2023` variant to `AnySuite`. --- Cargo.toml | 3 +- crates/bbs/Cargo.toml | 5 +- crates/bbs/src/lib.rs | 103 ++- crates/claims/core/Cargo.toml | 1 + crates/claims/core/src/signature.rs | 68 ++ .../claims/crates/data-integrity/Cargo.toml | 4 +- .../crates/data-integrity/core/src/lib.rs | 26 +- .../data-integrity/core/src/proof/mod.rs | 6 + .../data-integrity/core/src/signing/jws.rs | 15 +- .../data-integrity/core/src/signing/mod.rs | 26 +- .../core/src/signing/multibase.rs | 9 +- .../data-integrity/sd-primitives/src/lib.rs | 2 + .../sd-primitives/src/skolemize.rs | 3 +- .../crates/data-integrity/src/any/macros.rs | 69 +- .../crates/data-integrity/src/any/mod.rs | 4 + .../crates/data-integrity/src/any/protocol.rs | 37 +- .../crates/data-integrity/src/any/sd.rs | 96 +++ .../data-integrity/src/any/signature.rs | 93 ++- .../src/any/signature_options.rs | 46 ++ .../data-integrity/src/any/suite/mod.rs | 11 +- .../claims/crates/data-integrity/src/lib.rs | 2 + .../suites/src/eip712/signature.rs | 2 +- ...ecdsa_secp256k1_recovery_signature_2020.rs | 2 +- .../unspecified/eip712_signature_2021.rs | 8 +- .../ethereum_personal_signature_2021.rs | 8 +- .../suites/src/suites/unspecified/tezos.rs | 13 +- ...e20_base58_check_encoded_signature_2021.rs | 2 +- ...e20_base58_check_encoded_signature_2021.rs | 2 +- .../tezos/tezos_jcs_signature_2021.rs | 2 +- .../unspecified/tezos/tezos_signature_2021.rs | 3 +- .../suites/src/suites/w3c/bbs_2023/derive.rs | 3 +- .../suites/src/suites/w3c/bbs_2023/hashing.rs | 6 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 6 +- .../src/suites/w3c/bbs_2023/signature/base.rs | 27 +- .../src/suites/w3c/bbs_2023/signature/mod.rs | 26 +- .../suites/w3c/bbs_2023/transformation/mod.rs | 2 +- .../suites/src/suites/w3c/ecdsa_rdfc_2019.rs | 32 + .../w3c/ecdsa_secp256k1_signature_2019.rs | 2 +- .../w3c/ecdsa_secp256r1_signature_2019.rs | 2 +- .../src/suites/w3c/ed25519_signature_2018.rs | 2 +- .../src/suites/w3c/ed25519_signature_2020.rs | 2 +- .../suites/src/suites/w3c/eddsa_2022.rs | 2 +- .../w3c/ethereum_eip712_signature_2021.rs | 11 +- .../src/suites/w3c/rsa_signature_2018.rs | 4 +- .../data-integrity/suites/tests/suite.rs | 6 +- crates/crypto/Cargo.toml | 5 +- crates/crypto/src/algorithm/mod.rs | 601 ++++++++++++++++++ crates/crypto/src/lib.rs | 3 + crates/dids/methods/key/Cargo.toml | 1 + crates/dids/methods/key/src/lib.rs | 114 +--- crates/json-ld/Cargo.toml | 3 +- crates/jwk/Cargo.toml | 3 + crates/jwk/src/algorithm.rs | 273 +++----- crates/jwk/src/bbs.rs | 201 ++++++ crates/jwk/src/error.rs | 3 + crates/jwk/src/lib.rs | 88 +-- crates/jwk/src/multicodec.rs | 178 ++++++ crates/verification-methods/Cargo.toml | 4 +- crates/verification-methods/core/Cargo.toml | 4 + crates/verification-methods/core/src/lib.rs | 34 +- .../core/src/signature/protocol.rs | 23 +- .../core/src/signature/signer/local.rs | 28 +- .../core/src/signature/signer/mod.rs | 139 ++-- crates/verification-methods/src/methods.rs | 82 ++- .../methods/unspecified/aleo_method_2021.rs | 5 +- .../blockchain_verification_method_2021.rs | 4 +- .../methods/unspecified/eip712_method_2021.rs | 8 +- .../methods/unspecified/solana_method_2021.rs | 4 +- ...digest_size20_base58_check_encoded_2021.rs | 10 +- ...digest_size20_base58_check_encoded_2021.rs | 14 +- .../unspecified/tezos/tezos_method_2021.rs | 13 +- .../ecdsa_secp_256k1_recovery_method_2020.rs | 15 +- .../ecdsa_secp_256k1_verification_key_2019.rs | 10 +- .../ecdsa_secp_256r1_verification_key_2019.rs | 10 +- .../w3c/ed25519_verification_key_2018.rs | 16 +- .../w3c/ed25519_verification_key_2020.rs | 18 +- .../src/methods/w3c/json_web_key_2020.rs | 6 +- .../src/methods/w3c/multikey.rs | 65 +- .../methods/w3c/rsa_verification_key_2018.rs | 6 +- src/lib.rs | 3 + tests/vcdm_v2_sign.rs | 61 +- 81 files changed, 2041 insertions(+), 818 deletions(-) create mode 100644 crates/claims/crates/data-integrity/src/any/sd.rs create mode 100644 crates/claims/crates/data-integrity/src/any/signature_options.rs create mode 100644 crates/crypto/src/algorithm/mod.rs create mode 100644 crates/jwk/src/bbs.rs create mode 100644 crates/jwk/src/multicodec.rs diff --git a/Cargo.toml b/Cargo.toml index 1e18501d1..a5b2e193c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,7 +199,7 @@ ethereum = ["ssi-claims/ethereum", "ssi-jwk/eip", "ssi-dids/eip"] ripemd-160 = ["ssi-jwk/ripemd-160", "ssi-dids/ripemd-160"] ## Enable bbs. -bbs = ["ssi-crypto/bbs", "ssi-claims/bbs"] +bbs = ["ssi-crypto/bbs", "ssi-claims/bbs", "ssi-bbs"] ## Use the Ring crate for crypto operations ring = ["ssi-jwk/ring", "ssi-jws/ring", "ssi-crypto/ring"] @@ -231,6 +231,7 @@ ssi-ucan.workspace = true ssi-zcap-ld.workspace = true ssi-multicodec.workspace = true ssi-ssh.workspace = true +ssi-bbs = { workspace = true, optional = true } xsd-types.workspace = true # public reexport document-features = "0.2" diff --git a/crates/bbs/Cargo.toml b/crates/bbs/Cargo.toml index b08cb8f81..512fe015b 100644 --- a/crates/bbs/Cargo.toml +++ b/crates/bbs/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-bbs/" [dependencies] +ssi-crypto.workspace = true ssi-claims-core.workspace = true -ssi-verification-methods = { workspace = true, features = ["bls12-381"] } -zkryptium = "0.2.2" \ No newline at end of file +zkryptium = "0.2.2" +rand.workspace = true \ No newline at end of file diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 9d5d89b65..36af61b3f 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -1,13 +1,20 @@ -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; -use ssi_verification_methods::{ - multikey::DecodedMultikey, MessageSignatureError, MultiSigningMethod, Multikey, -}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; +use ssi_crypto::algorithm::BbsParameters; pub use zkryptium::bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}; use zkryptium::{ - bbsplus::{ciphersuites::Bls12381Sha256, commitment::BlindFactor}, - schemes::algorithms::BBSplus, + bbsplus::{ + ciphersuites::{BbsCiphersuite, Bls12381Sha256}, + commitment::BlindFactor, + }, + keys::pair::KeyPair, + schemes::{ + algorithms::BBSplus, + generics::{BlindSignature, Signature}, + }, }; +pub use ssi_crypto::algorithm::Bbs; + #[derive(Debug)] pub struct ProofGenFailed; @@ -57,60 +64,46 @@ pub fn proof_verify( ) .map_err(|_| InvalidProof::Signature)) } -pub enum Bbs { - Baseline { - header: [u8; 64], - }, - Blind { - header: [u8; 64], - commitment_with_proof: Option>, - signer_blind: Option<[u8; 32]>, - }, -} - -impl MultiSigningMethod for Multikey { - fn sign_bytes_multi( - &self, - secret: &BBSplusSecretKey, - algorithm: Bbs, - messages: &[Vec], - ) -> Result, MessageSignatureError> { - use zkryptium::schemes::generics::{BlindSignature, Signature}; - - let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? else { - return Err(MessageSignatureError::InvalidPublicKey); - }; - let signature = match algorithm { - Bbs::Baseline { header } => Signature::>::sign( - Some(messages), - secret, +pub fn sign( + params: BbsParameters, + sk: &BBSplusSecretKey, + pk: &BBSplusPublicKey, + messages: &[Vec], +) -> Result, MessageSignatureError> { + match params { + BbsParameters::Baseline { header } => { + Ok( + Signature::>::sign(Some(messages), sk, pk, Some(&header)) + .map_err(MessageSignatureError::signature_failed)? + .to_bytes() + .to_vec(), + ) + } + BbsParameters::Blind { + header, + commitment_with_proof, + signer_blind, + } => { + let signer_blind = signer_blind.map(|b| BlindFactor::from_bytes(&b).unwrap()); + Ok(BlindSignature::>::blind_sign( + sk, pk, + commitment_with_proof.as_deref(), Some(&header), + Some(messages), + signer_blind.as_ref(), ) .map_err(MessageSignatureError::signature_failed)? .to_bytes() - .to_vec(), - Bbs::Blind { - header, - commitment_with_proof, - signer_blind, - } => { - let signer_blind = signer_blind.map(|b| BlindFactor::from_bytes(&b).unwrap()); - BlindSignature::>::blind_sign( - secret, - pk, - commitment_with_proof.as_deref(), - Some(&header), - Some(messages), - signer_blind.as_ref(), - ) - .map_err(MessageSignatureError::signature_failed)? - .to_bytes() - .to_vec() - } - }; - - Ok(signature) + .to_vec()) + } } } + +pub fn generate_secret_key(rng: &mut impl rand::RngCore) -> BBSplusSecretKey { + let mut key_material = [0; Bls12381Sha256::IKM_LEN as usize]; + rng.fill_bytes(&mut key_material); + let pair = KeyPair::>::generate(&key_material, None, None).unwrap(); + pair.into_parts().0 +} diff --git a/crates/claims/core/Cargo.toml b/crates/claims/core/Cargo.toml index e4e1a7df5..8063b7c63 100644 --- a/crates/claims/core/Cargo.toml +++ b/crates/claims/core/Cargo.toml @@ -27,6 +27,7 @@ linked-data = ["dep:linked-data"] [dependencies] ssi-core.workspace = true +ssi-crypto.workspace = true educe.workspace = true thiserror.workspace = true chrono.workspace = true diff --git a/crates/claims/core/src/signature.rs b/crates/claims/core/src/signature.rs index db076554a..6285a56e1 100644 --- a/crates/claims/core/src/signature.rs +++ b/crates/claims/core/src/signature.rs @@ -1,5 +1,6 @@ use core::fmt; +use ssi_crypto::algorithm::{AlgorithmError, UnsupportedAlgorithm}; use ssi_eip712::Eip712TypesLoaderProvider; use ssi_json_ld::JsonLdLoaderProvider; @@ -58,6 +59,73 @@ impl From for SignatureError { } } +#[derive(Debug, thiserror::Error)] +pub enum MessageSignatureError { + #[error("0")] + SignatureFailed(String), + + #[error("invalid signature client query")] + InvalidQuery, + + #[error("invalid signer response")] + InvalidResponse, + + #[error("invalid public key")] + InvalidPublicKey, + + #[error("invalid secret key")] + InvalidSecretKey, + + #[error("missing signature algorithm")] + MissingAlgorithm, + + #[error("unsupported signature algorithm `{0}`")] + UnsupportedAlgorithm(String), + + #[error("unsupported verification method `{0}`")] + UnsupportedVerificationMethod(String), + + /// Signature algorithm does not support multi-message signing. + #[error("too many messages")] + TooManyMessages, + + /// Signature algorithm requires at least one message. + #[error("missing message")] + MissingMessage, +} + +impl MessageSignatureError { + pub fn signature_failed(e: impl ToString) -> Self { + Self::SignatureFailed(e.to_string()) + } +} + +impl From for SignatureError { + fn from(value: MessageSignatureError) -> Self { + match value { + MessageSignatureError::MissingAlgorithm => Self::MissingAlgorithm, + MessageSignatureError::UnsupportedAlgorithm(name) => Self::UnsupportedAlgorithm(name), + MessageSignatureError::InvalidSecretKey => Self::InvalidSecretKey, + other => Self::other(other), + } + } +} + +impl From for MessageSignatureError { + fn from(value: AlgorithmError) -> Self { + match value { + AlgorithmError::Missing => Self::MissingAlgorithm, + AlgorithmError::Unsupported(a) => Self::UnsupportedAlgorithm(a.to_string()), + } + } +} + +impl From for MessageSignatureError { + fn from(value: UnsupportedAlgorithm) -> Self { + Self::UnsupportedAlgorithm(value.0.to_string()) + } +} + /// Signature environment. /// /// This is a common environment implementation expected to work with most diff --git a/crates/claims/crates/data-integrity/Cargo.toml b/crates/claims/crates/data-integrity/Cargo.toml index 05df8193a..5aacfdfe5 100644 --- a/crates/claims/crates/data-integrity/Cargo.toml +++ b/crates/claims/crates/data-integrity/Cargo.toml @@ -94,7 +94,7 @@ solana = ["ssi-data-integrity-suites/solana"] ethereum = ["ssi-data-integrity-suites/ethereum"] ## BBS cryptographic suites. -bbs = ["ssi-data-integrity-suites/bbs"] +bbs = ["ssi-bbs", "ssi-data-integrity-suites/bbs"] [dependencies] ssi-data-integrity-core.workspace = true @@ -109,6 +109,8 @@ ssi-json-ld.workspace = true ssi-verification-methods.workspace = true ssi-eip712.workspace = true ssi-claims-core.workspace = true +ssi-di-sd-primitives.workspace = true +ssi-bbs = { workspace = true, optional = true } iref.workspace = true rdf-types.workspace = true linked-data.workspace = true diff --git a/crates/claims/crates/data-integrity/core/src/lib.rs b/crates/claims/crates/data-integrity/core/src/lib.rs index 463f1a32f..24a8853df 100644 --- a/crates/claims/crates/data-integrity/core/src/lib.rs +++ b/crates/claims/crates/data-integrity/core/src/lib.rs @@ -100,26 +100,14 @@ impl DataIntegrity { None => Err(SelectionError::MissingProof), } } -} -// impl DataIntegrity -// where -// T: ValidateClaims>, -// Proofs: ValidateProof, -// { -// /// Verify the claims and proofs with the default verification parameters. -// /// -// /// This function should be available for most claims and cryptosuite. -// /// If you need to customize the verification parameters, such as -// /// changing the verification date and time or the JSON-LD context loader, -// /// use the [`Self::verify_with`] method. -// /// -// /// See the [`VerificationParameters`] type for more information about the -// /// default verification parameters. -// pub async fn verify(&self) -> Result { -// VerifiableClaims::verify(self, VerificationParameters::default()).await -// } -// } + pub fn map(self, f: impl FnOnce(T) -> U) -> DataIntegrity { + DataIntegrity { + claims: f(self.claims), + proofs: self.proofs, + } + } +} impl Deref for DataIntegrity { type Target = T; diff --git a/crates/claims/crates/data-integrity/core/src/proof/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/mod.rs index ee77a36c9..f8b6bd500 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/mod.rs @@ -330,6 +330,12 @@ impl From>> for Proofs { } } +impl FromIterator> for Proofs { + fn from_iter>>(iter: T) -> Self { + Proofs(Vec::from_iter(iter)) + } +} + impl ssi_claims_core::ValidateProof for Proofs where S: CryptographicSuiteVerification, diff --git a/crates/claims/crates/data-integrity/core/src/signing/jws.rs b/crates/claims/crates/data-integrity/core/src/signing/jws.rs index 08da9617f..b0c69140e 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/jws.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/jws.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, marker::PhantomData}; use ssi_claims_core::{ProofValidationError, SignatureError}; +use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}; use ssi_jwk::{Algorithm, JWK}; use ssi_jws::{CompactJWSString, JWSSignature, JWS}; use ssi_verification_methods_core::{MessageSigner, VerifyBytes, VerifyBytesWithRecoveryJwk}; @@ -50,15 +51,15 @@ impl JwsSignature { Ok((signing_bytes, signature, header.algorithm)) } - pub async fn sign_detached, S: MessageSigner>( + pub async fn sign_detached, S: MessageSigner>( payload: &[u8], signer: S, key_id: Option, - algorithm: A, + algorithm_instance: A::Instance, ) -> Result { - let header = ssi_jws::Header::new_unencoded(algorithm.clone().into(), key_id); + let header = ssi_jws::Header::new_unencoded(algorithm_instance.algorithm().into(), key_id); let signing_bytes = header.encode_signing_bytes(payload); - let signature = signer.sign(algorithm, &signing_bytes).await?; + let signature = signer.sign(algorithm_instance, &signing_bytes).await?; let jws = ssi_jws::CompactJWSString::encode_detached(header, &signature); Ok(JwsSignature::new(jws)) } @@ -86,7 +87,9 @@ impl SignatureAlgorithm for DetachedJwsSigning where S: CryptographicSuite, S::PreparedClaims: AsRef<[u8]>, - A: Clone + AlgorithmSelection + Into, + A: SignatureAlgorithmType + + AlgorithmSelection + + Into, T: MessageSigner, { async fn sign( @@ -147,7 +150,7 @@ where S: CryptographicSuite, S::PreparedClaims: AsRef<[u8]>, S::ProofOptions: RecoverPublicJwk, - A: Clone + AlgorithmSelection + Into, + A: Clone + Into + AlgorithmSelection, T: MessageSigner, { async fn sign( diff --git a/crates/claims/crates/data-integrity/core/src/signing/mod.rs b/crates/claims/crates/data-integrity/core/src/signing/mod.rs index b329cfcf6..be525a151 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/mod.rs @@ -1,5 +1,8 @@ use ssi_claims_core::{ProofValidationError, SignatureError}; -use ssi_jwk::algorithm; +use ssi_crypto::{ + algorithm::{self, Algorithm, SignatureAlgorithmType}, + AlgorithmInstance, +}; mod jws; pub use jws::*; @@ -30,15 +33,30 @@ impl From for ProofValidationError { } } -pub trait AlgorithmSelection: Sized { +pub trait AlgorithmSelection: SignatureAlgorithmType { fn select_algorithm( verification_method: &M, options: &O, - ) -> Result; + ) -> Result; } impl AlgorithmSelection - for algorithm::Algorithm + for Algorithm +{ + fn select_algorithm( + verification_method: &M, + _options: &O, + ) -> Result { + verification_method + .to_jwk() + .get_algorithm() + .ok_or(AlgorithmSelectionError::MissingAlgorithm) + .map(Into::into) + } +} + +impl AlgorithmSelection + for ssi_jwk::Algorithm { fn select_algorithm( verification_method: &M, diff --git a/crates/claims/crates/data-integrity/core/src/signing/multibase.rs b/crates/claims/crates/data-integrity/core/src/signing/multibase.rs index 4d03fe624..39dde5f33 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/multibase.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/multibase.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use multibase::Base; use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_crypto::algorithm::SignatureAlgorithmInstance; use ssi_verification_methods_core::{MessageSigner, VerifyBytes}; use crate::{ @@ -112,9 +113,13 @@ where prepared_claims: S::PreparedClaims, proof: ProofRef, ) -> Result { - let algorithm = A::select_algorithm(verification_method, proof.options)?; + let algorithm_instance = A::select_algorithm(verification_method, proof.options)?; let (_, signature_bytes) = proof.signature.decode()?; // Should we check the base? - verification_method.verify_bytes(algorithm, prepared_claims.as_ref(), &signature_bytes) + verification_method.verify_bytes( + algorithm_instance.algorithm(), + prepared_claims.as_ref(), + &signature_bytes, + ) } } diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs b/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs index b4be06032..4d1d256dd 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs @@ -3,6 +3,8 @@ use sha2::Sha256; pub type HmacSha256 = Hmac; +pub type HmacKey = [u8; 32]; + pub mod canonicalize; pub mod group; pub mod json_pointer; diff --git a/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs index aedc9b2a6..d724a9951 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/skolemize.rs @@ -62,8 +62,9 @@ pub async fn compact_to_deskolemized_nquads( urn_scheme: &str, document: ssi_json_ld::syntax::Object, ) -> Result, SkolemError> { + let mut generator = generator::Blank::new(); let mut quads: Vec = RemoteDocument::new(None, None, Value::Object(document)) - .to_rdf(&mut generator::Blank::new(), loader) + .to_rdf(&mut generator, loader) .await .map_err(SkolemError::to_rdf)? .cloned_quads() diff --git a/crates/claims/crates/data-integrity/src/any/macros.rs b/crates/claims/crates/data-integrity/src/any/macros.rs index 26bcfb827..e4edfb863 100644 --- a/crates/claims/crates/data-integrity/src/any/macros.rs +++ b/crates/claims/crates/data-integrity/src/any/macros.rs @@ -47,7 +47,7 @@ macro_rules! crypto_suites { } #[allow(unused)] - trait Project: ssi_data_integrity_core::CryptographicSuite { + pub(crate) trait Project: ssi_data_integrity_core::CryptographicSuite { fn project_input_options( options: ssi_data_integrity_core::suite::InputProofOptions ) -> Result, ssi_data_integrity_core::suite::ConfigurationError>; @@ -63,6 +63,10 @@ macro_rules! crypto_suites { fn project_proof( proof: ssi_data_integrity_core::ProofRef ) -> ssi_data_integrity_core::ProofRef; + + fn project_transformation_options( + options: AnyTransformationOptions + ) -> ssi_data_integrity_core::suite::TransformationOptions; } $( @@ -122,6 +126,15 @@ macro_rules! crypto_suites { } ) } + + fn project_transformation_options( + options: AnyTransformationOptions + ) -> ssi_data_integrity_core::suite::TransformationOptions { + match options { + AnyTransformationOptions::$name(o) => o, + _ => panic!("malformed `AnySuite` instance") + } + } } )* @@ -131,6 +144,7 @@ macro_rules! crypto_suites { C: ssi_json_ld::JsonLdLoaderProvider + ssi_eip712::Eip712TypesLoaderProvider, T: serde::Serialize + ssi_json_ld::Expandable + ssi_json_ld::JsonLdNodeObject, + T::Expanded: Into, // R: ssi_verification_methods::VerificationMethodResolver, S: ssi_verification_methods::Signer, @@ -143,7 +157,7 @@ macro_rules! crypto_suites { signer: S, claims: &T, proof_configuration: ssi_data_integrity_core::ProofConfigurationRef<'_, Self>, - transformation_options: ssi_data_integrity_core::suite::TransformationOptions + transformation_options: AnyTransformationOptions ) -> Result { match self { $( @@ -159,7 +173,7 @@ macro_rules! crypto_suites { signer, claims, Self::project_proof_configuration(proof_configuration), - transformation_options + >::project_transformation_options(transformation_options) ).await.map(AnySignature::$name) }, )* @@ -174,6 +188,7 @@ macro_rules! crypto_suites { impl ssi_data_integrity_core::suite::CryptographicSuiteVerification for AnySuite where T: serde::Serialize + ssi_json_ld::Expandable + ssi_json_ld::JsonLdNodeObject, + T::Expanded: Into, V: ssi_claims_core::ResolverProvider + ssi_json_ld::JsonLdLoaderProvider + ssi_eip712::Eip712TypesLoaderProvider, V::Resolver: ssi_verification_methods::VerificationMethodResolver, { @@ -182,7 +197,7 @@ macro_rules! crypto_suites { verifier: &V, claims: &T, proof: ssi_data_integrity_core::ProofRef<'_, Self>, - transformation_options: () + transformation_options: AnyTransformationOptions ) -> Result { match self { $( @@ -199,7 +214,7 @@ macro_rules! crypto_suites { &verifier, claims, Self::project_proof(proof), - transformation_options + >::project_transformation_options(transformation_options) ).await }, )* @@ -293,7 +308,7 @@ macro_rules! crypto_suites { } } - /// Any hashed document. + /// Any signature. #[derive(Debug, Clone, serde::Serialize)] #[serde(untagged)] pub enum AnySignature { @@ -395,21 +410,30 @@ macro_rules! crypto_suites { } } + /// Any transformation options. + pub enum AnyTransformationOptions { + $( + $(#[cfg($($t)*)])? + $name(ssi_data_integrity_core::suite::TransformationOptions), + )* + Unknown + } + pub struct AnyConfigurationAlgorithm; #[allow(unused_variables)] impl ssi_data_integrity_core::suite::ConfigurationAlgorithm for AnyConfigurationAlgorithm { type InputVerificationMethod = ssi_verification_methods::AnyMethod; type InputSuiteOptions = crate::AnyInputSuiteOptions; - type InputSignatureOptions = (); + type InputSignatureOptions = AnySignatureOptions; type InputVerificationOptions = (); - type TransformationOptions = (); + type TransformationOptions = AnyTransformationOptions; fn configure_signature( suite: &AnySuite, options: ssi_data_integrity_core::suite::InputProofOptions, - signature_options: () - ) -> Result<(ssi_data_integrity_core::ProofConfiguration, ()), ssi_data_integrity_core::suite::ConfigurationError> { + signature_options: AnySignatureOptions + ) -> Result<(ssi_data_integrity_core::ProofConfiguration, AnyTransformationOptions), ssi_data_integrity_core::suite::ConfigurationError> { match suite { $( $(#[cfg($($t)*)])? @@ -421,14 +445,14 @@ macro_rules! crypto_suites { let (proof_configuration, transformation_options) = ::configure_signature( &ssi_data_integrity_suites::$name, options, - signature_options + signature_options.try_into()? )?; Ok((proof_configuration.map( |_| AnySuite::$name, |m| AnySuiteVerificationMethod::$name(m), |o| AnyProofOptions::$name(o) - ), transformation_options)) + ), AnyTransformationOptions::$name(transformation_options))) }, )* AnySuite::Unknown(_) => Ok(( @@ -436,16 +460,27 @@ macro_rules! crypto_suites { |m| AnySuiteVerificationMethod::Unknown(m.into()), |_| AnyProofOptions::Unknown ).into_configuration(suite.clone())?, - () + AnyTransformationOptions::Unknown )) } } fn configure_verification( - _suite: &AnySuite, - _verification_options: &() - ) -> Result<(), ssi_data_integrity_core::suite::ConfigurationError> { - Ok(()) + suite: &AnySuite, + verification_options: &() + ) -> Result { + match suite { + $( + $(#[cfg($($t)*)])? + AnySuite::$name => { + ::configure_verification( + &ssi_data_integrity_suites::$name, + verification_options + ).map(AnyTransformationOptions::$name) + }, + )* + AnySuite::Unknown(_) => Ok(AnyTransformationOptions::Unknown) + } } } diff --git a/crates/claims/crates/data-integrity/src/any/mod.rs b/crates/claims/crates/data-integrity/src/any/mod.rs index 15b4b563b..cdd45a535 100644 --- a/crates/claims/crates/data-integrity/src/any/mod.rs +++ b/crates/claims/crates/data-integrity/src/any/mod.rs @@ -2,11 +2,15 @@ pub(crate) mod macros; mod options; mod protocol; mod resolution; +mod sd; mod signature; +mod signature_options; mod suite; pub use options::*; pub use protocol::*; pub use resolution::*; +pub use sd::*; pub use signature::*; +pub use signature_options::*; pub use suite::*; diff --git a/crates/claims/crates/data-integrity/src/any/protocol.rs b/crates/claims/crates/data-integrity/src/any/protocol.rs index de3ef96bd..21b2d19d8 100644 --- a/crates/claims/crates/data-integrity/src/any/protocol.rs +++ b/crates/claims/crates/data-integrity/src/any/protocol.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use ssi_verification_methods::{protocol, MessageSignatureError, SignatureProtocol}; +use ssi_claims_core::MessageSignatureError; +use ssi_verification_methods::{protocol, SignatureProtocol}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum AnyProtocol { @@ -12,7 +13,7 @@ pub enum AnyProtocol { TezosWallet, } -impl SignatureProtocol for AnyProtocol { +impl SignatureProtocol for AnyProtocol { fn prepare_message<'b>(&self, bytes: &'b [u8]) -> Cow<'b, [u8]> { match self { Self::None => SignatureProtocol::::prepare_message(&(), bytes), @@ -30,7 +31,7 @@ impl SignatureProtocol for AnyProtocol { ), #[cfg(feature = "tezos")] Self::TezosWallet => { - SignatureProtocol::::prepare_message( + SignatureProtocol::::prepare_message( &ssi_data_integrity_suites::tezos::TezosWallet, bytes, ) @@ -40,19 +41,35 @@ impl SignatureProtocol for AnyProtocol { fn encode_signature( &self, - algorithm: ssi_jwk::Algorithm, + algorithm: ssi_crypto::Algorithm, signature: Vec, ) -> Result, MessageSignatureError> { match self { - Self::None => ().encode_signature(algorithm, signature), - Self::Base58Btc => protocol::Base58Btc.encode_signature(algorithm, signature), + Self::None => SignatureProtocol::::encode_signature( + &(), + algorithm, + signature, + ), + Self::Base58Btc => SignatureProtocol::::encode_signature( + &protocol::Base58Btc, + algorithm, + signature, + ), Self::Base58BtcMultibase => { - protocol::Base58BtcMultibase.encode_signature(algorithm, signature) + SignatureProtocol::::encode_signature( + &protocol::Base58BtcMultibase, + algorithm, + signature, + ) } - Self::EthereumWallet => protocol::EthereumWallet.encode_signature(algorithm, signature), + Self::EthereumWallet => SignatureProtocol::::encode_signature( + &protocol::EthereumWallet, + algorithm, + signature, + ), #[cfg(feature = "tezos")] Self::TezosWallet => { - let algorithm: ssi_jwk::algorithm::AnyBlake2b = algorithm.try_into()?; + let algorithm: ssi_crypto::algorithm::AnyBlake2b = algorithm.try_into()?; ssi_data_integrity_suites::tezos::TezosWallet.encode_signature(algorithm, signature) } } @@ -80,7 +97,7 @@ impl SignatureProtocol for AnyProtocol { ), #[cfg(feature = "tezos")] Self::TezosWallet => { - SignatureProtocol::::decode_signature( + SignatureProtocol::::decode_signature( &ssi_data_integrity_suites::tezos::TezosWallet, encoded_signature, ) diff --git a/crates/claims/crates/data-integrity/src/any/sd.rs b/crates/claims/crates/data-integrity/src/any/sd.rs new file mode 100644 index 000000000..938726c77 --- /dev/null +++ b/crates/claims/crates/data-integrity/src/any/sd.rs @@ -0,0 +1,96 @@ +use serde::Serialize; +use ssi_claims_core::ResolverProvider; +use ssi_data_integrity_core::{ + suite::{CryptographicSuiteSelect, SelectionError, SelectiveCryptographicSuite}, + DataIntegrity, ProofRef, +}; +use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; +use ssi_rdf::LexicalInterpretation; +use ssi_verification_methods::{AnyMethod, VerificationMethodResolver}; + +use crate::AnySuite; + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct AnySelectionOptions { + pub selective_pointers: Vec, + pub presentation_header: Option>, +} + +#[cfg(all(feature = "w3c", feature = "bbs"))] +impl From for ssi_data_integrity_suites::bbs_2023::DeriveOptions { + fn from(value: AnySelectionOptions) -> Self { + Self { + selective_pointers: value.selective_pointers, + presentation_header: value.presentation_header, + feature_option: Default::default(), + } + } +} + +impl SelectiveCryptographicSuite for AnySuite { + type SelectionOptions = AnySelectionOptions; +} + +impl CryptographicSuiteSelect for AnySuite +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, + P: JsonLdLoaderProvider + ResolverProvider, + P::Resolver: VerificationMethodResolver, +{ + async fn select( + &self, + unsecured_document: &T, + proof: ProofRef<'_, Self>, + params: P, + options: Self::SelectionOptions, + ) -> Result, SelectionError> { + match self { + #[cfg(all(feature = "w3c", feature = "bbs"))] + Self::Bbs2023 => { + let params = crate::AnyVerifier { + resolver: crate::AnyResolver::<_, ssi_verification_methods::Multikey>::new( + params.resolver(), + ), + json_ld_loader: params.loader(), + eip712_loader: (), + }; + + let DataIntegrity { claims, proofs } = ssi_data_integrity_suites::Bbs2023 + .select( + unsecured_document, + crate::Project::project_proof(proof), + params, + options.into(), + ) + .await?; + + Ok(DataIntegrity { + claims, + proofs: proofs + .into_iter() + .map(|p| ssi_data_integrity_core::Proof { + context: p.context, + type_: Self::Bbs2023, + created: p.created, + verification_method: p + .verification_method + .map(crate::AnySuiteVerificationMethod::Bbs2023), + proof_purpose: p.proof_purpose, + expires: p.expires, + domains: p.domains, + challenge: p.challenge, + nonce: p.nonce, + options: crate::AnyProofOptions::Bbs2023(p.options), + signature: crate::AnySignature::Bbs2023(p.signature), + extra_properties: p.extra_properties, + }) + .collect(), + }) + } + _ => Err(SelectionError::NonSelectiveSuite), + } + } +} diff --git a/crates/claims/crates/data-integrity/src/any/signature.rs b/crates/claims/crates/data-integrity/src/any/signature.rs index 170c6c641..0281dd124 100644 --- a/crates/claims/crates/data-integrity/src/any/signature.rs +++ b/crates/claims/crates/data-integrity/src/any/signature.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use ssi_claims_core::SignatureError; +use ssi_claims_core::{MessageSignatureError, SignatureError}; +use ssi_crypto::algorithm::SignatureAlgorithmType; use ssi_verification_methods::{protocol::WithProtocol, VerificationMethod}; use crate::AnyProtocol; @@ -29,105 +30,131 @@ where pub struct AnyMessageSigner(pub S); -impl ssi_verification_methods::MessageSigner for AnyMessageSigner +impl ssi_verification_methods::MessageSigner + for AnyMessageSigner where S: ssi_verification_methods::MessageSigner, - A: IntoAnySignatureAlgorithm, + A::Instance: IntoAnySignatureAlgorithm, { async fn sign( self, - algorithm: A, + algorithm: A::Instance, message: &[u8], - ) -> Result, ssi_verification_methods::MessageSignatureError> { + ) -> Result, MessageSignatureError> { self.0 .sign(algorithm.into_any_signature_algorithm(), message) .await } + + async fn sign_multi( + self, + algorithm: ::Instance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + self.0 + .sign_multi(algorithm.into_any_signature_algorithm(), messages) + .await + } } -pub type AnySignatureAlgorithm = WithProtocol; +pub type AnySignatureAlgorithm = WithProtocol; + +pub type AnySignatureAlgorithmInstance = WithProtocol; pub trait IntoAnySignatureAlgorithm { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm; + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance; } impl IntoAnySignatureAlgorithm for ssi_jwk::Algorithm { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { + WithProtocol(self.into(), AnyProtocol::None) + } +} + +impl IntoAnySignatureAlgorithm for ssi_crypto::AlgorithmInstance { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self, AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::RS256 { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::RS256 { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::ES256 { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ES256 { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::ES256K { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ES256K { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::ES256KR { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ES256KR { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::AnyESKeccakK { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::AnyESKeccakK { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::EdDSA { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::EdDSA { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::EdBlake2b { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::EdBlake2b { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } -impl IntoAnySignatureAlgorithm for ssi_jwk::algorithm::ESBlake2b { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ESBlake2b { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } #[cfg(all(feature = "w3c", any(feature = "secp256r1", feature = "secp384r1")))] impl IntoAnySignatureAlgorithm for ssi_data_integrity_suites::ecdsa_rdfc_2019::ES256OrES384 { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { + WithProtocol(self.into(), AnyProtocol::None) + } +} + +#[cfg(all(feature = "w3c", feature = "bbs"))] +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::BbsInstance { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } } #[cfg(feature = "tezos")] impl IntoAnySignatureAlgorithm - for WithProtocol + for WithProtocol { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.0.into(), self.1.into()) } } impl IntoAnySignatureAlgorithm for WithProtocol< - ssi_jwk::algorithm::AnyESKeccakK, + ssi_crypto::algorithm::AnyESKeccakK, ssi_verification_methods::protocol::EthereumWallet, > { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.0.into(), self.1.into()) } } @@ -136,8 +163,8 @@ impl IntoAnySignatureAlgorithm impl IntoAnySignatureAlgorithm for WithProtocol { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { - WithProtocol(self.0, self.1.into()) + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { + WithProtocol(self.0.into(), self.1.into()) } } @@ -145,7 +172,7 @@ impl IntoAnySignatureAlgorithm impl IntoAnySignatureAlgorithm for WithProtocol { - fn into_any_signature_algorithm(self) -> AnySignatureAlgorithm { - WithProtocol(self.0, self.1.into()) + fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { + WithProtocol(self.0.into(), self.1.into()) } } diff --git a/crates/claims/crates/data-integrity/src/any/signature_options.rs b/crates/claims/crates/data-integrity/src/any/signature_options.rs new file mode 100644 index 000000000..fa1df62eb --- /dev/null +++ b/crates/claims/crates/data-integrity/src/any/signature_options.rs @@ -0,0 +1,46 @@ +use ssi_di_sd_primitives::JsonPointerBuf; + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct AnySignatureOptions { + pub mandatory_pointers: Vec, + + #[cfg(all(feature = "w3c", feature = "bbs"))] + pub feature_option: ssi_data_integrity_suites::bbs_2023::FeatureOption, + + #[cfg(all(feature = "w3c", feature = "bbs"))] + pub commitment_with_proof: Option>, +} + +impl From for () { + fn from(_value: AnySignatureOptions) -> Self {} +} + +impl From<()> for AnySignatureOptions { + fn from(_value: ()) -> Self { + AnySignatureOptions::default() + } +} + +#[cfg(all(feature = "w3c", feature = "bbs"))] +impl From for ssi_data_integrity_suites::bbs_2023::Bbs2023SignatureOptions { + fn from(o: AnySignatureOptions) -> Self { + Self { + mandatory_pointers: o.mandatory_pointers, + feature_option: o.feature_option, + commitment_with_proof: o.commitment_with_proof, + hmac_key: None, + } + } +} + +#[cfg(all(feature = "w3c", feature = "bbs"))] +impl From for AnySignatureOptions { + fn from(value: ssi_data_integrity_suites::bbs_2023::Bbs2023SignatureOptions) -> Self { + Self { + mandatory_pointers: value.mandatory_pointers, + feature_option: value.feature_option, + commitment_with_proof: value.commitment_with_proof, + } + } +} diff --git a/crates/claims/crates/data-integrity/src/any/suite/mod.rs b/crates/claims/crates/data-integrity/src/any/suite/mod.rs index 3d3c4680e..ca480e07e 100644 --- a/crates/claims/crates/data-integrity/src/any/suite/mod.rs +++ b/crates/claims/crates/data-integrity/src/any/suite/mod.rs @@ -4,7 +4,7 @@ use ssi_eip712::Eip712TypesLoaderProvider; use ssi_json_ld::JsonLdLoaderProvider; pub use unknown::*; -use crate::{macros, AnyResolver}; +use crate::{macros, AnyResolver, AnySignatureOptions}; mod pick; @@ -60,6 +60,9 @@ macros::crypto_suites! { #[cfg(all(feature = "w3c", feature = "eip712"))] ethereum_eip712_signature_2021_v0_1: EthereumEip712Signature2021v0_1, + #[cfg(all(feature = "w3c", feature = "bbs"))] + bbs_2023: Bbs2023, + /// DIF Ecdsa Secp256k1 Recovery Signature 2020. /// /// See: @@ -145,9 +148,9 @@ impl AnyProofOptions { } pub struct AnyVerifier { - resolver: AnyResolver, - json_ld_loader: L1, - eip712_loader: L2, + pub resolver: AnyResolver, + pub json_ld_loader: L1, + pub eip712_loader: L2, } impl ResolverProvider for AnyVerifier { diff --git a/crates/claims/crates/data-integrity/src/lib.rs b/crates/claims/crates/data-integrity/src/lib.rs index ee705b957..d4596d263 100644 --- a/crates/claims/crates/data-integrity/src/lib.rs +++ b/crates/claims/crates/data-integrity/src/lib.rs @@ -1,6 +1,8 @@ pub use ssi_data_integrity_core::*; pub use ssi_data_integrity_suites as suites; +pub use ssi_di_sd_primitives::{JsonPointer, JsonPointerBuf}; + mod any; pub use any::*; diff --git a/crates/claims/crates/data-integrity/suites/src/eip712/signature.rs b/crates/claims/crates/data-integrity/suites/src/eip712/signature.rs index 8f2ba755a..099357b86 100644 --- a/crates/claims/crates/data-integrity/suites/src/eip712/signature.rs +++ b/crates/claims/crates/data-integrity/suites/src/eip712/signature.rs @@ -2,7 +2,7 @@ use iref::UriBuf; use linked_data::{LinkedData, LinkedDataGraph}; use rdf_types::{Interpretation, Vocabulary}; use ssi_claims_core::{ProofValidationError, SignatureError}; -use ssi_jwk::algorithm::AnyESKeccakK; +use ssi_crypto::algorithm::AnyESKeccakK; use ssi_verification_methods::MessageSigner; /// Common signature format for EIP-712-based cryptographic suites. diff --git a/crates/claims/crates/data-integrity/suites/src/suites/dif/ecdsa_secp256k1_recovery_signature_2020.rs b/crates/claims/crates/data-integrity/suites/src/suites/dif/ecdsa_secp256k1_recovery_signature_2020.rs index 5aa8c7427..a28cef70e 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/dif/ecdsa_secp256k1_recovery_signature_2020.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/dif/ecdsa_secp256k1_recovery_signature_2020.rs @@ -30,7 +30,7 @@ impl StandardCryptographicSuite for EcdsaSecp256k1RecoverySignature2020 { type VerificationMethod = EcdsaSecp256k1RecoveryMethod2020; - type SignatureAlgorithm = DetachedJwsSigning; + type SignatureAlgorithm = DetachedJwsSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs index 443b3b730..8283beb32 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/eip712_signature_2021.rs @@ -243,7 +243,7 @@ impl SignatureAndVerificationAlgorithm for Eip712SignatureAlgorithm { impl SignatureAlgorithm for Eip712SignatureAlgorithm where - T: MessageSigner, + T: MessageSigner, { async fn sign( verification_method: &::VerificationMethod, @@ -282,13 +282,13 @@ verification_method_union! { } impl VerificationMethod { - pub fn algorithm(&self) -> ssi_jwk::algorithm::AnyESKeccakK { + pub fn algorithm(&self) -> ssi_crypto::algorithm::AnyESKeccakK { match self { Self::EcdsaSecp256k1VerificationKey2019(_) => { - ssi_jwk::algorithm::AnyESKeccakK::ESKeccakK + ssi_crypto::algorithm::AnyESKeccakK::ESKeccakK } Self::Eip712Method2021(_) | Self::EcdsaSecp256k1RecoveryMethod2020(_) => { - ssi_jwk::algorithm::AnyESKeccakK::ESKeccakKR + ssi_crypto::algorithm::AnyESKeccakK::ESKeccakKR } } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/ethereum_personal_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/ethereum_personal_signature_2021.rs index 56cf91d82..a2bbf6e65 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/ethereum_personal_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/ethereum_personal_signature_2021.rs @@ -108,7 +108,7 @@ impl SignatureAlgorithm for EthereumWalletSigning where S: CryptographicSuite, S::PreparedClaims: AsRef<[u8]>, - T: MessageSigner>, + T: MessageSigner>, { async fn sign( verification_method: &S::VerificationMethod, @@ -153,13 +153,13 @@ verification_method_union! { } impl VerificationMethod { - pub fn algorithm(&self) -> ssi_jwk::algorithm::AnyESKeccakK { + pub fn algorithm(&self) -> ssi_crypto::algorithm::AnyESKeccakK { match self { Self::EcdsaSecp256k1VerificationKey2019(_) => { - ssi_jwk::algorithm::AnyESKeccakK::ESKeccakK + ssi_crypto::algorithm::AnyESKeccakK::ESKeccakK } Self::EcdsaSecp256k1RecoveryMethod2020(_) => { - ssi_jwk::algorithm::AnyESKeccakK::ESKeccakKR + ssi_crypto::algorithm::AnyESKeccakK::ESKeccakKR } } } diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos.rs index 4ea18d7d1..34d7b5ad5 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos.rs @@ -14,13 +14,14 @@ pub use ed25519_blake2b_digest_size20_base58_check_encoded_signature_2021::Ed255 #[cfg(feature = "secp256r1")] pub use p256_blake2b_digest_size20_base58_check_encoded_signature_2021::P256BLAKE2BDigestSize20Base58CheckEncodedSignature2021; -use ssi_claims_core::{ProofValidationError, SignatureError}; +use ssi_claims_core::{MessageSignatureError, ProofValidationError, SignatureError}; +use ssi_crypto::algorithm::AnyBlake2b; use ssi_data_integrity_core::signing::RecoverPublicJwk; -use ssi_jwk::{algorithm::AnyBlake2b, JWK}; +use ssi_jwk::JWK; use ssi_security::{Multibase, MultibaseBuf}; use ssi_verification_methods::{ protocol::{InvalidProtocolSignature, WithProtocol}, - MessageSignatureError, MessageSigner, SignatureProtocol, + MessageSigner, SignatureProtocol, }; pub use tezos_jcs_signature_2021::TezosJcsSignature2021; pub use tezos_signature_2021::TezosSignature2021; @@ -138,7 +139,11 @@ impl Signature { signer: S, ) -> Result { match public_key { - Some(jwk) => match jwk.algorithm.try_into() { + Some(jwk) => match jwk + .algorithm + .ok_or(MessageSignatureError::MissingAlgorithm)? + .try_into() + { Ok(algorithm) => { let proof_value_bytes = signer .sign(WithProtocol(algorithm, TezosWallet), message) diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/ed25519_blake2b_digest_size20_base58_check_encoded_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/ed25519_blake2b_digest_size20_base58_check_encoded_signature_2021.rs index cb49f3ecf..3fdddf38c 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/ed25519_blake2b_digest_size20_base58_check_encoded_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/ed25519_blake2b_digest_size20_base58_check_encoded_signature_2021.rs @@ -30,7 +30,7 @@ impl StandardCryptographicSuite for Ed25519BLAKE2BDigestSize20Base58CheckEncoded type VerificationMethod = Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021; - type SignatureAlgorithm = DetachedJwsRecoverySigning; + type SignatureAlgorithm = DetachedJwsRecoverySigning; type ProofOptions = Options; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/p256_blake2b_digest_size20_base58_check_encoded_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/p256_blake2b_digest_size20_base58_check_encoded_signature_2021.rs index 521d96037..b0dd287d7 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/p256_blake2b_digest_size20_base58_check_encoded_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/p256_blake2b_digest_size20_base58_check_encoded_signature_2021.rs @@ -30,7 +30,7 @@ impl StandardCryptographicSuite for P256BLAKE2BDigestSize20Base58CheckEncodedSig type VerificationMethod = P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021; - type SignatureAlgorithm = DetachedJwsRecoverySigning; + type SignatureAlgorithm = DetachedJwsRecoverySigning; type ProofOptions = Options; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_jcs_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_jcs_signature_2021.rs index d4ef61fb2..05d516c07 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_jcs_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_jcs_signature_2021.rs @@ -2,6 +2,7 @@ use iref::Iri; use json_syntax::Print; use lazy_static::lazy_static; use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_crypto::algorithm::AnyBlake2b; use ssi_data_integrity_core::{ suite::{ standard::{ @@ -12,7 +13,6 @@ use ssi_data_integrity_core::{ }, CryptographicSuite, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef, }; -use ssi_jwk::algorithm::AnyBlake2b; use ssi_security::MultibaseBuf; use ssi_tzkey::EncodeTezosSignedMessageError; use ssi_verification_methods::{protocol::WithProtocol, MessageSigner, TezosMethod2021}; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_signature_2021.rs index cc7e6d5ee..1d48a8975 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/unspecified/tezos/tezos_signature_2021.rs @@ -1,5 +1,6 @@ use crate::TezosWallet; use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_crypto::algorithm::AnyBlake2b; use ssi_data_integrity_core::{ canonicalization::{CanonicalClaimsAndConfiguration, CanonicalizeClaimsAndConfiguration}, suite::{ @@ -11,7 +12,7 @@ use ssi_data_integrity_core::{ }, CryptographicSuite, ProofConfigurationRef, ProofRef, StandardCryptographicSuite, TypeRef, }; -use ssi_jwk::{algorithm::AnyBlake2b, JWK}; +use ssi_jwk::JWK; use ssi_tzkey::EncodeTezosSignedMessageError; use ssi_verification_methods::{protocol::WithProtocol, MessageSigner, TezosMethod2021}; use static_iref::iri; diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs index 0466815d3..423b9e737 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/derive.rs @@ -88,9 +88,10 @@ pub struct DeriveOptions { pub feature_option: DerivedFeatureOption, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Default, Clone, Serialize, Deserialize)] #[serde(tag = "featureOption")] pub enum DerivedFeatureOption { + #[default] Baseline, AnonymousHolderBinding { holder_secret: String, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs index 6ceeae295..73562d914 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/hashing.rs @@ -72,20 +72,20 @@ fn create_verify_data2(t: TransformedDerived) -> DerivedHashData { } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum HashData { Base(BaseHashData), Derived(DerivedHashData), } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct BaseHashData { pub transformed_document: TransformedBase, pub proof_hash: [u8; 32], pub mandatory_hash: [u8; 32], } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct DerivedHashData { pub canonical_configuration: Vec, pub quads: Vec, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs index 8b94a0b57..84ef85114 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/mod.rs @@ -11,7 +11,7 @@ use ssi_data_integrity_core::{ CryptosuiteStr, DataIntegrity, ProofConfiguration, ProofRef, StandardCryptographicSuite, Type, TypeRef, UnsupportedProofSuite, }; -use ssi_di_sd_primitives::JsonPointerBuf; +use ssi_di_sd_primitives::{HmacKey, JsonPointerBuf}; use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; use ssi_rdf::LexicalInterpretation; use ssi_verification_methods::{Multikey, VerificationMethodResolver}; @@ -101,7 +101,7 @@ impl TryFrom for Bbs2023 { } } -#[derive(Clone)] +#[derive(Debug, Default, Clone)] pub struct Bbs2023SignatureOptions { pub mandatory_pointers: Vec, @@ -121,8 +121,6 @@ pub enum FeatureOption { PseudonymHiddenPid, } -pub type HmacKey = [u8; 32]; - /// Base Proof Configuration. /// /// See: diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs index 6bcdf60da..2840f23ec 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/base.rs @@ -1,12 +1,11 @@ -use std::borrow::Cow; - use multibase::Base; use ssi_bbs::{BBSplusPublicKey, Bbs}; use ssi_claims_core::SignatureError; +use ssi_crypto::algorithm::{BbsInstance, BbsParameters}; use ssi_di_sd_primitives::JsonPointerBuf; use ssi_rdf::IntoNQuads; use ssi_security::MultibaseBuf; -use ssi_verification_methods::{multikey::DecodedMultikey, MultiMessageSigner, Multikey, Signer}; +use ssi_verification_methods::{multikey::DecodedMultikey, MessageSigner, Multikey}; use crate::bbs_2023::{hashing::BaseHashData, FeatureOption}; @@ -18,8 +17,7 @@ pub async fn generate_base_proof( hash_data: BaseHashData, ) -> Result where - T: Signer, - T::MessageSigner: MultiMessageSigner, + T: MessageSigner, { // See: let DecodedMultikey::Bls12_381(public_key) = verification_method.public_key.decode()? else { @@ -42,18 +40,13 @@ where .map(String::into_bytes) .collect(); - let message_signer = signer - .for_method(Cow::Borrowed(verification_method)) - .await? - .ok_or(SignatureError::MissingSigner)?; - - let (algorithm, description) = match feature_option { + let (bbs_params, description) = match feature_option { FeatureOption::Baseline => ( - Bbs::Baseline { header: bbs_header }, + BbsParameters::Baseline { header: bbs_header }, Bbs2023SignatureDescription::Baseline, ), FeatureOption::AnonymousHolderBinding => ( - Bbs::Blind { + BbsParameters::Blind { header: bbs_header, commitment_with_proof: None, signer_blind: None, @@ -68,7 +61,7 @@ where messages.push(pid.to_vec()); ( - Bbs::Baseline { header: bbs_header }, + BbsParameters::Baseline { header: bbs_header }, Bbs2023SignatureDescription::PseudonymIssuerPid { pid }, ) } @@ -82,7 +75,7 @@ where .ok_or_else(|| SignatureError::missing_required_option("commitment_with_proof"))?; ( - Bbs::Blind { + BbsParameters::Blind { header: bbs_header, commitment_with_proof: Some(commitment_with_proof), signer_blind: None, @@ -92,7 +85,9 @@ where } }; - let signature = message_signer.sign_multi(algorithm, &messages).await?; + let signature = signer + .sign_multi(BbsInstance(Box::new(bbs_params)), &messages) + .await?; Ok(Bbs2023Signature::encode_base( &signature, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs index cfb9a5458..a126a7dd6 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/bbs_2023/signature/mod.rs @@ -3,11 +3,12 @@ use serde::{Deserialize, Serialize}; use ssi_bbs::Bbs; use ssi_claims_core::{ProofValidationError, SignatureError}; use ssi_data_integrity_core::{ + signing::AlterSignature, suite::standard::{SignatureAlgorithm, SignatureAndVerificationAlgorithm}, ProofConfigurationRef, }; use ssi_security::MultibaseBuf; -use ssi_verification_methods::{MultiMessageSigner, Multikey, Signer}; +use ssi_verification_methods::{MessageSigner, Multikey}; use super::HashData; @@ -26,7 +27,7 @@ impl From for ProofValidationError { #[error("unsupported bbs-2023 signature type")] pub struct UnsupportedBbs2023Signature; -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Bbs2023Signature { pub proof_value: MultibaseBuf, @@ -38,6 +39,12 @@ impl AsRef for Bbs2023Signature { } } +impl AlterSignature for Bbs2023Signature { + fn alter(&mut self) { + self.proof_value = MultibaseBuf::encode(multibase::Base::Base58Btc, [0]) + } +} + #[derive(Clone)] pub enum Bbs2023SignatureDescription { Baseline, @@ -54,8 +61,7 @@ impl SignatureAndVerificationAlgorithm for Bbs2023SignatureAlgorithm { impl SignatureAlgorithm for Bbs2023SignatureAlgorithm where - T: Signer, - T::MessageSigner: MultiMessageSigner, + T: MessageSigner, { async fn sign( verification_method: &Multikey, @@ -76,9 +82,13 @@ where #[cfg(test)] mod tests { + use std::borrow::Cow; + use nquads_syntax::Parse; use ssi_data_integrity_core::{suite::standard::SignatureAlgorithm, ProofConfiguration}; - use ssi_verification_methods::{Multikey, ProofPurpose, ReferenceOrOwned, SingleSecretSigner}; + use ssi_verification_methods::{ + Multikey, ProofPurpose, ReferenceOrOwned, Signer, SingleSecretSigner, + }; use static_iref::uri; use crate::{ @@ -182,7 +192,11 @@ _:b5 \"2023\"^^ -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct TransformedBase { pub options: Bbs2023SignatureOptions, pub mandatory: Vec, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs index 9de444c6d..2178db749 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_rdfc_2019.rs @@ -2,6 +2,7 @@ //! //! See: use core::fmt; +use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}; use ssi_data_integrity_core::{ canonicalization::{ CanonicalClaimsAndConfiguration, CanonicalizeClaimsAndConfiguration, @@ -100,6 +101,7 @@ impl AsRef<[u8]> for EcdsaRdfc2019Hash { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ES256OrES384 { ES256, ES384, @@ -114,6 +116,18 @@ impl ES256OrES384 { } } +impl SignatureAlgorithmType for ES256OrES384 { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for ES256OrES384 { + type Algorithm = Self; + + fn algorithm(&self) -> Self { + *self + } +} + impl fmt::Display for ES256OrES384 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.name().fmt(f) @@ -147,3 +161,21 @@ impl From for ssi_jwk::Algorithm { } } } + +impl From for ssi_crypto::Algorithm { + fn from(value: ES256OrES384) -> Self { + match value { + ES256OrES384::ES256 => Self::ES256, + ES256OrES384::ES384 => Self::ES384, + } + } +} + +impl From for ssi_crypto::AlgorithmInstance { + fn from(value: ES256OrES384) -> Self { + match value { + ES256OrES384::ES256 => Self::ES256, + ES256OrES384::ES384 => Self::ES384, + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256k1_signature_2019.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256k1_signature_2019.rs index 7c9d93ecd..6ff4767ca 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256k1_signature_2019.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256k1_signature_2019.rs @@ -30,7 +30,7 @@ impl StandardCryptographicSuite for EcdsaSecp256k1Signature2019 { type VerificationMethod = EcdsaSecp256k1VerificationKey2019; - type SignatureAlgorithm = DetachedJwsSigning; + type SignatureAlgorithm = DetachedJwsSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256r1_signature_2019.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256r1_signature_2019.rs index 6e58d0669..a978aa728 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256r1_signature_2019.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_secp256r1_signature_2019.rs @@ -30,7 +30,7 @@ impl StandardCryptographicSuite for EcdsaSecp256r1Signature2019 { type VerificationMethod = EcdsaSecp256r1VerificationKey2019; - type SignatureAlgorithm = DetachedJwsSigning; + type SignatureAlgorithm = DetachedJwsSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2018.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2018.rs index 8cb473e5f..09a3ab277 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2018.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2018.rs @@ -29,7 +29,7 @@ impl StandardCryptographicSuite for Ed25519Signature2018 { type VerificationMethod = Ed25519VerificationKey2018; - type SignatureAlgorithm = DetachedJwsSigning; + type SignatureAlgorithm = DetachedJwsSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2020.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2020.rs index 58380816f..c2db8949f 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2020.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ed25519_signature_2020.rs @@ -56,7 +56,7 @@ impl StandardCryptographicSuite for Ed25519Signature2020 { type VerificationMethod = Ed25519VerificationKey2020; - type SignatureAlgorithm = MultibaseSigning; + type SignatureAlgorithm = MultibaseSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs index 3d6c907c8..81c1b5911 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/eddsa_2022.rs @@ -37,7 +37,7 @@ impl StandardCryptographicSuite for EdDsa2022 { type VerificationMethod = Multikey; - type SignatureAlgorithm = MultibaseSigning; + type SignatureAlgorithm = MultibaseSigning; type ProofOptions = (); diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs index 58fd527c1..e32bae29b 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ethereum_eip712_signature_2021.rs @@ -3,7 +3,8 @@ //! See: use lazy_static::lazy_static; use serde::Serialize; -use ssi_claims_core::{ProofValidationError, ProofValidity, SignatureError}; +use ssi_claims_core::{MessageSignatureError, ProofValidationError, ProofValidity, SignatureError}; +use ssi_crypto::algorithm::{AlgorithmError, AnyESKeccakK}; use ssi_data_integrity_core::{ suite::{ standard::{ @@ -16,12 +17,10 @@ use ssi_data_integrity_core::{ StandardCryptographicSuite, TypeRef, }; use ssi_eip712::{Eip712TypesLoaderProvider, TypesLoader, Value}; -use ssi_jwk::algorithm::{AlgorithmError, AnyESKeccakK}; use ssi_verification_methods::{ ecdsa_secp_256k1_recovery_method_2020, ecdsa_secp_256k1_verification_key_2019, verification_method_union, AnyMethod, EcdsaSecp256k1RecoveryMethod2020, - EcdsaSecp256k1VerificationKey2019, InvalidVerificationMethod, JsonWebKey2020, - MessageSignatureError, MessageSigner, + EcdsaSecp256k1VerificationKey2019, InvalidVerificationMethod, JsonWebKey2020, MessageSigner, }; use static_iref::{iri, iri_ref}; @@ -198,7 +197,7 @@ impl VerificationMethod { Self::JsonWebKey2020(m) => match m.public_key.algorithm { Some(ssi_jwk::Algorithm::ES256K) => Ok(AnyESKeccakK::ESKeccakK), Some(ssi_jwk::Algorithm::ES256KR) => Ok(AnyESKeccakK::ESKeccakKR), - Some(other) => Err(AlgorithmError::Unsupported(other)), + Some(other) => Err(AlgorithmError::Unsupported(other.into())), None => Err(AlgorithmError::Missing), }, } @@ -320,7 +319,7 @@ impl SignatureAlgorithm for EthereumEip712SignatureAlgorithm where S: CryptographicSuite, S::PreparedClaims: AsRef<[u8]>, - T: MessageSigner, + T: MessageSigner, { async fn sign( verification_method: &S::VerificationMethod, diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/rsa_signature_2018.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/rsa_signature_2018.rs index 931d11a20..1c72556ef 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c/rsa_signature_2018.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/rsa_signature_2018.rs @@ -70,7 +70,7 @@ impl SignatureAndVerificationAlgorithm for RsaSignatureAlgorithm { impl SignatureAlgorithm for RsaSignatureAlgorithm where - T: MessageSigner, + T: MessageSigner, { async fn sign( _verification_method: &RsaVerificationKey2018, @@ -79,7 +79,7 @@ where _proof_configuration: ProofConfigurationRef<'_, RsaSignature2018>, ) -> Result { let signature = signer - .sign(ssi_jwk::algorithm::RS256, &prepared_claims) + .sign(ssi_crypto::algorithm::RS256, &prepared_claims) .await?; Ok(Signature { diff --git a/crates/claims/crates/data-integrity/suites/tests/suite.rs b/crates/claims/crates/data-integrity/suites/tests/suite.rs index bb21be74e..f4878f665 100644 --- a/crates/claims/crates/data-integrity/suites/tests/suite.rs +++ b/crates/claims/crates/data-integrity/suites/tests/suite.rs @@ -3,13 +3,13 @@ use std::{borrow::Cow, path::Path}; use hashbrown::HashMap; use iref::UriBuf; use serde::{de::DeserializeOwned, Deserialize}; -use ssi_claims_core::SignatureError; +use ssi_claims_core::{MessageSignatureError, SignatureError}; use ssi_data_integrity_core::{CryptographicSuite, DataIntegrityDocument, ProofOptions}; use ssi_multicodec::MultiEncodedBuf; use ssi_security::{Multibase, MultibaseBuf}; use ssi_verification_methods::{ - MessageSignatureError, MessageSigner, ReferenceOrOwnedRef, ResolutionOptions, - VerificationMethodResolutionError, VerificationMethodResolver, + MessageSigner, ReferenceOrOwnedRef, ResolutionOptions, VerificationMethodResolutionError, + VerificationMethodResolver, }; fn load_json(path: impl AsRef) -> serde_json::Value { diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 51d2fee78..d4e1bb981 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -11,11 +11,12 @@ documentation = "https://docs.rs/ssi-crypto/" [features] default = ["secp256k1", "ripemd-160"] secp256k1 = ["k256", "getrandom", "keccak"] -bbs = ["dep:bbs", "pairing-plus", "rand_old", "getrandom", "sha2_old", "hkdf", "serde"] +bbs = ["dep:bbs", "pairing-plus", "rand_old", "getrandom", "sha2_old", "hkdf"] ripemd-160 = ["ripemd160", "secp256k1"] keccak = ["keccak-hash"] ring = ["dep:ring"] + [dependencies] thiserror.workspace = true sha2 = { workspace = true } @@ -31,7 +32,7 @@ ed25519-dalek = { workspace = true, optional = true } ripemd160 = { version = "0.9", optional = true } bbs = { version = "=0.4.1", optional = true } pairing-plus = { version = "=0.19.0", optional = true } -serde = { workspace = true, features = ["derive"], optional = true } +serde = { workspace = true, features = ["derive"] } zeroize = { version = "1.5", features = ["zeroize_derive"] } bs58 = { workspace = true, features = ["check"] } digest = "0.9" diff --git a/crates/crypto/src/algorithm/mod.rs b/crates/crypto/src/algorithm/mod.rs new file mode 100644 index 000000000..00b27727f --- /dev/null +++ b/crates/crypto/src/algorithm/mod.rs @@ -0,0 +1,601 @@ +use core::fmt; + +use serde::{Deserialize, Serialize}; + +pub trait SignatureAlgorithmType { + type Instance: SignatureAlgorithmInstance; +} + +pub trait SignatureAlgorithmInstance { + type Algorithm; + + fn algorithm(&self) -> Self::Algorithm; +} + +macro_rules! algorithms { + ($( + $(#[doc = $doc:tt])* + $(#[doc($doc_tag:ident)])? + $(#[serde $serde:tt])? + $id:ident $( ($arg:ty) )? : $name:literal + ),*) => { + /// Signature algorithm. + #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Hash, Eq)] + pub enum Algorithm { + $( + $(#[doc = $doc])* + $(#[doc($doc_tag)])? + $(#[serde $serde])? + #[serde(rename = $name)] + $id, + )* + /// No signature. + /// + /// Per the specs it should only be `none` but `None` is kept for backwards + /// compatibility. + #[serde(alias = "None")] + None + } + + impl Algorithm { + pub fn as_str(&self) -> &'static str { + match self { + $( + Self::$id => $name, + )* + Self::None => "none" + } + } + + pub fn into_str(self) -> &'static str { + match self { + $( + Self::$id => $name, + )* + Self::None => "none" + } + } + } + + impl SignatureAlgorithmType for Algorithm { + type Instance = AlgorithmInstance; + } + + #[derive(Debug, Clone)] + pub enum AlgorithmInstance { + $( + $(#[doc = $doc])* + $(#[doc($doc_tag)])? + $(#[serde $serde])? + $id $( ($arg) )?, + )* + /// No signature + None + } + + impl AlgorithmInstance { + pub fn algorithm(&self) -> Algorithm { + match self { + $(Self::$id $( (algorithms!(@ignore_arg $arg)) )? => Algorithm::$id,)* + Self::None => Algorithm::None + } + } + } + + impl SignatureAlgorithmInstance for AlgorithmInstance { + type Algorithm = Algorithm; + + fn algorithm(&self) -> Algorithm { + self.algorithm() + } + } + + $( + $(#[doc = $doc])* + #[derive(Debug, Default, Clone, Copy, PartialEq, Hash, Eq)] + pub struct $id; + + algorithms!(@instance $id $($arg)?); + + impl TryFrom for $id { + type Error = UnsupportedAlgorithm; + + fn try_from(a: Algorithm) -> Result { + match a { + Algorithm::$id => Ok(Self), + a => Err(UnsupportedAlgorithm(a)) + } + } + } + + impl From<$id> for Algorithm { + fn from(_a: $id) -> Self { + Self::$id + } + } + )* + }; + { @instance $id:ident } => { + impl SignatureAlgorithmType for $id { + type Instance = Self; + } + + impl SignatureAlgorithmInstance for $id { + type Algorithm = $id; + + fn algorithm(&self) -> $id { + *self + } + } + + impl TryFrom for $id { + type Error = UnsupportedAlgorithm; + + fn try_from(a: AlgorithmInstance) -> Result { + match a { + AlgorithmInstance::$id => Ok(Self), + other => Err(UnsupportedAlgorithm(other.algorithm())) + } + } + } + + impl From<$id> for AlgorithmInstance { + fn from(_: $id) -> Self { + Self::$id + } + } + }; + { @instance $id:ident $arg:ty } => { + impl SignatureAlgorithmType for $id { + type Instance = $arg; + } + + impl SignatureAlgorithmInstance for $arg { + type Algorithm = $id; + + fn algorithm(&self) -> $id { + $id + } + } + + impl TryFrom for $arg { + type Error = UnsupportedAlgorithm; + + fn try_from(a: AlgorithmInstance) -> Result { + match a { + AlgorithmInstance::$id(arg) => Ok(arg), + other => Err(UnsupportedAlgorithm(other.algorithm())) + } + } + } + + impl From<$arg> for AlgorithmInstance { + fn from(value: $arg) -> Self { + Self::$id(value) + } + } + }; + { @ignore_arg $arg:ty } => { _ }; +} + +algorithms! { + /// HMAC using SHA-256. + /// + /// See: + HS256: "HS256", + + /// HMAC using SHA-384. + /// + /// See: + HS384: "HS384", + + /// HMAC using SHA-512. + /// + /// See: + HS512: "HS512", + + /// RSASSA-PKCS1-v1_5 using SHA-256. + /// + /// See: + RS256: "RS256", + + /// RSASSA-PKCS1-v1_5 using SHA-384. + /// + /// See: + RS384: "RS384", + + /// RSASSA-PKCS1-v1_5 using SHA-512. + /// + /// See: + RS512: "RS512", + + /// RSASSA-PSS using SHA-256 and MGF1 with SHA-256. + /// + /// See: + PS256: "PS256", + + /// RSASSA-PSS using SHA-384 and MGF1 with SHA-384. + /// + /// See: + PS384: "PS384", + + /// RSASSA-PSS using SHA-512 and MGF1 with SHA-512. + /// + /// See: + PS512: "PS512", + + /// Edwards-curve Digital Signature Algorithm (EdDSA) using SHA-256. + /// + /// The following curves are defined for use with `EdDSA`: + /// - `Ed25519` + /// - `Ed448` + /// + /// See: + EdDSA: "EdDSA", + + /// EdDSA using SHA-256 and Blake2b as pre-hash function. + EdBlake2b: "EdBlake2b", // TODO Blake2b is supposed to replace SHA-256 + + /// ECDSA using P-256 and SHA-256. + /// + /// See: + ES256: "ES256", + + /// ECDSA using P-384 and SHA-384. + /// + /// See: + ES384: "ES384", + + /// ECDSA using secp256k1 (K-256) and SHA-256. + /// + /// See: + ES256K: "ES256K", + + /// ECDSA using secp256k1 (K-256) and SHA-256 with a recovery bit. + /// + /// `ES256K-R` is similar to `ES256K` with the recovery bit appended, making + /// the signature 65 bytes instead of 64. The recovery bit is used to + /// extract the public key from the signature. + /// + /// See: + ES256KR: "ES256K-R", + + /// ECDSA using secp256k1 (K-256) and Keccak-256. + /// + /// Like `ES256K` but using Keccak-256 instead of SHA-256. + ESKeccakK: "ESKeccakK", + + /// ECDSA using secp256k1 (K-256) and Keccak-256 with a recovery bit. + /// + /// Like `ES256K-R` but using Keccak-256 instead of SHA-256. + ESKeccakKR: "ESKeccakKR", + + /// ECDSA using P-256 and Blake2b. + ESBlake2b: "ESBlake2b", + + /// ECDSA using secp256k1 (K-256) and Blake2b. + ESBlake2bK: "ESBlake2bK", + + /// BBS scheme. + Bbs(BbsInstance): "BBS", + // Bbs: "BBS", + + #[doc(hidden)] + AleoTestnet1Signature: "AleoTestnet1Signature" +} + +impl Algorithm { + /// Checks if this algorithm is compatible with the `other` algorithm. + /// + /// An algorithm `A` is compatible with `B` if `A` can be used to verify a + /// signature created from `B`. + pub fn is_compatible_with(&self, other: Self) -> bool { + match self { + Self::ES256K | Self::ES256KR | Self::ESKeccakK | Self::ESKeccakKR => matches!( + other, + Self::ES256K | Self::ES256KR | Self::ESKeccakK | Self::ESKeccakKR + ), + a => *a == other, + } + } +} + +impl Default for Algorithm { + fn default() -> Self { + Self::None + } +} + +impl AsRef for Algorithm { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl fmt::Display for Algorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AlgorithmError { + /// Missing algorithm. + #[error("missing algorithm")] + Missing, + + /// Unsupported algorithm. + #[error("unsupported signature algorithm `{0}`")] + Unsupported(Algorithm), +} + +#[derive(Debug, thiserror::Error)] +#[error("unsupported signature algorithm `{0}`")] +pub struct UnsupportedAlgorithm(pub Algorithm); + +/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AnyES256K { + /// ECDSA using secp256k1 (K-256) and SHA-256, without recovery bit. + ES256K, + + /// ECDSA using secp256k1 (K-256) and SHA-256, with recovery bit. + ES256KR, +} + +impl SignatureAlgorithmType for AnyES256K { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for AnyES256K { + type Algorithm = AnyES256K; + + fn algorithm(&self) -> AnyES256K { + *self + } +} + +impl TryFrom for AnyES256K { + type Error = UnsupportedAlgorithm; + + fn try_from(value: Algorithm) -> Result { + match value { + Algorithm::ES256K => Ok(Self::ES256K), + Algorithm::ES256KR => Ok(Self::ES256KR), + other => Err(UnsupportedAlgorithm(other)), + } + } +} + +impl From for Algorithm { + fn from(value: AnyES256K) -> Self { + match value { + AnyES256K::ES256K => Self::ES256K, + AnyES256K::ES256KR => Self::ES256KR, + } + } +} + +impl From for AnyES256K { + fn from(_value: ES256K) -> Self { + Self::ES256K + } +} + +impl From for AnyES256K { + fn from(_value: ES256KR) -> Self { + Self::ES256KR + } +} + +/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AnyESKeccakK { + /// ECDSA using secp256k1 (K-256) and Keccak-256. + /// + /// Like `ES256K` but using Keccak-256 instead of SHA-256. + ESKeccakK, + + /// ECDSA using secp256k1 (K-256) and Keccak-256 with a recovery bit. + /// + /// Like `ES256K-R` but using Keccak-256 instead of SHA-256. + ESKeccakKR, +} + +impl SignatureAlgorithmType for AnyESKeccakK { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for AnyESKeccakK { + type Algorithm = AnyESKeccakK; + + fn algorithm(&self) -> AnyESKeccakK { + *self + } +} + +impl TryFrom for AnyESKeccakK { + type Error = UnsupportedAlgorithm; + + fn try_from(value: Algorithm) -> Result { + match value { + Algorithm::ESKeccakK => Ok(Self::ESKeccakK), + Algorithm::ESKeccakKR => Ok(Self::ESKeccakKR), + other => Err(UnsupportedAlgorithm(other)), + } + } +} + +impl From for Algorithm { + fn from(value: AnyESKeccakK) -> Self { + match value { + AnyESKeccakK::ESKeccakK => Self::ESKeccakK, + AnyESKeccakK::ESKeccakKR => Self::ESKeccakKR, + } + } +} + +impl From for AlgorithmInstance { + fn from(value: AnyESKeccakK) -> Self { + match value { + AnyESKeccakK::ESKeccakK => Self::ESKeccakK, + AnyESKeccakK::ESKeccakKR => Self::ESKeccakKR, + } + } +} + +impl From for AnyESKeccakK { + fn from(_value: ESKeccakK) -> Self { + Self::ESKeccakK + } +} + +impl From for AnyESKeccakK { + fn from(_value: ESKeccakKR) -> Self { + Self::ESKeccakKR + } +} + +/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AnyES { + /// ECDSA using secp256k1 (K-256) and SHA-256, without recovery bit. + ES256K, + + /// ECDSA using secp256k1 (K-256) and SHA-256, with recovery bit. + ES256KR, + + ESKeccakK, + + /// ECDSA using secp256k1 (K-256) and Keccak-256 with a recovery bit. + /// + /// Like `ES256K-R` but using Keccak-256 instead of SHA-256. + ESKeccakKR, +} + +impl SignatureAlgorithmType for AnyES { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for AnyES { + type Algorithm = AnyES; + + fn algorithm(&self) -> AnyES { + *self + } +} + +impl TryFrom for AnyES { + type Error = UnsupportedAlgorithm; + + fn try_from(value: Algorithm) -> Result { + match value { + Algorithm::ES256K => Ok(Self::ES256K), + Algorithm::ES256KR => Ok(Self::ES256KR), + other => Err(UnsupportedAlgorithm(other)), + } + } +} + +impl From for Algorithm { + fn from(value: AnyES) -> Self { + match value { + AnyES::ES256K => Self::ES256K, + AnyES::ES256KR => Self::ES256KR, + AnyES::ESKeccakK => Self::ESKeccakK, + AnyES::ESKeccakKR => Self::ESKeccakKR, + } + } +} + +impl From for AnyES { + fn from(_value: ES256K) -> Self { + Self::ES256K + } +} + +impl From for AnyES { + fn from(_value: ES256KR) -> Self { + Self::ES256KR + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AnyBlake2b { + EdBlake2b, + ESBlake2bK, + ESBlake2b, +} + +impl SignatureAlgorithmType for AnyBlake2b { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for AnyBlake2b { + type Algorithm = Self; + + fn algorithm(&self) -> AnyBlake2b { + *self + } +} + +impl From for Algorithm { + fn from(value: AnyBlake2b) -> Self { + match value { + AnyBlake2b::EdBlake2b => Self::EdBlake2b, + AnyBlake2b::ESBlake2bK => Self::ESBlake2bK, + AnyBlake2b::ESBlake2b => Self::ESBlake2b, + } + } +} + +impl From for AlgorithmInstance { + fn from(value: AnyBlake2b) -> Self { + match value { + AnyBlake2b::EdBlake2b => Self::EdBlake2b, + AnyBlake2b::ESBlake2bK => Self::ESBlake2bK, + AnyBlake2b::ESBlake2b => Self::ESBlake2b, + } + } +} + +impl TryFrom for AnyBlake2b { + type Error = UnsupportedAlgorithm; + + fn try_from(value: Algorithm) -> Result { + match value { + Algorithm::EdBlake2b => Ok(Self::EdBlake2b), + Algorithm::ESBlake2bK => Ok(Self::ESBlake2bK), + Algorithm::ESBlake2b => Ok(Self::ESBlake2b), + a => Err(UnsupportedAlgorithm(a)), + } + } +} + +impl TryFrom for AnyBlake2b { + type Error = UnsupportedAlgorithm; + + fn try_from(value: AlgorithmInstance) -> Result { + match value { + AlgorithmInstance::EdBlake2b => Ok(Self::EdBlake2b), + AlgorithmInstance::ESBlake2bK => Ok(Self::ESBlake2bK), + AlgorithmInstance::ESBlake2b => Ok(Self::ESBlake2b), + a => Err(UnsupportedAlgorithm(a.algorithm())), + } + } +} + +#[derive(Debug, Clone)] +pub struct BbsInstance(pub Box); + +#[derive(Debug, Clone)] +pub enum BbsParameters { + Baseline { + header: [u8; 64], + }, + Blind { + header: [u8; 64], + commitment_with_proof: Option>, + signer_blind: Option<[u8; 32]>, + }, +} diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs index 1013bff98..e4ba73857 100644 --- a/crates/crypto/src/lib.rs +++ b/crates/crypto/src/lib.rs @@ -1,4 +1,7 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +pub mod algorithm; pub mod hashes; pub mod signatures; + +pub use algorithm::{Algorithm, AlgorithmError, AlgorithmInstance, UnsupportedAlgorithm}; diff --git a/crates/dids/methods/key/Cargo.toml b/crates/dids/methods/key/Cargo.toml index 2bce4a9e4..a0223db70 100644 --- a/crates/dids/methods/key/Cargo.toml +++ b/crates/dids/methods/key/Cargo.toml @@ -22,6 +22,7 @@ secp256r1 = [ "ssi-jwk/secp256r1" ] secp384r1 = ["ssi-jwk/secp384r1"] +bbs = ["ssi-jwk/bbs"] [dependencies] ssi-dids-core.workspace = true diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 763be07f3..7b08f9a9d 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -17,21 +17,6 @@ use ssi_multicodec::MultiEncodedBuf; use static_iref::{iri, iri_ref}; use std::collections::BTreeMap; -#[derive(Debug, thiserror::Error)] -pub enum GenerateError { - #[error("missing curve")] - MissingCurve, - - #[error("unsupported curve `{0}`")] - UnsupportedCurve(String), - - #[error("unsupported key type")] - UnsupportedKeyType, - - #[error("invalid input key")] - InvalidInputKey, -} - /// The did:key Method v0.7. /// /// See: @@ -39,99 +24,22 @@ pub struct DIDKey; impl DIDKey { pub fn generate(jwk: &JWK) -> Result { - use ssi_jwk::Params; - let id = match jwk.params { - Params::OKP(ref params) => match ¶ms.curve[..] { - "Ed25519" => multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes( - ssi_multicodec::ED25519_PUB, - ¶ms.public_key.0, - ) - .into_bytes(), - ), - "Bls12381G2" => multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes( - ssi_multicodec::BLS12_381_G2_PUB, - ¶ms.public_key.0, - ) - .into_bytes(), - ), - _ => return Err(GenerateError::UnsupportedCurve(params.curve.clone())), - }, - Params::EC(ref params) => { - let curve = match params.curve { - Some(ref curve) => curve, - None => return Err(GenerateError::MissingCurve), - }; - - match curve.as_str() { - #[cfg(feature = "secp256k1")] - "secp256k1" => { - use k256::elliptic_curve::sec1::ToEncodedPoint; - let pk = match k256::PublicKey::try_from(params) { - Ok(pk) => pk, - Err(_err) => return Err(GenerateError::InvalidInputKey), - }; - - multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes( - ssi_multicodec::SECP256K1_PUB, - pk.to_encoded_point(true).as_bytes(), - ) - .into_bytes(), - ) - } - #[cfg(feature = "secp256r1")] - "P-256" => { - use p256::elliptic_curve::sec1::ToEncodedPoint; - let pk = match p256::PublicKey::try_from(params) { - Ok(pk) => pk, - Err(_err) => return Err(GenerateError::InvalidInputKey), - }; - - multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes( - ssi_multicodec::P256_PUB, - pk.to_encoded_point(true).as_bytes(), - ) - .into_bytes(), - ) - } - #[cfg(feature = "secp384r1")] - "P-384" => { - let pk_bytes = match ssi_jwk::serialize_p384(params) { - Ok(pk) => pk, - Err(_err) => return Err(GenerateError::InvalidInputKey), - }; - - multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes(ssi_multicodec::P384_PUB, &pk_bytes) - .into_bytes(), - ) - } - _ => return Err(GenerateError::UnsupportedCurve(curve.to_owned())), - } - } - Params::RSA(ref params) => { - let der = simple_asn1::der_encode(¶ms.to_public()) - .map_err(|_| GenerateError::InvalidInputKey)?; - multibase::encode( - multibase::Base::Base58Btc, - MultiEncodedBuf::encode_bytes(ssi_multicodec::RSA_PUB, &der).into_bytes(), - ) - } - _ => return Err(GenerateError::UnsupportedKeyType), - }; + let multi_encoded = jwk.to_multicodec()?; + let id = multibase::encode(multibase::Base::Base58Btc, multi_encoded.into_bytes()); Ok(DIDBuf::from_string(format!("did:key:{id}")).unwrap()) } + + pub fn generate_url(jwk: &JWK) -> Result { + let multi_encoded = jwk.to_multicodec()?; + let id = multibase::encode(multibase::Base::Base58Btc, multi_encoded.into_bytes()); + + Ok(DIDURLBuf::from_string(format!("did:key:{id}#{id}")).unwrap()) + } } +pub type GenerateError = ssi_jwk::ToMulticodecError; + impl DIDMethod for DIDKey { const DID_METHOD_NAME: &'static str = "key"; } diff --git a/crates/json-ld/Cargo.toml b/crates/json-ld/Cargo.toml index da65fa8e2..5835fb528 100644 --- a/crates/json-ld/Cargo.toml +++ b/crates/json-ld/Cargo.toml @@ -11,7 +11,8 @@ documentation = "https://docs.rs/ssi-json-ld/" [dependencies] thiserror.workspace = true async-std = { version = "1.9", features = ["attributes"] } -json-ld = { version = "0.20", features = ["serde"] } +# json-ld = { version = "0.20", features = ["serde"] } +json-ld = { git = "https://github.com/timothee-haudebourg/json-ld", branch = "ensure-to-rdf-is-send", features = ["serde"] } iref.workspace = true static-iref.workspace = true rdf-types.workspace = true diff --git a/crates/jwk/Cargo.toml b/crates/jwk/Cargo.toml index 0501e0161..e62cb2bfb 100644 --- a/crates/jwk/Cargo.toml +++ b/crates/jwk/Cargo.toml @@ -42,6 +42,8 @@ tezos = ["blake2b_simd", "secp256k1", "secp256r1", "bs58"] ring = ["dep:ring"] +bbs = ["ssi-bbs"] + [dependencies] ssi-claims-core.workspace = true num-bigint = "0.4" @@ -62,6 +64,7 @@ rand = { workspace = true, optional = true } ed25519-dalek = { workspace = true, optional = true, features = ["rand_core"] } lazy_static = "1.4" bs58 = { workspace = true, features = ["check"], optional = true } +ssi-bbs = { workspace = true, optional = true } blake2b_simd = { version = "0.5", optional = true } multibase = "0.9.1" diff --git a/crates/jwk/src/algorithm.rs b/crates/jwk/src/algorithm.rs index 8495731a9..a4b242821 100644 --- a/crates/jwk/src/algorithm.rs +++ b/crates/jwk/src/algorithm.rs @@ -1,6 +1,9 @@ use core::fmt; - use serde::{Deserialize, Serialize}; +use ssi_crypto::{ + algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}, + UnsupportedAlgorithm, +}; macro_rules! algorithms { ($( @@ -47,28 +50,66 @@ macro_rules! algorithms { } } + impl From for ssi_crypto::Algorithm { + fn from(a: Algorithm) -> Self { + match a { + $(Algorithm::$id => Self::$id,)* + Algorithm::None => Self::None + } + } + } + + impl From for ssi_crypto::AlgorithmInstance { + fn from(a: Algorithm) -> Self { + match a { + $(Algorithm::$id => Self::$id,)* + Algorithm::None => Self::None + } + } + } + $( - $(#[doc = $doc])* - #[derive(Debug, Default, Clone, Copy, PartialEq, Hash, Eq)] - pub struct $id; + impl From for Algorithm { + fn from(_: ssi_crypto::algorithm::$id) -> Self { + Self::$id + } + } - impl TryFrom for $id { + impl TryFrom for ssi_crypto::algorithm::$id { type Error = UnsupportedAlgorithm; - fn try_from(a: Algorithm) -> Result { - match a { + fn try_from(value: Algorithm) -> Result { + match value { Algorithm::$id => Ok(Self), - a => Err(UnsupportedAlgorithm(a)) + other => Err(UnsupportedAlgorithm(other.into())) } } } + )* - impl From<$id> for Algorithm { - fn from(_a: $id) -> Self { - Self::$id + impl TryFrom for Algorithm { + type Error = UnsupportedAlgorithm; + + fn try_from(a: ssi_crypto::Algorithm) -> Result { + match a { + $(ssi_crypto::Algorithm::$id => Ok(Self::$id),)* + ssi_crypto::Algorithm::None => Ok(Self::None), + other => Err(UnsupportedAlgorithm(other)) } } - )* + } + + impl TryFrom for Algorithm { + type Error = UnsupportedAlgorithm; + + fn try_from(a: ssi_crypto::AlgorithmInstance) -> Result { + match a { + $(ssi_crypto::AlgorithmInstance::$id => Ok(Self::$id),)* + ssi_crypto::AlgorithmInstance::None => Ok(Self::None), + other => Err(UnsupportedAlgorithm(other.algorithm())) + } + } + } }; } @@ -190,6 +231,18 @@ impl Algorithm { } } +impl SignatureAlgorithmType for Algorithm { + type Instance = Self; +} + +impl SignatureAlgorithmInstance for Algorithm { + type Algorithm = Self; + + fn algorithm(&self) -> Self { + *self + } +} + impl Default for Algorithm { fn default() -> Self { Self::None @@ -208,203 +261,25 @@ impl fmt::Display for Algorithm { } } -#[derive(Debug, thiserror::Error)] -pub enum AlgorithmError { - /// Missing algorithm. - #[error("missing algorithm")] - Missing, - - /// Unsupported algorithm. - #[error("unsupported signature algorithm `{0}`")] - Unsupported(Algorithm), -} - -#[derive(Debug, thiserror::Error)] -#[error("unsupported signature algorithm `{0}`")] -pub struct UnsupportedAlgorithm(pub Algorithm); - -/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum AnyES256K { - /// ECDSA using secp256k1 (K-256) and SHA-256, without recovery bit. - ES256K, - - /// ECDSA using secp256k1 (K-256) and SHA-256, with recovery bit. - ES256KR, -} - -impl TryFrom for AnyES256K { - type Error = UnsupportedAlgorithm; - - fn try_from(value: Algorithm) -> Result { +impl From for Algorithm { + fn from(value: ssi_crypto::algorithm::AnyBlake2b) -> Self { match value { - Algorithm::ES256K => Ok(Self::ES256K), - Algorithm::ES256KR => Ok(Self::ES256KR), - other => Err(UnsupportedAlgorithm(other)), + ssi_crypto::algorithm::AnyBlake2b::ESBlake2b => Self::ESBlake2b, + ssi_crypto::algorithm::AnyBlake2b::ESBlake2bK => Self::ESBlake2bK, + ssi_crypto::algorithm::AnyBlake2b::EdBlake2b => Self::EdBlake2b, } } } -impl From for Algorithm { - fn from(value: AnyES256K) -> Self { - match value { - AnyES256K::ES256K => Self::ES256K, - AnyES256K::ES256KR => Self::ES256KR, - } - } -} - -impl From for AnyES256K { - fn from(_value: ES256K) -> Self { - Self::ES256K - } -} - -impl From for AnyES256K { - fn from(_value: ES256KR) -> Self { - Self::ES256KR - } -} - -/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum AnyESKeccakK { - /// ECDSA using secp256k1 (K-256) and Keccak-256. - /// - /// Like `ES256K` but using Keccak-256 instead of SHA-256. - ESKeccakK, - - /// ECDSA using secp256k1 (K-256) and Keccak-256 with a recovery bit. - /// - /// Like `ES256K-R` but using Keccak-256 instead of SHA-256. - ESKeccakKR, -} - -impl TryFrom for AnyESKeccakK { +impl TryFrom for ssi_crypto::algorithm::AnyBlake2b { type Error = UnsupportedAlgorithm; fn try_from(value: Algorithm) -> Result { match value { - Algorithm::ESKeccakK => Ok(Self::ESKeccakK), - Algorithm::ESKeccakKR => Ok(Self::ESKeccakKR), - other => Err(UnsupportedAlgorithm(other)), - } - } -} - -impl From for Algorithm { - fn from(value: AnyESKeccakK) -> Self { - match value { - AnyESKeccakK::ESKeccakK => Self::ESKeccakK, - AnyESKeccakK::ESKeccakKR => Self::ESKeccakKR, - } - } -} - -impl From for AnyESKeccakK { - fn from(_value: ESKeccakK) -> Self { - Self::ESKeccakK - } -} - -impl From for AnyESKeccakK { - fn from(_value: ESKeccakKR) -> Self { - Self::ESKeccakKR - } -} - -/// ECDSA using secp256k1 (K-256) and SHA-256, with or without recovery bit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum AnyES { - /// ECDSA using secp256k1 (K-256) and SHA-256, without recovery bit. - ES256K, - - /// ECDSA using secp256k1 (K-256) and SHA-256, with recovery bit. - ES256KR, - - ESKeccakK, - - /// ECDSA using secp256k1 (K-256) and Keccak-256 with a recovery bit. - /// - /// Like `ES256K-R` but using Keccak-256 instead of SHA-256. - ESKeccakKR, -} - -impl TryFrom for AnyES { - type Error = UnsupportedAlgorithm; - - fn try_from(value: Algorithm) -> Result { - match value { - Algorithm::ES256K => Ok(Self::ES256K), - Algorithm::ES256KR => Ok(Self::ES256KR), - other => Err(UnsupportedAlgorithm(other)), - } - } -} - -impl From for Algorithm { - fn from(value: AnyES) -> Self { - match value { - AnyES::ES256K => Self::ES256K, - AnyES::ES256KR => Self::ES256KR, - AnyES::ESKeccakK => Self::ESKeccakK, - AnyES::ESKeccakKR => Self::ESKeccakKR, - } - } -} - -impl From for AnyES { - fn from(_value: ES256K) -> Self { - Self::ES256K - } -} - -impl From for AnyES { - fn from(_value: ES256KR) -> Self { - Self::ES256KR - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum AnyBlake2b { - EdBlake2b, - ESBlake2bK, - ESBlake2b, -} - -impl From for Algorithm { - fn from(value: AnyBlake2b) -> Self { - match value { - AnyBlake2b::EdBlake2b => Self::EdBlake2b, - AnyBlake2b::ESBlake2bK => Self::ESBlake2bK, - AnyBlake2b::ESBlake2b => Self::ESBlake2b, - } - } -} - -impl TryFrom for AnyBlake2b { - type Error = UnsupportedAlgorithm; - - fn try_from(value: Algorithm) -> Result { - match value { - Algorithm::EdBlake2b => Ok(Self::EdBlake2b), - Algorithm::ESBlake2bK => Ok(Self::ESBlake2bK), Algorithm::ESBlake2b => Ok(Self::ESBlake2b), - a => Err(UnsupportedAlgorithm(a)), - } - } -} - -impl TryFrom> for AnyBlake2b { - type Error = AlgorithmError; - - fn try_from(value: Option) -> Result { - match value { - Some(Algorithm::EdBlake2b) => Ok(Self::EdBlake2b), - Some(Algorithm::ESBlake2bK) => Ok(Self::ESBlake2bK), - Some(Algorithm::ESBlake2b) => Ok(Self::ESBlake2b), - Some(a) => Err(AlgorithmError::Unsupported(a)), - None => Err(AlgorithmError::Missing), + Algorithm::ESBlake2bK => Ok(Self::ESBlake2bK), + Algorithm::EdBlake2b => Ok(Self::EdBlake2b), + other => Err(UnsupportedAlgorithm(other.into())), } } } diff --git a/crates/jwk/src/bbs.rs b/crates/jwk/src/bbs.rs new file mode 100644 index 000000000..941f94c28 --- /dev/null +++ b/crates/jwk/src/bbs.rs @@ -0,0 +1,201 @@ +use ssi_bbs::{BBSplusPublicKey, BBSplusSecretKey}; + +use crate::{Base64urlUInt, ECParams, Error, Params, JWK}; + +impl JWK { + pub fn generate_bls12381g2_with(rng: &mut (impl rand::CryptoRng + rand::RngCore)) -> Self { + ssi_bbs::generate_secret_key(rng).into() + } + + pub fn generate_bls12381g2() -> Self { + let mut rng = rand::rngs::OsRng {}; + Self::generate_bls12381g2_with(&mut rng) + } +} + +impl<'a> TryFrom<&'a JWK> for BBSplusPublicKey { + type Error = Error; + + fn try_from(value: &'a JWK) -> Result { + (&value.params).try_into() + } +} + +impl TryFrom for BBSplusPublicKey { + type Error = Error; + + fn try_from(value: JWK) -> Result { + value.params.try_into() + } +} + +impl<'a> From<&'a BBSplusPublicKey> for ECParams { + fn from(value: &'a BBSplusPublicKey) -> Self { + let (x, y) = value.to_coordinates(); + Self { + curve: Some("BLS12381G2".to_owned()), + x_coordinate: Some(Base64urlUInt(x.to_vec())), + y_coordinate: Some(Base64urlUInt(y.to_vec())), + ecc_private_key: None, + } + } +} + +impl From for ECParams { + fn from(value: BBSplusPublicKey) -> Self { + (&value).into() + } +} + +impl<'a> From<&'a BBSplusPublicKey> for Params { + fn from(value: &'a BBSplusPublicKey) -> Self { + Self::EC(value.into()) + } +} + +impl From for Params { + fn from(value: BBSplusPublicKey) -> Self { + Self::EC(value.into()) + } +} + +impl<'a> From<&'a BBSplusPublicKey> for JWK { + fn from(value: &'a BBSplusPublicKey) -> Self { + Params::from(value).into() + } +} + +impl From for JWK { + fn from(value: BBSplusPublicKey) -> Self { + Params::from(value).into() + } +} + +impl<'a> TryFrom<&'a Params> for BBSplusPublicKey { + type Error = Error; + + fn try_from(value: &'a Params) -> Result { + match value { + Params::EC(params) => match params.curve.as_deref() { + Some("BLS12381G2") => { + let x: &[u8; 96] = params + .x_coordinate + .as_ref() + .ok_or(Error::MissingPoint)? + .0 + .as_slice() + .try_into() + .map_err(|_| Error::InvalidCoordinates)?; + let y: &[u8; 96] = params + .y_coordinate + .as_ref() + .ok_or(Error::MissingPoint)? + .0 + .as_slice() + .try_into() + .map_err(|_| Error::InvalidCoordinates)?; + + BBSplusPublicKey::from_coordinates(x, y).map_err(|_| Error::InvalidCoordinates) + } + Some(other) => Err(Error::CurveNotImplemented(other.to_owned())), + None => Err(Error::MissingCurve), + }, + _ => Err(Error::UnsupportedKeyType), + } + } +} + +impl TryFrom for BBSplusPublicKey { + type Error = Error; + + fn try_from(value: Params) -> Result { + (&value).try_into() + } +} + +impl<'a> TryFrom<&'a JWK> for BBSplusSecretKey { + type Error = Error; + + fn try_from(value: &'a JWK) -> Result { + (&value.params).try_into() + } +} + +impl TryFrom for BBSplusSecretKey { + type Error = Error; + + fn try_from(value: JWK) -> Result { + value.params.try_into() + } +} + +impl<'a> TryFrom<&'a Params> for BBSplusSecretKey { + type Error = Error; + + fn try_from(value: &'a Params) -> Result { + match value { + Params::EC(params) => match params.curve.as_deref() { + Some("BLS12381G2") => { + let p = params + .ecc_private_key + .as_ref() + .ok_or(Error::MissingPrivateKey)? + .0 + .as_slice(); + + BBSplusSecretKey::from_bytes(p).map_err(|_| Error::InvalidCoordinates) + } + Some(other) => Err(Error::CurveNotImplemented(other.to_owned())), + None => Err(Error::MissingCurve), + }, + _ => Err(Error::UnsupportedKeyType), + } + } +} + +impl TryFrom for BBSplusSecretKey { + type Error = Error; + + fn try_from(value: Params) -> Result { + (&value).try_into() + } +} + +impl<'a> From<&'a BBSplusSecretKey> for ECParams { + fn from(value: &'a BBSplusSecretKey) -> Self { + let pk = value.public_key(); + let mut params: ECParams = pk.into(); + params.ecc_private_key = Some(Base64urlUInt(value.to_bytes().to_vec())); + params + } +} + +impl From for ECParams { + fn from(value: BBSplusSecretKey) -> Self { + (&value).into() + } +} + +impl<'a> From<&'a BBSplusSecretKey> for Params { + fn from(value: &'a BBSplusSecretKey) -> Self { + Self::EC(value.into()) + } +} + +impl From for Params { + fn from(value: BBSplusSecretKey) -> Self { + Self::EC(value.into()) + } +} + +impl<'a> From<&'a BBSplusSecretKey> for JWK { + fn from(value: &'a BBSplusSecretKey) -> Self { + Params::from(value).into() + } +} + +impl From for JWK { + fn from(value: BBSplusSecretKey) -> Self { + Params::from(value).into() + } +} diff --git a/crates/jwk/src/error.rs b/crates/jwk/src/error.rs index efd05f32c..57c115925 100644 --- a/crates/jwk/src/error.rs +++ b/crates/jwk/src/error.rs @@ -136,6 +136,9 @@ pub enum Error { /// Error parsing or producing multibase #[error(transparent)] Multibase(#[from] multibase::Error), + + #[error("Invalid coordinates")] + InvalidCoordinates, } #[cfg(feature = "ring")] diff --git a/crates/jwk/src/lib.rs b/crates/jwk/src/lib.rs index 08383895d..9884b6f37 100644 --- a/crates/jwk/src/lib.rs +++ b/crates/jwk/src/lib.rs @@ -3,7 +3,6 @@ use core::fmt; use num_bigint::{BigInt, Sign}; use simple_asn1::{ASN1Block, ASN1Class, ToASN1}; -use ssi_multicodec::MultiEncoded; use std::result::Result; use std::{convert::TryFrom, str::FromStr}; use zeroize::Zeroize; @@ -29,6 +28,12 @@ pub mod eip155; #[cfg(feature = "tezos")] pub mod blakesig; +#[cfg(feature = "bbs")] +mod bbs; + +mod multicodec; +pub use multicodec::*; + pub mod der; use der::{ @@ -494,87 +499,6 @@ impl JWK { let thumbprint = String::from(Base64urlUInt(hash.to_vec())); Ok(thumbprint) } - - pub fn from_multicodec(multicodec: &MultiEncoded) -> Result { - #[allow(unused_variables)] - let (codec, k) = multicodec.parts(); - match codec { - #[cfg(feature = "rsa")] - ssi_multicodec::RSA_PUB => rsa_x509_pub_parse(k).map_err(FromMulticodecError::RsaPub), - #[cfg(feature = "ed25519")] - ssi_multicodec::ED25519_PUB => { - ed25519_parse(k).map_err(FromMulticodecError::Ed25519Pub) - } - #[cfg(feature = "ed25519")] - ssi_multicodec::ED25519_PRIV => { - ed25519_parse_private(k).map_err(FromMulticodecError::Ed25519Priv) - } - #[cfg(feature = "secp256k1")] - ssi_multicodec::SECP256K1_PUB => { - secp256k1_parse(k).map_err(FromMulticodecError::Secp256k1Pub) - } - #[cfg(feature = "secp256k1")] - ssi_multicodec::SECP256K1_PRIV => { - secp256k1_parse_private(k).map_err(FromMulticodecError::Secp256k1Priv) - } - #[cfg(feature = "secp256r1")] - ssi_multicodec::P256_PUB => p256_parse(k).map_err(FromMulticodecError::Secp256r1Pub), - #[cfg(feature = "secp256r1")] - ssi_multicodec::P256_PRIV => { - p256_parse_private(k).map_err(FromMulticodecError::Secp256r1Priv) - } - #[cfg(feature = "secp384r1")] - ssi_multicodec::P384_PUB => p384_parse(k).map_err(FromMulticodecError::Secp384r1Pub), - #[cfg(feature = "secp384r1")] - ssi_multicodec::P384_PRIV => { - p384_parse_private(k).map_err(FromMulticodecError::Secp384r1Priv) - } - _ => Err(FromMulticodecError::UnsupportedCodec(codec)), - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum FromMulticodecError { - #[cfg(feature = "rsa")] - #[error(transparent)] - RsaPub(RsaX509PubParseError), - - #[cfg(feature = "ed25519")] - #[error(transparent)] - Ed25519Pub(Error), - - #[cfg(feature = "ed25519")] - #[error(transparent)] - Ed25519Priv(Error), - - #[cfg(feature = "secp256k1")] - #[error(transparent)] - Secp256k1Pub(Error), - - #[cfg(feature = "secp256k1")] - #[error(transparent)] - Secp256k1Priv(Error), - - #[cfg(feature = "secp256r1")] - #[error(transparent)] - Secp256r1Pub(Error), - - #[cfg(feature = "secp256r1")] - #[error(transparent)] - Secp256r1Priv(Error), - - #[cfg(feature = "secp384r1")] - #[error(transparent)] - Secp384r1Pub(Error), - - #[cfg(feature = "secp384r1")] - #[error(transparent)] - Secp384r1Priv(Error), - - /// Unexpected multibase (multicodec) key prefix multicodec - #[error("Unsupported multicodec key type 0x{0:x}")] - UnsupportedCodec(u64), } impl From for JWK { diff --git a/crates/jwk/src/multicodec.rs b/crates/jwk/src/multicodec.rs new file mode 100644 index 000000000..adcf11a84 --- /dev/null +++ b/crates/jwk/src/multicodec.rs @@ -0,0 +1,178 @@ +use ssi_multicodec::{MultiEncoded, MultiEncodedBuf}; + +use crate::{Error, Params, RsaX509PubParseError, JWK}; + +impl JWK { + pub fn from_multicodec(multicodec: &MultiEncoded) -> Result { + #[allow(unused_variables)] + let (codec, k) = multicodec.parts(); + match codec { + #[cfg(feature = "rsa")] + ssi_multicodec::RSA_PUB => { + crate::rsa_x509_pub_parse(k).map_err(FromMulticodecError::RsaPub) + } + #[cfg(feature = "ed25519")] + ssi_multicodec::ED25519_PUB => { + crate::ed25519_parse(k).map_err(FromMulticodecError::Ed25519Pub) + } + #[cfg(feature = "ed25519")] + ssi_multicodec::ED25519_PRIV => { + crate::ed25519_parse_private(k).map_err(FromMulticodecError::Ed25519Priv) + } + #[cfg(feature = "secp256k1")] + ssi_multicodec::SECP256K1_PUB => { + crate::secp256k1_parse(k).map_err(FromMulticodecError::Secp256k1Pub) + } + #[cfg(feature = "secp256k1")] + ssi_multicodec::SECP256K1_PRIV => { + crate::secp256k1_parse_private(k).map_err(FromMulticodecError::Secp256k1Priv) + } + #[cfg(feature = "secp256r1")] + ssi_multicodec::P256_PUB => { + crate::p256_parse(k).map_err(FromMulticodecError::Secp256r1Pub) + } + #[cfg(feature = "secp256r1")] + ssi_multicodec::P256_PRIV => { + crate::p256_parse_private(k).map_err(FromMulticodecError::Secp256r1Priv) + } + #[cfg(feature = "secp384r1")] + ssi_multicodec::P384_PUB => { + crate::p384_parse(k).map_err(FromMulticodecError::Secp384r1Pub) + } + #[cfg(feature = "secp384r1")] + ssi_multicodec::P384_PRIV => { + crate::p384_parse_private(k).map_err(FromMulticodecError::Secp384r1Priv) + } + _ => Err(FromMulticodecError::UnsupportedCodec(codec)), + } + } + + pub fn to_multicodec(&self) -> Result { + match self.params { + Params::OKP(ref params) => match ¶ms.curve[..] { + "Ed25519" => Ok(MultiEncodedBuf::encode_bytes( + ssi_multicodec::ED25519_PUB, + ¶ms.public_key.0, + )), + _ => Err(ToMulticodecError::UnsupportedCurve(params.curve.clone())), + }, + Params::EC(ref params) => { + let curve = match params.curve { + Some(ref curve) => curve, + None => return Err(ToMulticodecError::MissingCurve), + }; + + match curve.as_str() { + #[cfg(feature = "secp256k1")] + "secp256k1" => { + use k256::elliptic_curve::sec1::ToEncodedPoint; + let pk = k256::PublicKey::try_from(params) + .map_err(ToMulticodecError::InvalidInputKey)?; + + Ok(MultiEncodedBuf::encode_bytes( + ssi_multicodec::SECP256K1_PUB, + pk.to_encoded_point(true).as_bytes(), + )) + } + #[cfg(feature = "secp256r1")] + "P-256" => { + use p256::elliptic_curve::sec1::ToEncodedPoint; + let pk = p256::PublicKey::try_from(params) + .map_err(ToMulticodecError::InvalidInputKey)?; + + Ok(MultiEncodedBuf::encode_bytes( + ssi_multicodec::P256_PUB, + pk.to_encoded_point(true).as_bytes(), + )) + } + #[cfg(feature = "secp384r1")] + "P-384" => { + let pk_bytes = crate::serialize_p384(params) + .map_err(ToMulticodecError::InvalidInputKey)?; + + Ok(MultiEncodedBuf::encode_bytes( + ssi_multicodec::P384_PUB, + &pk_bytes, + )) + } + #[cfg(feature = "bbs")] + "BLS12381G2" => { + let pk: ssi_bbs::BBSplusPublicKey = self + .try_into() + .map_err(ToMulticodecError::InvalidInputKey)?; + + Ok(MultiEncodedBuf::encode_bytes( + ssi_multicodec::BLS12_381_G2_PUB, + &pk.to_bytes(), + )) + } + _ => Err(ToMulticodecError::UnsupportedCurve(curve.to_owned())), + } + } + Params::RSA(ref params) => { + let der = simple_asn1::der_encode(¶ms.to_public()) + .map_err(ToMulticodecError::InvalidInputKey)?; + Ok(MultiEncodedBuf::encode_bytes(ssi_multicodec::RSA_PUB, &der)) + } + _ => Err(ToMulticodecError::UnsupportedKeyType), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum FromMulticodecError { + #[cfg(feature = "rsa")] + #[error(transparent)] + RsaPub(RsaX509PubParseError), + + #[cfg(feature = "ed25519")] + #[error(transparent)] + Ed25519Pub(Error), + + #[cfg(feature = "ed25519")] + #[error(transparent)] + Ed25519Priv(Error), + + #[cfg(feature = "secp256k1")] + #[error(transparent)] + Secp256k1Pub(Error), + + #[cfg(feature = "secp256k1")] + #[error(transparent)] + Secp256k1Priv(Error), + + #[cfg(feature = "secp256r1")] + #[error(transparent)] + Secp256r1Pub(Error), + + #[cfg(feature = "secp256r1")] + #[error(transparent)] + Secp256r1Priv(Error), + + #[cfg(feature = "secp384r1")] + #[error(transparent)] + Secp384r1Pub(Error), + + #[cfg(feature = "secp384r1")] + #[error(transparent)] + Secp384r1Priv(Error), + + /// Unexpected multibase (multicodec) key prefix multicodec + #[error("Unsupported multicodec key type 0x{0:x}")] + UnsupportedCodec(u64), +} + +#[derive(Debug, thiserror::Error)] +pub enum ToMulticodecError { + #[error("unsupported key type")] + UnsupportedKeyType, + + #[error("unsupported curve `{0}`")] + UnsupportedCurve(String), + + #[error("missing curve")] + MissingCurve, + + #[error("invalid input key: {0}")] + InvalidInputKey(Error), +} diff --git a/crates/verification-methods/Cargo.toml b/crates/verification-methods/Cargo.toml index d8289f076..d32a46da3 100644 --- a/crates/verification-methods/Cargo.toml +++ b/crates/verification-methods/Cargo.toml @@ -37,7 +37,7 @@ aleo = ["ssi-caips/aleo"] solana = [] -bls12-381 = ["ssi-multicodec/bls12-381", "zkryptium"] +bls12-381 = ["ssi-multicodec/bls12-381", "ssi-jwk/bbs", "ssi-bbs", "ssi-verification-methods-core/bbs"] [dependencies] ssi-core.workspace = true @@ -50,6 +50,7 @@ ssi-claims-core.workspace = true ssi-verification-methods-core.workspace = true ssi-tzkey = { workspace = true, optional = true } ssi-eip712 = { workspace = true, optional = true } +ssi-bbs = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true json-syntax = { workspace = true, features = ["serde"] } @@ -73,7 +74,6 @@ sha3 = { workspace = true, optional = true } k256 = { workspace = true, optional = true, features = ["ecdsa", "sha256"] } p256 = { workspace = true, optional = true, features = ["ecdsa"] } p384 = { workspace = true, optional = true, features = ["ecdsa"] } -zkryptium = { workspace = true, optional = true } # rand_core_0_5 = { version = "0.5", optional = true, package = "rand_core" } rand_core = { version = "0.6.4", optional = true } \ No newline at end of file diff --git a/crates/verification-methods/core/Cargo.toml b/crates/verification-methods/core/Cargo.toml index 2d5c050e6..457c09039 100644 --- a/crates/verification-methods/core/Cargo.toml +++ b/crates/verification-methods/core/Cargo.toml @@ -8,6 +8,9 @@ description = "Core traits and types for verification methods in ssi" repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-verification-methods/" +[features] +bbs = ["ssi-bbs"] + [dependencies] ssi-core.workspace = true ssi-crypto.workspace = true @@ -15,6 +18,7 @@ ssi-claims-core.workspace = true ssi-jwk.workspace = true ssi-jws.workspace = true ssi-json-ld.workspace = true +ssi-bbs = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true iref = { workspace = true, features = ["serde"] } diff --git a/crates/verification-methods/core/src/lib.rs b/crates/verification-methods/core/src/lib.rs index cc7b6f6df..0594cd39f 100644 --- a/crates/verification-methods/core/src/lib.rs +++ b/crates/verification-methods/core/src/lib.rs @@ -1,7 +1,8 @@ use std::{borrow::Cow, collections::HashMap, sync::Arc}; use iref::{Iri, IriBuf}; -use ssi_claims_core::{ProofValidationError, SignatureError}; +use ssi_claims_core::{MessageSignatureError, ProofValidationError, SignatureError}; +use ssi_crypto::algorithm::SignatureAlgorithmType; use ssi_jwk::JWK; use static_iref::iri; @@ -187,22 +188,27 @@ impl VerificationMethodResolver for HashMap { } } -pub trait SigningMethod: VerificationMethod { +pub trait SigningMethod: VerificationMethod { fn sign_bytes( &self, secret: &S, - algorithm: A, + algorithm: A::Instance, bytes: &[u8], ) -> Result, MessageSignatureError>; -} -pub trait MultiSigningMethod: VerificationMethod { fn sign_bytes_multi( &self, secret: &S, - algorithm: A, + algorithm: A::Instance, messages: &[Vec], - ) -> Result, MessageSignatureError>; + ) -> Result, MessageSignatureError> { + match messages.split_first() { + Some((message, [])) => self.sign_bytes(secret, algorithm, message), + // Some(_) => Err(MessageSignatureError::TooManyMessages), + Some(_) => todo!(), + None => Err(MessageSignatureError::MissingMessage), + } + } } pub struct MethodWithSecret { @@ -216,16 +222,20 @@ impl MethodWithSecret { } } -impl, S> MessageSigner for MethodWithSecret { - async fn sign(self, algorithm: A, message: &[u8]) -> Result, MessageSignatureError> { +impl, S> MessageSigner + for MethodWithSecret +{ + async fn sign( + self, + algorithm: A::Instance, + message: &[u8], + ) -> Result, MessageSignatureError> { self.method.sign_bytes(&self.secret, algorithm, message) } -} -impl, S> MultiMessageSigner for MethodWithSecret { async fn sign_multi( self, - algorithm: A, + algorithm: ::Instance, messages: &[Vec], ) -> Result, MessageSignatureError> { self.method diff --git a/crates/verification-methods/core/src/signature/protocol.rs b/crates/verification-methods/core/src/signature/protocol.rs index 5d6db8839..2be7b2f0e 100644 --- a/crates/verification-methods/core/src/signature/protocol.rs +++ b/crates/verification-methods/core/src/signature/protocol.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use crate::MessageSignatureError; +use ssi_claims_core::MessageSignatureError; +use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WithProtocol(pub A, pub P); @@ -11,6 +12,18 @@ impl WithProtocol { } } +impl SignatureAlgorithmType for WithProtocol { + type Instance = WithProtocol; +} + +impl SignatureAlgorithmInstance for WithProtocol { + type Algorithm = WithProtocol; + + fn algorithm(&self) -> Self::Algorithm { + WithProtocol(self.0.algorithm(), self.1) + } +} + /// Signature protocol. /// /// Specifies how the client and signer communicates together to produce a @@ -28,11 +41,15 @@ impl WithProtocol { /// The simplest protocol is described by the unit `()` type, where the raw /// message is transmitted to the signer, which must sign it and return the /// raw bytes. -pub trait SignatureProtocol { +pub trait SignatureProtocol: Copy { fn prepare_message<'b>(&self, bytes: &'b [u8]) -> Cow<'b, [u8]> { Cow::Borrowed(bytes) } + fn prepare_messages<'b>(&self, bytes: &'b [Vec]) -> Cow<'b, [Vec]> { + Cow::Borrowed(bytes) + } + fn encode_signature( &self, _algorithm: A, @@ -61,6 +78,7 @@ impl SignatureProtocol for () {} /// the `Base58Btc` base. /// /// [1]: +#[derive(Debug, Clone, Copy)] pub struct Base58BtcMultibase; impl Base58BtcMultibase { @@ -104,6 +122,7 @@ impl SignatureProtocol for Base58BtcMultibase { /// /// The signer must sent back the signature encoded in base58 (bitcoin /// alphabet). +#[derive(Debug, Clone, Copy)] pub struct Base58Btc; impl Base58Btc { diff --git a/crates/verification-methods/core/src/signature/signer/local.rs b/crates/verification-methods/core/src/signature/signer/local.rs index e318bed2a..b835fb76a 100644 --- a/crates/verification-methods/core/src/signature/signer/local.rs +++ b/crates/verification-methods/core/src/signature/signer/local.rs @@ -1,11 +1,9 @@ use std::ops::Deref; -use ssi_claims_core::SignatureError; +use ssi_claims_core::{MessageSignatureError, SignatureError}; +use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}; -use crate::{ - protocol::WithProtocol, MessageSignatureError, MessageSigner, SignatureProtocol, Signer, - VerificationMethod, -}; +use crate::{protocol::WithProtocol, MessageSigner, SignatureProtocol, Signer, VerificationMethod}; pub struct LocalSigner(pub S); @@ -30,16 +28,28 @@ impl Deref for LocalSigner { pub struct LocalMessageSigner(pub S); -impl, S: MessageSigner> MessageSigner> - for LocalMessageSigner +impl, S: MessageSigner> + MessageSigner> for LocalMessageSigner { async fn sign( self, - WithProtocol(algorithm, protocol): WithProtocol, + WithProtocol(algorithm_instance, protocol): WithProtocol, message: &[u8], ) -> Result, MessageSignatureError> { + let algorithm = algorithm_instance.algorithm(); let message = protocol.prepare_message(message); - let signature = self.0.sign(algorithm, &message).await?; + let signature = self.0.sign(algorithm_instance, &message).await?; + protocol.encode_signature(algorithm, signature) + } + + async fn sign_multi( + self, + WithProtocol(algorithm_instance, protocol): WithProtocol, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + let algorithm = algorithm_instance.algorithm(); + let messages = protocol.prepare_messages(messages); + let signature = self.0.sign_multi(algorithm_instance, &messages).await?; protocol.encode_signature(algorithm, signature) } } diff --git a/crates/verification-methods/core/src/signature/signer/mod.rs b/crates/verification-methods/core/src/signature/signer/mod.rs index b036f2a3e..a7140892e 100644 --- a/crates/verification-methods/core/src/signature/signer/mod.rs +++ b/crates/verification-methods/core/src/signature/signer/mod.rs @@ -1,4 +1,5 @@ -use ssi_claims_core::SignatureError; +use ssi_claims_core::{MessageSignatureError, SignatureError}; +use ssi_crypto::algorithm::SignatureAlgorithmType; use ssi_jwk::JWK; use std::{borrow::Cow, marker::PhantomData}; @@ -32,86 +33,61 @@ impl<'s, M: VerificationMethod, S: Signer> Signer for &'s S { } } -#[derive(Debug, thiserror::Error)] -pub enum MessageSignatureError { - #[error("0")] - SignatureFailed(String), - - #[error("invalid signature client query")] - InvalidQuery, - - #[error("invalid signer response")] - InvalidResponse, - - #[error("invalid public key")] - InvalidPublicKey, - - #[error("invalid secret key")] - InvalidSecretKey, - - #[error("missing signature algorithm")] - MissingAlgorithm, - - #[error("unsupported signature algorithm `{0}`")] - UnsupportedAlgorithm(String), - - #[error("unsupported verification method `{0}`")] - UnsupportedVerificationMethod(String), -} - -impl MessageSignatureError { - pub fn signature_failed(e: impl ToString) -> Self { - Self::SignatureFailed(e.to_string()) - } -} - -impl From for MessageSignatureError { - fn from(value: ssi_jwk::algorithm::AlgorithmError) -> Self { - match value { - ssi_jwk::algorithm::AlgorithmError::Missing => Self::MissingAlgorithm, - ssi_jwk::algorithm::AlgorithmError::Unsupported(a) => { - Self::UnsupportedAlgorithm(a.to_string()) - } - } - } -} - -impl From for MessageSignatureError { - fn from(value: ssi_jwk::algorithm::UnsupportedAlgorithm) -> Self { - Self::UnsupportedAlgorithm(value.0.to_string()) - } -} +pub trait MessageSigner: Sized { + #[allow(async_fn_in_trait)] + async fn sign( + self, + algorithm: A::Instance, + message: &[u8], + ) -> Result, MessageSignatureError>; -impl From for SignatureError { - fn from(value: MessageSignatureError) -> Self { - match value { - MessageSignatureError::MissingAlgorithm => Self::MissingAlgorithm, - MessageSignatureError::UnsupportedAlgorithm(name) => Self::UnsupportedAlgorithm(name), - MessageSignatureError::InvalidSecretKey => Self::InvalidSecretKey, - other => Self::other(other), + #[allow(async_fn_in_trait)] + async fn sign_multi( + self, + algorithm: A::Instance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + match messages.split_first() { + Some((message, [])) => self.sign(algorithm, message).await, + // Some(_) => Err(MessageSignatureError::TooManyMessages), + Some(_) => todo!(), + None => Err(MessageSignatureError::MissingMessage), } } } -pub trait MessageSigner { - #[allow(async_fn_in_trait)] - async fn sign(self, algorithm: A, message: &[u8]) -> Result, MessageSignatureError>; -} - -impl> MessageSigner for JWK { - async fn sign(self, algorithm: A, message: &[u8]) -> Result, MessageSignatureError> { - ssi_jws::sign_bytes(algorithm.into(), message, &self) +impl MessageSigner for JWK +where + A::Instance: Into, +{ + async fn sign( + self, + algorithm: A::Instance, + message: &[u8], + ) -> Result, MessageSignatureError> { + ssi_jws::sign_bytes(algorithm.into().try_into()?, message, &self) .map_err(MessageSignatureError::signature_failed) } -} -pub trait MultiMessageSigner { - #[allow(async_fn_in_trait)] async fn sign_multi( self, - algorithm: A, + algorithm: ::Instance, messages: &[Vec], - ) -> Result, MessageSignatureError>; + ) -> Result, MessageSignatureError> { + match algorithm.into() { + #[cfg(feature = "bbs")] + ssi_crypto::AlgorithmInstance::Bbs(bbs) => { + let sk: ssi_bbs::BBSplusSecretKey = self + .try_into() + .map_err(|_| MessageSignatureError::InvalidSecretKey)?; + let pk = sk.public_key(); + ssi_bbs::sign(*bbs.0, &sk, &pk, messages) + } + other => Err(MessageSignatureError::UnsupportedAlgorithm( + other.algorithm().to_string(), + )), + } + } } pub struct MessageSignerAdapter { @@ -130,15 +106,32 @@ impl MessageSignerAdapter { } } -impl, A, B> MessageSigner for MessageSignerAdapter +impl, A: SignatureAlgorithmType, B: SignatureAlgorithmType> MessageSigner + for MessageSignerAdapter where - A: TryFrom, + A::Instance: TryFrom, { - async fn sign(self, algorithm: B, message: &[u8]) -> Result, MessageSignatureError> { + async fn sign( + self, + algorithm: B::Instance, + message: &[u8], + ) -> Result, MessageSignatureError> { let algorithm = algorithm .try_into() .map_err(|_| MessageSignatureError::InvalidQuery)?; self.signer.sign(algorithm, message).await } + + async fn sign_multi( + self, + algorithm: ::Instance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + let algorithm = algorithm + .try_into() + .map_err(|_| MessageSignatureError::InvalidQuery)?; + + self.signer.sign_multi(algorithm, messages).await + } } diff --git a/crates/verification-methods/src/methods.rs b/crates/verification-methods/src/methods.rs index 0799ced6f..80fc7e7fa 100644 --- a/crates/verification-methods/src/methods.rs +++ b/crates/verification-methods/src/methods.rs @@ -1,10 +1,10 @@ mod w3c; use std::borrow::Cow; +use ssi_claims_core::MessageSignatureError; use ssi_jwk::JWK; use ssi_verification_methods_core::{ - GenericVerificationMethod, JwkVerificationMethod, MaybeJwkVerificationMethod, - MessageSignatureError, SigningMethod, + GenericVerificationMethod, JwkVerificationMethod, MaybeJwkVerificationMethod, SigningMethod, }; pub use w3c::*; @@ -116,11 +116,11 @@ impl MaybeJwkVerificationMethod for AnyMethod { } } -impl SigningMethod for AnyMethod { +impl SigningMethod for AnyMethod { fn sign_bytes( &self, secret: &JWK, - algorithm: ssi_jwk::Algorithm, + algorithm: ssi_crypto::AlgorithmInstance, bytes: &[u8], ) -> Result, MessageSignatureError> { match self { @@ -132,42 +132,52 @@ impl SigningMethod for AnyMethod { } #[cfg(feature = "ed25519")] Self::Ed25519VerificationKey2020(m) => match algorithm { - ssi_jwk::Algorithm::EdDSA => m.sign_bytes(secret, bytes), + ssi_crypto::AlgorithmInstance::EdDSA => m.sign_bytes(secret, bytes), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, #[cfg(feature = "secp256k1")] Self::EcdsaSecp256k1VerificationKey2019(m) => match algorithm { - ssi_jwk::Algorithm::ES256K => m.sign_bytes( + ssi_crypto::AlgorithmInstance::ES256K => m.sign_bytes( secret, ecdsa_secp_256k1_verification_key_2019::DigestFunction::Sha256, bytes, ), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, #[cfg(feature = "secp256k1")] Self::EcdsaSecp256k1RecoveryMethod2020(m) => match algorithm { - ssi_jwk::Algorithm::ES256KR => { - m.sign_bytes(secret, ssi_jwk::algorithm::ES256KR, bytes) + ssi_crypto::AlgorithmInstance::ES256KR => { + SigningMethod::<_, ssi_crypto::algorithm::ES256KR>::sign_bytes( + m, + secret, + ssi_crypto::algorithm::ES256KR, + bytes, + ) } - ssi_jwk::Algorithm::ESKeccakKR => { - m.sign_bytes(secret, ssi_jwk::algorithm::ESKeccakKR, bytes) + ssi_crypto::AlgorithmInstance::ESKeccakKR => { + SigningMethod::<_, ssi_crypto::algorithm::ESKeccakKR>::sign_bytes( + m, + secret, + ssi_crypto::algorithm::ESKeccakKR, + bytes, + ) } _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, #[cfg(feature = "secp256r1")] Self::EcdsaSecp256r1VerificationKey2019(m) => match algorithm { - ssi_jwk::Algorithm::ES256 => m.sign_bytes(secret, bytes), + ssi_crypto::AlgorithmInstance::ES256 => m.sign_bytes(secret, bytes), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, - Self::JsonWebKey2020(m) => m.sign_bytes(secret, Some(algorithm), bytes), + Self::JsonWebKey2020(m) => m.sign_bytes(secret, Some(algorithm.try_into()?), bytes), #[cfg(feature = "ed25519")] Self::Multikey(m) => m.sign_bytes(secret, algorithm, bytes), #[cfg(all(feature = "tezos", feature = "ed25519"))] @@ -184,20 +194,36 @@ impl SigningMethod for AnyMethod { Self::AleoMethod2021(m) => { m.sign_bytes(secret, bytes) // FIXME: check key algorithm? } - Self::BlockchainVerificationMethod2021(m) => m.sign_bytes(secret, algorithm, bytes), + Self::BlockchainVerificationMethod2021(m) => { + m.sign_bytes(secret, algorithm.try_into()?, bytes) + } #[cfg(all(feature = "eip712", feature = "secp256k1"))] Self::Eip712Method2021(m) => { SigningMethod::sign_bytes(m, secret, algorithm.try_into()?, bytes) } #[cfg(feature = "solana")] Self::SolanaMethod2021(m) => { - m.sign_bytes(secret, Some(algorithm), bytes) // FIXME: check algorithm? + m.sign_bytes(secret, Some(algorithm.try_into()?), bytes) // FIXME: check algorithm? } m => Err(MessageSignatureError::UnsupportedVerificationMethod( m.type_().name().to_owned(), )), } } + + fn sign_bytes_multi( + &self, + secret: &JWK, + algorithm: ssi_crypto::AlgorithmInstance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + match self { + Self::Multikey(m) => m.sign_bytes_multi(secret, algorithm, messages), + m => Err(MessageSignatureError::UnsupportedVerificationMethod( + m.type_().name().to_owned(), + )), + } + } } ssi_verification_methods_core::verification_method_union! { @@ -257,11 +283,11 @@ impl JwkVerificationMethod for AnyJwkMethod { } } -impl SigningMethod for AnyJwkMethod { +impl SigningMethod for AnyJwkMethod { fn sign_bytes( &self, secret: &JWK, - algorithm: ssi_jwk::Algorithm, + algorithm: ssi_crypto::AlgorithmInstance, bytes: &[u8], ) -> Result, MessageSignatureError> { match self { @@ -273,33 +299,33 @@ impl SigningMethod for AnyJwkMethod { } #[cfg(feature = "ed25519")] Self::Ed25519VerificationKey2020(m) => match algorithm { - ssi_jwk::Algorithm::EdDSA => m.sign_bytes(secret, bytes), + ssi_crypto::AlgorithmInstance::EdDSA => m.sign_bytes(secret, bytes), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, #[cfg(feature = "secp256k1")] Self::EcdsaSecp256k1VerificationKey2019(m) => match algorithm { - ssi_jwk::Algorithm::ES256K => m.sign_bytes( + ssi_crypto::AlgorithmInstance::ES256K => m.sign_bytes( secret, ecdsa_secp_256k1_verification_key_2019::DigestFunction::Sha256, bytes, ), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, #[cfg(feature = "secp256r1")] Self::EcdsaSecp256r1VerificationKey2019(m) => match algorithm { - ssi_jwk::Algorithm::ES256 => m.sign_bytes(secret, bytes), + ssi_crypto::AlgorithmInstance::ES256 => m.sign_bytes(secret, bytes), _ => Err(MessageSignatureError::UnsupportedAlgorithm( - algorithm.to_string(), + algorithm.algorithm().to_string(), )), }, - Self::JsonWebKey2020(m) => m.sign_bytes(secret, Some(algorithm), bytes), + Self::JsonWebKey2020(m) => m.sign_bytes(secret, Some(algorithm.try_into()?), bytes), #[cfg(feature = "solana")] Self::SolanaMethod2021(m) => { - m.sign_bytes(secret, Some(algorithm), bytes) // FIXME: check algorithm? + m.sign_bytes(secret, Some(algorithm.try_into()?), bytes) // FIXME: check algorithm? } } } diff --git a/crates/verification-methods/src/methods/unspecified/aleo_method_2021.rs b/crates/verification-methods/src/methods/unspecified/aleo_method_2021.rs index 3e0a41da2..fd2767912 100644 --- a/crates/verification-methods/src/methods/unspecified/aleo_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/aleo_method_2021.rs @@ -1,13 +1,14 @@ use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; use ssi_caips::caip10::AleoBlockchainAccountId; +use ssi_claims_core::MessageSignatureError; use ssi_jwk::JWK; use static_iref::iri; use std::hash::Hash; use crate::{ - ExpectedType, GenericVerificationMethod, InvalidVerificationMethod, MessageSignatureError, - TypedVerificationMethod, VerificationMethod, VerificationMethodSet, + ExpectedType, GenericVerificationMethod, InvalidVerificationMethod, TypedVerificationMethod, + VerificationMethod, VerificationMethodSet, }; // pub const ALEO_METHOD_2021_IRI: &Iri = iri!("https://w3id.org/security#AleoMethod2021"); diff --git a/crates/verification-methods/src/methods/unspecified/blockchain_verification_method_2021.rs b/crates/verification-methods/src/methods/unspecified/blockchain_verification_method_2021.rs index 7a0fca252..b15072da0 100644 --- a/crates/verification-methods/src/methods/unspecified/blockchain_verification_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/blockchain_verification_method_2021.rs @@ -2,9 +2,9 @@ use std::hash::Hash; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::ProofValidationError; +use ssi_claims_core::{MessageSignatureError, ProofValidationError}; use ssi_jwk::JWK; -use ssi_verification_methods_core::{MessageSignatureError, VerificationMethodSet}; +use ssi_verification_methods_core::VerificationMethodSet; use static_iref::iri; use crate::{ diff --git a/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs b/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs index fe8477d8a..6b9358677 100644 --- a/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/eip712_method_2021.rs @@ -3,9 +3,9 @@ use std::hash::Hash; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; use ssi_caips::caip10::BlockchainAccountIdVerifyError; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::JWK; -use ssi_verification_methods_core::{MessageSignatureError, VerificationMethodSet}; +use ssi_verification_methods_core::VerificationMethodSet; use static_iref::iri; use crate::{ @@ -185,11 +185,11 @@ impl TryFrom for Eip712Method2021 { } } -impl SigningMethod for Eip712Method2021 { +impl SigningMethod for Eip712Method2021 { fn sign_bytes( &self, key: &JWK, - _algorithm: ssi_jwk::algorithm::ESKeccakKR, + _algorithm: ssi_crypto::algorithm::ESKeccakKR, bytes: &[u8], ) -> Result, MessageSignatureError> { self.sign_bytes(key, bytes) diff --git a/crates/verification-methods/src/methods/unspecified/solana_method_2021.rs b/crates/verification-methods/src/methods/unspecified/solana_method_2021.rs index fac03965c..d296093f5 100644 --- a/crates/verification-methods/src/methods/unspecified/solana_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/solana_method_2021.rs @@ -2,9 +2,9 @@ use std::{borrow::Cow, hash::Hash}; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::ProofValidationError; +use ssi_claims_core::{MessageSignatureError, ProofValidationError}; use ssi_jwk::JWK; -use ssi_verification_methods_core::{JwkVerificationMethod, MessageSignatureError}; +use ssi_verification_methods_core::JwkVerificationMethod; use static_iref::iri; use crate::{ diff --git a/crates/verification-methods/src/methods/unspecified/tezos/ed25519_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs b/crates/verification-methods/src/methods/unspecified/tezos/ed25519_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs index 9ee616c83..e39b4b6ae 100644 --- a/crates/verification-methods/src/methods/unspecified/tezos/ed25519_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/tezos/ed25519_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs @@ -2,11 +2,9 @@ use std::hash::Hash; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::{Algorithm, JWK}; -use ssi_verification_methods_core::{ - MessageSignatureError, VerificationMethodSet, VerifyBytesWithRecoveryJwk, -}; +use ssi_verification_methods_core::{VerificationMethodSet, VerifyBytesWithRecoveryJwk}; use static_iref::iri; use crate::{ @@ -158,13 +156,13 @@ impl TryFrom } } -impl SigningMethod +impl SigningMethod for Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 { fn sign_bytes( &self, key: &JWK, - _algorithm: ssi_jwk::algorithm::EdBlake2b, + _algorithm: ssi_crypto::algorithm::EdBlake2b, bytes: &[u8], ) -> Result, MessageSignatureError> { ssi_jws::sign_bytes(Algorithm::EdBlake2b, bytes, key) diff --git a/crates/verification-methods/src/methods/unspecified/tezos/p256_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs b/crates/verification-methods/src/methods/unspecified/tezos/p256_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs index 336c7d23c..1789abdef 100644 --- a/crates/verification-methods/src/methods/unspecified/tezos/p256_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/tezos/p256_public_key_blake2b_digest_size20_base58_check_encoded_2021.rs @@ -2,11 +2,9 @@ use std::hash::Hash; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::{Algorithm, JWK}; -use ssi_verification_methods_core::{ - MessageSignatureError, VerificationMethodSet, VerifyBytesWithRecoveryJwk, -}; +use ssi_verification_methods_core::{VerificationMethodSet, VerifyBytesWithRecoveryJwk}; use static_iref::iri; use crate::{ @@ -102,13 +100,13 @@ impl TypedVerificationMethod for P256PublicKeyBLAKE2BDigestSize20Base58CheckEnco } } -impl VerifyBytesWithRecoveryJwk +impl VerifyBytesWithRecoveryJwk for P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 { fn verify_bytes_with_public_jwk( &self, public_jwk: &JWK, - _: ssi_jwk::algorithm::ESBlake2b, + _: ssi_crypto::algorithm::ESBlake2b, signing_bytes: &[u8], signature: &[u8], ) -> Result { @@ -160,13 +158,13 @@ impl TryFrom for P256PublicKeyBLAKE2BDigestSize20Base } } -impl SigningMethod +impl SigningMethod for P256PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021 { fn sign_bytes( &self, key: &JWK, - _algorithm: ssi_jwk::algorithm::ESBlake2b, + _algorithm: ssi_crypto::algorithm::ESBlake2b, bytes: &[u8], ) -> Result, MessageSignatureError> { ssi_jws::sign_bytes(Algorithm::ESBlake2b, bytes, key) diff --git a/crates/verification-methods/src/methods/unspecified/tezos/tezos_method_2021.rs b/crates/verification-methods/src/methods/unspecified/tezos/tezos_method_2021.rs index 8c4bbb11e..f2d0cb33e 100644 --- a/crates/verification-methods/src/methods/unspecified/tezos/tezos_method_2021.rs +++ b/crates/verification-methods/src/methods/unspecified/tezos/tezos_method_2021.rs @@ -1,8 +1,9 @@ use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; -use ssi_jwk::{algorithm::AnyBlake2b, JWK}; -use ssi_verification_methods_core::{MessageSignatureError, VerificationMethodSet}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; +use ssi_crypto::algorithm::AnyBlake2b; +use ssi_jwk::JWK; +use ssi_verification_methods_core::VerificationMethodSet; use static_iref::iri; use std::{collections::BTreeMap, hash::Hash}; @@ -176,7 +177,7 @@ impl PublicKey { pub fn sign_bytes( &self, key: &JWK, - algorithm: ssi_jwk::algorithm::AnyBlake2b, + algorithm: ssi_crypto::algorithm::AnyBlake2b, bytes: &[u8], ) -> Result, MessageSignatureError> { ssi_jws::sign_bytes(algorithm.into(), bytes, key) @@ -235,11 +236,11 @@ impl TryFrom for TezosMethod2021 { } } -impl SigningMethod for TezosMethod2021 { +impl SigningMethod for TezosMethod2021 { fn sign_bytes( &self, key: &JWK, - algorithm: ssi_jwk::algorithm::AnyBlake2b, + algorithm: ssi_crypto::algorithm::AnyBlake2b, bytes: &[u8], ) -> Result, MessageSignatureError> { ssi_jws::sign_bytes(algorithm.into(), bytes, key) diff --git a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs index 8bd9021ac..880423b26 100644 --- a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs +++ b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_recovery_method_2020.rs @@ -2,9 +2,10 @@ use hex::FromHexError; use iref::{Iri, IriBuf, UriBuf}; use rdf_types::{Interpretation, Vocabulary}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; -use ssi_jwk::{algorithm::ES256KR, JWK}; -use ssi_verification_methods_core::{MessageSignatureError, VerificationMethodSet, VerifyBytes}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; +use ssi_crypto::algorithm::ES256KR; +use ssi_jwk::JWK; +use ssi_verification_methods_core::{VerificationMethodSet, VerifyBytes}; use static_iref::iri; use std::{borrow::Cow, hash::Hash, str::FromStr}; @@ -416,11 +417,11 @@ impl TryFrom for EcdsaSecp256k1RecoveryMethod2020 { } } -impl SigningMethod for EcdsaSecp256k1RecoveryMethod2020 { +impl SigningMethod for EcdsaSecp256k1RecoveryMethod2020 { fn sign_bytes( &self, secret: &JWK, - _algorithm: ssi_jwk::algorithm::ES256KR, + _algorithm: ssi_crypto::algorithm::ES256KR, bytes: &[u8], ) -> Result, MessageSignatureError> { self.sign(secret, bytes, DigestFunction::Sha256) @@ -428,11 +429,11 @@ impl SigningMethod for EcdsaSecp256k1RecoveryM } } -impl SigningMethod for EcdsaSecp256k1RecoveryMethod2020 { +impl SigningMethod for EcdsaSecp256k1RecoveryMethod2020 { fn sign_bytes( &self, secret: &JWK, - _algorithm: ssi_jwk::algorithm::ESKeccakKR, + _algorithm: ssi_crypto::algorithm::ESKeccakKR, bytes: &[u8], ) -> Result, MessageSignatureError> { self.sign(secret, bytes, DigestFunction::Keccack) diff --git a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs index 9976f4a60..51aa43dbd 100644 --- a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs +++ b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256k1_verification_key_2019.rs @@ -4,11 +4,9 @@ use hex::FromHexError; use iref::{Iri, IriBuf, UriBuf}; use rdf_types::{Interpretation, Vocabulary}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::JWK; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, VerifyBytes, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet, VerifyBytes}; use static_iref::iri; use crate::{ @@ -159,10 +157,10 @@ impl JwkVerificationMethod for EcdsaSecp256k1VerificationKey2019 { } } -impl VerifyBytes for EcdsaSecp256k1VerificationKey2019 { +impl VerifyBytes for EcdsaSecp256k1VerificationKey2019 { fn verify_bytes( &self, - _: ssi_jwk::algorithm::ES256K, + _: ssi_crypto::algorithm::ES256K, signing_bytes: &[u8], signature: &[u8], ) -> Result { diff --git a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256r1_verification_key_2019.rs b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256r1_verification_key_2019.rs index c47d7bc7b..b5b0ecfa7 100644 --- a/crates/verification-methods/src/methods/w3c/ecdsa_secp_256r1_verification_key_2019.rs +++ b/crates/verification-methods/src/methods/w3c/ecdsa_secp_256r1_verification_key_2019.rs @@ -1,13 +1,11 @@ use iref::{Iri, IriBuf, UriBuf}; use rdf_types::{Interpretation, Vocabulary}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::JWK; use ssi_multicodec::MultiEncodedBuf; use ssi_security::{Multibase, MultibaseBuf}; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, VerifyBytes, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet, VerifyBytes}; use static_iref::iri; use std::{borrow::Cow, hash::Hash, str::FromStr}; @@ -180,10 +178,10 @@ impl JwkVerificationMethod for EcdsaSecp256r1VerificationKey2019 { } } -impl VerifyBytes for EcdsaSecp256r1VerificationKey2019 { +impl VerifyBytes for EcdsaSecp256r1VerificationKey2019 { fn verify_bytes( &self, - _: ssi_jwk::algorithm::ES256, + _: ssi_crypto::algorithm::ES256, signing_bytes: &[u8], signature: &[u8], ) -> Result { diff --git a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2018.rs b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2018.rs index eb46f401e..7b69fbc4e 100644 --- a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2018.rs +++ b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2018.rs @@ -4,12 +4,12 @@ use ed25519_dalek::{Signer, Verifier}; use iref::{Iri, IriBuf, UriBuf}; use rdf_types::{Interpretation, Vocabulary}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity, SignatureError}; +use ssi_claims_core::{ + InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, +}; use ssi_jwk::JWK; use ssi_jws::CompactJWSString; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, VerifyBytes, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet, VerifyBytes}; use static_iref::iri; use crate::{ @@ -146,11 +146,11 @@ impl TryFrom for Ed25519VerificationKey2018 { } } -impl SigningMethod for Ed25519VerificationKey2018 { +impl SigningMethod for Ed25519VerificationKey2018 { fn sign_bytes( &self, secret: &JWK, - _algorithm: ssi_jwk::algorithm::EdDSA, + _algorithm: ssi_crypto::algorithm::EdDSA, bytes: &[u8], ) -> Result, MessageSignatureError> { ssi_jws::sign_bytes(ssi_jwk::Algorithm::EdDSA, bytes, secret) @@ -158,10 +158,10 @@ impl SigningMethod for Ed25519VerificationKey201 } } -impl VerifyBytes for Ed25519VerificationKey2018 { +impl VerifyBytes for Ed25519VerificationKey2018 { fn verify_bytes( &self, - _: ssi_jwk::algorithm::EdDSA, + _: ssi_crypto::algorithm::EdDSA, signing_bytes: &[u8], signature: &[u8], ) -> Result { diff --git a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs index ee7d55897..df369dd09 100644 --- a/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs +++ b/crates/verification-methods/src/methods/w3c/ed25519_verification_key_2020.rs @@ -5,13 +5,11 @@ use iref::{Iri, IriBuf, UriBuf}; use rand_core::{CryptoRng, RngCore}; use rdf_types::{Interpretation, Vocabulary}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::JWK; use ssi_multicodec::MultiEncodedBuf; use ssi_security::{Multibase, MultibaseBuf}; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, VerifyBytes, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet, VerifyBytes}; use static_iref::iri; use crate::{ @@ -189,34 +187,34 @@ impl JwkVerificationMethod for Ed25519VerificationKey2020 { } } -impl SigningMethod +impl SigningMethod for Ed25519VerificationKey2020 { fn sign_bytes( &self, secret: &ed25519_dalek::SigningKey, - _algorithm: ssi_jwk::algorithm::EdDSA, + _algorithm: ssi_crypto::algorithm::EdDSA, message: &[u8], ) -> Result, MessageSignatureError> { self.sign_bytes(secret, message) } } -impl SigningMethod for Ed25519VerificationKey2020 { +impl SigningMethod for Ed25519VerificationKey2020 { fn sign_bytes( &self, secret_key: &JWK, - _algorithm: ssi_jwk::algorithm::EdDSA, + _algorithm: ssi_crypto::algorithm::EdDSA, message: &[u8], ) -> Result, MessageSignatureError> { self.sign_bytes(secret_key, message) } } -impl VerifyBytes for Ed25519VerificationKey2020 { +impl VerifyBytes for Ed25519VerificationKey2020 { fn verify_bytes( &self, - _: ssi_jwk::algorithm::EdDSA, + _: ssi_crypto::algorithm::EdDSA, signing_bytes: &[u8], signature: &[u8], ) -> Result { diff --git a/crates/verification-methods/src/methods/w3c/json_web_key_2020.rs b/crates/verification-methods/src/methods/w3c/json_web_key_2020.rs index 2afc2609d..c8b008190 100644 --- a/crates/verification-methods/src/methods/w3c/json_web_key_2020.rs +++ b/crates/verification-methods/src/methods/w3c/json_web_key_2020.rs @@ -2,11 +2,9 @@ use std::{borrow::Cow, hash::Hash}; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::{Algorithm, JWK}; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, VerifyBytes, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet, VerifyBytes}; use static_iref::iri; use crate::{ diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 88e2a1e95..3794f7415 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -7,13 +7,15 @@ use rdf_types::{ Interpretation, Vocabulary, }; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity, SignatureError}; +use ssi_claims_core::{ + InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, +}; +use ssi_crypto::algorithm::{Bbs, BbsInstance}; use ssi_jwk::JWK; use ssi_multicodec::{Codec, MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ - MaybeJwkVerificationMethod, MessageSignatureError, SigningMethod, VerificationMethodSet, - VerifyBytes, + MaybeJwkVerificationMethod, SigningMethod, VerificationMethodSet, VerifyBytes, }; use static_iref::iri; use std::{borrow::Cow, hash::Hash, str::FromStr, sync::OnceLock}; @@ -173,24 +175,44 @@ impl> VerifyBytes for Multikey { } } -impl SigningMethod for Multikey { +impl SigningMethod for Multikey { fn sign_bytes( &self, secret: &JWK, - algorithm: ssi_jwk::Algorithm, + algorithm: ssi_crypto::AlgorithmInstance, bytes: &[u8], ) -> Result, MessageSignatureError> { - ssi_jws::sign_bytes(algorithm, bytes, secret) + ssi_jws::sign_bytes(algorithm.try_into()?, bytes, secret) .map_err(MessageSignatureError::signature_failed) } + + fn sign_bytes_multi( + &self, + secret: &JWK, + algorithm: ssi_crypto::AlgorithmInstance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + match algorithm { + #[cfg(feature = "bls12-381")] + ssi_crypto::AlgorithmInstance::Bbs(bbs_algorithm) => { + let secret: ssi_bbs::BBSplusSecretKey = secret + .try_into() + .map_err(|_| MessageSignatureError::InvalidSecretKey)?; + self.sign_bytes_multi(&secret, bbs_algorithm, messages) + } + other => Err(MessageSignatureError::UnsupportedAlgorithm( + other.algorithm().to_string(), + )), + } + } } #[cfg(feature = "ed25519")] -impl SigningMethod for Multikey { +impl SigningMethod for Multikey { fn sign_bytes( &self, secret: &ed25519_dalek::SigningKey, - _algorithm: ssi_jwk::algorithm::EdDSA, + _algorithm: ssi_crypto::algorithm::EdDSA, bytes: &[u8], ) -> Result, MessageSignatureError> { use ed25519_dalek::Signer; @@ -199,6 +221,31 @@ impl SigningMethod for Mul } } +#[cfg(feature = "bls12-381")] +impl SigningMethod for Multikey { + fn sign_bytes( + &self, + secret: &ssi_bbs::BBSplusSecretKey, + algorithm: BbsInstance, + bytes: &[u8], + ) -> Result, MessageSignatureError> { + self.sign_bytes_multi(secret, algorithm, &[bytes.to_vec()]) + } + + fn sign_bytes_multi( + &self, + secret: &ssi_bbs::BBSplusSecretKey, + algorithm: BbsInstance, + messages: &[Vec], + ) -> Result, MessageSignatureError> { + let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? else { + return Err(MessageSignatureError::InvalidPublicKey); + }; + + ssi_bbs::sign(*algorithm.0, secret, pk, messages) + } +} + impl TypedVerificationMethod for Multikey { fn expected_type() -> Option { Some(MULTIKEY_TYPE.to_string().into()) @@ -398,7 +445,7 @@ pub enum DecodedMultikey { P384(p384::PublicKey), #[cfg(feature = "bls12-381")] - Bls12_381(zkryptium::bbsplus::keys::BBSplusPublicKey), + Bls12_381(ssi_bbs::BBSplusPublicKey), } impl DecodedMultikey { diff --git a/crates/verification-methods/src/methods/w3c/rsa_verification_key_2018.rs b/crates/verification-methods/src/methods/w3c/rsa_verification_key_2018.rs index ea0437da0..656e8449c 100644 --- a/crates/verification-methods/src/methods/w3c/rsa_verification_key_2018.rs +++ b/crates/verification-methods/src/methods/w3c/rsa_verification_key_2018.rs @@ -2,11 +2,9 @@ use std::{borrow::Cow, hash::Hash}; use iref::{Iri, IriBuf, UriBuf}; use serde::{Deserialize, Serialize}; -use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_jwk::JWK; -use ssi_verification_methods_core::{ - JwkVerificationMethod, MessageSignatureError, VerificationMethodSet, -}; +use ssi_verification_methods_core::{JwkVerificationMethod, VerificationMethodSet}; use static_iref::iri; use crate::{ diff --git a/src/lib.rs b/src/lib.rs index 5e5ef3131..fd24d0085 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,3 +333,6 @@ pub use ssi_multicodec as multicodec; /// Secure Shell utilities. #[doc(inline)] pub use ssi_ssh as ssh; + +#[cfg(feature = "bbs")] +pub use ssi_bbs as bbs; diff --git a/tests/vcdm_v2_sign.rs b/tests/vcdm_v2_sign.rs index 5b8c2666a..2e03f5055 100644 --- a/tests/vcdm_v2_sign.rs +++ b/tests/vcdm_v2_sign.rs @@ -5,7 +5,7 @@ use ssi_claims::{ vc::v2::JsonCredential, VerificationParameters, }; -use ssi_dids::{AnyDidMethod, VerificationMethodDIDResolver}; +use ssi_dids::{AnyDidMethod, DIDKey, VerificationMethodDIDResolver}; use ssi_verification_methods::{AnyMethod, SingleSecretSigner}; use static_iref::iri; @@ -134,3 +134,62 @@ async fn ecdsa_rdfc_2019_p384() { .unwrap() .unwrap(); } + +#[cfg(all(feature = "w3c", feature = "bbs"))] +#[async_std::test] +async fn bbs_2023() { + use json_syntax::Value; + + let jwk = JWK::generate_bls12381g2(); + let did_url = DIDKey::generate_url(&jwk).unwrap(); + + let resolver = VerificationMethodDIDResolver::<_, AnyMethod>::new(AnyDidMethod::default()); + let vc: JsonCredential = serde_json::from_value(json!({ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "foo": "http://example.org/#foo", + "bar": "http://example.org/#bar" + } + ], + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:key:z6MkhTNL7i2etLerDK8Acz5t528giE5KA4p75T6ka1E1D74r", + "foo": "value1", + "bar": "value2" + }, + "id": "urn:uuid:7a6cafb9-11c3-41a8-98d8-8b5a45c2548f", + "issuer": did_url.to_string() + })) + .unwrap(); + + let base_vc = AnySuite::Bbs2023 + .sign( + vc, + &resolver, + SingleSecretSigner::new(jwk).into_local(), + ProofOptions::from_method(did_url.into_iri().into()), + ) + .await + .unwrap(); + + let params = VerificationParameters::from_resolver(&resolver); + let mut selection = ssi::claims::data_integrity::AnySelectionOptions::default(); + selection.selective_pointers = vec![ + "/id".parse().unwrap(), + "/type".parse().unwrap(), + "/credentialSubject/foo".parse().unwrap(), + "/issuer".parse().unwrap(), + ]; + let derived = base_vc + .select(¶ms, selection) + .await + .unwrap() + .map(|object| { + ssi::json_ld::syntax::from_value::(Value::Object(object)).unwrap() + }); + + derived.verify(params).await.unwrap().unwrap(); +} From e8dfe7dd5c4c4921e0581597d97ee8f15479e385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 17:32:41 +0200 Subject: [PATCH 25/34] Fix feature gates. --- crates/bbs/src/lib.rs | 7 +++++-- .../crates/data-integrity/src/any/sd.rs | 3 ++- crates/dids/methods/key/src/lib.rs | 19 +++++++++++-------- crates/jwk/src/bbs.rs | 4 ++++ crates/jwk/src/lib.rs | 2 ++ crates/jwk/src/multicodec.rs | 8 ++++++++ .../core/src/signature/signer/mod.rs | 1 + .../src/methods/w3c/multikey.rs | 8 ++++---- 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/crates/bbs/src/lib.rs b/crates/bbs/src/lib.rs index 36af61b3f..16969ea2d 100644 --- a/crates/bbs/src/lib.rs +++ b/crates/bbs/src/lib.rs @@ -1,6 +1,9 @@ use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity}; use ssi_crypto::algorithm::BbsParameters; -pub use zkryptium::bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}; +pub use zkryptium::{ + bbsplus::keys::{BBSplusPublicKey, BBSplusSecretKey}, + errors::Error, +}; use zkryptium::{ bbsplus::{ ciphersuites::{BbsCiphersuite, Bls12381Sha256}, @@ -102,7 +105,7 @@ pub fn sign( } pub fn generate_secret_key(rng: &mut impl rand::RngCore) -> BBSplusSecretKey { - let mut key_material = [0; Bls12381Sha256::IKM_LEN as usize]; + let mut key_material = [0; Bls12381Sha256::IKM_LEN]; rng.fill_bytes(&mut key_material); let pair = KeyPair::>::generate(&key_material, None, None).unwrap(); pair.into_parts().0 diff --git a/crates/claims/crates/data-integrity/src/any/sd.rs b/crates/claims/crates/data-integrity/src/any/sd.rs index 938726c77..8e9c44f3c 100644 --- a/crates/claims/crates/data-integrity/src/any/sd.rs +++ b/crates/claims/crates/data-integrity/src/any/sd.rs @@ -40,6 +40,7 @@ where P: JsonLdLoaderProvider + ResolverProvider, P::Resolver: VerificationMethodResolver, { + #[allow(unused_variables)] async fn select( &self, unsecured_document: &T, @@ -83,7 +84,7 @@ where domains: p.domains, challenge: p.challenge, nonce: p.nonce, - options: crate::AnyProofOptions::Bbs2023(p.options), + options: crate::AnyProofOptions::Bbs2023(()), signature: crate::AnySignature::Bbs2023(p.signature), extra_properties: p.extra_properties, }) diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 7b08f9a9d..3838322f8 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -113,6 +113,7 @@ impl DIDMethodResolver for DIDKey { } #[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum VerificationMethodType { Multikey, Ed25519VerificationKey2020, @@ -121,6 +122,7 @@ pub enum VerificationMethodType { EcdsaSecp256k1VerificationKey2019, EcdsaSecp256r1VerificationKey2019, JsonWebKey2020, + #[cfg(feature = "bbs")] Bls12381G2Key2020, } @@ -134,6 +136,7 @@ impl VerificationMethodType { "EcdsaSecp256k1VerificationKey2019" => Some(Self::EcdsaSecp256k1VerificationKey2019), "EcdsaSecp256r1VerificationKey2019" => Some(Self::EcdsaSecp256r1VerificationKey2019), "JsonWebKey2020" => Some(Self::JsonWebKey2020), + #[cfg(feature = "bbs")] "Bls12381G2Key2020" => Some(Self::Bls12381G2Key2020), _ => None, } @@ -148,6 +151,7 @@ impl VerificationMethodType { Self::EcdsaSecp256k1VerificationKey2019 => "EcdsaSecp256k1VerificationKey2019", Self::EcdsaSecp256r1VerificationKey2019 => "EcdsaSecp256r1VerificationKey2019", Self::JsonWebKey2020 => "JsonWebKey2020", + #[cfg(feature = "bbs")] Self::Bls12381G2Key2020 => "Bls12381G2Key2020", } } @@ -195,13 +199,10 @@ impl VerificationMethodType { .map_err(Error::internal)?; Ok(PublicKey::Jwk(Box::new(key))) } + #[cfg(feature = "bbs")] Self::Bls12381G2Key2020 => match encoded.codec() { ssi_multicodec::BLS12_381_G2_PUB => { - let jwk = JWK::from(ssi_jwk::Params::OKP(ssi_jwk::OctetParams { - curve: "Bls12381G2".to_string(), - public_key: ssi_jwk::Base64urlUInt(encoded.data().to_vec()), - private_key: None, - })); + let jwk = ssi_jwk::bls12381g2_parse(encoded.data()).map_err(Error::internal)?; // https://datatracker.ietf.org/doc/html/draft-denhartog-pairing-curves-jose-cose-00#section-3.1.3 // FIXME: This should be a base 58 key according to the spec. Ok(PublicKey::Jwk(Box::new(jwk))) @@ -285,6 +286,7 @@ impl VerificationMethodType { Self::JsonWebKey2020 => Some(ContextEntry::IriRef( iri_ref!("https://w3id.org/security/suites/jws-2020/v1").to_owned(), )), + #[cfg(feature = "bbs")] Self::Bls12381G2Key2020 => { let mut definition = Definition::new(); definition.bindings.insert( @@ -512,9 +514,10 @@ mod tests { // implementations. // Related issue: https://github.com/mattrglobal/bls12381-jwk-draft/issues/5 let key_expected: JWK = serde_json::from_value(serde_json::json!({ - "kty": "OKP", - "crv": "Bls12381G2", - "x": "tKWJu0SOY7onl4tEyOOH11XBriQN2JgzV-UmjgBMSsNkcAx3_l97SVYViSDBouTVBkBfrLh33C5icDD-4UEDxNO3Wn1ijMHvn2N63DU4pkezA3kGN81jGbwbrsMPpiOF" + "kty": "EC", + "crv": "BLS12381G2", + "x": "FKWJu0SOY7onl4tEyOOH11XBriQN2JgzV-UmjgBMSsNkcAx3_l97SVYViSDBouTVBkBfrLh33C5icDD-4UEDxNO3Wn1ijMHvn2N63DU4pkezA3kGN81jGbwbrsMPpiOF", + "y": "DxwQn0pJ1DsBB8esxf3JvxFzS8BlyJVYvY_-HkYUxI-u6GdOHnMvNVSXKlEGjHw3DyTPeGOZ8KNbh62CaqWGE-4XAm23nzoD5dWg61Nvs5DGV4S4tLPmOXRYgHIPfRdq" })) .unwrap(); assert_eq!(key, key_expected); diff --git a/crates/jwk/src/bbs.rs b/crates/jwk/src/bbs.rs index 941f94c28..d077975ed 100644 --- a/crates/jwk/src/bbs.rs +++ b/crates/jwk/src/bbs.rs @@ -13,6 +13,10 @@ impl JWK { } } +pub fn bls12381g2_parse(bytes: &[u8]) -> Result { + Ok(BBSplusPublicKey::from_bytes(bytes)?.into()) +} + impl<'a> TryFrom<&'a JWK> for BBSplusPublicKey { type Error = Error; diff --git a/crates/jwk/src/lib.rs b/crates/jwk/src/lib.rs index 9884b6f37..aaa94b1dd 100644 --- a/crates/jwk/src/lib.rs +++ b/crates/jwk/src/lib.rs @@ -30,6 +30,8 @@ pub mod blakesig; #[cfg(feature = "bbs")] mod bbs; +#[cfg(feature = "bbs")] +pub use bbs::*; mod multicodec; pub use multicodec::*; diff --git a/crates/jwk/src/multicodec.rs b/crates/jwk/src/multicodec.rs index adcf11a84..dca561c71 100644 --- a/crates/jwk/src/multicodec.rs +++ b/crates/jwk/src/multicodec.rs @@ -43,6 +43,10 @@ impl JWK { ssi_multicodec::P384_PRIV => { crate::p384_parse_private(k).map_err(FromMulticodecError::Secp384r1Priv) } + #[cfg(feature = "bbs")] + ssi_multicodec::BLS12_381_G2_PUB => { + crate::bls12381g2_parse(k).map_err(FromMulticodecError::Bls12381G2Pub) + } _ => Err(FromMulticodecError::UnsupportedCodec(codec)), } } @@ -157,6 +161,10 @@ pub enum FromMulticodecError { #[error(transparent)] Secp384r1Priv(Error), + #[cfg(feature = "bbs")] + #[error(transparent)] + Bls12381G2Pub(ssi_bbs::Error), + /// Unexpected multibase (multicodec) key prefix multicodec #[error("Unsupported multicodec key type 0x{0:x}")] UnsupportedCodec(u64), diff --git a/crates/verification-methods/core/src/signature/signer/mod.rs b/crates/verification-methods/core/src/signature/signer/mod.rs index a7140892e..4b07a0bac 100644 --- a/crates/verification-methods/core/src/signature/signer/mod.rs +++ b/crates/verification-methods/core/src/signature/signer/mod.rs @@ -69,6 +69,7 @@ where .map_err(MessageSignatureError::signature_failed) } + #[allow(unused_variables)] async fn sign_multi( self, algorithm: ::Instance, diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 3794f7415..dfc4c13f8 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; use ssi_claims_core::{ InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, }; -use ssi_crypto::algorithm::{Bbs, BbsInstance}; use ssi_jwk::JWK; use ssi_multicodec::{Codec, MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; @@ -186,6 +185,7 @@ impl SigningMethod for Multikey { .map_err(MessageSignatureError::signature_failed) } + #[allow(unused_variables)] fn sign_bytes_multi( &self, secret: &JWK, @@ -222,11 +222,11 @@ impl SigningMethod for } #[cfg(feature = "bls12-381")] -impl SigningMethod for Multikey { +impl SigningMethod for Multikey { fn sign_bytes( &self, secret: &ssi_bbs::BBSplusSecretKey, - algorithm: BbsInstance, + algorithm: ssi_crypto::algorithm::BbsInstance, bytes: &[u8], ) -> Result, MessageSignatureError> { self.sign_bytes_multi(secret, algorithm, &[bytes.to_vec()]) @@ -235,7 +235,7 @@ impl SigningMethod for Multikey { fn sign_bytes_multi( &self, secret: &ssi_bbs::BBSplusSecretKey, - algorithm: BbsInstance, + algorithm: ssi_crypto::algorithm::BbsInstance, messages: &[Vec], ) -> Result, MessageSignatureError> { let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? else { From 73cbd4474a029dae258818060ba9e053a2765275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 17:54:50 +0200 Subject: [PATCH 26/34] Fix imports & feature gates. --- crates/jwk/Cargo.toml | 2 +- crates/jwk/src/multicodec.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/jwk/Cargo.toml b/crates/jwk/Cargo.toml index e62cb2bfb..c3db009d1 100644 --- a/crates/jwk/Cargo.toml +++ b/crates/jwk/Cargo.toml @@ -42,7 +42,7 @@ tezos = ["blake2b_simd", "secp256k1", "secp256r1", "bs58"] ring = ["dep:ring"] -bbs = ["ssi-bbs"] +bbs = ["ssi-bbs", "rand"] [dependencies] ssi-claims-core.workspace = true diff --git a/crates/jwk/src/multicodec.rs b/crates/jwk/src/multicodec.rs index dca561c71..de8532ca6 100644 --- a/crates/jwk/src/multicodec.rs +++ b/crates/jwk/src/multicodec.rs @@ -1,6 +1,6 @@ use ssi_multicodec::{MultiEncoded, MultiEncodedBuf}; -use crate::{Error, Params, RsaX509PubParseError, JWK}; +use crate::{Error, Params, JWK}; impl JWK { pub fn from_multicodec(multicodec: &MultiEncoded) -> Result { @@ -127,7 +127,7 @@ impl JWK { pub enum FromMulticodecError { #[cfg(feature = "rsa")] #[error(transparent)] - RsaPub(RsaX509PubParseError), + RsaPub(crate::RsaX509PubParseError), #[cfg(feature = "ed25519")] #[error(transparent)] From 74db7678a86d7586d0ca067d3ca22ffd20a0cfa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 18:14:04 +0200 Subject: [PATCH 27/34] Upgrade `json-ld` to version 0.21 --- crates/json-ld/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/json-ld/Cargo.toml b/crates/json-ld/Cargo.toml index 5835fb528..e9bf3fd67 100644 --- a/crates/json-ld/Cargo.toml +++ b/crates/json-ld/Cargo.toml @@ -11,8 +11,7 @@ documentation = "https://docs.rs/ssi-json-ld/" [dependencies] thiserror.workspace = true async-std = { version = "1.9", features = ["attributes"] } -# json-ld = { version = "0.20", features = ["serde"] } -json-ld = { git = "https://github.com/timothee-haudebourg/json-ld", branch = "ensure-to-rdf-is-send", features = ["serde"] } +json-ld = { version = "0.21", features = ["serde"] } iref.workspace = true static-iref.workspace = true rdf-types.workspace = true From 932bbf8ae1ba354e36750710a9459859358d2e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 18:14:15 +0200 Subject: [PATCH 28/34] Remove feature gates on `Multikey`. --- crates/verification-methods/src/methods.rs | 3 --- crates/verification-methods/src/methods/w3c/multikey.rs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/verification-methods/src/methods.rs b/crates/verification-methods/src/methods.rs index 80fc7e7fa..4eabc6c80 100644 --- a/crates/verification-methods/src/methods.rs +++ b/crates/verification-methods/src/methods.rs @@ -38,7 +38,6 @@ ssi_verification_methods_core::complete_verification_method_union! { JsonWebKey2020, /// `Multikey`. - #[cfg(feature = "ed25519")] Multikey, #[cfg(all(feature = "tezos", feature = "ed25519"))] @@ -82,7 +81,6 @@ impl AnyMethod { #[cfg(feature = "secp256r1")] Self::EcdsaSecp256r1VerificationKey2019(m) => Some(Cow::Owned(m.public_key_jwk())), Self::JsonWebKey2020(m) => Some(Cow::Borrowed(m.public_key_jwk())), - #[cfg(feature = "ed25519")] Self::Multikey(m) => m.public_key_jwk().map(Cow::Owned), #[cfg(all(feature = "tezos", feature = "ed25519"))] Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021(_) => None, @@ -178,7 +176,6 @@ impl SigningMethod for AnyMethod { )), }, Self::JsonWebKey2020(m) => m.sign_bytes(secret, Some(algorithm.try_into()?), bytes), - #[cfg(feature = "ed25519")] Self::Multikey(m) => m.sign_bytes(secret, algorithm, bytes), #[cfg(all(feature = "tezos", feature = "ed25519"))] Self::Ed25519PublicKeyBLAKE2BDigestSize20Base58CheckEncoded2021(m) => { diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index dfc4c13f8..211eafa3a 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -466,6 +466,7 @@ impl DecodedMultikey { } impl MultiCodec for DecodedMultikey { + #[allow(unused_variables)] fn from_codec_and_bytes(codec: u64, bytes: &[u8]) -> Result { match codec { #[cfg(feature = "ed25519")] From 38a1062506e65a18d0116912cd6198994ea9d0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 18:27:23 +0200 Subject: [PATCH 29/34] Enable `ssi-jwk/bbs` with `ssi-verification-methods-core/bbs`. --- crates/verification-methods/core/Cargo.toml | 2 +- tests/vcdm_v2_sign.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/verification-methods/core/Cargo.toml b/crates/verification-methods/core/Cargo.toml index 457c09039..af515bc2d 100644 --- a/crates/verification-methods/core/Cargo.toml +++ b/crates/verification-methods/core/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-verification-methods/" [features] -bbs = ["ssi-bbs"] +bbs = ["ssi-jwk/bbs", "ssi-bbs"] [dependencies] ssi-core.workspace = true diff --git a/tests/vcdm_v2_sign.rs b/tests/vcdm_v2_sign.rs index 2e03f5055..c4cc3cfa8 100644 --- a/tests/vcdm_v2_sign.rs +++ b/tests/vcdm_v2_sign.rs @@ -5,7 +5,7 @@ use ssi_claims::{ vc::v2::JsonCredential, VerificationParameters, }; -use ssi_dids::{AnyDidMethod, DIDKey, VerificationMethodDIDResolver}; +use ssi_dids::{AnyDidMethod, VerificationMethodDIDResolver}; use ssi_verification_methods::{AnyMethod, SingleSecretSigner}; use static_iref::iri; @@ -141,7 +141,7 @@ async fn bbs_2023() { use json_syntax::Value; let jwk = JWK::generate_bls12381g2(); - let did_url = DIDKey::generate_url(&jwk).unwrap(); + let did_url = ssi::dids::DIDKey::generate_url(&jwk).unwrap(); let resolver = VerificationMethodDIDResolver::<_, AnyMethod>::new(AnyDidMethod::default()); let vc: JsonCredential = serde_json::from_value(json!({ From 46a4bbf1d7d07f3ad5fe637b2b1937e74d93afe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 18:38:51 +0200 Subject: [PATCH 30/34] Rename `verification-methods/bls12-381` feature into `bbs`. --- crates/claims/crates/data-integrity/suites/Cargo.toml | 2 +- crates/verification-methods/Cargo.toml | 2 +- .../verification-methods/src/methods/w3c/multikey.rs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 419618684..163715f51 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -92,7 +92,7 @@ solana = ["ssi-verification-methods/solana", "k256"] ethereum = ["serde_json"] ## BBS cryptographic suites. -bbs = ["ssi-verification-methods/bls12-381"] +bbs = ["ssi-verification-methods/bbs"] [dependencies] ssi-data-integrity-core.workspace = true diff --git a/crates/verification-methods/Cargo.toml b/crates/verification-methods/Cargo.toml index d32a46da3..06a3e43b2 100644 --- a/crates/verification-methods/Cargo.toml +++ b/crates/verification-methods/Cargo.toml @@ -37,7 +37,7 @@ aleo = ["ssi-caips/aleo"] solana = [] -bls12-381 = ["ssi-multicodec/bls12-381", "ssi-jwk/bbs", "ssi-bbs", "ssi-verification-methods-core/bbs"] +bbs = ["ssi-multicodec/bls12-381", "ssi-jwk/bbs", "ssi-bbs", "ssi-verification-methods-core/bbs"] [dependencies] ssi-core.workspace = true diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 211eafa3a..01284cdb6 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -193,7 +193,7 @@ impl SigningMethod for Multikey { messages: &[Vec], ) -> Result, MessageSignatureError> { match algorithm { - #[cfg(feature = "bls12-381")] + #[cfg(feature = "bbs")] ssi_crypto::AlgorithmInstance::Bbs(bbs_algorithm) => { let secret: ssi_bbs::BBSplusSecretKey = secret .try_into() @@ -221,7 +221,7 @@ impl SigningMethod for } } -#[cfg(feature = "bls12-381")] +#[cfg(feature = "bbs")] impl SigningMethod for Multikey { fn sign_bytes( &self, @@ -444,7 +444,7 @@ pub enum DecodedMultikey { #[cfg(feature = "secp384r1")] P384(p384::PublicKey), - #[cfg(feature = "bls12-381")] + #[cfg(feature = "bbs")] Bls12_381(ssi_bbs::BBSplusPublicKey), } @@ -477,7 +477,7 @@ impl MultiCodec for DecodedMultikey { ssi_multicodec::P256_PUB => Codec::from_bytes(bytes).map(Self::P256), #[cfg(feature = "secp384r1")] ssi_multicodec::P384_PUB => Codec::from_bytes(bytes).map(Self::P384), - #[cfg(feature = "bls12-381")] + #[cfg(feature = "bbs")] ssi_multicodec::BLS12_381_G2_PUB => Codec::from_bytes(bytes).map(Self::Bls12_381), _ => Err(ssi_multicodec::Error::UnexpectedCodec(codec)), } @@ -493,7 +493,7 @@ impl MultiCodec for DecodedMultikey { Self::P256(k) => k.to_codec_and_bytes(), #[cfg(feature = "secp384r1")] Self::P384(k) => k.to_codec_and_bytes(), - #[cfg(feature = "bls12-381")] + #[cfg(feature = "bbs")] Self::Bls12_381(k) => k.to_codec_and_bytes(), #[allow(unreachable_patterns)] _ => unreachable!(), // references are always considered inhabited. From d6c39726e8df913ec3839ddef0c7ba819503922f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Tue, 9 Jul 2024 18:52:23 +0200 Subject: [PATCH 31/34] Fix more feature gates. --- .../data-integrity/suites/tests/suite.rs | 1 + crates/dids/methods/key/src/lib.rs | 1 + .../src/methods/w3c/multikey.rs | 22 +++++++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/claims/crates/data-integrity/suites/tests/suite.rs b/crates/claims/crates/data-integrity/suites/tests/suite.rs index f4878f665..3a95f7a77 100644 --- a/crates/claims/crates/data-integrity/suites/tests/suite.rs +++ b/crates/claims/crates/data-integrity/suites/tests/suite.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::{borrow::Cow, path::Path}; use hashbrown::HashMap; diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 3838322f8..70911e6a4 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -485,6 +485,7 @@ mod tests { assert_eq!(did1, did); } + #[cfg(feature = "bbs")] #[async_std::test] async fn from_did_key_bls() { // https://w3c-ccg.github.io/did-method-key/#bls-12381 diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 01284cdb6..b1a3558e9 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -11,7 +11,7 @@ use ssi_claims_core::{ InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, }; use ssi_jwk::JWK; -use ssi_multicodec::{Codec, MultiCodec, MultiEncodedBuf}; +use ssi_multicodec::{MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ MaybeJwkVerificationMethod, SigningMethod, VerificationMethodSet, VerifyBytes, @@ -238,7 +238,9 @@ impl SigningMethod for Mu algorithm: ssi_crypto::algorithm::BbsInstance, messages: &[Vec], ) -> Result, MessageSignatureError> { - let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? else { + #[allow(irrefutable_let_patterns)] + let DecodedMultikey::Bls12_381(pk) = self.public_key.decode()? + else { return Err(MessageSignatureError::InvalidPublicKey); }; @@ -470,15 +472,21 @@ impl MultiCodec for DecodedMultikey { fn from_codec_and_bytes(codec: u64, bytes: &[u8]) -> Result { match codec { #[cfg(feature = "ed25519")] - ssi_multicodec::ED25519_PUB => Codec::from_bytes(bytes).map(Self::Ed25519), + ssi_multicodec::ED25519_PUB => { + ssi_multicodec::Codec::from_bytes(bytes).map(Self::Ed25519) + } #[cfg(feature = "secp256k1")] - ssi_multicodec::SECP256K1_PUB => Codec::from_bytes(bytes).map(Self::Secp256k1), + ssi_multicodec::SECP256K1_PUB => { + ssi_multicodec::Codec::from_bytes(bytes).map(Self::Secp256k1) + } #[cfg(feature = "secp256r1")] - ssi_multicodec::P256_PUB => Codec::from_bytes(bytes).map(Self::P256), + ssi_multicodec::P256_PUB => ssi_multicodec::Codec::from_bytes(bytes).map(Self::P256), #[cfg(feature = "secp384r1")] - ssi_multicodec::P384_PUB => Codec::from_bytes(bytes).map(Self::P384), + ssi_multicodec::P384_PUB => ssi_multicodec::Codec::from_bytes(bytes).map(Self::P384), #[cfg(feature = "bbs")] - ssi_multicodec::BLS12_381_G2_PUB => Codec::from_bytes(bytes).map(Self::Bls12_381), + ssi_multicodec::BLS12_381_G2_PUB => { + ssi_multicodec::Codec::from_bytes(bytes).map(Self::Bls12_381) + } _ => Err(ssi_multicodec::Error::UnexpectedCodec(codec)), } } From 00b720e0b5e4942a48f771861b565d20c01205a0 Mon Sep 17 00:00:00 2001 From: Simon Bihel Date: Tue, 9 Jul 2024 18:03:39 +0100 Subject: [PATCH 32/34] Add to_jwk for Bls multikeys --- .../src/methods/w3c/multikey.rs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index b1a3558e9..6dadf49c6 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use ssi_claims_core::{ InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, }; -use ssi_jwk::JWK; +use ssi_jwk::{Base64urlUInt, ECParams, Params, JWK}; use ssi_multicodec::{MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ @@ -462,6 +462,27 @@ impl DecodedMultikey { Self::P256(key) => Some((*key).into()), #[cfg(feature = "secp384r1")] Self::P384(key) => Some((*key).into()), + #[cfg(feature = "bbs")] + Self::Bls12_381(key) => { + let (x, y) = key.to_coordinates(); + let params = ECParams { + curve: Some("BLS12381G2".to_string()), + x_coordinate: Some(Base64urlUInt(x.to_vec())), + y_coordinate: Some(Base64urlUInt(y.to_vec())), + ecc_private_key: None, + }; + Some(JWK { + params: Params::EC(params), + public_key_use: None, + key_operations: None, + algorithm: None, + key_id: None, + x509_url: None, + x509_certificate_chain: None, + x509_thumbprint_sha1: None, + x509_thumbprint_sha256: None, + }) + } _ => None, } } From cd6b5b0004f234c1114db21ce707275d277220d2 Mon Sep 17 00:00:00 2001 From: Simon Bihel Date: Tue, 9 Jul 2024 18:07:24 +0100 Subject: [PATCH 33/34] Revert manual JWK conversion --- .../src/methods/w3c/multikey.rs | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index 6dadf49c6..e54a5bb32 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use ssi_claims_core::{ InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity, SignatureError, }; -use ssi_jwk::{Base64urlUInt, ECParams, Params, JWK}; +use ssi_jwk::JWK; use ssi_multicodec::{MultiCodec, MultiEncodedBuf}; use ssi_security::MultibaseBuf; use ssi_verification_methods_core::{ @@ -463,26 +463,7 @@ impl DecodedMultikey { #[cfg(feature = "secp384r1")] Self::P384(key) => Some((*key).into()), #[cfg(feature = "bbs")] - Self::Bls12_381(key) => { - let (x, y) = key.to_coordinates(); - let params = ECParams { - curve: Some("BLS12381G2".to_string()), - x_coordinate: Some(Base64urlUInt(x.to_vec())), - y_coordinate: Some(Base64urlUInt(y.to_vec())), - ecc_private_key: None, - }; - Some(JWK { - params: Params::EC(params), - public_key_use: None, - key_operations: None, - algorithm: None, - key_id: None, - x509_url: None, - x509_certificate_chain: None, - x509_thumbprint_sha1: None, - x509_thumbprint_sha256: None, - }) - } + Self::Bls12_381(key) => Some(key.into()), _ => None, } } From 3aaa2b4ec668e648199cec6f6a01b4e474765584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Wed, 10 Jul 2024 11:15:38 +0200 Subject: [PATCH 34/34] Upgrade `json-ld` to 0.21.1 --- crates/json-ld/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-ld/Cargo.toml b/crates/json-ld/Cargo.toml index e9bf3fd67..05ab91db3 100644 --- a/crates/json-ld/Cargo.toml +++ b/crates/json-ld/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/ssi-json-ld/" [dependencies] thiserror.workspace = true async-std = { version = "1.9", features = ["attributes"] } -json-ld = { version = "0.21", features = ["serde"] } +json-ld = { version = "0.21.1", features = ["serde"] } iref.workspace = true static-iref.workspace = true rdf-types.workspace = true