From 88110a95573be5615737c8749cdef811e6f3ea87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 19 Jul 2024 06:22:51 -0300 Subject: [PATCH] `ecdsa-sd-2023` cryptosuite. (#582) * Fix sd dataset canonicalization --- .../claims/crates/data-integrity/Cargo.toml | 9 +- .../crates/data-integrity/core/Cargo.toml | 6 +- .../core/src/canonicalization.rs | 1 + .../crates/data-integrity/core/src/options.rs | 2 +- .../core/src/proof/configuration/mod.rs | 22 +- .../core/src/proof/configuration/reference.rs | 2 +- .../core/src/proof/de/configuration.rs | 155 ++++++++++++ .../data-integrity/core/src/proof/de/mod.rs | 2 + .../core/src/proof/de/ref_or_value.rs | 2 +- .../data-integrity/core/src/proof/mod.rs | 25 +- .../core/src/proof/reference.rs | 2 +- .../data-integrity/core/src/proof/type.rs | 19 ++ .../data-integrity/core/src/signing/jws.rs | 2 +- .../data-integrity/core/src/signing/mod.rs | 28 +- .../core/src/signing/multibase.rs | 2 +- .../data-integrity/core/src/suite/bounds.rs | 2 +- .../core/src/suite/configuration.rs | 7 + .../data-integrity/core/src/suite/mod.rs | 2 +- .../data-integrity/core/src/suite/sd.rs | 2 +- .../core/src/suite/standard/mod.rs | 17 +- .../core/src/suite/standard/transformation.rs | 8 + .../data-integrity/sd-primitives/Cargo.toml | 5 +- .../sd-primitives/src/canonicalize.rs | 7 +- .../data-integrity/sd-primitives/src/group.rs | 4 +- .../sd-primitives/src/json_pointer.rs | 21 +- .../data-integrity/sd-primitives/src/lib.rs | 225 ++++++++++++++++- .../crates/data-integrity/src/any/sd.rs | 78 ++++-- .../data-integrity/src/any/signature.rs | 3 +- .../src/any/signature_options.rs | 56 +++- .../data-integrity/src/any/suite/mod.rs | 3 + .../crates/data-integrity/suites/Cargo.toml | 10 +- .../unspecified/eip712_signature_2021.rs | 1 + .../data-integrity/suites/src/suites/w3c.rs | 5 + .../suites/src/suites/w3c/bbs_2023/derive.rs | 9 +- .../suites/src/suites/w3c/bbs_2023/mod.rs | 9 +- .../src/suites/w3c/bbs_2023/signature/mod.rs | 5 +- .../w3c/bbs_2023/transformation/base.rs | 114 +-------- .../suites/w3c/bbs_2023/transformation/mod.rs | 14 +- .../src/suites/w3c/bbs_2023/verification.rs | 10 +- .../suites/src/suites/w3c/ecdsa_rdfc_2019.rs | 84 +----- .../suites/w3c/ecdsa_sd_2023/configuration.rs | 55 ++++ .../src/suites/w3c/ecdsa_sd_2023/derive.rs | 210 +++++++++++++++ .../src/suites/w3c/ecdsa_sd_2023/hashing.rs | 86 +++++++ .../src/suites/w3c/ecdsa_sd_2023/mod.rs | 84 ++++++ .../w3c/ecdsa_sd_2023/signature/base.rs | 211 ++++++++++++++++ .../w3c/ecdsa_sd_2023/signature/derived.rs | 181 +++++++++++++ .../suites/w3c/ecdsa_sd_2023/signature/mod.rs | 62 +++++ .../w3c/ecdsa_sd_2023/transformation/base.rs | 92 +++++++ .../ecdsa_sd_2023/transformation/derive.rs | 47 ++++ .../w3c/ecdsa_sd_2023/transformation/mod.rs | 74 ++++++ .../suites/w3c/ecdsa_sd_2023/verification.rs | 130 ++++++++++ .../w3c/ethereum_eip712_signature_2021.rs | 1 + .../suites/tests/ecdsa_rdfc_2019/input.jsonld | 16 -- .../suites/tests/ecdsa_rdfc_2019/keys.json | 4 - .../suites/tests/ecdsa_rdfc_2019/options.json | 11 - .../tests/ecdsa_rdfc_2019/output.jsonld | 27 -- .../data-integrity/suites/tests/suite.rs | 239 ------------------ .../crates/data-integrity/tests/common/mod.rs | 50 ++++ .../data-integrity/tests/common/selection.rs | 41 +++ .../data-integrity/tests/common/signature.rs | 57 +++++ .../tests/common/verification.rs | 30 +++ .../tests/ecdsa_rdfc_2019/p256_signature.json | 66 +++++ .../ecdsa_rdfc_2019/p256_verification.json | 39 +++ .../tests/ecdsa_rdfc_2019/p384_signature.json | 66 +++++ .../ecdsa_rdfc_2019/p384_verification.json | 61 +++++ .../tests/ecdsa_sd_2023/selection.json | 119 +++++++++ .../tests/ecdsa_sd_2023/signature.json | 137 ++++++++++ .../tests/ecdsa_sd_2023/verification.json | 59 +++++ .../tests/ecdsa_sd_2023/verification_2.json | 97 +++++++ .../tests/ecdsa_sd_2023/verification_3.json | 57 +++++ .../crates/data-integrity/tests/suite.rs | 65 +++++ crates/crypto/src/algorithm/mod.rs | 51 ++++ crates/jwk/src/algorithm.rs | 11 +- crates/multicodec/src/codec/p256.rs | 17 +- crates/multicodec/src/codec/p384.rs | 3 +- crates/multicodec/src/lib.rs | 6 + .../verification-methods/core/src/methods.rs | 1 + .../src/methods/w3c/multikey.rs | 35 +++ 78 files changed, 2992 insertions(+), 586 deletions(-) create mode 100644 crates/claims/crates/data-integrity/core/src/proof/de/configuration.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/configuration.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/derive.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/hashing.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/mod.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/base.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/derived.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/mod.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/base.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/derive.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/mod.rs create mode 100644 crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/verification.rs delete mode 100644 crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/input.jsonld delete mode 100644 crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/keys.json delete mode 100644 crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/options.json delete mode 100644 crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/output.jsonld delete mode 100644 crates/claims/crates/data-integrity/suites/tests/suite.rs create mode 100644 crates/claims/crates/data-integrity/tests/common/mod.rs create mode 100644 crates/claims/crates/data-integrity/tests/common/selection.rs create mode 100644 crates/claims/crates/data-integrity/tests/common/signature.rs create mode 100644 crates/claims/crates/data-integrity/tests/common/verification.rs create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_signature.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_verification.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_signature.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_verification.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/selection.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/signature.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_2.json create mode 100644 crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_3.json create mode 100644 crates/claims/crates/data-integrity/tests/suite.rs diff --git a/crates/claims/crates/data-integrity/Cargo.toml b/crates/claims/crates/data-integrity/Cargo.toml index 5aacfdfe5..4b3d3e7f1 100644 --- a/crates/claims/crates/data-integrity/Cargo.toml +++ b/crates/claims/crates/data-integrity/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-claims/" [features] -# default = ["w3c", "ed25519", "rsa", "secp256k1", "secp256r1", "tezos", "ethereum", "eip712", "aleo", "solana"] +default = ["w3c", "ed25519", "rsa", "secp256k1", "secp256r1"] ## Signature suites specified by the W3C. ## @@ -118,4 +118,9 @@ serde.workspace = true serde_json.workspace = true json-syntax.workspace = true thiserror.workspace = true -chrono.workspace = true \ No newline at end of file +chrono.workspace = true + +[dev-dependencies] +ssi-multicodec.workspace = true +serde = { workspace = true, features = ["derive"] } +async-std = { workspace = true, features = ["attributes"] } \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/core/Cargo.toml b/crates/claims/crates/data-integrity/core/Cargo.toml index feb090cd7..9cd70d308 100644 --- a/crates/claims/crates/data-integrity/core/Cargo.toml +++ b/crates/claims/crates/data-integrity/core/Cargo.toml @@ -8,8 +8,12 @@ description = "Verifiable Credential Data Integrity 1.0 core implementation for repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi-data-integrity/" +[features] +secp256r1 = ["ssi-verification-methods/secp256r1"] +secp384r1 = ["ssi-verification-methods/secp384r1"] + [dependencies] -ssi-verification-methods-core.workspace = true +ssi-verification-methods.workspace = true rdf-types.workspace = true xsd-types = { workspace = true, features = ["serde"] } linked-data = { workspace = true, features = ["derive"] } diff --git a/crates/claims/crates/data-integrity/core/src/canonicalization.rs b/crates/claims/crates/data-integrity/core/src/canonicalization.rs index 449f3dbb3..2fcc728c3 100644 --- a/crates/claims/crates/data-integrity/core/src/canonicalization.rs +++ b/crates/claims/crates/data-integrity/core/src/canonicalization.rs @@ -39,6 +39,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, + _verification_method: &S::VerificationMethod, _transformation_options: TransformationOptions, ) -> Result { let mut ld = LdEnvironment::default(); diff --git a/crates/claims/crates/data-integrity/core/src/options.rs b/crates/claims/crates/data-integrity/core/src/options.rs index e81058f2a..d41e26f55 100644 --- a/crates/claims/crates/data-integrity/core/src/options.rs +++ b/crates/claims/crates/data-integrity/core/src/options.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwned}; +use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use crate::{suite::ConfigurationError, CryptographicSuite, ProofConfiguration}; diff --git a/crates/claims/crates/data-integrity/core/src/proof/configuration/mod.rs b/crates/claims/crates/data-integrity/core/src/proof/configuration/mod.rs index c1e2f90b1..648e5aff3 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/configuration/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/configuration/mod.rs @@ -1,6 +1,6 @@ use iref::Iri; use serde::Serialize; -use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwned}; +use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use static_iref::iri; use std::collections::BTreeMap; @@ -140,6 +140,26 @@ impl ProofConfiguration { Self::from_method_and_options(type_, verification_method, Default::default()) } + pub fn into_suite_and_options( + self, + ) -> (S, ProofOptions) { + ( + self.type_, + ProofOptions { + context: self.context, + created: self.created, + verification_method: Some(self.verification_method), + proof_purpose: self.proof_purpose, + expires: self.expires, + domains: self.domains, + challenge: self.challenge, + nonce: self.nonce, + options: self.options, + extra_properties: self.extra_properties, + }, + ) + } + pub fn into_options(self) -> ProofOptions { ProofOptions { context: self.context, diff --git a/crates/claims/crates/data-integrity/core/src/proof/configuration/reference.rs b/crates/claims/crates/data-integrity/core/src/proof/configuration/reference.rs index 1b35e9c34..abb569922 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/configuration/reference.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/configuration/reference.rs @@ -1,6 +1,6 @@ use educe::Educe; use serde::Serialize; -use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwnedRef}; +use ssi_verification_methods::{ProofPurpose, ReferenceOrOwnedRef}; use std::collections::BTreeMap; use crate::{ diff --git a/crates/claims/crates/data-integrity/core/src/proof/de/configuration.rs b/crates/claims/crates/data-integrity/core/src/proof/de/configuration.rs new file mode 100644 index 000000000..2f0f11945 --- /dev/null +++ b/crates/claims/crates/data-integrity/core/src/proof/de/configuration.rs @@ -0,0 +1,155 @@ +use crate::{ + suite::bounds::{OptionsOf, VerificationMethodOf}, + CryptosuiteString, DeserializeCryptographicSuite, ProofConfiguration, Type, +}; +use serde::{ + de::{DeserializeSeed, MapAccess}, + Deserialize, +}; +use ssi_core::de::WithType; +use std::{collections::BTreeMap, marker::PhantomData}; + +use super::{Field, RefOrValue, ReplayMap, TypeField}; + +impl<'de, T: DeserializeCryptographicSuite<'de>> ProofConfiguration { + fn deserialize_with_type(type_: Type, mut deserializer: S) -> Result + where + S: serde::de::MapAccess<'de>, + { + let suite: T = type_ + .try_into() + .map_err(|_| serde::de::Error::custom("unexpected cryptosuite"))?; + + let mut context = None; + let mut created = None; + let mut verification_method = None; + let mut proof_purpose = None; + let mut expires = None; + let mut domains = None; + let mut challenge = None; + let mut nonce = None; + + let mut other = Vec::new(); + + while let Some(key) = deserializer.next_key::()? { + match key { + Field::Context => context = Some(deserializer.next_value()?), + Field::Created => created = Some(deserializer.next_value()?), + Field::VerificationMethod => { + verification_method = Some({ + deserializer + .next_value_seed( + WithType::>>::new(&suite), + )? + }) + } + Field::ProofPurpose => proof_purpose = Some(deserializer.next_value()?), + Field::Expires => expires = Some(deserializer.next_value()?), + Field::Domains => domains = Some(deserializer.next_value()?), + Field::Challenge => challenge = Some(deserializer.next_value()?), + Field::Nonce => nonce = Some(deserializer.next_value()?), + Field::Other(key) => other.push(Some((key, deserializer.next_value()?))), + } + } + + let options = WithType::>::new(&suite) + .deserialize(serde::__private::de::FlatMapDeserializer( + &mut other, + PhantomData, + ))? + .0; + + Ok(Self { + context, + type_: suite, + created, + verification_method: verification_method + .map(|v| v.map(VerificationMethodOf::unwrap).into()) + .ok_or_else(|| serde::de::Error::custom("missing `verificationMethod` property"))?, + proof_purpose: proof_purpose + .ok_or_else(|| serde::de::Error::custom("missing `proofPurpose` property"))?, + expires, + domains: domains.unwrap_or_default(), + challenge, + nonce, + options, + extra_properties: BTreeMap::deserialize(serde::__private::de::FlatMapDeserializer( + &mut other, + PhantomData, + ))?, + }) + } +} + +impl<'de, T: DeserializeCryptographicSuite<'de>> serde::Deserialize<'de> for ProofConfiguration { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(ProofConfigurationVisitor(PhantomData)) + } +} + +struct ProofConfigurationVisitor(PhantomData); + +impl<'de, T: DeserializeCryptographicSuite<'de>> serde::de::Visitor<'de> + for ProofConfigurationVisitor +{ + type Value = ProofConfiguration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "Data-Integrity proof") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut keep = Vec::new(); + let mut cryptosuite = None; + let mut data_integrity_proof = false; + + while let Some(key) = map.next_key::()? { + match key { + TypeField::Type => { + let name = map.next_value::()?; + + if name == "DataIntegrityProof" { + match cryptosuite.take() { + Some(c) => { + return ProofConfiguration::::deserialize_with_type( + Type::DataIntegrityProof(c), + ReplayMap::new(keep, map), + ); + } + None => { + data_integrity_proof = true; + } + } + } else { + return ProofConfiguration::::deserialize_with_type( + Type::Other(name), + ReplayMap::new(keep, map), + ); + } + } + TypeField::Cryptosuite => { + let name = map.next_value::()?; + if data_integrity_proof { + return ProofConfiguration::::deserialize_with_type( + Type::DataIntegrityProof(name), + ReplayMap::new(keep, map), + ); + } else { + cryptosuite = Some(name) + } + } + TypeField::Other(key) => { + keep.push((key, map.next_value()?)); + } + } + } + + Err(serde::de::Error::custom("missing type")) + } +} 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 33f5cd358..3e8aa6bd0 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 @@ -26,6 +26,8 @@ pub use ref_or_value::*; mod replay_map; pub use replay_map::*; +mod configuration; + impl<'de, T: DeserializeCryptographicSuite<'de>> Proof { fn deserialize_with_type(type_: Type, mut deserializer: S) -> Result where diff --git a/crates/claims/crates/data-integrity/core/src/proof/de/ref_or_value.rs b/crates/claims/crates/data-integrity/core/src/proof/de/ref_or_value.rs index abd1e67cd..f58edaa5b 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/de/ref_or_value.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/de/ref_or_value.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use iref::IriBuf; use serde::de::MapAccess; use ssi_core::de::DeserializeTyped; -use ssi_verification_methods_core::ReferenceOrOwned; +use ssi_verification_methods::ReferenceOrOwned; pub enum RefOrValue { Ref(IriBuf), 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 7ee9b9c5e..17e867d50 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/mod.rs @@ -10,7 +10,7 @@ use educe::Educe; use serde::{Deserialize, Serialize}; use ssi_claims_core::{AttachProof, ProofValidationError, ProofValidity, ResourceProvider}; use ssi_core::{one_or_many::OneOrManyRef, OneOrMany}; -use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwned}; +use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; use std::collections::BTreeMap; use std::{ borrow::{Borrow, BorrowMut}, @@ -178,6 +178,29 @@ impl Proof { extra_properties: &self.extra_properties, } } + + pub fn map_type( + self, + type_: impl FnOnce(T) -> U, + verification_method: impl FnOnce(T::VerificationMethod) -> U::VerificationMethod, + options: impl FnOnce(T::ProofOptions) -> U::ProofOptions, + signature: impl FnOnce(T::Signature) -> U::Signature, + ) -> Proof { + Proof { + context: self.context, + type_: type_(self.type_), + created: self.created, + verification_method: self.verification_method.map(verification_method), + proof_purpose: self.proof_purpose, + expires: self.expires, + domains: self.domains, + challenge: self.challenge, + nonce: self.nonce, + options: options(self.options), + signature: signature(self.signature), + extra_properties: self.extra_properties, + } + } } impl Clone for Proof { diff --git a/crates/claims/crates/data-integrity/core/src/proof/reference.rs b/crates/claims/crates/data-integrity/core/src/proof/reference.rs index 3ed59a2b3..de1faccd7 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/reference.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/reference.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use ssi_verification_methods_core::{ProofPurpose, ReferenceOrOwnedRef}; +use ssi_verification_methods::{ProofPurpose, ReferenceOrOwnedRef}; use crate::{CryptographicSuite, ProofConfigurationRef}; 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 8db7ab3a1..45f2fdc12 100644 --- a/crates/claims/crates/data-integrity/core/src/proof/type.rs +++ b/crates/claims/crates/data-integrity/core/src/proof/type.rs @@ -47,6 +47,14 @@ impl Type { } } +impl TryFrom for Type { + type Error = MissingCryptosuite; + + fn try_from(value: CompactType) -> Result { + Self::new(value.name, value.cryptosuite) + } +} + impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -68,6 +76,17 @@ impl<'a> PartialEq> for Type { } } +impl<'de> Deserialize<'de> for Type { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + CompactType::deserialize(deserializer)? + .try_into() + .map_err(serde::de::Error::custom) + } +} + /// Proof type reference. pub enum TypeRef<'a> { DataIntegrityProof(&'a CryptosuiteStr), 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 b0c69140e..40dc5defa 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/jws.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/jws.rs @@ -4,7 +4,7 @@ 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}; +use ssi_verification_methods::{MessageSigner, VerifyBytes, VerifyBytesWithRecoveryJwk}; use crate::{ suite::standard::{ 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 be525a151..cbf0a7b01 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/mod.rs @@ -1,6 +1,6 @@ use ssi_claims_core::{ProofValidationError, SignatureError}; use ssi_crypto::{ - algorithm::{self, Algorithm, SignatureAlgorithmType}, + algorithm::{self, Algorithm, ES256OrES384, SignatureAlgorithmType}, AlgorithmInstance, }; @@ -9,6 +9,7 @@ pub use jws::*; mod multibase; pub use multibase::*; +use ssi_verification_methods::Multikey; pub enum AlgorithmSelectionError { MissingAlgorithm, @@ -40,9 +41,7 @@ pub trait AlgorithmSelection: SignatureAlgorithmType { ) -> Result; } -impl AlgorithmSelection - for Algorithm -{ +impl AlgorithmSelection for Algorithm { fn select_algorithm( verification_method: &M, _options: &O, @@ -55,7 +54,7 @@ impl AlgorithmSelect } } -impl AlgorithmSelection +impl AlgorithmSelection for ssi_jwk::Algorithm { fn select_algorithm( @@ -123,6 +122,25 @@ impl AlgorithmSelection for algorithm::ESBlake2b { } } +impl AlgorithmSelection for ES256OrES384 { + fn select_algorithm( + verification_method: &Multikey, + _options: &O, + ) -> Result { + match verification_method + .public_key + .decode() + .map_err(|_| AlgorithmSelectionError::InvalidKey)? + { + #[cfg(feature = "secp256r1")] + ssi_verification_methods::multikey::DecodedMultikey::P256(_) => Ok(Self::ES256), + #[cfg(feature = "secp384r1")] + ssi_verification_methods::multikey::DecodedMultikey::P384(_) => Ok(Self::ES384), + _ => Err(AlgorithmSelectionError::InvalidKey), + } + } +} + pub trait AlterSignature { fn alter(&mut self); } 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 39dde5f33..655403b9f 100644 --- a/crates/claims/crates/data-integrity/core/src/signing/multibase.rs +++ b/crates/claims/crates/data-integrity/core/src/signing/multibase.rs @@ -3,7 +3,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 ssi_verification_methods::{MessageSigner, VerifyBytes}; use crate::{ suite::standard::{ diff --git a/crates/claims/crates/data-integrity/core/src/suite/bounds.rs b/crates/claims/crates/data-integrity/core/src/suite/bounds.rs index 0afa31f2e..7588e2b1d 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/bounds.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/bounds.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use serde::{Deserializer, Serialize, Serializer}; use ssi_core::de::DeserializeTyped; -use ssi_verification_methods_core::{ReferenceOrOwned, ReferenceOrOwnedRef}; +use ssi_verification_methods::{ReferenceOrOwned, ReferenceOrOwnedRef}; use crate::Type; 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 dc62e7ae6..ccc3b7624 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/configuration.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/configuration.rs @@ -29,6 +29,9 @@ pub enum ConfigurationError { #[error("missing option `{0}`")] MissingOption(String), + #[error("invalid option `{0}`")] + InvalidOption(String), + #[error("{0}")] Other(String), } @@ -40,6 +43,10 @@ impl From for ConfigurationError { } impl ConfigurationError { + pub fn invalid_option(e: impl ToString) -> Self { + Self::InvalidOption(e.to_string()) + } + pub fn other(e: impl ToString) -> Self { Self::Other(e.to_string()) } 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 a96b1d834..d07b88dbb 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/mod.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/mod.rs @@ -1,7 +1,7 @@ use ssi_claims_core::{ ProofPreparationError, ProofValidationError, SignatureEnvironment, SignatureError, }; -use ssi_verification_methods_core::VerificationMethod; +use ssi_verification_methods::VerificationMethod; mod signature; pub use signature::*; diff --git a/crates/claims/crates/data-integrity/core/src/suite/sd.rs b/crates/claims/crates/data-integrity/core/src/suite/sd.rs index c7dc8275e..9f3f6d461 100644 --- a/crates/claims/crates/data-integrity/core/src/suite/sd.rs +++ b/crates/claims/crates/data-integrity/core/src/suite/sd.rs @@ -1,4 +1,4 @@ -use ssi_verification_methods_core::VerificationMethodResolutionError; +use ssi_verification_methods::VerificationMethodResolutionError; use crate::{CryptographicSuite, DataIntegrity, ProofRef}; 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 952ca6f3c..7bfa15684 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 @@ -4,7 +4,7 @@ use std::borrow::Cow; use ssi_claims_core::{ ProofValidationError, ProofValidity, ResolverProvider, ResourceProvider, SignatureError, }; -use ssi_verification_methods_core::{Signer, VerificationMethodResolver, VerificationMethodSet}; +use ssi_verification_methods::{Signer, VerificationMethodResolver, VerificationMethodSet}; use crate::{CryptographicSuite, ProofConfigurationRef, ProofRef, TypeRef}; @@ -61,6 +61,7 @@ pub trait StandardCryptographicSuite: Clone { context: &C, unsecured_document: &T, proof_configuration: ProofConfigurationRef<'_, Self>, + verification_method: &Self::VerificationMethod, transformation_options: TransformationOptions, ) -> Result, TransformationError> where @@ -70,6 +71,7 @@ pub trait StandardCryptographicSuite: Clone { context, unsecured_document, proof_configuration, + verification_method, transformation_options, ) .await @@ -131,7 +133,7 @@ where proof_configuration: ProofConfigurationRef<'_, Self>, transformation_options: TransformationOptions, ) -> Result { - let options = ssi_verification_methods_core::ResolutionOptions { + let options = ssi_verification_methods::ResolutionOptions { accept: Some(Box::new(Self::VerificationMethod::type_set())), }; @@ -145,7 +147,13 @@ where .await?; let transformed = self - .transform(context, claims, proof_configuration, transformation_options) + .transform( + context, + claims, + proof_configuration, + &method, + transformation_options, + ) .await?; let hashed = self.hash(transformed, proof_configuration, &method)?; @@ -174,7 +182,7 @@ where proof: ProofRef<'_, Self>, transformation_options: TransformationOptions, ) -> Result { - let options = ssi_verification_methods_core::ResolutionOptions { + let options = ssi_verification_methods::ResolutionOptions { accept: Some(Box::new(Self::VerificationMethod::type_set())), }; @@ -191,6 +199,7 @@ where verifier, claims, proof_configuration, + &method, transformation_options, ) .await?; 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 94c1d653c..ad908599f 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 @@ -32,6 +32,12 @@ pub enum TransformationError { #[error("invalid input")] InvalidInput, + #[error("invalid options")] + InvalidOptions, + + #[error("invalid key")] + InvalidKey, + #[error("{0}")] Internal(String), } @@ -72,6 +78,7 @@ pub trait TypedTransformationAlgorithm: context: &C, data: &T, proof_configuration: ProofConfigurationRef, + verification_method: &S::VerificationMethod, transformation_options: TransformationOptions, ) -> Result; } @@ -89,6 +96,7 @@ impl TypedTransformationAlgorith _context: &C, data: &T, _options: ProofConfigurationRef<'_, S>, + _verification_method: &S::VerificationMethod, _transformation_options: TransformationOptions, ) -> Result { json_syntax::to_value(data) diff --git a/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml b/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml index 0e0c099d6..29d62a51f 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml +++ b/crates/claims/crates/data-integrity/sd-primitives/Cargo.toml @@ -21,9 +21,10 @@ digest.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true uuid = { workspace = true, features = ["v4"] } +getrandom.workspace = true +hex.workspace = true [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 +json-syntax.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 index 8b938b878..e5d4eee51 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/canonicalize.rs @@ -1,20 +1,19 @@ -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; +use crate::HmacShaAny; pub fn create_hmac_id_label_map_function( - hmac: &mut HmacSha256, + hmac: &mut HmacShaAny, ) -> 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 digest = hmac.finalize_reset(); let b64_url_digest = BlankIdBuf::new(format!( "_:u{}", base64::encode_config(digest, base64::URL_SAFE_NO_PAD) 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 125e39da1..58e6552cf 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/group.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/group.rs @@ -129,7 +129,7 @@ mod tests { use ssi_json_ld::CompactJsonLd; use ssi_rdf::IntoNQuads; - use crate::{canonicalize::create_hmac_id_label_map_function, JsonPointerBuf}; + use crate::{canonicalize::create_hmac_id_label_map_function, HmacShaAny, JsonPointerBuf}; use super::canonicalize_and_group; @@ -203,7 +203,7 @@ mod tests { 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 mut hmac = HmacShaAny::Sha256(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(); 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 83c099e16..c957d1416 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 @@ -1,7 +1,7 @@ use core::fmt; use std::{borrow::Cow, ops::Deref, str::FromStr}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, thiserror::Error)] #[error("invalid JSON pointer `{0}`")] @@ -158,12 +158,31 @@ impl FromStr for JsonPointerBuf { } } +impl TryFrom for JsonPointerBuf { + type Error = InvalidJsonPointer; + + fn try_from(value: String) -> Result { + Self::new(value) + } +} + impl fmt::Display for JsonPointerBuf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } +impl<'de> Deserialize<'de> for JsonPointerBuf { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(serde::de::Error::custom) + } +} + #[derive(Debug)] #[repr(transparent)] pub struct ReferenceToken(str); 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 4d1d256dd..41013827a 100644 --- a/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs +++ b/crates/claims/crates/data-integrity/sd-primitives/src/lib.rs @@ -1,9 +1,230 @@ +use std::ops::Deref; + +use getrandom::getrandom; pub use hmac::Hmac; -use sha2::Sha256; +use hmac::Mac; +use serde::{Deserialize, Serialize}; +use sha2::{Sha256, Sha384}; pub type HmacSha256 = Hmac; +pub type HmacSha384 = Hmac; + +#[derive(Debug, Clone, Copy)] +pub enum ShaAny { + Sha256, + Sha384, +} + +impl ShaAny { + pub fn into_key(self, key: Option) -> Result { + match (self, key) { + (Self::Sha256, Some(HmacShaAnyKey::Sha256(key))) => Ok(HmacShaAnyKey::Sha256(key)), + (Self::Sha384, Some(HmacShaAnyKey::Sha384(key))) => Ok(HmacShaAnyKey::Sha384(key)), + (_, Some(_)) => Err(IntoHmacError::IncompatibleKey), + (Self::Sha256, None) => { + let mut key = HmacSha256Key::default(); + getrandom(&mut key).map_err(IntoHmacError::rng)?; + Ok(HmacShaAnyKey::Sha256(key)) + } + (Self::Sha384, None) => { + let mut key = [0; 48]; + getrandom(&mut key).map_err(IntoHmacError::rng)?; + Ok(HmacShaAnyKey::Sha384(key)) + } + } + } + + pub fn into_hmac(self, key: Option) -> Result { + match (self, key) { + (Self::Sha256, Some(HmacShaAnyKey::Sha256(key))) => Ok(HmacShaAny::Sha256( + HmacSha256::new_from_slice(&key).unwrap(), + )), + (Self::Sha384, Some(HmacShaAnyKey::Sha384(key))) => Ok(HmacShaAny::Sha384( + HmacSha384::new_from_slice(&key).unwrap(), + )), + (_, Some(_)) => Err(IntoHmacError::IncompatibleKey), + (Self::Sha256, None) => { + let mut key = HmacSha256Key::default(); + getrandom(&mut key).map_err(IntoHmacError::rng)?; + Ok(HmacShaAny::Sha256( + HmacSha256::new_from_slice(&key).unwrap(), + )) + } + (Self::Sha384, None) => { + let mut key = [0; 48]; + getrandom(&mut key).map_err(IntoHmacError::rng)?; + Ok(HmacShaAny::Sha384( + HmacSha384::new_from_slice(&key).unwrap(), + )) + } + } + } + + pub fn hash_all(&self, iter: I) -> ShaAnyBytes + where + I::Item: AsRef<[u8]>, + { + use sha2::Digest; + match self { + Self::Sha256 => ShaAnyBytes::Sha256( + iter.into_iter() + .fold(Sha256::new(), |h, line| h.chain_update(line.as_ref())) + .finalize() + .into(), + ), + Self::Sha384 => ShaAnyBytes::Sha384( + iter.into_iter() + .fold(Sha384::new(), |h, line| h.chain_update(line.as_ref())) + .finalize() + .into(), + ), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum IntoHmacError { + #[error("incompatible key")] + IncompatibleKey, + + #[error("random number generation failed: {0}")] + RandomGenerationFailed(String), +} + +impl IntoHmacError { + fn rng(e: impl ToString) -> Self { + Self::RandomGenerationFailed(e.to_string()) + } +} + +pub enum HmacShaAny { + Sha256(HmacSha256), + Sha384(HmacSha384), +} + +impl HmacShaAny { + pub fn update(&mut self, data: &[u8]) { + match self { + Self::Sha256(hmac) => hmac.update(data), + Self::Sha384(hmac) => hmac.update(data), + } + } + + pub fn finalize_reset(&mut self) -> ShaAnyBytes { + match self { + Self::Sha256(hmac) => ShaAnyBytes::Sha256(hmac.finalize_reset().into_bytes().into()), + Self::Sha384(hmac) => ShaAnyBytes::Sha384(hmac.finalize_reset().into_bytes().into()), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ShaAnyBytes { + Sha256([u8; 32]), + Sha384([u8; 48]), +} + +impl ShaAnyBytes { + pub fn as_slice(&self) -> &[u8] { + match self { + Self::Sha256(bytes) => bytes, + Self::Sha384(bytes) => bytes, + } + } +} + +impl Deref for ShaAnyBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl AsRef<[u8]> for ShaAnyBytes { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid HMAC key")] +pub struct InvalidHmacKey; + +pub type HmacSha256Key = [u8; 32]; +pub type HmacSha384Key = [u8; 48]; + +#[derive(Debug, Clone, Copy)] +pub enum HmacShaAnyKey { + Sha256(HmacSha256Key), + Sha384(HmacSha384Key), +} + +impl HmacShaAnyKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes.len() { + 32 => Ok(Self::Sha256(bytes.try_into().unwrap())), + 48 => Ok(Self::Sha384(bytes.try_into().unwrap())), + _ => Err(InvalidHmacKey), + } + } + + pub fn as_slice(&self) -> &[u8] { + match self { + Self::Sha256(bytes) => bytes, + Self::Sha384(bytes) => bytes, + } + } + + pub fn to_hmac(&self) -> HmacShaAny { + match self { + Self::Sha256(key) => HmacShaAny::Sha256(HmacSha256::new_from_slice(key).unwrap()), + Self::Sha384(key) => HmacShaAny::Sha384(HmacSha384::new_from_slice(key).unwrap()), + } + } + + pub fn algorithm(&self) -> ShaAny { + match self { + Self::Sha256(_) => ShaAny::Sha256, + Self::Sha384(_) => ShaAny::Sha384, + } + } + + pub fn into_sha256(self) -> Result { + match self { + Self::Sha256(k) => Ok(k), + other => Err(other), + } + } +} + +impl Deref for HmacShaAnyKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl Serialize for HmacShaAnyKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + hex::encode(self.as_slice()).serialize(serializer) + } +} -pub type HmacKey = [u8; 32]; +impl<'de> Deserialize<'de> for HmacShaAnyKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string = String::deserialize(deserializer)?; + let bytes = hex::decode(hex_string).map_err(serde::de::Error::custom)?; + HmacShaAnyKey::from_bytes(&bytes).map_err(serde::de::Error::custom) + } +} pub mod canonicalize; pub mod group; diff --git a/crates/claims/crates/data-integrity/src/any/sd.rs b/crates/claims/crates/data-integrity/src/any/sd.rs index 8e9c44f3c..e074f91bc 100644 --- a/crates/claims/crates/data-integrity/src/any/sd.rs +++ b/crates/claims/crates/data-integrity/src/any/sd.rs @@ -1,4 +1,4 @@ -use serde::Serialize; +use serde::{Deserialize, Serialize}; use ssi_claims_core::ResolverProvider; use ssi_data_integrity_core::{ suite::{CryptographicSuiteSelect, SelectionError, SelectiveCryptographicSuite}, @@ -11,7 +11,8 @@ use ssi_verification_methods::{AnyMethod, VerificationMethodResolver}; use crate::AnySuite; -#[derive(Debug, Default)] +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AnySelectionOptions { pub selective_pointers: Vec, @@ -29,6 +30,15 @@ impl From for ssi_data_integrity_suites::bbs_2023::DeriveOp } } +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +impl From for ssi_data_integrity_suites::ecdsa_sd_2023::DeriveOptions { + fn from(value: AnySelectionOptions) -> Self { + Self { + selective_pointers: value.selective_pointers, + } + } +} + impl SelectiveCryptographicSuite for AnySuite { type SelectionOptions = AnySelectionOptions; } @@ -48,17 +58,43 @@ where params: P, options: Self::SelectionOptions, ) -> Result, SelectionError> { + let params = crate::AnyVerifier { + resolver: crate::AnyResolver::<_, ssi_verification_methods::Multikey>::new( + params.resolver(), + ), + json_ld_loader: params.loader(), + eip712_loader: (), + }; + match self { + #[cfg(all(feature = "w3c", feature = "secp256r1"))] + Self::EcdsaSd2023 => { + let DataIntegrity { claims, proofs } = ssi_data_integrity_suites::EcdsaSd2023 + .select( + unsecured_document, + crate::Project::project_proof(proof), + params, + options.into(), + ) + .await?; + + Ok(DataIntegrity { + claims, + proofs: proofs + .into_iter() + .map(|p| { + p.map_type( + |_| Self::EcdsaSd2023, + crate::AnySuiteVerificationMethod::EcdsaSd2023, + |_| crate::AnyProofOptions::EcdsaSd2023(()), + crate::AnySignature::EcdsaSd2023, + ) + }) + .collect(), + }) + } #[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, @@ -72,21 +108,13 @@ where 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(()), - signature: crate::AnySignature::Bbs2023(p.signature), - extra_properties: p.extra_properties, + .map(|p| { + p.map_type( + |_| Self::Bbs2023, + crate::AnySuiteVerificationMethod::Bbs2023, + |_| crate::AnyProofOptions::Bbs2023(()), + crate::AnySignature::Bbs2023, + ) }) .collect(), }) diff --git a/crates/claims/crates/data-integrity/src/any/signature.rs b/crates/claims/crates/data-integrity/src/any/signature.rs index 0281dd124..478d5a81f 100644 --- a/crates/claims/crates/data-integrity/src/any/signature.rs +++ b/crates/claims/crates/data-integrity/src/any/signature.rs @@ -125,8 +125,7 @@ impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ESBlake2b { } } -#[cfg(all(feature = "w3c", any(feature = "secp256r1", feature = "secp384r1")))] -impl IntoAnySignatureAlgorithm for ssi_data_integrity_suites::ecdsa_rdfc_2019::ES256OrES384 { +impl IntoAnySignatureAlgorithm for ssi_crypto::algorithm::ES256OrES384 { fn into_any_signature_algorithm(self) -> AnySignatureAlgorithmInstance { WithProtocol(self.into(), AnyProtocol::None) } diff --git a/crates/claims/crates/data-integrity/src/any/signature_options.rs b/crates/claims/crates/data-integrity/src/any/signature_options.rs index fa1df62eb..163bb9924 100644 --- a/crates/claims/crates/data-integrity/src/any/signature_options.rs +++ b/crates/claims/crates/data-integrity/src/any/signature_options.rs @@ -1,11 +1,20 @@ -use ssi_di_sd_primitives::JsonPointerBuf; +use serde::Deserialize; +use ssi_di_sd_primitives::{HmacShaAnyKey, JsonPointerBuf}; +use ssi_verification_methods::multikey::MultikeyPair; -#[derive(Debug, Default)] +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AnySignatureOptions { pub mandatory_pointers: Vec, + #[serde(rename = "hmacKeyString")] + pub hmac_key: Option, + + pub key_pair: Option, + #[cfg(all(feature = "w3c", feature = "bbs"))] + #[serde(default)] pub feature_option: ssi_data_integrity_suites::bbs_2023::FeatureOption, #[cfg(all(feature = "w3c", feature = "bbs"))] @@ -23,14 +32,22 @@ impl From<()> for AnySignatureOptions { } #[cfg(all(feature = "w3c", feature = "bbs"))] -impl From for ssi_data_integrity_suites::bbs_2023::Bbs2023SignatureOptions { - fn from(o: AnySignatureOptions) -> Self { - Self { +impl TryFrom for ssi_data_integrity_suites::bbs_2023::Bbs2023SignatureOptions { + type Error = ssi_data_integrity_core::suite::ConfigurationError; + + fn try_from(o: AnySignatureOptions) -> Result { + Ok(Self { mandatory_pointers: o.mandatory_pointers, feature_option: o.feature_option, commitment_with_proof: o.commitment_with_proof, - hmac_key: None, - } + hmac_key: o + .hmac_key + .map(HmacShaAnyKey::into_sha256) + .transpose() + .map_err(|_| { + ssi_data_integrity_core::suite::ConfigurationError::invalid_option("hmacKey") + })?, + }) } } @@ -41,6 +58,31 @@ impl From for AnyS mandatory_pointers: value.mandatory_pointers, feature_option: value.feature_option, commitment_with_proof: value.commitment_with_proof, + hmac_key: value.hmac_key.map(HmacShaAnyKey::Sha256), + ..Default::default() + } + } +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +impl From for ssi_data_integrity_suites::ecdsa_sd_2023::SignatureOptions { + fn from(o: AnySignatureOptions) -> Self { + Self { + mandatory_pointers: o.mandatory_pointers, + hmac_key: o.hmac_key, + key_pair: o.key_pair, + } + } +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +impl From for AnySignatureOptions { + fn from(value: ssi_data_integrity_suites::ecdsa_sd_2023::SignatureOptions) -> Self { + Self { + mandatory_pointers: value.mandatory_pointers, + hmac_key: value.hmac_key, + key_pair: value.key_pair, + ..Default::default() } } } 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 21c91ed6c..fd8f606da 100644 --- a/crates/claims/crates/data-integrity/src/any/suite/mod.rs +++ b/crates/claims/crates/data-integrity/src/any/suite/mod.rs @@ -55,6 +55,9 @@ macros::crypto_suites! { #[cfg(all(feature = "w3c", any(feature = "secp256r1", feature = "secp384r1")))] ecdsa_rdfc_2019: EcdsaRdfc2019, + #[cfg(all(feature = "w3c", feature = "secp256r1"))] + ecdsa_sd_2023: EcdsaSd2023, + /// W3C JSON Web Signature 2020. /// /// See: diff --git a/crates/claims/crates/data-integrity/suites/Cargo.toml b/crates/claims/crates/data-integrity/suites/Cargo.toml index 163715f51..8d8adf1c1 100644 --- a/crates/claims/crates/data-integrity/suites/Cargo.toml +++ b/crates/claims/crates/data-integrity/suites/Cargo.toml @@ -48,13 +48,13 @@ secp256k1 = ["ssi-verification-methods/secp256k1", "k256"] ## This includes: ## - `EcdsaSecp256r1Signature2019` (requires `w3c`) ## - `EcdsaRdfc2019` (requires `w3c`) -secp256r1 = ["ssi-verification-methods/secp256r1", "p256"] +secp256r1 = ["ssi-data-integrity-core/secp256r1", "ssi-verification-methods/secp256r1", "p256"] ## Signature suites based on secp384r1. ## ## This includes: ## - `EcdsaRdfc2019` (requires `w3c`) -secp384r1 = ["ssi-verification-methods/secp384r1", "p384"] +secp384r1 = ["ssi-data-integrity-core/secp384r1", "ssi-verification-methods/secp384r1", "p384"] ## Signature suites based on RSA. ## @@ -147,10 +147,11 @@ json-syntax = { workspace = true, features = ["canonicalize"] } serde_json = { workspace = true, optional = true } serde_jcs = { workspace = true, optional = true } -# BBS +# Selective disclosure. ssi-di-sd-primitives.workspace = true + +# BBS ssi-bbs.workspace = true -hmac = "0.12.1" serde_cbor = "0.11.2" # rand_chacha.workspace = true @@ -158,7 +159,6 @@ serde_cbor = "0.11.2" async-std = { version = "1.9", features = ["attributes"] } 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 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 8283beb32..de30b24c3 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>, + _verification_method: &VerificationMethod, _transformation_options: (), ) -> 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 dd61fb741..6b6ffa06a 100644 --- a/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c.rs @@ -38,6 +38,11 @@ pub mod ecdsa_rdfc_2019; #[cfg(any(feature = "secp256r1", feature = "secp384r1"))] pub use ecdsa_rdfc_2019::EcdsaRdfc2019; +#[cfg(feature = "secp256r1")] +pub mod ecdsa_sd_2023; +#[cfg(feature = "secp256r1")] +pub use ecdsa_sd_2023::EcdsaSd2023; + #[cfg(feature = "eip712")] pub mod ethereum_eip712_signature_2021; #[cfg(feature = "eip712")] 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 423b9e737..6c73071d5 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 @@ -1,7 +1,3 @@ -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}; @@ -9,7 +5,7 @@ use ssi_data_integrity_core::{DataIntegrity, Proof, ProofRef}; use ssi_di_sd_primitives::{ group::{canonicalize_and_group, GroupError}, select::{select_json_ld, DanglingJsonPointer}, - JsonPointerBuf, + HmacShaAnyKey, JsonPointerBuf, }; use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; use ssi_rdf::LexicalInterpretation; @@ -17,6 +13,7 @@ use ssi_verification_methods::{ multikey::{self, DecodedMultikey}, Multikey, }; +use std::{borrow::Cow, collections::HashMap, hash::Hash}; use crate::{bbs_2023::transformation::create_shuffled_id_label_map_function, Bbs2023}; @@ -185,7 +182,7 @@ where let decoded_base_proof = base_signature.decode_base()?; - let mut hmac = Hmac::::new_from_slice(&decoded_base_proof.hmac_key).unwrap(); + let mut hmac = HmacShaAnyKey::Sha256(decoded_base_proof.hmac_key).to_hmac(); let label_map_factory_function = create_shuffled_id_label_map_function(&mut hmac); 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 84ef85114..3de5d8b1c 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,7 +1,7 @@ //! Data Integrity BBS Cryptosuite 2023 (v1.0) implementation. //! //! See: -use serde::Serialize; +use serde::{Deserialize, Serialize}; use ssi_claims_core::ResolverProvider; use ssi_data_integrity_core::{ suite::{ @@ -11,7 +11,7 @@ use ssi_data_integrity_core::{ CryptosuiteStr, DataIntegrity, ProofConfiguration, ProofRef, StandardCryptographicSuite, Type, TypeRef, UnsupportedProofSuite, }; -use ssi_di_sd_primitives::{HmacKey, JsonPointerBuf}; +use ssi_di_sd_primitives::{HmacSha256Key, JsonPointerBuf}; use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; use ssi_rdf::LexicalInterpretation; use ssi_verification_methods::{Multikey, VerificationMethodResolver}; @@ -109,10 +109,11 @@ pub struct Bbs2023SignatureOptions { pub commitment_with_proof: Option>, - pub hmac_key: Option, + pub hmac_key: Option, } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub enum FeatureOption { #[default] Baseline, 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 a126a7dd6..74517c7b0 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 @@ -86,6 +86,7 @@ mod tests { use nquads_syntax::Parse; use ssi_data_integrity_core::{suite::standard::SignatureAlgorithm, ProofConfiguration}; + use ssi_di_sd_primitives::HmacSha256Key; use ssi_verification_methods::{ Multikey, ProofPurpose, ReferenceOrOwned, Signer, SingleSecretSigner, }; @@ -94,7 +95,7 @@ mod tests { use crate::{ bbs_2023::{ hashing::BaseHashData, transformation::TransformedBase, Bbs2023SignatureOptions, - FeatureOption, HashData, HmacKey, + FeatureOption, HashData, }, Bbs2023, }; @@ -151,7 +152,7 @@ _:b5 \"2023\"^^ key, None => { // Generate a random key - let mut key = HmacKey::default(); + let mut key = HmacSha256Key::default(); getrandom(&mut key).map_err(TransformationError::internal)?; key } }; - let mut hmac = Hmac::::new_from_slice(&hmac_key).unwrap(); + let mut hmac = HmacShaAnyKey::Sha256(hmac_key).to_hmac(); let mut group_definitions = HashMap::new(); group_definitions.insert( @@ -72,24 +70,9 @@ struct Mandatory; #[cfg(test)] mod tests { - use std::{borrow::Cow, collections::HashMap}; - - use hmac::{Hmac, Mac}; - use k256::sha2::Sha256; - use ssi_data_integrity_core::{ - suite::standard::TypedTransformationAlgorithm, ProofConfiguration, - }; - use ssi_di_sd_primitives::group::canonicalize_and_group; + use ssi_di_sd_primitives::{group::canonicalize_and_group, HmacSha256Key, HmacShaAnyKey}; use ssi_rdf::IntoNQuads; - use ssi_verification_methods::{ProofPurpose, ReferenceOrOwned}; - - use crate::{ - bbs_2023::{ - Bbs2023SignatureOptions, Bbs2023Transformation, Bbs2023TransformationOptions, - FeatureOption, HmacKey, - }, - Bbs2023, - }; + use std::{borrow::Cow, collections::HashMap}; use super::{super::super::tests::*, create_shuffled_id_label_map_function, Mandatory}; @@ -97,9 +80,9 @@ mod tests { async fn hmac_canonicalize_and_group() { let loader = ssi_json_ld::ContextLoader::default(); - let mut hmac_key = HmacKey::default(); + let mut hmac_key = HmacSha256Key::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 hmac = HmacShaAnyKey::Sha256(hmac_key).to_hmac(); let mut group_definitions = HashMap::new(); group_definitions.insert(Mandatory, Cow::Borrowed(MANDATORY_POINTERS.as_slice())); @@ -148,85 +131,4 @@ mod tests { assert_eq!(canonical.quads.into_nquads_lines(), EXPECTED_NQUADS) } - - #[async_std::test] - async fn transform_test() { - let context = ssi_claims_core::SignatureEnvironment::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( - &context, - &*UNSIGNED_BASE_DOCUMENT, - proof_configuration.borrowed(), - Bbs2023TransformationOptions::BaseSignature(Bbs2023SignatureOptions { - 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) - } - } } 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 47b29e51e..53fbc6849 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,16 +1,17 @@ -use super::{Bbs2023SignatureOptions, HmacKey}; +use super::Bbs2023SignatureOptions; use crate::Bbs2023; -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; +use ssi_di_sd_primitives::{ + canonicalize::create_hmac_id_label_map_function, HmacSha256Key, HmacShaAny, +}; use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation}; +use ssi_verification_methods::Multikey; use std::collections::HashMap; mod base; @@ -32,6 +33,7 @@ where context: &C, unsecured_document: &T, proof_configuration: ProofConfigurationRef<'_, Bbs2023>, + _verification_method: &Multikey, transformation_options: Bbs2023TransformationOptions, ) -> Result { let canonical_configuration = proof_configuration @@ -67,7 +69,7 @@ where /// /// See: pub fn create_shuffled_id_label_map_function( - hmac: &mut Hmac, + hmac: &mut HmacShaAny, ) -> impl '_ + FnMut(&NormalizingSubstitution) -> HashMap { |canonical_map| { let mut map = create_hmac_id_label_map_function(hmac)(canonical_map); @@ -109,7 +111,7 @@ pub struct TransformedBase { pub options: Bbs2023SignatureOptions, pub mandatory: Vec, pub non_mandatory: Vec, - pub hmac_key: HmacKey, + pub hmac_key: HmacSha256Key, pub canonical_configuration: Vec, } 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 f7ff118b8..83ab20a2e 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 @@ -38,8 +38,8 @@ impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { let disclosed_messages: Vec<_> = data .non_mandatory - .iter() - .map(|quad| format!("{quad} .\n").into_bytes()) + .into_iter() + .map(String::into_bytes) .collect(); match data.feature_option { @@ -61,7 +61,7 @@ impl VerificationAlgorithm for Bbs2023SignatureAlgorithm { struct VerifyData<'a> { base_signature: Vec, proof_hash: &'a [u8; 32], - non_mandatory: Vec, + non_mandatory: Vec, mandatory_hash: [u8; 32], selective_indexes: Vec, feature_option: DerivedFeatureOption, @@ -80,7 +80,7 @@ fn create_verify_data3<'a>( 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); + let mut canonical_quads = relabel_quads(&label_map, quads).into_nquads_lines(); canonical_quads.sort_unstable(); canonical_quads.dedup(); @@ -101,8 +101,6 @@ fn create_verify_data3<'a>( 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(); 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 2178db749..cd843cf19 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 @@ -1,14 +1,13 @@ //! `ecdsa-rdfc-2019` cryptosuite implementation. //! //! See: -use core::fmt; -use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}; +use ssi_crypto::algorithm::ES256OrES384; use ssi_data_integrity_core::{ canonicalization::{ CanonicalClaimsAndConfiguration, CanonicalizeClaimsAndConfiguration, HashCanonicalClaimsAndConfiguration, }, - signing::{AlgorithmSelection, AlgorithmSelectionError, Base58Btc, MultibaseSigning}, + signing::{Base58Btc, MultibaseSigning}, suite::{ standard::{HashingAlgorithm, HashingError}, NoConfiguration, @@ -100,82 +99,3 @@ impl AsRef<[u8]> for EcdsaRdfc2019Hash { } } } - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ES256OrES384 { - ES256, - ES384, -} - -impl ES256OrES384 { - pub fn name(&self) -> &'static str { - match self { - Self::ES256 => "ES256", - Self::ES384 => "ES384", - } - } -} - -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) - } -} - -impl AlgorithmSelection for ES256OrES384 { - fn select_algorithm( - verification_method: &Multikey, - _options: &O, - ) -> Result { - 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), - } - } -} - -impl From for ssi_jwk::Algorithm { - fn from(value: ES256OrES384) -> Self { - match value { - ES256OrES384::ES256 => Self::ES256, - ES256OrES384::ES384 => Self::ES384, - } - } -} - -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_sd_2023/configuration.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/configuration.rs new file mode 100644 index 000000000..a03ed173b --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/configuration.rs @@ -0,0 +1,55 @@ +use ssi_data_integrity_core::{ + suite::{ConfigurationError, InputSignatureOptions, InputVerificationOptions}, + ProofConfiguration, ProofOptions, +}; +use ssi_di_sd_primitives::{HmacShaAnyKey, JsonPointerBuf}; +use ssi_verification_methods::{multikey::MultikeyPair, Multikey}; + +use crate::EcdsaSd2023; + +use super::TransformationOptions; + +#[derive(Debug, Clone)] +pub struct SignatureOptions { + pub mandatory_pointers: Vec, + + pub hmac_key: Option, + + pub key_pair: Option, +} + +pub struct ConfigurationAlgorithm; + +impl ssi_data_integrity_core::suite::ConfigurationAlgorithm + for ConfigurationAlgorithm +{ + type InputVerificationMethod = Multikey; + + type InputSuiteOptions = (); + + type InputSignatureOptions = SignatureOptions; + + type InputVerificationOptions = (); + + type TransformationOptions = TransformationOptions; + + fn configure_signature( + suite: &EcdsaSd2023, + proof_options: ProofOptions, + signature_options: InputSignatureOptions, + ) -> Result<(ProofConfiguration, Self::TransformationOptions), ConfigurationError> + { + let proof_configuration = proof_options.into_configuration(*suite)?; + Ok(( + proof_configuration, + TransformationOptions::Base(signature_options), + )) + } + + fn configure_verification( + _suite: &EcdsaSd2023, + _verification_options: &InputVerificationOptions, + ) -> Result { + Ok(TransformationOptions::Derived) + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/derive.rs new file mode 100644 index 000000000..13442c2ee --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/derive.rs @@ -0,0 +1,210 @@ +use std::{borrow::Cow, collections::HashMap}; + +use rdf_types::{BlankIdBuf, Quad}; +use serde::Serialize; +use ssi_data_integrity_core::{DataIntegrity, Proof, ProofRef}; +use ssi_di_sd_primitives::{ + canonicalize::create_hmac_id_label_map_function, + group::{canonicalize_and_group, GroupError}, + select::{select_json_ld, DanglingJsonPointer}, + JsonPointerBuf, +}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; +use ssi_multicodec::MultiEncodedBuf; +use ssi_rdf::LexicalInterpretation; + +use crate::EcdsaSd2023; + +use super::{InvalidBaseSignature, Signature}; + +#[derive(Debug, thiserror::Error)] +pub enum DeriveError { + #[error("JSON serialization failed: {0}")] + JsonSerialization(#[from] json_syntax::SerializeError), + + #[error("expected JSON object")] + ExpectedJsonObject, + + #[error("invalid base signature")] + InvalidBaseSignature, + + #[error(transparent)] + Group(#[from] GroupError), + + #[error("dangling JSON pointer")] + DanglingJsonPointer, +} + +impl From for DeriveError { + fn from(_value: InvalidBaseSignature) -> Self { + Self::InvalidBaseSignature + } +} + +impl From for DeriveError { + fn from(_value: DanglingJsonPointer) -> Self { + Self::DanglingJsonPointer + } +} + +pub struct DeriveOptions { + pub selective_pointers: Vec, +} + +pub async fn add_derived_proof( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + options: DeriveOptions, + base_proof: ProofRef<'_, EcdsaSd2023>, +) -> Result, DeriveError> +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let data = create_disclosure_data( + loader, + unsecured_document, + base_proof.signature, + options.selective_pointers, + ) + .await?; + + let new_proof = Proof { + context: base_proof.context.cloned(), + type_: EcdsaSd2023, + 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: Signature::encode_derived( + &data.base_signature, + &data.public_key, + &data.signatures, + &data.label_map, + &data.mandatory_indexes, + ), + extra_properties: base_proof.extra_properties.clone(), + }; + + Ok(DataIntegrity::new(data.reveal_document, new_proof.into())) +} + +struct DisclosureData { + pub base_signature: Vec, + pub public_key: MultiEncodedBuf, + pub signatures: Vec>, + pub label_map: HashMap, + pub mandatory_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, + base_signature: &Signature, + selective_pointers: Vec, +) -> 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 = decoded_base_proof.hmac_key.to_hmac(); + + let label_map_factory_function = create_hmac_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 mut mandatory_indexes = Vec::with_capacity(mandatory_group.matching.len()); + for (relative_index, absolute_index) in combined_group.matching.keys().enumerate() { + // convert the absolute index to any mandatory quad to an index relative + // to the combined output that is to be revealed. + if mandatory_group.matching.contains_key(absolute_index) { + mandatory_indexes.push(relative_index); + } + } + + let mut index = 0; + let mut filtered_signatures = Vec::new(); + for signature in decoded_base_proof.signatures { + while mandatory_group.matching.contains_key(&index) { + index += 1 + } + + if selective_group.matching.contains_key(&index) { + filtered_signatures.push(signature) + } + + index += 1 + } + + 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 { + base_signature: decoded_base_proof.base_signature, + public_key: decoded_base_proof.public_key, + signatures: filtered_signatures, + label_map: verifier_label_map, + mandatory_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/ecdsa_sd_2023/hashing.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/hashing.rs new file mode 100644 index 000000000..001e610f3 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/hashing.rs @@ -0,0 +1,86 @@ +use ssi_data_integrity_core::{ + suite::standard::{self, HashingError}, + ProofConfigurationRef, +}; +use ssi_di_sd_primitives::{ShaAny, ShaAnyBytes}; +use ssi_rdf::{urdna2015::NormalizingSubstitution, IntoNQuads, LexicalQuad}; +use ssi_verification_methods::{multikey::DecodedMultikey, Multikey}; + +use crate::EcdsaSd2023; + +use super::{Transformed, TransformedBase, TransformedDerived}; + +pub struct HashingAlgorithm; + +#[derive(Debug, Clone)] +pub enum HashData { + Base(Box), + Derived(Box), +} + +#[derive(Debug, Clone)] +pub struct BaseHashData { + pub transformed_document: TransformedBase, + pub proof_hash: ShaAnyBytes, + pub mandatory_hash: ShaAnyBytes, +} + +#[derive(Debug, Clone)] +pub struct DerivedHashData { + pub canonical_configuration: Vec, + pub quads: Vec, + pub canonical_id_map: NormalizingSubstitution, + pub proof_hash: ShaAnyBytes, +} + +impl standard::HashingAlgorithm for HashingAlgorithm { + type Output = HashData; + + fn hash( + input: Transformed, + _proof_configuration: ProofConfigurationRef<'_, EcdsaSd2023>, + verification_method: &Multikey, + ) -> Result { + match input { + Transformed::Base(t) => { + let sha = t.hmac_key.algorithm(); + + let proof_hash = sha.hash_all(&t.canonical_configuration); + let mandatory_hash = sha.hash_all(t.mandatory.iter().into_nquads_lines()); + + Ok(HashData::Base(Box::new(BaseHashData { + transformed_document: t, + proof_hash, + mandatory_hash, + }))) + } + Transformed::Derived(t) => { + let decoded_key = verification_method + .public_key + .decode() + .map_err(|_| HashingError::InvalidKey)?; + + let sha = match decoded_key { + #[cfg(feature = "secp256r1")] + DecodedMultikey::P256(_) => ShaAny::Sha256, + #[cfg(feature = "secp384r1")] + DecodedMultikey::P384(_) => ShaAny::Sha384, + _ => return Err(HashingError::InvalidKey), + }; + + Ok(HashData::Derived(Box::new(create_verify_data2(t, sha)))) + } + } + } +} + +fn create_verify_data2(t: TransformedDerived, sha: ShaAny) -> DerivedHashData { + let proof_hash = sha.hash_all(&t.canonical_configuration); + + DerivedHashData { + canonical_configuration: t.canonical_configuration, + quads: t.quads, + canonical_id_map: t.canonical_id_map, + proof_hash, + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/mod.rs new file mode 100644 index 000000000..d6296b6af --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/mod.rs @@ -0,0 +1,84 @@ +use serde::Serialize; +use ssi_data_integrity_core::{ + suite::{CryptographicSuiteSelect, SelectionError, SelectiveCryptographicSuite}, + CryptosuiteStr, DataIntegrity, ProofRef, StandardCryptographicSuite, Type, TypeRef, + UnsupportedProofSuite, +}; + +mod configuration; +pub use configuration::*; + +mod transformation; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; +use ssi_rdf::LexicalInterpretation; +use ssi_verification_methods::Multikey; +pub use transformation::*; + +mod hashing; +pub use hashing::*; + +mod signature; +pub use signature::*; + +mod derive; +pub use derive::*; + +mod verification; + +/// The `ecdsa-sd-2023` cryptographic suite. +/// +/// See: +#[derive(Debug, Clone, Copy)] +pub struct EcdsaSd2023; + +impl StandardCryptographicSuite for EcdsaSd2023 { + type Configuration = ConfigurationAlgorithm; + + type Transformation = TransformationAlgorithm; + + type Hashing = HashingAlgorithm; + + type VerificationMethod = Multikey; + + type ProofOptions = (); + + type SignatureAlgorithm = SignatureAlgorithm; + + fn type_(&self) -> TypeRef { + TypeRef::DataIntegrityProof(CryptosuiteStr::new("ecdsa-sd-2023").unwrap()) + } +} + +impl SelectiveCryptographicSuite for EcdsaSd2023 { + type SelectionOptions = DeriveOptions; +} + +impl CryptographicSuiteSelect for EcdsaSd2023 +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, + P: JsonLdLoaderProvider, +{ + async fn select( + &self, + unsecured_document: &T, + proof: ProofRef<'_, Self>, + params: P, + options: Self::SelectionOptions, + ) -> Result, SelectionError> { + derive::add_derived_proof(params.loader(), unsecured_document, options, proof) + .await + .map_err(SelectionError::proof_derivation) + } +} + +impl TryFrom for EcdsaSd2023 { + type Error = UnsupportedProofSuite; + + fn try_from(value: Type) -> Result { + match value { + Type::DataIntegrityProof(c) if c == "ecdsa-sd-2023" => Ok(Self), + ty => Err(UnsupportedProofSuite::Compact(ty)), + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/base.rs new file mode 100644 index 000000000..99b9a079b --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/base.rs @@ -0,0 +1,211 @@ +use multibase::Base; +use ssi_claims_core::SignatureError; +use ssi_crypto::algorithm::ES256OrES384; +use ssi_di_sd_primitives::{HmacShaAnyKey, JsonPointerBuf, ShaAny, ShaAnyBytes}; +use ssi_multicodec::{MultiEncoded, MultiEncodedBuf}; +use ssi_rdf::IntoNQuads; +use ssi_security::MultibaseBuf; +use ssi_verification_methods::MessageSigner; + +use crate::ecdsa_sd_2023::BaseHashData; + +use super::Signature; + +#[derive(Debug, thiserror::Error)] +#[error("invalid base signature")] +pub struct InvalidBaseSignature; + +pub async fn generate_proof( + signer: T, + hash_data: BaseHashData, +) -> Result +where + T: MessageSigner, +{ + let proof_scoped_key_pair = match hash_data.transformed_document.options.key_pair { + Some(key_pair) => { + let (_, multi_encoded) = key_pair.secret.decode().map_err(SignatureError::other)?; + let multi_encoded = + MultiEncodedBuf::new(multi_encoded).map_err(SignatureError::other)?; + multi_encoded.decode().map_err(SignatureError::other)? + } + None => { + let mut rng = rand::thread_rng(); + + // Locally generated P-256 ECDSA key pair, scoped to the specific proof and + // destroyed with this algorithm terminates. + p256::SecretKey::random(&mut rng) + } + }; + + let public_key = proof_scoped_key_pair.public_key(); + let signing_key: p256::ecdsa::SigningKey = proof_scoped_key_pair.into(); + + let signatures: Vec<[u8; 64]> = hash_data + .transformed_document + .non_mandatory + .into_nquads_lines() + .into_iter() + .map(|line| { + use p256::ecdsa::{signature::Signer, Signature}; + // Sha256::digest(line).into() + let signature: Signature = signing_key.sign(line.as_bytes()); + signature.to_bytes().into() + }) + .collect(); + + let encoded_public_key: MultiEncodedBuf = MultiEncodedBuf::encode(&public_key); + + let to_sign = serialize_sign_data( + &hash_data.proof_hash, + &hash_data.mandatory_hash, + &encoded_public_key, + ); + + let algorithm = match hash_data.transformed_document.hmac_key.algorithm() { + ShaAny::Sha256 => ES256OrES384::ES256, + ShaAny::Sha384 => ES256OrES384::ES384, + }; + + let base_signature = signer.sign(algorithm, &to_sign).await?; + + Ok(Signature::encode_base( + &base_signature, + &encoded_public_key, + hash_data.transformed_document.hmac_key, + &signatures, + &hash_data.transformed_document.options.mandatory_pointers, + )) +} + +/// Serialize sign data. +/// +/// See: +pub(crate) fn serialize_sign_data( + proof_hash: &ShaAnyBytes, + mandatory_hash: &ShaAnyBytes, + public_key: &MultiEncoded, +) -> Vec { + let mut bytes = Vec::with_capacity(proof_hash.len() + mandatory_hash.len() + public_key.len()); + bytes.extend(proof_hash.as_slice()); + bytes.extend(public_key.as_bytes()); + bytes.extend(mandatory_hash.as_slice()); + bytes +} + +impl Signature { + pub fn encode_base( + base_signature: &[u8], + public_key: &MultiEncoded, + hmac_key: HmacShaAnyKey, + signatures: &[[u8; 64]], + mandatory_pointers: &[JsonPointerBuf], + ) -> Self { + let components = vec![ + serde_cbor::Value::Bytes(base_signature.to_vec()), + serde_cbor::Value::Bytes(public_key.as_bytes().to_vec()), + serde_cbor::Value::Bytes(hmac_key.to_vec()), + serde_cbor::Value::Array(signatures.iter().map(|p| p.to_vec().into()).collect()), + serde_cbor::Value::Array( + mandatory_pointers + .iter() + .map(|p| p.as_str().to_owned().into()) + .collect(), + ), + ]; + + let mut proof_value = vec![0xd9, 0x5d, 0x00]; + serde_cbor::to_writer(&mut proof_value, &components).unwrap(); + + Self { + proof_value: MultibaseBuf::encode(multibase::Base::Base64Url, proof_value), + } + } + + pub fn decode_base(&self) -> Result { + let (base, decoded_proof_value) = self + .proof_value + .decode() + .map_err(|_| InvalidBaseSignature)?; + + if base != Base::Base64Url || decoded_proof_value.len() < 3 { + return Err(InvalidBaseSignature); + } + + let header = [ + decoded_proof_value[0], + decoded_proof_value[1], + decoded_proof_value[2], + ]; + + if header != [0xd9, 0x5d, 0x00] { + return Err(InvalidBaseSignature); + } + + let mut components = + serde_cbor::from_slice::>(&decoded_proof_value[3..]) + .map_err(|_| InvalidBaseSignature)? + .into_iter(); + + let Some(serde_cbor::Value::Bytes(base_signature)) = components.next() else { + return Err(InvalidBaseSignature); + }; + + let Some(serde_cbor::Value::Bytes(public_key_bytes)) = components.next() else { + return Err(InvalidBaseSignature); + }; + + let public_key = + MultiEncodedBuf::new(public_key_bytes).map_err(|_| InvalidBaseSignature)?; + + let Some(serde_cbor::Value::Bytes(hmac_key_bytes)) = components.next() else { + return Err(InvalidBaseSignature); + }; + + let hmac_key = + HmacShaAnyKey::from_bytes(&hmac_key_bytes).map_err(|_| InvalidBaseSignature)?; + + let Some(serde_cbor::Value::Array(signatures_values)) = components.next() else { + return Err(InvalidBaseSignature); + }; + + let mut signatures = Vec::with_capacity(signatures_values.len()); + for value in signatures_values { + let serde_cbor::Value::Bytes(bytes) = value else { + return Err(InvalidBaseSignature); + }; + + signatures.push(bytes) + } + + let Some(serde_cbor::Value::Array(mandatory_pointers_values)) = components.next() else { + return Err(InvalidBaseSignature); + }; + + let mut mandatory_pointers = Vec::with_capacity(mandatory_pointers_values.len()); + for value in mandatory_pointers_values { + let serde_cbor::Value::Text(text) = value else { + return Err(InvalidBaseSignature); + }; + + mandatory_pointers.push(JsonPointerBuf::new(text).map_err(|_| InvalidBaseSignature)?) + } + + Ok(DecodedBaseProof { + base_signature, + public_key, + hmac_key, + signatures, + mandatory_pointers, + }) + } +} + +#[derive(Clone)] +pub struct DecodedBaseProof { + pub base_signature: Vec, + pub public_key: MultiEncodedBuf, + pub hmac_key: HmacShaAnyKey, + pub signatures: Vec>, + pub mandatory_pointers: Vec, +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/derived.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/derived.rs new file mode 100644 index 000000000..73faf0489 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/derived.rs @@ -0,0 +1,181 @@ +use multibase::Base; +use rdf_types::BlankIdBuf; +use ssi_claims_core::ProofValidationError; +use ssi_multicodec::{MultiEncoded, MultiEncodedBuf}; +use ssi_security::MultibaseBuf; +use std::collections::{BTreeMap, HashMap}; + +use super::Signature; + +#[derive(Debug, thiserror::Error)] +#[error("invalid derived signature")] +pub struct InvalidDerivedSignature; + +impl From for ProofValidationError { + fn from(_value: InvalidDerivedSignature) -> Self { + Self::InvalidProof + } +} + +impl Signature { + pub fn encode_derived( + base_signature: &[u8], + public_key: &MultiEncoded, + signatures: &[Vec], + label_map: &HashMap, + mandatory_indexes: &[usize], + ) -> Self { + let components = vec![ + serde_cbor::Value::Bytes(base_signature.to_vec()), + serde_cbor::Value::Bytes(public_key.as_bytes().to_vec()), + serde_cbor::Value::Array(signatures.iter().map(|s| s.to_vec().into()).collect()), + serde_cbor::Value::Map(compress_label_map(label_map)), + serde_cbor::Value::Array( + mandatory_indexes + .iter() + .map(|&i| serde_cbor::Value::Integer(i as i128)) + .collect(), + ), + ]; + + let mut proof_value = vec![0xd9, 0x5d, 0x01]; + serde_cbor::to_writer(&mut proof_value, &components).unwrap(); + + Self { + proof_value: MultibaseBuf::encode(Base::Base64Url, proof_value), + } + } + + pub fn decode_derived(&self) -> Result { + let (base, decoded_proof_value) = self + .proof_value + .decode() + .map_err(|_| InvalidDerivedSignature)?; + + if base != Base::Base64Url || decoded_proof_value.len() < 3 { + return Err(InvalidDerivedSignature); + } + + let header = [ + decoded_proof_value[0], + decoded_proof_value[1], + decoded_proof_value[2], + ]; + + if header != [0xd9, 0x5d, 0x01] { + return Err(InvalidDerivedSignature); + } + + let mut components = + serde_cbor::from_slice::>(&decoded_proof_value[3..]) + .map_err(|_| InvalidDerivedSignature)? + .into_iter(); + + let Some(serde_cbor::Value::Bytes(signature_bytes)) = components.next() else { + return Err(InvalidDerivedSignature); + }; + + let Some(serde_cbor::Value::Bytes(public_key_bytes)) = components.next() else { + return Err(InvalidDerivedSignature); + }; + + let public_key = + MultiEncodedBuf::new(public_key_bytes).map_err(|_| InvalidDerivedSignature)?; + + let Some(serde_cbor::Value::Array(signatures_values)) = components.next() else { + return Err(InvalidDerivedSignature); + }; + + let mut signatures = Vec::with_capacity(signatures_values.len()); + for value in signatures_values { + let serde_cbor::Value::Bytes(signature) = value else { + return Err(InvalidDerivedSignature); + }; + + signatures.push(signature) + } + + let Some(serde_cbor::Value::Map(compressed_label_map)) = components.next() else { + return Err(InvalidDerivedSignature); + }; + + let label_map = decompress_label_map(&compressed_label_map)?; + + let Some(serde_cbor::Value::Array(mandatory_indexes)) = components.next() else { + return Err(InvalidDerivedSignature); + }; + + let mandatory_indexes = decode_indexes(mandatory_indexes)?; + + Ok(DecodedDerivedProof { + base_signature: signature_bytes, + public_key, + signatures, + label_map, + mandatory_indexes, + }) + } +} + +pub struct DecodedDerivedProof { + pub base_signature: Vec, + pub public_key: MultiEncodedBuf, + pub signatures: Vec>, + pub label_map: HashMap, + pub mandatory_indexes: Vec, +} + +fn compress_label_map( + label_map: &HashMap, +) -> BTreeMap { + let mut map = BTreeMap::new(); + + for (k, v) in label_map { + let ki = k.strip_prefix("_:c14n").unwrap().parse().unwrap(); + let vb = Base::Base64Url + .decode(v.strip_prefix("_:u").unwrap()) + .unwrap(); + map.insert(serde_cbor::Value::Integer(ki), serde_cbor::Value::Bytes(vb)); + } + + map +} + +fn decompress_label_map( + compressed_label_map: &BTreeMap, +) -> Result, InvalidDerivedSignature> { + let mut map = HashMap::new(); + + for (ki, vb) in compressed_label_map { + let serde_cbor::Value::Integer(ki) = ki else { + return Err(InvalidDerivedSignature); + }; + + let serde_cbor::Value::Bytes(vb) = vb else { + return Err(InvalidDerivedSignature); + }; + + let k = BlankIdBuf::new(format!("_:c14n{ki}")).unwrap(); + let v = BlankIdBuf::new(format!("_:u{}", Base::Base64Url.encode(vb))).unwrap(); + + map.insert(k, v); + } + + Ok(map) +} + +fn decode_indexes( + encoded_indexes: Vec, +) -> Result, InvalidDerivedSignature> { + let mut indexes = Vec::with_capacity(encoded_indexes.len()); + + for v in encoded_indexes { + let serde_cbor::Value::Integer(i) = v else { + return Err(InvalidDerivedSignature); + }; + + indexes.push(i.try_into().map_err(|_| InvalidDerivedSignature)?) + } + + Ok(indexes) +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/mod.rs new file mode 100644 index 000000000..8079e8a99 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/signature/mod.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; +use ssi_claims_core::SignatureError; +use ssi_crypto::algorithm::ES256OrES384; +use ssi_data_integrity_core::{ + signing::AlterSignature, + suite::standard::{self, SignatureAndVerificationAlgorithm}, + ProofConfigurationRef, +}; +use ssi_security::MultibaseBuf; +use ssi_verification_methods::{MessageSigner, Multikey}; + +use crate::EcdsaSd2023; + +use super::HashData; + +mod base; +mod derived; + +pub use base::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Signature { + pub proof_value: MultibaseBuf, +} + +impl AsRef for Signature { + fn as_ref(&self) -> &str { + self.proof_value.as_str() + } +} + +impl AlterSignature for Signature { + fn alter(&mut self) { + self.proof_value = MultibaseBuf::encode(multibase::Base::Base58Btc, [0]) + } +} + +pub struct SignatureAlgorithm; + +impl SignatureAndVerificationAlgorithm for SignatureAlgorithm { + type Signature = Signature; +} + +impl standard::SignatureAlgorithm for SignatureAlgorithm +where + T: MessageSigner, +{ + async fn sign( + _verification_method: &Multikey, + signer: T, + prepared_claims: HashData, + _proof_configuration: ProofConfigurationRef<'_, EcdsaSd2023>, + ) -> Result { + match prepared_claims { + HashData::Base(hash_data) => base::generate_proof(signer, *hash_data).await, + HashData::Derived(_) => Err(SignatureError::other( + "unable to sign derived claims without a base proof", + )), + } + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/base.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/base.rs new file mode 100644 index 000000000..a2511875e --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/base.rs @@ -0,0 +1,92 @@ +use ssi_data_integrity_core::suite::standard::TransformationError; +use ssi_di_sd_primitives::{ + canonicalize::create_hmac_id_label_map_function, group::canonicalize_and_group, HmacShaAnyKey, + IntoHmacError, ShaAny, +}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject}; +use ssi_rdf::{LexicalInterpretation, LexicalQuad}; +use ssi_verification_methods::{multikey::DecodedMultikey, Multikey}; +use std::{borrow::Cow, collections::HashMap}; + +use crate::ecdsa_sd_2023::SignatureOptions; + +#[derive(Debug, Clone)] +pub struct TransformedBase { + pub options: SignatureOptions, + pub mandatory: Vec, + pub non_mandatory: Vec, + pub hmac_key: HmacShaAnyKey, + pub canonical_configuration: Vec, +} + +/// Base Proof Transformation. +/// +/// See: +pub async fn base_proof_transformation( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + canonical_configuration: Vec, + verification_method: &Multikey, + transform_options: SignatureOptions, +) -> Result +where + T: JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let decoded_key = verification_method + .public_key + .decode() + .map_err(|_| TransformationError::InvalidKey)?; + + let sha = match decoded_key { + #[cfg(feature = "secp256r1")] + DecodedMultikey::P256(_) => ShaAny::Sha256, + #[cfg(feature = "secp384r1")] + DecodedMultikey::P384(_) => ShaAny::Sha384, + _ => return Err(TransformationError::InvalidKey), + }; + + let hmac_key = sha + .into_key(transform_options.hmac_key) + .map_err(hmac_error)?; + let mut hmac = hmac_key.to_hmac(); + + let label_map_factory_function = create_hmac_id_label_map_function(&mut hmac); + + let mut group_definitions = HashMap::new(); + group_definitions.insert( + Mandatory, + Cow::Borrowed(transform_options.mandatory_pointers.as_slice()), + ); + + let mut canonical = canonicalize_and_group( + loader, + label_map_factory_function, + group_definitions, + unsecured_document, + ) + .await + .map_err(TransformationError::internal)?; + + let mandatory_group = canonical.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)] +pub struct Mandatory; + +fn hmac_error(e: IntoHmacError) -> TransformationError { + match e { + IntoHmacError::IncompatibleKey => TransformationError::InvalidKey, + IntoHmacError::RandomGenerationFailed(e) => TransformationError::internal(e), + } +} diff --git a/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/derive.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/derive.rs new file mode 100644 index 000000000..02b1d9c8c --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/derive.rs @@ -0,0 +1,47 @@ +// use ssi_data_integrity_core::suite::standard::TransformationError; + +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::{ + urdna2015::NormalizingSubstitution, LdEnvironment, LexicalInterpretation, LexicalQuad, +}; + +pub struct TransformedDerived { + pub canonical_configuration: Vec, + pub quads: Vec, + pub canonical_id_map: NormalizingSubstitution, +} + +pub async fn create_verify_data1( + loader: &impl ssi_json_ld::Loader, + unsecured_document: &T, + canonical_configuration: Vec, +) -> Result +where + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + let mut ld = LdEnvironment::default(); + + 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 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/ecdsa_sd_2023/transformation/mod.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/mod.rs new file mode 100644 index 000000000..852c88347 --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/transformation/mod.rs @@ -0,0 +1,74 @@ +use serde::Serialize; +use ssi_data_integrity_core::{ + suite::standard::{self, TransformationError}, + ProofConfigurationRef, +}; +use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject}; +use ssi_rdf::LexicalInterpretation; +use ssi_verification_methods::Multikey; + +use crate::EcdsaSd2023; + +mod base; +mod derive; + +pub use base::TransformedBase; +pub use derive::TransformedDerived; + +use super::SignatureOptions; + +pub enum TransformationOptions { + Base(SignatureOptions), + Derived, +} + +pub enum Transformed { + Base(TransformedBase), + Derived(TransformedDerived), +} + +pub struct TransformationAlgorithm; + +impl standard::TransformationAlgorithm for TransformationAlgorithm { + type Output = Transformed; +} + +impl standard::TypedTransformationAlgorithm for TransformationAlgorithm +where + C: JsonLdLoaderProvider, + T: Serialize + JsonLdNodeObject + Expandable, + T::Expanded: Into, +{ + async fn transform( + context: &C, + unsecured_document: &T, + proof_configuration: ProofConfigurationRef<'_, EcdsaSd2023>, + verification_method: &Multikey, + transformation_options: TransformationOptions, + ) -> Result { + let canonical_configuration = proof_configuration + .expand(context, unsecured_document) + .await + .map_err(TransformationError::ProofConfigurationExpansion)? + .nquads_lines(); + + match transformation_options { + TransformationOptions::Base(signature_options) => base::base_proof_transformation( + context.loader(), + unsecured_document, + canonical_configuration, + verification_method, + signature_options, + ) + .await + .map(Transformed::Base), + TransformationOptions::Derived => derive::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/ecdsa_sd_2023/verification.rs b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/verification.rs new file mode 100644 index 000000000..ca5e7d96b --- /dev/null +++ b/crates/claims/crates/data-integrity/suites/src/suites/w3c/ecdsa_sd_2023/verification.rs @@ -0,0 +1,130 @@ +use ssi_claims_core::{InvalidProof, ProofValidationError, ProofValidity}; +use ssi_crypto::algorithm::ES256OrES384; +use ssi_data_integrity_core::{suite::standard::VerificationAlgorithm, ProofRef}; +use ssi_di_sd_primitives::{ + canonicalize::{create_label_map_function, relabel_quads}, + ShaAny, ShaAnyBytes, +}; +use ssi_multicodec::MultiEncodedBuf; +use ssi_rdf::{urdna2015::NormalizingSubstitution, IntoNQuads, LexicalQuad}; +use ssi_verification_methods::{Multikey, VerifyBytes}; + +use crate::{ecdsa_sd_2023::serialize_sign_data, EcdsaSd2023}; + +use super::{HashData, SignatureAlgorithm}; + +impl VerificationAlgorithm for SignatureAlgorithm { + fn verify( + method: &Multikey, + prepared_claims: HashData, + proof: ProofRef<'_, EcdsaSd2023>, + ) -> Result { + match prepared_claims { + HashData::Base(_) => Err(ProofValidationError::other( + "selective disclosure base proof", + )), + HashData::Derived(t) => { + use p256::ecdsa::signature::Verifier; + + let data = + create_verify_data3(&t.proof_hash, &t.canonical_id_map, &t.quads, proof)?; + + if data.signatures.len() != data.non_mandatory.len() { + return Err(ProofValidationError::InvalidSignature); + } + + let to_verify = + serialize_sign_data(data.proof_hash, &data.mandatory_hash, &data.public_key); + + let algorithm = match data.proof_hash { + ShaAnyBytes::Sha256(_) => ES256OrES384::ES256, + ShaAnyBytes::Sha384(_) => ES256OrES384::ES384, + }; + + if method + .verify_bytes(algorithm, &to_verify, &data.base_signature)? + .is_err() + { + return Ok(ProofValidity::Err(InvalidProof::Signature)); + } + + let public_key: p256::PublicKey = data + .public_key + .decode() + .map_err(|_| ProofValidationError::InvalidSignature)?; + let verifying_key: p256::ecdsa::VerifyingKey = public_key.into(); + + for (i, quad_signature) in data.signatures.into_iter().enumerate() { + let quad = data + .non_mandatory + .get(i) + .ok_or(ProofValidationError::InvalidProof)?; + + let quad_signature = p256::ecdsa::Signature::from_slice(&quad_signature) + .map_err(|_| ProofValidationError::InvalidProof)?; + + if verifying_key + .verify(quad.as_bytes(), &quad_signature) + .is_err() + { + return Ok(ProofValidity::Err(InvalidProof::Signature)); + } + } + + Ok(ProofValidity::Ok(())) + } + } + } +} + +struct VerifyData<'a> { + base_signature: Vec, + proof_hash: &'a ShaAnyBytes, + public_key: MultiEncodedBuf, + signatures: Vec>, + non_mandatory: Vec, + mandatory_hash: ShaAnyBytes, +} + +/// See: +fn create_verify_data3<'a>( + proof_hash: &'a ShaAnyBytes, + 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).into_nquads_lines(); + 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: ShaAnyBytes = ShaAny::Sha256.hash_all(&mandatory); + + Ok(VerifyData { + base_signature: decoded_signature.base_signature, + proof_hash, + public_key: decoded_signature.public_key, + signatures: decoded_signature.signatures, + non_mandatory, + mandatory_hash, + }) +} 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 e32bae29b..a763c333a 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 @@ -274,6 +274,7 @@ where context: &C, data: &T, proof_configuration: ProofConfigurationRef<'_, S>, + _verification_method: &S::VerificationMethod, _transformation_options: TransformationOptions, ) -> Result { let types = match proof_configuration.options.types() { diff --git a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/input.jsonld b/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/input.jsonld deleted file mode 100644 index 8e198e382..000000000 --- a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/input.jsonld +++ /dev/null @@ -1,16 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://www.w3.org/ns/credentials/examples/v2" - ], - "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", - "type": ["VerifiableCredential", "AlumniCredential"], - "name": "Alumni Credential", - "description": "A minimum viable example of an Alumni Credential.", - "issuer": "https://vc.example/issuers/5678", - "validFrom": "2023-01-01T00:00:00Z", - "credentialSubject": { - "id": "did:example:abcdefgh", - "alumniOf": "The School of Examples" - } -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/keys.json b/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/keys.json deleted file mode 100644 index b7c56fb60..000000000 --- a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/keys.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", - "privateKeyMultibase": "z42twTcNeSYcnqg1FLuSFs2bsGH3ZqbRHFmvS9XMsYhjxvHN" -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/options.json b/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/options.json deleted file mode 100644 index 2a30b01cb..000000000 --- a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/options.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "DataIntegrityProof", - "cryptosuite": "ecdsa-rdfc-2019", - "created": "2023-02-24T23:36:38Z", - "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", - "proofPurpose": "assertionMethod", - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://www.w3.org/ns/credentials/examples/v2" - ] -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/output.jsonld b/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/output.jsonld deleted file mode 100644 index 870fc030d..000000000 --- a/crates/claims/crates/data-integrity/suites/tests/ecdsa_rdfc_2019/output.jsonld +++ /dev/null @@ -1,27 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://www.w3.org/ns/credentials/examples/v2" - ], - "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", - "type": [ - "VerifiableCredential", - "AlumniCredential" - ], - "name": "Alumni Credential", - "description": "A minimum viable example of an Alumni Credential.", - "issuer": "https://vc.example/issuers/5678", - "validFrom": "2023-01-01T00:00:00Z", - "credentialSubject": { - "id": "did:example:abcdefgh", - "alumniOf": "The School of Examples" - }, - "proof": { - "type": "DataIntegrityProof", - "cryptosuite": "ecdsa-rdfc-2019", - "created": "2023-02-24T23:36:38Z", - "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", - "proofPurpose": "assertionMethod", - "proofValue": "z4KKHqaD4F7GHyLA6f3wK9Ehxtogv5jQRFpQBM4sXkSf7Bozd7bAf7dF6UkfM2aSCBMm24mPvaFXmzQmimzaEC3SL" - } -} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/suites/tests/suite.rs b/crates/claims/crates/data-integrity/suites/tests/suite.rs deleted file mode 100644 index 3a95f7a77..000000000 --- a/crates/claims/crates/data-integrity/suites/tests/suite.rs +++ /dev/null @@ -1,239 +0,0 @@ -#![allow(unused)] -use std::{borrow::Cow, path::Path}; - -use hashbrown::HashMap; -use iref::UriBuf; -use serde::{de::DeserializeOwned, Deserialize}; -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::{ - MessageSigner, ReferenceOrOwnedRef, ResolutionOptions, VerificationMethodResolutionError, - VerificationMethodResolver, -}; - -fn load_json(path: impl AsRef) -> serde_json::Value { - let buffer = std::fs::read_to_string(path).unwrap(); - serde_json::from_str(&buffer).unwrap() -} - -fn load_unsecured_vc(path: impl AsRef) -> DataIntegrityDocument { - let buffer = std::fs::read_to_string(path).unwrap(); - serde_json::from_str(&buffer).unwrap() -} - -// fn load_secured_vc(path: impl AsRef) -> DataIntegrity { -// let buffer = std::fs::read_to_string(path).unwrap(); -// serde_json::from_str(&buffer).unwrap() -// } - -fn load_options(path: impl AsRef) -> ProofOptions -where - M: DeserializeOwned, - T: DeserializeOwned, -{ - let buffer = std::fs::read_to_string(path).unwrap(); - let mut options: ProofOptions = serde_json::from_str(&buffer).unwrap(); - options.context = None; - options -} - -#[derive(Deserialize)] -struct MultikeyPair { - #[serde(rename = "publicKeyMultibase")] - public: MultibaseBuf, - - #[serde(rename = "privateKeyMultibase")] - private: MultibaseBuf, -} - -impl MultikeyPair { - fn load(path: impl AsRef) -> Self { - let buffer = std::fs::read_to_string(path).unwrap(); - serde_json::from_str(&buffer).unwrap() - } -} - -#[derive(Default)] -struct MultikeyRing { - map: HashMap, -} - -impl MultikeyRing { - fn insert(&mut self, key_pair: MultikeyPair) { - self.map.insert(key_pair.public, key_pair.private); - } -} - -#[cfg(feature = "w3c")] -impl VerificationMethodResolver for MultikeyRing { - type Method = ssi_verification_methods::Multikey; - - async fn resolve_verification_method_with( - &self, - _issuer: Option<&iref::Iri>, - method: Option>, - _options: ResolutionOptions, - ) -> Result, VerificationMethodResolutionError> { - match method { - Some(ReferenceOrOwnedRef::Owned(method)) => Ok(Cow::Owned(method.clone())), - Some(ReferenceOrOwnedRef::Reference(id)) => match id.fragment() { - Some(fragment) => { - let public_key = MultibaseBuf::new(fragment.to_owned().into_string()); - let fragment_start = id.len() - fragment.len() - 1; - let controller = &id[..fragment_start]; - Ok(Cow::Owned(ssi_verification_methods::Multikey { - id: id.to_owned(), - controller: UriBuf::new(controller.to_owned().into_bytes()).unwrap(), - public_key: public_key.into(), - })) - } - None => Err(VerificationMethodResolutionError::UnknownKey), - }, - None => Err(VerificationMethodResolutionError::UnknownKey), - } - } -} - -#[cfg(feature = "w3c")] -impl ssi_verification_methods::Signer for MultikeyRing { - type MessageSigner = PrivateKey; - - async fn for_method( - &self, - method: Cow<'_, ssi_verification_methods::Multikey>, - ) -> Result, SignatureError> { - method - .id - .fragment() - .and_then(|id| self.map.get(id.as_str())) - .map(MultibaseBuf::as_multibase) - .map(PrivateKey::from_multibase) - .transpose() - .map_err(SignatureError::other) - } -} - -enum PrivateKey { - #[cfg(feature = "secp256r1")] - P256(p256::SecretKey), - - #[cfg(feature = "secp384r1")] - P384(p384::SecretKey), -} - -#[cfg(all(feature = "w3c", any(feature = "secp256r1", feature = "secp384r1")))] -impl MessageSigner for PrivateKey { - async fn sign( - self, - algorithm: ssi_data_integrity_suites::ecdsa_rdfc_2019::ES256OrES384, - message: &[u8], - ) -> Result, MessageSignatureError> { - match algorithm { - #[cfg(feature = "secp256r1")] - ssi_data_integrity_suites::ecdsa_rdfc_2019::ES256OrES384::ES256 => { - #[allow(irrefutable_let_patterns)] - if let Self::P256(secret_key) = self { - use p256::ecdsa::{signature::Signer, Signature}; - let signing_key = p256::ecdsa::SigningKey::from(secret_key); - let sig: Signature = signing_key.try_sign(message).unwrap(); // Uses SHA-256 by default. - Ok(sig.to_bytes().to_vec()) - } else { - Err(MessageSignatureError::InvalidSecretKey) - } - } - #[cfg(feature = "secp384r1")] - ssi_data_integrity_suites::ecdsa_rdfc_2019::ES256OrES384::ES384 => { - #[allow(irrefutable_let_patterns)] - if let Self::P384(secret_key) = self { - use p384::ecdsa::{signature::Signer, Signature}; - let signing_key = p384::ecdsa::SigningKey::from(secret_key); - let sig: Signature = signing_key.try_sign(message).unwrap(); // Uses SHA-256 by default. - Ok(sig.to_bytes().to_vec()) - } else { - Err(MessageSignatureError::InvalidSecretKey) - } - } - #[allow(unreachable_patterns)] - a => Err(MessageSignatureError::UnsupportedAlgorithm(a.to_string())), - } - } -} - -#[derive(Debug, thiserror::Error)] -enum PrivateKeyDecodeError { - #[error("multibase: {0}")] - Multibase(#[from] multibase::Error), - - #[error("multicodec: {0}")] - Multicodec(#[from] ssi_multicodec::Error), - - #[error("unsupported codec 0x{0:2x}")] - Unsupported(u64), - - #[cfg(feature = "secp256r1")] - #[error("P-256: {0}")] - P256(p256::elliptic_curve::Error), - - #[cfg(feature = "secp384r1")] - #[error("P-384: {0}")] - P384(p384::elliptic_curve::Error), -} - -impl PrivateKey { - fn from_multibase(multibase: &Multibase) -> Result { - let (_, decoded) = multibase.decode()?; - let multi_encoded = MultiEncodedBuf::new(decoded)?; - let (codec, data) = multi_encoded.parts(); - - match codec { - #[cfg(feature = "secp256r1")] - ssi_multicodec::P256_PRIV => p256::SecretKey::from_slice(data) - .map(Self::P256) - .map_err(PrivateKeyDecodeError::P256), - - #[cfg(feature = "secp384r1")] - ssi_multicodec::P384_PRIV => p384::SecretKey::from_slice(data) - .map(Self::P384) - .map_err(PrivateKeyDecodeError::P384), - - c => Err(PrivateKeyDecodeError::Unsupported(c)), - } - } -} - -#[cfg(all(feature = "w3c", feature = "secp256r1"))] -#[async_std::test] -async fn test_ecdsa_rdfc_2019() { - use ssi_claims_core::VerificationParameters; - use ssi_data_integrity_suites::EcdsaRdfc2019; - let input = load_unsecured_vc("tests/ecdsa_rdfc_2019/input.jsonld"); - let expected_output = load_json("tests/ecdsa_rdfc_2019/output.jsonld"); - let options = load_options("tests/ecdsa_rdfc_2019/options.json"); - let key_pair = MultikeyPair::load("tests/ecdsa_rdfc_2019/keys.json"); - - let mut keys = MultikeyRing::default(); - keys.insert(key_pair); - - let vc = EcdsaRdfc2019 - .sign(input, &keys, &keys, options) - .await - .unwrap(); - - let output = serde_json::to_value(&vc).unwrap(); - - eprintln!( - "output = {}", - serde_json::to_string_pretty(&output).unwrap() - ); - eprintln!( - "expected = {}", - serde_json::to_string_pretty(&expected_output).unwrap() - ); - - assert_eq!(output, expected_output); - - let params = VerificationParameters::from_resolver(&keys); - assert!(vc.verify(params).await.unwrap().is_ok()); -} diff --git a/crates/claims/crates/data-integrity/tests/common/mod.rs b/crates/claims/crates/data-integrity/tests/common/mod.rs new file mode 100644 index 000000000..edfcdc52d --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/common/mod.rs @@ -0,0 +1,50 @@ +#![allow(unused)] +use json_syntax::Parse; +use serde::Deserialize; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +mod selection; +mod signature; +mod verification; + +pub use selection::*; +pub use signature::*; +pub use verification::*; + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum Test { + #[serde(rename = "SignatureTest")] + Signature(SignatureTest), + + #[serde(rename = "SelectionTest")] + Selection(SelectionTest), + + #[serde(rename = "VerificationTest")] + Verification(VerificationTest), +} + +fn test_path(path: impl AsRef) -> PathBuf { + let mut result: PathBuf = format!("{}/tests", env!("CARGO_MANIFEST_DIR")).into(); + result.extend(path.as_ref()); + result +} + +impl Test { + pub fn load(path: impl AsRef) -> Self { + let content = fs::read_to_string(test_path(path)).unwrap(); + let json = json_syntax::Value::parse_str(&content).unwrap().0; + json_syntax::from_value(json).unwrap() + } + + pub async fn run(self) { + match self { + Self::Signature(test) => test.run().await, + Self::Selection(test) => test.run().await, + Self::Verification(test) => test.run().await, + } + } +} diff --git a/crates/claims/crates/data-integrity/tests/common/selection.rs b/crates/claims/crates/data-integrity/tests/common/selection.rs new file mode 100644 index 000000000..7a53067b1 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/common/selection.rs @@ -0,0 +1,41 @@ +#![allow(unused)] +use std::collections::HashMap; + +use iref::IriBuf; +use json_syntax::Print; +use serde::Deserialize; +use ssi_claims_core::VerificationParameters; +use ssi_data_integrity::{AnyDataIntegrity, AnySelectionOptions}; +use ssi_verification_methods::AnyMethod; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SelectionTest { + pub id: Option, + pub verification_methods: HashMap, + pub options: AnySelectionOptions, + pub input: AnyDataIntegrity, + pub expected_output: json_syntax::Value, +} + +impl SelectionTest { + pub async fn run(mut self) { + let params = VerificationParameters::from_resolver(self.verification_methods); + + let vc = self.input.select(params, self.options).await.unwrap(); + + let mut json = json_syntax::to_value(vc).unwrap(); + json.canonicalize(); + + self.expected_output.canonicalize(); + + if json != self.expected_output { + eprintln!("expected: {}", self.expected_output.pretty_print()); + eprintln!("found: {}", json.pretty_print()); + match self.id { + Some(id) => panic!("test <{}> failed", id), + None => panic!("test failed"), + } + } + } +} diff --git a/crates/claims/crates/data-integrity/tests/common/signature.rs b/crates/claims/crates/data-integrity/tests/common/signature.rs new file mode 100644 index 000000000..8101021c0 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/common/signature.rs @@ -0,0 +1,57 @@ +#![allow(unused)] +use std::collections::HashMap; + +use iref::IriBuf; +use json_syntax::Print; +use serde::Deserialize; +use ssi_claims_core::SignatureEnvironment; +use ssi_data_integrity::{ + AnyDataIntegrity, AnySignatureOptions, AnySuite, CryptographicSuite, DataIntegrityDocument, + ProofConfiguration, +}; +use ssi_verification_methods::{multikey::MultikeyPair, AnyMethod, SingleSecretSigner}; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignatureTest { + pub id: Option, + pub key_pair: MultikeyPair, + pub verification_methods: HashMap, + pub configuration: ProofConfiguration, + #[serde(default)] + pub options: AnySignatureOptions, + pub input: DataIntegrityDocument, + pub expected_output: json_syntax::Value, +} + +impl SignatureTest { + pub async fn run(mut self) { + let (suite, options) = self.configuration.into_suite_and_options(); + + let vc: AnyDataIntegrity = suite + .sign_with( + SignatureEnvironment::default(), + self.input, + &self.verification_methods, + SingleSecretSigner::new(self.key_pair.secret_jwk().unwrap()).into_local(), + options.cast(), + self.options, + ) + .await + .unwrap(); + + let mut json = json_syntax::to_value(vc).unwrap(); + json.canonicalize(); + + self.expected_output.canonicalize(); + + if json != self.expected_output { + eprintln!("expected: {}", self.expected_output.pretty_print()); + eprintln!("found: {}", json.pretty_print()); + match self.id { + Some(id) => panic!("test <{}> failed", id), + None => panic!("test failed"), + } + } + } +} diff --git a/crates/claims/crates/data-integrity/tests/common/verification.rs b/crates/claims/crates/data-integrity/tests/common/verification.rs new file mode 100644 index 000000000..1c86fc4aa --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/common/verification.rs @@ -0,0 +1,30 @@ +#![allow(unused)] +use std::collections::HashMap; + +use iref::IriBuf; +use serde::Deserialize; +use ssi_claims_core::VerificationParameters; +use ssi_data_integrity::AnyDataIntegrity; +use ssi_verification_methods::AnyMethod; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerificationTest { + pub id: Option, + pub verification_methods: HashMap, + pub input: AnyDataIntegrity, +} + +impl VerificationTest { + pub async fn run(self) { + let params = VerificationParameters::from_resolver(self.verification_methods); + let result = self.input.verify(params).await.unwrap(); + + if let Err(e) = result { + match self.id { + Some(id) => panic!("<{}> verification failed: {e}", id), + None => panic!("verification failed: {e}"), + } + } + } +} diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_signature.json b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_signature.json new file mode 100644 index 000000000..b247d26eb --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_signature.json @@ -0,0 +1,66 @@ +{ + "type": "SignatureTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#representation-ecdsa-rdfc-2019-with-curve-p-256", + "keyPair": { + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "secretKeyMultibase": "z42twTcNeSYcnqg1FLuSFs2bsGH3ZqbRHFmvS9XMsYhjxvHN" + }, + "verificationMethods": { + "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "https://vc.example/issuers/5678", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "configuration": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod" + }, + "input": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": ["VerifiableCredential", "AlumniCredential"], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + } + }, + "expectedOutput": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "z4KKHqaD4F7GHyLA6f3wK9Ehxtogv5jQRFpQBM4sXkSf7Bozd7bAf7dF6UkfM2aSCBMm24mPvaFXmzQmimzaEC3SL" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_verification.json b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_verification.json new file mode 100644 index 000000000..9d9a68d87 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p256_verification.json @@ -0,0 +1,39 @@ +{ + "type": "VerificationTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#representation-ecdsa-rdfc-2019-with-curve-p-256", + "verificationMethods": { + "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "https://vc.example/issuers/5678", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "input": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "z4KKHqaD4F7GHyLA6f3wK9Ehxtogv5jQRFpQBM4sXkSf7Bozd7bAf7dF6UkfM2aSCBMm24mPvaFXmzQmimzaEC3SL" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_signature.json b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_signature.json new file mode 100644 index 000000000..31bc4e8bb --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_signature.json @@ -0,0 +1,66 @@ +{ + "type": "SignatureTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#representation-ecdsa-rdfc-2019-with-curve-p-384", + "keyPair": { + "publicKeyMultibase": "z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "secretKeyMultibase": "z2fanyY7zgwNpZGxX5fXXibvScNaUWNprHU9dKx7qpVj7mws9J8LLt4mDB5TyH2GLHWkUc" + }, + "verificationMethods": { + "https://vc.example/issuers/5678#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ": { + "type": "Multikey", + "id": "https://vc.example/issuers/5678#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "controller": "https://vc.example/issuers/5678", + "publicKeyMultibase": "z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ" + } + }, + "configuration": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "proofPurpose": "assertionMethod" + }, + "input": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": ["VerifiableCredential", "AlumniCredential"], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + } + }, + "expectedOutput": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "proofPurpose": "assertionMethod", + "proofValue": "zpuEu1cJ7Wpb453b4RiV3ex7SKGYm3fdAd4WUTVpR8Me3ZXkCCVUfd4M4TvHF9Wv1tRNWe5SkZhQTGYLUxdugFRGC2uyYRNTnimS6UMN6wkenTViRK1Mei7DooSBpumHHjYu" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_verification.json b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_verification.json new file mode 100644 index 000000000..6ea86ca54 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_rdfc_2019/p384_verification.json @@ -0,0 +1,61 @@ +{ + "type": "VerificationTest", + "verificationMethods": { + "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce#z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce": { + "type": "Multikey", + "id": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce#z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", + "controller": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", + "publicKeyMultibase": "z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce" + } + }, + "input": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "@protected": true, + "DriverLicenseCredential": "urn:example:DriverLicenseCredential", + "DriverLicense": { + "@id": "urn:example:DriverLicense", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "documentIdentifier": "urn:example:documentIdentifier", + "dateOfBirth": "urn:example:dateOfBirth", + "expirationDate": "urn:example:expiration", + "issuingAuthority": "urn:example:issuingAuthority" + } + }, + "driverLicense": { + "@id": "urn:example:driverLicense", + "@type": "@id" + } + }, + "https://w3id.org/security/data-integrity/v2" + ], + "id": "urn:uuid:5dce72cb-5b97-4ab0-8b4d-1617482e5505", + "type": [ + "VerifiableCredential", + "DriverLicenseCredential" + ], + "credentialSubject": { + "id": "urn:uuid:1a0e4ef5-091f-4060-842e-18e519ab9440", + "driverLicense": { + "type": "DriverLicense", + "documentIdentifier": "T21387yc328c7y32h23f23", + "dateOfBirth": "01-01-1990", + "expirationDate": "01-01-2030", + "issuingAuthority": "VA" + } + }, + "issuer": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", + "issuanceDate": "2024-07-18T14:02:26Z", + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-rdfc-2019", + "verificationMethod": "did:key:z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce#z82LkvutaARmY8poLhUnMCAhFbts88q4yDBmkqwRFYbxpFvmE1nbGUGLKf9fD66LGUbXDce", + "proofPurpose": "assertionMethod", + "proofValue": "z56QEeKyiawVpNmLKLiEV4jnxqWFaLg8McRLwxTQanPqx8iHxYEcWzpPAwKv6Mtoari6FiBzkrzZMTJg9MJdFqxL4oaNowupTM1e2GxsyGcnxhk9FD33EAfBWfFwA65A8Wso" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/selection.json b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/selection.json new file mode 100644 index 000000000..e3a157b16 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/selection.json @@ -0,0 +1,119 @@ +{ + "type": "SelectionTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#derived-proof", + "options": { + "selectivePointers": ["/credentialSubject/boards/0", "/credentialSubject/boards/1"] + }, + "verificationMethods": { + "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "input": { + "@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": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0AhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5NYIAARIjNEVWZ3iJmqu8zd7v8AESIzRFVmd4iZqrvM3e7_jlhAKVYKM250DDcNWOYQpUmYC1Z5NZhJRwie8vVUev94QGst83WhoW7_UM6JULsKjNVHjxZlZQyovN4xw1M_mhn6TFhAsqMSgz0EeaPe0Hmo5SN1JNZmCjiZ-CNJB4ScmyK46s7hDotNZuGHxKGaAFC43O0FxcKeUC96q_z9PGeF5C3VpVhAmeoEU8I1ZzxFyR-QMxwoSkqRG9E8_CaSrhH8TD2t-tV32HKAC4hJkKl6xHuz6XL2G-V0cm6d_rWozjhmmVaMjVhAbQMMckpcMAEo15WC6C8Mo3bCEWFGtOTkMxND-LJMdfkCSovB7RnCR7SXzk5-0YVigtJ5Fzg71AAob5yg1WNNk1hApQHlYRGlUVkv-WX1OjJYJ19Ow7ipvVwUvm90Sn3IjNRLuy9pr5DHm3wVlVMPVpLqjS-E8_jJDeJV5pY0bfK_A1hAas2wx9bcEj0Sh7t8w9Cj-2FpceGpdRhaLZxYs1ZEG8-obUjb0CHOyH8S7uwDtn7oSW2oCW2SpZvlX-2jW17rmlhAe34eQ8-gJHyQahY0EmZh8mZoy0svnpTjkdcLnroLIBsiVkfCzMKLOWeEtWZUVnIBeugT8I2C7mnmpHNjdo2d4lhAM8okCUX8F4GYx9rlnSDvr5pTHPOjOOJ47JzFdDtX_Q4bZxWwLGwqltYojDecyt4oxQHYz55ZRnhTXLHqa74B7VhAO_Hj0vxsuJZzpVGtgoMKK2ZlGKvhLX3_vUCvdL-MTlszVr2iC3XJpCbOc8B_W_On-csaLPzUSvlSDtNec1ZVk1hAdm2Ht4sv_ec3s1HRqeul--yEGx4SrpwyNQRdLa5ZKyJDgqr4h-EtVNzc-J-VllvKrHN8wBKtUqarqI4Npnrx7VhAORMLXYz3l59Ozc7SDk2ej7clrer9Bn6eaBUQG773AqQ56bc-oGXeemekwZCNHjFLOESNoNq7qetO8FRbiFHb4FhAW-otSFVlUPFmg119n3TeSE7up5hBS34AqP2TGUQA5pDGyOTetrf8qq3bWj1lpCu1Z6yEZJlQ6nrLiCoaNVhpL1hA1wW_HhsTPUfUlqMX6ZMsLem8hbWaFe_rZDpPp5NN02vMHInDjO1Gn0BrXUyVAMnTY3fGrDjsuy2sGgMzR-bo11hAvOGSXH51eRoCWtV9LlpZD10ix0IuuVCnat5fRxU7hqGs0AzM09kGsmuDMRjowp51xhiFJ3iMajIOOhWUhPxHCoVnL2lzc3VlcngdL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxOdW1iZXJ4Gi9jcmVkZW50aWFsU3ViamVjdC9zYWlscy8xeCAvY3JlZGVudGlhbFN1YmplY3QvYm9hcmRzLzAveWVhcngaL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxzLzI" + } + }, + "expectedOutput": { + "@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": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0BhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5OGWEBtAwxySlwwASjXlYLoLwyjdsIRYUa05OQzE0P4skx1-QJKi8HtGcJHtJfOTn7RhWKC0nkXODvUAChvnKDVY02TWEClAeVhEaVRWS_5ZfU6MlgnX07DuKm9XBS-b3RKfciM1Eu7L2mvkMebfBWVUw9WkuqNL4Tz-MkN4lXmljRt8r8DWEBqzbDH1twSPRKHu3zD0KP7YWlx4al1GFotnFizVkQbz6htSNvQIc7IfxLu7AO2fuhJbagJbZKlm-Vf7aNbXuuaWEA78ePS_Gy4lnOlUa2CgworZmUYq-Etff-9QK90v4xOWzNWvaILdcmkJs5zwH9b86f5yxos_NRK-VIO015zVlWTWEB2bYe3iy_95zezUdGp66X77IQbHhKunDI1BF0trlkrIkOCqviH4S1U3Nz4n5WWW8qsc3zAEq1Spquojg2mevHtWEA5EwtdjPeXn07NztIOTZ6PtyWt6v0Gfp5oFRAbvvcCpDnptz6gZd56Z6TBkI0eMUs4RI2g2rup607wVFuIUdvgpgBYIOGCDmZ9TBxEtWeCI9oVmRt0eHRGAaoOXx08gxL2IQt_AVggVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKwCWCBD6o5lQOWjNGwaTjq7H2Cn1-NPbwXLeDedy2YyiqL9TQNYIJEdvfdRibsv05I3pv8e6S1aUuAuBpGQHLhrYj4QX0knBFggk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDkFWCDYgT4e07o_IdCwae6qE7WZfpXtGRFESEXR3SxZmXE05o4AAQIFBggJCg4PEBESEw" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/signature.json b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/signature.json new file mode 100644 index 000000000..c9c145444 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/signature.json @@ -0,0 +1,137 @@ +{ + "type": "SignatureTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#base-proof", + "keyPair": { + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "secretKeyMultibase": "z42twTcNeSYcnqg1FLuSFs2bsGH3ZqbRHFmvS9XMsYhjxvHN" + }, + "verificationMethods": { + "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "configuration": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod" + }, + "options": { + "keyPair": { + "publicKeyMultibase": "zDnaeTHfhmSaQKBc7CmdL3K7oYg3D6SC7yowe2eBeVd2DH32r", + "secretKeyMultibase": "z42tqvNGyzyXRzotAYn43UhcFtzDUVdxJ7461fwrfhBPLmfY" + }, + "hmacKeyString": "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF", + "mandatoryPointers": ["/issuer", "/credentialSubject/sailNumber", "/credentialSubject/sails/1", "/credentialSubject/boards/0/year", "/credentialSubject/sails/2"] + }, + "input": { + "@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 + } + ] + } + }, + "expectedOutput": { + "@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": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0AhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5NYIAARIjNEVWZ3iJmqu8zd7v8AESIzRFVmd4iZqrvM3e7_jlhAKVYKM250DDcNWOYQpUmYC1Z5NZhJRwie8vVUev94QGst83WhoW7_UM6JULsKjNVHjxZlZQyovN4xw1M_mhn6TFhAsqMSgz0EeaPe0Hmo5SN1JNZmCjiZ-CNJB4ScmyK46s7hDotNZuGHxKGaAFC43O0FxcKeUC96q_z9PGeF5C3VpVhAmeoEU8I1ZzxFyR-QMxwoSkqRG9E8_CaSrhH8TD2t-tV32HKAC4hJkKl6xHuz6XL2G-V0cm6d_rWozjhmmVaMjVhAbQMMckpcMAEo15WC6C8Mo3bCEWFGtOTkMxND-LJMdfkCSovB7RnCR7SXzk5-0YVigtJ5Fzg71AAob5yg1WNNk1hApQHlYRGlUVkv-WX1OjJYJ19Ow7ipvVwUvm90Sn3IjNRLuy9pr5DHm3wVlVMPVpLqjS-E8_jJDeJV5pY0bfK_A1hAas2wx9bcEj0Sh7t8w9Cj-2FpceGpdRhaLZxYs1ZEG8-obUjb0CHOyH8S7uwDtn7oSW2oCW2SpZvlX-2jW17rmlhAe34eQ8-gJHyQahY0EmZh8mZoy0svnpTjkdcLnroLIBsiVkfCzMKLOWeEtWZUVnIBeugT8I2C7mnmpHNjdo2d4lhAM8okCUX8F4GYx9rlnSDvr5pTHPOjOOJ47JzFdDtX_Q4bZxWwLGwqltYojDecyt4oxQHYz55ZRnhTXLHqa74B7VhAO_Hj0vxsuJZzpVGtgoMKK2ZlGKvhLX3_vUCvdL-MTlszVr2iC3XJpCbOc8B_W_On-csaLPzUSvlSDtNec1ZVk1hAdm2Ht4sv_ec3s1HRqeul--yEGx4SrpwyNQRdLa5ZKyJDgqr4h-EtVNzc-J-VllvKrHN8wBKtUqarqI4Npnrx7VhAORMLXYz3l59Ozc7SDk2ej7clrer9Bn6eaBUQG773AqQ56bc-oGXeemekwZCNHjFLOESNoNq7qetO8FRbiFHb4FhAW-otSFVlUPFmg119n3TeSE7up5hBS34AqP2TGUQA5pDGyOTetrf8qq3bWj1lpCu1Z6yEZJlQ6nrLiCoaNVhpL1hA1wW_HhsTPUfUlqMX6ZMsLem8hbWaFe_rZDpPp5NN02vMHInDjO1Gn0BrXUyVAMnTY3fGrDjsuy2sGgMzR-bo11hAvOGSXH51eRoCWtV9LlpZD10ix0IuuVCnat5fRxU7hqGs0AzM09kGsmuDMRjowp51xhiFJ3iMajIOOhWUhPxHCoVnL2lzc3VlcngdL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxOdW1iZXJ4Gi9jcmVkZW50aWFsU3ViamVjdC9zYWlscy8xeCAvY3JlZGVudGlhbFN1YmplY3QvYm9hcmRzLzAveWVhcngaL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxzLzI" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification.json b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification.json new file mode 100644 index 000000000..e1cad604b --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification.json @@ -0,0 +1,59 @@ +{ + "type": "VerificationTest", + "id": "https://www.w3.org/TR/vc-di-ecdsa/#derived-proof", + "verificationMethods": { + "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "input": { + "@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": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0BhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5OGWEBtAwxySlwwASjXlYLoLwyjdsIRYUa05OQzE0P4skx1-QJKi8HtGcJHtJfOTn7RhWKC0nkXODvUAChvnKDVY02TWEClAeVhEaVRWS_5ZfU6MlgnX07DuKm9XBS-b3RKfciM1Eu7L2mvkMebfBWVUw9WkuqNL4Tz-MkN4lXmljRt8r8DWEBqzbDH1twSPRKHu3zD0KP7YWlx4al1GFotnFizVkQbz6htSNvQIc7IfxLu7AO2fuhJbagJbZKlm-Vf7aNbXuuaWEA78ePS_Gy4lnOlUa2CgworZmUYq-Etff-9QK90v4xOWzNWvaILdcmkJs5zwH9b86f5yxos_NRK-VIO015zVlWTWEB2bYe3iy_95zezUdGp66X77IQbHhKunDI1BF0trlkrIkOCqviH4S1U3Nz4n5WWW8qsc3zAEq1Spquojg2mevHtWEA5EwtdjPeXn07NztIOTZ6PtyWt6v0Gfp5oFRAbvvcCpDnptz6gZd56Z6TBkI0eMUs4RI2g2rup607wVFuIUdvgpgBYIOGCDmZ9TBxEtWeCI9oVmRt0eHRGAaoOXx08gxL2IQt_AVggVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKwCWCBD6o5lQOWjNGwaTjq7H2Cn1-NPbwXLeDedy2YyiqL9TQNYIJEdvfdRibsv05I3pv8e6S1aUuAuBpGQHLhrYj4QX0knBFggk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDkFWCDYgT4e07o_IdCwae6qE7WZfpXtGRFESEXR3SxZmXE05o4AAQIFBggJCg4PEBESEw" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_2.json b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_2.json new file mode 100644 index 000000000..4f33e8d2e --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_2.json @@ -0,0 +1,97 @@ +{ + "type": "VerificationTest", + "verificationMethods": { + "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "input": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@protected":true, + "ExampleAchievementCredential": "urn:example:ExampleAchievementCredential", + "WindsailingAchievement": { + "@id":"urn:example:WindsailingAchievement", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "sailNumber": "urn:example:sailNumber", + "sails":{ + "@id": "urn:example:sails", + "@context": { + "@protected": true, + "sailName": "urn:example:sailName", + "size": "urn:example:size", + "year": "urn:example:year" + } + }, + "boards": { + "@id": "urn:example:boards", + "@context": { + "@protected": true, + "boardName": "urn:example:boardName", + "brand": "urn:example:brand", + "year": "urn:example:year" + } + } + } + }, + "achievements": { + "@id": "urn:example:achievements", + "@type": "@id" + } + } + ], + "type": ["VerifiableCredential","ExampleAchievementCredential"], + "issuer": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "credentialSubject": { + "achievements": [ + { + "type": "WindsailingAchievement", + "sails": [ + { + "size": 6.1, + "sailName": "Eagle-FR", + "year": 2023 + }, + { + "size": 7, + "sailName": "Eagle-FR", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Eagle-FR", + "year": 2023 + } + ], + "boards":[ + {"boardName":"CompFoil170","brand":"Tillo","year":2022}, + {"boardName":"Tillo Custom","brand":"Tillo","year":2019} + ] + }, + { + "type":"WindsailingAchievement", + "sailNumber":"Mars101", + "sails": [ + {"size":5.9,"sailName":"Chicken","year":2022}, + {"size":4.9,"sailName":"Vulture-FR","year":2023} + ] + } + ] + }, + "proof":{ + "type":"DataIntegrityProof", + "created":"2024-07-18T14:02:31Z", + "verificationMethod":"did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "cryptosuite":"ecdsa-sd-2023", + "proofPurpose":"assertionMethod", + "proofValue":"u2V0BhVhATjCU5xPjKFYvaAagQfzr18jP5_f_wOi0u1r6BcZ4Wd7cTYuESQqC_WX-HjW-viaEEZeKpJuF8HjjxuG1czaOxlgjgCQCElBZxYIVrcZlMqRQuTo08AeYA1oLmqWorFUihOxENTuYIlhAwsTMNS-BuZsd44u7B8zutJbSrbzUgVoIekuwcLSWz4enhAjCiQ1uLh8nD3J6lBpw2C85OMDFuJpy2stvPopBaVhAckAM-L7tMuii3OaeKs8fwTRN29zDjP_Yqs7MsPyuoeK4WhQ49eM0xIhYr2viIC1dXcshbMqGoKlyEBzoJBcRQVhAAcej6x3pkg9KIJMA9syISoOD8KPZS1XokUYGp9lcCwpph4h0FV41xAaN-loFmgO5TJgILoFa89Nm-rn3QRMkB1hAI6K5uhFBwxRgo8hdBooxP2QSEFPYMxh9OiBnKwD2NxgeSinb_I7N-l72QGzELdbFZIqVh0glHYF0WnaUa1cKkFhA91UFT7TW9hos5H8RUB-wDttEP0hU0jtas_RK4CI825TdECZYMZ6W436xjt-zqA9Ns0TG9y7DrdlIrkUFRhoSL1hALJkFyJyLSxiJCk1uOrZ7jzujrrP6epTASTkHwxGYADA0q7KaqWfVd2qwobRxJWNfVLirKyPm_sJ7-_iYdUNiNFhAfXIDFzOOugs6AdCJkjJtKEPAcZzHGZC0TT3D9I8Vk-co3hUznecjvQchPpp_5CerWsFBmI9nQt9y5OQ9CcWPAFhAcgpQxRf-ZutVIrgRXyvbeGLOo7vWKG6mPsMcq4saH66iV4Y1QF76NCRcbdbHmXZ7uABrTCXmgYWqU8AjPhbCMFhAX8V6Xiup6zvK91PtBJAAmMZXdIjwDrr75ZDOEoxsGhVrcwy0cib867B6IgujxcJMfvUCQdM2VH9xoqpkTuKfW1hA8ZwnYGB8MfqDW2ouZwGg3ovO1etIUBeY1eHvMenqbkWXyCHerNR6ROP2zYISpg4L5783c4uQcD6p9zdj-fiBflhAQy1ebIkt9Sd8xkd_tnPOOp-JDAfix57kxuuAw5G-wjMuYi1noZEB2aj8bD41C_K4nrSMAGiBL2Sm-1z9It3LyFhAehX5KSbDSS9zkiS3ZOKLzagHIBQ0ZXIpDWUSI9oz6KhqOHKcZcETLT0jTcjpGbwQA55sZirmuh2_riI9b2-n01hANpTkhPWfL3xQASTQ4mMiJzVWeTgeFm266OIQCAQd8Iip4ykjwYo5y_fh2cvpgnb9HCHy3FIW8aNIkouRwba9E1hAI4U5oYA9Uzla58Q_pocLa7xPIRicJPJkwo-2waJB_9nZiUsLGJ-ROw0ZtblJY0A84ooEEWbdpgcH7SAsGFHn6lhANsuaqjotPJZXld6FbhigXjUUZK4Bxohj-BMzhebrYBNOv667ssJrrSOnjwkDwYnX3QAN-rDqq-CJv_iYT-1jVVhAwl_Tghgv9wmQvo6HdZMXTXx47oVmoHtfu86sasz0pWt-OEiAxaOQkzVFvoUBmfh9QN77dyg03cz_DPm4Z3SkMVhApQydLvzGjiJY6-7CPWHy-YUJrF3liKDqCkjSB0rF5IvPC-fOydnXUgeZuE64YCVF6a1_XuQrhcw3XqiFbdzC_lhAxTdwwv5FA0fXrpLXdSOKUcUu-Sw6xPHpQ642rHYsrG9ivhNMRcFrJuURXB0JgmXvnwMeP7g90d5soIHHrHeHaFhAyp93XLgwvb1CyAV6W41X7qVe2_tPVfq9TJ19VYdHs5S1nUibnbpTYs3INe69e6jgmvwg8vXuWmVq5qhciyoMT1hAILiI2_YqaZKBDNFURXp_dpyzhxqsR47v-vqct3d8l_--JNn3BpILnZtoxT-u2uEugd2zGvYSk6ag5a2QXXxXdFhAmwoNJ6_nJBAX72VOZiwQBDnIb4PTXVBGhgmLCXZY1NUviNbhAJVTpqDd1k_lncNNaJvpqaNJYIwUUCjov1AoNlhAW1llVpyXZ5uAKufTRfhy3LlqjShD9cOeuoR5fX9cH1RpQkdupWs8n-dshfxuVd0c7nWNzq8PH4sP2IHWfxa-9lhAQy6WNFRuRXIwuv7kgOpSusBIRZ5iAy9tRR10zw17ofw88XBkF6xHdbfwiy3d5DedBTz3myqu_9s_Scrr_OMwGlhASp9t7cPQXXirUDPZOCV_leukSvb2KutdUxzKONJDumuyPuFaIJ2-DP_BqQKQyKmfYF5SZGEHjhw_ihhTXJuqjlhAZELTnSn-xhw9HqvjV5K-Pu5zC6QPdA-TQlPY0pI2CDZ2K772fpqELKWdbFGj66o7HWUjbYNxpr5qaX9VpEeldlhAAz7sFxpT_3W-YezehTTMRB65tobqizAHkEcoB5o9EZV_SXnNZvUbDXPBgZfsU8xBx1YGjWRkEu8UzLNzxRAy4lhANeyAhcLKm171JAZxHQjEDM3xYmP6kqPkfDfghehQK_i6g_O2RFU-5KWg18BvmH7QbYKg0QMK6Is5TQiwFYSOh1hAMX_7RlMMQUx8v9pzy1Vnh-_Sp0bOyyaY9l6v9UlD-54S4d5qkAV_l-mnqCwGm4TB0MM8mlLL_DaIl71YatAel1hABRfGIAm-fCaEsbF9QXfZRX3zMjgTMj-7A1ZlCt1jFVyY-aqF3D4U25cZywhxmOUAQBV-Fv1iKKmO2FfkDw5VflhA9RGL9H6NyizwLjs0HPkqYXh67jUyGzGlMmeUvGafw6aGNxdGDRF1p43DteOxfYUF7Rq4DgUb0Krbc0qY0rGL2lhAZcVJgwEcDjavmBc_KwKeKx2Pm5f2BPCI3pwdqeLAi7fzrztueGVpt_JDW0Zh6qYQNAW9YGGZCYTZuz6g2MOCDVhAmexJ2c25tDR-sCYaDpDyLW29N5osEkpP_U0AIuNDtzcEac6wbrQVlfYKbsb8C93WmixQ29sGlwaKpcm7WZtvOlhAg6IAG5VfwJYUUWO46k6iT22dc3t3JkFNFuMsL1wHWwzjy6DtKwVe4Xo2hVtsY57d77GU1lxODXEbH7gmnvu9B1hAa624k561MBdKF8M8TEJKUtoq-C8_GsbwxgBETQZ31egbYN6jfoOZamUkGHCKmrm8kPaAm5aYNRcZtLGfM9KBoasAWCDEsuOsTpogGxgW4AIkONPOwaoDlXnhhyLCk3FJXJ6srwFYIJ98Njsm2rjA0kjkDRmHYoAMOHaIJsOjc_ZnMDMkvUQDAlggFiR_Ln1SE_MlGSrJUShlA05lxmwzsbxYlHs7II3Wtb4DWCByDlkIF5Rs2bl6vYsiuyJ5CWmhqCcZ9WXHY5laZAPz4wRYILywufXNFMHWeaH65ReSI-jeqVz1lKhpvMqVz5XBrJN5BVggkjO8Pq2dQplzdr0I84c4O5UIlktsGYY9c3IN5LLTIIwGWCCBqpuXQO6WzGNTLbvZslCdIHKH_ZhdR2kaWKtKw_jZEgdYIPvIeHZCCAJxFbxyRZW2bFWOEj5PGoSdOB__fx_qDHT0CFggX9H9fl_XP0ZuxhuhdB_1N96tYtp1IXCr-K9YUl2NlOMJWCBdBHiVzJuLTWYUv_FaUl_CSnFowhTfX-GiyKzrzghP2gpYIIS9RZGOYuP0Byt5_aB-8O04NJU6QQUIIVjb21uMxh1EgxghGCIYJA" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_3.json b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_3.json new file mode 100644 index 000000000..3332a4dfd --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/ecdsa_sd_2023/verification_3.json @@ -0,0 +1,57 @@ +{ + "type": "VerificationTest", + "verificationMethods": { + "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP": { + "type": "Multikey", + "id": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "controller": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "publicKeyMultibase": "zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP" + } + }, + "input": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@protected": true, + "DriverLicenseCredential": "urn:example:DriverLicenseCredential", + "DriverLicense": { + "@id": "urn:example:DriverLicense", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "documentIdentifier": "urn:example:documentIdentifier", + "dateOfBirth": "urn:example:dateOfBirth", + "expirationDate": "urn:example:expiration", + "issuingAuthority": "urn:example:issuingAuthority" + } + }, + "driverLicense": { + "@id": "urn:example:driverLicense", + "@type": "@id" + } + } + ], + "type": [ + "VerifiableCredential", + "DriverLicenseCredential" + ], + "issuer": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "credentialSubject": { + "id": "urn:uuid:1a0e4ef5-091f-4060-842e-18e519ab9440", + "driverLicense": { + "type": "DriverLicense", + "dateOfBirth": "01-01-1990", + "expirationDate": "01-01-2030" + } + }, + "proof": { + "type": "DataIntegrityProof", + "created": "2024-07-18T14:38:51Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "cryptosuite": "ecdsa-sd-2023", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0BhVhAhUknG_rovYoIggiW8fJHHRYrvMgbDNGybpKadJSZ2PV_AXQRCPbL1VgCUHjHzqhR_nEIcP93PxG1HkSQEj297FgjgCQCEvvVHxgENgJ3lU5xCzjBFceTegK-OvZ2A2bwMxbD-2OFWECmLX64z2QrVZwy2TfEAz14KQV6PiX8BeSu8ZfK_2VSszLZ5RxOwh6C_jY9y7pH1ABL6r6KWQbT4ieL30BoY-_OWEDXAiSjbb2pPDs7syt_17oNwOnHFNW7EqpHGGoqzETYRZt9RvL7VixPdRayYXSLyvZ8taBmzlT0DqZVdkj-GWSHWEBEUM8MkkSO7Ow578Z8wEnTCvDbUujdOUo26zi0iu-MUHJXVtw1DHL_ZDWX2TONE-haAiuxg1jUtnvskuh6G5cOWEBx2Lma_DoZzqClsjdTTc4iYFzbOD_dFnnfsViFTzzFh4bNTs4SXjab4u3HPVpkndOT6QH-XI8KqZ8GmszivY0zWEDkvrfhTZQxWYQ-FUXQPOFOszH-PWRUbSQQXc9vUeaaMYPk6pW0yGHPHjzhB-h8IvSaBKmGgIO1FMkpanrUR1N_ogBYIH3xohicTtcoVxCf_xJlY7EYQOTRjaq8lwFRY5EbhaOEAVggBBJO0DMRxBwOFvfQ_0KmA1hqdtf_Gemwe9FKC69MODGDBAUH" + } + } +} \ No newline at end of file diff --git a/crates/claims/crates/data-integrity/tests/suite.rs b/crates/claims/crates/data-integrity/tests/suite.rs new file mode 100644 index 000000000..ca3162789 --- /dev/null +++ b/crates/claims/crates/data-integrity/tests/suite.rs @@ -0,0 +1,65 @@ +#![allow(unused)] +mod common; +use common::Test; + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_rdfc_2019_p256_signature() { + Test::load("ecdsa_rdfc_2019/p256_signature.json") + .run() + .await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_rdfc_2019_p256_verification() { + Test::load("ecdsa_rdfc_2019/p256_verification.json") + .run() + .await +} + +#[cfg(all(feature = "w3c", feature = "secp384r1"))] +#[async_std::test] +async fn ecdsa_rdfc_2019_p384_signature() { + Test::load("ecdsa_rdfc_2019/p384_signature.json") + .run() + .await +} + +#[cfg(all(feature = "w3c", feature = "secp384r1"))] +#[async_std::test] +async fn ecdsa_rdfc_2019_p384_verification() { + Test::load("ecdsa_rdfc_2019/p384_verification.json") + .run() + .await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_sd_2023_signature() { + Test::load("ecdsa_sd_2023/signature.json").run().await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_sd_2023_selection() { + Test::load("ecdsa_sd_2023/selection.json").run().await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_sd_2023_verification() { + Test::load("ecdsa_sd_2023/verification.json").run().await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_sd_2023_verification_2() { + Test::load("ecdsa_sd_2023/verification_2.json").run().await +} + +#[cfg(all(feature = "w3c", feature = "secp256r1"))] +#[async_std::test] +async fn ecdsa_sd_2023_verification_3() { + Test::load("ecdsa_sd_2023/verification_3.json").run().await +} diff --git a/crates/crypto/src/algorithm/mod.rs b/crates/crypto/src/algorithm/mod.rs index 00b27727f..e98e681bc 100644 --- a/crates/crypto/src/algorithm/mod.rs +++ b/crates/crypto/src/algorithm/mod.rs @@ -585,6 +585,57 @@ impl TryFrom for AnyBlake2b { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ES256OrES384 { + ES256, + ES384, +} + +impl ES256OrES384 { + pub fn name(&self) -> &'static str { + match self { + Self::ES256 => "ES256", + Self::ES384 => "ES384", + } + } +} + +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) + } +} + +impl From for Algorithm { + fn from(value: ES256OrES384) -> Self { + match value { + ES256OrES384::ES256 => Self::ES256, + ES256OrES384::ES384 => Self::ES384, + } + } +} + +impl From for AlgorithmInstance { + fn from(value: ES256OrES384) -> Self { + match value { + ES256OrES384::ES256 => Self::ES256, + ES256OrES384::ES384 => Self::ES384, + } + } +} + #[derive(Debug, Clone)] pub struct BbsInstance(pub Box); diff --git a/crates/jwk/src/algorithm.rs b/crates/jwk/src/algorithm.rs index a4b242821..2a5d9d975 100644 --- a/crates/jwk/src/algorithm.rs +++ b/crates/jwk/src/algorithm.rs @@ -1,7 +1,7 @@ use core::fmt; use serde::{Deserialize, Serialize}; use ssi_crypto::{ - algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType}, + algorithm::{ES256OrES384, SignatureAlgorithmInstance, SignatureAlgorithmType}, UnsupportedAlgorithm, }; @@ -283,3 +283,12 @@ impl TryFrom for ssi_crypto::algorithm::AnyBlake2b { } } } + +impl From for Algorithm { + fn from(value: ES256OrES384) -> Self { + match value { + ES256OrES384::ES256 => Self::ES256, + ES256OrES384::ES384 => Self::ES384, + } + } +} diff --git a/crates/multicodec/src/codec/p256.rs b/crates/multicodec/src/codec/p256.rs index 8dd46509c..cc3fef3d6 100644 --- a/crates/multicodec/src/codec/p256.rs +++ b/crates/multicodec/src/codec/p256.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::{Codec, Error, P256_PUB}; +use crate::{Codec, Error, P256_PRIV, P256_PUB}; impl Codec for p256::PublicKey { const CODEC: u64 = P256_PUB; @@ -10,6 +10,19 @@ impl Codec for p256::PublicKey { } fn to_bytes(&self) -> Cow<[u8]> { - Cow::Owned(self.to_sec1_bytes().into_vec()) + use p256::elliptic_curve::sec1::ToEncodedPoint; + Cow::Owned(self.to_encoded_point(true).as_bytes().to_vec()) + } +} + +impl Codec for p256::SecretKey { + const CODEC: u64 = P256_PRIV; + + fn from_bytes(bytes: &[u8]) -> Result { + Self::from_slice(bytes).map_err(|_| Error::InvalidData) + } + + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(self.to_bytes().to_vec()) } } diff --git a/crates/multicodec/src/codec/p384.rs b/crates/multicodec/src/codec/p384.rs index 7d89842ba..ee57afc70 100644 --- a/crates/multicodec/src/codec/p384.rs +++ b/crates/multicodec/src/codec/p384.rs @@ -10,6 +10,7 @@ impl Codec for p384::PublicKey { } fn to_bytes(&self) -> Cow<[u8]> { - Cow::Owned(self.to_sec1_bytes().into_vec()) + use p384::elliptic_curve::sec1::ToEncodedPoint; + Cow::Owned(self.to_encoded_point(true).as_bytes().to_vec()) } } diff --git a/crates/multicodec/src/lib.rs b/crates/multicodec/src/lib.rs index 981645bae..800e606ba 100644 --- a/crates/multicodec/src/lib.rs +++ b/crates/multicodec/src/lib.rs @@ -49,6 +49,11 @@ impl MultiEncoded { unsafe { std::mem::transmute(bytes) } } + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.0.len() + } + #[inline(always)] pub fn parts(&self) -> (u64, &[u8]) { unsigned_varint::decode::u64(&self.0).unwrap() @@ -77,6 +82,7 @@ impl MultiEncoded { } } +#[derive(Clone)] pub struct MultiEncodedBuf(Vec); impl MultiEncodedBuf { diff --git a/crates/verification-methods/core/src/methods.rs b/crates/verification-methods/core/src/methods.rs index 2b6d21ea4..4f442ee24 100644 --- a/crates/verification-methods/core/src/methods.rs +++ b/crates/verification-methods/core/src/methods.rs @@ -217,6 +217,7 @@ macro_rules! complete_verification_method_union { } } => { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, linked_data::Serialize, linked_data::Deserialize)] + #[serde(untagged)] $vis enum $name { $( $(#[$meta])* diff --git a/crates/verification-methods/src/methods/w3c/multikey.rs b/crates/verification-methods/src/methods/w3c/multikey.rs index cac0ea3b2..92b80b760 100644 --- a/crates/verification-methods/src/methods/w3c/multikey.rs +++ b/crates/verification-methods/src/methods/w3c/multikey.rs @@ -514,3 +514,38 @@ impl MultiCodec for DecodedMultikey { } } } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MultikeyPair { + #[serde(rename = "publicKeyMultibase")] + pub public: MultibaseBuf, + + #[serde(rename = "secretKeyMultibase")] + pub secret: MultibaseBuf, +} + +impl MultikeyPair { + pub fn public_jwk(&self) -> Result { + let (_, decoded) = self.public.decode()?; + let multi_encoded = MultiEncodedBuf::new(decoded)?; + JWK::from_multicodec(&multi_encoded).map_err(Into::into) + } + + pub fn secret_jwk(&self) -> Result { + let (_, decoded) = self.secret.decode()?; + let multi_encoded = MultiEncodedBuf::new(decoded)?; + JWK::from_multicodec(&multi_encoded).map_err(Into::into) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ToJWKError { + #[error(transparent)] + Multibase(#[from] multibase::Error), + + #[error(transparent)] + MultiCodec(#[from] ssi_multicodec::Error), + + #[error(transparent)] + JWK(#[from] ssi_jwk::FromMulticodecError), +}