From 12722a3fb6e50f1a45b8bd25d030f7d7c719e547 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 14 Nov 2023 17:53:43 +0100 Subject: [PATCH 001/163] mapping from Jwk (JwkExt) from json-proof-token to Jwk from identity_jose --- identity_zk/Cargo.toml | 20 ++++++++++++++++++++ identity_zk/src/jwp_ext.rs | 1 + identity_zk/src/lib.rs | 1 + 3 files changed, 22 insertions(+) create mode 100644 identity_zk/Cargo.toml create mode 100644 identity_zk/src/jwp_ext.rs create mode 100644 identity_zk/src/lib.rs diff --git a/identity_zk/Cargo.toml b/identity_zk/Cargo.toml new file mode 100644 index 0000000000..9600afc935 --- /dev/null +++ b/identity_zk/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "identity_zk" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords = ["iota", "zk", "identity"] +license.workspace = true +readme = "./README.md" +repository.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde.workspace = true +serde_json.workspace = true +jsonprooftoken.workspace = true +hex = "0.4.3" +identity_verification = { version = "=1.0.0", path = "../identity_verification", default_features = false } diff --git a/identity_zk/src/jwp_ext.rs b/identity_zk/src/jwp_ext.rs new file mode 100644 index 0000000000..0c95299b61 --- /dev/null +++ b/identity_zk/src/jwp_ext.rs @@ -0,0 +1 @@ +// pub trait JwpDocumentExt \ No newline at end of file diff --git a/identity_zk/src/lib.rs b/identity_zk/src/lib.rs new file mode 100644 index 0000000000..98ed35f6dd --- /dev/null +++ b/identity_zk/src/lib.rs @@ -0,0 +1 @@ +pub mod jwp_ext; \ No newline at end of file From b012e97689be59054ce5b2d3053e1bbf3e2fd9e5 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 15 Nov 2023 18:44:13 +0100 Subject: [PATCH 002/163] implemented JwpDocumentExt for IotaDocument and CoreDocument --- Cargo.toml | 2 + examples/0_basic/0_create_did.rs | 11 +- examples/1_advanced/7_zkp.rs | 97 +++++++++ examples/Cargo.toml | 5 + examples/utils/utils.rs | 4 +- identity_jose/Cargo.toml | 1 + identity_jose/src/jwk/jwk_ext.rs | 86 ++++++++ identity_jose/src/jwk/key_operation.rs | 8 + identity_jose/src/jwk/key_use.rs | 4 + identity_jose/src/jwk/mod.rs | 2 + identity_storage/Cargo.toml | 2 + .../src/key_storage/jwk_storage.rs | 11 + identity_storage/src/key_storage/memstore.rs | 34 +++ .../src/storage/jwk_document_ext.rs | 2 +- .../src/storage/jwp_document_ext.rs | 203 ++++++++++++++++++ identity_storage/src/storage/mod.rs | 2 + 16 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 examples/1_advanced/7_zkp.rs create mode 100644 identity_jose/src/jwk/jwk_ext.rs create mode 100644 identity_storage/src/storage/jwp_document_ext.rs diff --git a/Cargo.toml b/Cargo.toml index d4726e0d4c..fb86cace4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "identity_jose", "identity_eddsa_verifier", "examples", + "identity_zk" ] exclude = ["bindings/wasm"] @@ -23,6 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } +jsonprooftoken = {version = "0.1.0", path = "../JOSE drafts/json-proof-token"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/0_basic/0_create_did.rs b/examples/0_basic/0_create_did.rs index ff89818b7f..e0ccbc48dd 100644 --- a/examples/0_basic/0_create_did.rs +++ b/examples/0_basic/0_create_did.rs @@ -4,22 +4,28 @@ use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; +use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyIdStorage; +use identity_iota::storage::Storage; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; +use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; + /// Demonstrates how to create a DID Document and publish it in a new Alias Output. /// /// In this example we connect to a locally running private network, but it can be adapted @@ -30,10 +36,9 @@ use iota_sdk::types::block::output::AliasOutput; #[tokio::main] async fn main() -> anyhow::Result<()> { // The API endpoint of an IOTA node, e.g. Hornet. - let api_endpoint: &str = "http://127.0.0.1:14265"; - + let api_endpoint: &str = "https://api.testnet.shimmer.network"; // The faucet endpoint allows requesting funds for testing purposes. - let faucet_endpoint: &str = "http://127.0.0.1:8091/api/enqueue"; + let faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; // Create a new client to interact with the IOTA ledger. let client: Client = Client::builder() diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs new file mode 100644 index 0000000000..be215f70fb --- /dev/null +++ b/examples/1_advanced/7_zkp.rs @@ -0,0 +1,97 @@ + +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_iota::did::DIDUrl; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyIdStorage; +use identity_iota::storage::Storage; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::api::core::response::WhiteFlagResponse; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; +use jsonprooftoken::jpa::algs::ProofAlgorithm; + + +/// Demonstrates how to create a DID Document and publish it in a new Alias Output. +/// +/// In this example we connect to a locally running private network, but it can be adapted +/// to run on any IOTA node by setting the network and faucet endpoints. +/// +/// See the following instructions on running your own private network +/// https://wiki.iota.org/hornet/develop/how_tos/private_tangle +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // The API endpoint of an IOTA node, e.g. Hornet. + let api_endpoint: &str = "https://api.testnet.shimmer.network"; + // The faucet endpoint allows requesting funds for testing purposes. + let faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(api_endpoint, None)? + .finish() + .await?; + + // Create a new secret manager backed by a Stronghold. + let secret_manager: SecretManager = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password".to_owned())) + .build(random_stronghold_path())?, + ); + + // Get an address with funds for testing. + let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // Insert a new Ed25519 verification method in the DID document. + let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); +// document +// .generate_method( +// &storage, +// JwkMemStore::ED25519_KEY_TYPE, +// JwsAlgorithm::EdDSA, +// None, +// MethodScope::VerificationMethod, +// ) +// .await?; + + document + .generate_method( + &storage, + JwkMemStore::BLS12381SHA256_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + None, + MethodScope::VerificationMethod, + ) + .await?; + + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok(()) +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index cc4cb21dc8..4b24484fd5 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,6 +14,7 @@ iota-sdk = { version = "1.0", default-features = false, features = ["tls", "clie primitive-types = "0.12.1" rand = "0.8.5" tokio = { version = "1.29", default-features = false, features = ["rt"] } +jsonprooftoken.workspace = true [lib] path = "utils/utils.rs" @@ -81,3 +82,7 @@ name = "5_custom_resolution" [[example]] path = "1_advanced/6_domain_linkage.rs" name = "6_domain_linkage" + +[[example]] +path = "1_advanced/7_zkp.rs" +name = "7_zkp" \ No newline at end of file diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index 5965123beb..41caa45c7f 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -27,8 +27,8 @@ use iota_sdk::types::block::address::Bech32Address; use iota_sdk::types::block::address::Hrp; use rand::distributions::DistString; -pub static API_ENDPOINT: &str = "http://localhost:14265"; -pub static FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; +pub static API_ENDPOINT: &str = "https://api.testnet.shimmer.network"; +pub static FAUCET_ENDPOINT: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; pub type MemStorage = Storage; diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index b00479ba81..8bd4d52a2f 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -19,6 +19,7 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] } subtle = { version = "2.5", default-features = false } thiserror.workspace = true zeroize = { version = "1.6", default-features = false, features = ["std", "zeroize_derive"] } +jsonprooftoken.workspace = true [dev-dependencies] anyhow = "1" diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs new file mode 100644 index 0000000000..ca9a3ea016 --- /dev/null +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -0,0 +1,86 @@ +//TODO: JwkExt + +use std::str::FromStr; + +use identity_core::common::Url; +use jsonprooftoken::jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters}}; + +use super::{Jwk, JwkOperation, JwkUse, JwkParams, JwkParamsOkp, JwkType}; + + +impl From for JwkOperation { + fn from(value: KeyOps) -> Self { + match value { + KeyOps::Sign => Self::Sign, + KeyOps::Verify => Self::Verify, + KeyOps::Encrypt => Self::Encrypt, + KeyOps::Decrypt => Self::Decrypt, + KeyOps::WrapKey => Self::WrapKey, + KeyOps::UnwrapKey => Self::UnwrapKey, + KeyOps::DeriveKey => Self::DeriveKey, + KeyOps::DeriveBits => Self::DeriveBits, + KeyOps::ProofGeneration => Self::ProofGeneration, + KeyOps::ProofVerification => Self::ProofVerification, + } + } +} + + +impl From for JwkUse { + fn from(value: PKUse) -> Self { + match value { + PKUse::Signature => Self::Signature, + PKUse::Encryption => Self::Encryption, + PKUse::Proof => Self::Proof, + } + } +} + + +impl From for JwkParamsOkp { + fn from(value: JwkOctetKeyPairParameters) -> Self { + Self { + crv: value.crv.to_string(), + x: value.x, + d: value.d + } + } +} + +impl TryFrom for Jwk { + + type Error = crate::error::Error; + + fn try_from(value: JwkExt) -> Result { + + let x5u = match value.x5u { + Some(v) => { + Some( + Url::from_str(&v).map_err(|_| { + return Self::Error::InvalidClaim("x5u"); + })? + ) + }, + None => None, + }; + + let (kty, params) = match value.key_params { + JwkAlgorithmParameters::OctetKeyPair(p) => (JwkType::Okp, JwkParams::Okp(JwkParamsOkp::from(p))), + }; + + Ok(Self { + kty: kty, + use_: value.pk_use.and_then(|u| Some(JwkUse::from(u))), + key_ops: value.key_ops.and_then(|vec_key_ops| { + Some(vec_key_ops.into_iter().map(JwkOperation::from).collect()) + }), + alg: value.alg.and_then(|a| Some(a.to_string())), + kid: value.kid, + x5u: x5u, + x5c: value.x5c, + x5t: value.x5t, + x5t_s256: None, + params: params + }) + } +} \ No newline at end of file diff --git a/identity_jose/src/jwk/key_operation.rs b/identity_jose/src/jwk/key_operation.rs index 8fda0b6a23..0302d6a98e 100644 --- a/identity_jose/src/jwk/key_operation.rs +++ b/identity_jose/src/jwk/key_operation.rs @@ -27,6 +27,10 @@ pub enum JwkOperation { DeriveKey, /// Derive bits not to be used as a key. DeriveBits, + + //TODO: add ProofGeneration/ProofVerification + ProofGeneration, + ProofVerification } impl JwkOperation { @@ -41,6 +45,8 @@ impl JwkOperation { Self::UnwrapKey => "unwrapKey", Self::DeriveKey => "deriveKey", Self::DeriveBits => "deriveBits", + Self::ProofGeneration => "proofGeneration", + Self::ProofVerification => "proofVerification", } } @@ -55,6 +61,8 @@ impl JwkOperation { Self::UnwrapKey => Self::WrapKey, Self::DeriveKey => Self::DeriveKey, Self::DeriveBits => Self::DeriveBits, + Self::ProofGeneration => Self::ProofVerification, + Self::ProofVerification => Self::ProofGeneration } } } diff --git a/identity_jose/src/jwk/key_use.rs b/identity_jose/src/jwk/key_use.rs index a686ba79cc..ac0edb1a2e 100644 --- a/identity_jose/src/jwk/key_use.rs +++ b/identity_jose/src/jwk/key_use.rs @@ -16,6 +16,9 @@ pub enum JwkUse { /// Encryption. #[serde(rename = "enc")] Encryption, + + //TODO: add Proof + Proof } impl JwkUse { @@ -24,6 +27,7 @@ impl JwkUse { match self { Self::Signature => "sig", Self::Encryption => "enc", + Self::Proof => "proof" } } } diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index a714cbf5ac..af32648858 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -10,6 +10,7 @@ mod key_params; mod key_set; mod key_type; mod key_use; +mod jwk_ext; pub use self::curve::*; pub use self::key::*; @@ -18,3 +19,4 @@ pub use self::key_params::*; pub use self::key_set::*; pub use self::key_type::*; pub use self::key_use::*; +pub use self::jwk_ext::*; diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 795721da5a..81161fda59 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -27,6 +27,8 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } +jsonprooftoken.workspace = true + [dev-dependencies] identity_credential = { version = "=1.0.0", path = "../identity_credential", features = ["revocation-bitmap"] } diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 4f1918934c..bcf5617543 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -7,6 +7,7 @@ use crate::key_storage::KeyType; use async_trait::async_trait; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; +use jsonprooftoken::jpa::algs::ProofAlgorithm; use super::jwk_gen_output::JwkGenOutput; @@ -62,3 +63,13 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { /// Returns `true` if the key with the given `key_id` exists in storage, `false` otherwise. async fn exists(&self, key_id: &KeyId) -> KeyStorageResult; } + + + +///TODO: Extension to the JwkStorage to handle BBS+ keys +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwkStorageExt : JwkStorage { + /// Generates a JWK representing a BBS+ signature + async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult; +} \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 7dd92fc3c2..c7a0840263 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -12,6 +12,10 @@ use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jwk::key; +use jsonprooftoken::jwk::key::Jwk as JwkExt; +use jsonprooftoken::jwk::types::KeyPairSubtype; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -25,6 +29,8 @@ use super::KeyStorageError; use super::KeyStorageErrorKind; use super::KeyStorageResult; use super::KeyType; +use super::key_type; +use crate::JwkStorageExt; use crate::key_storage::JwkStorage; /// The map from key ids to JWKs. @@ -189,6 +195,9 @@ impl JwkMemStore { const ED25519_KEY_TYPE_STR: &str = "Ed25519"; /// The Ed25519 key type. pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ED25519_KEY_TYPE_STR); + + const BLS12381SHA256_KEY_TYPE_STR: &str = "Bls12381Sha256"; + pub const BLS12381SHA256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHA256_KEY_TYPE_STR); } impl MemStoreKeyType { @@ -269,6 +278,31 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> } } + +//TODO: implementation of JwkStorageExt for JwkMemStore +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwkStorageExt for JwkMemStore { + async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult { + let keysubtype = KeyPairSubtype::from_str(key_type.as_str()).map_err(|_| KeyStorageErrorKind::UnsupportedKeyType)?; + let mut jwk = Jwk::try_from(JwkExt::generate(keysubtype).map_err(|err| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(err))?) + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(err))?; + + let kid: KeyId = random_key_id(); + + jwk.set_alg(alg.to_string()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + let public_jwk: Jwk = jwk.to_public().expect("should only panic if kty == oct"); + + let mut jwk_store: RwLockWriteGuard<'_, JwkKeyStore> = self.jwk_store.write().await; + jwk_store.insert(kid.clone(), jwk); + + Ok(JwkGenOutput::new(kid, public_jwk)) + } +} + + + pub(crate) mod shared { use core::fmt::Debug; use core::fmt::Formatter; diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index f47ca10c77..46aa6072a9 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -505,7 +505,7 @@ impl JwkDocumentExt for CoreDocument { /// Attempt to revert key generation. If this succeeds the original `source_error` is returned, /// otherwise [`JwkStorageDocumentError::UndoOperationFailed`] is returned with the `source_error` attached as /// `source`. -async fn try_undo_key_generation(storage: &Storage, key_id: &KeyId, source_error: Error) -> Error +pub(crate) async fn try_undo_key_generation(storage: &Storage, key_id: &KeyId, source_error: Error) -> Error where K: JwkStorage, I: KeyIdStorage, diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs new file mode 100644 index 0000000000..b81fb15b05 --- /dev/null +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -0,0 +1,203 @@ +//TODO:: JwpDocumentExt + +use identity_document::document::CoreDocument; +use crate::Storage; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use crate::KeyType; +use identity_verification::MethodScope; +use crate::StorageResult; +use crate::JwkStorageExt; +use crate::KeyIdStorage; +use crate::JwkGenOutput; + +use crate::key_id_storage::KeyIdStorageResult; +use crate::key_id_storage::MethodDigest; +use crate::key_storage::JwkStorage; +use crate::key_storage::KeyId; +use crate::key_storage::KeyStorageResult; +use super::JwkStorageDocumentError as Error; +use super::JwsSignatureOptions; + +use async_trait::async_trait; +use identity_core::common::Object; +use identity_credential::credential::Credential; +use identity_credential::credential::Jws; +use identity_credential::credential::Jwt; +use identity_credential::presentation::JwtPresentationOptions; +use identity_credential::presentation::Presentation; +use identity_did::DIDUrl; +use identity_verification::jose::jws::CompactJwsEncoder; +use identity_verification::jose::jws::CompactJwsEncodingOptions; +use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jose::jws::JwsHeader; +use identity_verification::jws::CharSet; +use identity_verification::MethodData; +use identity_verification::VerificationMethod; +use serde::de::DeserializeOwned; +use serde::Serialize; +use crate::try_undo_key_generation; + +macro_rules! generate_method_for_document_type { + ($t:ty, $name:ident) => { + async fn $name( + document: &mut $t, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + { + let JwkGenOutput { key_id, jwk } = ::generate_bbs_key(&storage.key_storage(), key_type, alg) + .await + .map_err(Error::KeyStorageError)?; + + // Produce a new verification method containing the generated JWK. If this operation fails we handle the error + // by attempting to revert key generation before returning an error. + let method: VerificationMethod = { + match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment) + .map_err(Error::VerificationMethodConstructionError) + { + Ok(method) => method, + Err(source) => { + return Err(try_undo_key_generation(storage, &key_id, source).await); + } + } + }; + + // Extract data from method before inserting it into the DID document. + let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; + let method_id: DIDUrl = method.id().clone(); + + // The fragment is always set on a method, so this error will never occur. + let fragment: String = method_id + .fragment() + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)? + .to_owned(); + + // Insert method into document and handle error upon failure. + if let Err(error) = document + .insert_method(method, scope) + .map_err(|_| Error::FragmentAlreadyExists) + { + return Err(try_undo_key_generation(storage, &key_id, error).await); + }; + + // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the + // operation fails. + if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError) + { + // Remove the method from the document as it can no longer be used. + let _ = document.remove_method(&method_id); + return Err(try_undo_key_generation(storage, &key_id, error).await); + } + + Ok(fragment) + } + }; +} + + + +/// Extension trait for JWK-based operations on DID documents. +/// +/// This trait is deliberately sealed and cannot be implemented by external crates. +/// The trait only exists as an extension of existing DID documents implemented in +/// dependent crates. Because those crates cannot also depend on this crate, +/// the extension trait is necessary. External crates however should simply wrap the methods +/// on the trait if they wish to reexport them on their DID document type. +/// This also allows them to use their own error type on those methods. +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwpDocumentExt { + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. + /// + /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. + /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type + /// constants for that use case. + /// + /// The fragment of the generated method is returned. + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage; +} + + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + + +generate_method_for_document_type!(CoreDocument, generate_method_core_document); + + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwpDocumentExt for CoreDocument { + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + { + generate_method_core_document(self, storage, key_type, alg, fragment, scope).await + } + +} + + + + + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use super::*; + use identity_credential::credential::Jwt; + use identity_iota_core::IotaDocument; + + generate_method_for_document_type!(IotaDocument, generate_method_iota_document); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwpDocumentExt for IotaDocument { + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + { + generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await + } + +} +} \ No newline at end of file diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index efbdc28cbb..e76a0146bd 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -5,12 +5,14 @@ mod error; mod jwk_document_ext; +mod jwp_document_ext; mod signature_options; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; pub use error::*; pub use jwk_document_ext::*; +pub use jwp_document_ext::*; pub use signature_options::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and From b17024907b9da476c232a85308811be63bab54ca Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 16 Nov 2023 10:40:56 +0100 Subject: [PATCH 003/163] generate_method_extended that handles both JWS and JWP algorithm --- examples/1_advanced/7_zkp.rs | 40 +- identity_jose/src/jwk/jwk_ext.rs | 11 +- .../src/storage/jwk_document_ext.rs | 80 +++- .../src/storage/jwp_document_ext.rs | 392 +++++++++--------- 4 files changed, 301 insertions(+), 222 deletions(-) diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index be215f70fb..2cfaa821d1 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -7,12 +7,13 @@ use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; -use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; use identity_iota::storage::KeyIdStorage; use identity_iota::storage::Storage; +use identity_iota::verification::jwk::Algorithm; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; @@ -64,26 +65,27 @@ async fn main() -> anyhow::Result<()> { // Insert a new Ed25519 verification method in the DID document. let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); -// document -// .generate_method( -// &storage, -// JwkMemStore::ED25519_KEY_TYPE, -// JwsAlgorithm::EdDSA, -// None, -// MethodScope::VerificationMethod, -// ) -// .await?; - document - .generate_method( - &storage, - JwkMemStore::BLS12381SHA256_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, - None, - MethodScope::VerificationMethod, - ) - .await?; + document + .generate_method_extended( + &storage, + JwkMemStore::BLS12381SHA256_KEY_TYPE, + Algorithm::JWP(ProofAlgorithm::BLS12381_SHA256), + None, + MethodScope::VerificationMethod, + ) + .await?; + + document + .generate_method_extended( + &storage, + JwkMemStore::ED25519_KEY_TYPE, + Algorithm::JWS(JwsAlgorithm::EdDSA), + None, + MethodScope::VerificationMethod, + ) + .await?; // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs index ca9a3ea016..bd00b93f8a 100644 --- a/identity_jose/src/jwk/jwk_ext.rs +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -3,7 +3,9 @@ use std::str::FromStr; use identity_core::common::Url; -use jsonprooftoken::jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters}}; +use jsonprooftoken::{jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters}}, jpa::algs::ProofAlgorithm}; + +use crate::jws::JwsAlgorithm; use super::{Jwk, JwkOperation, JwkUse, JwkParams, JwkParamsOkp, JwkType}; @@ -83,4 +85,11 @@ impl TryFrom for Jwk { params: params }) } +} + + +//Supported Algorithm for JSON Web Signatures and JSON Web Proofs +pub enum Algorithm { + JWS(JwsAlgorithm), + JWP(ProofAlgorithm) } \ No newline at end of file diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index 46aa6072a9..e6b4ff0e13 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crate::JwkStorageExt; use crate::key_id_storage::KeyIdStorage; use crate::key_id_storage::KeyIdStorageResult; use crate::key_id_storage::MethodDigest; @@ -27,10 +28,12 @@ use identity_verification::jose::jws::CompactJwsEncoder; use identity_verification::jose::jws::CompactJwsEncodingOptions; use identity_verification::jose::jws::JwsAlgorithm; use identity_verification::jose::jws::JwsHeader; +use identity_verification::jwk::Algorithm; use identity_verification::jws::CharSet; use identity_verification::MethodData; use identity_verification::MethodScope; use identity_verification::VerificationMethod; +use jsonprooftoken::jpa::algs::ProofAlgorithm; use serde::de::DeserializeOwned; use serde::Serialize; @@ -68,6 +71,21 @@ pub trait JwkDocumentExt: private::Sealed { K: JwkStorage, I: KeyIdStorage; + + //TODO: new method that handle both JWS and JWP algorithm + /// Add new method to DID Document (handling JWS and JWP algorithms) + async fn generate_method_extended( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: Algorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage + JwkStorageExt, //TODO: to mantain + I: KeyIdStorage; + /// Remove the method identified by the given `id` from the document and delete the corresponding key material in /// the given `storage`. /// @@ -153,20 +171,20 @@ mod private { // copious amounts of repetition. // NOTE: If such use of macros becomes very common it is probably better to use the duplicate crate: https://docs.rs/duplicate/latest/duplicate/ macro_rules! generate_method_for_document_type { - ($t:ty, $name:ident) => { + ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { async fn $name( document: &mut $t, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithm, + alg: $a, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult where - K: JwkStorage, + K: $k, I: KeyIdStorage, { - let JwkGenOutput { key_id, jwk } = ::generate(&storage.key_storage(), key_type, alg) + let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg) .await .map_err(Error::KeyStorageError)?; @@ -304,7 +322,8 @@ macro_rules! purge_method_for_document_type { // CoreDocument // ==================================================================================================================== -generate_method_for_document_type!(CoreDocument, generate_method_core_document); +generate_method_for_document_type!(CoreDocument, JwsAlgorithm, JwkStorage, JwkStorage::generate, generate_method_core_document); +generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_core_document_ext); purge_method_for_document_type!(CoreDocument, purge_method_core_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -314,7 +333,7 @@ impl JwkDocumentExt for CoreDocument { &mut self, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithm, + alg: JwsAlgorithm, //TODO: changed to generic Algorithm fragment: Option<&str>, scope: MethodScope, ) -> StorageResult @@ -323,6 +342,27 @@ impl JwkDocumentExt for CoreDocument { I: KeyIdStorage, { generate_method_core_document(self, storage, key_type, alg, fragment, scope).await + + } + + + async fn generate_method_extended( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: Algorithm, //TODO: changed to generic Algorithm + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage + JwkStorageExt, + I: KeyIdStorage, + { + match alg { + Algorithm::JWS(a) => generate_method_core_document(self, storage, key_type, a, fragment, scope).await, + Algorithm::JWP(a) => generate_method_core_document_ext(self, storage, key_type, a, fragment, scope).await, + } + } async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> @@ -530,8 +570,14 @@ mod iota_document { use super::*; use identity_credential::credential::Jwt; use identity_iota_core::IotaDocument; +use jsonprooftoken::jpa::algs::ProofAlgorithm; + + + //TODO: change macro to handle both traits + generate_method_for_document_type!(IotaDocument, JwsAlgorithm, JwkStorage, JwkStorage::generate, generate_method_iota_document); + generate_method_for_document_type!(IotaDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_iota_document_ext); + - generate_method_for_document_type!(IotaDocument, generate_method_iota_document); purge_method_for_document_type!(IotaDocument, purge_method_iota_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -552,6 +598,26 @@ mod iota_document { generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await } + //TODO: generate_method_extended implementation for IotaDocument + async fn generate_method_extended( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: Algorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage + JwkStorageExt, + I: KeyIdStorage, + { + match alg { + Algorithm::JWS(a) => generate_method_iota_document(self, storage, key_type, a, fragment, scope).await, + Algorithm::JWP(a) => generate_method_iota_document_ext(self, storage, key_type, a, fragment, scope).await, + } + + } + async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> where K: JwkStorage, diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index b81fb15b05..7c6fffa093 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -1,203 +1,205 @@ //TODO:: JwpDocumentExt -use identity_document::document::CoreDocument; -use crate::Storage; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use crate::KeyType; -use identity_verification::MethodScope; -use crate::StorageResult; -use crate::JwkStorageExt; -use crate::KeyIdStorage; -use crate::JwkGenOutput; - -use crate::key_id_storage::KeyIdStorageResult; -use crate::key_id_storage::MethodDigest; -use crate::key_storage::JwkStorage; -use crate::key_storage::KeyId; -use crate::key_storage::KeyStorageResult; -use super::JwkStorageDocumentError as Error; -use super::JwsSignatureOptions; - -use async_trait::async_trait; -use identity_core::common::Object; -use identity_credential::credential::Credential; -use identity_credential::credential::Jws; -use identity_credential::credential::Jwt; -use identity_credential::presentation::JwtPresentationOptions; -use identity_credential::presentation::Presentation; -use identity_did::DIDUrl; -use identity_verification::jose::jws::CompactJwsEncoder; -use identity_verification::jose::jws::CompactJwsEncodingOptions; -use identity_verification::jose::jws::JwsAlgorithm; -use identity_verification::jose::jws::JwsHeader; -use identity_verification::jws::CharSet; -use identity_verification::MethodData; -use identity_verification::VerificationMethod; -use serde::de::DeserializeOwned; -use serde::Serialize; -use crate::try_undo_key_generation; - -macro_rules! generate_method_for_document_type { - ($t:ty, $name:ident) => { - async fn $name( - document: &mut $t, - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage, - { - let JwkGenOutput { key_id, jwk } = ::generate_bbs_key(&storage.key_storage(), key_type, alg) - .await - .map_err(Error::KeyStorageError)?; +// use identity_document::document::CoreDocument; +// use crate::Storage; +// use jsonprooftoken::jpa::algs::ProofAlgorithm; +// use crate::KeyType; +// use identity_verification::MethodScope; +// use crate::StorageResult; +// use crate::JwkStorageExt; +// use crate::KeyIdStorage; +// use crate::JwkGenOutput; + +// use crate::key_id_storage::KeyIdStorageResult; +// use crate::key_id_storage::MethodDigest; +// use crate::key_storage::JwkStorage; +// use crate::key_storage::KeyId; +// use crate::key_storage::KeyStorageResult; +// use super::JwkStorageDocumentError as Error; +// use super::JwsSignatureOptions; + +// use async_trait::async_trait; +// use identity_core::common::Object; +// use identity_credential::credential::Credential; +// use identity_credential::credential::Jws; +// use identity_credential::credential::Jwt; +// use identity_credential::presentation::JwtPresentationOptions; +// use identity_credential::presentation::Presentation; +// use identity_did::DIDUrl; +// use identity_verification::jose::jws::CompactJwsEncoder; +// use identity_verification::jose::jws::CompactJwsEncodingOptions; +// use identity_verification::jose::jws::JwsAlgorithm; +// use identity_verification::jose::jws::JwsHeader; +// use identity_verification::jws::CharSet; +// use identity_verification::MethodData; +// use identity_verification::VerificationMethod; +// use serde::de::DeserializeOwned; +// use serde::Serialize; +// use crate::try_undo_key_generation; + + +// macro_rules! generate_method_for_document_type { +// ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { +// async fn $name( +// document: &mut $t, +// storage: &Storage, +// key_type: KeyType, +// alg: $a, +// fragment: Option<&str>, +// scope: MethodScope, +// ) -> StorageResult +// where +// K: $k, +// I: KeyIdStorage, +// { +// let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg) +// .await +// .map_err(Error::KeyStorageError)?; + - // Produce a new verification method containing the generated JWK. If this operation fails we handle the error - // by attempting to revert key generation before returning an error. - let method: VerificationMethod = { - match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment) - .map_err(Error::VerificationMethodConstructionError) - { - Ok(method) => method, - Err(source) => { - return Err(try_undo_key_generation(storage, &key_id, source).await); - } - } - }; +// // Produce a new verification method containing the generated JWK. If this operation fails we handle the error +// // by attempting to revert key generation before returning an error. +// let method: VerificationMethod = { +// match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment) +// .map_err(Error::VerificationMethodConstructionError) +// { +// Ok(method) => method, +// Err(source) => { +// return Err(try_undo_key_generation(storage, &key_id, source).await); +// } +// } +// }; - // Extract data from method before inserting it into the DID document. - let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; - let method_id: DIDUrl = method.id().clone(); +// // Extract data from method before inserting it into the DID document. +// let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; +// let method_id: DIDUrl = method.id().clone(); - // The fragment is always set on a method, so this error will never occur. - let fragment: String = method_id - .fragment() - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)? - .to_owned(); +// // The fragment is always set on a method, so this error will never occur. +// let fragment: String = method_id +// .fragment() +// .ok_or(identity_verification::Error::MissingIdFragment) +// .map_err(Error::VerificationMethodConstructionError)? +// .to_owned(); - // Insert method into document and handle error upon failure. - if let Err(error) = document - .insert_method(method, scope) - .map_err(|_| Error::FragmentAlreadyExists) - { - return Err(try_undo_key_generation(storage, &key_id, error).await); - }; +// // Insert method into document and handle error upon failure. +// if let Err(error) = document +// .insert_method(method, scope) +// .map_err(|_| Error::FragmentAlreadyExists) +// { +// return Err(try_undo_key_generation(storage, &key_id, error).await); +// }; - // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the - // operation fails. - if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError) - { - // Remove the method from the document as it can no longer be used. - let _ = document.remove_method(&method_id); - return Err(try_undo_key_generation(storage, &key_id, error).await); - } +// // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the +// // operation fails. +// if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) +// .await +// .map_err(Error::KeyIdStorageError) +// { +// // Remove the method from the document as it can no longer be used. +// let _ = document.remove_method(&method_id); +// return Err(try_undo_key_generation(storage, &key_id, error).await); +// } - Ok(fragment) - } - }; -} - - - -/// Extension trait for JWK-based operations on DID documents. -/// -/// This trait is deliberately sealed and cannot be implemented by external crates. -/// The trait only exists as an extension of existing DID documents implemented in -/// dependent crates. Because those crates cannot also depend on this crate, -/// the extension trait is necessary. External crates however should simply wrap the methods -/// on the trait if they wish to reexport them on their DID document type. -/// This also allows them to use their own error type on those methods. -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -pub trait JwpDocumentExt { - /// Generate new key material in the given `storage` and insert a new verification method with the corresponding - /// public key material into the DID document. - /// - /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. - /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type - /// constants for that use case. - /// - /// The fragment of the generated method is returned. - async fn generate_method( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage; -} - - -// ==================================================================================================================== -// CoreDocument -// ==================================================================================================================== - - -generate_method_for_document_type!(CoreDocument, generate_method_core_document); - - -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -impl JwpDocumentExt for CoreDocument { - async fn generate_method( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage, - { - generate_method_core_document(self, storage, key_type, alg, fragment, scope).await - } - -} - - - - - -// ==================================================================================================================== -// IotaDocument -// ==================================================================================================================== -#[cfg(feature = "iota-document")] -mod iota_document { - use super::*; - use identity_credential::credential::Jwt; - use identity_iota_core::IotaDocument; - - generate_method_for_document_type!(IotaDocument, generate_method_iota_document); - - #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] - #[cfg_attr(feature = "send-sync-storage", async_trait)] - impl JwpDocumentExt for IotaDocument { - async fn generate_method( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage, - { - generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await - } - -} -} \ No newline at end of file +// Ok(fragment) +// } +// }; +// } + + + +// /// Extension trait for JWK-based operations on DID documents. +// /// +// /// This trait is deliberately sealed and cannot be implemented by external crates. +// /// The trait only exists as an extension of existing DID documents implemented in +// /// dependent crates. Because those crates cannot also depend on this crate, +// /// the extension trait is necessary. External crates however should simply wrap the methods +// /// on the trait if they wish to reexport them on their DID document type. +// /// This also allows them to use their own error type on those methods. +// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +// #[cfg_attr(feature = "send-sync-storage", async_trait)] +// pub trait JwpDocumentExt { +// /// Generate new key material in the given `storage` and insert a new verification method with the corresponding +// /// public key material into the DID document. +// /// +// /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. +// /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type +// /// constants for that use case. +// /// +// /// The fragment of the generated method is returned. +// async fn generate_method( +// &mut self, +// storage: &Storage, +// key_type: KeyType, +// alg: ProofAlgorithm, +// fragment: Option<&str>, +// scope: MethodScope, +// ) -> StorageResult +// where +// K: JwkStorageExt, +// I: KeyIdStorage; +// } + + +// // ==================================================================================================================== +// // CoreDocument +// // ==================================================================================================================== + + +// generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_core_document); + + +// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +// #[cfg_attr(feature = "send-sync-storage", async_trait)] +// impl JwpDocumentExt for CoreDocument { +// async fn generate_method( +// &mut self, +// storage: &Storage, +// key_type: KeyType, +// alg: ProofAlgorithm, +// fragment: Option<&str>, +// scope: MethodScope, +// ) -> StorageResult +// where +// K: JwkStorageExt, +// I: KeyIdStorage, +// { +// generate_method_core_document(self, storage, key_type, alg, fragment, scope).await +// } + +// } + + + + + +// // ==================================================================================================================== +// // IotaDocument +// // ==================================================================================================================== +// #[cfg(feature = "iota-document")] +// mod iota_document { +// use super::*; +// use identity_credential::credential::Jwt; +// use identity_iota_core::IotaDocument; + +// generate_method_for_document_type!(IotaDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_iota_document); + +// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +// #[cfg_attr(feature = "send-sync-storage", async_trait)] +// impl JwpDocumentExt for IotaDocument { +// async fn generate_method( +// &mut self, +// storage: &Storage, +// key_type: KeyType, +// alg: ProofAlgorithm, +// fragment: Option<&str>, +// scope: MethodScope, +// ) -> StorageResult +// where +// K: JwkStorageExt, +// I: KeyIdStorage, +// { +// generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await +// } + +// } +// } \ No newline at end of file From 7d9fee098f4c233ef1eaabe12d95505e4abfc517 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 16 Nov 2023 11:59:52 +0100 Subject: [PATCH 004/163] new trait JwpDocumentExt separated from JwkDocumentExt using macros defined in it --- examples/1_advanced/7_zkp.rs | 25 +- identity_jose/src/jwk/jwk_ext.rs | 6 - .../src/storage/jwk_document_ext.rs | 66 +---- .../src/storage/jwp_document_ext.rs | 234 ++++++++---------- identity_storage/src/storage/mod.rs | 2 + 5 files changed, 114 insertions(+), 219 deletions(-) diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 2cfaa821d1..8ed67f3cb2 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -8,12 +8,12 @@ use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; use identity_iota::storage::KeyIdStorage; use identity_iota::storage::Storage; -use identity_iota::verification::jwk::Algorithm; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; @@ -67,24 +67,15 @@ async fn main() -> anyhow::Result<()> { let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - document - .generate_method_extended( - &storage, - JwkMemStore::BLS12381SHA256_KEY_TYPE, - Algorithm::JWP(ProofAlgorithm::BLS12381_SHA256), - None, - MethodScope::VerificationMethod, - ) + + // It's ok like this or it is better to have different names so you can call the two function like this: + // document.generate_method(...) + // document.generate_method_ext(...) + + JwpDocumentExt::generate_method(&mut document, &storage, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256, None, MethodScope::VerificationMethod) .await?; - document - .generate_method_extended( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - Algorithm::JWS(JwsAlgorithm::EdDSA), - None, - MethodScope::VerificationMethod, - ) + JwkDocumentExt::generate_method(&mut document, &storage, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA, None, MethodScope::VerificationMethod) .await?; // Construct an Alias Output containing the DID document, with the wallet address diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs index bd00b93f8a..e4b3695db3 100644 --- a/identity_jose/src/jwk/jwk_ext.rs +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -87,9 +87,3 @@ impl TryFrom for Jwk { } } - -//Supported Algorithm for JSON Web Signatures and JSON Web Proofs -pub enum Algorithm { - JWS(JwsAlgorithm), - JWP(ProofAlgorithm) -} \ No newline at end of file diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index e6b4ff0e13..ede538da98 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -1,7 +1,6 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::JwkStorageExt; use crate::key_id_storage::KeyIdStorage; use crate::key_id_storage::KeyIdStorageResult; use crate::key_id_storage::MethodDigest; @@ -28,12 +27,10 @@ use identity_verification::jose::jws::CompactJwsEncoder; use identity_verification::jose::jws::CompactJwsEncodingOptions; use identity_verification::jose::jws::JwsAlgorithm; use identity_verification::jose::jws::JwsHeader; -use identity_verification::jwk::Algorithm; use identity_verification::jws::CharSet; use identity_verification::MethodData; use identity_verification::MethodScope; use identity_verification::VerificationMethod; -use jsonprooftoken::jpa::algs::ProofAlgorithm; use serde::de::DeserializeOwned; use serde::Serialize; @@ -71,21 +68,6 @@ pub trait JwkDocumentExt: private::Sealed { K: JwkStorage, I: KeyIdStorage; - - //TODO: new method that handle both JWS and JWP algorithm - /// Add new method to DID Document (handling JWS and JWP algorithms) - async fn generate_method_extended( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: Algorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorage + JwkStorageExt, //TODO: to mantain - I: KeyIdStorage; - /// Remove the method identified by the given `id` from the document and delete the corresponding key material in /// the given `storage`. /// @@ -170,7 +152,7 @@ mod private { // be implemented in terms of &mut CoreDocument for IotaDocument. To work around this limitation we use macros to avoid // copious amounts of repetition. // NOTE: If such use of macros becomes very common it is probably better to use the duplicate crate: https://docs.rs/duplicate/latest/duplicate/ -macro_rules! generate_method_for_document_type { +macro_rules! generate_method_for_document_type { //TODO: changed macro to handle both JwkDocumentExt and JwpDocumentExt traits ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { async fn $name( document: &mut $t, @@ -323,7 +305,6 @@ macro_rules! purge_method_for_document_type { // ==================================================================================================================== generate_method_for_document_type!(CoreDocument, JwsAlgorithm, JwkStorage, JwkStorage::generate, generate_method_core_document); -generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_core_document_ext); purge_method_for_document_type!(CoreDocument, purge_method_core_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -345,26 +326,6 @@ impl JwkDocumentExt for CoreDocument { } - - async fn generate_method_extended( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: Algorithm, //TODO: changed to generic Algorithm - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorage + JwkStorageExt, - I: KeyIdStorage, - { - match alg { - Algorithm::JWS(a) => generate_method_core_document(self, storage, key_type, a, fragment, scope).await, - Algorithm::JWP(a) => generate_method_core_document_ext(self, storage, key_type, a, fragment, scope).await, - } - - } - async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> where K: JwkStorage, @@ -570,14 +531,9 @@ mod iota_document { use super::*; use identity_credential::credential::Jwt; use identity_iota_core::IotaDocument; -use jsonprooftoken::jpa::algs::ProofAlgorithm; - //TODO: change macro to handle both traits generate_method_for_document_type!(IotaDocument, JwsAlgorithm, JwkStorage, JwkStorage::generate, generate_method_iota_document); - generate_method_for_document_type!(IotaDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_iota_document_ext); - - purge_method_for_document_type!(IotaDocument, purge_method_iota_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -598,26 +554,6 @@ use jsonprooftoken::jpa::algs::ProofAlgorithm; generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await } - //TODO: generate_method_extended implementation for IotaDocument - async fn generate_method_extended( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: Algorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorage + JwkStorageExt, - I: KeyIdStorage, - { - match alg { - Algorithm::JWS(a) => generate_method_iota_document(self, storage, key_type, a, fragment, scope).await, - Algorithm::JWP(a) => generate_method_iota_document_ext(self, storage, key_type, a, fragment, scope).await, - } - - } - async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> where K: JwkStorage, diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 7c6fffa093..503cf757c5 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -1,41 +1,20 @@ //TODO:: JwpDocumentExt -// use identity_document::document::CoreDocument; -// use crate::Storage; -// use jsonprooftoken::jpa::algs::ProofAlgorithm; -// use crate::KeyType; -// use identity_verification::MethodScope; -// use crate::StorageResult; -// use crate::JwkStorageExt; -// use crate::KeyIdStorage; -// use crate::JwkGenOutput; - -// use crate::key_id_storage::KeyIdStorageResult; -// use crate::key_id_storage::MethodDigest; -// use crate::key_storage::JwkStorage; -// use crate::key_storage::KeyId; -// use crate::key_storage::KeyStorageResult; -// use super::JwkStorageDocumentError as Error; -// use super::JwsSignatureOptions; - -// use async_trait::async_trait; -// use identity_core::common::Object; -// use identity_credential::credential::Credential; -// use identity_credential::credential::Jws; -// use identity_credential::credential::Jwt; -// use identity_credential::presentation::JwtPresentationOptions; -// use identity_credential::presentation::Presentation; -// use identity_did::DIDUrl; -// use identity_verification::jose::jws::CompactJwsEncoder; -// use identity_verification::jose::jws::CompactJwsEncodingOptions; -// use identity_verification::jose::jws::JwsAlgorithm; -// use identity_verification::jose::jws::JwsHeader; -// use identity_verification::jws::CharSet; -// use identity_verification::MethodData; -// use identity_verification::VerificationMethod; -// use serde::de::DeserializeOwned; -// use serde::Serialize; -// use crate::try_undo_key_generation; +use identity_document::document::CoreDocument; +use crate::Storage; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use crate::KeyType; +use identity_verification::MethodScope; +use crate::StorageResult; +use crate::JwkStorageExt; +use crate::KeyIdStorage; +use crate::JwkGenOutput; +use crate::key_id_storage::MethodDigest; +use super::JwkStorageDocumentError as Error; +use async_trait::async_trait; +use identity_did::DIDUrl; +use identity_verification::VerificationMethod; +use crate::try_undo_key_generation; // macro_rules! generate_method_for_document_type { @@ -107,99 +86,92 @@ -// /// Extension trait for JWK-based operations on DID documents. -// /// -// /// This trait is deliberately sealed and cannot be implemented by external crates. -// /// The trait only exists as an extension of existing DID documents implemented in -// /// dependent crates. Because those crates cannot also depend on this crate, -// /// the extension trait is necessary. External crates however should simply wrap the methods -// /// on the trait if they wish to reexport them on their DID document type. -// /// This also allows them to use their own error type on those methods. -// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -// #[cfg_attr(feature = "send-sync-storage", async_trait)] -// pub trait JwpDocumentExt { -// /// Generate new key material in the given `storage` and insert a new verification method with the corresponding -// /// public key material into the DID document. -// /// -// /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. -// /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type -// /// constants for that use case. -// /// -// /// The fragment of the generated method is returned. -// async fn generate_method( -// &mut self, -// storage: &Storage, -// key_type: KeyType, -// alg: ProofAlgorithm, -// fragment: Option<&str>, -// scope: MethodScope, -// ) -> StorageResult -// where -// K: JwkStorageExt, -// I: KeyIdStorage; -// } - - -// // ==================================================================================================================== -// // CoreDocument -// // ==================================================================================================================== - - -// generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_core_document); - - -// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -// #[cfg_attr(feature = "send-sync-storage", async_trait)] -// impl JwpDocumentExt for CoreDocument { -// async fn generate_method( -// &mut self, -// storage: &Storage, -// key_type: KeyType, -// alg: ProofAlgorithm, -// fragment: Option<&str>, -// scope: MethodScope, -// ) -> StorageResult -// where -// K: JwkStorageExt, -// I: KeyIdStorage, -// { -// generate_method_core_document(self, storage, key_type, alg, fragment, scope).await -// } - -// } - - - - -// // ==================================================================================================================== -// // IotaDocument -// // ==================================================================================================================== -// #[cfg(feature = "iota-document")] -// mod iota_document { -// use super::*; -// use identity_credential::credential::Jwt; -// use identity_iota_core::IotaDocument; - -// generate_method_for_document_type!(IotaDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_iota_document); - -// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -// #[cfg_attr(feature = "send-sync-storage", async_trait)] -// impl JwpDocumentExt for IotaDocument { -// async fn generate_method( -// &mut self, -// storage: &Storage, -// key_type: KeyType, -// alg: ProofAlgorithm, -// fragment: Option<&str>, -// scope: MethodScope, -// ) -> StorageResult -// where -// K: JwkStorageExt, -// I: KeyIdStorage, -// { -// generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await -// } - -// } -// } \ No newline at end of file +///New trait to handle JWP-based operations on DID Documents +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwpDocumentExt { + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. + /// + /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. + /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type + /// constants for that use case. + /// + /// The fragment of the generated method is returned. + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage; +} + + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + + +generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_core_document); + + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwpDocumentExt for CoreDocument { + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + { + generate_method_core_document(self, storage, key_type, alg, fragment, scope).await + } + +} + + + + + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use super::*; + use identity_iota_core::IotaDocument; + + generate_method_for_document_type!(IotaDocument, ProofAlgorithm, JwkStorageExt, JwkStorageExt::generate_bbs_key, generate_method_iota_document); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwpDocumentExt for IotaDocument { + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + { + generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await + } + +} +} \ No newline at end of file diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index e76a0146bd..7d3ebc14d7 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -4,6 +4,7 @@ //! This module provides a type wrapping a key and key id storage. mod error; +#[macro_use] //TODO: to be able to call the macro from jwp_document_ext.rs mod jwk_document_ext; mod jwp_document_ext; mod signature_options; @@ -11,6 +12,7 @@ mod signature_options; pub(crate) mod tests; pub use error::*; + pub use jwk_document_ext::*; pub use jwp_document_ext::*; pub use signature_options::*; From d948aeab71c8ddf5756526c093b537c25aa58148 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 16 Nov 2023 17:59:18 +0100 Subject: [PATCH 005/163] json-proof-token dependency update --- Cargo.toml | 2 +- examples/Cargo.toml | 2 +- identity_jose/Cargo.toml | 2 +- identity_storage/Cargo.toml | 2 +- identity_zk/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb86cace4a..30856145af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -jsonprooftoken = {version = "0.1.0", path = "../JOSE drafts/json-proof-token"} +json-proof-token = {version = "0.1.1"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4b24484fd5..cca0419caf 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,7 +14,7 @@ iota-sdk = { version = "1.0", default-features = false, features = ["tls", "clie primitive-types = "0.12.1" rand = "0.8.5" tokio = { version = "1.29", default-features = false, features = ["rt"] } -jsonprooftoken.workspace = true +json-proof-token.workspace = true [lib] path = "utils/utils.rs" diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 8bd4d52a2f..4755f6343c 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -19,7 +19,7 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] } subtle = { version = "2.5", default-features = false } thiserror.workspace = true zeroize = { version = "1.6", default-features = false, features = ["std", "zeroize_derive"] } -jsonprooftoken.workspace = true +json-proof-token.workspace = true [dev-dependencies] anyhow = "1" diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 81161fda59..8f50e37b68 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -27,7 +27,7 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } -jsonprooftoken.workspace = true +json-proof-token.workspace = true [dev-dependencies] diff --git a/identity_zk/Cargo.toml b/identity_zk/Cargo.toml index 9600afc935..39c463b427 100644 --- a/identity_zk/Cargo.toml +++ b/identity_zk/Cargo.toml @@ -15,6 +15,6 @@ rust-version.workspace = true [dependencies] serde.workspace = true serde_json.workspace = true -jsonprooftoken.workspace = true +json-proof-token.workspace = true hex = "0.4.3" identity_verification = { version = "=1.0.0", path = "../identity_verification", default_features = false } From db4672277bae694f96d25d0f4712b3edb12f6890 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 29 Nov 2023 16:07:56 +0100 Subject: [PATCH 006/163] From trait for JptClaims and create_credential_jwp implementation --- Cargo.toml | 2 +- examples/1_advanced/7_zkp.rs | 26 +- identity_credential/Cargo.toml | 1 + .../src/credential/credential.rs | 11 + identity_credential/src/credential/jpt.rs | 34 ++ .../src/credential/jwt_serialization.rs | 55 ++++ identity_credential/src/credential/mod.rs | 2 + identity_storage/src/storage/error.rs | 5 + .../src/storage/jwp_document_ext.rs | 291 +++++++++++++----- identity_storage/src/storage/jwp_options.rs | 124 ++++++++ identity_storage/src/storage/mod.rs | 3 + 11 files changed, 463 insertions(+), 91 deletions(-) create mode 100644 identity_credential/src/credential/jpt.rs create mode 100644 identity_storage/src/storage/jwp_options.rs diff --git a/Cargo.toml b/Cargo.toml index 30856145af..3f0380474b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.1.1"} +json-proof-token = {version = "0.1.4"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 8ed67f3cb2..6e7884e7b0 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -66,17 +66,21 @@ async fn main() -> anyhow::Result<()> { // Insert a new Ed25519 verification method in the DID document. let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - - // It's ok like this or it is better to have different names so you can call the two function like this: - // document.generate_method(...) - // document.generate_method_ext(...) - - JwpDocumentExt::generate_method(&mut document, &storage, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256, None, MethodScope::VerificationMethod) - .await?; - - JwkDocumentExt::generate_method(&mut document, &storage, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA, None, MethodScope::VerificationMethod) - .await?; + document.generate_method_jwp( + &storage, + JwkMemStore::BLS12381SHA256_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + None, + MethodScope::VerificationMethod + ).await?; + + document.generate_method( + &storage, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod + ).await?; // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 59633f516a..24034e2688 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -29,6 +29,7 @@ serde_repr = { version = "0.1", default-features = false, optional = true } strum.workspace = true thiserror.workspace = true url = { version = "2.4", default-features = false } +json-proof-token.workspace = true [dev-dependencies] identity_eddsa_verifier = { version = "=1.0.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } diff --git a/identity_credential/src/credential/credential.rs b/identity_credential/src/credential/credential.rs index decbb8b7c2..79ec1bd9f5 100644 --- a/identity_credential/src/credential/credential.rs +++ b/identity_credential/src/credential/credential.rs @@ -5,6 +5,7 @@ use core::fmt::Display; use core::fmt::Formatter; use identity_core::convert::ToJson; +use jsonprooftoken::jpt::claims::JptClaims; use once_cell::sync::Lazy; use serde::Deserialize; use serde::Serialize; @@ -174,6 +175,16 @@ impl Credential { .to_json() .map_err(|err| Error::JwtClaimsSetSerializationError(err.into())) } + + //TODO: new method serialization for credential + ///Serializes the [`Credential`] as a JPT claims set + pub fn serialize_jpt(&self, custom_claims: Option) -> Result + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + let jwt_representation: CredentialJwtClaims<'_, T> = CredentialJwtClaims::new(self, custom_claims)?; + Ok(jwt_representation.into()) + } } impl Display for Credential diff --git a/identity_credential/src/credential/jpt.rs b/identity_credential/src/credential/jpt.rs new file mode 100644 index 0000000000..4cbde948d6 --- /dev/null +++ b/identity_credential/src/credential/jpt.rs @@ -0,0 +1,34 @@ +use jsonprooftoken::jpt::claims::JptClaims; +use serde::Deserialize; +use serde::Serialize; + +use super::CredentialJwtClaims; + +///TODO: JPT +/// This JSON Proof Token could represent a JWP both in the Issued and Presented forms +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct Jpt(String); + +impl Jpt { + /// Creates a new `Jwt` from the given string. + pub fn new(jpt_string: String) -> Self { + Self(jpt_string) + } + + /// Returns a reference of the JWT string. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From for Jpt { + fn from(jpt: String) -> Self { + Self::new(jpt) + } +} + +impl From for String { + fn from(jpt: Jpt) -> Self { + jpt.0 + } +} \ No newline at end of file diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 8a9bc280e8..e2554b4f81 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; +use jsonprooftoken::jpt::claims::JptClaims; use serde::Deserialize; use serde::Serialize; @@ -676,3 +677,57 @@ mod tests { )); } } + + +//TODO: convert to JptClaims structure which basically contains the same claim names +impl<'credential, T> From> for JptClaims +where + T: ToOwned + Serialize, + ::Owned: DeserializeOwned, +{ + fn from(item: CredentialJwtClaims<'credential, T>) -> Self { + + let CredentialJwtClaims { + exp, + iss, + issuance_date, + jti, + sub, + vc, + custom + } = item; + + + let mut claims = JptClaims::new(); + + exp.map(|v| { + claims.set_exp(v); + }); + + claims.set_iss(iss.url().to_string()); + + issuance_date.iat.map(|v| { + claims.set_iat(v); + }); + + issuance_date.nbf.map(|v| { + claims.set_nbf(v); + }); + + jti.map(|v| { + claims.set_jti(v.to_string()); + }); + + sub.map(|v| { + claims.set_sub(v.to_string()); + }); + + claims.add_claim("vp", vc, true); + + custom.map(|v| { + claims.add_claim("", v, true); + }); + + claims + } +} \ No newline at end of file diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index efa20a3c87..060eadd148 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -11,6 +11,7 @@ mod evidence; mod issuer; mod jws; mod jwt; +mod jpt; mod jwt_serialization; mod linked_domain_service; mod policy; @@ -28,6 +29,7 @@ pub use self::evidence::Evidence; pub use self::issuer::Issuer; pub use self::jws::Jws; pub use self::jwt::Jwt; +pub use self::jpt::Jpt; pub use self::linked_domain_service::LinkedDomainService; pub use self::policy::Policy; pub use self::proof::Proof; diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index 7abac68286..52dbd0adbb 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -27,6 +27,11 @@ pub enum JwkStorageDocumentError { /// Caused by an invalid JWS algorithm. #[error("invalid JWS algorithm")] InvalidJwsAlgorithm, + + /// Caused by an invalid JWP algorithm. //TODO: new error + #[error("invalid JWP algorithm")] + InvalidJwpAlgorithm, + /// Caused by a failure to construct a verification method. #[error("method generation failed: unable to create a valid verification method")] VerificationMethodConstructionError(#[source] identity_verification::Error), diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 503cf757c5..f4972ece51 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -1,6 +1,19 @@ //TODO:: JwpDocumentExt +use identity_core::common::Object; +use identity_credential::credential::Credential; use identity_document::document::CoreDocument; +use identity_verification::MethodData; +use jsonprooftoken::jpt::claims; +use jsonprooftoken::jpt::claims::Claims; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jpt::payloads::Payloads; +use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use jsonprooftoken::jwp::issued::JwpIssued; +use jsonprooftoken::jwp::presented::JwpPresented; +use serde::Serialize; +use serde::de::DeserializeOwned; +use crate::JwpOptions; use crate::Storage; use jsonprooftoken::jpa::algs::ProofAlgorithm; use crate::KeyType; @@ -15,75 +28,7 @@ use async_trait::async_trait; use identity_did::DIDUrl; use identity_verification::VerificationMethod; use crate::try_undo_key_generation; - - -// macro_rules! generate_method_for_document_type { -// ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { -// async fn $name( -// document: &mut $t, -// storage: &Storage, -// key_type: KeyType, -// alg: $a, -// fragment: Option<&str>, -// scope: MethodScope, -// ) -> StorageResult -// where -// K: $k, -// I: KeyIdStorage, -// { -// let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg) -// .await -// .map_err(Error::KeyStorageError)?; - - -// // Produce a new verification method containing the generated JWK. If this operation fails we handle the error -// // by attempting to revert key generation before returning an error. -// let method: VerificationMethod = { -// match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment) -// .map_err(Error::VerificationMethodConstructionError) -// { -// Ok(method) => method, -// Err(source) => { -// return Err(try_undo_key_generation(storage, &key_id, source).await); -// } -// } -// }; - -// // Extract data from method before inserting it into the DID document. -// let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; -// let method_id: DIDUrl = method.id().clone(); - -// // The fragment is always set on a method, so this error will never occur. -// let fragment: String = method_id -// .fragment() -// .ok_or(identity_verification::Error::MissingIdFragment) -// .map_err(Error::VerificationMethodConstructionError)? -// .to_owned(); - -// // Insert method into document and handle error upon failure. -// if let Err(error) = document -// .insert_method(method, scope) -// .map_err(|_| Error::FragmentAlreadyExists) -// { -// return Err(try_undo_key_generation(storage, &key_id, error).await); -// }; - -// // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the -// // operation fails. -// if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) -// .await -// .map_err(Error::KeyIdStorageError) -// { -// // Remove the method from the document as it can no longer be used. -// let _ = document.remove_method(&method_id); -// return Err(try_undo_key_generation(storage, &key_id, error).await); -// } - -// Ok(fragment) -// } -// }; -// } - +use identity_credential::credential::Jpt; @@ -91,15 +36,10 @@ use crate::try_undo_key_generation; #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwpDocumentExt { + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding - /// public key material into the DID document. - /// - /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. - /// - The `key_type` must be compatible with the given `storage`. [`Storage`]s are expected to export key type - /// constants for that use case. - /// - /// The fragment of the generated method is returned. - async fn generate_method( + /// public key material into the DID document. This support BBS+ keys. + async fn generate_method_jwp( &mut self, storage: &Storage, key_type: KeyType, @@ -110,6 +50,48 @@ pub trait JwpDocumentExt { where K: JwkStorageExt, I: KeyIdStorage; + + + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage; + + + async fn create_presented_jwp( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage; + + /// Produces a JWP where the payload is produced from the given `credential`. + /// TODO: add references to Drafts + async fn create_credential_jwp( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync; + + + } @@ -124,7 +106,7 @@ generate_method_for_document_type!(CoreDocument, ProofAlgorithm, JwkStorageExt, #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwpDocumentExt for CoreDocument { - async fn generate_method( + async fn generate_method_jwp( &mut self, storage: &Storage, key_type: KeyType, @@ -139,6 +121,100 @@ impl JwpDocumentExt for CoreDocument { generate_method_core_document(self, storage, key_type, alg, fragment, scope).await } + + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + //Extract claims and payloads separated + let (claims, payloads) = jpt_claims.get_claims_and_payloads(); + + // Extract JwsAlgorithm. + let alg: ProofAlgorithm = jwk + .alg() + .unwrap_or("") + .parse() + .map_err(|_| Error::InvalidJwpAlgorithm)?; + + + let typ = if let Some(typ) = &options.typ { + typ.clone() + } else { + // https://www.w3.org/TR/vc-data-model/#jwt-encoding + "JWT".to_string() + }; + + let kid = if let Some(ref kid) = options.kid { + kid.clone() + } else { + method.id().to_string() + }; + + let issued_header = IssuerProtectedHeader{ + typ: Some(typ), + alg, + kid: Some(kid), + cid: None, + claims: Some(claims) + }; + + todo!() + + } + + + async fn create_presented_jwp( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + todo!() + } + + /// Produces a JWP where the payload is produced from the given `credential`. + /// TODO: add references to Drafts + async fn create_credential_jwp( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync + { + let jpt_claims = credential + .serialize_jpt(custom_claims) + .map_err(Error::ClaimsSerializationError)?; + + self + .create_issued_jwp(storage, fragment, &jpt_claims, options) + .await + .map(|jwp| Jpt::new(jwp)) + } + } @@ -158,7 +234,7 @@ mod iota_document { #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwpDocumentExt for IotaDocument { - async fn generate_method( + async fn generate_method_jwp( &mut self, storage: &Storage, key_type: KeyType, @@ -173,5 +249,62 @@ mod iota_document { generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await } + + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + self + .core_document() + .create_issued_jwp(storage, fragment, jpt_claims, options) + .await + } + + + async fn create_presented_jwp( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwpOptions, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + self + .core_document() + .create_presented_jwp(storage, fragment, payload, options) + .await + } + + /// Produces a JWP where the payload is produced from the given `credential`. + /// TODO: add references to Drafts + async fn create_credential_jwp( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync + { + self + .core_document() + .create_credential_jwp(credential, storage, fragment, options, custom_claims) + .await + } + } } \ No newline at end of file diff --git a/identity_storage/src/storage/jwp_options.rs b/identity_storage/src/storage/jwp_options.rs new file mode 100644 index 0000000000..e9d4df575f --- /dev/null +++ b/identity_storage/src/storage/jwp_options.rs @@ -0,0 +1,124 @@ + +use identity_core::common::Object; +use identity_core::common::Url; + +//TODO: have to choose which options makes sense in the context of jwp + +/// Options for creating a JSON Web Signature. +#[non_exhaustive] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct JwpOptions { + /// Whether to attach the public key in the corresponding method + /// to the JWS header. + pub attach_jwk: bool, + + /// Whether to Base64url encode the payload or not. + /// + /// [More Info](https://tools.ietf.org/html/rfc7797#section-3) + #[serde(skip_serializing_if = "Option::is_none")] + pub b64: Option, + + /// The Type value to be placed in the protected header. + /// + /// [More Info](https://tools.ietf.org/html/rfc7515#section-4.1.9) + #[serde(skip_serializing_if = "Option::is_none")] + pub typ: Option, + + /// Content Type to be placed in the protected header. + /// + /// [More Info](https://tools.ietf.org/html/rfc7515#section-4.1.10) + #[serde(skip_serializing_if = "Option::is_none")] + pub cty: Option, + + /// The URL to be placed in the protected header. + /// + /// [More Info](https://tools.ietf.org/html/rfc8555#section-6.4.1) + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, + + /// The nonce to be placed in the protected header. + /// + /// [More Info](https://tools.ietf.org/html/rfc8555#section-6.5.2) + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + /// The kid to set in the protected header. + /// + /// If unset, the kid of the JWK with which the JWS is produced is used. + /// + /// [More Info](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.4) + #[serde(skip_serializing_if = "Option::is_none")] + pub kid: Option, + + /// Whether the payload should be detached from the JWS. + /// + /// [More Info](https://www.rfc-editor.org/rfc/rfc7515#appendix-F). + pub detached_payload: bool, + + /// Additional header parameters. + #[serde(skip_serializing_if = "Option::is_none")] + pub custom_header_parameters: Option, +} + +impl JwpOptions { + /// Creates a new [`JwsSignatureOptions`]. + pub fn new() -> Self { + Self::default() + } + + /// Replace the value of the `attach_jwk` field. + pub fn attach_jwk_to_header(mut self, value: bool) -> Self { + self.attach_jwk = value; + self + } + + /// Replace the value of the `b64` field. + pub fn b64(mut self, value: bool) -> Self { + self.b64 = Some(value); + self + } + + /// Replace the value of the `typ` field. + pub fn typ(mut self, value: impl Into) -> Self { + self.typ = Some(value.into()); + self + } + + /// Replace the value of the `cty` field. + pub fn cty(mut self, value: impl Into) -> Self { + self.cty = Some(value.into()); + self + } + + /// Replace the value of the `url` field. + pub fn url(mut self, value: Url) -> Self { + self.url = Some(value); + self + } + + /// Replace the value of the `nonce` field. + pub fn nonce(mut self, value: impl Into) -> Self { + self.nonce = Some(value.into()); + self + } + + /// Replace the value of the `kid` field. + pub fn kid(mut self, value: impl Into) -> Self { + self.kid = Some(value.into()); + self + } + + /// Replace the value of the `detached_payload` field. + pub fn detached_payload(mut self, value: bool) -> Self { + self.detached_payload = value; + self + } + + /// Adds additional header parameters. + pub fn custom_header_parameters(mut self, value: Object) -> Self { + self.custom_header_parameters = Some(value); + self + } +} diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 7d3ebc14d7..7f8fbbbc6b 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -7,7 +7,9 @@ mod error; #[macro_use] //TODO: to be able to call the macro from jwp_document_ext.rs mod jwk_document_ext; mod jwp_document_ext; +mod jwp_options; mod signature_options; + #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -15,6 +17,7 @@ pub use error::*; pub use jwk_document_ext::*; pub use jwp_document_ext::*; +pub use jwp_options::*; pub use signature_options::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and From 21b9588d8cbd40d7e55d1219aa986817cf5e06ae Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 29 Nov 2023 16:09:35 +0100 Subject: [PATCH 007/163] new JwpOptions --- identity_storage/src/storage/jwp_options.rs | 69 +-------------------- 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/identity_storage/src/storage/jwp_options.rs b/identity_storage/src/storage/jwp_options.rs index e9d4df575f..bf4ec9abf6 100644 --- a/identity_storage/src/storage/jwp_options.rs +++ b/identity_storage/src/storage/jwp_options.rs @@ -10,15 +10,6 @@ use identity_core::common::Url; #[serde(rename_all = "camelCase")] #[serde(default)] pub struct JwpOptions { - /// Whether to attach the public key in the corresponding method - /// to the JWS header. - pub attach_jwk: bool, - - /// Whether to Base64url encode the payload or not. - /// - /// [More Info](https://tools.ietf.org/html/rfc7797#section-3) - #[serde(skip_serializing_if = "Option::is_none")] - pub b64: Option, /// The Type value to be placed in the protected header. /// @@ -26,18 +17,6 @@ pub struct JwpOptions { #[serde(skip_serializing_if = "Option::is_none")] pub typ: Option, - /// Content Type to be placed in the protected header. - /// - /// [More Info](https://tools.ietf.org/html/rfc7515#section-4.1.10) - #[serde(skip_serializing_if = "Option::is_none")] - pub cty: Option, - - /// The URL to be placed in the protected header. - /// - /// [More Info](https://tools.ietf.org/html/rfc8555#section-6.4.1) - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, - /// The nonce to be placed in the protected header. /// /// [More Info](https://tools.ietf.org/html/rfc8555#section-6.5.2) @@ -50,16 +29,7 @@ pub struct JwpOptions { /// /// [More Info](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.4) #[serde(skip_serializing_if = "Option::is_none")] - pub kid: Option, - - /// Whether the payload should be detached from the JWS. - /// - /// [More Info](https://www.rfc-editor.org/rfc/rfc7515#appendix-F). - pub detached_payload: bool, - - /// Additional header parameters. - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_header_parameters: Option, + pub kid: Option } impl JwpOptions { @@ -68,36 +38,12 @@ impl JwpOptions { Self::default() } - /// Replace the value of the `attach_jwk` field. - pub fn attach_jwk_to_header(mut self, value: bool) -> Self { - self.attach_jwk = value; - self - } - - /// Replace the value of the `b64` field. - pub fn b64(mut self, value: bool) -> Self { - self.b64 = Some(value); - self - } - /// Replace the value of the `typ` field. pub fn typ(mut self, value: impl Into) -> Self { self.typ = Some(value.into()); self } - /// Replace the value of the `cty` field. - pub fn cty(mut self, value: impl Into) -> Self { - self.cty = Some(value.into()); - self - } - - /// Replace the value of the `url` field. - pub fn url(mut self, value: Url) -> Self { - self.url = Some(value); - self - } - /// Replace the value of the `nonce` field. pub fn nonce(mut self, value: impl Into) -> Self { self.nonce = Some(value.into()); @@ -109,16 +55,5 @@ impl JwpOptions { self.kid = Some(value.into()); self } - - /// Replace the value of the `detached_payload` field. - pub fn detached_payload(mut self, value: bool) -> Self { - self.detached_payload = value; - self - } - - /// Adds additional header parameters. - pub fn custom_header_parameters(mut self, value: Object) -> Self { - self.custom_header_parameters = Some(value); - self - } + } From fb8e3038c9d641c9b4b0ffdbbbb3df8d1d388acc Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 30 Nov 2023 14:59:47 +0100 Subject: [PATCH 008/163] successful issuing of a jpt VC --- Cargo.toml | 2 +- alice.stronghold | Bin 0 -> 611 bytes examples/1_advanced/7_zkp.rs | 144 +++++++++++++----- examples/utils/utils.rs | 1 + .../src/key_storage/jwk_storage.rs | 6 + .../src/key_storage/key_storage_error.rs | 3 + identity_storage/src/key_storage/memstore.rs | 54 +++++++ .../src/storage/jwp_document_ext.rs | 15 +- issuer.stronghold | Bin 0 -> 609 bytes 9 files changed, 185 insertions(+), 40 deletions(-) create mode 100644 alice.stronghold create mode 100644 issuer.stronghold diff --git a/Cargo.toml b/Cargo.toml index 3f0380474b..af3a380bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.1.4"} +json-proof-token = {version = "0.1.6"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/alice.stronghold b/alice.stronghold new file mode 100644 index 0000000000000000000000000000000000000000..b85b8789ed02eda1993d875f9e7326d4b3bd1260 GIT binary patch literal 611 zcmV-p0-XI&K~hvn0{~%XWi4fHV{&ATc=#YiC4Dd2DMlFlH^ZbDUVQZP78G&wd` zQ*3rYdQmHGLQ8K7EiEk|c28q>XJv3oPGfX7ZEGP|JFh*Wlg|!~uX-m-EsY;`nCI|V z_7(4nzrRTUE#jMM5|bT^PK@@;Vr6U^64^hTMF3BNw_;ID#0;#cF3=MwOY~i-1i~Ba zbD8UKcp#*z7phq&5s(?@KZ68go>%`RCzXDQT3_g4AH6-yGO2S8?g8h5ytLbQU)aoc zn-#=@^l3Colyb)Ry`f$ndc-G@OdXj~UF7QMu6)6(ZXq$_BNWDN;Njs#Lhk|=rL xAkBJuS!!gAeQC1dwA48`ta5JE^Lwj+`}c-CZ!n#{KdRXOu);(ncN65wM1jdf4EO*5 literal 0 HcmV?d00001 diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 6e7884e7b0..a3d9b04df1 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -2,6 +2,14 @@ use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; +use identity_iota::core::FromJson; +use identity_iota::core::Url; +use identity_iota::core::json; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::Jpt; +use identity_iota::credential::Subject; +use identity_iota::did::DID; use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; @@ -11,8 +19,10 @@ use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwkStorage; +use identity_iota::storage::JwpOptions; use identity_iota::storage::KeyIdMemstore; use identity_iota::storage::KeyIdStorage; +use identity_iota::storage::KeyType; use identity_iota::storage::Storage; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; @@ -25,62 +35,45 @@ use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; use jsonprooftoken::jpa::algs::ProofAlgorithm; +// The API endpoint of an IOTA node, e.g. Hornet. +const api_endpoint: &str = "http://localhost:14265"; +// The faucet endpoint allows requesting funds for testing purposes. +const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; + + +// const api_endpoint: &str = "https://api.testnet.shimmer.network"; +// const faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; -/// Demonstrates how to create a DID Document and publish it in a new Alias Output. -/// -/// In this example we connect to a locally running private network, but it can be adapted -/// to run on any IOTA node by setting the network and faucet endpoints. -/// -/// See the following instructions on running your own private network -/// https://wiki.iota.org/hornet/develop/how_tos/private_tangle -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // The API endpoint of an IOTA node, e.g. Hornet. - let api_endpoint: &str = "https://api.testnet.shimmer.network"; - // The faucet endpoint allows requesting funds for testing purposes. - let faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(api_endpoint, None)? - .finish() - .await?; - // Create a new secret manager backed by a Stronghold. - let secret_manager: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password".to_owned())) - .build(random_stronghold_path())?, - ); +async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: ProofAlgorithm ) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; - + // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); - // Insert a new Ed25519 verification method in the DID document. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - document.generate_method_jwp( + let fragment = document.generate_method_jwp( &storage, - JwkMemStore::BLS12381SHA256_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, + key_type, + alg, None, MethodScope::VerificationMethod ).await?; - document.generate_method( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod - ).await?; + // issuer_document.generate_method( + // &storage, + // JwkMemStore::ED25519_KEY_TYPE, + // JwsAlgorithm::EdDSA, + // None, + // MethodScope::VerificationMethod + // ).await?; // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. @@ -90,5 +83,82 @@ async fn main() -> anyhow::Result<()> { let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; println!("Published DID document: {document:#}"); + Ok((address, document, fragment)) +} + + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(api_endpoint, None)? + .finish() + .await?; + + + let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password("issuer".to_owned()) + .build("./issuer.stronghold")?); + + // Insert a new Ed25519 verification method in the DID document. + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256).await?; + + + let mut secret_manager_alice = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password("alice".to_owned()) + .build("./alice.stronghold")?); + + // Insert a new Ed25519 verification method in the DID document. + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_alice, &storage_alice, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256_PROOF).await?; + + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jwp( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpOptions::default(), + None, + ) + .await?; + + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!("Sending credential (as JPT) to the holder: {}", credential_jpt.as_str()); + Ok(()) } diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index 41caa45c7f..110948855f 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -87,6 +87,7 @@ pub async fn get_address_with_funds( ) -> anyhow::Result
{ let address: Bech32Address = get_address(client, stronghold).await?; + request_faucet_funds(client, address, faucet_endpoint) .await .context("failed to request faucet funds")?; diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index bcf5617543..a4b0654459 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -8,6 +8,10 @@ use async_trait::async_trait; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jpt::payloads; +use jsonprooftoken::jpt::payloads::Payloads; +use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use jsonprooftoken::jwp::issued::JwpIssued; use super::jwk_gen_output::JwkGenOutput; @@ -72,4 +76,6 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { pub trait JwkStorageExt : JwkStorage { /// Generates a JWK representing a BBS+ signature async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult; + + async fn generate_issuer_proof(&self, key_id: &KeyId, jwp_issued: JwpIssued, public_key: &Jwk) -> KeyStorageResult; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/key_storage_error.rs b/identity_storage/src/key_storage/key_storage_error.rs index 59ff10968e..efcf8929b6 100644 --- a/identity_storage/src/key_storage/key_storage_error.rs +++ b/identity_storage/src/key_storage/key_storage_error.rs @@ -23,6 +23,8 @@ pub enum KeyStorageErrorKind { /// Indicates an attempt to parse a signature algorithm that is not recognized by the key storage implementation. UnsupportedSignatureAlgorithm, + UnsupportedProofAlgorithm, //TODO: new error + /// Indicates that the key storage implementation is not able to find the requested key. KeyNotFound, @@ -59,6 +61,7 @@ impl KeyStorageErrorKind { Self::UnsupportedKeyType => "key generation failed: the provided multikey schema is not supported", Self::KeyAlgorithmMismatch => "the key type cannot be used with the algorithm", Self::UnsupportedSignatureAlgorithm => "signing algorithm parsing failed", + Self::UnsupportedProofAlgorithm => "proof algorithm parsing failed", Self::KeyNotFound => "key not found in storage", Self::Unavailable => "key storage unavailable", Self::Unauthenticated => "authentication with the key storage failed", diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index c7a0840263..ae1eb1b836 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -12,10 +12,13 @@ use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jwk::curves::EllipticCurveTypes; use jsonprooftoken::jwk::key; use jsonprooftoken::jwk::key::Jwk as JwkExt; use jsonprooftoken::jwk::types::KeyPairSubtype; +use jsonprooftoken::jwp::issued::JwpIssued; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -285,6 +288,7 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> impl JwkStorageExt for JwkMemStore { async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult { let keysubtype = KeyPairSubtype::from_str(key_type.as_str()).map_err(|_| KeyStorageErrorKind::UnsupportedKeyType)?; + let mut jwk = Jwk::try_from(JwkExt::generate(keysubtype).map_err(|err| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(err))?) .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::RetryableIOFailure).with_source(err))?; @@ -299,6 +303,56 @@ impl JwkStorageExt for JwkMemStore { Ok(JwkGenOutput::new(kid, public_jwk)) } + + async fn generate_issuer_proof(&self, key_id: &KeyId, jwp_issued: JwpIssued, public_key: &Jwk) -> KeyStorageResult { + let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; + + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + match alg { + ProofAlgorithm::BLS12381_SHA256 | ProofAlgorithm::BLS12381_SHAKE256 => { + let okp_params = public_key.try_okp_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with Okp params in order to sign with {alg}")) + .with_source(err) + })?; + if okp_params.crv != EllipticCurveTypes::Bls12381G2.to_string() { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message(format!( + "expected Jwk with Okp {} crv in order to generate the proof with {alg}", + EllipticCurveTypes::Bls12381G2 + )), + ); + } + } + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + } + + // Obtain the corresponding private key and sign `data`. + let jwk: &Jwk = jwk_store + .get(key_id) + .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; + + // Serialize Jwk to JSON + let jwk_json = serde_json::to_string(&jwk).map_err(|_| KeyStorageErrorKind::SerializationError)?; + // Deserialize JSON to JwkExt + let jwk_ext: JwkExt = serde_json::from_str(&jwk_json).map_err(|_| KeyStorageErrorKind::SerializationError)?; + + let jwp = jwp_issued.encode(SerializationType::COMPACT, &jwk_ext).map_err(|_| KeyStorageErrorKind::Unspecified)?; + + Ok(jwp) + } } diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index f4972ece51..1346e7c35f 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -163,7 +163,7 @@ impl JwpDocumentExt for CoreDocument { method.id().to_string() }; - let issued_header = IssuerProtectedHeader{ + let issuer_header = IssuerProtectedHeader{ typ: Some(typ), alg, kid: Some(kid), @@ -171,7 +171,18 @@ impl JwpDocumentExt for CoreDocument { claims: Some(claims) }; - todo!() + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + let issued_jwp = JwpIssued::new(issuer_header, payloads); + + let jwp = ::generate_issuer_proof(storage.key_storage(), &key_id, issued_jwp, jwk) + .await + .map_err(Error::KeyStorageError)?; + Ok(jwp) } diff --git a/issuer.stronghold b/issuer.stronghold new file mode 100644 index 0000000000000000000000000000000000000000..46e0e7f8ab3da9cfaeb7c1a842b3b0c38146b4b8 GIT binary patch literal 609 zcmV-n0-pU)K~hvn0{~%XWi4fHV{&NGaPg8FS=BJeOR`PsJXKF`riuY@D7qgJ(j+a3;b35l0%)SCHB{32kvE3ytWUMQCS#*B~6fS{N z2fUZ-cqa;wlr6U3Y#S2lr>^+AiIbxCcmP7zXlgswbJl5`5v vzrJMCdCr#9rS@p>To$F`yRdydH&g)tXR^0z(&b|BjAzCYNd3lEq3MspvP=z* literal 0 HcmV?d00001 From 732e4c6262f5ee7603fce89490af90c144a45d36 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 30 Nov 2023 15:11:43 +0100 Subject: [PATCH 009/163] fix stronhold path --- alice.stronghold | Bin 611 -> 0 bytes examples/1_advanced/7_zkp.rs | 8 ++++---- issuer.stronghold | Bin 609 -> 0 bytes 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 alice.stronghold delete mode 100644 issuer.stronghold diff --git a/alice.stronghold b/alice.stronghold deleted file mode 100644 index b85b8789ed02eda1993d875f9e7326d4b3bd1260..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 611 zcmV-p0-XI&K~hvn0{~%XWi4fHV{&ATc=#YiC4Dd2DMlFlH^ZbDUVQZP78G&wd` zQ*3rYdQmHGLQ8K7EiEk|c28q>XJv3oPGfX7ZEGP|JFh*Wlg|!~uX-m-EsY;`nCI|V z_7(4nzrRTUE#jMM5|bT^PK@@;Vr6U^64^hTMF3BNw_;ID#0;#cF3=MwOY~i-1i~Ba zbD8UKcp#*z7phq&5s(?@KZ68go>%`RCzXDQT3_g4AH6-yGO2S8?g8h5ytLbQU)aoc zn-#=@^l3Colyb)Ry`f$ndc-G@OdXj~UF7QMu6)6(ZXq$_BNWDN;Njs#Lhk|=rL xAkBJuS!!gAeQC1dwA48`ta5JE^Lwj+`}c-CZ!n#{KdRXOu);(ncN65wM1jdf4EO*5 diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index a3d9b04df1..8606015e93 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -100,8 +100,8 @@ async fn main() -> anyhow::Result<()> { let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password("issuer".to_owned()) - .build("./issuer.stronghold")?); + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?); // Insert a new Ed25519 verification method in the DID document. let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); @@ -111,8 +111,8 @@ async fn main() -> anyhow::Result<()> { let mut secret_manager_alice = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password("alice".to_owned()) - .build("./alice.stronghold")?); + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?); // Insert a new Ed25519 verification method in the DID document. let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); diff --git a/issuer.stronghold b/issuer.stronghold deleted file mode 100644 index 46e0e7f8ab3da9cfaeb7c1a842b3b0c38146b4b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmV-n0-pU)K~hvn0{~%XWi4fHV{&NGaPg8FS=BJeOR`PsJXKF`riuY@D7qgJ(j+a3;b35l0%)SCHB{32kvE3ytWUMQCS#*B~6fS{N z2fUZ-cqa;wlr6U3Y#S2lr>^+AiIbxCcmP7zXlgswbJl5`5v vzrJMCdCr#9rS@p>To$F`yRdydH&g)tXR^0z(&b|BjAzCYNd3lEq3MspvP=z* From 234843f30e8575d92f4082616ea6528b7ac40747 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 30 Nov 2023 15:44:09 +0100 Subject: [PATCH 010/163] fix JPT type --- identity_storage/src/storage/jwp_document_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 1346e7c35f..5425fb2907 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -154,7 +154,7 @@ impl JwpDocumentExt for CoreDocument { typ.clone() } else { // https://www.w3.org/TR/vc-data-model/#jwt-encoding - "JWT".to_string() + "JPT".to_string() }; let kid = if let Some(ref kid) = options.kid { From e783430d3536496db885a2771e59d2cc3b5262ec Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 14 Dec 2023 15:14:32 +0100 Subject: [PATCH 011/163] update to latest version of json-proof-token library --- Cargo.toml | 2 +- examples/1_advanced/7_zkp.rs | 27 +- .../src/credential/credential.rs | 2 +- .../src/credential/jwt_serialization.rs | 4 +- identity_credential/src/error.rs | 8 + .../decoded_jpt_credential.rs | 13 + .../jpt_credential_validator.rs | 235 ++++++++++++++++++ .../jpt_credential_validation/mod.rs | 5 + .../jwt_credential_validation/error.rs | 10 + identity_credential/src/validator/mod.rs | 2 + identity_jose/src/jwk/jwk_ext.rs | 84 ++++++- .../src/key_storage/jwk_storage.rs | 6 +- identity_storage/src/key_storage/memstore.rs | 11 +- .../src/storage/jwp_document_ext.rs | 27 +- 14 files changed, 409 insertions(+), 27 deletions(-) create mode 100644 identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs create mode 100644 identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs create mode 100644 identity_credential/src/validator/jpt_credential_validation/mod.rs diff --git a/Cargo.toml b/Cargo.toml index af3a380bc2..fe2281b4ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.1.6"} +json-proof-token = {version = "0.2.5"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 8606015e93..d9f2cbf393 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -3,11 +3,15 @@ use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; use identity_iota::core::FromJson; +use identity_iota::core::Object; use identity_iota::core::Url; use identity_iota::core::json; use identity_iota::credential::Credential; use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::FailFast; use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JwtCredentialValidationOptions; use identity_iota::credential::Subject; use identity_iota::did::DID; use identity_iota::did::DIDUrl; @@ -33,6 +37,7 @@ use iota_sdk::client::Password; use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; +use iota_sdk::types::block::output::TokenId; use jsonprooftoken::jpa::algs::ProofAlgorithm; // The API endpoint of an IOTA node, e.g. Hornet. @@ -103,7 +108,7 @@ async fn main() -> anyhow::Result<()> { .password(Password::from("secure_password_1".to_owned())) .build(random_stronghold_path())?); - // Insert a new Ed25519 verification method in the DID document. + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = @@ -114,7 +119,7 @@ async fn main() -> anyhow::Result<()> { .password(Password::from("secure_password_2".to_owned())) .build(random_stronghold_path())?); - // Insert a new Ed25519 verification method in the DID document. + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = @@ -145,7 +150,7 @@ async fn main() -> anyhow::Result<()> { .build()?; let credential_jpt: Jpt = issuer_document - .create_credential_jwp( + .create_credential_jpt( &credential, &storage_issuer, &fragment_issuer, @@ -155,10 +160,26 @@ async fn main() -> anyhow::Result<()> { .await?; + // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + // =========================================================================== // Step 3: Issuer sends the Verifiable Credential to the holder. // =========================================================================== println!("Sending credential (as JPT) to the holder: {}", credential_jpt.as_str()); + + + Ok(()) } diff --git a/identity_credential/src/credential/credential.rs b/identity_credential/src/credential/credential.rs index 79ec1bd9f5..5c334d80d4 100644 --- a/identity_credential/src/credential/credential.rs +++ b/identity_credential/src/credential/credential.rs @@ -176,7 +176,7 @@ impl Credential { .map_err(|err| Error::JwtClaimsSetSerializationError(err.into())) } - //TODO: new method serialization for credential + //TODO: new method serialization for credential. The CredentialJwtClaims can be reused ///Serializes the [`Credential`] as a JPT claims set pub fn serialize_jpt(&self, custom_claims: Option) -> Result where diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index e2554b4f81..4f5fa4acfd 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -722,10 +722,10 @@ where claims.set_sub(v.to_string()); }); - claims.add_claim("vp", vc, true); + claims.set_claim(Some("vc"), vc, true); custom.map(|v| { - claims.add_claim("", v, true); + claims.set_claim(None, v, true); }); claims diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index 356d89d3d2..75acb18c22 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -68,4 +68,12 @@ pub enum Error { /// JSON. #[error("could not deserialize JWT claims set")] JwtClaimsSetDeserializationError(#[source] Box), + + + + //TODO: new error for jwp claims + /// Caused by a failure to deserialize the JPT claims set representation of a `Credential` JSON. + #[error("could not deserialize JWT claims set")] + JptClaimsSetDeserializationError(#[source] Box), + } diff --git a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs new file mode 100644 index 0000000000..1153fe065b --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs @@ -0,0 +1,13 @@ +use identity_core::common::Object; +use jsonprooftoken::jwp::issued::JwpIssued; + +use crate::credential::Credential; + +pub struct DecodedJptCredential { + /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). + pub credential: Credential, + /// The custom claims parsed from the JWT. + pub custom_claims: Option, + /// The decoded Issued Jwp, will be used to construct the Presented Jwp + pub decoded_jwp: JwpIssued +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs new file mode 100644 index 0000000000..d720c08661 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -0,0 +1,235 @@ +use identity_core::convert::{FromJson, ToJson}; +use identity_did::{DIDUrl, CoreDID}; +use identity_document::{document::CoreDocument, verifiable::JwsVerificationOptions}; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwk::key::Jwk as JwkExt; +use jsonprooftoken::jwp::issued::JwpIssuedVerifier; +use jsonprooftoken::{jwp::issued::JwpIssued, encoding::SerializationType}; + +use crate::credential::CredentialJwtClaims; +use crate::validator::{JwtCredentialValidatorUtils, JwtCredentialValidationOptions, CompoundCredentialValidationError}; +use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationError, jwt_credential_validation::SignerContext}}; + +use super::DecodedJptCredential; + +/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: validator +#[non_exhaustive] +pub struct JptCredentialValidator; + +impl JptCredentialValidator { + + + /// Decodes and validates a [`Credential`] issued as a JPT. A [`DecodedJptCredential`] is returned upon success. + /// + /// The following properties are validated according to `options`: + /// - the issuer's proof on the JWP, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + pub fn validate( + credential_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field + issuer: &DOC, + options: &JwtCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + Self::validate_extended::( + credential_jpt, + std::slice::from_ref(issuer.as_ref()), + options, + fail_fast, + ) + } + + + + + // This method takes a slice of issuer's instead of a single issuer in order to better accommodate presentation + // validation. It also validates the relationship between a holder and the credential subjects when + // `relationship_criterion` is Some. + pub(crate) fn validate_extended( + credential: &Jpt, + issuers: &[DOC], + options: &JwtCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + // First verify the JWS signature and decode the result into a credential token, then apply all other validations. + // If this errors we have to return early regardless of the `fail_fast` flag as all other validations require a + // `&Credential`. + let credential_token = + Self::verify_proof(credential, issuers, &options.verification_options) + .map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; + + let credential: &Credential = &credential_token.credential; + + // Run all single concern Credential validations in turn and fail immediately if `fail_fast` is true. + + let expiry_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_expires_on_or_after( + &credential_token.credential, + options.earliest_expiry_date.unwrap_or_default(), + ) + }); + + let issuance_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_issued_on_or_before( + credential, + options.latest_issuance_date.unwrap_or_default(), + ) + }); + + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); + + let subject_holder_validation = std::iter::once_with(|| { + options + .subject_holder_relationship + .as_ref() + .map(|(holder, relationship)| { + JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship) + }) + .unwrap_or(Ok(())) + }); + + let validation_units_iter = issuance_date_validation + .chain(expiry_date_validation) + .chain(structure_validation) + .chain(subject_holder_validation); + + #[cfg(feature = "revocation-bitmap")] + let validation_units_iter = { + let revocation_validation = + std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status)); + validation_units_iter.chain(revocation_validation) + }; + + let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); + let validation_errors: Vec = match fail_fast { + FailFast::FirstError => validation_units_error_iter.take(1).collect(), + FailFast::AllErrors => validation_units_error_iter.collect(), + }; + + if validation_errors.is_empty() { + Ok(credential_token) + } else { + Err(CompoundCredentialValidationError { validation_errors }) + } + } + + + +/// Stateless version of [`Self::verify_signature`] +fn verify_proof( + credential: &Jpt, + trusted_issuers: &[DOC], + options: &JwsVerificationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + + let decoded = JwpIssuedVerifier::decode(credential.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier + // of a verification method in a trusted issuer's DID document. + let method_id: DIDUrl = match &options.method_id { + Some(method_id) => method_id.clone(), + None => { + let kid: &str = decoded.get_header().kid().ok_or( + JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract kid from protected header", + signer_ctx: SignerContext::Issuer, + }, + )?; + + // Convert kid to DIDUrl + DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError { + source: Some(err.into()), + message: "could not parse kid as a DID Url", + signer_ctx: SignerContext::Issuer, + })? + } + }; + + // locate the corresponding issuer + let issuer: &CoreDocument = trusted_issuers + .iter() + .map(AsRef::as_ref) + .find(|issuer_doc| ::id(issuer_doc) == method_id.did()) + .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?; + + // Obtain the public key from the issuer's DID document + let public_key: JwkExt = issuer + .resolve_method(&method_id, options.method_scope) + .and_then(|method| method.data().public_key_jwk()) + .and_then(|k| k.try_into().ok()) //Conversio into jsonprooftoken::Jwk type + .ok_or_else(|| JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract JWK from a method identified by kid", + signer_ctx: SignerContext::Issuer, + })?; + + + let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?; + + // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before + // returning. + let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?; + if &issuer_id != method_id.did() { + return Err(JwtValidationError::IdentifierMismatch { + signer_ctx: SignerContext::Issuer, + }); + }; + Ok(credential_token) + } + + + /// Verify the signature using the given `public_key` and `signature_verifier`. + fn verify_decoded_jwp( + decoded: JwpIssuedVerifier, + public_key: &JwkExt, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + + //Verify Jwp proof + let decoded_jwp = decoded.verify(&public_key).map_err(|err| JwtValidationError::JwpProofVerifiationError(err))?; + + let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + let payloads = decoded_jwp.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, T> = + CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + let custom_claims = credential_claims.custom.clone(); + + // Construct the credential token containing the credential and the protected header. + let credential: Credential = credential_claims + .try_into_credential() + .map_err(JwtValidationError::CredentialStructure)?; + + Ok(DecodedJptCredential { + credential, + custom_claims, + decoded_jwp + }) + } + +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs new file mode 100644 index 0000000000..bd5072e107 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -0,0 +1,5 @@ +mod jpt_credential_validator; +mod decoded_jpt_credential; + +pub use jpt_credential_validator::*; +pub use decoded_jpt_credential::*; \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index c418db9676..0aecc293c2 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -101,6 +101,16 @@ pub enum JwtValidationError { /// Indicates that the credential has been revoked. #[error("credential has been revoked")] Revoked, + + + //TODO: new errors for jwp + /// Indicates that the JWS representation of an issued credential or presentation could not be decoded. + #[error("could not decode jwp")] + JwpDecodingError(#[source] jsonprooftoken::errors::CustomError), + + #[error("could not verify jwp")] + JwpProofVerifiationError(#[source] jsonprooftoken::errors::CustomError), + } /// Specifies whether an error is related to a credential issuer or the presentation holder. diff --git a/identity_credential/src/validator/mod.rs b/identity_credential/src/validator/mod.rs index c630f6c2ed..23b8198faf 100644 --- a/identity_credential/src/validator/mod.rs +++ b/identity_credential/src/validator/mod.rs @@ -5,10 +5,12 @@ pub use self::jwt_credential_validation::*; pub use self::jwt_presentation_validation::*; +pub use self::jpt_credential_validation::*; pub use self::options::FailFast; pub use self::options::StatusCheck; pub use self::options::SubjectHolderRelationship; +mod jpt_credential_validation; mod jwt_credential_validation; mod jwt_presentation_validation; mod options; diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs index e4b3695db3..7d2e27034a 100644 --- a/identity_jose/src/jwk/jwk_ext.rs +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use identity_core::common::Url; -use jsonprooftoken::{jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters}}, jpa::algs::ProofAlgorithm}; +use jsonprooftoken::{jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters, Algorithm}, types::KeyType, curves::EllipticCurveTypes}, jpa::algs::ProofAlgorithm}; use crate::jws::JwsAlgorithm; @@ -27,6 +27,23 @@ impl From for JwkOperation { } } +impl Into for JwkOperation { + fn into(self) -> KeyOps { + match self { + Self::Sign => KeyOps::Sign, + Self::Verify => KeyOps::Verify, + Self::Encrypt => KeyOps::Encrypt, + Self::Decrypt => KeyOps::Decrypt, + Self::WrapKey => KeyOps::WrapKey, + Self::UnwrapKey => KeyOps::UnwrapKey, + Self::DeriveKey => KeyOps::DeriveKey, + Self::DeriveBits => KeyOps::DeriveBits, + Self::ProofGeneration => KeyOps::ProofGeneration, + Self::ProofVerification => KeyOps::ProofVerification, + } + } +} + impl From for JwkUse { fn from(value: PKUse) -> Self { @@ -38,6 +55,16 @@ impl From for JwkUse { } } +impl Into for JwkUse { + fn into(self) -> PKUse { + match self { + Self::Signature => PKUse::Signature, + Self::Encryption => PKUse::Encryption, + Self::Proof => PKUse::Proof, + } + } +} + impl From for JwkParamsOkp { fn from(value: JwkOctetKeyPairParameters) -> Self { @@ -49,6 +76,20 @@ impl From for JwkParamsOkp { } } +impl TryInto for &JwkParamsOkp { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Ok(JwkOctetKeyPairParameters { + kty: KeyType::OctetKeyPair, + crv: EllipticCurveTypes::from_str(&self.crv).map_err(|_| { + return Self::Error::KeyError("Invalid crv!") })?, + x: self.x.clone(), + d: self.d.clone(), + }) + } +} + impl TryFrom for Jwk { type Error = crate::error::Error; @@ -87,3 +128,44 @@ impl TryFrom for Jwk { } } + +impl TryInto for &Jwk { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + + let params = match &self.params { + + JwkParams::Okp(p) => JwkAlgorithmParameters::OctetKeyPair(p.try_into()?), + _ => return Err(Self::Error::InvalidParam("Parameters not supported!")) + }; + + let alg = match &self.alg { + Some(a) => { + Some(Algorithm::Proof(ProofAlgorithm::from_str(&a).map_err(|_| Self::Error::KeyError("Invalid alg"))?)) + }, + None => None, + }; + + Ok(JwkExt{ + kid: self.kid.clone(), + pk_use: self.use_.and_then(|u| Some(u.into())), + key_ops: self.key_ops.as_deref().and_then(|vec_key_ops| { + vec_key_ops.iter().map(|o| Some((*o).into())).collect() + }), + alg: alg, + x5u: match &self.x5u { + Some(v) => { + Some( + v.as_str().to_string() + ) + }, + None => None, + }, + x5c: self.x5c.clone(), + x5t: self.x5t.clone(), + key_params: params + }) + + } +} diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index a4b0654459..6bd80150fe 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -5,13 +5,16 @@ use crate::key_storage::KeyId; use crate::key_storage::KeyStorageError; use crate::key_storage::KeyType; use async_trait::async_trait; +use identity_credential::credential::Issuer; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jpt::payloads; use jsonprooftoken::jpt::payloads::Payloads; use jsonprooftoken::jwp::header::IssuerProtectedHeader; use jsonprooftoken::jwp::issued::JwpIssued; +use jsonprooftoken::jwp::issued::JwpIssuedBuilder; use super::jwk_gen_output::JwkGenOutput; @@ -77,5 +80,6 @@ pub trait JwkStorageExt : JwkStorage { /// Generates a JWK representing a BBS+ signature async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult; - async fn generate_issuer_proof(&self, key_id: &KeyId, jwp_issued: JwpIssued, public_key: &Jwk) -> KeyStorageResult; + /// Generate the JPT representing a JWP in the Issuer form + async fn generate_issuer_proof(&self, key_id: &KeyId, header: IssuerProtectedHeader, claims: JptClaims, public_key: &Jwk) -> KeyStorageResult; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index ae1eb1b836..491e49f3ef 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -14,11 +14,14 @@ use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwk::curves::EllipticCurveTypes; use jsonprooftoken::jwk::key; use jsonprooftoken::jwk::key::Jwk as JwkExt; use jsonprooftoken::jwk::types::KeyPairSubtype; +use jsonprooftoken::jwp::header::IssuerProtectedHeader; use jsonprooftoken::jwp::issued::JwpIssued; +use jsonprooftoken::jwp::issued::JwpIssuedBuilder; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -304,7 +307,7 @@ impl JwkStorageExt for JwkMemStore { Ok(JwkGenOutput::new(kid, public_jwk)) } - async fn generate_issuer_proof(&self, key_id: &KeyId, jwp_issued: JwpIssued, public_key: &Jwk) -> KeyStorageResult { + async fn generate_issuer_proof(&self, key_id: &KeyId, header: IssuerProtectedHeader, claims: JptClaims, public_key: &Jwk) -> KeyStorageResult { let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; // Extract the required alg from the given public key @@ -349,7 +352,11 @@ impl JwkStorageExt for JwkMemStore { // Deserialize JSON to JwkExt let jwk_ext: JwkExt = serde_json::from_str(&jwk_json).map_err(|_| KeyStorageErrorKind::SerializationError)?; - let jwp = jwp_issued.encode(SerializationType::COMPACT, &jwk_ext).map_err(|_| KeyStorageErrorKind::Unspecified)?; + let jwp = JwpIssuedBuilder::new() + .issuer_protected_header(header) + .jpt_claims(claims) + .build(&jwk_ext).map_err(|_| KeyStorageErrorKind::Unspecified)? + .encode(SerializationType::COMPACT).map_err(|_| KeyStorageErrorKind::Unspecified)?; Ok(jwp) } diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 5425fb2907..2c6e15f0ac 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -10,6 +10,7 @@ use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jpt::payloads::Payloads; use jsonprooftoken::jwp::header::IssuerProtectedHeader; use jsonprooftoken::jwp::issued::JwpIssued; +use jsonprooftoken::jwp::issued::JwpIssuedBuilder; use jsonprooftoken::jwp::presented::JwpPresented; use serde::Serialize; use serde::de::DeserializeOwned; @@ -77,7 +78,7 @@ pub trait JwpDocumentExt { /// Produces a JWP where the payload is produced from the given `credential`. /// TODO: add references to Drafts - async fn create_credential_jwp( + async fn create_credential_jpt( &self, credential: &Credential, storage: &Storage, @@ -139,9 +140,6 @@ impl JwpDocumentExt for CoreDocument { return Err(Error::NotPublicKeyJwk); }; - //Extract claims and payloads separated - let (claims, payloads) = jpt_claims.get_claims_and_payloads(); - // Extract JwsAlgorithm. let alg: ProofAlgorithm = jwk .alg() @@ -163,13 +161,10 @@ impl JwpDocumentExt for CoreDocument { method.id().to_string() }; - let issuer_header = IssuerProtectedHeader{ - typ: Some(typ), - alg, - kid: Some(kid), - cid: None, - claims: Some(claims) - }; + let mut issuer_header = IssuerProtectedHeader::new(alg); + issuer_header.set_typ(Some(typ)); + issuer_header.set_kid(Some(kid)); + // Get the key identifier corresponding to the given method from the KeyId storage. let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; @@ -177,11 +172,11 @@ impl JwpDocumentExt for CoreDocument { .await .map_err(Error::KeyIdStorageError)?; - let issued_jwp = JwpIssued::new(issuer_header, payloads); - let jwp = ::generate_issuer_proof(storage.key_storage(), &key_id, issued_jwp, jwk) + let jwp = ::generate_issuer_proof(storage.key_storage(), &key_id, issuer_header, jpt_claims.clone(), jwk) .await .map_err(Error::KeyStorageError)?; + Ok(jwp) } @@ -203,7 +198,7 @@ impl JwpDocumentExt for CoreDocument { /// Produces a JWP where the payload is produced from the given `credential`. /// TODO: add references to Drafts - async fn create_credential_jwp( + async fn create_credential_jpt( &self, credential: &Credential, storage: &Storage, @@ -298,7 +293,7 @@ mod iota_document { /// Produces a JWP where the payload is produced from the given `credential`. /// TODO: add references to Drafts - async fn create_credential_jwp( + async fn create_credential_jpt( &self, credential: &Credential, storage: &Storage, @@ -313,7 +308,7 @@ mod iota_document { { self .core_document() - .create_credential_jwp(credential, storage, fragment, options, custom_claims) + .create_credential_jpt(credential, storage, fragment, options, custom_claims) .await } From 53051fec3f4dca70f624e320db16e1d384981098 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 15 Dec 2023 11:49:39 +0100 Subject: [PATCH 012/163] fix key conversion --- examples/1_advanced/7_zkp.rs | 2 +- .../jpt_credential_validation/decoded_jpt_credential.rs | 5 +++-- identity_storage/src/key_storage/memstore.rs | 5 ++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index d9f2cbf393..7f33fa0a3f 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -160,7 +160,7 @@ async fn main() -> anyhow::Result<()> { .await?; - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, // that the issuance date is not in the future and that the expiration date is not in the past: let decoded_jpt = JptCredentialValidator::validate::<_, Object>( &credential_jpt, diff --git a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs index 1153fe065b..93e9219583 100644 --- a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs +++ b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs @@ -3,11 +3,12 @@ use jsonprooftoken::jwp::issued::JwpIssued; use crate::credential::Credential; +/// Decoded [`Credential`] from a cryptographically verified JWP. pub struct DecodedJptCredential { /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). pub credential: Credential, - /// The custom claims parsed from the JWT. + /// The custom claims parsed from the JPT. pub custom_claims: Option, - /// The decoded Issued Jwp, will be used to construct the Presented Jwp + /// The decoded and verifier Issued JWP, will be used to construct the Presented JWP pub decoded_jwp: JwpIssued } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index df00e17740..52f7be0586 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -347,10 +347,9 @@ impl JwkStorageExt for JwkMemStore { .get(key_id) .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; - // Serialize Jwk to JSON - let jwk_json = serde_json::to_string(&jwk).map_err(|_| KeyStorageErrorKind::SerializationError)?; + // Deserialize JSON to JwkExt - let jwk_ext: JwkExt = serde_json::from_str(&jwk_json).map_err(|_| KeyStorageErrorKind::SerializationError)?; + let jwk_ext: JwkExt = jwk.try_into().map_err(|_| KeyStorageErrorKind::SerializationError)?; let jwp = JwpIssuedBuilder::new() .issuer_protected_header(header) From 4b9a1993ce297c3c1c5801b88a935614bc2f3d57 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 22 Dec 2023 17:34:57 +0100 Subject: [PATCH 013/163] alternative selective disclosure method --- Cargo.toml | 2 +- examples/1_advanced/7_zkp.rs | 51 ++++++++++++ identity_credential/src/error.rs | 3 + .../presentation/jwp_presentation_builder.rs | 81 +++++++++++++++++++ identity_credential/src/presentation/mod.rs | 2 + .../jpt_credential_validator.rs | 6 +- .../jpt_credential_validator_utils.rs | 47 +++++++++++ .../jpt_credential_validation/mod.rs | 4 +- .../src/storage/jwp_document_ext.rs | 35 +++++++- identity_storage/src/storage/jwp_options.rs | 14 +--- 10 files changed, 228 insertions(+), 17 deletions(-) create mode 100644 identity_credential/src/presentation/jwp_presentation_builder.rs create mode 100644 identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs diff --git a/Cargo.toml b/Cargo.toml index fe2281b4ad..257dc9307b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.2.5"} +json-proof-token = {version = "0.2.9"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 7f33fa0a3f..d7f7e825cf 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -1,4 +1,6 @@ +use std::str::FromStr; + use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; @@ -12,13 +14,16 @@ use identity_iota::credential::FailFast; use identity_iota::credential::Jpt; use identity_iota::credential::JptCredentialValidator; use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::SelectiveDiscosurePresentation; use identity_iota::credential::Subject; +use identity_iota::did::CoreDID; use identity_iota::did::DID; use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; @@ -39,6 +44,8 @@ use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; use iota_sdk::types::block::output::TokenId; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use jsonprooftoken::jwp::presented::JwpPresentedBuilder; // The API endpoint of an IOTA node, e.g. Hornet. const api_endpoint: &str = "http://localhost:14265"; @@ -134,6 +141,7 @@ async fn main() -> anyhow::Result<()> { let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], "degree": { "type": "BachelorDegree", "name": "Bachelor of Science and Arts", @@ -179,6 +187,49 @@ async fn main() -> anyhow::Result<()> { println!("Sending credential (as JPT) to the holder: {}", credential_jpt.as_str()); + // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decoded_credential = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + let issuer = CoreDID::from_str(decoded_credential.credential.issuer.url().as_str()).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + + let mut selective_disclosure_presentation = SelectiveDiscosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation.undisclose_subject("id").unwrap(); + selective_disclosure_presentation.undisclose_subject("mainCourses[1]").unwrap(); + selective_disclosure_presentation.undisclose_subject("degree.name").unwrap(); + + // selective_disclosure_presentation.set_undisclosed("id").unwrap(); + // selective_disclosure_presentation.set_undisclosed("vc.credentialSubject.id").unwrap(); + // selective_disclosure_presentation.set_undisclosed("vc.credentialSubject.mainCourses[1]").unwrap(); + + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + //TODO: TBD + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &selective_disclosure_presentation, + &JwpOptions::default().nonce(challenge) + ) + .await?; + + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!("Sending presentation (as JPT) to the verifier: {}", presentation_jpt.as_str()); Ok(()) diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index 75acb18c22..a64d7c1cd9 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -76,4 +76,7 @@ pub enum Error { #[error("could not deserialize JWT claims set")] JptClaimsSetDeserializationError(#[source] Box), + /// Cause by an invalid attribute path + #[error("Attribute Not found")] + SelectiveDiscosureError } diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs new file mode 100644 index 0000000000..c7adbe9ae2 --- /dev/null +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -0,0 +1,81 @@ +use jsonprooftoken::jwp::{presented::JwpPresentedBuilder, issued::JwpIssued}; +use crate::error::Error; +use crate::error::Result; + +//TODO: Used for Selective Disclosure +/// Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes +/// - @context MUST NOT be blinded +/// - id MUST be blinded +/// - type MUST NOT be blinded +/// - issuer MUST NOT be blinded +/// - issuanceDate MUST be blinded (only if Timeslot Revocation mechanism is used) +/// - expirationDate MUST be blinded (only if Timeslot Revocation mechanism is used) +/// - credentialSubject (User have to choose which attribute must be blinded) +/// - credentialSchema MUST NOT be blinded +/// - credentialStatus NO reason to use it in ZK VC (will be in any case blinded) +/// - refreshService MUST NOT be blinded (will be used for Timeslot Revocation mechanism) +/// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) +/// - evidence (User have to choose which attribute must be blinded) +pub struct SelectiveDiscosurePresentation { + jwp_builder: JwpPresentedBuilder +} + +impl SelectiveDiscosurePresentation { + + /// Inizialize a presentation starting from an Issued JWP + pub fn new(issued_jwp: &JwpIssued) -> Self { + let mut jwp_builder = JwpPresentedBuilder::new(issued_jwp); + + jwp_builder.set_undisclosed("id").ok(); + + jwp_builder.set_undisclosed("issuanceDate").ok(); + + jwp_builder.set_undisclosed("expirationDate").ok(); + + jwp_builder.set_undisclosed("credentialStatus").ok(); + + jwp_builder.set_undisclosed("termsOfUse").ok(); + + Self{jwp_builder} + } + + /// Selectively disclose "credentialSubject" attributes. + /// # Example + /// ``` + /// { + /// "id": 1234, + /// "name": "Alice", + /// "mainCourses": ["Object-oriented Programming", "Mathematics"], + /// "degree": { + /// "type": "BachelorDegree", + /// "name": "Bachelor of Science and Arts", + /// }, + /// "GPA": "4.0", + /// } + /// ``` + /// If you want to undisclose for example the Mathematics course and the name of the degree: + /// ``` + /// subject("mainCourses[1]"); + /// subject("degree.name"); + /// ``` + pub fn undisclose_subject(&mut self, path: &str) -> Result<(), Error>{ + let _ = self.jwp_builder.set_undisclosed(&("vc.credentialSubject.".to_owned() + path)).map_err(|_| Error::SelectiveDiscosureError); + Ok(()) + } + + /// Selectively disclose "evidence" attributes + pub fn undisclose_evidence(&mut self, path: &str) -> Result<(), Error> { + let _ = self.jwp_builder.set_undisclosed(&("vc.evidence.".to_owned() + path)).map_err(|_| Error::SelectiveDiscosureError); + Ok(()) + } + + + // Other option + + /// Undisclose a generic attribute passing its path + pub fn set_undisclosed(&mut self, path: &str) -> Result<()>{ + let _ = self.jwp_builder.set_undisclosed(path).map_err(|_| Error::SelectiveDiscosureError); + Ok(()) + } + +} \ No newline at end of file diff --git a/identity_credential/src/presentation/mod.rs b/identity_credential/src/presentation/mod.rs index 94f8768e02..cd7d0d836c 100644 --- a/identity_credential/src/presentation/mod.rs +++ b/identity_credential/src/presentation/mod.rs @@ -9,10 +9,12 @@ mod jwt_presentation_options; mod jwt_serialization; mod presentation; mod presentation_builder; +mod jwp_presentation_builder; pub use self::jwt_presentation_options::JwtPresentationOptions; pub use self::presentation::Presentation; pub use self::presentation_builder::PresentationBuilder; +pub use self::jwp_presentation_builder::SelectiveDiscosurePresentation; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::PresentationJwtClaims; diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index d720c08661..a911be3dde 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -3,7 +3,7 @@ use identity_did::{DIDUrl, CoreDID}; use identity_document::{document::CoreDocument, verifiable::JwsVerificationOptions}; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwk::key::Jwk as JwkExt; -use jsonprooftoken::jwp::issued::JwpIssuedVerifier; +use jsonprooftoken::jwp::issued::JwpIssuedDecoder; use jsonprooftoken::{jwp::issued::JwpIssued, encoding::SerializationType}; use crate::credential::CredentialJwtClaims; @@ -137,7 +137,7 @@ fn verify_proof( DOC: AsRef, { - let decoded = JwpIssuedVerifier::decode(credential.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; // If no method_url is set, parse the `kid` to a DID Url which should be the identifier // of a verification method in a trusted issuer's DID document. @@ -196,7 +196,7 @@ fn verify_proof( /// Verify the signature using the given `public_key` and `signature_verifier`. fn verify_decoded_jwp( - decoded: JwpIssuedVerifier, + decoded: JwpIssuedDecoder, public_key: &JwkExt, ) -> Result, JwtValidationError> where diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs new file mode 100644 index 0000000000..ae48a81993 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -0,0 +1,47 @@ +use std::str::FromStr; + +use identity_core::{convert::{ToJson, FromJson}, common::Object}; +use identity_did::DID; +use jsonprooftoken::{jwp::issued::JwpIssuedDecoder, jpt::claims::JptClaims, encoding::SerializationType}; + +use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, CredentialJwtClaims}}; + +/// Utility functions for verifying JPT credentials. +#[derive(Debug)] +#[non_exhaustive] +pub struct JptCredentialValidatorUtils; + +type ValidationUnitResult = std::result::Result; + +impl JptCredentialValidatorUtils { + + /// Utility for extracting the issuer field of a credential in JWT representation as DID. + /// + /// # Errors + /// + /// If the JWT decoding fails or the issuer field is not a valid DID. + pub fn extract_issuer_from_jpt(credential: &Jpt) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + + let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + let claims = decoded.get_header().claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + let payloads = decoded.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, Object> = + CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs index bd5072e107..dde9e4b3d6 100644 --- a/identity_credential/src/validator/jpt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -1,5 +1,7 @@ mod jpt_credential_validator; mod decoded_jpt_credential; +mod jpt_credential_validator_utils; pub use jpt_credential_validator::*; -pub use decoded_jpt_credential::*; \ No newline at end of file +pub use decoded_jpt_credential::*; +pub use jpt_credential_validator_utils::*; \ No newline at end of file diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 2c6e15f0ac..34506addda 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -2,6 +2,7 @@ use identity_core::common::Object; use identity_credential::credential::Credential; +use identity_credential::presentation::SelectiveDiscosurePresentation; use identity_document::document::CoreDocument; use identity_verification::MethodData; use jsonprooftoken::jpt::claims; @@ -76,7 +77,7 @@ pub trait JwpDocumentExt { K: JwkStorageExt, I: KeyIdStorage; - /// Produces a JWP where the payload is produced from the given `credential`. + /// Produces a JPT where the payload is produced from the given `credential`. /// TODO: add references to Drafts async fn create_credential_jpt( &self, @@ -92,6 +93,16 @@ pub trait JwpDocumentExt { T: ToOwned + Serialize + DeserializeOwned + Sync; + + /// Produces a JPT in the pre where the payload is produced from the given `credential`. + /// TODO: add references to Drafts + async fn create_presentation_jpt( + &self, + presentation: &SelectiveDiscosurePresentation, + options: &JwpOptions, + ) -> StorageResult; + + } @@ -196,7 +207,7 @@ impl JwpDocumentExt for CoreDocument { todo!() } - /// Produces a JWP where the payload is produced from the given `credential`. + /// Produces a JPT where the payload is produced from the given `credential`. /// TODO: add references to Drafts async fn create_credential_jpt( &self, @@ -221,6 +232,15 @@ impl JwpDocumentExt for CoreDocument { .map(|jwp| Jpt::new(jwp)) } + + async fn create_presentation_jpt( + &self, + presentation: &SelectiveDiscosurePresentation, + options: &JwpOptions, + ) -> StorageResult { + todo!() + } + } @@ -312,5 +332,16 @@ mod iota_document { .await } + + async fn create_presentation_jpt( + &self, + presentation: &SelectiveDiscosurePresentation, + options: &JwpOptions, + ) -> StorageResult { + self + .core_document() + .create_presentation_jpt(presentation, options) + .await + } } } \ No newline at end of file diff --git a/identity_storage/src/storage/jwp_options.rs b/identity_storage/src/storage/jwp_options.rs index bf4ec9abf6..0ca8dc3076 100644 --- a/identity_storage/src/storage/jwp_options.rs +++ b/identity_storage/src/storage/jwp_options.rs @@ -4,7 +4,7 @@ use identity_core::common::Url; //TODO: have to choose which options makes sense in the context of jwp -/// Options for creating a JSON Web Signature. +/// Options for creating a JSON Web Proof. #[non_exhaustive] #[derive(Debug, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Clone)] #[serde(rename_all = "camelCase")] @@ -12,22 +12,16 @@ use identity_core::common::Url; pub struct JwpOptions { /// The Type value to be placed in the protected header. - /// - /// [More Info](https://tools.ietf.org/html/rfc7515#section-4.1.9) #[serde(skip_serializing_if = "Option::is_none")] pub typ: Option, - /// The nonce to be placed in the protected header. - /// - /// [More Info](https://tools.ietf.org/html/rfc8555#section-6.5.2) + /// The nonce to be placed in the Presentation Protected Header. #[serde(skip_serializing_if = "Option::is_none")] pub nonce: Option, - /// The kid to set in the protected header. - /// - /// If unset, the kid of the JWK with which the JWS is produced is used. + /// The kid to set in the Issuer Protected Header. /// - /// [More Info](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.4) + /// If unset, the kid of the JWK with which the JWP is produced is used. #[serde(skip_serializing_if = "Option::is_none")] pub kid: Option } From a7fbe6b070e38952af5ae81f360e480089d34b3f Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 3 Jan 2024 17:47:08 +0100 Subject: [PATCH 014/163] Jwp Presentation creation --- Cargo.toml | 2 +- examples/1_advanced/7_zkp.rs | 50 ++++------ .../presentation/jwp_presentation_builder.rs | 15 +++ identity_storage/src/storage/error.rs | 3 + .../src/storage/jwp_document_ext.rs | 99 +++++++++++++------ 5 files changed, 107 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 257dc9307b..9d4e2c59cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.2.9"} +json-proof-token = {version = "0.3.0"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index d7f7e825cf..598e495ad1 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -13,6 +13,7 @@ use identity_iota::credential::CredentialBuilder; use identity_iota::credential::FailFast; use identity_iota::credential::Jpt; use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; use identity_iota::credential::JwtCredentialValidationOptions; use identity_iota::credential::SelectiveDiscosurePresentation; use identity_iota::credential::Subject; @@ -122,24 +123,12 @@ async fn main() -> anyhow::Result<()> { create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256).await?; - let mut secret_manager_alice = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?); - - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_alice, &storage_alice, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256_PROOF).await?; - - // =========================================================================== // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. // =========================================================================== // Create a credential subject indicating the degree earned by Alice. let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), "name": "Alice", "mainCourses": ["Object-oriented Programming", "Mathematics"], "degree": { @@ -184,7 +173,14 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== // Step 3: Issuer sends the Verifiable Credential to the holder. // =========================================================================== - println!("Sending credential (as JPT) to the holder: {}", credential_jpt.as_str()); + println!("Sending credential (as JPT) to the holder: {}\n", credential_jpt.as_str()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + // Resolve Issuer DID + let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_jpt(&credential_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented @@ -196,40 +192,30 @@ async fn main() -> anyhow::Result<()> { ) .unwrap(); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - - let issuer = CoreDID::from_str(decoded_credential.credential.issuer.url().as_str()).unwrap(); - let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; - + let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); let mut selective_disclosure_presentation = SelectiveDiscosurePresentation::new(&decoded_credential.decoded_jwp); - selective_disclosure_presentation.undisclose_subject("id").unwrap(); selective_disclosure_presentation.undisclose_subject("mainCourses[1]").unwrap(); selective_disclosure_presentation.undisclose_subject("degree.name").unwrap(); - // selective_disclosure_presentation.set_undisclosed("id").unwrap(); - // selective_disclosure_presentation.set_undisclosed("vc.credentialSubject.id").unwrap(); - // selective_disclosure_presentation.set_undisclosed("vc.credentialSubject.mainCourses[1]").unwrap(); - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - //TODO: TBD + let presentation_jpt: Jpt = issuer_document - .create_presentation_jpt( - &selective_disclosure_presentation, - &JwpOptions::default().nonce(challenge) - ) - .await?; + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpOptions::default().nonce(challenge) + ) + .await?; // =========================================================================== // Step 6: Holder sends a verifiable presentation to the verifier. // =========================================================================== - println!("Sending presentation (as JPT) to the verifier: {}", presentation_jpt.as_str()); + println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); Ok(()) diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index c7adbe9ae2..a06c3f0caa 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -1,3 +1,6 @@ +use identity_verification::jwk::Jwk; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use jsonprooftoken::jwp::presented::JwpPresented; use jsonprooftoken::jwp::{presented::JwpPresentedBuilder, issued::JwpIssued}; use crate::error::Error; use crate::error::Result; @@ -78,4 +81,16 @@ impl SelectiveDiscosurePresentation { Ok(()) } + + /// Set Presenation Protected Header + pub fn set_presentation_header(&mut self, ph: PresentationProtectedHeader) { + self.jwp_builder.presentation_protected_header(ph); + } + + + /// Get the builder + pub fn builder(&self) -> &JwpPresentedBuilder { + &self.jwp_builder + } + } \ No newline at end of file diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index 52dbd0adbb..c4ceaae490 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -31,6 +31,9 @@ pub enum JwkStorageDocumentError { /// Caused by an invalid JWP algorithm. //TODO: new error #[error("invalid JWP algorithm")] InvalidJwpAlgorithm, + /// Cannot cunstruct a valid Jwp (issued or presented form) + #[error("Not able to construct a valid Jwp")] + JwpBuildingError, /// Caused by a failure to construct a verification method. #[error("method generation failed: unable to create a valid verification method")] diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 34506addda..5612e7a600 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -2,16 +2,22 @@ use identity_core::common::Object; use identity_credential::credential::Credential; +use identity_credential::presentation; use identity_credential::presentation::SelectiveDiscosurePresentation; use identity_document::document::CoreDocument; use identity_verification::MethodData; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpa::algs::PresentationProofAlgorithm; use jsonprooftoken::jpt::claims; use jsonprooftoken::jpt::claims::Claims; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jpt::payloads::Payloads; +use jsonprooftoken::jwk::key::Jwk; use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; use jsonprooftoken::jwp::issued::JwpIssued; use jsonprooftoken::jwp::issued::JwpIssuedBuilder; +use jsonprooftoken::jwp::presented; use jsonprooftoken::jwp::presented::JwpPresented; use serde::Serialize; use serde::de::DeserializeOwned; @@ -26,6 +32,7 @@ use crate::KeyIdStorage; use crate::JwkGenOutput; use crate::key_id_storage::MethodDigest; use super::JwkStorageDocumentError as Error; +use super::jwp_options; use async_trait::async_trait; use identity_did::DIDUrl; use identity_verification::VerificationMethod; @@ -66,16 +73,12 @@ pub trait JwpDocumentExt { I: KeyIdStorage; - async fn create_presented_jwp( + async fn create_presented_jwp( &self, - storage: &Storage, - fragment: &str, - payload: &[u8], + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, - ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage; + ) -> StorageResult; /// Produces a JPT where the payload is produced from the given `credential`. /// TODO: add references to Drafts @@ -98,7 +101,8 @@ pub trait JwpDocumentExt { /// TODO: add references to Drafts async fn create_presentation_jpt( &self, - presentation: &SelectiveDiscosurePresentation, + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, ) -> StorageResult; @@ -193,18 +197,51 @@ impl JwpDocumentExt for CoreDocument { } - async fn create_presented_jwp( + async fn create_presented_jwp( &self, - storage: &Storage, - fragment: &str, - payload: &[u8], + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage { - todo!() + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(method_id, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + // Extract JwsAlgorithm. + let alg: ProofAlgorithm = jwk + .alg() + .unwrap_or("") + .parse() + .map_err(|_| Error::InvalidJwpAlgorithm)?; + + let public_key: Jwk = jwk.try_into().map_err(|_| Error::NotPublicKeyJwk)?; + + + + let kid = if let Some(ref kid) = options.kid { + kid.clone() + } else { + method.id().to_string() + }; + + + let mut presentation_header = PresentationProtectedHeader::new(alg.into()); + presentation_header.set_kid(Some(kid)); + presentation_header.set_nonce(options.nonce.clone()); + + + presentation.set_presentation_header(presentation_header); + + let jwp_builder = presentation.builder(); + + let presented_jwp = jwp_builder.build(&public_key).map_err(|_| Error::JwpBuildingError)?; + + Ok(presented_jwp.encode(SerializationType::COMPACT).map_err(|e| Error::EncodingError(Box::new(e)))?) + + } /// Produces a JPT where the payload is produced from the given `credential`. @@ -235,12 +272,19 @@ impl JwpDocumentExt for CoreDocument { async fn create_presentation_jpt( &self, - presentation: &SelectiveDiscosurePresentation, + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, ) -> StorageResult { - todo!() + + self + .create_presented_jwp(presentation, method_id, options) + .await + .map(|jwp| Jpt::new(jwp)) + } + } @@ -294,20 +338,16 @@ mod iota_document { } - async fn create_presented_jwp( + async fn create_presented_jwp( &self, - storage: &Storage, - fragment: &str, - payload: &[u8], + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, ) -> StorageResult - where - K: JwkStorageExt, - I: KeyIdStorage { self .core_document() - .create_presented_jwp(storage, fragment, payload, options) + .create_presented_jwp(presentation, method_id, options) .await } @@ -335,12 +375,13 @@ mod iota_document { async fn create_presentation_jpt( &self, - presentation: &SelectiveDiscosurePresentation, + presentation: &mut SelectiveDiscosurePresentation, + method_id: &str, options: &JwpOptions, ) -> StorageResult { self .core_document() - .create_presentation_jpt(presentation, options) + .create_presentation_jpt(presentation, method_id, options) .await } } From 9e5a595bf03d713b6dacb2bf9ca218217036dafc Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 8 Jan 2024 15:50:37 +0100 Subject: [PATCH 015/163] remove JwpOptions and introduce JptCredentialValidationOptions and JptPresentationValidationOptions --- examples/1_advanced/7_zkp.rs | 18 ++--- identity_credential/src/credential/jpt.rs | 2 - .../src/credential/jwp_credential_options.rs | 30 ++++++++ identity_credential/src/credential/mod.rs | 2 + .../presentation/jwp_presentation_builder.rs | 5 +- .../presentation/jwp_presentation_options.rs | 40 +++++++++++ identity_credential/src/presentation/mod.rs | 4 +- .../jpt_credential_validation_options.rs | 58 ++++++++++++++++ .../jpt_credential_validator.rs | 42 +++++------- .../jpt_credential_validator_utils.rs | 6 +- .../jpt_credential_validation/mod.rs | 4 +- .../jpt_presentation_validation_options.rs | 68 +++++++++++++++++++ .../jpt_presentation_validator.rs | 40 +++++++++++ .../jpt_presentation_validator_utils.rs | 45 ++++++++++++ .../jpt_presentation_validation/mod.rs | 7 ++ identity_credential/src/validator/mod.rs | 2 + .../verifiable/jwp_verification_options.rs | 36 ++++++++++ identity_document/src/verifiable/mod.rs | 2 + .../src/storage/jwp_document_ext.rs | 62 +++++++---------- identity_storage/src/storage/jwp_options.rs | 53 --------------- identity_storage/src/storage/mod.rs | 2 - 21 files changed, 394 insertions(+), 134 deletions(-) create mode 100644 identity_credential/src/credential/jwp_credential_options.rs create mode 100644 identity_credential/src/presentation/jwp_presentation_options.rs create mode 100644 identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs create mode 100644 identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs create mode 100644 identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs create mode 100644 identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs create mode 100644 identity_credential/src/validator/jpt_presentation_validation/mod.rs create mode 100644 identity_document/src/verifiable/jwp_verification_options.rs delete mode 100644 identity_storage/src/storage/jwp_options.rs diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 598e495ad1..d3412a533f 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -14,8 +14,10 @@ use identity_iota::credential::FailFast; use identity_iota::credential::Jpt; use identity_iota::credential::JptCredentialValidator; use identity_iota::credential::JptCredentialValidatorUtils; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::SelectiveDiscosurePresentation; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::SelectiveDisclosurePresentation; use identity_iota::credential::Subject; use identity_iota::did::CoreDID; use identity_iota::did::DID; @@ -29,11 +31,11 @@ use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwkStorage; -use identity_iota::storage::JwpOptions; use identity_iota::storage::KeyIdMemstore; use identity_iota::storage::KeyIdStorage; use identity_iota::storage::KeyType; use identity_iota::storage::Storage; +use identity_iota::verification::MethodRelationship; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; @@ -151,7 +153,7 @@ async fn main() -> anyhow::Result<()> { &credential, &storage_issuer, &fragment_issuer, - &JwpOptions::default(), + &JwpCredentialOptions::default(), None, ) .await?; @@ -162,7 +164,7 @@ async fn main() -> anyhow::Result<()> { let decoded_jpt = JptCredentialValidator::validate::<_, Object>( &credential_jpt, &issuer_document, - &JwtCredentialValidationOptions::default(), + &JptCredentialValidationOptions::default(), FailFast::FirstError, ) .unwrap(); @@ -187,14 +189,14 @@ async fn main() -> anyhow::Result<()> { let decoded_credential = JptCredentialValidator::validate::<_, Object>( &credential_jpt, &issuer_document, - &JwtCredentialValidationOptions::default(), + &JptCredentialValidationOptions::default(), FailFast::FirstError, ) .unwrap(); let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); - let mut selective_disclosure_presentation = SelectiveDiscosurePresentation::new(&decoded_credential.decoded_jwp); + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); selective_disclosure_presentation.undisclose_subject("mainCourses[1]").unwrap(); selective_disclosure_presentation.undisclose_subject("degree.name").unwrap(); @@ -207,7 +209,7 @@ async fn main() -> anyhow::Result<()> { .create_presentation_jpt( &mut selective_disclosure_presentation, method_id, - &JwpOptions::default().nonce(challenge) + &JwpPresentationOptions::default().nonce(challenge) ) .await?; diff --git a/identity_credential/src/credential/jpt.rs b/identity_credential/src/credential/jpt.rs index 4cbde948d6..dfce486c30 100644 --- a/identity_credential/src/credential/jpt.rs +++ b/identity_credential/src/credential/jpt.rs @@ -1,8 +1,6 @@ -use jsonprooftoken::jpt::claims::JptClaims; use serde::Deserialize; use serde::Serialize; -use super::CredentialJwtClaims; ///TODO: JPT /// This JSON Proof Token could represent a JWP both in the Issued and Presented forms diff --git a/identity_credential/src/credential/jwp_credential_options.rs b/identity_credential/src/credential/jwp_credential_options.rs new file mode 100644 index 0000000000..11c269bacc --- /dev/null +++ b/identity_credential/src/credential/jwp_credential_options.rs @@ -0,0 +1,30 @@ + +//TODO: JwpCredentialOptions -> have to choose which options makes sense in the context of jwp + +/// Options for creating a JSON Web Proof. +#[non_exhaustive] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct JwpCredentialOptions { + + /// The kid to set in the Issuer Protected Header. + /// + /// If unset, the kid of the JWK with which the JWP is produced is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub kid: Option +} + +impl JwpCredentialOptions { + /// Creates a new [`JwsSignatureOptions`]. + pub fn new() -> Self { + Self::default() + } + + /// Replace the value of the `kid` field. + pub fn kid(mut self, value: impl Into) -> Self { + self.kid = Some(value.into()); + self + } + +} diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 060eadd148..7f7451161b 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -22,6 +22,7 @@ mod revocation_bitmap_status; mod schema; mod status; mod subject; +mod jwp_credential_options; pub use self::builder::CredentialBuilder; pub use self::credential::Credential; @@ -39,6 +40,7 @@ pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; +pub use self::jwp_credential_options::JwpCredentialOptions; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::CredentialJwtClaims; diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index a06c3f0caa..af1fd1f288 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -19,11 +19,12 @@ use crate::error::Result; /// - refreshService MUST NOT be blinded (will be used for Timeslot Revocation mechanism) /// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) /// - evidence (User have to choose which attribute must be blinded) -pub struct SelectiveDiscosurePresentation { +/// TODO: change this(JwpPresentedBuilder should be constructed inside create_presentation_jwp()) +pub struct SelectiveDisclosurePresentation { jwp_builder: JwpPresentedBuilder } -impl SelectiveDiscosurePresentation { +impl SelectiveDisclosurePresentation { /// Inizialize a presentation starting from an Issued JWP pub fn new(issued_jwp: &JwpIssued) -> Self { diff --git a/identity_credential/src/presentation/jwp_presentation_options.rs b/identity_credential/src/presentation/jwp_presentation_options.rs new file mode 100644 index 0000000000..8191d1fcb6 --- /dev/null +++ b/identity_credential/src/presentation/jwp_presentation_options.rs @@ -0,0 +1,40 @@ +use identity_core::common::Url; +use serde::{Serialize, Deserialize}; + + +/// Options to be set in the JWT claims of a verifiable presentation. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JwpPresentationOptions { + /// Sets the audience for presentation (`aud` property in JWP Presentation Header). + /// Default: `None`. + pub audience: Option, + + /// The nonce to be placed in the Presentation Protected Header. + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, +} + +impl JwpPresentationOptions { + + /// Sets the audience for presentation (`aud` property in JWT claims). + pub fn audience(mut self, audience: Url) -> Self { + self.audience = Some(audience); + self + } + + /// Replace the value of the `nonce` field. + pub fn nonce(mut self, value: impl Into) -> Self { + self.nonce = Some(value.into()); + self + } +} + +impl Default for JwpPresentationOptions { + fn default() -> Self { + Self { + audience: None, + nonce: None + } + } +} diff --git a/identity_credential/src/presentation/mod.rs b/identity_credential/src/presentation/mod.rs index cd7d0d836c..7764fdd946 100644 --- a/identity_credential/src/presentation/mod.rs +++ b/identity_credential/src/presentation/mod.rs @@ -10,11 +10,13 @@ mod jwt_serialization; mod presentation; mod presentation_builder; mod jwp_presentation_builder; +mod jwp_presentation_options; pub use self::jwt_presentation_options::JwtPresentationOptions; pub use self::presentation::Presentation; pub use self::presentation_builder::PresentationBuilder; -pub use self::jwp_presentation_builder::SelectiveDiscosurePresentation; +pub use self::jwp_presentation_builder::SelectiveDisclosurePresentation; +pub use jwp_presentation_options::JwpPresentationOptions; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::PresentationJwtClaims; diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs new file mode 100644 index 0000000000..5d0c9e8664 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -0,0 +1,58 @@ + +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_document::verifiable::JwpVerificationOptions; +use identity_document::verifiable::JwsVerificationOptions; +use serde::Deserialize; +use serde::Serialize; + +use crate::validator::SubjectHolderRelationship; + +/// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. +#[non_exhaustive] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JptCredentialValidationOptions { + /// Declares that the credential is **not** considered valid if it expires before this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub earliest_expiry_date: Option, + + /// Declares that the credential is **not** considered valid if it was issued later than this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub latest_issuance_date: Option, + + /// Options which affect the verification of the proof on the credential. + #[serde(default)] + pub verification_options: JwpVerificationOptions, +} + +impl JptCredentialValidationOptions { + /// Constructor that sets all options to their defaults. + pub fn new() -> Self { + Self::default() + } + + /// Declare that the credential is **not** considered valid if it expires before this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn earliest_expiry_date(mut self, timestamp: Timestamp) -> Self { + self.earliest_expiry_date = Some(timestamp); + self + } + + /// Declare that the credential is **not** considered valid if it was issued later than this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn latest_issuance_date(mut self, timestamp: Timestamp) -> Self { + self.latest_issuance_date = Some(timestamp); + self + } + + /// Set options which affect the verification of the JWS signature. + pub fn verification_options(mut self, options: JwpVerificationOptions) -> Self { + self.verification_options = options; + self + } +} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index a911be3dde..ab9a5f3fa6 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -1,5 +1,6 @@ use identity_core::convert::{FromJson, ToJson}; use identity_did::{DIDUrl, CoreDID}; +use identity_document::verifiable::JwpVerificationOptions; use identity_document::{document::CoreDocument, verifiable::JwsVerificationOptions}; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwk::key::Jwk as JwkExt; @@ -7,12 +8,12 @@ use jsonprooftoken::jwp::issued::JwpIssuedDecoder; use jsonprooftoken::{jwp::issued::JwpIssued, encoding::SerializationType}; use crate::credential::CredentialJwtClaims; -use crate::validator::{JwtCredentialValidatorUtils, JwtCredentialValidationOptions, CompoundCredentialValidationError}; +use crate::validator::{JwtCredentialValidatorUtils, JptCredentialValidationOptions, CompoundCredentialValidationError}; use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationError, jwt_credential_validation::SignerContext}}; use super::DecodedJptCredential; -/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: validator +/// A type for decoding and validating [`Credential`]s in JPT format representing a JWP in Issued form.. //TODO: validator #[non_exhaustive] pub struct JptCredentialValidator; @@ -29,7 +30,7 @@ impl JptCredentialValidator { pub fn validate( credential_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field issuer: &DOC, - options: &JwtCredentialValidationOptions, + options: &JptCredentialValidationOptions, fail_fast: FailFast, ) -> Result, CompoundCredentialValidationError> where @@ -53,7 +54,7 @@ impl JptCredentialValidator { pub(crate) fn validate_extended( credential: &Jpt, issuers: &[DOC], - options: &JwtCredentialValidationOptions, + options: &JptCredentialValidationOptions, fail_fast: FailFast, ) -> Result, CompoundCredentialValidationError> where @@ -89,27 +90,20 @@ impl JptCredentialValidator { let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); - let subject_holder_validation = std::iter::once_with(|| { - options - .subject_holder_relationship - .as_ref() - .map(|(holder, relationship)| { - JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship) - }) - .unwrap_or(Ok(())) - }); let validation_units_iter = issuance_date_validation .chain(expiry_date_validation) - .chain(structure_validation) - .chain(subject_holder_validation); - - #[cfg(feature = "revocation-bitmap")] - let validation_units_iter = { - let revocation_validation = - std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status)); - validation_units_iter.chain(revocation_validation) - }; + .chain(structure_validation); + + + //TODO: check revocation when implemented + + // #[cfg(feature = "revocation-bitmap")] + // let validation_units_iter = { + // let revocation_validation = + // std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status)); + // validation_units_iter.chain(revocation_validation) + // }; let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); let validation_errors: Vec = match fail_fast { @@ -126,11 +120,11 @@ impl JptCredentialValidator { -/// Stateless version of [`Self::verify_signature`] +/// Proof verification function fn verify_proof( credential: &Jpt, trusted_issuers: &[DOC], - options: &JwsVerificationOptions, + options: &JwpVerificationOptions, ) -> Result, JwtValidationError> where T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index ae48a81993..de9102bb1a 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -11,15 +11,13 @@ use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, Cr #[non_exhaustive] pub struct JptCredentialValidatorUtils; -type ValidationUnitResult = std::result::Result; - impl JptCredentialValidatorUtils { - /// Utility for extracting the issuer field of a credential in JWT representation as DID. + /// Utility for extracting the issuer field of a credential in JPT representation as DID. /// /// # Errors /// - /// If the JWT decoding fails or the issuer field is not a valid DID. + /// If the JPT decoding fails or the issuer field is not a valid DID. pub fn extract_issuer_from_jpt(credential: &Jpt) -> std::result::Result where D: DID, diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs index dde9e4b3d6..e90e67fae9 100644 --- a/identity_credential/src/validator/jpt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -1,7 +1,9 @@ mod jpt_credential_validator; mod decoded_jpt_credential; mod jpt_credential_validator_utils; +mod jpt_credential_validation_options; pub use jpt_credential_validator::*; pub use decoded_jpt_credential::*; -pub use jpt_credential_validator_utils::*; \ No newline at end of file +pub use jpt_credential_validator_utils::*; +pub use jpt_credential_validation_options::*; \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs new file mode 100644 index 0000000000..93a8eb7286 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -0,0 +1,68 @@ + +use identity_document::verifiable::JwpVerificationOptions; +use serde::Deserialize; +use serde::Serialize; + +use identity_core::common::Timestamp; +use identity_document::verifiable::JwsVerificationOptions; + +/// Criteria for validating a [`Presentation`](crate::presentation::Presentation). +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[non_exhaustive] +#[serde(rename_all = "camelCase")] +pub struct JptPresentationValidationOptions { + /// Options which affect the verification of the signature on the presentation. + #[serde(default)] + pub presentation_verifier_options: JwpVerificationOptions, + + /// The nonce to be placed in the Presentation Protected Header. + #[serde(default)] + pub nonce: Option, + + /// Declares that the presentation is **not** considered valid if it expires before this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub earliest_expiry_date: Option, + + /// Declares that the presentation is **not** considered valid if it was issued later than this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub latest_issuance_date: Option, +} + +impl JptPresentationValidationOptions { + /// Constructor that sets all options to their defaults. + pub fn new() -> Self { + Self::default() + } + + /// Set options which affect the verification of the signature on the presentation. + pub fn presentation_verifier_options(mut self, options: JwpVerificationOptions) -> Self { + self.presentation_verifier_options = options; + self + } + + /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn nonce(mut self, nonce: String) -> Self { + self.nonce = Some(nonce); + self + } + + + /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn earliest_expiry_date(mut self, timestamp: Timestamp) -> Self { + self.earliest_expiry_date = Some(timestamp); + self + } + + /// Declare that the presentation is **not** considered valid if it was issued later than this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn latest_issuance_date(mut self, timestamp: Timestamp) -> Self { + self.latest_issuance_date = Some(timestamp); + self + } +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs new file mode 100644 index 0000000000..84d5946862 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -0,0 +1,40 @@ +use identity_document::document::CoreDocument; + +use crate::{credential::Jpt, validator::FailFast}; + +use super::JptPresentationValidationOptions; + + + +// /// A type for decoding and validating [`Credential`]s in JPT format representing a JWP in Presented form. //TODO: validator +// #[non_exhaustive] +// pub struct JptPresentationValidator; + +// impl JptPresentationValidator { + + +// /// Decodes and validates a [`Credential`] issued as a JPT. A [`DecodedJptCredential`] is returned upon success. +// /// +// /// The following properties are validated according to `options`: +// /// - the issuer's proof on the JWP, +// /// - the expiration date, +// /// - the issuance date, +// /// - the semantic structure. +// pub fn validate( +// credential_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field +// issuer: &DOC, +// options: &JptPresentationValidationOptions, +// fail_fast: FailFast, +// ) -> Result, CompoundCredentialValidationError> +// where +// T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, +// DOC: AsRef, +// { +// Self::validate_extended::( +// credential_jpt, +// std::slice::from_ref(issuer.as_ref()), +// options, +// fail_fast, +// ) +// } +// } \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs new file mode 100644 index 0000000000..aa2f6ba55a --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs @@ -0,0 +1,45 @@ +use std::str::FromStr; + +use identity_core::{convert::{ToJson, FromJson}, common::Object}; +use identity_did::DID; +use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, jpt::claims::JptClaims, encoding::SerializationType}; + +use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, CredentialJwtClaims}}; + +/// Utility functions for verifying JPT credentials. +#[derive(Debug)] +#[non_exhaustive] +pub struct JptPresentationValidatorUtils; + +impl JptPresentationValidatorUtils { + + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// + /// # Errors + /// + /// If the JPT decoding fails or the issuer field is not a valid DID. + pub fn extract_issuer_from_jpt(presentation: &Jpt) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + + let decoded = JwpPresentedDecoder::decode(presentation.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + let claims = decoded.get_issuer_header().claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + let payloads = decoded.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, Object> = + CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs new file mode 100644 index 0000000000..f6937f99ae --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -0,0 +1,7 @@ +mod jpt_presentation_validator; +mod jpt_presentation_validation_options; +mod jpt_presentation_validator_utils; + +pub use jpt_presentation_validator::*; +pub use jpt_presentation_validation_options::*; +pub use jpt_presentation_validator_utils::*; \ No newline at end of file diff --git a/identity_credential/src/validator/mod.rs b/identity_credential/src/validator/mod.rs index 23b8198faf..832b78103a 100644 --- a/identity_credential/src/validator/mod.rs +++ b/identity_credential/src/validator/mod.rs @@ -6,11 +6,13 @@ pub use self::jwt_credential_validation::*; pub use self::jwt_presentation_validation::*; pub use self::jpt_credential_validation::*; +pub use self::jpt_presentation_validation::*; pub use self::options::FailFast; pub use self::options::StatusCheck; pub use self::options::SubjectHolderRelationship; mod jpt_credential_validation; +mod jpt_presentation_validation; mod jwt_credential_validation; mod jwt_presentation_validation; mod options; diff --git a/identity_document/src/verifiable/jwp_verification_options.rs b/identity_document/src/verifiable/jwp_verification_options.rs new file mode 100644 index 0000000000..46459499e1 --- /dev/null +++ b/identity_document/src/verifiable/jwp_verification_options.rs @@ -0,0 +1,36 @@ + +use identity_did::DIDUrl; +use identity_verification::MethodScope; + +//TODO: JwpVerificationOptions + +/// Holds additional options for verifying a JWP +#[non_exhaustive] +#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct JwpVerificationOptions { + /// Verify the signing verification method relation matches this. + pub method_scope: Option, + /// The DID URl of the method, whose JWK should be used to verify the JWP. + /// If unset, the `kid` of the JWP is used as the DID Url. + pub method_id: Option, +} + +impl JwpVerificationOptions { + /// Creates a new [`JwsVerificationOptions`]. + pub fn new() -> Self { + Self::default() + } + + /// Set the scope of the verification methods that may be used to verify the given JWS. + pub fn method_scope(mut self, value: MethodScope) -> Self { + self.method_scope = Some(value); + self + } + + /// The DID URl of the method, whose JWK should be used to verify the JWS. + pub fn method_id(mut self, value: DIDUrl) -> Self { + self.method_id = Some(value); + self + } +} diff --git a/identity_document/src/verifiable/mod.rs b/identity_document/src/verifiable/mod.rs index da91055ca1..91c846c0e7 100644 --- a/identity_document/src/verifiable/mod.rs +++ b/identity_document/src/verifiable/mod.rs @@ -4,5 +4,7 @@ //! Additional functionality for DID assisted digital signatures. pub use self::jws_verification_options::JwsVerificationOptions; +pub use self::jwp_verification_options::JwpVerificationOptions; mod jws_verification_options; +mod jwp_verification_options; \ No newline at end of file diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 5612e7a600..dcb84489bd 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -2,8 +2,10 @@ use identity_core::common::Object; use identity_credential::credential::Credential; +use identity_credential::credential::JwpCredentialOptions; use identity_credential::presentation; -use identity_credential::presentation::SelectiveDiscosurePresentation; +use identity_credential::presentation::JwpPresentationOptions; +use identity_credential::presentation::SelectiveDisclosurePresentation; use identity_document::document::CoreDocument; use identity_verification::MethodData; use jsonprooftoken::encoding::SerializationType; @@ -21,7 +23,6 @@ use jsonprooftoken::jwp::presented; use jsonprooftoken::jwp::presented::JwpPresented; use serde::Serialize; use serde::de::DeserializeOwned; -use crate::JwpOptions; use crate::Storage; use jsonprooftoken::jpa::algs::ProofAlgorithm; use crate::KeyType; @@ -32,7 +33,6 @@ use crate::KeyIdStorage; use crate::JwkGenOutput; use crate::key_id_storage::MethodDigest; use super::JwkStorageDocumentError as Error; -use super::jwp_options; use async_trait::async_trait; use identity_did::DIDUrl; use identity_verification::VerificationMethod; @@ -66,7 +66,7 @@ pub trait JwpDocumentExt { storage: &Storage, fragment: &str, jpt_claims: &JptClaims, - options: &JwpOptions, + options: &JwpCredentialOptions, ) -> StorageResult where K: JwkStorageExt, @@ -75,9 +75,9 @@ pub trait JwpDocumentExt { async fn create_presented_jwp( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult; /// Produces a JPT where the payload is produced from the given `credential`. @@ -87,7 +87,7 @@ pub trait JwpDocumentExt { credential: &Credential, storage: &Storage, fragment: &str, - options: &JwpOptions, + options: &JwpCredentialOptions, custom_claims: Option, ) -> StorageResult where @@ -101,9 +101,9 @@ pub trait JwpDocumentExt { /// TODO: add references to Drafts async fn create_presentation_jpt( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult; @@ -143,7 +143,7 @@ impl JwpDocumentExt for CoreDocument { storage: &Storage, fragment: &str, jpt_claims: &JptClaims, - options: &JwpOptions, + options: &JwpCredentialOptions, ) -> StorageResult where K: JwkStorageExt, @@ -163,12 +163,9 @@ impl JwpDocumentExt for CoreDocument { .map_err(|_| Error::InvalidJwpAlgorithm)?; - let typ = if let Some(typ) = &options.typ { - typ.clone() - } else { - // https://www.w3.org/TR/vc-data-model/#jwt-encoding - "JPT".to_string() - }; + // https://www.w3.org/TR/vc-data-model/#jwt-encoding + let typ = "JPT".to_string(); + let kid = if let Some(ref kid) = options.kid { kid.clone() @@ -199,9 +196,9 @@ impl JwpDocumentExt for CoreDocument { async fn create_presented_jwp( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult { // Obtain the method corresponding to the given fragment. @@ -220,18 +217,9 @@ impl JwpDocumentExt for CoreDocument { let public_key: Jwk = jwk.try_into().map_err(|_| Error::NotPublicKeyJwk)?; - - let kid = if let Some(ref kid) = options.kid { - kid.clone() - } else { - method.id().to_string() - }; - - let mut presentation_header = PresentationProtectedHeader::new(alg.into()); - presentation_header.set_kid(Some(kid)); presentation_header.set_nonce(options.nonce.clone()); - + presentation_header.set_aud(options.audience.as_ref().and_then(|u| Some(u.to_string()))); presentation.set_presentation_header(presentation_header); @@ -251,7 +239,7 @@ impl JwpDocumentExt for CoreDocument { credential: &Credential, storage: &Storage, fragment: &str, - options: &JwpOptions, + options: &JwpCredentialOptions, custom_claims: Option, ) -> StorageResult where @@ -272,9 +260,9 @@ impl JwpDocumentExt for CoreDocument { async fn create_presentation_jpt( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult { self @@ -325,7 +313,7 @@ mod iota_document { storage: &Storage, fragment: &str, jpt_claims: &JptClaims, - options: &JwpOptions, + options: &JwpCredentialOptions, ) -> StorageResult where K: JwkStorageExt, @@ -340,9 +328,9 @@ mod iota_document { async fn create_presented_jwp( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult { self @@ -358,7 +346,7 @@ mod iota_document { credential: &Credential, storage: &Storage, fragment: &str, - options: &JwpOptions, + options: &JwpCredentialOptions, custom_claims: Option, ) -> StorageResult where @@ -375,9 +363,9 @@ mod iota_document { async fn create_presentation_jpt( &self, - presentation: &mut SelectiveDiscosurePresentation, + presentation: &mut SelectiveDisclosurePresentation, method_id: &str, - options: &JwpOptions, + options: &JwpPresentationOptions, ) -> StorageResult { self .core_document() diff --git a/identity_storage/src/storage/jwp_options.rs b/identity_storage/src/storage/jwp_options.rs deleted file mode 100644 index 0ca8dc3076..0000000000 --- a/identity_storage/src/storage/jwp_options.rs +++ /dev/null @@ -1,53 +0,0 @@ - -use identity_core::common::Object; -use identity_core::common::Url; - -//TODO: have to choose which options makes sense in the context of jwp - -/// Options for creating a JSON Web Proof. -#[non_exhaustive] -#[derive(Debug, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Clone)] -#[serde(rename_all = "camelCase")] -#[serde(default)] -pub struct JwpOptions { - - /// The Type value to be placed in the protected header. - #[serde(skip_serializing_if = "Option::is_none")] - pub typ: Option, - - /// The nonce to be placed in the Presentation Protected Header. - #[serde(skip_serializing_if = "Option::is_none")] - pub nonce: Option, - - /// The kid to set in the Issuer Protected Header. - /// - /// If unset, the kid of the JWK with which the JWP is produced is used. - #[serde(skip_serializing_if = "Option::is_none")] - pub kid: Option -} - -impl JwpOptions { - /// Creates a new [`JwsSignatureOptions`]. - pub fn new() -> Self { - Self::default() - } - - /// Replace the value of the `typ` field. - pub fn typ(mut self, value: impl Into) -> Self { - self.typ = Some(value.into()); - self - } - - /// Replace the value of the `nonce` field. - pub fn nonce(mut self, value: impl Into) -> Self { - self.nonce = Some(value.into()); - self - } - - /// Replace the value of the `kid` field. - pub fn kid(mut self, value: impl Into) -> Self { - self.kid = Some(value.into()); - self - } - -} diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 7f8fbbbc6b..9abf873674 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -7,7 +7,6 @@ mod error; #[macro_use] //TODO: to be able to call the macro from jwp_document_ext.rs mod jwk_document_ext; mod jwp_document_ext; -mod jwp_options; mod signature_options; #[cfg(all(test, feature = "memstore"))] @@ -17,7 +16,6 @@ pub use error::*; pub use jwk_document_ext::*; pub use jwp_document_ext::*; -pub use jwp_options::*; pub use signature_options::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and From 217a754cb468383a01a79ff5b11afac181117bcd Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 8 Jan 2024 18:11:41 +0100 Subject: [PATCH 016/163] JWP Presentation verification --- examples/1_advanced/7_zkp.rs | 21 +- .../presentation/jwp_presentation_builder.rs | 2 - .../jpt_credential_validation_options.rs | 4 - .../jpt_credential_validator.rs | 64 +++--- .../decoded_jpt_presentation.rs | 17 ++ .../jpt_presentation_validation_options.rs | 37 +-- .../jpt_presentation_validator.rs | 215 ++++++++++++++---- .../jpt_presentation_validator_utils.rs | 45 ---- .../jpt_presentation_validation/mod.rs | 4 +- .../jwt_credential_validation/error.rs | 7 +- identity_jose/src/jwk/jwk_ext.rs | 4 - identity_jose/src/jwk/key_operation.rs | 4 +- identity_jose/src/jwk/key_use.rs | 3 +- identity_jose/src/jwk/mod.rs | 1 - .../src/key_storage/jwk_storage.rs | 5 - .../src/key_storage/key_storage_error.rs | 1 + identity_storage/src/key_storage/memstore.rs | 8 +- .../src/storage/jwp_document_ext.rs | 11 +- 18 files changed, 267 insertions(+), 186 deletions(-) create mode 100644 identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs delete mode 100644 identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index d3412a533f..6caeaca34a 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -15,6 +15,8 @@ use identity_iota::credential::Jpt; use identity_iota::credential::JptCredentialValidator; use identity_iota::credential::JptCredentialValidatorUtils; use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; use identity_iota::credential::JwpCredentialOptions; use identity_iota::credential::JwpPresentationOptions; use identity_iota::credential::SelectiveDisclosurePresentation; @@ -204,7 +206,8 @@ async fn main() -> anyhow::Result<()> { // A unique random challenge generated by the requester per presentation can mitigate replay attacks. let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - + // Construct a JPT(JWP in the Presentation form) representing the Selectively Dislosed Verifiable Credential + // NOTE that we are not following the W3C Presentation standard since not compatible with the JWP standard let presentation_jpt: Jpt = issuer_document .create_presentation_jpt( &mut selective_disclosure_presentation, @@ -215,10 +218,24 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. + // Step 4: Holder sends a verifiable presentation to the verifier. // =========================================================================== println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); + let presentation_validation_options = + JptPresentationValidationOptions::default().nonce(challenge.to_owned()); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("Presented Credential successfully validated: {:#?}", decoded_presented_credential.credential); Ok(()) } diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index af1fd1f288..8c1eb59eb3 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -1,6 +1,4 @@ -use identity_verification::jwk::Jwk; use jsonprooftoken::jwp::header::PresentationProtectedHeader; -use jsonprooftoken::jwp::presented::JwpPresented; use jsonprooftoken::jwp::{presented::JwpPresentedBuilder, issued::JwpIssued}; use crate::error::Error; use crate::error::Result; diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs index 5d0c9e8664..062c33cb41 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -1,13 +1,9 @@ use identity_core::common::Timestamp; -use identity_core::common::Url; use identity_document::verifiable::JwpVerificationOptions; -use identity_document::verifiable::JwsVerificationOptions; use serde::Deserialize; use serde::Serialize; -use crate::validator::SubjectHolderRelationship; - /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. #[non_exhaustive] #[derive(Debug, Default, Clone, Serialize, Deserialize)] diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index ab9a5f3fa6..d534287eed 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -1,11 +1,11 @@ use identity_core::convert::{FromJson, ToJson}; use identity_did::{DIDUrl, CoreDID}; use identity_document::verifiable::JwpVerificationOptions; -use identity_document::{document::CoreDocument, verifiable::JwsVerificationOptions}; +use identity_document::document::CoreDocument; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwk::key::Jwk as JwkExt; use jsonprooftoken::jwp::issued::JwpIssuedDecoder; -use jsonprooftoken::{jwp::issued::JwpIssued, encoding::SerializationType}; +use jsonprooftoken::encoding::SerializationType; use crate::credential::CredentialJwtClaims; use crate::validator::{JwtCredentialValidatorUtils, JptCredentialValidationOptions, CompoundCredentialValidationError}; @@ -13,14 +13,14 @@ use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationEr use super::DecodedJptCredential; -/// A type for decoding and validating [`Credential`]s in JPT format representing a JWP in Issued form.. //TODO: validator +/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: validator #[non_exhaustive] pub struct JptCredentialValidator; impl JptCredentialValidator { - /// Decodes and validates a [`Credential`] issued as a JPT. A [`DecodedJptCredential`] is returned upon success. + /// Decodes and validates a [`Credential`] issued as a JPT (JWP Issued Form). A [`DecodedJptCredential`] is returned upon success. /// /// The following properties are validated according to `options`: /// - the issuer's proof on the JWP, @@ -37,46 +37,41 @@ impl JptCredentialValidator { T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, DOC: AsRef, { - Self::validate_extended::( - credential_jpt, - std::slice::from_ref(issuer.as_ref()), + + + // First verify the JWP proof and decode the result into a credential token, then apply all other validations. + let credential_token = + Self::verify_proof(credential_jpt, std::slice::from_ref(issuer.as_ref()), &options.verification_options) + .map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; + + let credential: &Credential = &credential_token.credential; + + Self::validate_credential::( + &credential, options, fail_fast, - ) - } - + )?; + Ok(credential_token) + } - // This method takes a slice of issuer's instead of a single issuer in order to better accommodate presentation - // validation. It also validates the relationship between a holder and the credential subjects when - // `relationship_criterion` is Some. - pub(crate) fn validate_extended( - credential: &Jpt, - issuers: &[DOC], + pub(crate) fn validate_credential( + credential: &Credential, options: &JptCredentialValidationOptions, fail_fast: FailFast, - ) -> Result, CompoundCredentialValidationError> + ) -> Result<(), CompoundCredentialValidationError> where T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, DOC: AsRef, { - // First verify the JWS signature and decode the result into a credential token, then apply all other validations. - // If this errors we have to return early regardless of the `fail_fast` flag as all other validations require a - // `&Credential`. - let credential_token = - Self::verify_proof(credential, issuers, &options.verification_options) - .map_err(|err| CompoundCredentialValidationError { - validation_errors: [err].into(), - })?; - - let credential: &Credential = &credential_token.credential; // Run all single concern Credential validations in turn and fail immediately if `fail_fast` is true. - let expiry_date_validation = std::iter::once_with(|| { JwtCredentialValidatorUtils::check_expires_on_or_after( - &credential_token.credential, + credential, options.earliest_expiry_date.unwrap_or_default(), ) }); @@ -112,7 +107,7 @@ impl JptCredentialValidator { }; if validation_errors.is_empty() { - Ok(credential_token) + Ok(()) } else { Err(CompoundCredentialValidationError { validation_errors }) } @@ -132,7 +127,8 @@ fn verify_proof( { let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; - + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier // of a verification method in a trusted issuer's DID document. let method_id: DIDUrl = match &options.method_id { @@ -188,7 +184,7 @@ fn verify_proof( } - /// Verify the signature using the given `public_key` and `signature_verifier`. + /// Verify the decoded issued JWP proof using the given `public_key`. fn verify_decoded_jwp( decoded: JwpIssuedDecoder, public_key: &JwkExt, @@ -197,8 +193,8 @@ fn verify_proof( T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, { - //Verify Jwp proof - let decoded_jwp = decoded.verify(&public_key).map_err(|err| JwtValidationError::JwpProofVerifiationError(err))?; + // Verify Jwp proof + let decoded_jwp = decoded.verify(&public_key).map_err(|err| JwtValidationError::JwpProofVerificationError(err))?; let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; let payloads = decoded_jwp.get_payloads(); diff --git a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs new file mode 100644 index 0000000000..baf3447e0f --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs @@ -0,0 +1,17 @@ +use identity_core::common::{Object, Url}; +use jsonprooftoken::jwp::presented::JwpPresented; + +use crate::credential::Credential; + +/// Decoded [`Credential`] from a cryptographically verified JWP. +pub struct DecodedJptPresentation { + /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). + pub credential: Credential, + /// The `aud` property parsed from the JWT claims. + pub aud: Option, + /// The custom claims parsed from the JPT. + pub custom_claims: Option, + /// The decoded and verifier Issued JWP, will be used to construct the Presented JWP + pub decoded_jwp: JwpPresented, + +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs index 93a8eb7286..656fdffce9 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -1,35 +1,21 @@ - -use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; - -use identity_core::common::Timestamp; -use identity_document::verifiable::JwsVerificationOptions; +use crate::validator::JptCredentialValidationOptions; /// Criteria for validating a [`Presentation`](crate::presentation::Presentation). #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[non_exhaustive] #[serde(rename_all = "camelCase")] pub struct JptPresentationValidationOptions { - /// Options which affect the verification of the signature on the presentation. - #[serde(default)] - pub presentation_verifier_options: JwpVerificationOptions, /// The nonce to be placed in the Presentation Protected Header. #[serde(default)] pub nonce: Option, - /// Declares that the presentation is **not** considered valid if it expires before this - /// [`Timestamp`]. - /// Uses the current datetime during validation if not set. + /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. #[serde(default)] - pub earliest_expiry_date: Option, + pub credential_validation_options: JptCredentialValidationOptions, - /// Declares that the presentation is **not** considered valid if it was issued later than this - /// [`Timestamp`]. - /// Uses the current datetime during validation if not set. - #[serde(default)] - pub latest_issuance_date: Option, } impl JptPresentationValidationOptions { @@ -39,8 +25,8 @@ impl JptPresentationValidationOptions { } /// Set options which affect the verification of the signature on the presentation. - pub fn presentation_verifier_options(mut self, options: JwpVerificationOptions) -> Self { - self.presentation_verifier_options = options; + pub fn credential_validation_options(mut self, options: JptCredentialValidationOptions) -> Self { + self.credential_validation_options = options; self } @@ -52,17 +38,4 @@ impl JptPresentationValidationOptions { } - /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. - /// Uses the current datetime during validation if not set. - pub fn earliest_expiry_date(mut self, timestamp: Timestamp) -> Self { - self.earliest_expiry_date = Some(timestamp); - self - } - - /// Declare that the presentation is **not** considered valid if it was issued later than this [`Timestamp`]. - /// Uses the current datetime during validation if not set. - pub fn latest_issuance_date(mut self, timestamp: Timestamp) -> Self { - self.latest_issuance_date = Some(timestamp); - self - } } diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index 84d5946862..543e1212d7 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -1,40 +1,179 @@ +use std::str::FromStr; + +use identity_core::{convert::{ToJson, FromJson}, common::Url}; +use identity_did::{DIDUrl, CoreDID}; use identity_document::document::CoreDocument; +use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, encoding::SerializationType, jwk::key::Jwk as JwkExt, jpt::claims::JptClaims}; + +use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{FailFast, JwtValidationError, SignerContext, JwtCredentialValidatorUtils, CompoundCredentialValidationError, JptCredentialValidator}}; + +use super::{JptPresentationValidationOptions, DecodedJptPresentation}; + +/// A type for decoding and validating Presented [`Credential`]s in JPT format. //TODO: validator +#[non_exhaustive] +pub struct JptPresentationValidator; + +impl JptPresentationValidator { + + + /// Decodes and validates a Presented [`Credential`] issued as a JPT (JWP Presented Form). A [`DecodedJptPresentation`] is returned upon success. + /// + /// The following properties are validated according to `options`: + /// - the holder's proof on the JWP, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + pub fn validate( + presentation_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field + issuer: &DOC, + options: &JptPresentationValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + + + // First verify the JWP proof and decode the result into a presented credential token, then apply all other validations. + let presented_credential_token = + Self::verify_proof(presentation_jpt, std::slice::from_ref(issuer.as_ref()), &options) + .map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; + + let credential: &Credential = &presented_credential_token.credential; + + JptCredentialValidator::validate_credential::( + credential, + &options.credential_validation_options, + fail_fast, + )?; + + Ok(presented_credential_token) + } + + + + /// Proof verification function + fn verify_proof( + presentation_jpt: &Jpt, + trusted_issuers: &[DOC], + options: &JptPresentationValidationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + + let decoded: JwpPresentedDecoder = JwpPresentedDecoder::decode(presentation_jpt.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + + let nonce: Option<&String> = options.nonce.as_ref(); + // Validate the nonce + if decoded.get_presentation_header().nonce() != nonce { + return Err(JwtValidationError::JwsDecodingError( + identity_verification::jose::error::Error::InvalidParam("invalid nonce value"), + )); + } + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier + // of a verification method in a trusted issuer's DID document. + let method_id: DIDUrl = match &options.credential_validation_options.verification_options.method_id { + Some(method_id) => method_id.clone(), + None => { + let kid: &str = decoded.get_issuer_header().kid().ok_or( + JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract kid from protected header", + signer_ctx: SignerContext::Issuer, + }, + )?; + + // Convert kid to DIDUrl + DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError { + source: Some(err.into()), + message: "could not parse kid as a DID Url", + signer_ctx: SignerContext::Issuer, + })? + } + }; + + // locate the corresponding issuer + let issuer: &CoreDocument = trusted_issuers + .iter() + .map(AsRef::as_ref) + .find(|issuer_doc| ::id(issuer_doc) == method_id.did()) + .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?; + + // Obtain the public key from the issuer's DID document + let public_key: JwkExt = issuer + .resolve_method(&method_id, options.credential_validation_options.verification_options.method_scope) + .and_then(|method| method.data().public_key_jwk()) + .and_then(|k| k.try_into().ok()) //Conversio into jsonprooftoken::Jwk type + .ok_or_else(|| JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract JWK from a method identified by kid", + signer_ctx: SignerContext::Issuer, + })?; + + + let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?; + + // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before + // returning. + let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?; + if &issuer_id != method_id.did() { + return Err(JwtValidationError::IdentifierMismatch { + signer_ctx: SignerContext::Issuer, + }); + }; + Ok(credential_token) + } + + + /// Verify the decoded presented JWP proof using the given `public_key`. + fn verify_decoded_jwp( + decoded: JwpPresentedDecoder, + public_key: &JwkExt, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + + //Verify Jwp proof + let decoded_jwp = decoded.verify(&public_key).map_err(|err| JwtValidationError::JwpProofVerificationError(err))?; + + let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + let payloads = decoded_jwp.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, T> = + CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + let custom_claims = credential_claims.custom.clone(); + + // Construct the credential token containing the credential and the protected header. + let credential: Credential = credential_claims + .try_into_credential() + .map_err(JwtValidationError::CredentialStructure)?; + + let aud: Option = decoded_jwp.get_presentation_protected_header().aud().map(|aud| { + Url::from_str(aud).map_err(|_| JwtValidationError::JwsDecodingError( + identity_verification::jose::error::Error::InvalidParam("invalid audience value"), + )).ok() + }).flatten(); + + Ok(DecodedJptPresentation { + credential, + aud, + custom_claims, + decoded_jwp + }) + } -use crate::{credential::Jpt, validator::FailFast}; - -use super::JptPresentationValidationOptions; - - - -// /// A type for decoding and validating [`Credential`]s in JPT format representing a JWP in Presented form. //TODO: validator -// #[non_exhaustive] -// pub struct JptPresentationValidator; - -// impl JptPresentationValidator { - - -// /// Decodes and validates a [`Credential`] issued as a JPT. A [`DecodedJptCredential`] is returned upon success. -// /// -// /// The following properties are validated according to `options`: -// /// - the issuer's proof on the JWP, -// /// - the expiration date, -// /// - the issuance date, -// /// - the semantic structure. -// pub fn validate( -// credential_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field -// issuer: &DOC, -// options: &JptPresentationValidationOptions, -// fail_fast: FailFast, -// ) -> Result, CompoundCredentialValidationError> -// where -// T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, -// DOC: AsRef, -// { -// Self::validate_extended::( -// credential_jpt, -// std::slice::from_ref(issuer.as_ref()), -// options, -// fail_fast, -// ) -// } -// } \ No newline at end of file +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs deleted file mode 100644 index aa2f6ba55a..0000000000 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::str::FromStr; - -use identity_core::{convert::{ToJson, FromJson}, common::Object}; -use identity_did::DID; -use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, jpt::claims::JptClaims, encoding::SerializationType}; - -use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, CredentialJwtClaims}}; - -/// Utility functions for verifying JPT credentials. -#[derive(Debug)] -#[non_exhaustive] -pub struct JptPresentationValidatorUtils; - -impl JptPresentationValidatorUtils { - - /// Utility for extracting the issuer field of a credential in JPT representation as DID. - /// - /// # Errors - /// - /// If the JPT decoding fails or the issuer field is not a valid DID. - pub fn extract_issuer_from_jpt(presentation: &Jpt) -> std::result::Result - where - D: DID, - ::Err: std::error::Error + Send + Sync + 'static, - { - - let decoded = JwpPresentedDecoder::decode(presentation.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; - let claims = decoded.get_issuer_header().claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; - let payloads = decoded.get_payloads(); - let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); - let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; - - - // Deserialize the raw claims - let credential_claims: CredentialJwtClaims<'_, Object> = - CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { - JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) - })?; - - D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { - signer_ctx: SignerContext::Issuer, - source: err.into(), - }) - } -} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs index f6937f99ae..7988cdf2ef 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -1,7 +1,7 @@ mod jpt_presentation_validator; mod jpt_presentation_validation_options; -mod jpt_presentation_validator_utils; +mod decoded_jpt_presentation; pub use jpt_presentation_validator::*; pub use jpt_presentation_validation_options::*; -pub use jpt_presentation_validator_utils::*; \ No newline at end of file +pub use decoded_jpt_presentation::*; \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index 0aecc293c2..9bfa7b7fba 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -104,12 +104,13 @@ pub enum JwtValidationError { //TODO: new errors for jwp - /// Indicates that the JWS representation of an issued credential or presentation could not be decoded. + /// Indicates that the JWP representation of an issued credential or presentation could not be decoded. #[error("could not decode jwp")] JwpDecodingError(#[source] jsonprooftoken::errors::CustomError), - + + /// Indicates that the verfication of the JWP has failed #[error("could not verify jwp")] - JwpProofVerifiationError(#[source] jsonprooftoken::errors::CustomError), + JwpProofVerificationError(#[source] jsonprooftoken::errors::CustomError), } diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs index 7d2e27034a..3337cc5ac5 100644 --- a/identity_jose/src/jwk/jwk_ext.rs +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -1,12 +1,8 @@ //TODO: JwkExt use std::str::FromStr; - use identity_core::common::Url; use jsonprooftoken::{jwk::{key::{Jwk as JwkExt, KeyOps, PKUse}, alg_parameters::{JwkAlgorithmParameters, JwkOctetKeyPairParameters, Algorithm}, types::KeyType, curves::EllipticCurveTypes}, jpa::algs::ProofAlgorithm}; - -use crate::jws::JwsAlgorithm; - use super::{Jwk, JwkOperation, JwkUse, JwkParams, JwkParamsOkp, JwkType}; diff --git a/identity_jose/src/jwk/key_operation.rs b/identity_jose/src/jwk/key_operation.rs index 0302d6a98e..38ea9e67e2 100644 --- a/identity_jose/src/jwk/key_operation.rs +++ b/identity_jose/src/jwk/key_operation.rs @@ -28,8 +28,10 @@ pub enum JwkOperation { /// Derive bits not to be used as a key. DeriveBits, - //TODO: add ProofGeneration/ProofVerification + //TODO: added ProofGeneration/ProofVerification + /// Compute proof ProofGeneration, + /// Verify proof ProofVerification } diff --git a/identity_jose/src/jwk/key_use.rs b/identity_jose/src/jwk/key_use.rs index ac0edb1a2e..ceb5777f66 100644 --- a/identity_jose/src/jwk/key_use.rs +++ b/identity_jose/src/jwk/key_use.rs @@ -17,7 +17,8 @@ pub enum JwkUse { #[serde(rename = "enc")] Encryption, - //TODO: add Proof + //TODO: added Proof + /// Proof Proof } diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index af32648858..9cf3279c0d 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -19,4 +19,3 @@ pub use self::key_params::*; pub use self::key_set::*; pub use self::key_type::*; pub use self::key_use::*; -pub use self::jwk_ext::*; diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 6bd80150fe..cf7df4e140 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -5,16 +5,11 @@ use crate::key_storage::KeyId; use crate::key_storage::KeyStorageError; use crate::key_storage::KeyType; use async_trait::async_trait; -use identity_credential::credential::Issuer; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; -use jsonprooftoken::jpt::payloads; -use jsonprooftoken::jpt::payloads::Payloads; use jsonprooftoken::jwp::header::IssuerProtectedHeader; -use jsonprooftoken::jwp::issued::JwpIssued; -use jsonprooftoken::jwp::issued::JwpIssuedBuilder; use super::jwk_gen_output::JwkGenOutput; diff --git a/identity_storage/src/key_storage/key_storage_error.rs b/identity_storage/src/key_storage/key_storage_error.rs index efcf8929b6..164f555a8d 100644 --- a/identity_storage/src/key_storage/key_storage_error.rs +++ b/identity_storage/src/key_storage/key_storage_error.rs @@ -23,6 +23,7 @@ pub enum KeyStorageErrorKind { /// Indicates an attempt to parse a signature algorithm that is not recognized by the key storage implementation. UnsupportedSignatureAlgorithm, + /// Indicates an attempt to parse a proof algorithm that is not recognized by the key storage implementation. UnsupportedProofAlgorithm, //TODO: new error /// Indicates that the key storage implementation is not able to find the requested key. diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 52f7be0586..5ba377956b 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -16,11 +16,9 @@ use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwk::curves::EllipticCurveTypes; -use jsonprooftoken::jwk::key; use jsonprooftoken::jwk::key::Jwk as JwkExt; use jsonprooftoken::jwk::types::KeyPairSubtype; use jsonprooftoken::jwp::header::IssuerProtectedHeader; -use jsonprooftoken::jwp::issued::JwpIssued; use jsonprooftoken::jwp::issued::JwpIssuedBuilder; use rand::distributions::DistString; use shared::Shared; @@ -35,7 +33,6 @@ use super::KeyStorageError; use super::KeyStorageErrorKind; use super::KeyStorageResult; use super::KeyType; -use super::key_type; use crate::JwkStorageExt; use crate::key_storage::JwkStorage; @@ -203,7 +200,12 @@ impl JwkMemStore { pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ED25519_KEY_TYPE_STR); const BLS12381SHA256_KEY_TYPE_STR: &str = "Bls12381Sha256"; + /// The BLS12381-SHA256 key type pub const BLS12381SHA256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHA256_KEY_TYPE_STR); + + const BLS12381SHAKE256_KEY_TYPE_STR: &str = "Bls12381Shake256"; + /// The BLS12381-SHAKE256 key type + pub const BLS12381SHAKE256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHAKE256_KEY_TYPE_STR); } impl MemStoreKeyType { diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index dcb84489bd..36b2f35854 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -3,24 +3,15 @@ use identity_core::common::Object; use identity_credential::credential::Credential; use identity_credential::credential::JwpCredentialOptions; -use identity_credential::presentation; use identity_credential::presentation::JwpPresentationOptions; use identity_credential::presentation::SelectiveDisclosurePresentation; use identity_document::document::CoreDocument; use identity_verification::MethodData; use jsonprooftoken::encoding::SerializationType; -use jsonprooftoken::jpa::algs::PresentationProofAlgorithm; -use jsonprooftoken::jpt::claims; -use jsonprooftoken::jpt::claims::Claims; use jsonprooftoken::jpt::claims::JptClaims; -use jsonprooftoken::jpt::payloads::Payloads; use jsonprooftoken::jwk::key::Jwk; use jsonprooftoken::jwp::header::IssuerProtectedHeader; use jsonprooftoken::jwp::header::PresentationProtectedHeader; -use jsonprooftoken::jwp::issued::JwpIssued; -use jsonprooftoken::jwp::issued::JwpIssuedBuilder; -use jsonprooftoken::jwp::presented; -use jsonprooftoken::jwp::presented::JwpPresented; use serde::Serialize; use serde::de::DeserializeOwned; use crate::Storage; @@ -61,6 +52,7 @@ pub trait JwpDocumentExt { I: KeyIdStorage; + /// Compute a JWP in the Issued form representing the Verifiable Credential async fn create_issued_jwp( &self, storage: &Storage, @@ -73,6 +65,7 @@ pub trait JwpDocumentExt { I: KeyIdStorage; + /// Compute a JWP in the Presented form representing the presented Verifiable Credential after the Selective Disclosure of attributes async fn create_presented_jwp( &self, presentation: &mut SelectiveDisclosurePresentation, From b33a604352dfd25fbae65fd498cf3066a22a5d9f Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 8 Jan 2024 18:31:21 +0100 Subject: [PATCH 017/163] removed multiple issuers during JWP verification since only one Credential is supported during presentation --- .../jpt_credential_validator.rs | 18 +++++++++--------- .../jpt_presentation_validator.rs | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index d534287eed..0af7d76864 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -28,7 +28,7 @@ impl JptCredentialValidator { /// - the issuance date, /// - the semantic structure. pub fn validate( - credential_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field + credential_jpt: &Jpt, issuer: &DOC, options: &JptCredentialValidationOptions, fail_fast: FailFast, @@ -41,7 +41,7 @@ impl JptCredentialValidator { // First verify the JWP proof and decode the result into a credential token, then apply all other validations. let credential_token = - Self::verify_proof(credential_jpt, std::slice::from_ref(issuer.as_ref()), &options.verification_options) + Self::verify_proof(credential_jpt, issuer, &options.verification_options) .map_err(|err| CompoundCredentialValidationError { validation_errors: [err].into(), })?; @@ -118,7 +118,7 @@ impl JptCredentialValidator { /// Proof verification function fn verify_proof( credential: &Jpt, - trusted_issuers: &[DOC], + issuer: &DOC, options: &JwpVerificationOptions, ) -> Result, JwtValidationError> where @@ -151,12 +151,12 @@ fn verify_proof( } }; - // locate the corresponding issuer - let issuer: &CoreDocument = trusted_issuers - .iter() - .map(AsRef::as_ref) - .find(|issuer_doc| ::id(issuer_doc) == method_id.did()) - .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?; + // check issuer + let issuer: &CoreDocument = issuer.as_ref(); + + if issuer.id() != method_id.did() { + return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer)); + } // Obtain the public key from the issuer's DID document let public_key: JwkExt = issuer diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index 543e1212d7..b3e60ab419 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -37,7 +37,7 @@ impl JptPresentationValidator { // First verify the JWP proof and decode the result into a presented credential token, then apply all other validations. let presented_credential_token = - Self::verify_proof(presentation_jpt, std::slice::from_ref(issuer.as_ref()), &options) + Self::verify_proof(presentation_jpt, issuer, &options) .map_err(|err| CompoundCredentialValidationError { validation_errors: [err].into(), })?; @@ -58,7 +58,7 @@ impl JptPresentationValidator { /// Proof verification function fn verify_proof( presentation_jpt: &Jpt, - trusted_issuers: &[DOC], + issuer: &DOC, options: &JptPresentationValidationOptions, ) -> Result, JwtValidationError> where @@ -98,12 +98,12 @@ impl JptPresentationValidator { } }; - // locate the corresponding issuer - let issuer: &CoreDocument = trusted_issuers - .iter() - .map(AsRef::as_ref) - .find(|issuer_doc| ::id(issuer_doc) == method_id.did()) - .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?; + // check issuer + let issuer: &CoreDocument = issuer.as_ref(); + + if issuer.id() != method_id.did() { + return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer)); + } // Obtain the public key from the issuer's DID document let public_key: JwkExt = issuer From 6429817d8990c55f2f6581c45e14188d67c09e01 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 9 Jan 2024 16:33:53 +0100 Subject: [PATCH 018/163] extract_issuer_from_presented_jpt util and remove identity_zk module --- Cargo.toml | 3 +- examples/1_advanced/7_zkp.rs | 11 +++-- .../jpt_credential_validator_utils.rs | 2 +- .../jpt_presentation_validation_options.rs | 4 +- .../jpt_presentation_validator_utils.rs | 45 +++++++++++++++++++ .../jpt_presentation_validation/mod.rs | 4 +- identity_zk/Cargo.toml | 20 --------- identity_zk/src/jwp_ext.rs | 1 - identity_zk/src/lib.rs | 1 - 9 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs delete mode 100644 identity_zk/Cargo.toml delete mode 100644 identity_zk/src/jwp_ext.rs delete mode 100644 identity_zk/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9d4e2c59cb..c9cd363632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ members = [ "identity_jose", "identity_eddsa_verifier", "examples", - "identity_zk" ] exclude = ["bindings/wasm"] @@ -24,7 +23,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.3.0"} +json-proof-token = {version = "0.3.2"} [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 6caeaca34a..d24f6f9ff8 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -17,6 +17,7 @@ use identity_iota::credential::JptCredentialValidatorUtils; use identity_iota::credential::JptCredentialValidationOptions; use identity_iota::credential::JptPresentationValidationOptions; use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; use identity_iota::credential::JwpCredentialOptions; use identity_iota::credential::JwpPresentationOptions; use identity_iota::credential::SelectiveDisclosurePresentation; @@ -182,8 +183,8 @@ async fn main() -> anyhow::Result<()> { let mut resolver: Resolver = Resolver::new(); resolver.attach_iota_handler(client); - // Resolve Issuer DID - let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_jpt(&credential_jpt).unwrap(); + // Holder resolve Issuer DID + let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_issued_jpt(&credential_jpt).unwrap(); let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; @@ -221,9 +222,13 @@ async fn main() -> anyhow::Result<()> { // Step 4: Holder sends a verifiable presentation to the verifier. // =========================================================================== println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); + + // Verifier resolve Issuer DID + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; let presentation_validation_options = - JptPresentationValidationOptions::default().nonce(challenge.to_owned()); + JptPresentationValidationOptions::default().nonce(challenge); // Verifier validate the Presented Credential and retrieve the JwpPresented let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index de9102bb1a..1c2ac0a748 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -18,7 +18,7 @@ impl JptCredentialValidatorUtils { /// # Errors /// /// If the JPT decoding fails or the issuer field is not a valid DID. - pub fn extract_issuer_from_jpt(credential: &Jpt) -> std::result::Result + pub fn extract_issuer_from_issued_jpt(credential: &Jpt) -> std::result::Result where D: DID, ::Err: std::error::Error + Send + Sync + 'static, diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs index 656fdffce9..ad9c15d41f 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -32,8 +32,8 @@ impl JptPresentationValidationOptions { /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. /// Uses the current datetime during validation if not set. - pub fn nonce(mut self, nonce: String) -> Self { - self.nonce = Some(nonce); + pub fn nonce(mut self, nonce: impl Into) -> Self { + self.nonce = Some(nonce.into()); self } diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs new file mode 100644 index 0000000000..74c2e57203 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs @@ -0,0 +1,45 @@ +use std::str::FromStr; + +use identity_core::{convert::{ToJson, FromJson}, common::Object}; +use identity_did::DID; +use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, jpt::claims::JptClaims, encoding::SerializationType}; + +use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, CredentialJwtClaims}}; + +/// Utility functions for verifying JPT credentials. +#[derive(Debug)] +#[non_exhaustive] +pub struct JptPresentationValidatorUtils; + +impl JptPresentationValidatorUtils { + + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// + /// # Errors + /// + /// If the JPT decoding fails or the issuer field is not a valid DID. + pub fn extract_issuer_from_presented_jpt(presentation: &Jpt) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + + let decoded = JwpPresentedDecoder::decode(presentation.as_str(), SerializationType::COMPACT).map_err(|err| JwtValidationError::JwpDecodingError(err))?; + let claims = decoded.get_issuer_header().claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + let payloads = decoded.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; + + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, Object> = + CredentialJwtClaims::from_json_slice(&jpt_claims_json).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } +} \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs index 7988cdf2ef..90c91e08fb 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -1,7 +1,9 @@ mod jpt_presentation_validator; mod jpt_presentation_validation_options; mod decoded_jpt_presentation; +mod jpt_presentation_validator_utils; pub use jpt_presentation_validator::*; pub use jpt_presentation_validation_options::*; -pub use decoded_jpt_presentation::*; \ No newline at end of file +pub use decoded_jpt_presentation::*; +pub use jpt_presentation_validator_utils::*; \ No newline at end of file diff --git a/identity_zk/Cargo.toml b/identity_zk/Cargo.toml deleted file mode 100644 index 39c463b427..0000000000 --- a/identity_zk/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "identity_zk" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -homepage.workspace = true -keywords = ["iota", "zk", "identity"] -license.workspace = true -readme = "./README.md" -repository.workspace = true -rust-version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde.workspace = true -serde_json.workspace = true -json-proof-token.workspace = true -hex = "0.4.3" -identity_verification = { version = "=1.0.0", path = "../identity_verification", default_features = false } diff --git a/identity_zk/src/jwp_ext.rs b/identity_zk/src/jwp_ext.rs deleted file mode 100644 index 0c95299b61..0000000000 --- a/identity_zk/src/jwp_ext.rs +++ /dev/null @@ -1 +0,0 @@ -// pub trait JwpDocumentExt \ No newline at end of file diff --git a/identity_zk/src/lib.rs b/identity_zk/src/lib.rs deleted file mode 100644 index 98ed35f6dd..0000000000 --- a/identity_zk/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod jwp_ext; \ No newline at end of file From 820672f0fbef8027640896699255fe2168faa2c6 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 11 Jan 2024 18:03:23 +0100 Subject: [PATCH 019/163] add some comments --- examples/1_advanced/7_zkp.rs | 38 ++++++++++++++++--- .../presentation/jwp_presentation_builder.rs | 11 +++--- .../jpt_credential_validation/mod.rs | 4 +- .../jpt_presentation_validation/mod.rs | 4 +- .../src/storage/jwp_document_ext.rs | 12 +++--- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index d24f6f9ff8..736e778632 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -108,7 +108,9 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M /// Demonstrates how to create an Anonymous Credential with BBS+. #[tokio::main] async fn main() -> anyhow::Result<()> { - + // =========================================================================== + // Step 1: Create identitiy for the issuer. + // =========================================================================== // Create a new client to interact with the IOTA ledger. let client: Client = Client::builder() @@ -143,6 +145,7 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; + // Build credential using subject above and issuer. let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -180,6 +183,11 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== println!("Sending credential (as JPT) to the holder: {}\n", credential_jpt.as_str()); + + // ============================================================================================ + // Step 4: Holder resolve Issuer's DID, retrieve Issuer's document and validate the Credential + // ============================================================================================ + let mut resolver: Resolver = Resolver::new(); resolver.attach_iota_handler(client); @@ -197,6 +205,21 @@ async fn main() -> anyhow::Result<()> { ) .unwrap(); + + // =========================================================================== + // Step 5: Verifier sends the holder a challenge and requests a Presentation. + // + // Please be aware that when we mention "Presentation," we are not alluding to the Verifiable Presentation standard as defined by W3C (https://www.w3.org/TR/vc-data-model/#presentations). + // Instead, our reference is to a JWP Presentation (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form), which differs from the W3C standard. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // ========================================================================================================= + // Step 6: Holder engages in the Selective Disclosure of credential's attributes + // ========================================================================================================= + let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); @@ -204,11 +227,11 @@ async fn main() -> anyhow::Result<()> { selective_disclosure_presentation.undisclose_subject("degree.name").unwrap(); - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + // ======================================================================================================================================= + // Step 7: Holder needs Issuer's Public Key to compute the Signature Proof of Knowledge and construct the Presentation JPT. + // ======================================================================================================================================= // Construct a JPT(JWP in the Presentation form) representing the Selectively Dislosed Verifiable Credential - // NOTE that we are not following the W3C Presentation standard since not compatible with the JWP standard let presentation_jpt: Jpt = issuer_document .create_presentation_jpt( &mut selective_disclosure_presentation, @@ -219,10 +242,15 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== - // Step 4: Holder sends a verifiable presentation to the verifier. + // Step 8: Holder sends a Presentation JPT to the Verifier. // =========================================================================== + println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); + // =========================================================================== + // Step 9: Verifier receives the Presentation and verifies it. + // =========================================================================== + // Verifier resolve Issuer DID let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 8c1eb59eb3..739d2e3782 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -17,7 +17,6 @@ use crate::error::Result; /// - refreshService MUST NOT be blinded (will be used for Timeslot Revocation mechanism) /// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) /// - evidence (User have to choose which attribute must be blinded) -/// TODO: change this(JwpPresentedBuilder should be constructed inside create_presentation_jwp()) pub struct SelectiveDisclosurePresentation { jwp_builder: JwpPresentedBuilder } @@ -28,15 +27,15 @@ impl SelectiveDisclosurePresentation { pub fn new(issued_jwp: &JwpIssued) -> Self { let mut jwp_builder = JwpPresentedBuilder::new(issued_jwp); - jwp_builder.set_undisclosed("id").ok(); + jwp_builder.set_undisclosed("id").ok(); // Provides linkability - jwp_builder.set_undisclosed("issuanceDate").ok(); + jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not - jwp_builder.set_undisclosed("expirationDate").ok(); + jwp_builder.set_undisclosed("expirationDate").ok(); // Depending on the revocation method used it will be necessary or not - jwp_builder.set_undisclosed("credentialStatus").ok(); + jwp_builder.set_undisclosed("credentialStatus").ok(); // Provides linkability so, there is NO reason to use it in ZK VC - jwp_builder.set_undisclosed("termsOfUse").ok(); + jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC, Self{jwp_builder} } diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs index e90e67fae9..8f836aaa6d 100644 --- a/identity_credential/src/validator/jpt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -6,4 +6,6 @@ mod jpt_credential_validation_options; pub use jpt_credential_validator::*; pub use decoded_jpt_credential::*; pub use jpt_credential_validator_utils::*; -pub use jpt_credential_validation_options::*; \ No newline at end of file +pub use jpt_credential_validation_options::*; + +// TODO: new module for JPT credential validation \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs index 90c91e08fb..e8934c0bea 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -6,4 +6,6 @@ mod jpt_presentation_validator_utils; pub use jpt_presentation_validator::*; pub use jpt_presentation_validation_options::*; pub use decoded_jpt_presentation::*; -pub use jpt_presentation_validator_utils::*; \ No newline at end of file +pub use jpt_presentation_validator_utils::*; + +// TODO: new module for JPT presentation validation \ No newline at end of file diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 36b2f35854..3b27a72ea2 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -53,6 +53,7 @@ pub trait JwpDocumentExt { /// Compute a JWP in the Issued form representing the Verifiable Credential + /// See [JSON Web Proof draft section 4.1](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-issued-form) async fn create_issued_jwp( &self, storage: &Storage, @@ -66,6 +67,7 @@ pub trait JwpDocumentExt { /// Compute a JWP in the Presented form representing the presented Verifiable Credential after the Selective Disclosure of attributes + /// See [JSON Web Proof draft section 4.2](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form) async fn create_presented_jwp( &self, presentation: &mut SelectiveDisclosurePresentation, @@ -74,7 +76,6 @@ pub trait JwpDocumentExt { ) -> StorageResult; /// Produces a JPT where the payload is produced from the given `credential`. - /// TODO: add references to Drafts async fn create_credential_jpt( &self, credential: &Credential, @@ -90,8 +91,7 @@ pub trait JwpDocumentExt { - /// Produces a JPT in the pre where the payload is produced from the given `credential`. - /// TODO: add references to Drafts + /// Produces a JPT where the payload contains the Selective Disclosed attributes of a `credential`. async fn create_presentation_jpt( &self, presentation: &mut SelectiveDisclosurePresentation, @@ -225,8 +225,7 @@ impl JwpDocumentExt for CoreDocument { } - /// Produces a JPT where the payload is produced from the given `credential`. - /// TODO: add references to Drafts + async fn create_credential_jpt( &self, credential: &Credential, @@ -332,8 +331,7 @@ mod iota_document { .await } - /// Produces a JWP where the payload is produced from the given `credential`. - /// TODO: add references to Drafts + async fn create_credential_jpt( &self, credential: &Credential, From 1fea3be9e20a3b4328437bb2f5b9b8a97f57112c Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 12 Jan 2024 15:46:14 +0100 Subject: [PATCH 020/163] fix jti undisclose --- .../src/presentation/jwp_presentation_builder.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 739d2e3782..3bd726dd63 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -9,12 +9,12 @@ use crate::error::Result; /// - id MUST be blinded /// - type MUST NOT be blinded /// - issuer MUST NOT be blinded -/// - issuanceDate MUST be blinded (only if Timeslot Revocation mechanism is used) -/// - expirationDate MUST be blinded (only if Timeslot Revocation mechanism is used) +/// - issuanceDate MUST be blinded (if Timeslot Revocation mechanism is used) +/// - expirationDate MUST be blinded (if Timeslot Revocation mechanism is used) /// - credentialSubject (User have to choose which attribute must be blinded) /// - credentialSchema MUST NOT be blinded -/// - credentialStatus NO reason to use it in ZK VC (will be in any case blinded) -/// - refreshService MUST NOT be blinded (will be used for Timeslot Revocation mechanism) +/// - credentialStatus NO reason to use it in ZK VC (will be blinded in any case) +/// - refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism) /// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) /// - evidence (User have to choose which attribute must be blinded) pub struct SelectiveDisclosurePresentation { @@ -27,7 +27,7 @@ impl SelectiveDisclosurePresentation { pub fn new(issued_jwp: &JwpIssued) -> Self { let mut jwp_builder = JwpPresentedBuilder::new(issued_jwp); - jwp_builder.set_undisclosed("id").ok(); // Provides linkability + jwp_builder.set_undisclosed("jti").ok(); // contains the credential's id, provides linkability jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not From ef77d680d1b544e89ee7e2115f39f71b01d278ca Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 15 Jan 2024 14:31:27 +0100 Subject: [PATCH 021/163] comments and fix nbf undisclosing --- identity_credential/src/credential/credential.rs | 2 +- identity_credential/src/credential/jpt.rs | 2 +- .../src/credential/jwp_credential_options.rs | 2 +- .../src/credential/jwt_serialization.rs | 2 +- identity_credential/src/error.rs | 2 +- .../src/presentation/jwp_presentation_builder.rs | 4 +++- .../src/presentation/jwp_presentation_options.rs | 1 + .../jpt_credential_validator.rs | 7 +++++-- .../src/validator/jpt_credential_validation/mod.rs | 2 +- .../jpt_presentation_validator.rs | 13 +++++++++---- .../validator/jpt_presentation_validation/mod.rs | 2 +- .../validator/jwt_credential_validation/error.rs | 2 +- identity_credential/src/validator/mod.rs | 2 ++ .../src/verifiable/jwp_verification_options.rs | 2 +- identity_jose/src/jwk/jwk_ext.rs | 2 +- identity_jose/src/jwk/key_operation.rs | 2 +- identity_jose/src/jwk/key_use.rs | 2 +- identity_storage/src/key_storage/jwk_storage.rs | 3 ++- .../src/key_storage/key_storage_error.rs | 2 +- identity_storage/src/key_storage/memstore.rs | 3 ++- identity_storage/src/storage/error.rs | 2 +- identity_storage/src/storage/jwk_document_ext.rs | 4 ++-- identity_storage/src/storage/jwp_document_ext.rs | 2 +- identity_storage/src/storage/mod.rs | 2 +- 24 files changed, 42 insertions(+), 27 deletions(-) diff --git a/identity_credential/src/credential/credential.rs b/identity_credential/src/credential/credential.rs index 5c334d80d4..391eeca6a6 100644 --- a/identity_credential/src/credential/credential.rs +++ b/identity_credential/src/credential/credential.rs @@ -176,7 +176,7 @@ impl Credential { .map_err(|err| Error::JwtClaimsSetSerializationError(err.into())) } - //TODO: new method serialization for credential. The CredentialJwtClaims can be reused + //TODO: ZKP - new method serialization for credential. The CredentialJwtClaims can be reused ///Serializes the [`Credential`] as a JPT claims set pub fn serialize_jpt(&self, custom_claims: Option) -> Result where diff --git a/identity_credential/src/credential/jpt.rs b/identity_credential/src/credential/jpt.rs index dfce486c30..671badb945 100644 --- a/identity_credential/src/credential/jpt.rs +++ b/identity_credential/src/credential/jpt.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use serde::Serialize; -///TODO: JPT +///TODO: ZKP - JPT /// This JSON Proof Token could represent a JWP both in the Issued and Presented forms #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct Jpt(String); diff --git a/identity_credential/src/credential/jwp_credential_options.rs b/identity_credential/src/credential/jwp_credential_options.rs index 11c269bacc..57b1c2d8aa 100644 --- a/identity_credential/src/credential/jwp_credential_options.rs +++ b/identity_credential/src/credential/jwp_credential_options.rs @@ -1,5 +1,5 @@ -//TODO: JwpCredentialOptions -> have to choose which options makes sense in the context of jwp +//TODO: ZKP - JwpCredentialOptions /// Options for creating a JSON Web Proof. #[non_exhaustive] diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 4f5fa4acfd..454655cef2 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -679,7 +679,7 @@ mod tests { } -//TODO: convert to JptClaims structure which basically contains the same claim names +//TODO: ZKP - convert to JptClaims structure which basically contains the same claim names impl<'credential, T> From> for JptClaims where T: ToOwned + Serialize, diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index a64d7c1cd9..67f2543dd0 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -71,7 +71,7 @@ pub enum Error { - //TODO: new error for jwp claims + //TODO: ZKP - new error for jwp claims /// Caused by a failure to deserialize the JPT claims set representation of a `Credential` JSON. #[error("could not deserialize JWT claims set")] JptClaimsSetDeserializationError(#[source] Box), diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 3bd726dd63..69532db008 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -3,7 +3,7 @@ use jsonprooftoken::jwp::{presented::JwpPresentedBuilder, issued::JwpIssued}; use crate::error::Error; use crate::error::Result; -//TODO: Used for Selective Disclosure +//TODO: ZKP - Used for Selective Disclosure /// Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes /// - @context MUST NOT be blinded /// - id MUST be blinded @@ -28,6 +28,8 @@ impl SelectiveDisclosurePresentation { let mut jwp_builder = JwpPresentedBuilder::new(issued_jwp); jwp_builder.set_undisclosed("jti").ok(); // contains the credential's id, provides linkability + + jwp_builder.set_undisclosed("nbf").ok(); // jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not diff --git a/identity_credential/src/presentation/jwp_presentation_options.rs b/identity_credential/src/presentation/jwp_presentation_options.rs index 8191d1fcb6..d6df1f1b73 100644 --- a/identity_credential/src/presentation/jwp_presentation_options.rs +++ b/identity_credential/src/presentation/jwp_presentation_options.rs @@ -1,6 +1,7 @@ use identity_core::common::Url; use serde::{Serialize, Deserialize}; +//TODO: ZKP - JwpPresentationOptions /// Options to be set in the JWT claims of a verifiable presentation. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index 0af7d76864..a2897abc5b 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -13,7 +13,7 @@ use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationEr use super::DecodedJptCredential; -/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: validator +/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: ZKP - validator #[non_exhaustive] pub struct JptCredentialValidator; @@ -76,6 +76,7 @@ impl JptCredentialValidator { ) }); + let issuance_date_validation = std::iter::once_with(|| { JwtCredentialValidatorUtils::check_issued_on_or_before( credential, @@ -83,6 +84,8 @@ impl JptCredentialValidator { ) }); + + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); @@ -91,7 +94,7 @@ impl JptCredentialValidator { .chain(structure_validation); - //TODO: check revocation when implemented + //TODO: ZKP - check revocation when implemented // #[cfg(feature = "revocation-bitmap")] // let validation_units_iter = { diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs index 8f836aaa6d..7aed007347 100644 --- a/identity_credential/src/validator/jpt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -8,4 +8,4 @@ pub use decoded_jpt_credential::*; pub use jpt_credential_validator_utils::*; pub use jpt_credential_validation_options::*; -// TODO: new module for JPT credential validation \ No newline at end of file +// TODO: ZKP - new module for JPT credential validation \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index b3e60ab419..e750e01834 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -9,7 +9,7 @@ use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{Fail use super::{JptPresentationValidationOptions, DecodedJptPresentation}; -/// A type for decoding and validating Presented [`Credential`]s in JPT format. //TODO: validator +/// A type for decoding and validating Presented [`Credential`]s in JPT format. //TODO: ZKP - validator #[non_exhaustive] pub struct JptPresentationValidator; @@ -24,7 +24,7 @@ impl JptPresentationValidator { /// - the issuance date, /// - the semantic structure. pub fn validate( - presentation_jpt: &Jpt, //TODO: the validation process could be handled both for JWT and JPT by the same function, the function could recognise if the token in input is a JWT or JPT based on the typ field + presentation_jpt: &Jpt, issuer: &DOC, options: &JptPresentationValidationOptions, fail_fast: FailFast, @@ -145,9 +145,14 @@ impl JptPresentationValidator { let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; let payloads = decoded_jwp.get_payloads(); - let jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + let mut jpt_claims = JptClaims::from_claims_and_payloads(&claims, payloads); + // if not set the deserializatioon will throw an error since even the iat is not set, so we set this to 0 + jpt_claims.nbf.map_or_else(|| { + jpt_claims.set_nbf(0); + }, |_| ()); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())))?; - + // Deserialize the raw claims let credential_claims: CredentialJwtClaims<'_, T> = diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs index e8934c0bea..1c44e1df68 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -8,4 +8,4 @@ pub use jpt_presentation_validation_options::*; pub use decoded_jpt_presentation::*; pub use jpt_presentation_validator_utils::*; -// TODO: new module for JPT presentation validation \ No newline at end of file +// TODO: ZKP - new module for JPT presentation validation \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index 9bfa7b7fba..18c4e24512 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -103,7 +103,7 @@ pub enum JwtValidationError { Revoked, - //TODO: new errors for jwp + //TODO: ZKP - new errors for jwp /// Indicates that the JWP representation of an issued credential or presentation could not be decoded. #[error("could not decode jwp")] JwpDecodingError(#[source] jsonprooftoken::errors::CustomError), diff --git a/identity_credential/src/validator/mod.rs b/identity_credential/src/validator/mod.rs index 832b78103a..fd083a3a34 100644 --- a/identity_credential/src/validator/mod.rs +++ b/identity_credential/src/validator/mod.rs @@ -11,6 +11,8 @@ pub use self::options::FailFast; pub use self::options::StatusCheck; pub use self::options::SubjectHolderRelationship; +//TODO: ZKP - new modules + mod jpt_credential_validation; mod jpt_presentation_validation; mod jwt_credential_validation; diff --git a/identity_document/src/verifiable/jwp_verification_options.rs b/identity_document/src/verifiable/jwp_verification_options.rs index 46459499e1..c23ad24ab2 100644 --- a/identity_document/src/verifiable/jwp_verification_options.rs +++ b/identity_document/src/verifiable/jwp_verification_options.rs @@ -2,7 +2,7 @@ use identity_did::DIDUrl; use identity_verification::MethodScope; -//TODO: JwpVerificationOptions +//TODO: ZKP - JwpVerificationOptions /// Holds additional options for verifying a JWP #[non_exhaustive] diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs index 3337cc5ac5..e5644e1cf4 100644 --- a/identity_jose/src/jwk/jwk_ext.rs +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -1,4 +1,4 @@ -//TODO: JwkExt +//TODO: ZKP - JwkExt use std::str::FromStr; use identity_core::common::Url; diff --git a/identity_jose/src/jwk/key_operation.rs b/identity_jose/src/jwk/key_operation.rs index 38ea9e67e2..7105d63940 100644 --- a/identity_jose/src/jwk/key_operation.rs +++ b/identity_jose/src/jwk/key_operation.rs @@ -28,7 +28,7 @@ pub enum JwkOperation { /// Derive bits not to be used as a key. DeriveBits, - //TODO: added ProofGeneration/ProofVerification + //TODO: ZKP - added ProofGeneration/ProofVerification /// Compute proof ProofGeneration, /// Verify proof diff --git a/identity_jose/src/jwk/key_use.rs b/identity_jose/src/jwk/key_use.rs index ceb5777f66..93a3fc76a0 100644 --- a/identity_jose/src/jwk/key_use.rs +++ b/identity_jose/src/jwk/key_use.rs @@ -17,7 +17,7 @@ pub enum JwkUse { #[serde(rename = "enc")] Encryption, - //TODO: added Proof + //TODO: ZKP - added Proof /// Proof Proof } diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index cf7df4e140..156fe6b26a 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -68,7 +68,8 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { -///TODO: Extension to the JwkStorage to handle BBS+ keys +//TODO: ZKP - JwkStorageExt +/// Extension to the JwkStorage to handle BBS+ keys #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwkStorageExt : JwkStorage { diff --git a/identity_storage/src/key_storage/key_storage_error.rs b/identity_storage/src/key_storage/key_storage_error.rs index 164f555a8d..b6afd28b9a 100644 --- a/identity_storage/src/key_storage/key_storage_error.rs +++ b/identity_storage/src/key_storage/key_storage_error.rs @@ -24,7 +24,7 @@ pub enum KeyStorageErrorKind { UnsupportedSignatureAlgorithm, /// Indicates an attempt to parse a proof algorithm that is not recognized by the key storage implementation. - UnsupportedProofAlgorithm, //TODO: new error + UnsupportedProofAlgorithm, //TODO: ZKP - new error /// Indicates that the key storage implementation is not able to find the requested key. KeyNotFound, diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 5ba377956b..8ba515dfc9 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -287,7 +287,8 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> } -//TODO: implementation of JwkStorageExt for JwkMemStore +//TODO: ZKP - impl JwkStorageExt for JwkMemStore +/// JwkStorageExt implementation for JwkMemStore #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkStorageExt for JwkMemStore { diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index c4ceaae490..ff5c9c6e89 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -28,7 +28,7 @@ pub enum JwkStorageDocumentError { #[error("invalid JWS algorithm")] InvalidJwsAlgorithm, - /// Caused by an invalid JWP algorithm. //TODO: new error + /// Caused by an invalid JWP algorithm. //TODO: ZKP - new error #[error("invalid JWP algorithm")] InvalidJwpAlgorithm, /// Cannot cunstruct a valid Jwp (issued or presented form) diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index ede538da98..49b32fbbb4 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -152,7 +152,7 @@ mod private { // be implemented in terms of &mut CoreDocument for IotaDocument. To work around this limitation we use macros to avoid // copious amounts of repetition. // NOTE: If such use of macros becomes very common it is probably better to use the duplicate crate: https://docs.rs/duplicate/latest/duplicate/ -macro_rules! generate_method_for_document_type { //TODO: changed macro to handle both JwkDocumentExt and JwpDocumentExt traits +macro_rules! generate_method_for_document_type { //TODO: ZKP - changed macro to handle both JwkDocumentExt and JwpDocumentExt traits ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { async fn $name( document: &mut $t, @@ -314,7 +314,7 @@ impl JwkDocumentExt for CoreDocument { &mut self, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithm, //TODO: changed to generic Algorithm + alg: JwsAlgorithm, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 3b27a72ea2..a28079a49d 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -1,4 +1,4 @@ -//TODO:: JwpDocumentExt +//TODO: ZKP - JwpDocumentExt use identity_core::common::Object; use identity_credential::credential::Credential; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 9abf873674..d7c8ffe830 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -4,7 +4,7 @@ //! This module provides a type wrapping a key and key id storage. mod error; -#[macro_use] //TODO: to be able to call the macro from jwp_document_ext.rs +#[macro_use] //TODO: ZKP - to be able to call the macro from jwp_document_ext.rs mod jwk_document_ext; mod jwp_document_ext; mod signature_options; From 4becafb7c1b220feb74fc88e201c521f6f5a6934 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 25 Jan 2024 16:57:58 +0100 Subject: [PATCH 022/163] new RevocationTimeframeStatus and revocation check --- examples/0_basic/7_revoke_vc.rs | 2 + examples/1_advanced/7_zkp.rs | 12 +- examples/1_advanced/8_zkp_revocation.rs | 280 ++++++++++++++++++ examples/Cargo.toml | 6 +- identity_core/src/common/mod.rs | 1 + identity_core/src/common/timestamp.rs | 21 ++ identity_credential/src/credential/mod.rs | 2 + .../credential/revocation_timeframe_status.rs | 194 ++++++++++++ .../presentation/jwp_presentation_builder.rs | 3 +- .../decoded_jpt_credential.rs | 2 + .../jpt_credential_validation_options.rs | 17 +- .../jpt_credential_validator.rs | 13 +- .../jpt_credential_validator_utils.rs | 84 ++++++ .../decoded_jpt_presentation.rs | 2 + .../jpt_presentation_validation_options.rs | 29 +- .../jpt_presentation_validator.rs | 86 ++++-- 16 files changed, 707 insertions(+), 47 deletions(-) create mode 100644 examples/1_advanced/8_zkp_revocation.rs create mode 100644 identity_credential/src/credential/revocation_timeframe_status.rs diff --git a/examples/0_basic/7_revoke_vc.rs b/examples/0_basic/7_revoke_vc.rs index eee2ba6b4b..c01d54d0a2 100644 --- a/examples/0_basic/7_revoke_vc.rs +++ b/examples/0_basic/7_revoke_vc.rs @@ -110,6 +110,8 @@ async fn main() -> anyhow::Result<()> { // Publish the updated Alias Output. issuer_document = client.publish_did_output(&secret_manager_issuer, alias_output).await?; + println!("DID Document > {issuer_document:#}"); + // Create a credential subject indicating the degree earned by Alice. let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index 736e778632..ff9f27cfa2 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -1,5 +1,7 @@ use std::str::FromStr; +use std::thread; +use std::time::Duration; use examples::get_address_with_funds; use examples::random_stronghold_path; @@ -20,8 +22,11 @@ use identity_iota::credential::JptPresentationValidator; use identity_iota::credential::JptPresentationValidatorUtils; use identity_iota::credential::JwpCredentialOptions; use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::RevocationTimeframeStatus; use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Status; use identity_iota::credential::Subject; +use identity_iota::credential::ValidityTimeframeGranularity; use identity_iota::did::CoreDID; use identity_iota::did::DID; use identity_iota::did::DIDUrl; @@ -48,6 +53,8 @@ use iota_sdk::client::Password; use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; +use iota_sdk::types::block::output::AliasOutputBuilder; +use iota_sdk::types::block::output::RentStructure; use iota_sdk::types::block::output::TokenId; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jwp::header::PresentationProtectedHeader; @@ -129,7 +136,6 @@ async fn main() -> anyhow::Result<()> { let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256).await?; - // =========================================================================== // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. // =========================================================================== @@ -145,7 +151,6 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - // Build credential using subject above and issuer. let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -240,7 +245,6 @@ async fn main() -> anyhow::Result<()> { ) .await?; - // =========================================================================== // Step 8: Holder sends a Presentation JPT to the Verifier. // =========================================================================== @@ -268,7 +272,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("Presented Credential successfully validated: {:#?}", decoded_presented_credential.credential); + println!("Presented Credential successfully validated: {:#}", decoded_presented_credential.credential); Ok(()) } diff --git a/examples/1_advanced/8_zkp_revocation.rs b/examples/1_advanced/8_zkp_revocation.rs new file mode 100644 index 0000000000..5bc73ac658 --- /dev/null +++ b/examples/1_advanced/8_zkp_revocation.rs @@ -0,0 +1,280 @@ + +use std::str::FromStr; +use std::thread; +use std::time::Duration; + +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::core::json; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::JwtValidationError; +use identity_iota::credential::RevocationTimeframeStatus; +use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Status; +use identity_iota::credential::Subject; +use identity_iota::credential::ValidityTimeframeGranularity; +use identity_iota::did::CoreDID; +use identity_iota::did::DID; +use identity_iota::did::DIDUrl; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyIdStorage; +use identity_iota::storage::KeyType; +use identity_iota::storage::Storage; +use identity_iota::verification::MethodRelationship; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::api::core::response::WhiteFlagResponse; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; +use iota_sdk::types::block::output::AliasOutputBuilder; +use iota_sdk::types::block::output::RentStructure; +use iota_sdk::types::block::output::TokenId; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use jsonprooftoken::jwp::presented::JwpPresentedBuilder; + +// The API endpoint of an IOTA node, e.g. Hornet. +const api_endpoint: &str = "http://localhost:14265"; +// The faucet endpoint allows requesting funds for testing purposes. +const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; + + +// const api_endpoint: &str = "https://api.testnet.shimmer.network"; +// const faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; + + + +async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: ProofAlgorithm ) -> anyhow::Result<(Address, IotaDocument, String)> { + + // Get an address with funds for testing. + let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + + let fragment = document.generate_method_jwp( + &storage, + key_type, + alg, + None, + MethodScope::VerificationMethod + ).await?; + + // issuer_document.generate_method( + // &storage, + // JwkMemStore::ED25519_KEY_TYPE, + // JwsAlgorithm::EdDSA, + // None, + // MethodScope::VerificationMethod + // ).await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) +} + + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(api_endpoint, None)? + .finish() + .await?; + + + let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?); + + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256).await?; + + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + + // ========================================================================================= + // Step 1: Create a new RevocationTimeframeStatus containing the current validityTimeframe + // ======================================================================================= + let status: Status = RevocationTimeframeStatus::new(ValidityTimeframeGranularity::MINUTE).into(); + + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .status(status) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + + + // Issuer sends the Verifiable Credential to the holder. + println!("Sending credential (as JPT) to the holder: {}\n", credential_jpt.as_str()); + + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_issued_jpt(&credential_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + + // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decoded_credential = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation.undisclose_subject("mainCourses[1]").unwrap(); + selective_disclosure_presentation.undisclose_subject("degree.name").unwrap(); + + + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge) + ) + .await?; + + + + + // Holder sends a Presentation JPT to the Verifier. + println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); + + // =========================================================================== + // Step 2a: Verifier receives the Presentation and verifies it. + // =========================================================================== + + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + let presentation_validation_options = + JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + // Check validityTimeframe + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("Presented Credential successfully validated: {:#}", decoded_presented_credential.credential); + + + // =========================================================================== + // Step 2b: Waiting for the next validityTimeframe, will result in the Credential being revoked + // =========================================================================== + + thread::sleep(Duration::from_secs(61)); //Will result revoked with GRANULARITY set to MINUTE + + let validation_result = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ); + + println!("VC validation result: {validation_result:?}"); + + // We expect validation to no longer succeed because the credential was NOT updated. + assert!(matches!( + validation_result.unwrap_err().validation_errors[0], + JwtValidationError::Revoked + )); + + Ok(()) +} \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index cca0419caf..dca76f1ff8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -85,4 +85,8 @@ name = "6_domain_linkage" [[example]] path = "1_advanced/7_zkp.rs" -name = "7_zkp" \ No newline at end of file +name = "7_zkp" + +[[example]] +path = "1_advanced/8_zkp_revocation.rs" +name = "8_zkp_revocation" \ No newline at end of file diff --git a/identity_core/src/common/mod.rs b/identity_core/src/common/mod.rs index 8d6be52251..226a573b18 100644 --- a/identity_core/src/common/mod.rs +++ b/identity_core/src/common/mod.rs @@ -15,6 +15,7 @@ pub use self::timestamp::Duration; pub use self::timestamp::Timestamp; pub use self::url::Url; + mod context; mod key_comparable; mod object; diff --git a/identity_core/src/common/timestamp.rs b/identity_core/src/common/timestamp.rs index 8de1832409..aa414c6952 100644 --- a/identity_core/src/common/timestamp.rs +++ b/identity_core/src/common/timestamp.rs @@ -112,6 +112,17 @@ impl Timestamp { .checked_sub(duration.0) .and_then(|offset_date_time| Self::from_unix(offset_date_time.unix_timestamp()).ok()) } + + //TODO: ZKP - validity_timeframe Timestamp + /// Truncate to minutes + pub fn to_minute(&self) -> Self { + Self(truncate_fractional_minutes(self.0)) + } + + /// Truncate to hours + pub fn to_hour(&self) -> Self { + Self(truncate_fractional_hours(self.0)) + } } impl Default for Timestamp { @@ -181,6 +192,16 @@ fn truncate_fractional_seconds(offset_date_time: OffsetDateTime) -> OffsetDateTi offset_date_time - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) } +/// Truncates an `OffsetDateTime` to the minute. +fn truncate_fractional_minutes(offset_date_time: OffsetDateTime) -> OffsetDateTime { + offset_date_time - time::Duration::seconds(offset_date_time.second() as i64) - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) +} + +/// Truncates an `OffsetDateTime` to the hour. +fn truncate_fractional_hours(offset_date_time: OffsetDateTime) -> OffsetDateTime { + offset_date_time - time::Duration::minutes(offset_date_time.minute() as i64) - time::Duration::seconds(offset_date_time.second() as i64) - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) +} + /// A span of time. /// /// This type is typically used to increment or decrement a [`Timestamp`]. diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 7f7451161b..2af7369ff6 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -23,6 +23,7 @@ mod schema; mod status; mod subject; mod jwp_credential_options; +mod revocation_timeframe_status; pub use self::builder::CredentialBuilder; pub use self::credential::Credential; @@ -41,6 +42,7 @@ pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; pub use self::jwp_credential_options::JwpCredentialOptions; +pub use self::revocation_timeframe_status::{RevocationTimeframeStatus, ValidityTimeframeGranularity}; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::CredentialJwtClaims; diff --git a/identity_credential/src/credential/revocation_timeframe_status.rs b/identity_credential/src/credential/revocation_timeframe_status.rs new file mode 100644 index 0000000000..5d60fc46b5 --- /dev/null +++ b/identity_credential/src/credential/revocation_timeframe_status.rs @@ -0,0 +1,194 @@ +use std::str::FromStr; + +use identity_core::common::{Object, Url, Timestamp}; +use identity_core::convert::{ToJson, FromJson}; +use identity_did::DIDUrl; +use identity_core::common::Value; +use serde::Deserialize; +use serde::Serialize; +use crate::error::Result; +use crate::error::Error; +use super::Status; + + +//TODO: ZKP - RevocationTimeframeStatus and ValidityTimeframeEpoch + + +/// Validity Timeframe granularity +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ValidityTimeframeGranularity { + /// Seconds + SECOND, + /// Minutes + MINUTE, + /// Hours + HOUR +} + +impl ToString for ValidityTimeframeGranularity { + fn to_string(&self) -> String { + match self { + ValidityTimeframeGranularity::SECOND => String::from("SECOND"), + ValidityTimeframeGranularity::MINUTE => String::from("MINUTE"), + ValidityTimeframeGranularity::HOUR => String::from("HOUR"), + } + } +} + +impl FromStr for ValidityTimeframeGranularity { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "SECOND" => Ok(ValidityTimeframeGranularity::SECOND), + "MINUTE" => Ok(ValidityTimeframeGranularity::MINUTE), + "HOUR" => Ok(ValidityTimeframeGranularity::HOUR), + _ => Err("Invalid string representation for ValidityTimeframeEpoch"), + } + } +} + +/// Information used to determine the current status of a [`Credential`][crate::credential::Credential] +/// using the `RevocationBitmap2022` specification. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RevocationTimeframeStatus(Status); + +impl RevocationTimeframeStatus { + const TIMEFRAME_PROPERTY: &'static str = "validityTimeFrame"; + const GRANULARITY: &'static str = "granularity"; + /// Type name of the revocation bitmap. + pub const TYPE: &'static str = "RevocationTimeframe2024"; + + /// Creates a new `RevocationTimeframeStatus`. + pub fn new(epoch: ValidityTimeframeGranularity) -> Self { + let did_url: DIDUrl = DIDUrl::parse("did:method:0xffff#revocation-1").unwrap(); + let mut object = Object::new(); + + + + let validity_timeframe = match epoch { + ValidityTimeframeGranularity::SECOND => Timestamp::now_utc(), + ValidityTimeframeGranularity::MINUTE => Timestamp::now_utc().to_minute(), + ValidityTimeframeGranularity::HOUR => Timestamp::now_utc().to_hour(), + }; + + object.insert(Self::TIMEFRAME_PROPERTY.to_owned(), Value::String(validity_timeframe.to_rfc3339())); + object.insert(Self::GRANULARITY.to_owned(), Value::String(epoch.to_string())); + Self(Status::new_with_properties( + Url::from(did_url), // Here maybe i could put the id of the service of the issuer document containing the revocationbitmap if we choose to use it and add also an index here + // if we use a database though this field is useless + Self::TYPE.to_owned(), + object, + )) + + } + + + /// Get validityTimeframe value + pub fn validity_timeframe(&self) -> Result { + if let Some(Value::String(timeframe)) = self.0.properties.get(Self::TIMEFRAME_PROPERTY) { + + Timestamp::from_str(&timeframe).map_err(|_| Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::TIMEFRAME_PROPERTY + ))) + + } else { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::TIMEFRAME_PROPERTY + ))); + } + } + + /// Get granularity value + pub fn granularity(&self) -> Result { + if let Some(Value::String(epoch)) = self.0.properties.get(Self::GRANULARITY) { + + ValidityTimeframeGranularity::from_str(&epoch).map_err(|_| Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::GRANULARITY + ))) + + } else { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::GRANULARITY + ))); + } + } + +} + + +impl TryFrom for RevocationTimeframeStatus { + type Error = Error; + + fn try_from(status: Status) -> Result { + if status.type_ != Self::TYPE { + return Err(Self::Error::InvalidStatus(format!( + "expected type '{}', got '{}'", + Self::TYPE, + status.type_ + ))); + } + + let revocation_timeframe: &Value = + if let Some(revocation_timeframe) = status.properties.get(Self::TIMEFRAME_PROPERTY) { + revocation_timeframe + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + Self::TIMEFRAME_PROPERTY + ))); + }; + + let revocation_timeframe: Timestamp = if let Value::String(timeframe) = revocation_timeframe { + + Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::TIMEFRAME_PROPERTY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::TIMEFRAME_PROPERTY + ))); + }; + + + let revocation_timeframe_epoch: &Value = + if let Some(revocation_timeframe_epoch) = status.properties.get(Self::GRANULARITY) { + revocation_timeframe_epoch + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + Self::GRANULARITY + ))); + }; + + let revocation_timeframe_epoch: ValidityTimeframeGranularity = if let Value::String(epoch) = revocation_timeframe_epoch { + + ValidityTimeframeGranularity::from_str(&epoch).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::GRANULARITY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::GRANULARITY + ))); + }; + + Ok(Self(status)) + } +} + + +impl From for Status { + fn from(status: RevocationTimeframeStatus) -> Self { + status.0 + } +} \ No newline at end of file diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 69532db008..61e9bf149a 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -34,7 +34,8 @@ impl SelectiveDisclosurePresentation { jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not jwp_builder.set_undisclosed("expirationDate").ok(); // Depending on the revocation method used it will be necessary or not - + jwp_builder.set_undisclosed("exp").ok(); + jwp_builder.set_undisclosed("credentialStatus").ok(); // Provides linkability so, there is NO reason to use it in ZK VC jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC, diff --git a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs index 93e9219583..442f87f75c 100644 --- a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs +++ b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs @@ -4,6 +4,8 @@ use jsonprooftoken::jwp::issued::JwpIssued; use crate::credential::Credential; /// Decoded [`Credential`] from a cryptographically verified JWP. +/// #[non_exhaustive] +#[derive(Debug, Clone)] pub struct DecodedJptCredential { /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). pub credential: Credential, diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs index 062c33cb41..4e07c5d58d 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -1,9 +1,12 @@ use identity_core::common::Timestamp; +use identity_core::common::Url; use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; +use crate::validator::SubjectHolderRelationship; + /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. #[non_exhaustive] #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -21,6 +24,12 @@ pub struct JptCredentialValidationOptions { #[serde(default)] pub latest_issuance_date: Option, + /// Validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + /// + /// Default: [`StatusCheck::Strict`](crate::validator::StatusCheck::Strict). + #[serde(default)] + pub status: crate::validator::StatusCheck, + /// Options which affect the verification of the proof on the credential. #[serde(default)] pub verification_options: JwpVerificationOptions, @@ -46,7 +55,13 @@ impl JptCredentialValidationOptions { self } - /// Set options which affect the verification of the JWS signature. + /// Sets the validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + pub fn status_check(mut self, status_check: crate::validator::StatusCheck) -> Self { + self.status = status_check; + self + } + + /// Set options which affect the verification of the JWP proof. pub fn verification_options(mut self, options: JwpVerificationOptions) -> Self { self.verification_options = options; self diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index a2897abc5b..68d8543566 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -11,7 +11,7 @@ use crate::credential::CredentialJwtClaims; use crate::validator::{JwtCredentialValidatorUtils, JptCredentialValidationOptions, CompoundCredentialValidationError}; use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationError, jwt_credential_validation::SignerContext}}; -use super::DecodedJptCredential; +use super::{DecodedJptCredential, JptCredentialValidatorUtils}; /// A type for decoding and validating [`Credential`]s in JPT format. //TODO: ZKP - validator #[non_exhaustive] @@ -96,12 +96,11 @@ impl JptCredentialValidator { //TODO: ZKP - check revocation when implemented - // #[cfg(feature = "revocation-bitmap")] - // let validation_units_iter = { - // let revocation_validation = - // std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status)); - // validation_units_iter.chain(revocation_validation) - // }; + let validation_units_iter = { + let revocation_validation = + std::iter::once_with(|| JptCredentialValidatorUtils::check_status(credential, options.status)); + validation_units_iter.chain(revocation_validation) + }; let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); let validation_errors: Vec = match fail_fast { diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index 1c2ac0a748..4454d4e6ff 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use crate::credential::Credential; use identity_core::{convert::{ToJson, FromJson}, common::Object}; use identity_did::DID; @@ -11,8 +12,26 @@ use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, Cr #[non_exhaustive] pub struct JptCredentialValidatorUtils; +type ValidationUnitResult = std::result::Result; + impl JptCredentialValidatorUtils { + /// Utility for extracting the issuer field of a [`Credential`] as a DID. + /// + /// # Errors + /// + /// Fails if the issuer field is not a valid DID. + pub fn extract_issuer(credential: &Credential) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + D::from_str(credential.issuer.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } + /// Utility for extracting the issuer field of a credential in JPT representation as DID. /// /// # Errors @@ -42,4 +61,69 @@ impl JptCredentialValidatorUtils { source: err.into(), }) } + + + /// Checks whether the credential status has been revoked. + pub fn check_status( + credential: &Credential, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + + use crate::credential::RevocationTimeframeStatus; + + + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: crate::credential::RevocationTimeframeStatus = + crate::credential::RevocationTimeframeStatus::try_from(status.clone()) + .map_err(JwtValidationError::InvalidStatus)?; + + Self::check_revocation_validity_timeframe_status(status) + + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + return Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))); + } + + } + } + } + + + fn check_revocation_validity_timeframe_status( + status: crate::credential::RevocationTimeframeStatus, + ) -> ValidationUnitResult { + use identity_core::common::Timestamp; + + + let now = Timestamp::now_utc(); + + let check = status.validity_timeframe().is_ok_and(|t| { + status.granularity().is_ok_and(|e| {{ + match e { + crate::credential::ValidityTimeframeGranularity::SECOND => now == t, + crate::credential::ValidityTimeframeGranularity::MINUTE => now.to_minute() == t, + crate::credential::ValidityTimeframeGranularity::HOUR => now.to_hour() == t, + } + }}) + }); + + if !check { + Err(JwtValidationError::Revoked) + } else { + Ok(()) + } + + } } \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs index baf3447e0f..d3f8762856 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs @@ -4,6 +4,8 @@ use jsonprooftoken::jwp::presented::JwpPresented; use crate::credential::Credential; /// Decoded [`Credential`] from a cryptographically verified JWP. +/// #[non_exhaustive] +#[derive(Debug, Clone)] pub struct DecodedJptPresentation { /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). pub credential: Credential, diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs index ad9c15d41f..f96aed3f21 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -1,3 +1,4 @@ +use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; use crate::validator::JptCredentialValidationOptions; @@ -12,9 +13,15 @@ pub struct JptPresentationValidationOptions { #[serde(default)] pub nonce: Option, - /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. + /// Validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + /// + /// Default: [`StatusCheck::Strict`](crate::validator::StatusCheck::Strict). #[serde(default)] - pub credential_validation_options: JptCredentialValidationOptions, + pub status: crate::validator::StatusCheck, + + /// Options which affect the verification of the proof on the credential. + #[serde(default)] + pub verification_options: JwpVerificationOptions, } @@ -24,12 +31,6 @@ impl JptPresentationValidationOptions { Self::default() } - /// Set options which affect the verification of the signature on the presentation. - pub fn credential_validation_options(mut self, options: JptCredentialValidationOptions) -> Self { - self.credential_validation_options = options; - self - } - /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. /// Uses the current datetime during validation if not set. pub fn nonce(mut self, nonce: impl Into) -> Self { @@ -38,4 +39,16 @@ impl JptPresentationValidationOptions { } + /// Sets the validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + pub fn status_check(mut self, status_check: crate::validator::StatusCheck) -> Self { + self.status = status_check; + self + } + + /// Set options which affect the verification of the JWP proof. + pub fn verification_options(mut self, options: JwpVerificationOptions) -> Self { + self.verification_options = options; + self + } + } diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index e750e01834..0e72c46626 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -5,7 +5,7 @@ use identity_did::{DIDUrl, CoreDID}; use identity_document::document::CoreDocument; use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, encoding::SerializationType, jwk::key::Jwk as JwkExt, jpt::claims::JptClaims}; -use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{FailFast, JwtValidationError, SignerContext, JwtCredentialValidatorUtils, CompoundCredentialValidationError, JptCredentialValidator}}; +use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{CompoundCredentialValidationError, FailFast, JptCredentialValidationOptions, JptCredentialValidator, JptCredentialValidatorUtils, JwtCredentialValidatorUtils, JwtValidationError, SignerContext}}; use super::{JptPresentationValidationOptions, DecodedJptPresentation}; @@ -24,34 +24,70 @@ impl JptPresentationValidator { /// - the issuance date, /// - the semantic structure. pub fn validate( - presentation_jpt: &Jpt, - issuer: &DOC, - options: &JptPresentationValidationOptions, - fail_fast: FailFast, - ) -> Result, CompoundCredentialValidationError> - where - T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, - DOC: AsRef, - { + presentation_jpt: &Jpt, + issuer: &DOC, + options: &JptPresentationValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { - // First verify the JWP proof and decode the result into a presented credential token, then apply all other validations. - let presented_credential_token = - Self::verify_proof(presentation_jpt, issuer, &options) - .map_err(|err| CompoundCredentialValidationError { - validation_errors: [err].into(), - })?; + // First verify the JWP proof and decode the result into a presented credential token, then apply all other validations. + let presented_credential_token = + Self::verify_proof(presentation_jpt, issuer, &options) + .map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; - let credential: &Credential = &presented_credential_token.credential; + let credential: &Credential = &presented_credential_token.credential; - JptCredentialValidator::validate_credential::( - credential, - &options.credential_validation_options, - fail_fast, - )?; + Self::validate_presented_credential::( + credential, + &options, + fail_fast, + )?; + + Ok(presented_credential_token) + } - Ok(presented_credential_token) + + pub(crate) fn validate_presented_credential( + credential: &Credential, + options: &JptPresentationValidationOptions, + fail_fast: FailFast, + ) -> Result<(), CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); + + let validation_units_iter = structure_validation; + + //TODO: ZKP - check revocation when implemented + + let validation_units_iter = { + let revocation_validation = + std::iter::once_with(|| JptCredentialValidatorUtils::check_status(credential, options.status)); + validation_units_iter.chain(revocation_validation) + }; + + let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); + let validation_errors: Vec = match fail_fast { + FailFast::FirstError => validation_units_error_iter.take(1).collect(), + FailFast::AllErrors => validation_units_error_iter.collect(), + }; + + if validation_errors.is_empty() { + Ok(()) + } else { + Err(CompoundCredentialValidationError { validation_errors }) } + } @@ -78,7 +114,7 @@ impl JptPresentationValidator { // If no method_url is set, parse the `kid` to a DID Url which should be the identifier // of a verification method in a trusted issuer's DID document. - let method_id: DIDUrl = match &options.credential_validation_options.verification_options.method_id { + let method_id: DIDUrl = match &options.verification_options.method_id { Some(method_id) => method_id.clone(), None => { let kid: &str = decoded.get_issuer_header().kid().ok_or( @@ -107,7 +143,7 @@ impl JptPresentationValidator { // Obtain the public key from the issuer's DID document let public_key: JwkExt = issuer - .resolve_method(&method_id, options.credential_validation_options.verification_options.method_scope) + .resolve_method(&method_id, options.verification_options.method_scope) .and_then(|method| method.data().public_key_jwk()) .and_then(|k| k.try_into().ok()) //Conversio into jsonprooftoken::Jwk type .ok_or_else(|| JwtValidationError::MethodDataLookupError { From b03bdc1c7a6af4067f032670e757d37744fb5fd5 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 25 Jan 2024 17:17:29 +0100 Subject: [PATCH 023/163] remove unused 'use' statements --- examples/1_advanced/7_zkp.rs | 38 +++---------------- examples/1_advanced/8_zkp_revocation.rs | 33 ++-------------- .../credential/revocation_timeframe_status.rs | 19 ++++------ .../jpt_credential_validation_options.rs | 3 -- .../jpt_presentation_validation_options.rs | 1 - .../jpt_presentation_validator.rs | 2 +- identity_storage/src/key_storage/memstore.rs | 4 +- 7 files changed, 20 insertions(+), 80 deletions(-) diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/7_zkp.rs index ff9f27cfa2..e6e400e472 100644 --- a/examples/1_advanced/7_zkp.rs +++ b/examples/1_advanced/7_zkp.rs @@ -1,8 +1,4 @@ -use std::str::FromStr; -use std::thread; -use std::time::Duration; - use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; @@ -22,48 +18,32 @@ use identity_iota::credential::JptPresentationValidator; use identity_iota::credential::JptPresentationValidatorUtils; use identity_iota::credential::JwpCredentialOptions; use identity_iota::credential::JwpPresentationOptions; -use identity_iota::credential::RevocationTimeframeStatus; use identity_iota::credential::SelectiveDisclosurePresentation; -use identity_iota::credential::Status; use identity_iota::credential::Subject; -use identity_iota::credential::ValidityTimeframeGranularity; use identity_iota::did::CoreDID; use identity_iota::did::DID; -use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::KeyIdStorage; use identity_iota::storage::KeyType; -use identity_iota::storage::Storage; -use identity_iota::verification::MethodRelationship; -use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; -use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::RentStructure; -use iota_sdk::types::block::output::TokenId; use jsonprooftoken::jpa::algs::ProofAlgorithm; -use jsonprooftoken::jwp::header::PresentationProtectedHeader; -use jsonprooftoken::jwp::presented::JwpPresentedBuilder; // The API endpoint of an IOTA node, e.g. Hornet. -const api_endpoint: &str = "http://localhost:14265"; +const API_ENDPOINT: &str = "http://localhost:14265"; // The faucet endpoint allows requesting funds for testing purposes. -const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; +const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; // const api_endpoint: &str = "https://api.testnet.shimmer.network"; @@ -74,7 +54,7 @@ const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: ProofAlgorithm ) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; + let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; @@ -83,7 +63,7 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); - + // New Verification Method containing a BBS+ key let fragment = document.generate_method_jwp( &storage, key_type, @@ -92,14 +72,6 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M MethodScope::VerificationMethod ).await?; - // issuer_document.generate_method( - // &storage, - // JwkMemStore::ED25519_KEY_TYPE, - // JwsAlgorithm::EdDSA, - // None, - // MethodScope::VerificationMethod - // ).await?; - // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; @@ -121,7 +93,7 @@ async fn main() -> anyhow::Result<()> { // Create a new client to interact with the IOTA ledger. let client: Client = Client::builder() - .with_primary_node(api_endpoint, None)? + .with_primary_node(API_ENDPOINT, None)? .finish() .await?; diff --git a/examples/1_advanced/8_zkp_revocation.rs b/examples/1_advanced/8_zkp_revocation.rs index 5bc73ac658..a011e5b422 100644 --- a/examples/1_advanced/8_zkp_revocation.rs +++ b/examples/1_advanced/8_zkp_revocation.rs @@ -1,8 +1,5 @@ - -use std::str::FromStr; use std::thread; use std::time::Duration; - use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; @@ -30,41 +27,28 @@ use identity_iota::credential::Subject; use identity_iota::credential::ValidityTimeframeGranularity; use identity_iota::did::CoreDID; use identity_iota::did::DID; -use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwpDocumentExt; use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::KeyIdStorage; use identity_iota::storage::KeyType; -use identity_iota::storage::Storage; -use identity_iota::verification::MethodRelationship; -use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; -use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::AliasOutputBuilder; -use iota_sdk::types::block::output::RentStructure; -use iota_sdk::types::block::output::TokenId; use jsonprooftoken::jpa::algs::ProofAlgorithm; -use jsonprooftoken::jwp::header::PresentationProtectedHeader; -use jsonprooftoken::jwp::presented::JwpPresentedBuilder; // The API endpoint of an IOTA node, e.g. Hornet. -const api_endpoint: &str = "http://localhost:14265"; +const API_ENDPOINT: &str = "http://localhost:14265"; // The faucet endpoint allows requesting funds for testing purposes. -const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; +const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; // const api_endpoint: &str = "https://api.testnet.shimmer.network"; @@ -75,7 +59,7 @@ const faucet_endpoint: &str = "http://localhost:8091/api/enqueue"; async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: ProofAlgorithm ) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, faucet_endpoint).await?; + let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; @@ -84,7 +68,6 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); - let fragment = document.generate_method_jwp( &storage, key_type, @@ -93,14 +76,6 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M MethodScope::VerificationMethod ).await?; - // issuer_document.generate_method( - // &storage, - // JwkMemStore::ED25519_KEY_TYPE, - // JwsAlgorithm::EdDSA, - // None, - // MethodScope::VerificationMethod - // ).await?; - // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; @@ -119,7 +94,7 @@ async fn main() -> anyhow::Result<()> { // Create a new client to interact with the IOTA ledger. let client: Client = Client::builder() - .with_primary_node(api_endpoint, None)? + .with_primary_node(API_ENDPOINT, None)? .finish() .await?; diff --git a/identity_credential/src/credential/revocation_timeframe_status.rs b/identity_credential/src/credential/revocation_timeframe_status.rs index 5d60fc46b5..31e2ec3b40 100644 --- a/identity_credential/src/credential/revocation_timeframe_status.rs +++ b/identity_credential/src/credential/revocation_timeframe_status.rs @@ -1,7 +1,5 @@ use std::str::FromStr; - use identity_core::common::{Object, Url, Timestamp}; -use identity_core::convert::{ToJson, FromJson}; use identity_did::DIDUrl; use identity_core::common::Value; use serde::Deserialize; @@ -143,10 +141,10 @@ impl TryFrom for RevocationTimeframeStatus { ))); }; - let revocation_timeframe: Timestamp = if let Value::String(timeframe) = revocation_timeframe { + if let Value::String(timeframe) = revocation_timeframe { Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( - "property '{}' is not a string", + "property '{}' is not a valid Timestamp", Self::TIMEFRAME_PROPERTY )))? @@ -158,9 +156,9 @@ impl TryFrom for RevocationTimeframeStatus { }; - let revocation_timeframe_epoch: &Value = - if let Some(revocation_timeframe_epoch) = status.properties.get(Self::GRANULARITY) { - revocation_timeframe_epoch + let revocation_timeframe_granularity: &Value = + if let Some(revocation_timeframe_granularity) = status.properties.get(Self::GRANULARITY) { + revocation_timeframe_granularity } else { return Err(Self::Error::InvalidStatus(format!( "missing required property '{}'", @@ -168,13 +166,12 @@ impl TryFrom for RevocationTimeframeStatus { ))); }; - let revocation_timeframe_epoch: ValidityTimeframeGranularity = if let Value::String(epoch) = revocation_timeframe_epoch { + if let Value::String(granularity) = revocation_timeframe_granularity { - ValidityTimeframeGranularity::from_str(&epoch).map_err(|_| Self::Error::InvalidStatus(format!( - "property '{}' is not a string", + ValidityTimeframeGranularity::from_str(&granularity).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a compatible ValidityTimeframeGranularity", Self::GRANULARITY )))? - } else { return Err(Self::Error::InvalidStatus(format!( "property '{}' is not a string", diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs index 4e07c5d58d..a692076f81 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -1,12 +1,9 @@ use identity_core::common::Timestamp; -use identity_core::common::Url; use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; -use crate::validator::SubjectHolderRelationship; - /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. #[non_exhaustive] #[derive(Debug, Default, Clone, Serialize, Deserialize)] diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs index f96aed3f21..44d0023f2e 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -1,7 +1,6 @@ use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; -use crate::validator::JptCredentialValidationOptions; /// Criteria for validating a [`Presentation`](crate::presentation::Presentation). #[derive(Debug, Default, Clone, Serialize, Deserialize)] diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index 0e72c46626..6275901ee4 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -5,7 +5,7 @@ use identity_did::{DIDUrl, CoreDID}; use identity_document::document::CoreDocument; use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, encoding::SerializationType, jwk::key::Jwk as JwkExt, jpt::claims::JptClaims}; -use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{CompoundCredentialValidationError, FailFast, JptCredentialValidationOptions, JptCredentialValidator, JptCredentialValidatorUtils, JwtCredentialValidatorUtils, JwtValidationError, SignerContext}}; +use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{CompoundCredentialValidationError, FailFast, JptCredentialValidatorUtils, JwtCredentialValidatorUtils, JwtValidationError, SignerContext}}; use super::{JptPresentationValidationOptions, DecodedJptPresentation}; diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 8ba515dfc9..dd5cf74350 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -199,11 +199,11 @@ impl JwkMemStore { /// The Ed25519 key type. pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ED25519_KEY_TYPE_STR); - const BLS12381SHA256_KEY_TYPE_STR: &str = "Bls12381Sha256"; + const BLS12381SHA256_KEY_TYPE_STR: &'static str = "Bls12381Sha256"; /// The BLS12381-SHA256 key type pub const BLS12381SHA256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHA256_KEY_TYPE_STR); - const BLS12381SHAKE256_KEY_TYPE_STR: &str = "Bls12381Shake256"; + const BLS12381SHAKE256_KEY_TYPE_STR: &'static str = "Bls12381Shake256"; /// The BLS12381-SHAKE256 key type pub const BLS12381SHAKE256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHAKE256_KEY_TYPE_STR); } From f27424311307e80b5edba679bfd251f7b03943f4 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 26 Jan 2024 11:30:38 +0100 Subject: [PATCH 024/163] remove ValidityTimeframeGranularity::SECOND --- examples/1_advanced/8_zkp_revocation.rs | 1 + .../credential/revocation_timeframe_status.rs | 18 +++++++----------- .../presentation/jwp_presentation_builder.rs | 9 +++------ .../jpt_credential_validator.rs | 3 ++- .../jpt_credential_validator_utils.rs | 1 - 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/examples/1_advanced/8_zkp_revocation.rs b/examples/1_advanced/8_zkp_revocation.rs index a011e5b422..0814757c75 100644 --- a/examples/1_advanced/8_zkp_revocation.rs +++ b/examples/1_advanced/8_zkp_revocation.rs @@ -68,6 +68,7 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); + // New Verification Method containing a BBS+ key let fragment = document.generate_method_jwp( &storage, key_type, diff --git a/identity_credential/src/credential/revocation_timeframe_status.rs b/identity_credential/src/credential/revocation_timeframe_status.rs index 31e2ec3b40..8ec85057eb 100644 --- a/identity_credential/src/credential/revocation_timeframe_status.rs +++ b/identity_credential/src/credential/revocation_timeframe_status.rs @@ -15,18 +15,19 @@ use super::Status; /// Validity Timeframe granularity #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ValidityTimeframeGranularity { - /// Seconds - SECOND, - /// Minutes + /// # Example: + /// `Timestamp::now_utc() = 2024-01-25T16:45:15Z` --> `validityTimeframe = 2024-01-25T16:45:00Z` \ + /// * It will be valid until `2024-01-25T16:46:00Z` MINUTE, - /// Hours + /// # Example: + /// `Timestamp::now_utc() = 2024-01-25T16:45:15Z` --> `validityTimeframe = 2024-01-25T16:00:00Z` \ + /// * It will be valid until `2024-01-25T17:00:00Z` HOUR } impl ToString for ValidityTimeframeGranularity { fn to_string(&self) -> String { match self { - ValidityTimeframeGranularity::SECOND => String::from("SECOND"), ValidityTimeframeGranularity::MINUTE => String::from("MINUTE"), ValidityTimeframeGranularity::HOUR => String::from("HOUR"), } @@ -38,7 +39,6 @@ impl FromStr for ValidityTimeframeGranularity { fn from_str(s: &str) -> Result { match s { - "SECOND" => Ok(ValidityTimeframeGranularity::SECOND), "MINUTE" => Ok(ValidityTimeframeGranularity::MINUTE), "HOUR" => Ok(ValidityTimeframeGranularity::HOUR), _ => Err("Invalid string representation for ValidityTimeframeEpoch"), @@ -59,13 +59,9 @@ impl RevocationTimeframeStatus { /// Creates a new `RevocationTimeframeStatus`. pub fn new(epoch: ValidityTimeframeGranularity) -> Self { - let did_url: DIDUrl = DIDUrl::parse("did:method:0xffff#revocation-1").unwrap(); let mut object = Object::new(); - - let validity_timeframe = match epoch { - ValidityTimeframeGranularity::SECOND => Timestamp::now_utc(), ValidityTimeframeGranularity::MINUTE => Timestamp::now_utc().to_minute(), ValidityTimeframeGranularity::HOUR => Timestamp::now_utc().to_hour(), }; @@ -73,7 +69,7 @@ impl RevocationTimeframeStatus { object.insert(Self::TIMEFRAME_PROPERTY.to_owned(), Value::String(validity_timeframe.to_rfc3339())); object.insert(Self::GRANULARITY.to_owned(), Value::String(epoch.to_string())); Self(Status::new_with_properties( - Url::from(did_url), // Here maybe i could put the id of the service of the issuer document containing the revocationbitmap if we choose to use it and add also an index here + Url::parse("http://example.com/").unwrap(), // Here maybe i could put the id of the service of the issuer document containing the revocationbitmap if we choose to use it and add also an index here // if we use a database though this field is useless Self::TYPE.to_owned(), object, diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 61e9bf149a..44706089b6 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -13,7 +13,7 @@ use crate::error::Result; /// - expirationDate MUST be blinded (if Timeslot Revocation mechanism is used) /// - credentialSubject (User have to choose which attribute must be blinded) /// - credentialSchema MUST NOT be blinded -/// - credentialStatus NO reason to use it in ZK VC (will be blinded in any case) +/// - credentialStatus MUST NOT be blinded /// - refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism) /// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) /// - evidence (User have to choose which attribute must be blinded) @@ -29,15 +29,12 @@ impl SelectiveDisclosurePresentation { jwp_builder.set_undisclosed("jti").ok(); // contains the credential's id, provides linkability - jwp_builder.set_undisclosed("nbf").ok(); // - jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not - + jwp_builder.set_undisclosed("nbf").ok(); + jwp_builder.set_undisclosed("expirationDate").ok(); // Depending on the revocation method used it will be necessary or not jwp_builder.set_undisclosed("exp").ok(); - jwp_builder.set_undisclosed("credentialStatus").ok(); // Provides linkability so, there is NO reason to use it in ZK VC - jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC, Self{jwp_builder} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs index 68d8543566..28df732b9a 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -13,7 +13,8 @@ use crate::{credential::{Jpt, Credential}, validator::{FailFast, JwtValidationEr use super::{DecodedJptCredential, JptCredentialValidatorUtils}; -/// A type for decoding and validating [`Credential`]s in JPT format. //TODO: ZKP - validator +//TODO: ZKP - validator +/// A type for decoding and validating [`Credential`]s in JPT format. #[non_exhaustive] pub struct JptCredentialValidator; diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index 4454d4e6ff..2ac9bb0918 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -112,7 +112,6 @@ impl JptCredentialValidatorUtils { let check = status.validity_timeframe().is_ok_and(|t| { status.granularity().is_ok_and(|e| {{ match e { - crate::credential::ValidityTimeframeGranularity::SECOND => now == t, crate::credential::ValidityTimeframeGranularity::MINUTE => now.to_minute() == t, crate::credential::ValidityTimeframeGranularity::HOUR => now.to_hour() == t, } From 9f3a5d95d2891538b67add20882e1a8fe32c0735 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 26 Jan 2024 13:51:07 +0100 Subject: [PATCH 025/163] rename examples and minor fix --- .../1_advanced/{8_zkp_revocation.rs => 10_zkp_revocation.rs} | 0 examples/1_advanced/{7_zkp.rs => 9_zkp.rs} | 0 .../jpt_credential_validation/decoded_jpt_credential.rs | 2 +- .../jpt_presentation_validation/decoded_jpt_presentation.rs | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename examples/1_advanced/{8_zkp_revocation.rs => 10_zkp_revocation.rs} (100%) rename examples/1_advanced/{7_zkp.rs => 9_zkp.rs} (100%) diff --git a/examples/1_advanced/8_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs similarity index 100% rename from examples/1_advanced/8_zkp_revocation.rs rename to examples/1_advanced/10_zkp_revocation.rs diff --git a/examples/1_advanced/7_zkp.rs b/examples/1_advanced/9_zkp.rs similarity index 100% rename from examples/1_advanced/7_zkp.rs rename to examples/1_advanced/9_zkp.rs diff --git a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs index 442f87f75c..10059a2de1 100644 --- a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs +++ b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs @@ -4,7 +4,7 @@ use jsonprooftoken::jwp::issued::JwpIssued; use crate::credential::Credential; /// Decoded [`Credential`] from a cryptographically verified JWP. -/// #[non_exhaustive] +#[non_exhaustive] #[derive(Debug, Clone)] pub struct DecodedJptCredential { /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). diff --git a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs index d3f8762856..c93064c793 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs @@ -4,7 +4,7 @@ use jsonprooftoken::jwp::presented::JwpPresented; use crate::credential::Credential; /// Decoded [`Credential`] from a cryptographically verified JWP. -/// #[non_exhaustive] +#[non_exhaustive] #[derive(Debug, Clone)] pub struct DecodedJptPresentation { /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). From a74f5aad09d31224a5adf4cbe473f8b66ba60c30 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 1 Feb 2024 16:53:07 +0100 Subject: [PATCH 026/163] add RevocationTimeframeStatus --- identity_core/src/common/timestamp.rs | 20 -- identity_credential/src/credential/mod.rs | 5 +- .../credential/revocation_bitmap_status.rs | 2 +- .../credential/revocation_timeframe_status.rs | 187 ----------- .../presentation/jwp_presentation_builder.rs | 4 +- identity_credential/src/revocation/mod.rs | 3 + .../revocation/validity_timeframe_2024/mod.rs | 5 + .../revocation_timeframe_status.rs | 315 ++++++++++++++++++ 8 files changed, 329 insertions(+), 212 deletions(-) delete mode 100644 identity_credential/src/credential/revocation_timeframe_status.rs create mode 100644 identity_credential/src/revocation/validity_timeframe_2024/mod.rs create mode 100644 identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs diff --git a/identity_core/src/common/timestamp.rs b/identity_core/src/common/timestamp.rs index aa414c6952..73df11e949 100644 --- a/identity_core/src/common/timestamp.rs +++ b/identity_core/src/common/timestamp.rs @@ -113,16 +113,6 @@ impl Timestamp { .and_then(|offset_date_time| Self::from_unix(offset_date_time.unix_timestamp()).ok()) } - //TODO: ZKP - validity_timeframe Timestamp - /// Truncate to minutes - pub fn to_minute(&self) -> Self { - Self(truncate_fractional_minutes(self.0)) - } - - /// Truncate to hours - pub fn to_hour(&self) -> Self { - Self(truncate_fractional_hours(self.0)) - } } impl Default for Timestamp { @@ -192,16 +182,6 @@ fn truncate_fractional_seconds(offset_date_time: OffsetDateTime) -> OffsetDateTi offset_date_time - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) } -/// Truncates an `OffsetDateTime` to the minute. -fn truncate_fractional_minutes(offset_date_time: OffsetDateTime) -> OffsetDateTime { - offset_date_time - time::Duration::seconds(offset_date_time.second() as i64) - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) -} - -/// Truncates an `OffsetDateTime` to the hour. -fn truncate_fractional_hours(offset_date_time: OffsetDateTime) -> OffsetDateTime { - offset_date_time - time::Duration::minutes(offset_date_time.minute() as i64) - time::Duration::seconds(offset_date_time.second() as i64) - time::Duration::nanoseconds(offset_date_time.nanosecond() as i64) -} - /// A span of time. /// /// This type is typically used to increment or decrement a [`Timestamp`]. diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 2af7369ff6..67fa5eb1d3 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -23,7 +23,7 @@ mod schema; mod status; mod subject; mod jwp_credential_options; -mod revocation_timeframe_status; + pub use self::builder::CredentialBuilder; pub use self::credential::Credential; @@ -37,12 +37,11 @@ pub use self::policy::Policy; pub use self::proof::Proof; pub use self::refresh::RefreshService; #[cfg(feature = "revocation-bitmap")] -pub use self::revocation_bitmap_status::RevocationBitmapStatus; +pub use self::revocation_bitmap_status::{RevocationBitmapStatus, try_index_to_u32}; pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; pub use self::jwp_credential_options::JwpCredentialOptions; -pub use self::revocation_timeframe_status::{RevocationTimeframeStatus, ValidityTimeframeGranularity}; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::CredentialJwtClaims; diff --git a/identity_credential/src/credential/revocation_bitmap_status.rs b/identity_credential/src/credential/revocation_bitmap_status.rs index d4310d154a..b607e1758d 100644 --- a/identity_credential/src/credential/revocation_bitmap_status.rs +++ b/identity_credential/src/credential/revocation_bitmap_status.rs @@ -129,7 +129,7 @@ impl From for Status { } /// Attempts to convert the given index string to a u32. -fn try_index_to_u32(index: &str, name: &str) -> Result { +pub fn try_index_to_u32(index: &str, name: &str) -> Result { u32::from_str(index).map_err(|err| { Error::InvalidStatus(format!( "{name} cannot be converted to an unsigned, 32-bit integer: {err}", diff --git a/identity_credential/src/credential/revocation_timeframe_status.rs b/identity_credential/src/credential/revocation_timeframe_status.rs deleted file mode 100644 index 8ec85057eb..0000000000 --- a/identity_credential/src/credential/revocation_timeframe_status.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::str::FromStr; -use identity_core::common::{Object, Url, Timestamp}; -use identity_did::DIDUrl; -use identity_core::common::Value; -use serde::Deserialize; -use serde::Serialize; -use crate::error::Result; -use crate::error::Error; -use super::Status; - - -//TODO: ZKP - RevocationTimeframeStatus and ValidityTimeframeEpoch - - -/// Validity Timeframe granularity -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ValidityTimeframeGranularity { - /// # Example: - /// `Timestamp::now_utc() = 2024-01-25T16:45:15Z` --> `validityTimeframe = 2024-01-25T16:45:00Z` \ - /// * It will be valid until `2024-01-25T16:46:00Z` - MINUTE, - /// # Example: - /// `Timestamp::now_utc() = 2024-01-25T16:45:15Z` --> `validityTimeframe = 2024-01-25T16:00:00Z` \ - /// * It will be valid until `2024-01-25T17:00:00Z` - HOUR -} - -impl ToString for ValidityTimeframeGranularity { - fn to_string(&self) -> String { - match self { - ValidityTimeframeGranularity::MINUTE => String::from("MINUTE"), - ValidityTimeframeGranularity::HOUR => String::from("HOUR"), - } - } -} - -impl FromStr for ValidityTimeframeGranularity { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "MINUTE" => Ok(ValidityTimeframeGranularity::MINUTE), - "HOUR" => Ok(ValidityTimeframeGranularity::HOUR), - _ => Err("Invalid string representation for ValidityTimeframeEpoch"), - } - } -} - -/// Information used to determine the current status of a [`Credential`][crate::credential::Credential] -/// using the `RevocationBitmap2022` specification. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RevocationTimeframeStatus(Status); - -impl RevocationTimeframeStatus { - const TIMEFRAME_PROPERTY: &'static str = "validityTimeFrame"; - const GRANULARITY: &'static str = "granularity"; - /// Type name of the revocation bitmap. - pub const TYPE: &'static str = "RevocationTimeframe2024"; - - /// Creates a new `RevocationTimeframeStatus`. - pub fn new(epoch: ValidityTimeframeGranularity) -> Self { - let mut object = Object::new(); - - let validity_timeframe = match epoch { - ValidityTimeframeGranularity::MINUTE => Timestamp::now_utc().to_minute(), - ValidityTimeframeGranularity::HOUR => Timestamp::now_utc().to_hour(), - }; - - object.insert(Self::TIMEFRAME_PROPERTY.to_owned(), Value::String(validity_timeframe.to_rfc3339())); - object.insert(Self::GRANULARITY.to_owned(), Value::String(epoch.to_string())); - Self(Status::new_with_properties( - Url::parse("http://example.com/").unwrap(), // Here maybe i could put the id of the service of the issuer document containing the revocationbitmap if we choose to use it and add also an index here - // if we use a database though this field is useless - Self::TYPE.to_owned(), - object, - )) - - } - - - /// Get validityTimeframe value - pub fn validity_timeframe(&self) -> Result { - if let Some(Value::String(timeframe)) = self.0.properties.get(Self::TIMEFRAME_PROPERTY) { - - Timestamp::from_str(&timeframe).map_err(|_| Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::TIMEFRAME_PROPERTY - ))) - - } else { - return Err(Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::TIMEFRAME_PROPERTY - ))); - } - } - - /// Get granularity value - pub fn granularity(&self) -> Result { - if let Some(Value::String(epoch)) = self.0.properties.get(Self::GRANULARITY) { - - ValidityTimeframeGranularity::from_str(&epoch).map_err(|_| Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::GRANULARITY - ))) - - } else { - return Err(Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::GRANULARITY - ))); - } - } - -} - - -impl TryFrom for RevocationTimeframeStatus { - type Error = Error; - - fn try_from(status: Status) -> Result { - if status.type_ != Self::TYPE { - return Err(Self::Error::InvalidStatus(format!( - "expected type '{}', got '{}'", - Self::TYPE, - status.type_ - ))); - } - - let revocation_timeframe: &Value = - if let Some(revocation_timeframe) = status.properties.get(Self::TIMEFRAME_PROPERTY) { - revocation_timeframe - } else { - return Err(Self::Error::InvalidStatus(format!( - "missing required property '{}'", - Self::TIMEFRAME_PROPERTY - ))); - }; - - if let Value::String(timeframe) = revocation_timeframe { - - Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( - "property '{}' is not a valid Timestamp", - Self::TIMEFRAME_PROPERTY - )))? - - } else { - return Err(Self::Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::TIMEFRAME_PROPERTY - ))); - }; - - - let revocation_timeframe_granularity: &Value = - if let Some(revocation_timeframe_granularity) = status.properties.get(Self::GRANULARITY) { - revocation_timeframe_granularity - } else { - return Err(Self::Error::InvalidStatus(format!( - "missing required property '{}'", - Self::GRANULARITY - ))); - }; - - if let Value::String(granularity) = revocation_timeframe_granularity { - - ValidityTimeframeGranularity::from_str(&granularity).map_err(|_| Self::Error::InvalidStatus(format!( - "property '{}' is not a compatible ValidityTimeframeGranularity", - Self::GRANULARITY - )))? - } else { - return Err(Self::Error::InvalidStatus(format!( - "property '{}' is not a string", - Self::GRANULARITY - ))); - }; - - Ok(Self(status)) - } -} - - -impl From for Status { - fn from(status: RevocationTimeframeStatus) -> Self { - status.0 - } -} \ No newline at end of file diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs index 44706089b6..c072b067ba 100644 --- a/identity_credential/src/presentation/jwp_presentation_builder.rs +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -35,7 +35,9 @@ impl SelectiveDisclosurePresentation { jwp_builder.set_undisclosed("expirationDate").ok(); // Depending on the revocation method used it will be necessary or not jwp_builder.set_undisclosed("exp").ok(); - jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC, + jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC + + jwp_builder.set_undisclosed("vc.credentialStatus.revocationBitmapIndex").ok(); Self{jwp_builder} } diff --git a/identity_credential/src/revocation/mod.rs b/identity_credential/src/revocation/mod.rs index 6732ff4194..f98ad3f89b 100644 --- a/identity_credential/src/revocation/mod.rs +++ b/identity_credential/src/revocation/mod.rs @@ -9,6 +9,9 @@ mod revocation_bitmap_2022; #[cfg(feature = "status-list-2021")] pub mod status_list_2021; +pub mod validity_timeframe_2024; + pub use self::error::RevocationError; pub use self::error::RevocationResult; pub use revocation_bitmap_2022::*; +pub use validity_timeframe_2024::*; diff --git a/identity_credential/src/revocation/validity_timeframe_2024/mod.rs b/identity_credential/src/revocation/validity_timeframe_2024/mod.rs new file mode 100644 index 0000000000..bc9a69903c --- /dev/null +++ b/identity_credential/src/revocation/validity_timeframe_2024/mod.rs @@ -0,0 +1,5 @@ +//! Implementation of a new Revocation mechanism for ZK Verifiable Credentials. + +mod revocation_timeframe_status; + +pub use revocation_timeframe_status::*; \ No newline at end of file diff --git a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs new file mode 100644 index 0000000000..c7dbf8f350 --- /dev/null +++ b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs @@ -0,0 +1,315 @@ +use std::str::FromStr; +use identity_core::common::Duration; +use identity_core::common::{Object, Url, Timestamp}; +use identity_did::DIDUrl; +use identity_core::common::Value; +use serde::Deserialize; +use serde::Serialize; +use crate::credential::{try_index_to_u32, Status}; +use crate::error::Result; +use crate::error::Error; + + +//TODO: ZKP - RevocationTimeframeStatus + + +/// Information used to determine the current status of a [`Credential`][crate::credential::Credential] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RevocationTimeframeStatus(Status); + +impl RevocationTimeframeStatus { + pub const START_TIMEFRAME_PROPERTY: &'static str = "startValidityTimeframe"; + pub const END_TIMEFRAME_PROPERTY: &'static str = "endValidityTimeframe"; + const INDEX_PROPERTY: &'static str = "revocationBitmapIndex"; + + /// Type name of the revocation mechanism. + pub const TYPE: &'static str = "RevocationTimeframe2024"; + + /// Creates a new `RevocationTimeframeStatus`. + pub fn new(granularity: Duration, id: DIDUrl, index: u32) -> Result { + + let mut object = Object::new(); + + let start_validity_timeframe = Timestamp::now_utc(); + + let end_validity_timeframe = start_validity_timeframe.checked_add(granularity) + .ok_or(Error::InvalidStatus( + "With that granularity, endValidityTimeFrame will turn out not to be in the valid range for RFC 3339".to_owned() + ))?; + + // id.set_query(Some(&format!("index={index}"))) + // .expect("the string should be non-empty and a valid URL query"); + + object.insert(Self::START_TIMEFRAME_PROPERTY.to_owned(), Value::String(start_validity_timeframe.to_rfc3339())); + object.insert(Self::END_TIMEFRAME_PROPERTY.to_owned(), Value::String(end_validity_timeframe.to_rfc3339())); + object.insert(Self::INDEX_PROPERTY.to_owned(), Value::String(index.to_string())); + + Ok(Self(Status::new_with_properties( + Url::from(id), + Self::TYPE.to_owned(), + object, + ))) + + } + + + /// Get startValidityTimeframe value + pub fn start_validity_timeframe(&self) -> Result { + if let Some(Value::String(timeframe)) = self.0.properties.get(Self::START_TIMEFRAME_PROPERTY) { + + Timestamp::from_str(&timeframe).map_err(|_| Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::START_TIMEFRAME_PROPERTY + ))) + + } else { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::START_TIMEFRAME_PROPERTY + ))); + } + } + + /// Get endValidityTimeframe value + pub fn end_validity_timeframe(&self) -> Result { + if let Some(Value::String(timeframe)) = self.0.properties.get(Self::END_TIMEFRAME_PROPERTY) { + + Timestamp::from_str(&timeframe).map_err(|_| Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::END_TIMEFRAME_PROPERTY + ))) + + } else { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::END_TIMEFRAME_PROPERTY + ))); + } + } + + /// Returns the [`DIDUrl`] of the `RevocationBitmapStatus`, which should resolve + /// to a `RevocationBitmap2022` service in a DID Document. + pub fn id(&self) -> Result { + DIDUrl::parse(self.0.id.as_str()) + .map_err(|err| Error::InvalidStatus(format!("invalid DID Url '{}': {:?}", self.0.id, err))) + } + + + /// Returns the index of the credential in the issuer's revocation bitmap if it can be decoded. + pub fn index(&self) -> Result { + if let Some(Value::String(index)) = self.0.properties.get(Self::INDEX_PROPERTY) { + try_index_to_u32(index, Self::INDEX_PROPERTY) + } else { + Err(Error::InvalidStatus(format!( + "expected {} to be an unsigned 32-bit integer expressed as a string", + Self::INDEX_PROPERTY + ))) + } + } + +} + + +impl TryFrom for RevocationTimeframeStatus { + type Error = Error; + + fn try_from(status: Status) -> Result { + if status.type_ != Self::TYPE { + return Err(Self::Error::InvalidStatus(format!( + "expected type '{}', got '{}'", + Self::TYPE, + status.type_ + ))); + } + + let start_revocation_timeframe: &Value = + if let Some(start_revocation_timeframe) = status.properties.get(Self::START_TIMEFRAME_PROPERTY) { + start_revocation_timeframe + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + Self::START_TIMEFRAME_PROPERTY + ))); + }; + + if let Value::String(timeframe) = start_revocation_timeframe { + + Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a valid Timestamp", + Self::START_TIMEFRAME_PROPERTY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::START_TIMEFRAME_PROPERTY + ))); + }; + + + let end_revocation_timeframe: &Value = + if let Some(end_revocation_timeframe) = status.properties.get(Self::END_TIMEFRAME_PROPERTY) { + end_revocation_timeframe + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + Self::END_TIMEFRAME_PROPERTY + ))); + }; + + if let Value::String(timeframe) = end_revocation_timeframe { + + Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a valid Timestamp", + Self::END_TIMEFRAME_PROPERTY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::END_TIMEFRAME_PROPERTY + ))); + }; + + + let revocation_bitmap_index: &Value = + if let Some(revocation_bitmap_index) = status.properties.get(Self::INDEX_PROPERTY) { + revocation_bitmap_index + } else { + return Err(Error::InvalidStatus(format!( + "missing required property '{}'", + Self::INDEX_PROPERTY + ))); + }; + + + if let Value::String(index) = revocation_bitmap_index { + try_index_to_u32(index, Self::INDEX_PROPERTY)? + } else { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a string", + Self::INDEX_PROPERTY + ))); + }; + + Ok(Self(status)) + } +} + + +impl From for Status { + fn from(status: RevocationTimeframeStatus) -> Self { + status.0 + } +} + + + + +/// Verifier +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifierRevocationTimeframeStatus(pub(crate) RevocationTimeframeStatus); + +impl VerifierRevocationTimeframeStatus { + /// Get startValidityTimeframe value + pub fn start_validity_timeframe(&self) -> Result { + self.0.start_validity_timeframe() + } + + /// Get endValidityTimeframe value + pub fn end_validity_timeframe(&self) -> Result { + self.0.end_validity_timeframe() + } +} + + +impl TryFrom for VerifierRevocationTimeframeStatus { + type Error = Error; + + fn try_from(status: Status) -> Result { + if status.type_ != RevocationTimeframeStatus::TYPE { + return Err(Self::Error::InvalidStatus(format!( + "expected type '{}', got '{}'", + RevocationTimeframeStatus::TYPE, + status.type_ + ))); + } + + let start_revocation_timeframe: &Value = + if let Some(start_revocation_timeframe) = status.properties.get(RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY) { + start_revocation_timeframe + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY + ))); + }; + + if let Value::String(timeframe) = start_revocation_timeframe { + + Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a valid Timestamp", + RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY + ))); + }; + + + let end_revocation_timeframe: &Value = + if let Some(end_revocation_timeframe) = status.properties.get(RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY) { + end_revocation_timeframe + } else { + return Err(Self::Error::InvalidStatus(format!( + "missing required property '{}'", + RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY + ))); + }; + + if let Value::String(timeframe) = end_revocation_timeframe { + + Timestamp::from_str(&timeframe).map_err(|_| Self::Error::InvalidStatus(format!( + "property '{}' is not a valid Timestamp", + RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY + )))? + + } else { + return Err(Self::Error::InvalidStatus(format!( + "property '{}' is not a string", + RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY + ))); + }; + + + let revocation_bitmap_index: &Value = + if let Some(revocation_bitmap_index) = status.properties.get(RevocationTimeframeStatus::INDEX_PROPERTY) { + revocation_bitmap_index + } else { + return Err(Error::InvalidStatus(format!( + "missing required property '{}'", + RevocationTimeframeStatus::INDEX_PROPERTY + ))); + }; + + + if &Value::Null != revocation_bitmap_index { + return Err(Error::InvalidStatus(format!( + "property '{}' is not a Null", + RevocationTimeframeStatus::INDEX_PROPERTY + ))); + }; + + Ok(Self(RevocationTimeframeStatus(status))) + } +} + + +impl From for Status { + fn from(status: VerifierRevocationTimeframeStatus) -> Self { + status.0.0 + } +} \ No newline at end of file From 23938a708cdfac521539f766272ad4f39e94d0cc Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 1 Feb 2024 16:55:00 +0100 Subject: [PATCH 027/163] revocation in credential and presentation validation --- .../jpt_credential_validation_options.rs | 2 + .../jpt_credential_validator_utils.rs | 85 +++++++++++++++---- .../jpt_presentation_validator.rs | 4 +- .../jpt_presentation_validator_utils.rs | 39 ++++++++- 4 files changed, 111 insertions(+), 19 deletions(-) diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs index a692076f81..7942739c2c 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -4,6 +4,8 @@ use identity_document::verifiable::JwpVerificationOptions; use serde::Deserialize; use serde::Serialize; +use crate::credential::Status; + /// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. #[non_exhaustive] #[derive(Debug, Default, Clone, Serialize, Deserialize)] diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index 2ac9bb0918..08903775c2 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -1,5 +1,5 @@ use std::str::FromStr; -use crate::credential::Credential; +use crate::{credential::Credential, revocation::{RevocationDocumentExt, RevocationTimeframeStatus, VerifierRevocationTimeframeStatus}}; use identity_core::{convert::{ToJson, FromJson}, common::Object}; use identity_did::DID; @@ -69,8 +69,6 @@ impl JptCredentialValidatorUtils { status_check: crate::validator::StatusCheck, ) -> ValidationUnitResult { - use crate::credential::RevocationTimeframeStatus; - if status_check == crate::validator::StatusCheck::SkipAll { return Ok(()); @@ -80,11 +78,10 @@ impl JptCredentialValidatorUtils { None => Ok(()), Some(status) => { if status.type_ == RevocationTimeframeStatus::TYPE { - let status: crate::credential::RevocationTimeframeStatus = - crate::credential::RevocationTimeframeStatus::try_from(status.clone()) + let status: RevocationTimeframeStatus = RevocationTimeframeStatus::try_from(status.clone()) .map_err(JwtValidationError::InvalidStatus)?; - Self::check_revocation_validity_timeframe_status(status) + Self::check_validity_timeframe_status(status) } else { if status_check == crate::validator::StatusCheck::SkipUnsupported { @@ -101,21 +98,18 @@ impl JptCredentialValidatorUtils { } - fn check_revocation_validity_timeframe_status( - status: crate::credential::RevocationTimeframeStatus, + pub(crate) fn check_validity_timeframe_status( + status: RevocationTimeframeStatus, ) -> ValidationUnitResult { use identity_core::common::Timestamp; - let now = Timestamp::now_utc(); - let check = status.validity_timeframe().is_ok_and(|t| { - status.granularity().is_ok_and(|e| {{ - match e { - crate::credential::ValidityTimeframeGranularity::MINUTE => now.to_minute() == t, - crate::credential::ValidityTimeframeGranularity::HOUR => now.to_hour() == t, - } - }}) + let check = status.start_validity_timeframe().is_ok_and(|start| { + + status.end_validity_timeframe().is_ok_and(|end| { + now >= start && now <= end + }) }); if !check { @@ -125,4 +119,63 @@ impl JptCredentialValidatorUtils { } } + + /// Checks whether the credential status has been revoked + /// + /// Only supports `RevocationTimeframe2024`. + pub fn check_revocation_with_validity_timeframe_2024 + ?Sized, T>( + credential: &Credential, + issuer: &DOC, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: RevocationTimeframeStatus = RevocationTimeframeStatus::try_from(status.clone()) + .map_err(JwtValidationError::InvalidStatus)?; + + Self::check_revocation_bitmap(issuer, status) + + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + return Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))); + } + + } + } + + } + + + /// Check the given `status` against the matching [`RevocationBitmap`] service in the issuer's DID Document. + fn check_revocation_bitmap + ?Sized>( + issuer: &DOC, + status: RevocationTimeframeStatus, + ) -> ValidationUnitResult { + + let issuer_service_url: identity_did::DIDUrl = status.id().map_err(JwtValidationError::InvalidStatus)?; + + // Check whether index is revoked. + let revocation_bitmap: crate::revocation::RevocationBitmap = issuer + .as_ref() + .resolve_revocation_bitmap(issuer_service_url.into()) + .map_err(|_| JwtValidationError::ServiceLookupError)?; + let index: u32 = status.index().map_err(JwtValidationError::InvalidStatus)?; + if revocation_bitmap.is_revoked(index) { + Err(JwtValidationError::Revoked) + } else { + Ok(()) + } + } } \ No newline at end of file diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs index 6275901ee4..266fa7e638 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -7,7 +7,7 @@ use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, encoding::Serializatio use crate::{credential::{Jpt, CredentialJwtClaims, Credential}, validator::{CompoundCredentialValidationError, FailFast, JptCredentialValidatorUtils, JwtCredentialValidatorUtils, JwtValidationError, SignerContext}}; -use super::{JptPresentationValidationOptions, DecodedJptPresentation}; +use super::{DecodedJptPresentation, JptPresentationValidationOptions, JptPresentationValidatorUtils}; /// A type for decoding and validating Presented [`Credential`]s in JPT format. //TODO: ZKP - validator #[non_exhaustive] @@ -72,7 +72,7 @@ impl JptPresentationValidator { let validation_units_iter = { let revocation_validation = - std::iter::once_with(|| JptCredentialValidatorUtils::check_status(credential, options.status)); + std::iter::once_with(|| JptPresentationValidatorUtils::check_status(credential, options.status)); validation_units_iter.chain(revocation_validation) }; diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs index 74c2e57203..1c309864d2 100644 --- a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs @@ -4,13 +4,15 @@ use identity_core::{convert::{ToJson, FromJson}, common::Object}; use identity_did::DID; use jsonprooftoken::{jwp::presented::JwpPresentedDecoder, jpt::claims::JptClaims, encoding::SerializationType}; -use crate::{validator::{JwtValidationError, SignerContext}, credential::{Jpt, CredentialJwtClaims}}; +use crate::{credential::{Credential, CredentialJwtClaims, Jpt}, revocation::{RevocationTimeframeStatus, VerifierRevocationTimeframeStatus}, validator::{JptCredentialValidatorUtils, JwtValidationError, SignerContext}}; /// Utility functions for verifying JPT credentials. #[derive(Debug)] #[non_exhaustive] pub struct JptPresentationValidatorUtils; +type ValidationUnitResult = std::result::Result; + impl JptPresentationValidatorUtils { /// Utility for extracting the issuer field of a credential in JPT representation as DID. @@ -42,4 +44,39 @@ impl JptPresentationValidatorUtils { source: err.into(), }) } + + + /// Checks whether the credential status has been revoked. + pub fn check_status( + credential: &Credential, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + + + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: VerifierRevocationTimeframeStatus = VerifierRevocationTimeframeStatus::try_from(status.clone()) + .map_err(JwtValidationError::InvalidStatus)?; + + JptCredentialValidatorUtils::check_validity_timeframe_status(status.0) + + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + return Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))); + } + + } + } + } } \ No newline at end of file From 071e6b8ffee6c1133403b012ce8b2fb2dd8b402b Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 1 Feb 2024 16:55:52 +0100 Subject: [PATCH 028/163] add TimeframeRevocationExtension for Core/IotaDocument --- Cargo.toml | 3 +- identity_credential/Cargo.toml | 2 + identity_storage/Cargo.toml | 1 + .../src/key_storage/jwk_storage.rs | 5 + identity_storage/src/key_storage/memstore.rs | 119 ++++++++++++++ identity_storage/src/storage/error.rs | 3 + identity_storage/src/storage/mod.rs | 2 + .../src/storage/timeframe_revocation_ext.rs | 148 ++++++++++++++++++ 8 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 identity_storage/src/storage/timeframe_revocation_ext.rs diff --git a/Cargo.toml b/Cargo.toml index c9cd363632..25ee25b0a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = {version = "0.3.2"} +json-proof-token = {git = "https://github.com/Cybersecurity-LINKS/json-proof-token.git"} +zkryptium = { git = "https://github.com/Cybersecurity-LINKS/zkryptium.git", default-features = false, features = ["bbsplus"] } [workspace.package] authors = ["IOTA Stiftung"] diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 448b8bc22d..580ad6a99a 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -33,6 +33,8 @@ strum.workspace = true thiserror.workspace = true url = { version = "2.5", default-features = false } json-proof-token.workspace = true +zkryptium.workspace = true +async-trait = { version = "0.1.64", default-features = false } [dev-dependencies] anyhow = "1.0.62" diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 6b0589aecd..fa6c458ff6 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -28,6 +28,7 @@ serde_json.workspace = true thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } json-proof-token.workspace = true +zkryptium.workspace = true [dev-dependencies] diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 156fe6b26a..e033e67720 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -5,11 +5,13 @@ use crate::key_storage::KeyId; use crate::key_storage::KeyStorageError; use crate::key_storage::KeyType; use async_trait::async_trait; +use identity_core::common::Timestamp; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use zkryptium::bbsplus::signature::BBSplusSignature; use super::jwk_gen_output::JwkGenOutput; @@ -78,4 +80,7 @@ pub trait JwkStorageExt : JwkStorage { /// Generate the JPT representing a JWP in the Issuer form async fn generate_issuer_proof(&self, key_id: &KeyId, header: IssuerProtectedHeader, claims: JptClaims, public_key: &Jwk) -> KeyStorageResult; + + /// Update proof functionality for timeframe revocation mechanism + async fn update_proof(&self, key_id: &KeyId, public_key: &Jwk, proof: &[u8; 112], old_start_validity_timeframe: String, new_start_validity_timeframe: String, old_end_validity_timeframe: String, new_end_validity_timeframe: String, index_start_validity_timeframe: usize, index_end_validity_timeframe: usize, n_messages: usize ) -> KeyStorageResult<[u8; 112]>; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index dd5cf74350..7a97329b4e 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -8,10 +8,12 @@ use std::str::FromStr; use async_trait::async_trait; use crypto::signatures::ed25519::SecretKey; +use identity_core::common::Timestamp; use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jwu; use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; @@ -24,6 +26,12 @@ use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; use tokio::sync::RwLockWriteGuard; +use zkryptium::bbsplus::keys::BBSplusPublicKey; +use zkryptium::bbsplus::keys::BBSplusSecretKey; +use zkryptium::schemes::algorithms::BBS_BLS12381_SHA256; +use zkryptium::schemes::algorithms::BBS_BLS12381_SHAKE256; +use zkryptium::schemes::generics::Signature; +use zkryptium::utils::message::BBSplusMessage; use super::ed25519::encode_jwk; use super::ed25519::expand_secret_jwk; @@ -362,6 +370,117 @@ impl JwkStorageExt for JwkMemStore { Ok(jwp) } + + + async fn update_proof(&self, key_id: &KeyId, public_key: &Jwk, proof: &[u8; 112], old_start_validity_timeframe: String, new_start_validity_timeframe: String, old_end_validity_timeframe: String, new_end_validity_timeframe: String, index_start_validity_timeframe: usize, index_end_validity_timeframe: usize, n_messages: usize ) -> KeyStorageResult<[u8; 112]> + { + let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; + + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + match alg { + ProofAlgorithm::BLS12381_SHA256 | ProofAlgorithm::BLS12381_SHAKE256 => { + let okp_params = public_key.try_okp_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with Okp params in order to sign with {alg}")) + .with_source(err) + })?; + if okp_params.crv != EllipticCurveTypes::Bls12381G2.to_string() { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message(format!( + "expected Jwk with Okp {} crv in order to generate the proof with {alg}", + EllipticCurveTypes::Bls12381G2 + )), + ); + } + } + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + } + + // Obtain the corresponding private key and sign `data`. + let jwk: &Jwk = jwk_store + .get(key_id) + .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; + + let params = jwk.try_okp_params().map_err(|_| KeyStorageErrorKind::Unspecified)?; + + let pk = BBSplusPublicKey::from_bytes(&jwu::decode_b64(¶ms.x) + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("unable to decode `d` param") + .with_source(err) + })?); + + let sk = BBSplusSecretKey::from_bytes(¶ms + .d + .as_deref() + .map(jwu::decode_b64) + .ok_or_else(|| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("expected Jwk `d` param to be present") + })? + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("unable to decode `d` param") + .with_source(err) + })?); + + let new_proof = match alg { + ProofAlgorithm::BLS12381_SHA256 => { + let vec_old_start_validity_timeframe = serde_json::to_vec(&old_start_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let old_start_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_old_start_validity_timeframe, None); + + let vec_new_start_validity_timeframe = serde_json::to_vec(&new_start_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let new_start_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_new_start_validity_timeframe, None); + + let vec_old_end_validity_timeframe = serde_json::to_vec(&old_end_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let old_end_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_old_end_validity_timeframe, None); + + let vec_new_end_validity_timeframe = serde_json::to_vec(&new_end_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let new_end_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_new_end_validity_timeframe, None); + + let proof = Signature::::from_bytes(proof).update_signature(&sk, &pk, n_messages, &old_start_validity_timeframe, &new_start_validity_timeframe, index_start_validity_timeframe); + let proof = proof.update_signature(&sk, &pk, n_messages, &old_end_validity_timeframe, &new_end_validity_timeframe, index_end_validity_timeframe).to_bytes(); + proof + }, + ProofAlgorithm::BLS12381_SHAKE256 => { + let vec_old_start_validity_timeframe = serde_json::to_vec(&old_start_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let old_start_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_old_start_validity_timeframe, None); + + let vec_new_start_validity_timeframe = serde_json::to_vec(&new_start_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let new_start_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_new_start_validity_timeframe, None); + + let vec_old_end_validity_timeframe = serde_json::to_vec(&old_end_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let old_end_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_old_end_validity_timeframe, None); + + let vec_new_end_validity_timeframe = serde_json::to_vec(&new_end_validity_timeframe).map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + let new_end_validity_timeframe = BBSplusMessage::map_message_to_scalar_as_hash::(&vec_new_end_validity_timeframe, None); + + let proof = Signature::::from_bytes(proof).update_signature(&sk, &pk, n_messages, &old_start_validity_timeframe, &new_start_validity_timeframe, index_start_validity_timeframe); + let proof = proof.update_signature(&sk, &pk, n_messages, &old_end_validity_timeframe, &new_end_validity_timeframe, index_end_validity_timeframe).to_bytes(); + proof + }, + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }; + + Ok(new_proof) + // proof.update_signature(&self, sk: &BBSplusSecretKey, generators: &Generators, old_message: &BBSplusMessage, new_message: &BBSplusMessage, update_index: usize) -> Self { + } } diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index ff5c9c6e89..5541180bf8 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -34,6 +34,9 @@ pub enum JwkStorageDocumentError { /// Cannot cunstruct a valid Jwp (issued or presented form) #[error("Not able to construct a valid Jwp")] JwpBuildingError, + /// Credential's proof update internal error + #[error("Credential's proof internal error")] + ProofUpdateError(String), /// Caused by a failure to construct a verification method. #[error("method generation failed: unable to create a valid verification method")] diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index d7c8ffe830..8e552aee69 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -8,6 +8,7 @@ mod error; mod jwk_document_ext; mod jwp_document_ext; mod signature_options; +mod timeframe_revocation_ext; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -17,6 +18,7 @@ pub use error::*; pub use jwk_document_ext::*; pub use jwp_document_ext::*; pub use signature_options::*; +pub use timeframe_revocation_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from diff --git a/identity_storage/src/storage/timeframe_revocation_ext.rs b/identity_storage/src/storage/timeframe_revocation_ext.rs new file mode 100644 index 0000000000..8e4f2609be --- /dev/null +++ b/identity_storage/src/storage/timeframe_revocation_ext.rs @@ -0,0 +1,148 @@ +use crate::{JwkStorageExt, KeyIdStorage, MethodDigest, Storage, StorageResult}; +use super::JwkStorageDocumentError as Error; +use async_trait::async_trait; +use identity_core::common::{Duration, Timestamp}; +use identity_credential::{credential::Jpt, revocation::{RevocationError, RevocationTimeframeStatus}}; +use identity_document::document::CoreDocument; +use identity_verification::{MethodData, VerificationMethod}; +use jsonprooftoken::{encoding::SerializationType, jpa::algs::ProofAlgorithm, jpt::{claims::JptClaims, payloads::{self, Payloads}}, jwp::issued::JwpIssued}; +use serde_json::Value; +use zkryptium::bbsplus::signature::BBSplusSignature; + + +//TODO: TimeframeRevocationExtension + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait TimeframeRevocationExtension { + + async fn update( + &self, + storage: &Storage, + fragment: &str, + granularity: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage; +} + + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl TimeframeRevocationExtension for CoreDocument { + + async fn update( + &self, + storage: &Storage, + fragment: &str, + granularity: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + + + let new_start_validity_timeframe = Timestamp::now_utc(); + let new_end_validity_timeframe = new_start_validity_timeframe.checked_add(granularity).ok_or(Error::ProofUpdateError("Invalid granularity".to_owned()))?; + let new_start_validity_timeframe = new_start_validity_timeframe.to_rfc3339(); + let new_end_validity_timeframe = new_end_validity_timeframe.to_rfc3339(); + + let proof = credential_jwp.get_proof(); + let claims = credential_jwp.get_claims().ok_or(Error::ProofUpdateError("Should not happen".to_owned()))?; + let mut payloads: Payloads = credential_jwp.get_payloads().clone(); + + let index_start_validity_timeframe = claims.get_claim_index(format!("vc.credentialStatus.{}", RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY)).ok_or(Error::ProofUpdateError("'startValidityTimeframe' property NOT found".to_owned()))?; + let index_end_validity_timeframe = claims.get_claim_index(format!("vc.credentialStatus.{}", RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY)).ok_or(Error::ProofUpdateError("'endValidityTimeframe' property NOT found".to_owned()))?; + + + let old_start_validity_timeframe = payloads.replace_payload_at_index(index_start_validity_timeframe, Value::String(new_start_validity_timeframe.clone())) + .map_err(|_| Error::ProofUpdateError("'startValidityTimeframe' value NOT found".to_owned()))? + .to_string(); + let old_end_validity_timeframe = payloads.replace_payload_at_index(index_end_validity_timeframe, Value::String(new_end_validity_timeframe.clone())) + .map_err(|_| Error::ProofUpdateError("'endValidityTimeframe' value NOT found".to_owned()))? + .to_string(); + + + let proof: [u8; 112] = proof.try_into().map_err(|_| Error::ProofUpdateError("Invalid bytes length of JWP proof".to_owned()))?; + + let new_proof = ::update_proof( + storage.key_storage(), + &key_id, + jwk, + &proof, + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + payloads.0.len() + ) + .await + .map_err(Error::KeyStorageError)?; + + credential_jwp.set_proof(&new_proof); + credential_jwp.set_payloads(payloads); + let jpt = credential_jwp.encode(SerializationType::COMPACT) + .map_err(|e| Error::EncodingError(Box::new(e)))?; + + Ok(Jpt::new(jpt)) + } +} + + + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use super::*; + use identity_iota_core::IotaDocument; + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl TimeframeRevocationExtension for IotaDocument { + + async fn update( + &self, + storage: &Storage, + fragment: &str, + granularity: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageExt, + I: KeyIdStorage + { + self + .core_document() + .update(storage, fragment, granularity, credential_jwp) + .await + } + + } + +} \ No newline at end of file From 1992b2884a35a2a67e8ed553490621da389ce623 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 1 Feb 2024 16:56:12 +0100 Subject: [PATCH 029/163] update 10_zkp_revocation example --- examples/1_advanced/10_zkp_revocation.rs | 200 +++++++++++++++++------ 1 file changed, 149 insertions(+), 51 deletions(-) diff --git a/examples/1_advanced/10_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs index 0814757c75..51821b2536 100644 --- a/examples/1_advanced/10_zkp_revocation.rs +++ b/examples/1_advanced/10_zkp_revocation.rs @@ -1,8 +1,16 @@ use std::thread; -use std::time::Duration; use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; +use identity_iota::core::Duration; +use identity_iota::credential::RevocationBitmap; +use identity_iota::credential::StatusCheck; +use identity_iota::did::DIDUrl; +use identity_iota::document::Service; +use identity_iota::storage::TimeframeRevocationExtension; +use iota_sdk::types::block::output::AliasOutputBuilder; +use iota_sdk::types::block::output::RentStructure; +use std::time::Duration as SleepDuration; use identity_iota::core::FromJson; use identity_iota::core::Object; use identity_iota::core::Url; @@ -24,7 +32,6 @@ use identity_iota::credential::RevocationTimeframeStatus; use identity_iota::credential::SelectiveDisclosurePresentation; use identity_iota::credential::Status; use identity_iota::credential::Subject; -use identity_iota::credential::ValidityTimeframeGranularity; use identity_iota::did::CoreDID; use identity_iota::did::DID; use identity_iota::iota::IotaClientExt; @@ -58,34 +65,43 @@ const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: ProofAlgorithm ) -> anyhow::Result<(Address, IotaDocument, String)> { - // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; - - // Get the Bech32 human-readable part (HRP) of the network. - let network_name: NetworkName = client.network_name().await?; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // New Verification Method containing a BBS+ key - let fragment = document.generate_method_jwp( - &storage, - key_type, - alg, - None, - MethodScope::VerificationMethod - ).await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; - println!("Published DID document: {document:#}"); - - Ok((address, document, fragment)) + // Get an address with funds for testing. + let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // New Verification Method containing a BBS+ key + let fragment = document.generate_method_jwp( + &storage, + key_type, + alg, + None, + MethodScope::VerificationMethod + ).await?; + + // Create a new empty revocation bitmap. No credential is revoked yet. + let revocation_bitmap: RevocationBitmap = RevocationBitmap::new(); + + // Add the revocation bitmap to the DID document of the issuer as a service. + let service_id: DIDUrl = document.id().to_url().join("#my-revocation-service")?; + let service: Service = revocation_bitmap.to_service(service_id)?; + + assert!(document.insert_service(service).is_ok()); + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) } @@ -107,7 +123,7 @@ async fn main() -> anyhow::Result<()> { let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = + let (_, mut issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381SHA256_KEY_TYPE, ProofAlgorithm::BLS12381_SHA256).await?; @@ -126,7 +142,11 @@ async fn main() -> anyhow::Result<()> { // ========================================================================================= // Step 1: Create a new RevocationTimeframeStatus containing the current validityTimeframe // ======================================================================================= - let status: Status = RevocationTimeframeStatus::new(ValidityTimeframeGranularity::MINUTE).into(); + let granularity = Duration::minutes(1); + // The issuer also chooses a unique `RevocationBitmap` index to be able to revoke it later. + let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; + let credential_index: u32 = 5; + let status: Status = RevocationTimeframeStatus::new(granularity, service_url, credential_index)?.into(); // Build credential using subject above and issuer. @@ -167,23 +187,28 @@ async fn main() -> anyhow::Result<()> { println!("Sending credential (as JPT) to the holder: {}\n", credential_jpt.as_str()); - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - - let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_issued_jpt(&credential_jpt).unwrap(); - let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; - - // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented - let decoded_credential = JptCredentialValidator::validate::<_, Object>( + // Checks also that the credentialStatus is correct, otherwise asks Issuer for an update + let validation_result = JptCredentialValidator::validate::<_, Object>( &credential_jpt, &issuer_document, &JptCredentialValidationOptions::default(), FailFast::FirstError, - ) - .unwrap(); + ); + if validation_result.as_ref().is_err_and(|e| matches!(e.validation_errors[0], JwtValidationError::Revoked)) { + //request update + todo!() + } + let decoded_credential = validation_result.unwrap(); + + // Holder checks if his credential has been revoked by the Issuer + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024(&decoded_credential.credential, &issuer_document, StatusCheck::Strict); + assert!(revocation_result.is_ok()); + + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); @@ -201,8 +226,6 @@ async fn main() -> anyhow::Result<()> { ) .await?; - - // Holder sends a Presentation JPT to the Verifier. println!("Sending presentation (as JPT) to the verifier: {}\n", presentation_jpt.as_str()); @@ -210,9 +233,6 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== // Step 2a: Verifier receives the Presentation and verifies it. // =========================================================================== - - let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); - let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); @@ -235,7 +255,7 @@ async fn main() -> anyhow::Result<()> { // Step 2b: Waiting for the next validityTimeframe, will result in the Credential being revoked // =========================================================================== - thread::sleep(Duration::from_secs(61)); //Will result revoked with GRANULARITY set to MINUTE + thread::sleep(SleepDuration::from_secs(61)); //Will result revoked with the granularity of 1 minute let validation_result = JptPresentationValidator::validate::<_, Object>( &presentation_jpt, @@ -247,10 +267,88 @@ async fn main() -> anyhow::Result<()> { println!("VC validation result: {validation_result:?}"); // We expect validation to no longer succeed because the credential was NOT updated. - assert!(matches!( + if matches!( validation_result.unwrap_err().validation_errors[0], JwtValidationError::Revoked - )); + ) { + println!("validityTimeframe NOT valid, Credential Revoked"); + } + + + // =========================================================================== + // Update credential + // =========================================================================== + + // Holder sends its credential to Issuer asking for an update + + // Issuer checks the Credential integrity. + // Issuer checks Credential's status. If validityTimeframe still valid does not perform the update + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ); + assert!(validation_result.as_ref().is_err_and(|e| matches!(e.validation_errors[0], JwtValidationError::Revoked))); + + + // Issuer checks if the Credential has been revoked + + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024(&decoded_credential.credential, &issuer_document, StatusCheck::Strict); + assert!(!revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + + + // Issuer updates the credential + + let new_credential_jpt = issuer_document + .update( + &storage_issuer, + &fragment_issuer, + granularity, + &mut decoded_credential.decoded_jwp.clone()) + .await?; + + + // Issuer sends back the credential updated + + println!("Sending updated credential (as JPT) to the holder: {}\n", new_credential_jpt.as_str()); - Ok(()) + // Holder check validity of the updated credential + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &new_credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ); + + assert!(!validation_result.as_ref().is_err_and(|e| matches!(e.validation_errors[0], JwtValidationError::Revoked))); + println!("Updated credential is VALID!"); + + + // =========================================================================== + // Issuer decides to Revoke Holder's Credential + // =========================================================================== + + + // Update the RevocationBitmap service in the issuer's DID Document. + // This revokes the credential's unique index. + + issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; + + // Publish the changes. + let alias_output: AliasOutput = client.update_did_output(issuer_document.clone()).await?; + let rent_structure: RentStructure = client.get_rent_structure().await?; + let alias_output: AliasOutput = AliasOutputBuilder::from(&alias_output) + .with_minimum_storage_deposit(rent_structure) + .finish()?; + issuer_document = client.publish_did_output(&secret_manager_issuer, alias_output).await?; + + + // Holder checks if his credential has been revoked by the Issuer + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024(&decoded_credential.credential, &issuer_document, StatusCheck::Strict); + assert!(revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + println!("Credential Revoked!"); + Ok(()) } \ No newline at end of file From 572518b4debfd17a96fd3ae33014812d1feb77ea Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 8 Feb 2024 19:09:51 +0100 Subject: [PATCH 030/163] implement ML-DSA algorithm keygen --- Cargo.toml | 2 + examples/1_advanced/10_zkp_revocation.rs | 10 +- examples/1_advanced/11_pqc.rs | 91 ++++++++++++++++ examples/Cargo.toml | 6 +- identity_jose/src/jwk/jwk_pq.rs | 53 ++++++++++ identity_jose/src/jwk/key.rs | 7 ++ identity_jose/src/jwk/key_params.rs | 10 ++ identity_jose/src/jwk/key_type.rs | 7 ++ identity_jose/src/jwk/mod.rs | 2 + identity_jose/src/jws/mod.rs | 2 + identity_jose/src/jws/pq_algorithm.rs | 65 ++++++++++++ identity_storage/Cargo.toml | 1 + .../src/key_storage/jwk_storage.rs | 11 ++ identity_storage/src/key_storage/memstore.rs | 66 ++++++++++++ identity_storage/src/storage/mod.rs | 2 + .../src/storage/pqc_jws_document_ext.rs | 100 ++++++++++++++++++ 16 files changed, 428 insertions(+), 7 deletions(-) create mode 100644 examples/1_advanced/11_pqc.rs create mode 100644 identity_jose/src/jwk/jwk_pq.rs create mode 100644 identity_jose/src/jws/pq_algorithm.rs create mode 100644 identity_storage/src/storage/pqc_jws_document_ext.rs diff --git a/Cargo.toml b/Cargo.toml index 25ee25b0a8..4f995936cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ strum = { version = "0.25", default-features = false, features = ["std", "derive serde_json = { version = "1.0", default-features = false } json-proof-token = {git = "https://github.com/Cybersecurity-LINKS/json-proof-token.git"} zkryptium = { git = "https://github.com/Cybersecurity-LINKS/zkryptium.git", default-features = false, features = ["bbsplus"] } +oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } + [workspace.package] authors = ["IOTA Stiftung"] diff --git a/examples/1_advanced/10_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs index 51821b2536..12b5edd6d1 100644 --- a/examples/1_advanced/10_zkp_revocation.rs +++ b/examples/1_advanced/10_zkp_revocation.rs @@ -196,11 +196,6 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ); - if validation_result.as_ref().is_err_and(|e| matches!(e.validation_errors[0], JwtValidationError::Revoked)) { - //request update - todo!() - } - let decoded_credential = validation_result.unwrap(); // Holder checks if his credential has been revoked by the Issuer @@ -208,7 +203,6 @@ async fn main() -> anyhow::Result<()> { assert!(revocation_result.is_ok()); - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; let method_id = decoded_credential.decoded_jwp.get_issuer_protected_header().kid().unwrap(); @@ -281,9 +275,13 @@ async fn main() -> anyhow::Result<()> { // Holder sends its credential to Issuer asking for an update + println!("Sending credential (as JPT) to the Issuer for update: {}\n", credential_jpt.as_str()); + + // Issuer checks the Credential integrity. // Issuer checks Credential's status. If validityTimeframe still valid does not perform the update + let validation_result = JptCredentialValidator::validate::<_, Object>( &credential_jpt, &issuer_document, diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs new file mode 100644 index 0000000000..4ec8bf9e50 --- /dev/null +++ b/examples/1_advanced/11_pqc.rs @@ -0,0 +1,91 @@ + +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwsDocumentExtPQC; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyType; +use identity_iota::verification::jws::JwsAlgorithmPQ; +use identity_iota::verification::MethodScope; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; + +// The API endpoint of an IOTA node, e.g. Hornet. +const API_ENDPOINT: &str = "http://localhost:14265"; +// The faucet endpoint allows requesting funds for testing purposes. +const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; + + +// const api_endpoint: &str = "https://api.testnet.shimmer.network"; +// const faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; + + + +async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithmPQ ) -> anyhow::Result<(Address, IotaDocument, String)> { + + // Get an address with funds for testing. + let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // New Verification Method containing a BBS+ key + let fragment = document.generate_method_pqc( + &storage, + key_type, + alg, + None, + MethodScope::VerificationMethod + ).await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) +} + + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identitiy for the issuer. + // =========================================================================== + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + + let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?); + + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithmPQ::ML_DSA_44).await?; + + Ok(()) +} \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 87429544c6..48b7bd8085 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -99,4 +99,8 @@ name = "9_zkp" [[example]] path = "1_advanced/10_zkp_revocation.rs" -name = "10_zkp_revocation" \ No newline at end of file +name = "10_zkp_revocation" + +[[example]] +path = "1_advanced/11_pqc.rs" +name = "11_pqc" \ No newline at end of file diff --git a/identity_jose/src/jwk/jwk_pq.rs b/identity_jose/src/jwk/jwk_pq.rs new file mode 100644 index 0000000000..cd7623ae5e --- /dev/null +++ b/identity_jose/src/jwk/jwk_pq.rs @@ -0,0 +1,53 @@ +// ============================================================================= +// Post-quantum algorithm key parameters +// ============================================================================= + +use zeroize::Zeroize; + +use super::{JwkParams, JwkType}; + +// TODO: PQ - parameter for PQ keys + +/// Parameters for Post-Quantum algorithm keys +/// +/// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium) +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize, Zeroize)] +#[zeroize(drop)] +pub struct JwkParamsPQ { + + /// The public key as a base64url-encoded value. + #[serde(rename = "pub")] + pub public: String, // Public Key + /// The private key as a base64url-encoded value. + #[serde(skip_serializing_if = "Option::is_none", rename = "priv")] + pub private: Option, // Private Key +} + +impl JwkParamsPQ { + /// Creates new JWK OKP Params. + pub const fn new() -> Self { + Self { + public: String::new(), + private: None, + } + } + + /// Returns a clone with _all_ private key components unset. + pub fn to_public(&self) -> Self { + Self { + public: self.public.clone(), + private: None, + } + } + + /// Returns `true` if _all_ private key components of the key are unset, `false` otherwise. + pub fn is_public(&self) -> bool { + self.private.is_none() + } + + /// Returns `true` if _all_ private key components of the key are set, `false` otherwise. + pub fn is_private(&self) -> bool { + self.private.is_some() + } + +} diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index be1db84c35..15059eca34 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -21,6 +21,8 @@ use crate::jwk::JwkType; use crate::jwk::JwkUse; use crate::jwu::encode_b64; +use super::JwkParamsPQ; + /// A SHA256 JSON Web Key Thumbprint. pub type JwkThumbprintSha256 = [u8; SHA256_LEN]; @@ -387,6 +389,10 @@ impl Jwk { JwkParams::Okp(JwkParamsOkp { crv, x, .. }) => { format!(r#"{{"crv":"{crv}","kty":"{kty}","x":"{x}"}}"#) } + //TODO: PQ - thumbprint for PQ keys + JwkParams::MLDSA(JwkParamsPQ { public, .. }) => { + format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) + } } } @@ -439,6 +445,7 @@ impl Jwk { JwkParams::Rsa(params) => params.is_private(), JwkParams::Oct(_) => true, JwkParams::Okp(params) => params.is_private(), + JwkParams::MLDSA(params) => params.is_private(), //TODO: PQ - is_private Jwk method } } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index f60d6d5e66..118f7960d8 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -10,6 +10,8 @@ use crate::jwk::EcxCurve; use crate::jwk::EdCurve; use crate::jwk::JwkType; +use super::JwkParamsPQ; + /// Algorithm-specific parameters for JSON Web Keys. /// /// [More Info](https://tools.ietf.org/html/rfc7518#section-6) @@ -26,6 +28,10 @@ pub enum JwkParams { Oct(JwkParamsOct), /// Octet Key Pairs parameters. Okp(JwkParamsOkp), + + //TODO: PQ - new JwkParams + /// ML-DSA parameters + MLDSA(JwkParamsPQ) } impl JwkParams { @@ -36,6 +42,7 @@ impl JwkParams { JwkType::Rsa => Self::Rsa(JwkParamsRsa::new()), JwkType::Oct => Self::Oct(JwkParamsOct::new()), JwkType::Okp => Self::Okp(JwkParamsOkp::new()), + JwkType::MLDSA => Self::MLDSA(JwkParamsPQ::new()), } } @@ -46,6 +53,7 @@ impl JwkParams { Self::Rsa(inner) => inner.kty(), Self::Oct(inner) => inner.kty(), Self::Okp(inner) => inner.kty(), + Self::MLDSA(_) => JwkType::MLDSA, } } @@ -58,6 +66,7 @@ impl JwkParams { Self::Ec(inner) => Some(Self::Ec(inner.to_public())), Self::Rsa(inner) => Some(Self::Rsa(inner.to_public())), Self::Oct(_) => None, + Self::MLDSA(inner) => Some(Self::MLDSA(inner.to_public())), } } @@ -68,6 +77,7 @@ impl JwkParams { Self::Ec(value) => value.is_public(), Self::Rsa(value) => value.is_public(), Self::Oct(value) => value.is_public(), + Self::MLDSA(value) => value.is_public(), } } } diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index 59cdbaa5cc..8df87f519c 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -22,6 +22,12 @@ pub enum JwkType { /// Octet string key pairs. #[serde(rename = "OKP")] Okp, + + //TODO: PQ - new JwkType + /// JSON Web Key Type for the ML-DSA Algorithm Family. + /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-02#section-4) + #[serde(rename = "ML-DSA")] + MLDSA } impl JwkType { @@ -32,6 +38,7 @@ impl JwkType { Self::Rsa => "RSA", Self::Oct => "oct", Self::Okp => "OKP", + Self::MLDSA => "ML-DSA" } } } diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index 9cf3279c0d..d72aeceee4 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -11,6 +11,7 @@ mod key_set; mod key_type; mod key_use; mod jwk_ext; +mod jwk_pq; pub use self::curve::*; pub use self::key::*; @@ -19,3 +20,4 @@ pub use self::key_params::*; pub use self::key_set::*; pub use self::key_type::*; pub use self::key_use::*; +pub use self::jwk_pq::*; diff --git a/identity_jose/src/jws/mod.rs b/identity_jose/src/jws/mod.rs index 6eef07a1e2..89b0b08389 100644 --- a/identity_jose/src/jws/mod.rs +++ b/identity_jose/src/jws/mod.rs @@ -16,6 +16,7 @@ mod encoding; mod format; mod header; mod recipient; +mod pq_algorithm; pub use self::algorithm::*; pub use self::charset::*; @@ -25,3 +26,4 @@ pub use self::encoding::*; pub use self::format::*; pub use self::header::*; pub use self::recipient::*; +pub use self::pq_algorithm::*; diff --git a/identity_jose/src/jws/pq_algorithm.rs b/identity_jose/src/jws/pq_algorithm.rs new file mode 100644 index 0000000000..aad97fe399 --- /dev/null +++ b/identity_jose/src/jws/pq_algorithm.rs @@ -0,0 +1,65 @@ +//TODO: JWS PQ algorithms + +use std::str::FromStr; +use crate::error::Error; +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result; + +/// Supported post-quantum algorithms for the JSON Web Signatures `alg` claim. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] +#[allow(non_camel_case_types)] +pub enum JwsAlgorithmPQ { + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-44")] + ML_DSA_44, + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-65")] + ML_DSA_65, + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-87")] + ML_DSA_87 +} + + + + +impl JwsAlgorithmPQ { + /// A slice of all supported [`JwsAlgorithmPQ`]s. + pub const ALL: &'static [Self] = &[ + Self::ML_DSA_44, + Self::ML_DSA_65, + Self::ML_DSA_87, + ]; + + /// Returns the JWS algorithm as a `str` slice. + pub const fn name(self) -> &'static str { + match self { + Self::ML_DSA_44 => "ML-DSA-44", + Self::ML_DSA_65 => "ML-DSA-65", + Self::ML_DSA_87 => "ML-DSA-87", + } + } + } + + impl FromStr for JwsAlgorithmPQ { + type Err = crate::error::Error; + + fn from_str(string: &str) -> std::result::Result { + match string { + "ML-DSA-44" => Ok(Self::ML_DSA_44), + "ML-DSA-65" => Ok(Self::ML_DSA_65), + "ML-DSA-87" => Ok(Self::ML_DSA_87), + _ => Err(Error::JwsAlgorithmParsingError), + } + } + } + + impl Display for JwsAlgorithmPQ { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(self.name()) + } + } \ No newline at end of file diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index fa6c458ff6..09e2b3d85d 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -29,6 +29,7 @@ thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } json-proof-token.workspace = true zkryptium.workspace = true +oqs.workspace = true [dev-dependencies] diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index e033e67720..9245e7ae56 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -8,9 +8,11 @@ use async_trait::async_trait; use identity_core::common::Timestamp; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jws::JwsAlgorithmPQ; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use oqs::sig::Algorithm; use zkryptium::bbsplus::signature::BBSplusSignature; use super::jwk_gen_output::JwkGenOutput; @@ -83,4 +85,13 @@ pub trait JwkStorageExt : JwkStorage { /// Update proof functionality for timeframe revocation mechanism async fn update_proof(&self, key_id: &KeyId, public_key: &Jwk, proof: &[u8; 112], old_start_validity_timeframe: String, new_start_validity_timeframe: String, old_end_validity_timeframe: String, new_end_validity_timeframe: String, index_start_validity_timeframe: usize, index_end_validity_timeframe: usize, n_messages: usize ) -> KeyStorageResult<[u8; 112]>; +} + + +/// Extension to the JwkStorage to handle post-quantum keys +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwkStoragePQ : JwkStorage { + /// Generates a JWK representing a BBS+ signature + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithmPQ) -> KeyStorageResult; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 7a97329b4e..ce007d9aa4 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -13,6 +13,9 @@ use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jwk::JwkParams; +use identity_verification::jwk::JwkParamsPQ; +use identity_verification::jws::JwsAlgorithmPQ; use identity_verification::jwu; use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; @@ -22,6 +25,8 @@ use jsonprooftoken::jwk::key::Jwk as JwkExt; use jsonprooftoken::jwk::types::KeyPairSubtype; use jsonprooftoken::jwp::header::IssuerProtectedHeader; use jsonprooftoken::jwp::issued::JwpIssuedBuilder; +use oqs::sig::Algorithm; +use oqs::sig::Sig; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -43,6 +48,7 @@ use super::KeyStorageResult; use super::KeyType; use crate::JwkStorageExt; use crate::key_storage::JwkStorage; +use crate::JwkStoragePQ; /// The map from key ids to JWKs. type JwkKeyStore = HashMap; @@ -214,6 +220,11 @@ impl JwkMemStore { const BLS12381SHAKE256_KEY_TYPE_STR: &'static str = "Bls12381Shake256"; /// The BLS12381-SHAKE256 key type pub const BLS12381SHAKE256_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381SHAKE256_KEY_TYPE_STR); + + const ML_DSA: &'static str = "ML-DSA"; + /// ML-DSA algorithms key types; + pub const ML_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ML_DSA); + } impl MemStoreKeyType { @@ -484,6 +495,61 @@ impl JwkStorageExt for JwkMemStore { } +fn check_pq_alg_compatibility(alg: JwsAlgorithmPQ) -> Algorithm{ + match alg { + JwsAlgorithmPQ::ML_DSA_44 => Algorithm::Dilithium2, + JwsAlgorithmPQ::ML_DSA_65 => Algorithm::Dilithium3, + JwsAlgorithmPQ::ML_DSA_87 => Algorithm::Dilithium5, + } +} + +//TODO: PQ - JwkStoragePQ +/// JwkStoragePQ implementation for JwkMemStore +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwkStoragePQ for JwkMemStore { + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithmPQ) -> KeyStorageResult { + + //TODO: maybe handle key_type + let oqs_alg = check_pq_alg_compatibility(alg); + oqs::init(); //TODO: check what this function does + + let scheme = Sig::new(oqs_alg).unwrap(); + let (pk, sk) = scheme.keypair().unwrap(); + + let kid: KeyId = random_key_id(); + + let public = jwu::encode_b64(pk.into_vec()); + let private = jwu::encode_b64(sk.into_vec()); + let mut jwk_params = match alg { + JwsAlgorithmPQ::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithmPQ::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithmPQ::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), + }; + + match jwk_params { + JwkParams::MLDSA(ref mut params) => { + params.public = public; + params.private = Some(private); + }, + _ => return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("Should NOT happen!"), + ), + } + + let mut jwk = Jwk::from_params(jwk_params); + + jwk.set_alg(alg.name()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + let public_jwk: Jwk = jwk.to_public().expect("should only panic if kty == oct"); + + let mut jwk_store: RwLockWriteGuard<'_, JwkKeyStore> = self.jwk_store.write().await; + jwk_store.insert(kid.clone(), jwk); + + Ok(JwkGenOutput::new(kid, public_jwk)) + } +} pub(crate) mod shared { use core::fmt::Debug; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 8e552aee69..8f62fa9036 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -9,6 +9,7 @@ mod jwk_document_ext; mod jwp_document_ext; mod signature_options; mod timeframe_revocation_ext; +mod pqc_jws_document_ext; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -19,6 +20,7 @@ pub use jwk_document_ext::*; pub use jwp_document_ext::*; pub use signature_options::*; pub use timeframe_revocation_ext::*; +pub use pqc_jws_document_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs new file mode 100644 index 0000000000..562fbc8259 --- /dev/null +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -0,0 +1,100 @@ +use identity_document::document::CoreDocument; +use identity_verification::{jws::JwsAlgorithmPQ, MethodScope}; +use async_trait::async_trait; +use crate::{JwkStoragePQ, KeyIdStorage, KeyType, Storage, StorageResult}; +use crate::JwkGenOutput; +use crate::key_id_storage::MethodDigest; +use super::JwkStorageDocumentError as Error; +use identity_did::DIDUrl; +use identity_verification::VerificationMethod; +use crate::try_undo_key_generation; + +///New trait to handle JWP-based operations on DID Documents +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwsDocumentExtPQC { + + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. This support BBS+ keys. + async fn generate_method_pqc( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithmPQ, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage; + +} + + + + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + + +generate_method_for_document_type!(CoreDocument, JwsAlgorithmPQ, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_core_document); + + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwsDocumentExtPQC for CoreDocument { + async fn generate_method_pqc( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithmPQ, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + { + // todo!() + generate_method_core_document(self, storage, key_type, alg, fragment, scope).await + } + +} + + + + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use crate::JwkStorage; + +use super::*; + use identity_iota_core::IotaDocument; + + generate_method_for_document_type!(IotaDocument, JwsAlgorithmPQ, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_iota_document); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwsDocumentExtPQC for IotaDocument { + async fn generate_method_pqc( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithmPQ, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + { + // todo!() + generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await + } + + } +} \ No newline at end of file From 8323975e26fa6f802682563e432532ecf8075c99 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 13 Feb 2024 17:20:24 +0100 Subject: [PATCH 031/163] create post quantum cleartext vc --- identity_jose/src/jwk/key.rs | 16 ++ identity_jose/src/jws/algorithm.rs | 23 ++ identity_jose/src/jws/mod.rs | 2 - identity_jose/src/jws/pq_algorithm.rs | 65 ------ .../src/key_storage/jwk_storage.rs | 8 +- identity_storage/src/key_storage/memstore.rs | 125 +++++++++- .../src/storage/pqc_jws_document_ext.rs | 219 +++++++++++++++++- 7 files changed, 366 insertions(+), 92 deletions(-) delete mode 100644 identity_jose/src/jws/pq_algorithm.rs diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index 15059eca34..8102736311 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -341,6 +341,22 @@ impl Jwk { } } + /// Returns the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA`. + pub fn try_mldsa_params(&self) -> Result<&JwkParamsPQ> { + match self.params() { + JwkParams::MLDSA(params) => Ok(params), + _ => Err(Error::KeyError("ML-DSA")), + } + } + + /// Returns a mutable reference to the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA`. + pub fn try_mldsa_params_mut(&mut self) -> Result<&mut JwkParamsPQ> { + match self.params_mut() { + JwkParams::MLDSA(params) => Ok(params), + _ => Err(Error::KeyError("ML-DSA")), + } + } + // =========================================================================== // Thumbprint // =========================================================================== diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index a60dc84050..ca8fa2b811 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -45,6 +45,20 @@ pub enum JwsAlgorithm { NONE, /// EdDSA signature algorithms EdDSA, + + //TODO: PQC - new PQ JwsAlgorithms + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-44")] + ML_DSA_44, + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-65")] + ML_DSA_65, + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "ML-DSA-87")] + ML_DSA_87 } impl JwsAlgorithm { @@ -65,6 +79,9 @@ impl JwsAlgorithm { Self::ES256K, Self::NONE, Self::EdDSA, + Self::ML_DSA_44, + Self::ML_DSA_65, + Self::ML_DSA_87, ]; /// Returns the JWS algorithm as a `str` slice. @@ -85,6 +102,9 @@ impl JwsAlgorithm { Self::ES256K => "ES256K", Self::NONE => "none", Self::EdDSA => "EdDSA", + Self::ML_DSA_44 => "ML-DSA-44", + Self::ML_DSA_65 => "ML-DSA-65", + Self::ML_DSA_87 => "ML-DSA-87", } } } @@ -109,6 +129,9 @@ impl FromStr for JwsAlgorithm { "ES256K" => Ok(Self::ES256K), "none" => Ok(Self::NONE), "EdDSA" => Ok(Self::EdDSA), + "ML-DSA-44" => Ok(Self::ML_DSA_44), + "ML-DSA-65" => Ok(Self::ML_DSA_65), + "ML-DSA-87" => Ok(Self::ML_DSA_87), _ => Err(Error::JwsAlgorithmParsingError), } } diff --git a/identity_jose/src/jws/mod.rs b/identity_jose/src/jws/mod.rs index 89b0b08389..6eef07a1e2 100644 --- a/identity_jose/src/jws/mod.rs +++ b/identity_jose/src/jws/mod.rs @@ -16,7 +16,6 @@ mod encoding; mod format; mod header; mod recipient; -mod pq_algorithm; pub use self::algorithm::*; pub use self::charset::*; @@ -26,4 +25,3 @@ pub use self::encoding::*; pub use self::format::*; pub use self::header::*; pub use self::recipient::*; -pub use self::pq_algorithm::*; diff --git a/identity_jose/src/jws/pq_algorithm.rs b/identity_jose/src/jws/pq_algorithm.rs deleted file mode 100644 index aad97fe399..0000000000 --- a/identity_jose/src/jws/pq_algorithm.rs +++ /dev/null @@ -1,65 +0,0 @@ -//TODO: JWS PQ algorithms - -use std::str::FromStr; -use crate::error::Error; -use core::fmt::Display; -use core::fmt::Formatter; -use core::fmt::Result; - -/// Supported post-quantum algorithms for the JSON Web Signatures `alg` claim. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] -#[allow(non_camel_case_types)] -pub enum JwsAlgorithmPQ { - /// JSON Web Signature Algorithm for ML-DSA-44 - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) - #[serde(rename = "ML-DSA-44")] - ML_DSA_44, - /// JSON Web Signature Algorithm for ML-DSA-44 - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) - #[serde(rename = "ML-DSA-65")] - ML_DSA_65, - /// JSON Web Signature Algorithm for ML-DSA-44 - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) - #[serde(rename = "ML-DSA-87")] - ML_DSA_87 -} - - - - -impl JwsAlgorithmPQ { - /// A slice of all supported [`JwsAlgorithmPQ`]s. - pub const ALL: &'static [Self] = &[ - Self::ML_DSA_44, - Self::ML_DSA_65, - Self::ML_DSA_87, - ]; - - /// Returns the JWS algorithm as a `str` slice. - pub const fn name(self) -> &'static str { - match self { - Self::ML_DSA_44 => "ML-DSA-44", - Self::ML_DSA_65 => "ML-DSA-65", - Self::ML_DSA_87 => "ML-DSA-87", - } - } - } - - impl FromStr for JwsAlgorithmPQ { - type Err = crate::error::Error; - - fn from_str(string: &str) -> std::result::Result { - match string { - "ML-DSA-44" => Ok(Self::ML_DSA_44), - "ML-DSA-65" => Ok(Self::ML_DSA_65), - "ML-DSA-87" => Ok(Self::ML_DSA_87), - _ => Err(Error::JwsAlgorithmParsingError), - } - } - } - - impl Display for JwsAlgorithmPQ { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.write_str(self.name()) - } - } \ No newline at end of file diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 9245e7ae56..851fa00992 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -8,7 +8,6 @@ use async_trait::async_trait; use identity_core::common::Timestamp; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; -use identity_verification::jws::JwsAlgorithmPQ; use jsonprooftoken::jpa::algs::ProofAlgorithm; use jsonprooftoken::jpt::claims::JptClaims; use jsonprooftoken::jwp::header::IssuerProtectedHeader; @@ -92,6 +91,9 @@ pub trait JwkStorageExt : JwkStorage { #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwkStoragePQ : JwkStorage { - /// Generates a JWK representing a BBS+ signature - async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithmPQ) -> KeyStorageResult; + /// Generates a JWK representing a PQ key + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult; + + /// Sign the provided `data` using a PQ algorithm + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult>; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index ce007d9aa4..0fcd88134c 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -15,7 +15,6 @@ use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; use identity_verification::jwk::JwkParams; use identity_verification::jwk::JwkParamsPQ; -use identity_verification::jws::JwsAlgorithmPQ; use identity_verification::jwu; use jsonprooftoken::encoding::SerializationType; use jsonprooftoken::jpa::algs::ProofAlgorithm; @@ -495,11 +494,17 @@ impl JwkStorageExt for JwkMemStore { } -fn check_pq_alg_compatibility(alg: JwsAlgorithmPQ) -> Algorithm{ +fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { match alg { - JwsAlgorithmPQ::ML_DSA_44 => Algorithm::Dilithium2, - JwsAlgorithmPQ::ML_DSA_65 => Algorithm::Dilithium3, - JwsAlgorithmPQ::ML_DSA_87 => Algorithm::Dilithium5, + JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), + JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::Dilithium3), + JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::Dilithium5), + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } } } @@ -508,23 +513,37 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithmPQ) -> Algorithm{ #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkStoragePQ for JwkMemStore { - async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithmPQ) -> KeyStorageResult { + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { //TODO: maybe handle key_type - let oqs_alg = check_pq_alg_compatibility(alg); + let oqs_alg = check_pq_alg_compatibility(alg)?; oqs::init(); //TODO: check what this function does - let scheme = Sig::new(oqs_alg).unwrap(); - let (pk, sk) = scheme.keypair().unwrap(); + let scheme = Sig::new(oqs_alg).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature scheme init failed")) + .with_source(err) + })?; + let (pk, sk) = scheme.keypair().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("keypair generation failed!")) + .with_source(err) + })?; let kid: KeyId = random_key_id(); let public = jwu::encode_b64(pk.into_vec()); let private = jwu::encode_b64(sk.into_vec()); let mut jwk_params = match alg { - JwsAlgorithmPQ::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithmPQ::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithmPQ::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } }; match jwk_params { @@ -549,6 +568,88 @@ impl JwkStoragePQ for JwkMemStore { Ok(JwkGenOutput::new(kid, public_jwk)) } + + + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; + + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .and_then(|alg_str| { + JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + })?; + + // Check that `kty` is `ML-DSA`. + match alg { + JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 => { + public_key.try_mldsa_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) + .with_source(err) + })? + }, + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }; + + // Obtain the corresponding private key and sign `data`. + let jwk: &Jwk = jwk_store + .get(key_id) + .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; + + let params = jwk.try_mldsa_params().unwrap(); + + let sk = params + .private + .as_deref() + .map(jwu::decode_b64) + .ok_or_else(|| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("expected Jwk `pub` param to be present") + })? + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("unable to decode `d` param") + .with_source(err) + })?; + + oqs::init(); //TODO: check what this function does + + let scheme = match alg { + JwsAlgorithm::ML_DSA_44 => Sig::new(Algorithm::Dilithium2), + JwsAlgorithm::ML_DSA_65 => Sig::new(Algorithm::Dilithium3), + JwsAlgorithm::ML_DSA_87 => Sig::new(Algorithm::Dilithium5), + + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }.map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature scheme init failed")) + .with_source(err) + })?; + + let secret_key = scheme.secret_key_from_bytes(&sk).ok_or( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)) + )?; + + let signature = scheme.sign(&data, secret_key).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature computation failed")) + .with_source(err) + })?; + + Ok(signature.into_vec()) + } } pub(crate) mod shared { diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index 562fbc8259..dc9e387e62 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -1,12 +1,17 @@ +use identity_core::common::Object; +use identity_credential::credential::{Credential, Jws, Jwt}; use identity_document::document::CoreDocument; -use identity_verification::{jws::JwsAlgorithmPQ, MethodScope}; +use identity_verification::jws::{CharSet, CompactJwsEncoder, CompactJwsEncodingOptions, JwsHeader}; +use identity_verification::{jws::JwsAlgorithm, MethodScope}; use async_trait::async_trait; -use crate::{JwkStoragePQ, KeyIdStorage, KeyType, Storage, StorageResult}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use crate::{JwkStoragePQ, JwsSignatureOptions, KeyIdStorage, KeyType, Storage, StorageResult}; use crate::JwkGenOutput; use crate::key_id_storage::MethodDigest; use super::JwkStorageDocumentError as Error; use identity_did::DIDUrl; -use identity_verification::VerificationMethod; +use identity_verification::{MethodData, VerificationMethod}; use crate::try_undo_key_generation; ///New trait to handle JWP-based operations on DID Documents @@ -20,7 +25,7 @@ pub trait JwsDocumentExtPQC { &mut self, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithmPQ, + alg: JwsAlgorithm, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult @@ -28,6 +33,33 @@ pub trait JwsDocumentExtPQC { K: JwkStoragePQ, I: KeyIdStorage; + + /// Create a JWS using a PQC + async fn create_jws_pqc( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage; + + /// Produces a JWT with PQC + async fn create_credential_jwt_pqc( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync; + } @@ -38,7 +70,7 @@ pub trait JwsDocumentExtPQC { // ==================================================================================================================== -generate_method_for_document_type!(CoreDocument, JwsAlgorithmPQ, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_core_document); +generate_method_for_document_type!(CoreDocument, JwsAlgorithm, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_core_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -48,7 +80,7 @@ impl JwsDocumentExtPQC for CoreDocument { &mut self, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithmPQ, + alg: JwsAlgorithm, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult @@ -60,6 +92,139 @@ impl JwsDocumentExtPQC for CoreDocument { generate_method_core_document(self, storage, key_type, alg, fragment, scope).await } + async fn create_jws_pqc( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + // Extract JwsAlgorithm. + let alg: JwsAlgorithm = jwk + .alg() + .unwrap_or("") + .parse() + .map_err(|_| Error::InvalidJwsAlgorithm)?; + + // Create JWS header in accordance with options. + let header: JwsHeader = { + let mut header = JwsHeader::new(); + + header.set_alg(alg); + if let Some(custom) = &options.custom_header_parameters { + header.set_custom(custom.clone()) + } + + if let Some(ref kid) = options.kid { + header.set_kid(kid.clone()); + } else { + header.set_kid(method.id().to_string()); + } + + if options.attach_jwk { + header.set_jwk(jwk.clone()) + }; + + if let Some(b64) = options.b64 { + // Follow recommendation in https://datatracker.ietf.org/doc/html/rfc7797#section-7. + if !b64 { + header.set_b64(b64); + header.set_crit(["b64"]); + } + }; + + if let Some(typ) = &options.typ { + header.set_typ(typ.clone()) + } else { + // https://www.w3.org/TR/vc-data-model/#jwt-encoding + header.set_typ("JWT") + } + + if let Some(cty) = &options.cty { + header.set_cty(cty.clone()) + }; + + if let Some(url) = &options.url { + header.set_url(url.clone()) + }; + + if let Some(nonce) = &options.nonce { + header.set_nonce(nonce.clone()) + }; + + header + }; + + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + // Extract Compact JWS encoding options. + let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload { + // We use this as a default and don't provide the extra UrlSafe check for now. + // Applications that require such checks can easily do so after JWS creation. + CompactJwsEncodingOptions::NonDetached { + charset_requirements: CharSet::Default, + } + } else { + CompactJwsEncodingOptions::Detached + }; + + let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) + .map_err(|err| Error::EncodingError(err.into()))?; + let signature = ::pq_sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk) + .await + .map_err(Error::KeyStorageError)?; + Ok(Jws::new(jws_encoder.into_jws(&signature))) + } + + async fn create_credential_jwt_pqc( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + if options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for credential signing", + ))); + } + + if !options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } + + let payload = credential + .serialize_jwt(custom_claims) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws_pqc(storage, fragment, payload.as_bytes(), options) + .await + .map(|jws| Jwt::new(jws.into())) + } + } @@ -70,12 +235,11 @@ impl JwsDocumentExtPQC for CoreDocument { // ==================================================================================================================== #[cfg(feature = "iota-document")] mod iota_document { - use crate::JwkStorage; use super::*; use identity_iota_core::IotaDocument; - generate_method_for_document_type!(IotaDocument, JwsAlgorithmPQ, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_iota_document); + generate_method_for_document_type!(IotaDocument, JwsAlgorithm, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_iota_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] @@ -84,7 +248,7 @@ use super::*; &mut self, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithmPQ, + alg: JwsAlgorithm, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult @@ -92,9 +256,44 @@ use super::*; K: JwkStoragePQ, I: KeyIdStorage, { - // todo!() generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await } + async fn create_jws_pqc( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + { + self + .core_document() + .create_jws_pqc(storage, fragment, payload, options) + .await + } + + async fn create_credential_jwt_pqc( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + self + .core_document() + .create_credential_jwt_pqc(credential, storage, fragment, options, custom_claims) + .await + } + } } \ No newline at end of file From a542b493df5e78a82083f4423ac4c70b12992c9c Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 13 Feb 2024 17:20:52 +0100 Subject: [PATCH 032/163] post quantum JwsVerifier --- Cargo.toml | 1 + examples/1_advanced/11_pqc.rs | 86 +++++++++++++++++++++-- examples/Cargo.toml | 1 + identity_pqc_verifier/Cargo.toml | 22 ++++++ identity_pqc_verifier/README.md | 4 ++ identity_pqc_verifier/src/lib.rs | 5 ++ identity_pqc_verifier/src/oqs_verifier.rs | 59 ++++++++++++++++ identity_pqc_verifier/src/pqc_verifier.rs | 42 +++++++++++ 8 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 identity_pqc_verifier/Cargo.toml create mode 100644 identity_pqc_verifier/README.md create mode 100644 identity_pqc_verifier/src/lib.rs create mode 100644 identity_pqc_verifier/src/oqs_verifier.rs create mode 100644 identity_pqc_verifier/src/pqc_verifier.rs diff --git a/Cargo.toml b/Cargo.toml index 4f995936cb..fed64c2a63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "identity_stronghold", "identity_jose", "identity_eddsa_verifier", + "identity_pqc_verifier", "examples", ] diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs index 4ec8bf9e50..9357f0cb37 100644 --- a/examples/1_advanced/11_pqc.rs +++ b/examples/1_advanced/11_pqc.rs @@ -2,22 +2,36 @@ use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::JwtCredentialValidator; +use identity_iota::credential::Subject; +use identity_iota::did::DID; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwsDocumentExtPQC; +use identity_iota::storage::JwsSignatureOptions; use identity_iota::storage::KeyIdMemstore; use identity_iota::storage::KeyType; -use identity_iota::verification::jws::JwsAlgorithmPQ; +use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; +use identity_pqc_verifier::PQCJwsVerifier; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; +use serde_json::json; // The API endpoint of an IOTA node, e.g. Hornet. const API_ENDPOINT: &str = "http://localhost:14265"; @@ -30,7 +44,7 @@ const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; -async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithmPQ ) -> anyhow::Result<(Address, IotaDocument, String)> { +async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithm) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; @@ -81,11 +95,75 @@ async fn main() -> anyhow::Result<()> { .password(Password::from("secure_password_1".to_owned())) .build(random_stronghold_path())?); - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithmPQ::ML_DSA_44).await?; + create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_87).await?; + + + let mut secret_manager_holder = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?); + + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_87).await?; + + + // ====================================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with a Post-Quantum algorithm. + // ====================================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_pqc( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!("Sending credential (as JWT) to the holder: {}", credential_jwt.as_str()); Ok(()) } \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 48b7bd8085..ee8cd10b33 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -17,6 +17,7 @@ sd-jwt-payload = { version = "0.1.2", default-features = false, features = ["sha serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } json-proof-token.workspace = true +identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } [lib] path = "utils/utils.rs" diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml new file mode 100644 index 0000000000..63ccce38ea --- /dev/null +++ b/identity_pqc_verifier/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "identity_pqc_verifier" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords = ["iota", "identity", "jose", "jwk", "jws"] +license.workspace = true +readme = "./README.md" +repository.workspace = true +rust-version.workspace = true +description = "JWS PQC signature verification for IOTA Identity" + +[dependencies] +identity_jose = { version = "=1.1.0-alpha.1", path = "../identity_jose", default-features = false } +oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } + +[features] +ml_dsa_44 = [] +ml_dsa_65 = [] +ml_dsa_87 = [] +default = ["ml_dsa_44", "ml_dsa_65", "ml_dsa_87"] \ No newline at end of file diff --git a/identity_pqc_verifier/README.md b/identity_pqc_verifier/README.md new file mode 100644 index 0000000000..994cdee1b6 --- /dev/null +++ b/identity_pqc_verifier/README.md @@ -0,0 +1,4 @@ +IOTA Identity - PQC Verifier +=== + +This crate implements a `JwsVerifier` capable of verifying ML-DSA signatures. \ No newline at end of file diff --git a/identity_pqc_verifier/src/lib.rs b/identity_pqc_verifier/src/lib.rs new file mode 100644 index 0000000000..eb06a82977 --- /dev/null +++ b/identity_pqc_verifier/src/lib.rs @@ -0,0 +1,5 @@ +mod oqs_verifier; +mod pqc_verifier; + +pub use oqs_verifier::*; +pub use pqc_verifier::*; \ No newline at end of file diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs new file mode 100644 index 0000000000..3f24da0691 --- /dev/null +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -0,0 +1,59 @@ + +use std::ops::Deref; + +use identity_jose::jwk::EdCurve; +use identity_jose::jwk::Jwk; +use identity_jose::jwk::JwkParamsOkp; +use identity_jose::jwk::JwkParamsPQ; +use identity_jose::jws::SignatureVerificationError; +use identity_jose::jws::SignatureVerificationErrorKind; +use identity_jose::jws::VerificationInput; +use oqs::sig::Algorithm; +use oqs::sig::Sig; + +/// A verifier that can handle the [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) algorithm. +#[derive(Debug)] +#[non_exhaustive] +pub struct OQSVerifier; + +impl OQSVerifier { + + /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. + pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { + + println!("ALG: {}", alg); + + // Obtain an ML-DSA-44 public key. + let params: &JwkParamsPQ = public_key + .try_mldsa_params() + .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; + + + let pk = + identity_jose::jwu::decode_b64(params.public.as_str()) + .map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) + .with_custom_message("could not decode 'pub' parameter from jwk") + })?; + + + oqs::init(); //TODO: check what this function does + + let scheme = Sig::new(alg) + .map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified) + .with_custom_message("signature scheme init failed") + })?; + + let public_key = scheme.public_key_from_bytes(&pk).ok_or( + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) + )?; + + let signature = scheme.signature_from_bytes(input.decoded_signature.deref()) + .ok_or(SignatureVerificationErrorKind::InvalidSignature)?; + + Ok(scheme.verify(&input.signing_input, signature, public_key) + .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?) + + } +} \ No newline at end of file diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs new file mode 100644 index 0000000000..6f7236100c --- /dev/null +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -0,0 +1,42 @@ + +use identity_jose::jwk::Jwk; +use identity_jose::jws::JwsVerifier; +use identity_jose::jws::SignatureVerificationError; +use identity_jose::jws::SignatureVerificationErrorKind; +use identity_jose::jws::VerificationInput; +use oqs::sig::Algorithm; + +/// An implementor of [`JwsVerifier`] that can handle the +/// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) +/// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) +/// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) algorithms. +#[derive(Debug)] +#[non_exhaustive] +pub struct PQCJwsVerifier; + +impl Default for PQCJwsVerifier { + /// Constructs an [`MLDSAJwsVerifier`]. This is the only way to obtain an [`MLDSAJwsVerifier`]. + fn default() -> Self { + Self + } +} + +impl JwsVerifier for PQCJwsVerifier { + /// This implements verification of JWS signatures signed with the + /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) + /// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) + /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) algorithms. + // Allow unused variables in case of no-default-features. + #[allow(unused_variables)] + fn verify(&self, input: VerificationInput, public_key: &Jwk) -> std::result::Result<(), SignatureVerificationError> { + match input.alg { + #[cfg(feature = "ml_dsa_44")] + identity_jose::jws::JwsAlgorithm::ML_DSA_44 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium2), + #[cfg(feature = "ml_dsa_65")] + identity_jose::jws::JwsAlgorithm::ML_DSA_65 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), + #[cfg(feature = "ml_dsa_87")] + identity_jose::jws::JwsAlgorithm::ML_DSA_87 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium5), + _ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()), + } + } +} \ No newline at end of file From 4ecb480e652e799e45be251be0a710be9a738ebf Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 13 Feb 2024 17:47:27 +0100 Subject: [PATCH 033/163] implement PQ VC/VP --- examples/1_advanced/11_pqc.rs | 123 ++++++++++++++++-- identity_pqc_verifier/src/oqs_verifier.rs | 5 - .../src/storage/pqc_jws_document_ext.rs | 88 ++++++++++++- 3 files changed, 201 insertions(+), 15 deletions(-) diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs index 9357f0cb37..afc22bce03 100644 --- a/examples/1_advanced/11_pqc.rs +++ b/examples/1_advanced/11_pqc.rs @@ -1,22 +1,39 @@ +use std::collections::HashMap; + use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; +use identity_iota::core::Duration; use identity_iota::core::FromJson; use identity_iota::core::Object; +use identity_iota::core::Timestamp; use identity_iota::core::Url; use identity_iota::credential::Credential; use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtCredential; +use identity_iota::credential::DecodedJwtPresentation; use identity_iota::credential::FailFast; use identity_iota::credential::Jwt; use identity_iota::credential::JwtCredentialValidationOptions; use identity_iota::credential::JwtCredentialValidator; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwsDocumentExtPQC; use identity_iota::storage::JwsSignatureOptions; @@ -39,11 +56,6 @@ const API_ENDPOINT: &str = "http://localhost:14265"; const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; -// const api_endpoint: &str = "https://api.testnet.shimmer.network"; -// const faucet_endpoint: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; - - - async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithm) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. @@ -56,7 +68,7 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); - // New Verification Method containing a BBS+ key + // New Verification Method containing a PQC key let fragment = document.generate_method_pqc( &storage, key_type, @@ -77,7 +89,7 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M } -/// Demonstrates how to create an Anonymous Credential with BBS+. +/// Demonstrates how to create a Post-Quantum Verifiable Credential. #[tokio::main] async fn main() -> anyhow::Result<()> { // =========================================================================== @@ -109,7 +121,7 @@ async fn main() -> anyhow::Result<()> { let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_87).await?; + create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_44).await?; // ====================================================================================== @@ -163,7 +175,100 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== // Step 3: Issuer sends the Verifiable Credential to the holder. // =========================================================================== - println!("Sending credential (as JWT) to the holder: {}", credential_jwt.as_str()); + println!("Sending credential (as JWT) to the holder: {}\n", credential_jwt.as_str()); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt_pqc( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!("Sending presentation (as JWT) to the verifier: {}\n", presentation_jwt.as_str()); + + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + PQCJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); Ok(()) } \ No newline at end of file diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 3f24da0691..e1d688f9ad 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -1,9 +1,6 @@ use std::ops::Deref; - -use identity_jose::jwk::EdCurve; use identity_jose::jwk::Jwk; -use identity_jose::jwk::JwkParamsOkp; use identity_jose::jwk::JwkParamsPQ; use identity_jose::jws::SignatureVerificationError; use identity_jose::jws::SignatureVerificationErrorKind; @@ -21,8 +18,6 @@ impl OQSVerifier { /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { - println!("ALG: {}", alg); - // Obtain an ML-DSA-44 public key. let params: &JwkParamsPQ = public_key .try_mldsa_params() diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index dc9e387e62..5ba389a521 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -1,5 +1,6 @@ use identity_core::common::Object; use identity_credential::credential::{Credential, Jws, Jwt}; +use identity_credential::presentation::{JwtPresentationOptions, Presentation}; use identity_document::document::CoreDocument; use identity_verification::jws::{CharSet, CompactJwsEncoder, CompactJwsEncodingOptions, JwsHeader}; use identity_verification::{jws::JwsAlgorithm, MethodScope}; @@ -46,7 +47,14 @@ pub trait JwsDocumentExtPQC { K: JwkStoragePQ, I: KeyIdStorage; - /// Produces a JWT with PQC + /// Produces a JWT using PQC algorithms where the payload is produced from the given `credential` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + /// + /// The `custom_claims` can be used to set additional claims on the resulting JWT. async fn create_credential_jwt_pqc( &self, credential: &Credential, @@ -60,6 +68,27 @@ pub trait JwsDocumentExtPQC { I: KeyIdStorage, T: ToOwned + Serialize + DeserializeOwned + Sync; + + /// Produces a JWT using PQC algorithms where the payload is produced from the given `presentation` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + async fn create_presentation_jwt_pqc( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + signature_options: &JwsSignatureOptions, + presentation_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync; + } @@ -225,6 +254,42 @@ impl JwsDocumentExtPQC for CoreDocument { .map(|jws| Jwt::new(jws.into())) } + + async fn create_presentation_jwt_pqc( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + jws_options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, + { + if jws_options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for presentation signing", + ))); + } + + if !jws_options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } + let payload = presentation + .serialize_jwt(jwt_options) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws_pqc(storage, fragment, payload.as_bytes(), jws_options) + .await + .map(|jws| Jwt::new(jws.into())) + } + } @@ -295,5 +360,26 @@ use super::*; .await } + + async fn create_presentation_jwt_pqc( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + jws_options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, + { + self + .core_document() + .create_presentation_jwt_pqc(presentation, storage, fragment, jws_options, jwt_options) + .await + + } } } \ No newline at end of file From f9208d83c13361682133c830f57c2e3dbf31e03c Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 15 Feb 2024 18:25:42 +0100 Subject: [PATCH 034/163] support SLH-DSA --- examples/1_advanced/11_pqc.rs | 4 +- identity_jose/src/jwk/key.rs | 18 ++++++--- identity_jose/src/jwk/key_params.rs | 8 +++- identity_jose/src/jwk/key_type.rs | 11 ++++-- identity_jose/src/jws/algorithm.rs | 23 ++++++++++- identity_pqc_verifier/Cargo.toml | 11 ++++-- identity_pqc_verifier/src/oqs_verifier.rs | 4 +- identity_pqc_verifier/src/pqc_verifier.rs | 18 +++++++-- identity_storage/src/key_storage/memstore.rs | 41 ++++++++++++-------- 9 files changed, 99 insertions(+), 39 deletions(-) diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs index afc22bce03..d089102b78 100644 --- a/examples/1_advanced/11_pqc.rs +++ b/examples/1_advanced/11_pqc.rs @@ -121,7 +121,7 @@ async fn main() -> anyhow::Result<()> { let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_44).await?; + create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::SLH_DSA_KEY_TYPE, JwsAlgorithm::SLH_DSA_SHA2_128s).await?; // ====================================================================================== @@ -258,6 +258,8 @@ async fn main() -> anyhow::Result<()> { let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + println!("--------------------------"); + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index 8102736311..2a5d51b2fc 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -341,19 +341,21 @@ impl Jwk { } } - /// Returns the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA`. - pub fn try_mldsa_params(&self) -> Result<&JwkParamsPQ> { + /// Returns the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA` or `SLH-DSA`. + pub fn try_pq_params(&self) -> Result<&JwkParamsPQ> { match self.params() { JwkParams::MLDSA(params) => Ok(params), - _ => Err(Error::KeyError("ML-DSA")), + JwkParams::SLHDSA(params) => Ok(params), + _ => Err(Error::KeyError("PQ")), } } - /// Returns a mutable reference to the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA`. - pub fn try_mldsa_params_mut(&mut self) -> Result<&mut JwkParamsPQ> { + /// Returns a mutable reference to the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA` or `SLH-DSA`. + pub fn try_pq_params_mut(&mut self) -> Result<&mut JwkParamsPQ> { match self.params_mut() { JwkParams::MLDSA(params) => Ok(params), - _ => Err(Error::KeyError("ML-DSA")), + JwkParams::SLHDSA(params) => Ok(params), + _ => Err(Error::KeyError("PQ")), } } @@ -409,6 +411,9 @@ impl Jwk { JwkParams::MLDSA(JwkParamsPQ { public, .. }) => { format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) } + JwkParams::SLHDSA(JwkParamsPQ { public, .. }) => { + format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) + } } } @@ -462,6 +467,7 @@ impl Jwk { JwkParams::Oct(_) => true, JwkParams::Okp(params) => params.is_private(), JwkParams::MLDSA(params) => params.is_private(), //TODO: PQ - is_private Jwk method + JwkParams::SLHDSA(params) => params.is_private(), } } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index 118f7960d8..c1cc528748 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -31,7 +31,9 @@ pub enum JwkParams { //TODO: PQ - new JwkParams /// ML-DSA parameters - MLDSA(JwkParamsPQ) + MLDSA(JwkParamsPQ), + /// SLH-DSA parameters + SLHDSA(JwkParamsPQ) } impl JwkParams { @@ -43,6 +45,7 @@ impl JwkParams { JwkType::Oct => Self::Oct(JwkParamsOct::new()), JwkType::Okp => Self::Okp(JwkParamsOkp::new()), JwkType::MLDSA => Self::MLDSA(JwkParamsPQ::new()), + JwkType::SLHDSA => Self::SLHDSA(JwkParamsPQ::new()), } } @@ -54,6 +57,7 @@ impl JwkParams { Self::Oct(inner) => inner.kty(), Self::Okp(inner) => inner.kty(), Self::MLDSA(_) => JwkType::MLDSA, + Self::SLHDSA(_) => JwkType::SLHDSA, } } @@ -67,6 +71,7 @@ impl JwkParams { Self::Rsa(inner) => Some(Self::Rsa(inner.to_public())), Self::Oct(_) => None, Self::MLDSA(inner) => Some(Self::MLDSA(inner.to_public())), + Self::SLHDSA(inner) => Some(Self::SLHDSA(inner.to_public())), } } @@ -78,6 +83,7 @@ impl JwkParams { Self::Rsa(value) => value.is_public(), Self::Oct(value) => value.is_public(), Self::MLDSA(value) => value.is_public(), + Self::SLHDSA(value) => value.is_public(), } } } diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index 8df87f519c..03d371d274 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -25,9 +25,13 @@ pub enum JwkType { //TODO: PQ - new JwkType /// JSON Web Key Type for the ML-DSA Algorithm Family. - /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-02#section-4) + /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-key-type) #[serde(rename = "ML-DSA")] - MLDSA + MLDSA, + /// JSON Web Key Type for the SLH-DSA Algorithm Family. + /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-key-type) + #[serde(rename = "SLH-DSA")] + SLHDSA, } impl JwkType { @@ -38,7 +42,8 @@ impl JwkType { Self::Rsa => "RSA", Self::Oct => "oct", Self::Okp => "OKP", - Self::MLDSA => "ML-DSA" + Self::MLDSA => "ML-DSA", + Self::SLHDSA => "SLH-DSA", } } } diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index ca8fa2b811..f2a8394295 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -58,7 +58,19 @@ pub enum JwsAlgorithm { /// JSON Web Signature Algorithm for ML-DSA-44 /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) #[serde(rename = "ML-DSA-87")] - ML_DSA_87 + ML_DSA_87, + /// JSON Web Signature Algorithm for SLH-DSA-SHA2-128s + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "SLH-DSA-SHA2-128s")] + SLH_DSA_SHA2_128s, + /// JSON Web Signature Algorithm for SLH-DSA-SHAKE-128s + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "SLH-DSA-SHAKE-128s")] + SLH_DSA_SHAKE_128s, + /// JSON Web Signature Algorithm for ML-DSA-44 + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + #[serde(rename = "SLH-DSA-SHA2-128f")] + SLH_DSA_SHA2_128f, } impl JwsAlgorithm { @@ -82,6 +94,9 @@ impl JwsAlgorithm { Self::ML_DSA_44, Self::ML_DSA_65, Self::ML_DSA_87, + Self::SLH_DSA_SHA2_128s, + Self::SLH_DSA_SHAKE_128s, + Self::SLH_DSA_SHA2_128f, ]; /// Returns the JWS algorithm as a `str` slice. @@ -105,6 +120,9 @@ impl JwsAlgorithm { Self::ML_DSA_44 => "ML-DSA-44", Self::ML_DSA_65 => "ML-DSA-65", Self::ML_DSA_87 => "ML-DSA-87", + Self::SLH_DSA_SHA2_128s => "SLH-DSA-SHA2-128s", + Self::SLH_DSA_SHAKE_128s => "SLH-DSA-SHAKE-128s", + Self::SLH_DSA_SHA2_128f => "SLH-DSA-SHA2-128f", } } } @@ -132,6 +150,9 @@ impl FromStr for JwsAlgorithm { "ML-DSA-44" => Ok(Self::ML_DSA_44), "ML-DSA-65" => Ok(Self::ML_DSA_65), "ML-DSA-87" => Ok(Self::ML_DSA_87), + "SLH-DSA-SHA2-128s" => Ok(Self::SLH_DSA_SHA2_128s), + "SLH-DSA-SHAKE-128s" => Ok(Self::SLH_DSA_SHAKE_128s), + "SLH-DSA-SHA2-128f" => Ok(Self::SLH_DSA_SHA2_128f), _ => Err(Error::JwsAlgorithmParsingError), } } diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 63ccce38ea..5896162b9c 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -16,7 +16,10 @@ identity_jose = { version = "=1.1.0-alpha.1", path = "../identity_jose", default oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } [features] -ml_dsa_44 = [] -ml_dsa_65 = [] -ml_dsa_87 = [] -default = ["ml_dsa_44", "ml_dsa_65", "ml_dsa_87"] \ No newline at end of file +ML_DSA_44 = [] +ML_DSA_65 = [] +ML_DSA_87 = [] +SLH_DSA_SHA2_128s = [] +SLH_DSA_SHA2_128f = [] +SLH_DSA_SHAKE_128s = [] +default = ["ML_DSA_44", "ML_DSA_65", "ML_DSA_87", "SLH_DSA_SHA2_128s", "SLH_DSA_SHA2_128f", "SLH_DSA_SHAKE_128s"] \ No newline at end of file diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index e1d688f9ad..8ed75fdecf 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -8,7 +8,7 @@ use identity_jose::jws::VerificationInput; use oqs::sig::Algorithm; use oqs::sig::Sig; -/// A verifier that can handle the [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) algorithm. +/// A verifier that can handle the [`Algorithm`] PQC algorithms. #[derive(Debug)] #[non_exhaustive] pub struct OQSVerifier; @@ -20,7 +20,7 @@ impl OQSVerifier { // Obtain an ML-DSA-44 public key. let params: &JwkParamsPQ = public_key - .try_mldsa_params() + .try_pq_params() .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 6f7236100c..619e8f2846 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -25,17 +25,27 @@ impl JwsVerifier for PQCJwsVerifier { /// This implements verification of JWS signatures signed with the /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) /// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) - /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) algorithms. + /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) + /// | [`JwsAlgorithm::SLH_DSA_SHA2_128s`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128s) + /// | [`JwsAlgorithm::SLH_DSA_SHA2_128f`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128f) + /// | [`JwsAlgorithm::SLH_DSA_SHAKE_128s`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHAKE_128s) algorithms. // Allow unused variables in case of no-default-features. #[allow(unused_variables)] fn verify(&self, input: VerificationInput, public_key: &Jwk) -> std::result::Result<(), SignatureVerificationError> { match input.alg { - #[cfg(feature = "ml_dsa_44")] + #[cfg(feature = "ML_DSA_44")] identity_jose::jws::JwsAlgorithm::ML_DSA_44 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium2), - #[cfg(feature = "ml_dsa_65")] + #[cfg(feature = "ML_DSA_65")] identity_jose::jws::JwsAlgorithm::ML_DSA_65 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), - #[cfg(feature = "ml_dsa_87")] + #[cfg(feature = "ML_DSA_87")] identity_jose::jws::JwsAlgorithm::ML_DSA_87 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium5), + + #[cfg(feature = "SLH_DSA_SHA2_128s")] + identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128s => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128sSimple), + #[cfg(feature = "SLH_DSA_SHA2_128f")] + identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128f => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128fSimple), + #[cfg(feature = "SLH_DSA_SHAKE_128s")] + identity_jose::jws::JwsAlgorithm::SLH_DSA_SHAKE_128s => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsShake128sSimple), _ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()), } } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 0fcd88134c..5d2d951c61 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -224,6 +224,10 @@ impl JwkMemStore { /// ML-DSA algorithms key types; pub const ML_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ML_DSA); + const SLH_DSA: &'static str = "SLH-DSA"; + /// SLH-DSA algorithms key types; + pub const SLH_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::SLH_DSA); + } impl MemStoreKeyType { @@ -499,6 +503,9 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::Dilithium3), JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::Dilithium5), + JwsAlgorithm::SLH_DSA_SHA2_128s => Ok(Algorithm::SphincsSha2128sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), + JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -538,6 +545,9 @@ impl JwkStoragePQ for JwkMemStore { JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::SLHDSA), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -550,7 +560,12 @@ impl JwkStoragePQ for JwkMemStore { JwkParams::MLDSA(ref mut params) => { params.public = public; params.private = Some(private); - }, + } + JwkParams::SLHDSA(ref mut params) => { + params.public = public; + params.private = Some(private); + } + , _ => return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) .with_custom_message("Should NOT happen!"), @@ -581,10 +596,13 @@ impl JwkStoragePQ for JwkMemStore { JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) })?; - // Check that `kty` is `ML-DSA`. + let oqs_alg = check_pq_alg_compatibility(alg)?; + + // Check that `kty` is `ML-DSA`or `SLH-DSA`. match alg { - JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 => { - public_key.try_mldsa_params().map_err(|err| { + JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 | + JwsAlgorithm:: SLH_DSA_SHA2_128s | JwsAlgorithm::SLH_DSA_SHA2_128f | JwsAlgorithm::SLH_DSA_SHAKE_128s => { + public_key.try_pq_params().map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) .with_source(err) @@ -603,7 +621,7 @@ impl JwkStoragePQ for JwkMemStore { .get(key_id) .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; - let params = jwk.try_mldsa_params().unwrap(); + let params = jwk.try_pq_params().unwrap(); let sk = params .private @@ -620,18 +638,7 @@ impl JwkStoragePQ for JwkMemStore { oqs::init(); //TODO: check what this function does - let scheme = match alg { - JwsAlgorithm::ML_DSA_44 => Sig::new(Algorithm::Dilithium2), - JwsAlgorithm::ML_DSA_65 => Sig::new(Algorithm::Dilithium3), - JwsAlgorithm::ML_DSA_87 => Sig::new(Algorithm::Dilithium5), - - other => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .with_custom_message(format!("{other} is not supported")), - ); - } - }.map_err(|err| { + let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("signature scheme init failed")) .with_source(err) From 5561a0148a8b3bba41ffa599fc389f5ae066bc53 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 28 Feb 2024 11:12:06 +0100 Subject: [PATCH 035/163] support all FALCON and SLH-DSA algs --- examples/1_advanced/11_pqc.rs | 4 +- identity_jose/src/jwk/key.rs | 6 ++ identity_jose/src/jwk/key_params.rs | 8 ++- identity_jose/src/jwk/key_type.rs | 3 + identity_jose/src/jws/algorithm.rs | 70 ++++++++++++++++++-- identity_pqc_verifier/Cargo.toml | 36 +++++++++- identity_pqc_verifier/src/pqc_verifier.rs | 41 ++++++++++-- identity_storage/src/key_storage/memstore.rs | 37 ++++++++++- 8 files changed, 187 insertions(+), 18 deletions(-) diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs index d089102b78..e23bef2c04 100644 --- a/examples/1_advanced/11_pqc.rs +++ b/examples/1_advanced/11_pqc.rs @@ -51,9 +51,9 @@ use iota_sdk::types::block::output::AliasOutput; use serde_json::json; // The API endpoint of an IOTA node, e.g. Hornet. -const API_ENDPOINT: &str = "http://localhost:14265"; +const API_ENDPOINT: &str = "http://localhost"; // The faucet endpoint allows requesting funds for testing purposes. -const FAUCET_ENDPOINT: &str = "http://localhost:8091/api/enqueue"; +const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithm) -> anyhow::Result<(Address, IotaDocument, String)> { diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index 2a5d51b2fc..95be3a0ca3 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -346,6 +346,7 @@ impl Jwk { match self.params() { JwkParams::MLDSA(params) => Ok(params), JwkParams::SLHDSA(params) => Ok(params), + JwkParams::FALCON(params) => Ok(params), _ => Err(Error::KeyError("PQ")), } } @@ -355,6 +356,7 @@ impl Jwk { match self.params_mut() { JwkParams::MLDSA(params) => Ok(params), JwkParams::SLHDSA(params) => Ok(params), + JwkParams::FALCON(params) => Ok(params), _ => Err(Error::KeyError("PQ")), } } @@ -414,6 +416,9 @@ impl Jwk { JwkParams::SLHDSA(JwkParamsPQ { public, .. }) => { format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) } + JwkParams::FALCON(JwkParamsPQ { public, .. }) => { + format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) + } } } @@ -468,6 +473,7 @@ impl Jwk { JwkParams::Okp(params) => params.is_private(), JwkParams::MLDSA(params) => params.is_private(), //TODO: PQ - is_private Jwk method JwkParams::SLHDSA(params) => params.is_private(), + JwkParams::FALCON(params) => params.is_private(), } } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index c1cc528748..bbc9dbbbef 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -33,7 +33,9 @@ pub enum JwkParams { /// ML-DSA parameters MLDSA(JwkParamsPQ), /// SLH-DSA parameters - SLHDSA(JwkParamsPQ) + SLHDSA(JwkParamsPQ), + /// FALCON parameters + FALCON(JwkParamsPQ), } impl JwkParams { @@ -46,6 +48,7 @@ impl JwkParams { JwkType::Okp => Self::Okp(JwkParamsOkp::new()), JwkType::MLDSA => Self::MLDSA(JwkParamsPQ::new()), JwkType::SLHDSA => Self::SLHDSA(JwkParamsPQ::new()), + JwkType::FALCON => Self::FALCON(JwkParamsPQ::new()), } } @@ -58,6 +61,7 @@ impl JwkParams { Self::Okp(inner) => inner.kty(), Self::MLDSA(_) => JwkType::MLDSA, Self::SLHDSA(_) => JwkType::SLHDSA, + Self::FALCON(_) => JwkType::FALCON, } } @@ -72,6 +76,7 @@ impl JwkParams { Self::Oct(_) => None, Self::MLDSA(inner) => Some(Self::MLDSA(inner.to_public())), Self::SLHDSA(inner) => Some(Self::SLHDSA(inner.to_public())), + Self::FALCON(inner) => Some(Self::FALCON(inner.to_public())), } } @@ -84,6 +89,7 @@ impl JwkParams { Self::Oct(value) => value.is_public(), Self::MLDSA(value) => value.is_public(), Self::SLHDSA(value) => value.is_public(), + Self::FALCON(value) => value.is_public(), } } } diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index 03d371d274..9dd60122a6 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -32,6 +32,8 @@ pub enum JwkType { /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-key-type) #[serde(rename = "SLH-DSA")] SLHDSA, + + FALCON, } impl JwkType { @@ -44,6 +46,7 @@ impl JwkType { Self::Okp => "OKP", Self::MLDSA => "ML-DSA", Self::SLHDSA => "SLH-DSA", + Self::FALCON => "FALCON", } } } diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index f2a8394295..c83b2828be 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -60,17 +60,39 @@ pub enum JwsAlgorithm { #[serde(rename = "ML-DSA-87")] ML_DSA_87, /// JSON Web Signature Algorithm for SLH-DSA-SHA2-128s - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-algorithm-famil) #[serde(rename = "SLH-DSA-SHA2-128s")] SLH_DSA_SHA2_128s, /// JSON Web Signature Algorithm for SLH-DSA-SHAKE-128s - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-algorithm-famil) #[serde(rename = "SLH-DSA-SHAKE-128s")] SLH_DSA_SHAKE_128s, - /// JSON Web Signature Algorithm for ML-DSA-44 - /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) + /// JSON Web Signature Algorithm for SLH-DSA-SHA2-128f + /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-algorithm-famil) #[serde(rename = "SLH-DSA-SHA2-128f")] SLH_DSA_SHA2_128f, + + #[serde(rename = "SLH-DSA-SHAKE-128f")] + SLH_DSA_SHAKE_128f, + #[serde(rename = "SLH-DSA-SHA2-192s")] + SLH_DSA_SHA2_192s, + #[serde(rename = "SLH-DSA-SHAKE-192s")] + SLH_DSA_SHAKE_192s, + #[serde(rename = "SLH-DSA-SHA2-192f")] + SLH_DSA_SHA2_192f, + #[serde(rename = "SLH-DSA-SHAKE-192f")] + SLH_DSA_SHAKE_192f, + #[serde(rename = "SLH-DSA-SHA2-256s")] + SLH_DSA_SHA2_256s, + #[serde(rename = "SLH-DSA-SHAKE-256s")] + SLH_DSA_SHAKE_256s, + #[serde(rename = "SLH-DSA-SHA2-256f")] + SLH_DSA_SHA2_256f, + #[serde(rename = "SLH-DSA-SHAKE-256f")] + SLH_DSA_SHAKE_256f, + + FALCON512, + FALCON1024 } impl JwsAlgorithm { @@ -97,6 +119,20 @@ impl JwsAlgorithm { Self::SLH_DSA_SHA2_128s, Self::SLH_DSA_SHAKE_128s, Self::SLH_DSA_SHA2_128f, + + Self::SLH_DSA_SHAKE_128f, + Self::SLH_DSA_SHA2_192s, + Self::SLH_DSA_SHAKE_192s, + Self::SLH_DSA_SHA2_192f, + Self::SLH_DSA_SHAKE_192f, + Self::SLH_DSA_SHA2_256s, + Self::SLH_DSA_SHAKE_256s, + Self::SLH_DSA_SHA2_256f, + Self::SLH_DSA_SHAKE_256f, + + Self::FALCON512, + Self::FALCON1024, + ]; /// Returns the JWS algorithm as a `str` slice. @@ -123,6 +159,19 @@ impl JwsAlgorithm { Self::SLH_DSA_SHA2_128s => "SLH-DSA-SHA2-128s", Self::SLH_DSA_SHAKE_128s => "SLH-DSA-SHAKE-128s", Self::SLH_DSA_SHA2_128f => "SLH-DSA-SHA2-128f", + + Self::SLH_DSA_SHAKE_128f => "SLH-DSA-SHAKE-128f", + Self::SLH_DSA_SHA2_192s => "SLH-DSA-SHA2-192s", + Self::SLH_DSA_SHAKE_192s => "SLH-DSA-SHAKE-192s", + Self::SLH_DSA_SHA2_192f => "SLH-DSA-SHA2-192f", + Self::SLH_DSA_SHAKE_192f => "SLH-DSA-SHAKE-192f", + Self::SLH_DSA_SHA2_256s => "SLH-DSA-SHA2-256s", + Self::SLH_DSA_SHAKE_256s => "SLH-DSA-SHAKE-256s", + Self::SLH_DSA_SHA2_256f => "SLH-DSA-SHA2-256f", + Self::SLH_DSA_SHAKE_256f => "SLH-DSA-SHAKE-256f", + + Self::FALCON512 => "FALCON512", + Self::FALCON1024 => "FALCON1024", } } } @@ -153,6 +202,19 @@ impl FromStr for JwsAlgorithm { "SLH-DSA-SHA2-128s" => Ok(Self::SLH_DSA_SHA2_128s), "SLH-DSA-SHAKE-128s" => Ok(Self::SLH_DSA_SHAKE_128s), "SLH-DSA-SHA2-128f" => Ok(Self::SLH_DSA_SHA2_128f), + + "SLH-DSA-SHAKE-128f" => Ok(Self::SLH_DSA_SHAKE_128f), + "SLH-DSA-SHA2-192s" => Ok(Self::SLH_DSA_SHA2_192s), + "SLH-DSA-SHAKE-192s" => Ok(Self::SLH_DSA_SHAKE_192s), + "SLH-DSA-SHA2-192f" => Ok(Self::SLH_DSA_SHA2_192f), + "SLH-DSA-SHAKE-192f" => Ok(Self::SLH_DSA_SHAKE_192f), + "SLH-DSA-SHA2-256s" => Ok(Self::SLH_DSA_SHA2_256s), + "SLH-DSA-SHAKE-256s" => Ok(Self::SLH_DSA_SHAKE_256s), + "SLH-DSA-SHA2-256f" => Ok(Self::SLH_DSA_SHA2_256f), + "SLH-DSA-SHAKE-256f" => Ok(Self::SLH_DSA_SHAKE_256f), + + "FALCON512" => Ok(Self::FALCON512), + "FALCON1024" => Ok(Self::FALCON1024), _ => Err(Error::JwsAlgorithmParsingError), } } diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 5896162b9c..0248bb9d1e 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -22,4 +22,38 @@ ML_DSA_87 = [] SLH_DSA_SHA2_128s = [] SLH_DSA_SHA2_128f = [] SLH_DSA_SHAKE_128s = [] -default = ["ML_DSA_44", "ML_DSA_65", "ML_DSA_87", "SLH_DSA_SHA2_128s", "SLH_DSA_SHA2_128f", "SLH_DSA_SHAKE_128s"] \ No newline at end of file + +SLH_DSA_SHAKE_128f = [] +SLH_DSA_SHA2_192s = [] +SLH_DSA_SHAKE_192s = [] +SLH_DSA_SHA2_192f = [] +SLH_DSA_SHAKE_192f = [] +SLH_DSA_SHA2_256s = [] +SLH_DSA_SHAKE_256s = [] +SLH_DSA_SHA2_256f = [] +SLH_DSA_SHAKE_256f = [] + +FALCON512 = [] +FALCON1024 = [] + +default = [ + "ML_DSA_44", + "ML_DSA_65", + "ML_DSA_87", + "SLH_DSA_SHA2_128s", + "SLH_DSA_SHA2_128f", + "SLH_DSA_SHAKE_128s", + + "SLH_DSA_SHAKE_128f", + "SLH_DSA_SHA2_192s", + "SLH_DSA_SHAKE_192s", + "SLH_DSA_SHA2_192f", + "SLH_DSA_SHAKE_192f", + "SLH_DSA_SHA2_256s", + "SLH_DSA_SHAKE_256s", + "SLH_DSA_SHA2_256f", + "SLH_DSA_SHAKE_256f", + + "FALCON512", + "FALCON1024", + ] \ No newline at end of file diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 619e8f2846..035e150aa6 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -1,11 +1,14 @@ use identity_jose::jwk::Jwk; +use identity_jose::jws::JwsAlgorithm; use identity_jose::jws::JwsVerifier; use identity_jose::jws::SignatureVerificationError; use identity_jose::jws::SignatureVerificationErrorKind; use identity_jose::jws::VerificationInput; use oqs::sig::Algorithm; +use crate::OQSVerifier; + /// An implementor of [`JwsVerifier`] that can handle the /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) /// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) @@ -34,18 +37,42 @@ impl JwsVerifier for PQCJwsVerifier { fn verify(&self, input: VerificationInput, public_key: &Jwk) -> std::result::Result<(), SignatureVerificationError> { match input.alg { #[cfg(feature = "ML_DSA_44")] - identity_jose::jws::JwsAlgorithm::ML_DSA_44 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium2), + JwsAlgorithm::ML_DSA_44 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium2), #[cfg(feature = "ML_DSA_65")] - identity_jose::jws::JwsAlgorithm::ML_DSA_65 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), + JwsAlgorithm::ML_DSA_65 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), #[cfg(feature = "ML_DSA_87")] - identity_jose::jws::JwsAlgorithm::ML_DSA_87 => crate::OQSVerifier::verify(input, public_key, Algorithm::Dilithium5), + JwsAlgorithm::ML_DSA_87 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium5), #[cfg(feature = "SLH_DSA_SHA2_128s")] - identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128s => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128sSimple), - #[cfg(feature = "SLH_DSA_SHA2_128f")] - identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128f => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128fSimple), + JwsAlgorithm::SLH_DSA_SHA2_128s => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128sSimple), #[cfg(feature = "SLH_DSA_SHAKE_128s")] - identity_jose::jws::JwsAlgorithm::SLH_DSA_SHAKE_128s => crate::OQSVerifier::verify(input, public_key, Algorithm::SphincsShake128sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_128s => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake128sSimple), + #[cfg(feature = "SLH_DSA_SHA2_128f")] + JwsAlgorithm::SLH_DSA_SHA2_128f => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128fSimple), + + #[cfg(feature = "SLH_DSA_SHAKE_128f")] + JwsAlgorithm::SLH_DSA_SHAKE_128f => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake128fSimple), + #[cfg(feature = "SLH_DSA_SHA2_192s")] + JwsAlgorithm::SLH_DSA_SHA2_192s => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2192sSimple), + #[cfg(feature = "SLH_DSA_SHAKE_192s")] + JwsAlgorithm::SLH_DSA_SHAKE_192s => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake192sSimple), + #[cfg(feature = "SLH_DSA_SHA2_192f")] + JwsAlgorithm::SLH_DSA_SHA2_192f => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2192fSimple), + #[cfg(feature = "SLH_DSA_SHAKE_192f")] + JwsAlgorithm::SLH_DSA_SHAKE_192f => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake192fSimple), + #[cfg(feature = "SLH_DSA_SHA2_256s")] + JwsAlgorithm::SLH_DSA_SHA2_256s => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2256sSimple), + #[cfg(feature = "SLH_DSA_SHAKE_256s")] + JwsAlgorithm::SLH_DSA_SHAKE_256s => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake256sSimple), + #[cfg(feature = "SLH_DSA_SHA2_256f")] + JwsAlgorithm::SLH_DSA_SHA2_256f => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2256fSimple), + #[cfg(feature = "SLH_DSA_SHAKE_256f")] + JwsAlgorithm::SLH_DSA_SHAKE_256f => OQSVerifier::verify(input, public_key, Algorithm::SphincsShake256fSimple), + + #[cfg(feature = "FALCON512")] + JwsAlgorithm::FALCON512 => OQSVerifier::verify(input, public_key, Algorithm::Falcon512), + #[cfg(feature = "FALCON1024")] + JwsAlgorithm::FALCON1024 => OQSVerifier::verify(input, public_key, Algorithm::Falcon1024), _ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()), } } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 5d2d951c61..899809f392 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -506,6 +506,20 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult JwsAlgorithm::SLH_DSA_SHA2_128s => Ok(Algorithm::SphincsSha2128sSimple), JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), + + + JwsAlgorithm::SLH_DSA_SHAKE_128f => Ok(Algorithm::SphincsShake128fSimple), + JwsAlgorithm::SLH_DSA_SHA2_192s => Ok(Algorithm::SphincsSha2192sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_192s => Ok(Algorithm::SphincsShake192sSimple), + JwsAlgorithm::SLH_DSA_SHA2_192f => Ok(Algorithm::SphincsSha2192fSimple), + JwsAlgorithm::SLH_DSA_SHAKE_192f => Ok(Algorithm::SphincsShake192fSimple), + JwsAlgorithm::SLH_DSA_SHA2_256s => Ok(Algorithm::SphincsSha2256sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_256s => Ok(Algorithm::SphincsShake256sSimple), + JwsAlgorithm::SLH_DSA_SHA2_256f => Ok(Algorithm::SphincsSha2256fSimple), + JwsAlgorithm::SLH_DSA_SHAKE_256f => Ok(Algorithm::SphincsShake256fSimple), + + JwsAlgorithm::FALCON512 => Ok(Algorithm::Falcon512), + JwsAlgorithm::FALCON1024 => Ok(Algorithm::Falcon1024), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -541,6 +555,8 @@ impl JwkStoragePQ for JwkMemStore { let public = jwu::encode_b64(pk.into_vec()); let private = jwu::encode_b64(sk.into_vec()); + + let mut jwk_params = match alg { JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), @@ -548,6 +564,17 @@ impl JwkStoragePQ for JwkMemStore { JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::SLHDSA), JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::SLHDSA), JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_128f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_192s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_192s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_192f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_192f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_256s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_256s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_256f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_256f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::FALCON512 => JwkParams::new(JwkType::FALCON), + JwsAlgorithm::FALCON1024 => JwkParams::new(JwkType::FALCON), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -598,10 +625,14 @@ impl JwkStoragePQ for JwkMemStore { let oqs_alg = check_pq_alg_compatibility(alg)?; - // Check that `kty` is `ML-DSA`or `SLH-DSA`. + // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. match alg { - JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 | - JwsAlgorithm:: SLH_DSA_SHA2_128s | JwsAlgorithm::SLH_DSA_SHA2_128f | JwsAlgorithm::SLH_DSA_SHAKE_128s => { + JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 + | JwsAlgorithm::SLH_DSA_SHA2_128s | JwsAlgorithm::SLH_DSA_SHAKE_128s | JwsAlgorithm::SLH_DSA_SHA2_128f + | JwsAlgorithm::SLH_DSA_SHAKE_128f | JwsAlgorithm::SLH_DSA_SHA2_192s | JwsAlgorithm::SLH_DSA_SHAKE_192s + | JwsAlgorithm::SLH_DSA_SHA2_192f | JwsAlgorithm::SLH_DSA_SHAKE_192f | JwsAlgorithm::SLH_DSA_SHA2_256s + | JwsAlgorithm::SLH_DSA_SHAKE_256s | JwsAlgorithm::SLH_DSA_SHA2_256f | JwsAlgorithm::SLH_DSA_SHAKE_256f + | JwsAlgorithm::FALCON512 | JwsAlgorithm::FALCON1024 => { public_key.try_pq_params().map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) From cc0a76e346ccd030cd2fa5ae79f16e21718d4b22 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 29 May 2024 18:57:08 +0200 Subject: [PATCH 036/163] add WebDID, WebDocument and a new Resolver (not working) --- examples/1_advanced/11_did_web.rs | 37 ++ examples/Cargo.toml | 4 + identity_iota_core/src/did/mod.rs | 3 +- identity_iota_core/src/did/web_did.rs | 244 ++++++++++++ .../src/document/iota_document.rs | 2 + identity_iota_core/src/document/mod.rs | 2 + .../src/document/web_document.rs | 347 ++++++++++++++++++ identity_iota_core/src/lib.rs | 1 + identity_resolver/Cargo.toml | 5 + identity_resolver/src/resolution/resolver.rs | 110 ++++++ .../src/storage/jwk_document_ext.rs | 107 ++++++ 11 files changed, 861 insertions(+), 1 deletion(-) create mode 100644 examples/1_advanced/11_did_web.rs create mode 100644 identity_iota_core/src/did/web_did.rs create mode 100644 identity_iota_core/src/document/web_document.rs diff --git a/examples/1_advanced/11_did_web.rs b/examples/1_advanced/11_did_web.rs new file mode 100644 index 0000000000..fa2142d7bf --- /dev/null +++ b/examples/1_advanced/11_did_web.rs @@ -0,0 +1,37 @@ +use std::str::FromStr; + +use examples::MemStorage; +use identity_iota::{core::Url, iota::{WebDID, WebDocument}, resolver::Resolver, storage::{JwkMemStore, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; +use identity_iota::storage::JwkDocumentExt; + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + + // Create a new Web DID document. + let mut document: WebDocument = WebDocument::new("https://cybersecurity-links.github.io/.well-known/did.json")?; + + // Insert a new Ed25519 verification method in the DID document. + let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + document + .generate_method( + &storage, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod, + ) + .await?; + + println!("Web DID Document: {:#}", document); + +// // let web_did = WebDID::from_str("did:web:192.168.1.196%3a3000:.well-known:did.json")?; //THIS MUST FAIL! + + let mut resolver = Resolver::::new(); + resolver.attach_web_handler()?; + + let resolved_document = resolver.resolve(document.id()).await?; + //TODO: fix Document validation, now is accepting everything + println!("Resolved Document: {:#}", resolved_document); + Ok(()) +} \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4e107cb042..a69ab83f2f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -100,3 +100,7 @@ name = "9_zkp" [[example]] path = "1_advanced/10_zkp_revocation.rs" name = "10_zkp_revocation" + +[[example]] +path = "1_advanced/11_did_web.rs" +name = "11_did_web" diff --git a/identity_iota_core/src/did/mod.rs b/identity_iota_core/src/did/mod.rs index 5e95c6ab28..87287b2a8e 100644 --- a/identity_iota_core/src/did/mod.rs +++ b/identity_iota_core/src/did/mod.rs @@ -2,5 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 pub use iota_did::IotaDID; - +pub use web_did::WebDID; mod iota_did; +mod web_did; diff --git a/identity_iota_core/src/did/web_did.rs b/identity_iota_core/src/did/web_did.rs new file mode 100644 index 0000000000..eefff93ccd --- /dev/null +++ b/identity_iota_core/src/did/web_did.rs @@ -0,0 +1,244 @@ +//TODO: Web - WebDID + +use std::{fmt::{Display, Formatter}, str::FromStr}; + +use identity_core::common::Url; +use identity_did::{CoreDID, Error, DID}; +use iota_sdk::utils::serde; +use ref_cast::{ref_cast_custom, RefCastCustom}; +use ::serde::{Deserialize, Serialize}; +use identity_did::Error as DIDError; + +/// Alias for a `Result` with the error type [`DIDError`]. +type Result = std::result::Result; + +/// A DID conforming to the Web DID method specification. +/// +/// This is a thin wrapper around the [`DID`][`CoreDID`] type from the +/// [`identity_did`][`identity_did`] crate. +#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, RefCastCustom)] +#[repr(transparent)] +#[serde(into = "CoreDID", try_from = "CoreDID")] +pub struct WebDID(CoreDID); + +impl WebDID { + /// The URL scheme for Decentralized Identifiers. + pub const SCHEME: &'static str = CoreDID::SCHEME; + + /// The IOTA DID method name (`"iota"`). + pub const METHOD: &'static str = "web"; + + /// Convert a `CoreDID` reference to an `WebDID` reference without checking the referenced value. + /// + /// # Warning + /// This method should only be called on [`CoreDIDs`](CoreDID) that + /// are known to satisfy the requirements of the Web DID Method specification. + /// + /// # Memory safety + /// + /// The `ref-cast` crate ensures a memory safe implementation. + #[ref_cast_custom] + pub(crate) const fn from_inner_ref_unchecked(did: &CoreDID) -> &Self; + + /// Create a new valid Web DID. + pub fn new(url: &str) -> Result { + let parsed_url: Url = Url::parse(url).map_err(|_| Error::Other("Not a valid Url"))?; + + // Extract the domain and path + if let Some(domain) = parsed_url.domain() { + + let port = parsed_url.port().map_or(String::new(), |p| format!("%3a{}", p)); + + + let path = parsed_url.path_segments().map_or(String::new(), |p| { + format!("{}{}", ":", p.into_iter().collect::>().join(":")) + }); + + let did_web = format!("did:{}:{}{}{}", Self::METHOD, domain, port, path); + println!("DID Web: {}", did_web); + let core_did = CoreDID::parse(did_web).map_err(|_| Error::Other("Cannot convert to CoreDID"))?; + println!("{}",core_did); + Ok(Self(core_did)) + + } else { + return Err(Error::InvalidMethodId); + } + } + + /// Parses an [`WebDID`] from the given `input`. + /// + /// # Errors + /// + /// Returns `Err` if the input does not conform to the [`WebDID`] specification. + pub fn parse(input: impl AsRef) -> Result { + CoreDID::parse(input.as_ref().to_lowercase()).and_then(Self::try_from_core) + } + + /// Converts a [`WebDID`] to a [`Url`] + pub fn to_url(&self) -> Url { + //This is safe because if i have constructed a WebDID I already know it is valid + WebDID::check_validity(self).unwrap() + } + + + /// Converts a [`CoreDID`] to a [`WebDID`]. + /// + /// # Errors + /// + /// Returns `Err` if the input does not conform to the [`IotaDID`] specification. + pub fn try_from_core(did: CoreDID) -> Result { + Self::check_validity(&did)?; + Ok(Self(did)) + } + + // =========================================================================== + // Validation + // =========================================================================== + + /// Checks if the given `DID` is syntactically valid according to the [`WebDID`] method specification. + /// + /// # Errors + /// + /// Returns the corresponding [`Url`] or `Err` if the input is not a syntactically valid [`WebDID`]. + pub fn check_validity(did: &D) -> Result { + Self::check_method(did) + .and_then(|_| Self::check_method_id(did)) + } + + /// Returns a `bool` indicating if the given `DID` is valid according to the + /// [`WebDID`] method specification. + /// + /// Equivalent to `WebDID::check_validity(did).is_ok()`. + pub fn is_valid(did: &CoreDID) -> bool { + Self::check_validity(did).is_ok() + } + + + // =========================================================================== + // Helpers + // =========================================================================== + + /// Checks if the given `DID` has a valid [`WebDID`] `method` (i.e. `"web"`). + /// + /// # Errors + /// + /// Returns `Err` if the input represents another method. + fn check_method(did: &D) -> Result<()> { + (did.method() == Self::METHOD) + .then_some(()) + .ok_or(DIDError::InvalidMethodName) + } + + /// Checks if the given `DID` has a valid [`IotaDID`] `method_id`. + /// + /// # Errors + /// + /// Returns `Err` if the input does not have a [`IotaDID`] compliant method id. + fn check_method_id(did: &D) -> Result { + let (domain, port, path) = Self::denormalized_components(did.method_id()); + + let port = port.map(|p| u16::from_str(&p)) + .map_or(Ok(None), |r| r.map(Some) + .map_err(|_| Error::InvalidMethodId))?; + + let mut url = Url::parse(&format!("https://{}", domain)) + .map_err(|_| Error::InvalidMethodId)?; + + url.set_port(port).map_err(|_| Error::InvalidMethodId)?; + + path.and_then(|p| Some(url.set_path(&p))); + + url.domain().ok_or(Error::InvalidMethodId)?; + + Ok(url) + } + + // did:web:cybersecurity-links.github.io%3A3000:did-web-server:.well-known:did.json + + /// cybersecurity-links.github.io%3A3000:did-web-server:.well-known:did.json -> https:://cybersecurity-links.github.io:3000/did-web-server/.well-known/did.json + #[inline(always)] + fn denormalized_components(input: &str) -> (String, Option, Option) { + + match input.find("%3a") { + Some(i) => { + let (domain, tail) = input.split_at(i); + match tail.find(":") { + Some(i) => { + let (port, path) = tail.split_at(i); + (domain.to_owned(), Some(port[3..].to_owned()), Some(path.replace(":", "/"))) + }, + None => (domain.to_owned(), Some(tail[3..].to_owned()), None), + } + + }, + None => { + match input.find(":") { + Some(i) => { + let (domain, path) = input.split_at(i); + (domain.to_owned(), None, Some(path.replace(":", "/"))) + }, + None => (input.to_owned(), None, None), + } + } + } + + } + +} + +impl From for CoreDID { + fn from(id: WebDID) -> Self { + id.0 + } +} + +impl TryFrom for WebDID { + type Error = DIDError; + + fn try_from(value: CoreDID) -> std::result::Result { + Self::try_from_core(value) + } +} + +impl FromStr for WebDID { + type Err = DIDError; + + fn from_str(s: &str) -> std::result::Result { + Self::parse(s) + } +} + +impl From for String { + fn from(did: WebDID) -> Self { + did.into_string() + } +} + + +impl TryFrom<&str> for WebDID { + type Error = DIDError; + + fn try_from(other: &str) -> std::result::Result { + Self::parse(other) + } +} + +impl TryFrom for WebDID { + type Error = DIDError; + + fn try_from(other: String) -> std::result::Result { + Self::parse(other) + } +} + +impl Display for WebDID { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl AsRef for WebDID { + fn as_ref(&self) -> &CoreDID { + &self.0 + } +} \ No newline at end of file diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs index 7ae60381d7..5ab8b2be8e 100644 --- a/identity_iota_core/src/document/iota_document.rs +++ b/identity_iota_core/src/document/iota_document.rs @@ -512,6 +512,8 @@ impl AsRef for IotaDocument { } } + +//TODO: Web DID - how to handle revocation? I think this code should be repeated for WebDocument. This can be avoided? #[cfg(feature = "revocation-bitmap")] mod iota_document_revocation { use identity_credential::revocation::RevocationDocumentExt; diff --git a/identity_iota_core/src/document/mod.rs b/identity_iota_core/src/document/mod.rs index 0c8668c74c..39492a0ab7 100644 --- a/identity_iota_core/src/document/mod.rs +++ b/identity_iota_core/src/document/mod.rs @@ -3,9 +3,11 @@ pub use iota_document::IotaDocument; pub use iota_document_metadata::IotaDocumentMetadata; +pub use web_document::WebDocument; mod iota_document; mod iota_document_metadata; +mod web_document; #[cfg(test)] pub(crate) mod test_utils; diff --git a/identity_iota_core/src/document/web_document.rs b/identity_iota_core/src/document/web_document.rs new file mode 100644 index 0000000000..4c551e5b5e --- /dev/null +++ b/identity_iota_core/src/document/web_document.rs @@ -0,0 +1,347 @@ +use core::fmt; +use core::fmt::Debug; +use core::fmt::Display; +use identity_credential::credential::Jws; +use identity_did::CoreDID; +use identity_did::DIDUrl; +use identity_document::verifiable::JwsVerificationOptions; +use identity_verification::jose::jws::DecodedJws; +use identity_verification::jose::jws::JwsVerifier; +use serde::Deserialize; +use serde::Serialize; + +use identity_core::common::Object; +use identity_core::common::OneOrSet; +use identity_core::common::OrderedSet; +use identity_core::common::Url; +use identity_core::convert::FmtJson; +use identity_document::document::CoreDocument; +use identity_document::service::Service; +use identity_document::utils::DIDUrlQuery; +use identity_verification::MethodRelationship; +use identity_verification::MethodScope; +use identity_verification::VerificationMethod; + +use crate::error::Result; +use crate::Error; +use crate::IotaDID; +use crate::IotaDocumentMetadata; +use crate::NetworkName; +use crate::StateMetadataDocument; +use crate::StateMetadataEncoding; +use crate::WebDID; + + + + + +/// A DID Document adhering to the Web DID method specification. +/// +/// This extends [`CoreDocument`]. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct WebDocument(CoreDocument); + +impl WebDocument { + // =========================================================================== + // Constructors + // =========================================================================== + + /// Constructs an empty DID Document with a [`WebDID`] identifier. + pub fn new(url: &str) -> Result { + Ok(Self::new_with_id(WebDID::new(url).map_err(|e| Error::DIDSyntaxError(e))?)) + } + + /// Constructs an empty DID Document with the given identifier. + pub fn new_with_id(id: WebDID) -> Self { + // PANIC: constructing an empty DID Document is infallible, caught by tests otherwise. + let document: CoreDocument = CoreDocument::builder(Object::default()) + .id(id.into()) + .build() + .expect("empty IotaDocument constructor failed"); + let metadata: IotaDocumentMetadata = IotaDocumentMetadata::new(); + Self(document) + } + + // =========================================================================== + // Properties + // =========================================================================== + + /// Returns the DID document identifier. + pub fn id(&self) -> &WebDID { + // CORRECTNESS: This cast is OK because the public API does not expose methods + // enabling unchecked mutation of the `id` field. + WebDID::from_inner_ref_unchecked(self.0.id()) + } + + /// Returns an iterator yielding the DID controllers. + pub fn controller(&self) -> impl Iterator + '_ { + let core_did_controller_iter = self + .0 + .controller() + .map(|controllers| controllers.iter()) + .into_iter() + .flatten(); + + // CORRECTNESS: These casts are OK because the public API only allows setting WebDIDs. + core_did_controller_iter.map(WebDID::from_inner_ref_unchecked) + } + + /// Sets the value of the document controller. + /// + /// Note: + /// * Duplicates in `controller` will be ignored. + /// * Use an empty collection to clear all controllers. + pub fn set_controller(&mut self, controller: T) + where + T: IntoIterator, + { + let controller_core_dids: Option> = { + let controller_set: OrderedSet = controller.into_iter().map(CoreDID::from).collect(); + if controller_set.is_empty() { + None + } else { + Some(OneOrSet::new_set(controller_set).expect("controller is checked to be not empty")) + } + }; + + *self.0.controller_mut() = controller_core_dids; + } + + /// Returns a reference to the `alsoKnownAs` set. + pub fn also_known_as(&self) -> &OrderedSet { + self.0.also_known_as() + } + + /// Returns a mutable reference to the `alsoKnownAs` set. + pub fn also_known_as_mut(&mut self) -> &mut OrderedSet { + self.0.also_known_as_mut() + } + + /// Returns a reference to the underlying [`CoreDocument`]. + pub fn core_document(&self) -> &CoreDocument { + &self.0 + } + + /// Returns a mutable reference to the underlying [`CoreDocument`]. + /// + /// WARNING: Mutating the inner document directly bypasses checks and + /// may have undesired consequences. + pub(crate) fn core_document_mut(&mut self) -> &mut CoreDocument { + &mut self.0 + } + + /// Returns a reference to the custom DID Document properties. + pub fn properties(&self) -> &Object { + self.0.properties() + } + + /// Returns a mutable reference to the custom DID Document properties. + /// + /// # Warning + /// + /// The properties returned are not checked against the standard fields in a [`CoreDocument`]. Incautious use can have + /// undesired consequences such as key collision when attempting to serialize the document or distinct resources (such + /// as services and methods) being identified by the same DID URL. + pub fn properties_mut_unchecked(&mut self) -> &mut Object { + self.0.properties_mut_unchecked() + } + + // =========================================================================== + // Services + // =========================================================================== + + /// Return a set of all [`Service`]s in the document. + pub fn service(&self) -> &OrderedSet { + self.0.service() + } + + /// Add a new [`Service`] to the document. + /// + /// # Errors + /// An error is returned if there already exists a service or (verification) method with + /// the same identifier in the document. + pub fn insert_service(&mut self, service: Service) -> Result<()> { + self + .core_document_mut() + .insert_service(service) + .map_err(Error::InvalidDoc) + } + + /// Remove and return the [`Service`] identified by the given [`DIDUrl`] from the document. + /// + /// `None` is returned if the service does not exist in the document. + pub fn remove_service(&mut self, did_url: &DIDUrl) -> Option { + self.core_document_mut().remove_service(did_url) + } + + // =========================================================================== + // Verification Methods + // =========================================================================== + + /// Returns a `Vec` of verification method references whose verification relationship matches `scope`. + /// + /// If `scope` is `None`, all **embedded** methods are returned. + pub fn methods(&self, scope: Option) -> Vec<&VerificationMethod> { + self.0.methods(scope) + } + + /// Adds a new [`VerificationMethod`] to the document in the given [`MethodScope`]. + /// + /// # Errors + /// + /// Returns an error if a method with the same fragment already exists. + pub fn insert_method(&mut self, method: VerificationMethod, scope: MethodScope) -> Result<()> { + self + .core_document_mut() + .insert_method(method, scope) + .map_err(Error::InvalidDoc) + } + + /// Removes and returns the [`VerificationMethod`] identified by `did_url` from the document. + /// + /// # Note + /// + /// All _references to the method_ found in the document will be removed. + /// This includes cases where the reference is to a method contained in another DID document. + pub fn remove_method(&mut self, did_url: &DIDUrl) -> Option { + self.core_document_mut().remove_method(did_url) + } + + /// Removes and returns the [`VerificationMethod`] from the document. The [`MethodScope`] under which the method was + /// found is appended to the second position of the returned tuple. + /// + /// # Note + /// + /// All _references to the method_ found in the document will be removed. + /// This includes cases where the reference is to a method contained in another DID document. + pub fn remove_method_and_scope(&mut self, did_url: &DIDUrl) -> Option<(VerificationMethod, MethodScope)> { + self.core_document_mut().remove_method_and_scope(did_url) + } + + /// Attaches the relationship to the method resolved by `method_query`. + /// + /// # Errors + /// + /// Returns an error if the method does not exist or if it is embedded. + /// To convert an embedded method into a generic verification method, remove it first + /// and insert it with [`MethodScope::VerificationMethod`]. + pub fn attach_method_relationship<'query, Q>( + &mut self, + method_query: Q, + relationship: MethodRelationship, + ) -> Result + where + Q: Into>, + { + self + .core_document_mut() + .attach_method_relationship(method_query, relationship) + .map_err(Error::InvalidDoc) + } + + /// Detaches the `relationship` from the method identified by `did_url`. + /// Returns `true` if the relationship was found and removed, `false` otherwise. + /// + /// # Errors + /// + /// Returns an error if the method does not exist or is embedded. + /// To remove an embedded method, use [`Self::remove_method`]. + /// + /// # Note + /// + /// If the method is referenced in the given scope, but the document does not contain the referenced verification + /// method, then the reference will persist in the document (i.e. it is not removed). + pub fn detach_method_relationship<'query, Q>( + &mut self, + method_query: Q, + relationship: MethodRelationship, + ) -> Result + where + Q: Into>, + { + self + .core_document_mut() + .detach_method_relationship(method_query, relationship) + .map_err(Error::InvalidDoc) + } + + /// Returns the first [`VerificationMethod`] with an `id` property matching the + /// provided `method_query` and the verification relationship specified by `scope` if present. + /// + /// # Warning + /// + /// Incorrect use of this method can lead to distinct document resources being identified by the same DID URL. + pub fn resolve_method_mut<'query, Q>( + &mut self, + method_query: Q, + scope: Option, + ) -> Option<&mut VerificationMethod> + where + Q: Into>, + { + self.0.resolve_method_mut(method_query, scope) + } + + /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. + // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains + // services whose ids are of the form #. + pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> + where + Q: Into>, + { + self.0.resolve_service(service_query) + } + + /// Returns the first [`VerificationMethod`] with an `id` property matching the + /// provided `method_query` and the verification relationship specified by `scope` if present. + // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains methods + // whose ids are of the form #. + pub fn resolve_method<'query, 'me, Q>( + &'me self, + method_query: Q, + scope: Option, + ) -> Option<&VerificationMethod> + where + Q: Into>, + { + self.0.resolve_method(method_query, scope) + } + + // =========================================================================== + // Signatures + // =========================================================================== + + /// Decodes and verifies the provided JWS according to the passed [`JwsVerificationOptions`] and + /// [`JwsVerifier`]. + /// + /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to + /// take place. + /// - The JWS must be encoded according to the JWS compact serialization. + /// - The `kid` value in the protected header must be an identifier of a verification method in this DID document. + pub fn verify_jws<'jws, T: JwsVerifier>( + &self, + jws: &'jws Jws, + detached_payload: Option<&'jws [u8]>, + signature_verifier: &T, + options: &JwsVerificationOptions, + ) -> Result> { + self + .core_document() + .verify_jws(jws.as_str(), detached_payload, signature_verifier, options) + .map_err(Error::JwsVerificationError) + } + + +} + +impl AsRef for WebDocument { + fn as_ref(&self) -> &CoreDocument { + &self.0 + } +} + +impl Display for WebDocument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_json(f) + } + } \ No newline at end of file diff --git a/identity_iota_core/src/lib.rs b/identity_iota_core/src/lib.rs index 6602fb10ba..d0e2d58f34 100644 --- a/identity_iota_core/src/lib.rs +++ b/identity_iota_core/src/lib.rs @@ -26,6 +26,7 @@ pub mod block { #[cfg(feature = "client")] pub use client::*; pub use did::IotaDID; +pub use did::WebDID; //TODO: Web DID - lib pub use document::*; pub use network::NetworkName; pub use state_metadata::*; diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index d176fc5437..eb0ed02981 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -23,6 +23,11 @@ serde = { version = "1.0", default-features = false, features = ["std", "derive" strum.workspace = true thiserror = { version = "1.0", default-features = false } +hickory-resolver = { version = "0.24.1", default-features = false, features = ["dns-over-https-rustls", "native-certs"]} +reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} +once_cell = "1.19.0" + + [dependencies.identity_iota_core] version = "=1.2.0" path = "../identity_iota_core" diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index b8ceffbc7f..83f8f80f1a 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -334,6 +334,116 @@ mod iota_handler { } } +//TODO: Web - resolver handler +// #[cfg(feature = "web")] +mod web_handler { + use crate::ErrorCause; + + use super::Resolver; + use hickory_resolver::config::ResolverConfig; +use hickory_resolver::config::ResolverOpts; +use hickory_resolver::TokioAsyncResolver; +use identity_document::document::CoreDocument; + use identity_iota_core::IotaDID; + use identity_iota_core::IotaDocument; + use identity_iota_core::IotaIdentityClientExt; + use identity_iota_core::WebDID; +use identity_iota_core::WebDocument; +use once_cell::sync::OnceCell; +use reqwest::dns::Addrs; +use reqwest::ClientBuilder; +use reqwest::IntoUrl; + use std::collections::HashMap; + use std::io; +use std::net::SocketAddr; +use std::str::FromStr; +use std::sync::Arc; +use crate::Error; +use crate::Result; + + + /// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. + #[derive(Debug, Default, Clone)] + pub(crate) struct DnsOverHttpsResolver { + /// Since we might not have been called in the context of a + /// Tokio Runtime in initialization, so we must delay the actual + /// construction of the resolver. + state: Arc>, + } + + impl DnsOverHttpsResolver { + fn new_https_resolver()-> io::Result { + let mut opt = ResolverOpts::default(); + opt.validate=true; + Ok(TokioAsyncResolver::tokio( + ResolverConfig::cloudflare_https(), + opt + )) + } + } + + impl reqwest::dns::Resolve for DnsOverHttpsResolver { + fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving { + let resolver = self.clone(); + Box::pin(async move { + let resolver = resolver.state.get_or_try_init(DnsOverHttpsResolver::new_https_resolver)?; + let lookup = resolver.lookup_ip(name.as_str()).await?; + let addrs: Addrs = Box::new( + lookup.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)) + ); + Ok(addrs) + }) + } + } + + + impl Resolver + where + DOC: From + AsRef + 'static, + { + /// Convenience method for attaching a new handler responsible for resolving Web DIDs. + /// + /// See also [`attach_handler`](Self::attach_handler). + pub fn attach_web_handler(&mut self) -> Result<(), Error> + { + + //TODO: to be moved in a folder identity_web with all the other Web DID Method related structures and functionalities. + // Construct a new Resolver with default configuration options + let resolver = DnsOverHttpsResolver::default(); + + let client = ClientBuilder::new() + .use_rustls_tls() + .dns_resolver(resolver.into()) + .build() + .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))?; + + + + let handler = move |did: WebDID| { + let future_client = client.clone(); + async move { + let did_url = reqwest::Url::from_str(&did.to_url().to_string()) + .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))?; + + future_client.get(did_url) + .send() + .await + .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))? + .json::() + .await + .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) })) + + } + }; + + self.attach_handler(WebDID::METHOD.to_owned(), handler); + Ok(()) + } + + } +} + + impl Default for Resolver where CMD: for<'r> Command<'r, Result>, diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index f9ee100986..b14fe038b6 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -142,6 +142,10 @@ mod private { impl Sealed for identity_document::document::CoreDocument {} #[cfg(feature = "iota-document")] impl Sealed for identity_iota_core::IotaDocument {} + + //TODO: Web DID - Sealed + // #[cfg(feature = "web-document")] + impl Sealed for identity_iota_core::WebDocument {} } // ==================================================================================================================== @@ -628,3 +632,106 @@ mod iota_document { } } } + + +// ==================================================================================================================== +// WebDocument +// ==================================================================================================================== +// #[cfg(feature = "web-document")] +// TODO: Web DID - JwkDocumentExt for WebDocument +mod web_document { + use super::*; + use identity_credential::credential::Jwt; + use identity_iota_core::WebDocument; + + generate_method_for_document_type!( + WebDocument, + JwsAlgorithm, + JwkStorage, + JwkStorage::generate, + generate_method_iota_document + ); + purge_method_for_document_type!(WebDocument, purge_method_iota_document); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwkDocumentExt for WebDocument { + async fn generate_method( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage, + I: KeyIdStorage, + { + generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await + } + + async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> + where + K: JwkStorage, + I: KeyIdStorage, + { + purge_method_iota_document(self, storage, id).await + } + + async fn create_jws( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStorage, + I: KeyIdStorage, + { + self + .core_document() + .create_jws(storage, fragment, payload, options) + .await + } + + async fn create_credential_jwt( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorage, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + self + .core_document() + .create_credential_jwt(credential, storage, fragment, options, custom_claims) + .await + } + async fn create_presentation_jwt( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStorage, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, + { + self + .core_document() + .create_presentation_jwt(presentation, storage, fragment, options, jwt_options) + .await + } + } +} From 91efeb6b5a9cdf655facc8e1d0ddf3151bcb06b5 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 6 Jun 2024 14:02:34 +0200 Subject: [PATCH 037/163] add identity_did_methods module --- Cargo.toml | 3 +- examples/1_advanced/11_did_web.rs | 29 +++- examples/Cargo.toml | 3 +- identity_did_methods/Cargo.toml | 43 +++++ identity_did_methods/README.md | 6 + identity_did_methods/src/did_key/mod.rs | 0 identity_did_methods/src/did_web/client.rs | 160 ++++++++++++++++++ identity_did_methods/src/did_web/mod.rs | 10 ++ .../src/did_web}/web_did.rs | 5 +- .../src/did_web}/web_document.rs | 27 +-- identity_did_methods/src/errors.rs | 26 +++ identity_did_methods/src/lib.rs | 10 ++ identity_iota/Cargo.toml | 4 + identity_iota/src/lib.rs | 12 ++ identity_iota_core/src/did/mod.rs | 3 +- identity_iota_core/src/document/mod.rs | 2 - identity_iota_core/src/lib.rs | 1 - identity_resolver/Cargo.toml | 15 +- identity_resolver/src/resolution/resolver.rs | 84 +-------- identity_storage/Cargo.toml | 5 +- .../src/storage/jwk_document_ext.rs | 9 +- 21 files changed, 343 insertions(+), 114 deletions(-) create mode 100644 identity_did_methods/Cargo.toml create mode 100644 identity_did_methods/README.md create mode 100644 identity_did_methods/src/did_key/mod.rs create mode 100644 identity_did_methods/src/did_web/client.rs create mode 100644 identity_did_methods/src/did_web/mod.rs rename {identity_iota_core/src/did => identity_did_methods/src/did_web}/web_did.rs (97%) rename {identity_iota_core/src/document => identity_did_methods/src/did_web}/web_document.rs (94%) create mode 100644 identity_did_methods/src/errors.rs create mode 100644 identity_did_methods/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index a0375aa810..736931c671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ members = [ "identity_jose", "identity_ecdsa_verifier", "identity_eddsa_verifier", - "examples", + "identity_did_methods", + "examples", "identity_did_methods", ] exclude = ["bindings/wasm", "bindings/grpc"] diff --git a/examples/1_advanced/11_did_web.rs b/examples/1_advanced/11_did_web.rs index fa2142d7bf..b9c11b0b98 100644 --- a/examples/1_advanced/11_did_web.rs +++ b/examples/1_advanced/11_did_web.rs @@ -1,15 +1,26 @@ -use std::str::FromStr; - use examples::MemStorage; -use identity_iota::{core::Url, iota::{WebDID, WebDocument}, resolver::Resolver, storage::{JwkMemStore, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; +use identity_iota::{core::Url, document::CoreDocument, resolver::Resolver, storage::{JwkMemStore, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}, web::{WebClient, WebClientBuilder, WebDocument}}; use identity_iota::storage::JwkDocumentExt; +use reqwest::{Certificate, ClientBuilder}; +use tokio::{fs::File, io::AsyncReadExt}; #[tokio::main] async fn main() -> anyhow::Result<()> { + let did_url: &str = "https://localhost:4443/.well-known/did.json"; + let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; + + // Create a new client to make HTTPS requests. + // let client = WebClient::default()?; + let client= WebClient::new(ClientBuilder::new() + .danger_accept_invalid_certs(true) //TODO: fix problem cannot build WebClient after calling function of inner structure + .build()?); + // Create a new Web DID document. - let mut document: WebDocument = WebDocument::new("https://cybersecurity-links.github.io/.well-known/did.json")?; + let mut document: WebDocument = WebDocument::new(did_url)?; + // let doc: WebDocument = client.get(Url::parse(did_url)?).send().await?.json().await?; + // println!("PPPPP: {}", doc); // Insert a new Ed25519 verification method in the DID document. let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); @@ -23,15 +34,19 @@ async fn main() -> anyhow::Result<()> { ) .await?; + document.write_to_file(Some(path_did_file))?; println!("Web DID Document: {:#}", document); -// // let web_did = WebDID::from_str("did:web:192.168.1.196%3a3000:.well-known:did.json")?; //THIS MUST FAIL! + + // let web_did = WebDID::from_str("did:web:192.168.1.196%3a3000:.well-known:did.json")?; //THIS MUST FAIL! + let mut resolver = Resolver::::new(); - resolver.attach_web_handler()?; + resolver.attach_web_handler(client)?; let resolved_document = resolver.resolve(document.id()).await?; - //TODO: fix Document validation, now is accepting everything println!("Resolved Document: {:#}", resolved_document); + + Ok(()) } \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a69ab83f2f..7812e9f4a6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,9 +8,10 @@ publish = false [dependencies] anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false } -identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus"] } +identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "resolver", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "web-method"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } +reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} json-proof-token.workspace = true primitive-types = "0.12.1" rand = "0.8.5" diff --git a/identity_did_methods/Cargo.toml b/identity_did_methods/Cargo.toml new file mode 100644 index 0000000000..088456bde3 --- /dev/null +++ b/identity_did_methods/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "identity_did_methods" +version = "1.2.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +keywords = ["iota", "web", "identity"] +license.workspace = true +readme = "./README.md" +repository.workspace = true +rust-version.workspace = true +description = "An integration for the Web DID Method." + + +[dependencies] +identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } +identity_credential = { version = "=1.2.0", path = "../identity_credential", default-features = false, features = ["validator"] } +ref-cast = { version = "1.0.14", default-features = false } +hickory-resolver = { version = "0.24.1", default-features = false, features = ["dns-over-https-rustls", "native-certs"]} +reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} +once_cell = "1.19.0" +serde.workspace = true +serde_json.workspace = true +strum.workspace = true +thiserror.workspace = true + + +[package.metadata.docs.rs] +# To build locally: +# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["key", "web"] +key = [] +web = [] + +[lints] +workspace = true diff --git a/identity_did_methods/README.md b/identity_did_methods/README.md new file mode 100644 index 0000000000..7449f223ee --- /dev/null +++ b/identity_did_methods/README.md @@ -0,0 +1,6 @@ +IOTA Identity +=== + +This crate provides the core data structures for: +* [Web DID Method Specification](https://w3c-ccg.github.io/did-method-web/). +* [Key DID Method Specification](https://w3c-ccg.github.io/did-method-key/). \ No newline at end of file diff --git a/identity_did_methods/src/did_key/mod.rs b/identity_did_methods/src/did_key/mod.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/identity_did_methods/src/did_web/client.rs b/identity_did_methods/src/did_web/client.rs new file mode 100644 index 0000000000..554456fbfa --- /dev/null +++ b/identity_did_methods/src/did_web/client.rs @@ -0,0 +1,160 @@ +use std::{fs::File, io::{self, Read}, net::SocketAddr, sync::Arc}; +use hickory_resolver::{config::{ResolverConfig, ResolverOpts}, TokioAsyncResolver}; +use once_cell::sync::OnceCell; +use reqwest::{dns::Addrs, Certificate, Client, ClientBuilder, RequestBuilder}; + +use crate::Error; +use crate::Result; + +/// A `WebClientBuilder` (wrapper to `reqwest::ClientBuilder`) can be used to create a `WebClient` with custom configuration. +#[must_use] +pub struct WebClientBuilder{ + inner: ClientBuilder +} + +impl WebClientBuilder { + /// Constructs a new `WebClientBuilder`. + /// This is the same as `WebClient::builder()`. + pub fn new() -> Self { + Self { inner: ClientBuilder::new() } + } + + pub fn from(builder: ClientBuilder) -> Self { + Self { inner: builder } + } + + pub fn add_root_certificate_pem(self, cert_path: &str) -> Result{ + let mut buf = Vec::new(); + File::open(cert_path) + .map_err(|_| Error::AddRootCertificateError)? + .read_to_end(&mut buf) + .map_err(|_| Error::AddRootCertificateError)?; + + let cert = Certificate::from_pem(&buf).map_err(Error::WebClientBuildError)?; + Ok(Self{inner: self.inner.add_root_certificate(cert)}) + } + + pub fn add_root_certificate_der(self, cert_path: &str) -> Result{ + let mut buf = Vec::new(); + File::open(cert_path) + .map_err(|_| Error::AddRootCertificateError)? + .read_to_end(&mut buf) + .map_err(|_| Error::AddRootCertificateError)?; + + let cert = Certificate::from_der(&buf).map_err(Error::WebClientBuildError)?; + Ok(Self{inner: self.inner.add_root_certificate(cert)}) + } + + /// Returns a `WebClient` that uses this `WebClientBuilder` configuration. + pub fn build(self) -> Result { + let client = self.inner.build().map_err(|e| Error::WebClientBuildError(e) )?; + Ok(WebClient{ inner: client }) + } +} + +impl std::ops::Deref for WebClientBuilder { + type Target = ClientBuilder; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for WebClientBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// An asynchronous `WebClient` to make Requests with. +/// The `WebClient` is a wrapper to `reqwest::Client`, so has various configuration values to tweak, but the defaults are set to what is usually the most commonly desired value for DID Web Resolve. +/// To configure a `WebClient`, use `WebClient::builder()`. +#[derive(Clone)] +pub struct WebClient{ + inner: Client +} + +impl WebClient { + + pub fn new(client: Client) -> Self { + Self { inner: client } + } + + /// Constructs a new `WebClient` with the defaul configuration using a DNS over HTTPS resolver. + pub fn default() -> Result { + // Construct a new Resolver with default configuration options + let resolver = DnsOverHttpsResolver::default(); + + let client = ClientBuilder::new() + .use_rustls_tls() + .dns_resolver(resolver.into()) + .build() + .map_err(|e| Error::WebClientBuildError(e) )?; + + Ok(Self { inner: client }) + } + + /// Creates a `WebClientBuilder` to configure a `WebClient`. + /// + /// This is the same as `WebClientBuilder::new()`. + pub fn builder() -> WebClientBuilder { + WebClientBuilder::new() + } + + /// Convenience method to make a GET request to a URL of type `identity_core::common::Url`. + pub fn get(&self, url: identity_core::common::Url) -> RequestBuilder { + self.inner.get(url.as_ref()) + } + +} + + +impl std::ops::Deref for WebClient { + type Target = Client; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for WebClient { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + + + +/// DNS over HTTPS resolver, which implements the `reqwest::dns::Resolve` trait. +#[derive(Debug, Default, Clone)] +pub(crate) struct DnsOverHttpsResolver { + /// Since we might not have been called in the context of a + /// Tokio Runtime in initialization, so we must delay the actual + /// construction of the resolver. + state: Arc>, +} + +impl DnsOverHttpsResolver { + fn new_https_resolver()-> io::Result { + let mut opt = ResolverOpts::default(); + opt.validate=true; + Ok(TokioAsyncResolver::tokio( + ResolverConfig::cloudflare_https(), + opt + )) + } +} + +impl reqwest::dns::Resolve for DnsOverHttpsResolver { + fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving { + let resolver = self.clone(); + Box::pin(async move { + let resolver = resolver.state.get_or_try_init(DnsOverHttpsResolver::new_https_resolver)?; + let lookup = resolver.lookup_ip(name.as_str()).await?; + let addrs: Addrs = Box::new( + lookup.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)) + ); + Ok(addrs) + }) + } +} diff --git a/identity_did_methods/src/did_web/mod.rs b/identity_did_methods/src/did_web/mod.rs new file mode 100644 index 0000000000..3af5bdb27c --- /dev/null +++ b/identity_did_methods/src/did_web/mod.rs @@ -0,0 +1,10 @@ +pub use web_did::WebDID; +pub use web_document::WebDocument; +pub use client::WebClient; +pub use client::WebClientBuilder; + + + +mod web_did; +mod web_document; +mod client; \ No newline at end of file diff --git a/identity_iota_core/src/did/web_did.rs b/identity_did_methods/src/did_web/web_did.rs similarity index 97% rename from identity_iota_core/src/did/web_did.rs rename to identity_did_methods/src/did_web/web_did.rs index eefff93ccd..e29bb941f1 100644 --- a/identity_iota_core/src/did/web_did.rs +++ b/identity_did_methods/src/did_web/web_did.rs @@ -4,7 +4,6 @@ use std::{fmt::{Display, Formatter}, str::FromStr}; use identity_core::common::Url; use identity_did::{CoreDID, Error, DID}; -use iota_sdk::utils::serde; use ref_cast::{ref_cast_custom, RefCastCustom}; use ::serde::{Deserialize, Serialize}; use identity_did::Error as DIDError; @@ -141,14 +140,14 @@ impl WebDID { .map_or(Ok(None), |r| r.map(Some) .map_err(|_| Error::InvalidMethodId))?; - let mut url = Url::parse(&format!("https://{}", domain)) + let mut url = Url::parse(&format!("https://{}", domain))//TODO: change to HTTPS .map_err(|_| Error::InvalidMethodId)?; url.set_port(port).map_err(|_| Error::InvalidMethodId)?; path.and_then(|p| Some(url.set_path(&p))); - url.domain().ok_or(Error::InvalidMethodId)?; + // url.domain().ok_or(Error::InvalidMethodId)?; //TODO: Web DID - Disabled just for testing with 127.0.0.1 Ok(url) } diff --git a/identity_iota_core/src/document/web_document.rs b/identity_did_methods/src/did_web/web_document.rs similarity index 94% rename from identity_iota_core/src/document/web_document.rs rename to identity_did_methods/src/did_web/web_document.rs index 4c551e5b5e..a6e361939d 100644 --- a/identity_iota_core/src/document/web_document.rs +++ b/identity_did_methods/src/did_web/web_document.rs @@ -1,6 +1,8 @@ use core::fmt; use core::fmt::Debug; use core::fmt::Display; +use std::fs::File; +use std::path::Path; use identity_credential::credential::Jws; use identity_did::CoreDID; use identity_did::DIDUrl; @@ -21,19 +23,13 @@ use identity_document::utils::DIDUrlQuery; use identity_verification::MethodRelationship; use identity_verification::MethodScope; use identity_verification::VerificationMethod; - -use crate::error::Result; +use crate::did_web::WebDID; use crate::Error; -use crate::IotaDID; -use crate::IotaDocumentMetadata; -use crate::NetworkName; -use crate::StateMetadataDocument; -use crate::StateMetadataEncoding; -use crate::WebDID; - - +use crate::Result; +// TODO: Web DID - Introduce other errors for these, so you dont have to depend on Errors defined in identity_iota_core +// Maybe introduce Deref and remove all reimplamentation of methods /// A DID Document adhering to the Web DID method specification. /// @@ -48,7 +44,7 @@ impl WebDocument { /// Constructs an empty DID Document with a [`WebDID`] identifier. pub fn new(url: &str) -> Result { - Ok(Self::new_with_id(WebDID::new(url).map_err(|e| Error::DIDSyntaxError(e))?)) + Ok(Self::new_with_id(WebDID::new(url).map_err(Error::DIDSyntaxError)?)) } /// Constructs an empty DID Document with the given identifier. @@ -58,7 +54,7 @@ impl WebDocument { .id(id.into()) .build() .expect("empty IotaDocument constructor failed"); - let metadata: IotaDocumentMetadata = IotaDocumentMetadata::new(); + Self(document) } @@ -331,6 +327,13 @@ impl WebDocument { .map_err(Error::JwsVerificationError) } + pub fn write_to_file(&self, path: Option<&str>) -> Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path).map_err(|_| Error::DocumentFileWriteError("Failed to create Path"))?; + serde_json::to_writer_pretty(file, self).map_err(|_| Error::DocumentFileWriteError("Failed to write document on file"))?; + Ok(()) + } + } diff --git a/identity_did_methods/src/errors.rs b/identity_did_methods/src/errors.rs new file mode 100644 index 0000000000..50b167c709 --- /dev/null +++ b/identity_did_methods/src/errors.rs @@ -0,0 +1,26 @@ +/// Alias for a `Result` with the error type [`Error`]. +pub type Result = core::result::Result; + +/// This type represents errors that can occur when constructing credentials and presentations or their serializations. +#[derive(Debug, thiserror::Error, strum::IntoStaticStr)] +#[non_exhaustive] +pub enum Error { + /// Caused by a failure during TLS backend initialization or configuration loading. + #[error("Error while adding a root certificate")] + AddRootCertificateError, + /// Caused by a failure during TLS backend initialization or configuration loading. + #[error("Error while building the WebClient")] + WebClientBuildError(#[source] reqwest::Error), + /// Caused by an error while writing a DID document to a file. + #[error("Error while writing a DID Document on a file")] + DocumentFileWriteError(&'static str), + /// Caused by an invalid DID. + #[error("invalid did")] + DIDSyntaxError(#[source] identity_did::Error), + /// Caused by an invalid DID document. + #[error("invalid document")] + InvalidDoc(#[source] identity_document::Error), + /// Caused by an error during JSON Web Signature verification. + #[error("jws signature verification failed")] + JwsVerificationError(#[source] identity_document::Error), +} \ No newline at end of file diff --git a/identity_did_methods/src/lib.rs b/identity_did_methods/src/lib.rs new file mode 100644 index 0000000000..e7461c223d --- /dev/null +++ b/identity_did_methods/src/lib.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "web")] +pub use did_web::*; +pub use self::errors::Error; +pub use self::errors::Result; + +#[cfg(feature = "key")] +mod did_key; +#[cfg(feature = "web")] +mod did_web; +mod errors; \ No newline at end of file diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 0183994b24..27135c0198 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -17,6 +17,7 @@ identity_credential = { version = "=1.2.0", path = "../identity_credential", fea identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false } +identity_did_methods = { version = "=1.2.0", path = "../identity_did_methods", default-features = false } identity_resolver = { version = "=1.2.0", path = "../identity_resolver", default-features = false, optional = true } identity_storage = { version = "=1.2.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } @@ -36,6 +37,9 @@ client = ["identity_iota_core/client"] # Enables the iota-client integration, the client trait implementations for it, and the `IotaClientExt` trait. iota-client = ["identity_iota_core/iota-client", "identity_resolver?/iota"] +# Enables the DID Web Method integration +web-method = ["identity_did_methods/web"] + # Enables revocation with `RevocationBitmap2022`. revocation-bitmap = [ "identity_credential/revocation-bitmap", diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 9ab2e53805..7f171f7d2b 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -69,6 +69,18 @@ pub mod iota { pub use identity_iota_core::*; } +#[cfg(feature = "web-method")] +pub mod web { + //! The Web DID method implementation for the IOTA ledger. + + pub use identity_did_methods::WebDID; + pub use identity_did_methods::WebDocument; + pub use identity_did_methods::WebClient; + pub use identity_did_methods::WebClientBuilder; +} + +//TODO: Web DID - IOTA principal module extension + pub mod prelude { //! Prelude of commonly used types diff --git a/identity_iota_core/src/did/mod.rs b/identity_iota_core/src/did/mod.rs index 87287b2a8e..5e95c6ab28 100644 --- a/identity_iota_core/src/did/mod.rs +++ b/identity_iota_core/src/did/mod.rs @@ -2,6 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 pub use iota_did::IotaDID; -pub use web_did::WebDID; + mod iota_did; -mod web_did; diff --git a/identity_iota_core/src/document/mod.rs b/identity_iota_core/src/document/mod.rs index 39492a0ab7..0c8668c74c 100644 --- a/identity_iota_core/src/document/mod.rs +++ b/identity_iota_core/src/document/mod.rs @@ -3,11 +3,9 @@ pub use iota_document::IotaDocument; pub use iota_document_metadata::IotaDocumentMetadata; -pub use web_document::WebDocument; mod iota_document; mod iota_document_metadata; -mod web_document; #[cfg(test)] pub(crate) mod test_utils; diff --git a/identity_iota_core/src/lib.rs b/identity_iota_core/src/lib.rs index d0e2d58f34..6602fb10ba 100644 --- a/identity_iota_core/src/lib.rs +++ b/identity_iota_core/src/lib.rs @@ -26,7 +26,6 @@ pub mod block { #[cfg(feature = "client")] pub use client::*; pub use did::IotaDID; -pub use did::WebDID; //TODO: Web DID - lib pub use document::*; pub use network::NetworkName; pub use state_metadata::*; diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index eb0ed02981..f2d8249f3b 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -23,10 +23,6 @@ serde = { version = "1.0", default-features = false, features = ["std", "derive" strum.workspace = true thiserror = { version = "1.0", default-features = false } -hickory-resolver = { version = "0.24.1", default-features = false, features = ["dns-over-https-rustls", "native-certs"]} -reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} -once_cell = "1.19.0" - [dependencies.identity_iota_core] version = "=1.2.0" @@ -35,16 +31,25 @@ default-features = false features = ["send-sync-client-ext", "iota-client"] optional = true +[dependencies.identity_did_methods] +version = "=1.2.0" +path = "../identity_did_methods" +default-features = false +features = ["web"] +optional = true + [dev-dependencies] identity_iota_core = { path = "../identity_iota_core", features = ["test"] } iota-sdk = { version = "1.1.5" } tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } [features] -default = ["revocation-bitmap", "iota"] +default = ["revocation-bitmap", "iota", "web"] revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_core?/revocation-bitmap"] # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] +# Enables the Web integration for the resolver. +web = ["dep:identity_did_methods"] [lints] workspace = true diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 83f8f80f1a..0f5ef715e0 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -335,66 +335,16 @@ mod iota_handler { } //TODO: Web - resolver handler -// #[cfg(feature = "web")] +#[cfg(feature = "web")] mod web_handler { use crate::ErrorCause; - use super::Resolver; - use hickory_resolver::config::ResolverConfig; -use hickory_resolver::config::ResolverOpts; -use hickory_resolver::TokioAsyncResolver; -use identity_document::document::CoreDocument; - use identity_iota_core::IotaDID; - use identity_iota_core::IotaDocument; - use identity_iota_core::IotaIdentityClientExt; - use identity_iota_core::WebDID; -use identity_iota_core::WebDocument; -use once_cell::sync::OnceCell; -use reqwest::dns::Addrs; -use reqwest::ClientBuilder; -use reqwest::IntoUrl; - use std::collections::HashMap; - use std::io; -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::Arc; -use crate::Error; -use crate::Result; - - - /// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. - #[derive(Debug, Default, Clone)] - pub(crate) struct DnsOverHttpsResolver { - /// Since we might not have been called in the context of a - /// Tokio Runtime in initialization, so we must delay the actual - /// construction of the resolver. - state: Arc>, - } - - impl DnsOverHttpsResolver { - fn new_https_resolver()-> io::Result { - let mut opt = ResolverOpts::default(); - opt.validate=true; - Ok(TokioAsyncResolver::tokio( - ResolverConfig::cloudflare_https(), - opt - )) - } - } - - impl reqwest::dns::Resolve for DnsOverHttpsResolver { - fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving { - let resolver = self.clone(); - Box::pin(async move { - let resolver = resolver.state.get_or_try_init(DnsOverHttpsResolver::new_https_resolver)?; - let lookup = resolver.lookup_ip(name.as_str()).await?; - let addrs: Addrs = Box::new( - lookup.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)) - ); - Ok(addrs) - }) - } - } + use identity_did_methods::WebClient; + use identity_document::document::CoreDocument; + use identity_did_methods::WebDID; + use identity_did_methods::WebDocument; + use crate::Error; + use crate::Result; impl Resolver @@ -404,28 +354,12 @@ use crate::Result; /// Convenience method for attaching a new handler responsible for resolving Web DIDs. /// /// See also [`attach_handler`](Self::attach_handler). - pub fn attach_web_handler(&mut self) -> Result<(), Error> + pub fn attach_web_handler(&mut self, client: WebClient) -> Result<(), Error> { - - //TODO: to be moved in a folder identity_web with all the other Web DID Method related structures and functionalities. - // Construct a new Resolver with default configuration options - let resolver = DnsOverHttpsResolver::default(); - - let client = ClientBuilder::new() - .use_rustls_tls() - .dns_resolver(resolver.into()) - .build() - .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))?; - - - let handler = move |did: WebDID| { let future_client = client.clone(); async move { - let did_url = reqwest::Url::from_str(&did.to_url().to_string()) - .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))?; - - future_client.get(did_url) + future_client.get(did.to_url()) .send() .await .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))? diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 590d532485..4b93df241f 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -20,6 +20,7 @@ identity_credential = { version = "=1.2.0", path = "../identity_credential", def identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false, optional = true } +identity_did_methods = { version = "=1.2.0", path = "../identity_did_methods", default-features = false, optional = true } identity_verification = { version = "=1.2.0", path = "../identity_verification", default_features = false } iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"], optional = true } json-proof-token = { workspace = true, optional = true } @@ -38,13 +39,15 @@ once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } [features] -default = ["iota-document", "memstore"] +default = ["iota-document", "web-document", "memstore"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto"] # Enables `Send` + `Sync` bounds for the storage traits. send-sync-storage = [] # Implements the JwkStorageDocumentExt trait for IotaDocument iota-document = ["dep:identity_iota_core"] +# Implements the JwkStorageDocumentExt trait for WebDocument +web-document = ["dep:identity_did_methods"] # Enables JSON Proof Token & BBS+ related features jpt-bbs-plus = ["identity_credential/jpt-bbs-plus", "dep:zkryptium", "dep:json-proof-token"] diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index b14fe038b6..c3bc6dc645 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -144,8 +144,8 @@ mod private { impl Sealed for identity_iota_core::IotaDocument {} //TODO: Web DID - Sealed - // #[cfg(feature = "web-document")] - impl Sealed for identity_iota_core::WebDocument {} + #[cfg(feature = "web-document")] + impl Sealed for identity_did_methods::WebDocument {} } // ==================================================================================================================== @@ -637,12 +637,13 @@ mod iota_document { // ==================================================================================================================== // WebDocument // ==================================================================================================================== -// #[cfg(feature = "web-document")] + // TODO: Web DID - JwkDocumentExt for WebDocument +#[cfg(feature = "web-document")] mod web_document { use super::*; use identity_credential::credential::Jwt; - use identity_iota_core::WebDocument; + use identity_did_methods::WebDocument; generate_method_for_document_type!( WebDocument, From d8d8d69f04ca961d89fcda43a7c74dda59e2f195 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 12 Jun 2024 16:10:43 +0200 Subject: [PATCH 038/163] fix errors --- Cargo.toml | 1 + bindings/wasm/Cargo.toml | 1 + examples/Cargo.toml | 2 +- identity_credential/Cargo.toml | 3 +- .../src/credential/jwt_serialization.rs | 82 +++++++++---------- identity_credential/src/credential/mod.rs | 5 -- identity_credential/src/presentation/mod.rs | 2 - .../jwt_credential_validation/error.rs | 13 +-- identity_credential/src/validator/mod.rs | 2 - identity_document/src/verifiable/mod.rs | 4 +- identity_jose/Cargo.toml | 2 +- identity_jose/src/jwk/mod.rs | 1 - identity_pqc_verifier/Cargo.toml | 2 +- identity_storage/Cargo.toml | 1 + identity_storage/src/key_storage/memstore.rs | 8 +- identity_storage/src/storage/mod.rs | 4 + identity_stronghold/Cargo.toml | 3 +- 17 files changed, 58 insertions(+), 78 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3774d5755..e83cc92240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ exclude = ["bindings/wasm", "bindings/grpc"] [workspace.dependencies] +bls12_381_plus = { version = "=0.8.15" } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 27d693e23b..8648bd3f4e 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -17,6 +17,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] async-trait = { version = "0.1", default-features = false } +bls12_381_plus = "=0.8.15" console_error_panic_hook = { version = "0.1" } futures = { version = "0.3" } identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0212d8b03c..fbfde1c718 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow = "1.0.62" +bls12_381_plus.workspace = true identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false } identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } @@ -17,7 +18,6 @@ rand = "0.8.5" sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } -json-proof-token.workspace = true identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } [lib] diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index e93fdd0699..07f119b853 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -13,6 +13,7 @@ description = "An implementation of the Verifiable Credentials standard." [dependencies] async-trait = { version = "0.1.64", default-features = false } +bls12_381_plus = { workspace = true, optional = true } flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true } futures = { version = "0.3", default-features = false, optional = true } identity_core = { version = "=1.3.0", path = "../identity_core", default-features = false } @@ -58,7 +59,7 @@ validator = ["dep:itertools", "dep:serde_repr", "credential", "presentation"] domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] -jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:json-proof-token"] +jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] [lints] workspace = true diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 61818fe210..cb0cde95e6 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -731,55 +731,55 @@ mod tests { } -//TODO: ZKP - convert to JptClaims structure which basically contains the same claim names -impl<'credential, T> From> for JptClaims -where - T: ToOwned + Serialize, - ::Owned: DeserializeOwned, -{ - fn from(item: CredentialJwtClaims<'credential, T>) -> Self { - - let CredentialJwtClaims { - exp, - iss, - issuance_date, - jti, - sub, - vc, - custom - } = item; +// //TODO: ZKP - convert to JptClaims structure which basically contains the same claim names +// impl<'credential, T> From> for JptClaims +// where +// T: ToOwned + Serialize, +// ::Owned: DeserializeOwned, +// { +// fn from(item: CredentialJwtClaims<'credential, T>) -> Self { + +// let CredentialJwtClaims { +// exp, +// iss, +// issuance_date, +// jti, +// sub, +// vc, +// custom +// } = item; - let mut claims = JptClaims::new(); +// let mut claims = JptClaims::new(); - exp.map(|v| { - claims.set_exp(v); - }); +// exp.map(|v| { +// claims.set_exp(v); +// }); - claims.set_iss(iss.url().to_string()); +// claims.set_iss(iss.url().to_string()); - issuance_date.iat.map(|v| { - claims.set_iat(v); - }); +// issuance_date.iat.map(|v| { +// claims.set_iat(v); +// }); - issuance_date.nbf.map(|v| { - claims.set_nbf(v); - }); +// issuance_date.nbf.map(|v| { +// claims.set_nbf(v); +// }); - jti.map(|v| { - claims.set_jti(v.to_string()); - }); +// jti.map(|v| { +// claims.set_jti(v.to_string()); +// }); - sub.map(|v| { - claims.set_sub(v.to_string()); - }); +// sub.map(|v| { +// claims.set_sub(v.to_string()); +// }); - claims.set_claim(Some("vc"), vc, true); +// claims.set_claim(Some("vc"), vc, true); - custom.map(|v| { - claims.set_claim(None, v, true); - }); +// custom.map(|v| { +// claims.set_claim(None, v, true); +// }); - claims - } -} \ No newline at end of file +// claims +// } +// } \ No newline at end of file diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 50b41ea123..710e97ef22 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -15,7 +15,6 @@ mod jpt; mod jwp_credential_options; mod jws; mod jwt; -mod jpt; mod jwt_serialization; mod linked_domain_service; mod policy; @@ -26,7 +25,6 @@ mod revocation_bitmap_status; mod schema; mod status; mod subject; -mod jwp_credential_options; pub use self::builder::CredentialBuilder; @@ -39,7 +37,6 @@ pub use self::jpt::Jpt; pub use self::jwp_credential_options::JwpCredentialOptions; pub use self::jws::Jws; pub use self::jwt::Jwt; -pub use self::jpt::Jpt; pub use self::linked_domain_service::LinkedDomainService; pub use self::policy::Policy; pub use self::proof::Proof; @@ -51,8 +48,6 @@ pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; -pub use self::jwp_credential_options::JwpCredentialOptions; - #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::CredentialJwtClaims; #[cfg(feature = "presentation")] diff --git a/identity_credential/src/presentation/mod.rs b/identity_credential/src/presentation/mod.rs index 6481435847..76adc145c6 100644 --- a/identity_credential/src/presentation/mod.rs +++ b/identity_credential/src/presentation/mod.rs @@ -13,8 +13,6 @@ mod jwt_presentation_options; mod jwt_serialization; mod presentation; mod presentation_builder; -mod jwp_presentation_builder; -mod jwp_presentation_options; #[cfg(feature = "jpt-bbs-plus")] pub use self::jwp_presentation_builder::SelectiveDisclosurePresentation; diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index f3cb0cf79b..19e10d8498 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -100,18 +100,7 @@ pub enum JwtValidationError { ServiceLookupError, /// Indicates that the credential has been revoked. #[error("credential has been revoked")] - Revoked, - - - //TODO: ZKP - new errors for jwp - /// Indicates that the JWP representation of an issued credential or presentation could not be decoded. - #[error("could not decode jwp")] - JwpDecodingError(#[source] jsonprooftoken::errors::CustomError), - - /// Indicates that the verfication of the JWP has failed - #[error("could not verify jwp")] - JwpProofVerificationError(#[source] jsonprooftoken::errors::CustomError), - + Revoked, /// Indicates that the credential has been suspended. #[error("credential has been suspended")] Suspended, diff --git a/identity_credential/src/validator/mod.rs b/identity_credential/src/validator/mod.rs index c544511cce..2266618ddd 100644 --- a/identity_credential/src/validator/mod.rs +++ b/identity_credential/src/validator/mod.rs @@ -9,8 +9,6 @@ pub use self::jpt_credential_validation::*; pub use self::jpt_presentation_validation::*; pub use self::jwt_credential_validation::*; pub use self::jwt_presentation_validation::*; -pub use self::jpt_credential_validation::*; -pub use self::jpt_presentation_validation::*; pub use self::options::FailFast; pub use self::options::StatusCheck; pub use self::options::SubjectHolderRelationship; diff --git a/identity_document/src/verifiable/mod.rs b/identity_document/src/verifiable/mod.rs index e6276d50e7..02d55b155b 100644 --- a/identity_document/src/verifiable/mod.rs +++ b/identity_document/src/verifiable/mod.rs @@ -5,8 +5,6 @@ pub use self::jwp_verification_options::JwpVerificationOptions; pub use self::jws_verification_options::JwsVerificationOptions; -pub use self::jwp_verification_options::JwpVerificationOptions; mod jwp_verification_options; -mod jws_verification_options; -mod jwp_verification_options; \ No newline at end of file +mod jws_verification_options; \ No newline at end of file diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 5a81dc18e3..3176142f33 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] +bls12_381_plus.workspace = true identity_core = { version = "=1.3.0", path = "../identity_core", default-features = false } iota-crypto = { version = "0.23", default-features = false, features = ["std", "sha"] } json-proof-token.workspace = true @@ -20,7 +21,6 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] } subtle = { version = "2.5", default-features = false } thiserror.workspace = true zeroize = { version = "1.6", default-features = false, features = ["std", "zeroize_derive"] } -json-proof-token.workspace = true [dev-dependencies] anyhow = "1" diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index b365138173..5feea0898a 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -11,7 +11,6 @@ mod key_params; mod key_set; mod key_type; mod key_use; -mod jwk_ext; mod jwk_pq; pub use self::curve::*; diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 0248bb9d1e..9773409ff8 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true description = "JWS PQC signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.1.0-alpha.1", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.3.0", path = "../identity_jose", default-features = false } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } [features] diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 8d11be4d82..02edcd98d9 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -14,6 +14,7 @@ description = "Abstractions over storage for cryptographic keys used in DID Docu [dependencies] anyhow = "1.0.82" async-trait = { version = "0.1.64", default-features = false } +bls12_381_plus = { workspace = true, optional = true } futures = { version = "0.3.27", default-features = false, features = ["async-await"] } identity_core = { version = "=1.3.0", path = "../identity_core", default-features = false } identity_credential = { version = "=1.3.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 3c39d3e6a1..ebf2b82d0f 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -23,12 +23,6 @@ use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; use tokio::sync::RwLockWriteGuard; -use zkryptium::bbsplus::keys::BBSplusPublicKey; -use zkryptium::bbsplus::keys::BBSplusSecretKey; -use zkryptium::schemes::algorithms::BBS_BLS12381_SHA256; -use zkryptium::schemes::algorithms::BBS_BLS12381_SHAKE256; -use zkryptium::schemes::generics::Signature; -use zkryptium::utils::message::BBSplusMessage; use super::ed25519::encode_jwk; use super::ed25519::expand_secret_jwk; @@ -38,7 +32,7 @@ use super::KeyStorageError; use super::KeyStorageErrorKind; use super::KeyStorageResult; use super::KeyType; -use crate::JwkStorageExt; + use crate::key_storage::JwkStorage; use crate::JwkStoragePQ; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 7643c41a95..b93c747b95 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -12,6 +12,8 @@ mod signature_options; #[cfg(feature = "jpt-bbs-plus")] mod timeframe_revocation_ext; +mod pqc_jws_document_ext; + #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -24,6 +26,8 @@ pub use signature_options::*; #[cfg(feature = "jpt-bbs-plus")] pub use timeframe_revocation_ext::*; +pub use pqc_jws_document_ext::*; + /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from /// [`JwkDocumentExt`](crate::storage::JwkDocumentExt). diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index 6fb1b82b07..73072ec9b4 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -13,6 +13,7 @@ description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] async-trait = { version = "0.1.64", default-features = false } +bls12_381_plus = { workspace = true, optional = true } identity_storage = { version = "=1.3.0", path = "../identity_storage", default_features = false } identity_verification = { version = "=1.3.0", path = "../identity_verification", default_features = false } iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"] } @@ -36,7 +37,7 @@ zkryptium = { workspace = true } default = [] # Enables `Send` + `Sync` bounds for the trait implementations on `StrongholdStorage`. send-sync-storage = ["identity_storage/send-sync-storage"] -bbs-plus = ["identity_storage/jpt-bbs-plus", "dep:zkryptium", "dep:json-proof-token"] +bbs-plus = ["identity_storage/jpt-bbs-plus", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] [lints] workspace = true From f19ee07539ca6295698d0aeca1c7bbc169949cc4 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 12 Jun 2024 16:13:41 +0200 Subject: [PATCH 039/163] fix version --- identity_pqc_verifier/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 9773409ff8..7698e6cfad 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true description = "JWS PQC signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.3.0", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } [features] From fc9c171095a9aa065025f60123110fa145cc0b2f Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 8 Jul 2024 12:15:51 +0200 Subject: [PATCH 040/163] fix did web --- Cargo.toml | 3 +- examples/1_advanced/11_did_web.rs | 201 +++++++++- examples/Cargo.toml | 4 +- .../web_did.rs => identity_did/src/did_web.rs | 30 +- identity_did/src/lib.rs | 2 + identity_did_methods/Cargo.toml | 43 --- identity_did_methods/README.md | 6 - identity_did_methods/src/did_key/mod.rs | 0 identity_did_methods/src/did_web/client.rs | 160 -------- identity_did_methods/src/did_web/mod.rs | 10 - .../src/did_web/web_document.rs | 350 ------------------ identity_did_methods/src/errors.rs | 26 -- identity_did_methods/src/lib.rs | 10 - .../src/document/core_document.rs | 16 + identity_iota/Cargo.toml | 4 - identity_iota/src/lib.rs | 2 +- identity_resolver/Cargo.toml | 11 +- identity_resolver/src/error.rs | 2 + identity_resolver/src/resolution/resolver.rs | 20 +- identity_storage/Cargo.toml | 5 +- .../src/storage/jwk_document_ext.rs | 204 +++++----- 21 files changed, 342 insertions(+), 767 deletions(-) rename identity_did_methods/src/did_web/web_did.rs => identity_did/src/did_web.rs (90%) delete mode 100644 identity_did_methods/Cargo.toml delete mode 100644 identity_did_methods/README.md delete mode 100644 identity_did_methods/src/did_key/mod.rs delete mode 100644 identity_did_methods/src/did_web/client.rs delete mode 100644 identity_did_methods/src/did_web/mod.rs delete mode 100644 identity_did_methods/src/did_web/web_document.rs delete mode 100644 identity_did_methods/src/errors.rs delete mode 100644 identity_did_methods/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 736931c671..a0375aa810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ members = [ "identity_jose", "identity_ecdsa_verifier", "identity_eddsa_verifier", - "identity_did_methods", - "examples", "identity_did_methods", + "examples", ] exclude = ["bindings/wasm", "bindings/grpc"] diff --git a/examples/1_advanced/11_did_web.rs b/examples/1_advanced/11_did_web.rs index b9c11b0b98..89c4477055 100644 --- a/examples/1_advanced/11_did_web.rs +++ b/examples/1_advanced/11_did_web.rs @@ -1,9 +1,19 @@ -use examples::MemStorage; -use identity_iota::{core::Url, document::CoreDocument, resolver::Resolver, storage::{JwkMemStore, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}, web::{WebClient, WebClientBuilder, WebDocument}}; +use std::{collections::HashMap, fs::File, path::Path}; + +use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::IotaDocument, resolver::Resolver, storage::{JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; use identity_iota::storage::JwkDocumentExt; +use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::{Certificate, ClientBuilder}; -use tokio::{fs::File, io::AsyncReadExt}; +use serde_json::json; +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -13,20 +23,20 @@ async fn main() -> anyhow::Result<()> { // Create a new client to make HTTPS requests. // let client = WebClient::default()?; - let client= WebClient::new(ClientBuilder::new() + let client= ClientBuilder::new() .danger_accept_invalid_certs(true) //TODO: fix problem cannot build WebClient after calling function of inner structure - .build()?); + .build()?; // Create a new Web DID document. - let mut document: WebDocument = WebDocument::new(did_url)?; + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; // let doc: WebDocument = client.get(Url::parse(did_url)?).send().await?.json().await?; // println!("PPPPP: {}", doc); // Insert a new Ed25519 verification method in the DID document. - let storage: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - document + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document .generate_method( - &storage, + &storage_issuer, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA, None, @@ -34,18 +44,177 @@ async fn main() -> anyhow::Result<()> { ) .await?; - document.write_to_file(Some(path_did_file))?; - println!("Web DID Document: {:#}", document); + write_to_file(&issuer_document, Some(path_did_file))?; + println!("Web DID Document: {:#}", issuer_document); + + + // Create a new client to interact with the IOTA ledger. + let iota_client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + // Create an identity for the holder, in this case also the subject. + let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?, + ); + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = + create_did(&iota_client, &mut secret_manager_alice, &storage_alice).await?; + + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!("Sending credential (as JWT) to the holder: {credential:#}"); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!("Sending presentation (as JWT) to the verifier: {presentation:#}"); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(iota_client); + + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + resolver_web.attach_web_handler(client); + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - // let web_did = WebDID::from_str("did:web:192.168.1.196%3a3000:.well-known:did.json")?; //THIS MUST FAIL! + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - let mut resolver = Resolver::::new(); - resolver.attach_web_handler(client)?; + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } - let resolved_document = resolver.resolve(document.id()).await?; - println!("Resolved Document: {:#}", resolved_document); + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); Ok(()) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 7812e9f4a6..563a7dadc5 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false } -identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "resolver", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "web-method"] } +identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "resolver", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} @@ -104,4 +104,4 @@ name = "10_zkp_revocation" [[example]] path = "1_advanced/11_did_web.rs" -name = "11_did_web" +name = "11_did_web" \ No newline at end of file diff --git a/identity_did_methods/src/did_web/web_did.rs b/identity_did/src/did_web.rs similarity index 90% rename from identity_did_methods/src/did_web/web_did.rs rename to identity_did/src/did_web.rs index e29bb941f1..94f38bdec5 100644 --- a/identity_did_methods/src/did_web/web_did.rs +++ b/identity_did/src/did_web.rs @@ -3,10 +3,10 @@ use std::{fmt::{Display, Formatter}, str::FromStr}; use identity_core::common::Url; -use identity_did::{CoreDID, Error, DID}; -use ref_cast::{ref_cast_custom, RefCastCustom}; +use crate::{CoreDID, Error, DID}; +// use ref_cast::{ref_cast_custom, RefCastCustom}; use ::serde::{Deserialize, Serialize}; -use identity_did::Error as DIDError; +use crate::Error as DIDError; /// Alias for a `Result` with the error type [`DIDError`]. type Result = std::result::Result; @@ -15,7 +15,7 @@ type Result = std::result::Result; /// /// This is a thin wrapper around the [`DID`][`CoreDID`] type from the /// [`identity_did`][`identity_did`] crate. -#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, RefCastCustom)] +#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize/* , RefCastCustom*/)] #[repr(transparent)] #[serde(into = "CoreDID", try_from = "CoreDID")] pub struct WebDID(CoreDID); @@ -27,17 +27,17 @@ impl WebDID { /// The IOTA DID method name (`"iota"`). pub const METHOD: &'static str = "web"; - /// Convert a `CoreDID` reference to an `WebDID` reference without checking the referenced value. - /// - /// # Warning - /// This method should only be called on [`CoreDIDs`](CoreDID) that - /// are known to satisfy the requirements of the Web DID Method specification. - /// - /// # Memory safety - /// - /// The `ref-cast` crate ensures a memory safe implementation. - #[ref_cast_custom] - pub(crate) const fn from_inner_ref_unchecked(did: &CoreDID) -> &Self; +// /// Convert a `CoreDID` reference to an `WebDID` reference without checking the referenced value. +// /// +// /// # Warning +// /// This method should only be called on [`CoreDIDs`](CoreDID) that +// /// are known to satisfy the requirements of the Web DID Method specification. +// /// +// /// # Memory safety +// /// +// /// The `ref-cast` crate ensures a memory safe implementation. +// #[ref_cast_custom] +// pub(crate) const fn from_inner_ref_unchecked(did: &CoreDID) -> &Self; /// Create a new valid Web DID. pub fn new(url: &str) -> Result { diff --git a/identity_did/src/lib.rs b/identity_did/src/lib.rs index 9289419211..229843476d 100644 --- a/identity_did/src/lib.rs +++ b/identity_did/src/lib.rs @@ -20,6 +20,7 @@ mod did; mod did_url; mod error; +mod did_web; pub use crate::did_url::DIDUrl; pub use crate::did_url::RelativeDIDUrl; @@ -27,3 +28,4 @@ pub use ::did_url_parser::DID as BaseDIDUrl; pub use did::CoreDID; pub use did::DID; pub use error::Error; +pub use did_web::*; \ No newline at end of file diff --git a/identity_did_methods/Cargo.toml b/identity_did_methods/Cargo.toml deleted file mode 100644 index 088456bde3..0000000000 --- a/identity_did_methods/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "identity_did_methods" -version = "1.2.0" -authors.workspace = true -edition.workspace = true -homepage.workspace = true -keywords = ["iota", "web", "identity"] -license.workspace = true -readme = "./README.md" -repository.workspace = true -rust-version.workspace = true -description = "An integration for the Web DID Method." - - -[dependencies] -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } -identity_credential = { version = "=1.2.0", path = "../identity_credential", default-features = false, features = ["validator"] } -ref-cast = { version = "1.0.14", default-features = false } -hickory-resolver = { version = "0.24.1", default-features = false, features = ["dns-over-https-rustls", "native-certs"]} -reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} -once_cell = "1.19.0" -serde.workspace = true -serde_json.workspace = true -strum.workspace = true -thiserror.workspace = true - - -[package.metadata.docs.rs] -# To build locally: -# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[features] -default = ["key", "web"] -key = [] -web = [] - -[lints] -workspace = true diff --git a/identity_did_methods/README.md b/identity_did_methods/README.md deleted file mode 100644 index 7449f223ee..0000000000 --- a/identity_did_methods/README.md +++ /dev/null @@ -1,6 +0,0 @@ -IOTA Identity -=== - -This crate provides the core data structures for: -* [Web DID Method Specification](https://w3c-ccg.github.io/did-method-web/). -* [Key DID Method Specification](https://w3c-ccg.github.io/did-method-key/). \ No newline at end of file diff --git a/identity_did_methods/src/did_key/mod.rs b/identity_did_methods/src/did_key/mod.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/identity_did_methods/src/did_web/client.rs b/identity_did_methods/src/did_web/client.rs deleted file mode 100644 index 554456fbfa..0000000000 --- a/identity_did_methods/src/did_web/client.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{fs::File, io::{self, Read}, net::SocketAddr, sync::Arc}; -use hickory_resolver::{config::{ResolverConfig, ResolverOpts}, TokioAsyncResolver}; -use once_cell::sync::OnceCell; -use reqwest::{dns::Addrs, Certificate, Client, ClientBuilder, RequestBuilder}; - -use crate::Error; -use crate::Result; - -/// A `WebClientBuilder` (wrapper to `reqwest::ClientBuilder`) can be used to create a `WebClient` with custom configuration. -#[must_use] -pub struct WebClientBuilder{ - inner: ClientBuilder -} - -impl WebClientBuilder { - /// Constructs a new `WebClientBuilder`. - /// This is the same as `WebClient::builder()`. - pub fn new() -> Self { - Self { inner: ClientBuilder::new() } - } - - pub fn from(builder: ClientBuilder) -> Self { - Self { inner: builder } - } - - pub fn add_root_certificate_pem(self, cert_path: &str) -> Result{ - let mut buf = Vec::new(); - File::open(cert_path) - .map_err(|_| Error::AddRootCertificateError)? - .read_to_end(&mut buf) - .map_err(|_| Error::AddRootCertificateError)?; - - let cert = Certificate::from_pem(&buf).map_err(Error::WebClientBuildError)?; - Ok(Self{inner: self.inner.add_root_certificate(cert)}) - } - - pub fn add_root_certificate_der(self, cert_path: &str) -> Result{ - let mut buf = Vec::new(); - File::open(cert_path) - .map_err(|_| Error::AddRootCertificateError)? - .read_to_end(&mut buf) - .map_err(|_| Error::AddRootCertificateError)?; - - let cert = Certificate::from_der(&buf).map_err(Error::WebClientBuildError)?; - Ok(Self{inner: self.inner.add_root_certificate(cert)}) - } - - /// Returns a `WebClient` that uses this `WebClientBuilder` configuration. - pub fn build(self) -> Result { - let client = self.inner.build().map_err(|e| Error::WebClientBuildError(e) )?; - Ok(WebClient{ inner: client }) - } -} - -impl std::ops::Deref for WebClientBuilder { - type Target = ClientBuilder; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for WebClientBuilder { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -/// An asynchronous `WebClient` to make Requests with. -/// The `WebClient` is a wrapper to `reqwest::Client`, so has various configuration values to tweak, but the defaults are set to what is usually the most commonly desired value for DID Web Resolve. -/// To configure a `WebClient`, use `WebClient::builder()`. -#[derive(Clone)] -pub struct WebClient{ - inner: Client -} - -impl WebClient { - - pub fn new(client: Client) -> Self { - Self { inner: client } - } - - /// Constructs a new `WebClient` with the defaul configuration using a DNS over HTTPS resolver. - pub fn default() -> Result { - // Construct a new Resolver with default configuration options - let resolver = DnsOverHttpsResolver::default(); - - let client = ClientBuilder::new() - .use_rustls_tls() - .dns_resolver(resolver.into()) - .build() - .map_err(|e| Error::WebClientBuildError(e) )?; - - Ok(Self { inner: client }) - } - - /// Creates a `WebClientBuilder` to configure a `WebClient`. - /// - /// This is the same as `WebClientBuilder::new()`. - pub fn builder() -> WebClientBuilder { - WebClientBuilder::new() - } - - /// Convenience method to make a GET request to a URL of type `identity_core::common::Url`. - pub fn get(&self, url: identity_core::common::Url) -> RequestBuilder { - self.inner.get(url.as_ref()) - } - -} - - -impl std::ops::Deref for WebClient { - type Target = Client; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for WebClient { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - - - -/// DNS over HTTPS resolver, which implements the `reqwest::dns::Resolve` trait. -#[derive(Debug, Default, Clone)] -pub(crate) struct DnsOverHttpsResolver { - /// Since we might not have been called in the context of a - /// Tokio Runtime in initialization, so we must delay the actual - /// construction of the resolver. - state: Arc>, -} - -impl DnsOverHttpsResolver { - fn new_https_resolver()-> io::Result { - let mut opt = ResolverOpts::default(); - opt.validate=true; - Ok(TokioAsyncResolver::tokio( - ResolverConfig::cloudflare_https(), - opt - )) - } -} - -impl reqwest::dns::Resolve for DnsOverHttpsResolver { - fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving { - let resolver = self.clone(); - Box::pin(async move { - let resolver = resolver.state.get_or_try_init(DnsOverHttpsResolver::new_https_resolver)?; - let lookup = resolver.lookup_ip(name.as_str()).await?; - let addrs: Addrs = Box::new( - lookup.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)) - ); - Ok(addrs) - }) - } -} diff --git a/identity_did_methods/src/did_web/mod.rs b/identity_did_methods/src/did_web/mod.rs deleted file mode 100644 index 3af5bdb27c..0000000000 --- a/identity_did_methods/src/did_web/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub use web_did::WebDID; -pub use web_document::WebDocument; -pub use client::WebClient; -pub use client::WebClientBuilder; - - - -mod web_did; -mod web_document; -mod client; \ No newline at end of file diff --git a/identity_did_methods/src/did_web/web_document.rs b/identity_did_methods/src/did_web/web_document.rs deleted file mode 100644 index a6e361939d..0000000000 --- a/identity_did_methods/src/did_web/web_document.rs +++ /dev/null @@ -1,350 +0,0 @@ -use core::fmt; -use core::fmt::Debug; -use core::fmt::Display; -use std::fs::File; -use std::path::Path; -use identity_credential::credential::Jws; -use identity_did::CoreDID; -use identity_did::DIDUrl; -use identity_document::verifiable::JwsVerificationOptions; -use identity_verification::jose::jws::DecodedJws; -use identity_verification::jose::jws::JwsVerifier; -use serde::Deserialize; -use serde::Serialize; - -use identity_core::common::Object; -use identity_core::common::OneOrSet; -use identity_core::common::OrderedSet; -use identity_core::common::Url; -use identity_core::convert::FmtJson; -use identity_document::document::CoreDocument; -use identity_document::service::Service; -use identity_document::utils::DIDUrlQuery; -use identity_verification::MethodRelationship; -use identity_verification::MethodScope; -use identity_verification::VerificationMethod; -use crate::did_web::WebDID; -use crate::Error; -use crate::Result; - - -// TODO: Web DID - Introduce other errors for these, so you dont have to depend on Errors defined in identity_iota_core -// Maybe introduce Deref and remove all reimplamentation of methods - -/// A DID Document adhering to the Web DID method specification. -/// -/// This extends [`CoreDocument`]. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct WebDocument(CoreDocument); - -impl WebDocument { - // =========================================================================== - // Constructors - // =========================================================================== - - /// Constructs an empty DID Document with a [`WebDID`] identifier. - pub fn new(url: &str) -> Result { - Ok(Self::new_with_id(WebDID::new(url).map_err(Error::DIDSyntaxError)?)) - } - - /// Constructs an empty DID Document with the given identifier. - pub fn new_with_id(id: WebDID) -> Self { - // PANIC: constructing an empty DID Document is infallible, caught by tests otherwise. - let document: CoreDocument = CoreDocument::builder(Object::default()) - .id(id.into()) - .build() - .expect("empty IotaDocument constructor failed"); - - Self(document) - } - - // =========================================================================== - // Properties - // =========================================================================== - - /// Returns the DID document identifier. - pub fn id(&self) -> &WebDID { - // CORRECTNESS: This cast is OK because the public API does not expose methods - // enabling unchecked mutation of the `id` field. - WebDID::from_inner_ref_unchecked(self.0.id()) - } - - /// Returns an iterator yielding the DID controllers. - pub fn controller(&self) -> impl Iterator + '_ { - let core_did_controller_iter = self - .0 - .controller() - .map(|controllers| controllers.iter()) - .into_iter() - .flatten(); - - // CORRECTNESS: These casts are OK because the public API only allows setting WebDIDs. - core_did_controller_iter.map(WebDID::from_inner_ref_unchecked) - } - - /// Sets the value of the document controller. - /// - /// Note: - /// * Duplicates in `controller` will be ignored. - /// * Use an empty collection to clear all controllers. - pub fn set_controller(&mut self, controller: T) - where - T: IntoIterator, - { - let controller_core_dids: Option> = { - let controller_set: OrderedSet = controller.into_iter().map(CoreDID::from).collect(); - if controller_set.is_empty() { - None - } else { - Some(OneOrSet::new_set(controller_set).expect("controller is checked to be not empty")) - } - }; - - *self.0.controller_mut() = controller_core_dids; - } - - /// Returns a reference to the `alsoKnownAs` set. - pub fn also_known_as(&self) -> &OrderedSet { - self.0.also_known_as() - } - - /// Returns a mutable reference to the `alsoKnownAs` set. - pub fn also_known_as_mut(&mut self) -> &mut OrderedSet { - self.0.also_known_as_mut() - } - - /// Returns a reference to the underlying [`CoreDocument`]. - pub fn core_document(&self) -> &CoreDocument { - &self.0 - } - - /// Returns a mutable reference to the underlying [`CoreDocument`]. - /// - /// WARNING: Mutating the inner document directly bypasses checks and - /// may have undesired consequences. - pub(crate) fn core_document_mut(&mut self) -> &mut CoreDocument { - &mut self.0 - } - - /// Returns a reference to the custom DID Document properties. - pub fn properties(&self) -> &Object { - self.0.properties() - } - - /// Returns a mutable reference to the custom DID Document properties. - /// - /// # Warning - /// - /// The properties returned are not checked against the standard fields in a [`CoreDocument`]. Incautious use can have - /// undesired consequences such as key collision when attempting to serialize the document or distinct resources (such - /// as services and methods) being identified by the same DID URL. - pub fn properties_mut_unchecked(&mut self) -> &mut Object { - self.0.properties_mut_unchecked() - } - - // =========================================================================== - // Services - // =========================================================================== - - /// Return a set of all [`Service`]s in the document. - pub fn service(&self) -> &OrderedSet { - self.0.service() - } - - /// Add a new [`Service`] to the document. - /// - /// # Errors - /// An error is returned if there already exists a service or (verification) method with - /// the same identifier in the document. - pub fn insert_service(&mut self, service: Service) -> Result<()> { - self - .core_document_mut() - .insert_service(service) - .map_err(Error::InvalidDoc) - } - - /// Remove and return the [`Service`] identified by the given [`DIDUrl`] from the document. - /// - /// `None` is returned if the service does not exist in the document. - pub fn remove_service(&mut self, did_url: &DIDUrl) -> Option { - self.core_document_mut().remove_service(did_url) - } - - // =========================================================================== - // Verification Methods - // =========================================================================== - - /// Returns a `Vec` of verification method references whose verification relationship matches `scope`. - /// - /// If `scope` is `None`, all **embedded** methods are returned. - pub fn methods(&self, scope: Option) -> Vec<&VerificationMethod> { - self.0.methods(scope) - } - - /// Adds a new [`VerificationMethod`] to the document in the given [`MethodScope`]. - /// - /// # Errors - /// - /// Returns an error if a method with the same fragment already exists. - pub fn insert_method(&mut self, method: VerificationMethod, scope: MethodScope) -> Result<()> { - self - .core_document_mut() - .insert_method(method, scope) - .map_err(Error::InvalidDoc) - } - - /// Removes and returns the [`VerificationMethod`] identified by `did_url` from the document. - /// - /// # Note - /// - /// All _references to the method_ found in the document will be removed. - /// This includes cases where the reference is to a method contained in another DID document. - pub fn remove_method(&mut self, did_url: &DIDUrl) -> Option { - self.core_document_mut().remove_method(did_url) - } - - /// Removes and returns the [`VerificationMethod`] from the document. The [`MethodScope`] under which the method was - /// found is appended to the second position of the returned tuple. - /// - /// # Note - /// - /// All _references to the method_ found in the document will be removed. - /// This includes cases where the reference is to a method contained in another DID document. - pub fn remove_method_and_scope(&mut self, did_url: &DIDUrl) -> Option<(VerificationMethod, MethodScope)> { - self.core_document_mut().remove_method_and_scope(did_url) - } - - /// Attaches the relationship to the method resolved by `method_query`. - /// - /// # Errors - /// - /// Returns an error if the method does not exist or if it is embedded. - /// To convert an embedded method into a generic verification method, remove it first - /// and insert it with [`MethodScope::VerificationMethod`]. - pub fn attach_method_relationship<'query, Q>( - &mut self, - method_query: Q, - relationship: MethodRelationship, - ) -> Result - where - Q: Into>, - { - self - .core_document_mut() - .attach_method_relationship(method_query, relationship) - .map_err(Error::InvalidDoc) - } - - /// Detaches the `relationship` from the method identified by `did_url`. - /// Returns `true` if the relationship was found and removed, `false` otherwise. - /// - /// # Errors - /// - /// Returns an error if the method does not exist or is embedded. - /// To remove an embedded method, use [`Self::remove_method`]. - /// - /// # Note - /// - /// If the method is referenced in the given scope, but the document does not contain the referenced verification - /// method, then the reference will persist in the document (i.e. it is not removed). - pub fn detach_method_relationship<'query, Q>( - &mut self, - method_query: Q, - relationship: MethodRelationship, - ) -> Result - where - Q: Into>, - { - self - .core_document_mut() - .detach_method_relationship(method_query, relationship) - .map_err(Error::InvalidDoc) - } - - /// Returns the first [`VerificationMethod`] with an `id` property matching the - /// provided `method_query` and the verification relationship specified by `scope` if present. - /// - /// # Warning - /// - /// Incorrect use of this method can lead to distinct document resources being identified by the same DID URL. - pub fn resolve_method_mut<'query, Q>( - &mut self, - method_query: Q, - scope: Option, - ) -> Option<&mut VerificationMethod> - where - Q: Into>, - { - self.0.resolve_method_mut(method_query, scope) - } - - /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. - // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains - // services whose ids are of the form #. - pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> - where - Q: Into>, - { - self.0.resolve_service(service_query) - } - - /// Returns the first [`VerificationMethod`] with an `id` property matching the - /// provided `method_query` and the verification relationship specified by `scope` if present. - // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains methods - // whose ids are of the form #. - pub fn resolve_method<'query, 'me, Q>( - &'me self, - method_query: Q, - scope: Option, - ) -> Option<&VerificationMethod> - where - Q: Into>, - { - self.0.resolve_method(method_query, scope) - } - - // =========================================================================== - // Signatures - // =========================================================================== - - /// Decodes and verifies the provided JWS according to the passed [`JwsVerificationOptions`] and - /// [`JwsVerifier`]. - /// - /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to - /// take place. - /// - The JWS must be encoded according to the JWS compact serialization. - /// - The `kid` value in the protected header must be an identifier of a verification method in this DID document. - pub fn verify_jws<'jws, T: JwsVerifier>( - &self, - jws: &'jws Jws, - detached_payload: Option<&'jws [u8]>, - signature_verifier: &T, - options: &JwsVerificationOptions, - ) -> Result> { - self - .core_document() - .verify_jws(jws.as_str(), detached_payload, signature_verifier, options) - .map_err(Error::JwsVerificationError) - } - - pub fn write_to_file(&self, path: Option<&str>) -> Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path).map_err(|_| Error::DocumentFileWriteError("Failed to create Path"))?; - serde_json::to_writer_pretty(file, self).map_err(|_| Error::DocumentFileWriteError("Failed to write document on file"))?; - Ok(()) - } - - -} - -impl AsRef for WebDocument { - fn as_ref(&self) -> &CoreDocument { - &self.0 - } -} - -impl Display for WebDocument { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_json(f) - } - } \ No newline at end of file diff --git a/identity_did_methods/src/errors.rs b/identity_did_methods/src/errors.rs deleted file mode 100644 index 50b167c709..0000000000 --- a/identity_did_methods/src/errors.rs +++ /dev/null @@ -1,26 +0,0 @@ -/// Alias for a `Result` with the error type [`Error`]. -pub type Result = core::result::Result; - -/// This type represents errors that can occur when constructing credentials and presentations or their serializations. -#[derive(Debug, thiserror::Error, strum::IntoStaticStr)] -#[non_exhaustive] -pub enum Error { - /// Caused by a failure during TLS backend initialization or configuration loading. - #[error("Error while adding a root certificate")] - AddRootCertificateError, - /// Caused by a failure during TLS backend initialization or configuration loading. - #[error("Error while building the WebClient")] - WebClientBuildError(#[source] reqwest::Error), - /// Caused by an error while writing a DID document to a file. - #[error("Error while writing a DID Document on a file")] - DocumentFileWriteError(&'static str), - /// Caused by an invalid DID. - #[error("invalid did")] - DIDSyntaxError(#[source] identity_did::Error), - /// Caused by an invalid DID document. - #[error("invalid document")] - InvalidDoc(#[source] identity_document::Error), - /// Caused by an error during JSON Web Signature verification. - #[error("jws signature verification failed")] - JwsVerificationError(#[source] identity_document::Error), -} \ No newline at end of file diff --git a/identity_did_methods/src/lib.rs b/identity_did_methods/src/lib.rs deleted file mode 100644 index e7461c223d..0000000000 --- a/identity_did_methods/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[cfg(feature = "web")] -pub use did_web::*; -pub use self::errors::Error; -pub use self::errors::Result; - -#[cfg(feature = "key")] -mod did_key; -#[cfg(feature = "web")] -mod did_web; -mod errors; \ No newline at end of file diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 1b226f9585..7067861a2e 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -7,6 +7,7 @@ use core::fmt::Formatter; use std::collections::HashMap; use std::convert::Infallible; +use identity_did::WebDID; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::DecodedJws; use identity_verification::jose::jws::Decoder; @@ -34,6 +35,7 @@ use identity_verification::MethodRelationship; use identity_verification::MethodScope; use identity_verification::VerificationMethod; + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[rustfmt::skip] pub(crate) struct CoreDocumentData @@ -984,6 +986,20 @@ impl CoreDocument { } } +//TODO: Web - impl CoreDocument (WebDID) +/// DID web +impl CoreDocument { + pub fn new_from_url(url: &str) -> Result{ + let id = WebDID::new(url).map_err(|_| Error::InvalidDocument("Invalid DID Web", None))?; + let document: CoreDocument = CoreDocument::builder(Object::default()) + .id(id.into()) + .build() + .map_err(|_| Error::InvalidDocument("empty Document construction failed", None))?; + + Ok(document) + } +} + #[cfg(test)] mod tests { use identity_core::convert::FromJson; diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 27135c0198..0183994b24 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -17,7 +17,6 @@ identity_credential = { version = "=1.2.0", path = "../identity_credential", fea identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false } -identity_did_methods = { version = "=1.2.0", path = "../identity_did_methods", default-features = false } identity_resolver = { version = "=1.2.0", path = "../identity_resolver", default-features = false, optional = true } identity_storage = { version = "=1.2.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } @@ -37,9 +36,6 @@ client = ["identity_iota_core/client"] # Enables the iota-client integration, the client trait implementations for it, and the `IotaClientExt` trait. iota-client = ["identity_iota_core/iota-client", "identity_resolver?/iota"] -# Enables the DID Web Method integration -web-method = ["identity_did_methods/web"] - # Enables revocation with `RevocationBitmap2022`. revocation-bitmap = [ "identity_credential/revocation-bitmap", diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 7f171f7d2b..f5e2ef78e2 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -73,7 +73,7 @@ pub mod iota { pub mod web { //! The Web DID method implementation for the IOTA ledger. - pub use identity_did_methods::WebDID; + // pub use identity_did_methods::WebDID; pub use identity_did_methods::WebDocument; pub use identity_did_methods::WebClient; pub use identity_did_methods::WebClientBuilder; diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index f2d8249f3b..3d0d11983b 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -22,6 +22,7 @@ identity_document = { version = "=1.2.0", path = "../identity_document", default serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } +reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} [dependencies.identity_iota_core] @@ -31,13 +32,6 @@ default-features = false features = ["send-sync-client-ext", "iota-client"] optional = true -[dependencies.identity_did_methods] -version = "=1.2.0" -path = "../identity_did_methods" -default-features = false -features = ["web"] -optional = true - [dev-dependencies] identity_iota_core = { path = "../identity_iota_core", features = ["test"] } iota-sdk = { version = "1.1.5" } @@ -48,8 +42,7 @@ default = ["revocation-bitmap", "iota", "web"] revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_core?/revocation-bitmap"] # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] -# Enables the Web integration for the resolver. -web = ["dep:identity_did_methods"] +web = [] [lints] workspace = true diff --git a/identity_resolver/src/error.rs b/identity_resolver/src/error.rs index d72a78fd4a..231c79a18a 100644 --- a/identity_resolver/src/error.rs +++ b/identity_resolver/src/error.rs @@ -71,4 +71,6 @@ pub enum ErrorCause { /// No client attached to the specific network. #[error("none of the attached clients support the network {0}")] UnsupportedNetwork(String), + #[error("resolved DID different from the DID Document id")] + DidNotMatching } diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 0f5ef715e0..e6a6751aac 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -339,33 +339,39 @@ mod iota_handler { mod web_handler { use crate::ErrorCause; use super::Resolver; - use identity_did_methods::WebClient; + use identity_did::DID; use identity_document::document::CoreDocument; - use identity_did_methods::WebDID; - use identity_did_methods::WebDocument; + use identity_did::WebDID; use crate::Error; use crate::Result; impl Resolver where - DOC: From + AsRef + 'static, + DOC: From + AsRef + 'static, { /// Convenience method for attaching a new handler responsible for resolving Web DIDs. /// /// See also [`attach_handler`](Self::attach_handler). - pub fn attach_web_handler(&mut self, client: WebClient) -> Result<(), Error> + pub fn attach_web_handler(&mut self, client: reqwest::Client) -> Result<(), Error> { let handler = move |did: WebDID| { let future_client = client.clone(); async move { - future_client.get(did.to_url()) + future_client.get(did.to_url().as_ref()) .send() .await .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))? - .json::() + .json::() .await .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) })) + .and_then(|d| + if d.id().as_str() == did.as_str() { + Ok(d) + } else { + Err(Error::new(ErrorCause::DidNotMatching)) + } + ) } }; diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 4b93df241f..590d532485 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -20,7 +20,6 @@ identity_credential = { version = "=1.2.0", path = "../identity_credential", def identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false, optional = true } -identity_did_methods = { version = "=1.2.0", path = "../identity_did_methods", default-features = false, optional = true } identity_verification = { version = "=1.2.0", path = "../identity_verification", default_features = false } iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"], optional = true } json-proof-token = { workspace = true, optional = true } @@ -39,15 +38,13 @@ once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } [features] -default = ["iota-document", "web-document", "memstore"] +default = ["iota-document", "memstore"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto"] # Enables `Send` + `Sync` bounds for the storage traits. send-sync-storage = [] # Implements the JwkStorageDocumentExt trait for IotaDocument iota-document = ["dep:identity_iota_core"] -# Implements the JwkStorageDocumentExt trait for WebDocument -web-document = ["dep:identity_did_methods"] # Enables JSON Proof Token & BBS+ related features jpt-bbs-plus = ["identity_credential/jpt-bbs-plus", "dep:zkryptium", "dep:json-proof-token"] diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index c3bc6dc645..3b08f3c99e 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -634,105 +634,105 @@ mod iota_document { } -// ==================================================================================================================== -// WebDocument -// ==================================================================================================================== - -// TODO: Web DID - JwkDocumentExt for WebDocument -#[cfg(feature = "web-document")] -mod web_document { - use super::*; - use identity_credential::credential::Jwt; - use identity_did_methods::WebDocument; - - generate_method_for_document_type!( - WebDocument, - JwsAlgorithm, - JwkStorage, - JwkStorage::generate, - generate_method_iota_document - ); - purge_method_for_document_type!(WebDocument, purge_method_iota_document); - - #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] - #[cfg_attr(feature = "send-sync-storage", async_trait)] - impl JwkDocumentExt for WebDocument { - async fn generate_method( - &mut self, - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResult - where - K: JwkStorage, - I: KeyIdStorage, - { - generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await - } - - async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> - where - K: JwkStorage, - I: KeyIdStorage, - { - purge_method_iota_document(self, storage, id).await - } - - async fn create_jws( - &self, - storage: &Storage, - fragment: &str, - payload: &[u8], - options: &JwsSignatureOptions, - ) -> StorageResult - where - K: JwkStorage, - I: KeyIdStorage, - { - self - .core_document() - .create_jws(storage, fragment, payload, options) - .await - } - - async fn create_credential_jwt( - &self, - credential: &Credential, - storage: &Storage, - fragment: &str, - options: &JwsSignatureOptions, - custom_claims: Option, - ) -> StorageResult - where - K: JwkStorage, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync, - { - self - .core_document() - .create_credential_jwt(credential, storage, fragment, options, custom_claims) - .await - } - async fn create_presentation_jwt( - &self, - presentation: &Presentation, - storage: &Storage, - fragment: &str, - options: &JwsSignatureOptions, - jwt_options: &JwtPresentationOptions, - ) -> StorageResult - where - K: JwkStorage, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync, - CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, - { - self - .core_document() - .create_presentation_jwt(presentation, storage, fragment, options, jwt_options) - .await - } - } -} +// // ==================================================================================================================== +// // WebDocument +// // ==================================================================================================================== + +// // TODO: Web DID - JwkDocumentExt for WebDocument +// #[cfg(feature = "web-document")] +// mod web_document { +// use super::*; +// use identity_credential::credential::Jwt; +// use identity_did_methods::WebDocument; + +// generate_method_for_document_type!( +// WebDocument, +// JwsAlgorithm, +// JwkStorage, +// JwkStorage::generate, +// generate_method_iota_document +// ); +// purge_method_for_document_type!(WebDocument, purge_method_iota_document); + +// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +// #[cfg_attr(feature = "send-sync-storage", async_trait)] +// impl JwkDocumentExt for WebDocument { +// async fn generate_method( +// &mut self, +// storage: &Storage, +// key_type: KeyType, +// alg: JwsAlgorithm, +// fragment: Option<&str>, +// scope: MethodScope, +// ) -> StorageResult +// where +// K: JwkStorage, +// I: KeyIdStorage, +// { +// generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await +// } + +// async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> +// where +// K: JwkStorage, +// I: KeyIdStorage, +// { +// purge_method_iota_document(self, storage, id).await +// } + +// async fn create_jws( +// &self, +// storage: &Storage, +// fragment: &str, +// payload: &[u8], +// options: &JwsSignatureOptions, +// ) -> StorageResult +// where +// K: JwkStorage, +// I: KeyIdStorage, +// { +// self +// .core_document() +// .create_jws(storage, fragment, payload, options) +// .await +// } + +// async fn create_credential_jwt( +// &self, +// credential: &Credential, +// storage: &Storage, +// fragment: &str, +// options: &JwsSignatureOptions, +// custom_claims: Option, +// ) -> StorageResult +// where +// K: JwkStorage, +// I: KeyIdStorage, +// T: ToOwned + Serialize + DeserializeOwned + Sync, +// { +// self +// .core_document() +// .create_credential_jwt(credential, storage, fragment, options, custom_claims) +// .await +// } +// async fn create_presentation_jwt( +// &self, +// presentation: &Presentation, +// storage: &Storage, +// fragment: &str, +// options: &JwsSignatureOptions, +// jwt_options: &JwtPresentationOptions, +// ) -> StorageResult +// where +// K: JwkStorage, +// I: KeyIdStorage, +// T: ToOwned + Serialize + DeserializeOwned + Sync, +// CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, +// { +// self +// .core_document() +// .create_presentation_jwt(presentation, storage, fragment, options, jwt_options) +// .await +// } +// } +// } From 4debbacb41c52254f0b63bc1af25fa20c1decc3e Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 17 Jul 2024 15:35:58 +0200 Subject: [PATCH 041/163] VC/VP hybrid --- examples/0_basic/0_create_did.rs | 5 - examples/1_advanced/12_hybrid.rs | 288 ++++++++++ examples/Cargo.toml | 5 + .../jwt_credential_validator_hybrid.rs | 337 ++++++++++++ .../jwt_credential_validation/mod.rs | 2 + .../jwt_presentation_validator_hybrid.rs | 168 ++++++ .../jwt_presentation_validation/mod.rs | 2 + .../src/document/core_document.rs | 45 ++ identity_jose/src/jws/algorithm.rs | 18 +- identity_jose/src/jws/decoder.rs | 78 +++ identity_pqc_verifier/src/oqs_verifier.rs | 1 - identity_storage/Cargo.toml | 2 +- .../src/key_id_storage/method_digest.rs | 11 + .../src/key_storage/jwk_storage.rs | 2 +- identity_storage/src/storage/error.rs | 3 + .../src/storage/hybrid_jws_document_ext.rs | 512 ++++++++++++++++++ .../src/storage/jwk_document_ext.rs | 2 + identity_storage/src/storage/mod.rs | 2 + identity_verification/src/error.rs | 4 + .../composite_public_key.rs | 69 +++ .../src/verification_method/material.rs | 22 +- .../src/verification_method/method.rs | 1 + .../src/verification_method/mod.rs | 2 + 23 files changed, 1571 insertions(+), 10 deletions(-) create mode 100644 examples/1_advanced/12_hybrid.rs create mode 100644 identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs create mode 100644 identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs create mode 100644 identity_storage/src/storage/hybrid_jws_document_ext.rs create mode 100644 identity_verification/src/verification_method/composite_public_key.rs diff --git a/examples/0_basic/0_create_did.rs b/examples/0_basic/0_create_did.rs index 7e3238aaf5..e797e16f9b 100644 --- a/examples/0_basic/0_create_did.rs +++ b/examples/0_basic/0_create_did.rs @@ -4,24 +4,19 @@ use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; -use identity_iota::did::DIDUrl; use identity_iota::iota::IotaClientExt; use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwkStorage; use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::KeyIdStorage; -use identity_iota::storage::Storage; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; -use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; diff --git a/examples/1_advanced/12_hybrid.rs b/examples/1_advanced/12_hybrid.rs new file mode 100644 index 0000000000..936bd1fbd4 --- /dev/null +++ b/examples/1_advanced/12_hybrid.rs @@ -0,0 +1,288 @@ +use std::collections::HashMap; + +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::Duration; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtCredential; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::JwtCredentialValidator; +use identity_iota::credential::JwtCredentialValidatorHybrid; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorHybrid; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; +use identity_iota::did::DIDUrl; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkDocumentExtHybrid; +use identity_iota::storage::JwkGenOutput; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyIdStorage; +use identity_iota::storage::Storage; +use identity_iota::verification::jwk::Jwk; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::CompositeAlgId; +use identity_iota::verification::MethodScope; +use identity_pqc_verifier::PQCJwsVerifier; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::api::core::response::WhiteFlagResponse; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; +use serde_json::json; + +// The API endpoint of an IOTA node, e.g. Hornet. +const API_ENDPOINT: &str = "http://localhost"; +// The faucet endpoint allows requesting funds for testing purposes. +const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; + + +async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, alg_id: CompositeAlgId) -> anyhow::Result<(Address, IotaDocument, String)> { + + // Get an address with funds for testing. + let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // New Verification Method containing a PQC key + let fragment = document.generate_method_hybrid( + &storage, + alg_id, + None, + MethodScope::VerificationMethod + ).await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) +} + + +/// Demonstrates how to create a DID Document and publish it in a new Alias Output. +/// +/// In this example we connect to a locally running private network, but it can be adapted +/// to run on any IOTA node by setting the network and faucet endpoints. +/// +/// See the following instructions on running your own private network +/// https://github.com/iotaledger/hornet/tree/develop/private_tangle +#[tokio::main] +async fn main() -> anyhow::Result<()> { + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?); + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_issuer, &storage_issuer, CompositeAlgId::IdMldsa65Ed25519Sha512).await?; + + + let mut secret_manager_holder = SecretManager::Stronghold(StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?); + + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = + create_did(&client, &mut secret_manager_holder, &storage_holder, CompositeAlgId::IdMldsa65Ed25519Sha512).await?; + + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_hybrid( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + println!("Credential JWT: {}", credential_jwt.as_str()); + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_credential: DecodedJwtCredential = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + println!("Credential JSON > {:#}", decoded_credential.credential); + + + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt_hybrid( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!("Sending presentation (as JWT) to the verifier: {}", presentation_jwt.as_str()); + + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( + EdDSAJwsVerifier::default(), + PQCJwsVerifier::default() + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + // Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check + // that the credentials do not have an issuance date in the future which is a default check. + + Ok(()) +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index da17c8370b..a41ac3b4b3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -19,6 +19,7 @@ sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } +serde.workspace = true [lib] path = "utils/utils.rs" @@ -106,3 +107,7 @@ name = "10_zkp_revocation" [[example]] path = "1_advanced/11_pqc.rs" name = "11_pqc" + +[[example]] +path = "1_advanced/12_hybrid.rs" +name = "12_hybrid" \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs new file mode 100644 index 0000000000..517200d6f8 --- /dev/null +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -0,0 +1,337 @@ + +use identity_core::convert::FromJson; +use identity_did::CoreDID; +use identity_did::DIDUrl; +use identity_document::document::CoreDocument; +use identity_document::verifiable::JwsVerificationOptions; +use identity_verification::jwk::Jwk; +use identity_verification::jws::DecodedJws; +use identity_verification::jws::Decoder; +use identity_verification::jws::JwsValidationItem; +use identity_verification::jws::JwsVerifier; +use identity_verification::CompositePublicKey; + +use super::CompoundCredentialValidationError; +use super::DecodedJwtCredential; +use super::JwtCredentialValidationOptions; +use super::JwtCredentialValidatorUtils; +use super::JwtValidationError; +use super::SignerContext; +use crate::credential::Credential; +use crate::credential::CredentialJwtClaims; +use crate::credential::Jwt; +use crate::validator::FailFast; + +/// A type for decoding and validating [`Credential`]s. +#[non_exhaustive] +pub struct JwtCredentialValidatorHybrid(TRV, PQV); + +impl JwtCredentialValidatorHybrid { + /// Create a new [`JwtCredentialValidator`] that delegates cryptographic signature verification to the given + /// `signature_verifier`. + pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self { + Self(traditional_signature_verifier, pq_signature_verifier) + } + + /// Decodes and validates a [`Credential`] issued as a JWT. A [`DecodedJwtCredential`] is returned upon success. + /// + /// The following properties are validated according to `options`: + /// - the issuer's signature on the JWS, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + /// + /// # Warning + /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be + /// trusted. This section contains more information on additional checks that should be carried out before and after + /// calling this method. + /// + /// ## The state of the issuer's DID Document + /// The caller must ensure that `issuer` represents an up-to-date DID Document. + /// + /// ## Properties that are not validated + /// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: + /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. + /// These should be manually checked after validation, according to your requirements. + /// + /// # Errors + /// An error is returned whenever a validated condition is not satisfied. + pub fn validate( + &self, + credential_jwt: &Jwt, + issuer: &DOC, + options: &JwtCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + let credential_token = self + .verify_signature( + credential_jwt, + std::slice::from_ref(issuer.as_ref()), + &options.verification_options, + ) + .map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; + + Self::validate_decoded_credential::( + credential_token, + std::slice::from_ref(issuer.as_ref()), + options, + fail_fast, + ) + } + + /// Decode and verify the JWS signature of a [`Credential`] issued as a JWT using the DID Document of a trusted + /// issuer. + /// + /// A [`DecodedJwtCredential`] is returned upon success. + /// + /// # Warning + /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date. + /// + /// ## Proofs + /// Only the JWS signature is verified. If the [`Credential`] contains a `proof` property this will not be verified + /// by this method. + /// + /// # Errors + /// This method immediately returns an error if + /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt + /// to verify the credential's signature will be made and an error is returned upon failure. + pub fn verify_signature( + &self, + credential: &Jwt, + trusted_issuers: &[DOC], + options: &JwsVerificationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + Self::verify_signature_with_verifiers(&self.0, &self.1, credential, trusted_issuers, options) + } + + // This method takes a slice of issuer's instead of a single issuer in order to better accommodate presentation + // validation. It also validates the relationship between a holder and the credential subjects when + // `relationship_criterion` is Some. + pub(crate) fn validate_decoded_credential( + credential_token: DecodedJwtCredential, + issuers: &[DOC], + options: &JwtCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + let credential: &Credential = &credential_token.credential; + // Run all single concern Credential validations in turn and fail immediately if `fail_fast` is true. + + let expiry_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_expires_on_or_after( + &credential_token.credential, + options.earliest_expiry_date.unwrap_or_default(), + ) + }); + + let issuance_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_issued_on_or_before( + credential, + options.latest_issuance_date.unwrap_or_default(), + ) + }); + + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); + + let subject_holder_validation = std::iter::once_with(|| { + options + .subject_holder_relationship + .as_ref() + .map(|(holder, relationship)| { + JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship) + }) + .unwrap_or(Ok(())) + }); + + let validation_units_iter = issuance_date_validation + .chain(expiry_date_validation) + .chain(structure_validation) + .chain(subject_holder_validation); + + #[cfg(feature = "revocation-bitmap")] + let validation_units_iter = { + let revocation_validation = + std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status)); + validation_units_iter.chain(revocation_validation) + }; + + let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); + let validation_errors: Vec = match fail_fast { + FailFast::FirstError => validation_units_error_iter.take(1).collect(), + FailFast::AllErrors => validation_units_error_iter.collect(), + }; + + if validation_errors.is_empty() { + Ok(credential_token) + } else { + Err(CompoundCredentialValidationError { validation_errors }) + } + } + + + pub(crate) fn parse_composite_pk<'a, 'i, DOC>( + jws: &JwsValidationItem<'a>, + trusted_issuers: &'i [DOC], + options: &JwsVerificationOptions, + ) -> Result<(&'a CompositePublicKey, DIDUrl), JwtValidationError> + where + DOC: AsRef, + 'i: 'a, + { + let nonce: Option<&str> = options.nonce.as_deref(); + // Validate the nonce + if jws.nonce() != nonce { + return Err(JwtValidationError::JwsDecodingError( + identity_verification::jose::error::Error::InvalidParam("invalid nonce value"), + )); + } + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier + // of a verification method in a trusted issuer's DID document. + let method_id: DIDUrl = + match &options.method_id { + Some(method_id) => method_id.clone(), + None => { + let kid: &str = jws.protected_header().and_then(|header| header.kid()).ok_or( + JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract kid from protected header", + signer_ctx: SignerContext::Issuer, + }, + )?; + + // Convert kid to DIDUrl + DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError { + source: Some(err.into()), + message: "could not parse kid as a DID Url", + signer_ctx: SignerContext::Issuer, + })? + } + }; + + // locate the corresponding issuer + let issuer: &CoreDocument = trusted_issuers + .iter() + .map(AsRef::as_ref) + .find(|issuer_doc| ::id(issuer_doc) == method_id.did()) + .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?; + + // Obtain the public key from the issuer's DID document + issuer + .resolve_method(&method_id, options.method_scope) + .and_then(|method| method.data().composite_public_key()) + .ok_or_else(|| JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract CompositePublicKey from a method identified by kid", + signer_ctx: SignerContext::Issuer, + }) + .map(move |c: &CompositePublicKey| (c, method_id)) + } + + /// Stateless version of [`Self::verify_signature`] + fn verify_signature_with_verifiers( + traditional_signature_verifier: &TRV, + pq_signature_verifier: &PQV, + credential: &Jwt, + trusted_issuers: &[DOC], + options: &JwsVerificationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + // S: JwsVerifier, + { + // Note the below steps are necessary because `CoreDocument::verify_jws` decodes the JWS and then searches for a + // method with a fragment (or full DID Url) matching `kid` in the given document. We do not want to carry out + // that process for potentially every document in `trusted_issuers`. + + // Start decoding the credential + let decoded: JwsValidationItem<'_> = Self::decode(credential.as_str())?; + + let (composite, method_id) = Self::parse_composite_pk(&decoded, trusted_issuers, options)?; + + + let credential_token = Self::verify_decoded_signature(decoded, composite.traditional_public_key(), composite.pq_public_key(), traditional_signature_verifier, pq_signature_verifier)?; + + // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before + // returning. + let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?; + if &issuer_id != method_id.did() { + return Err(JwtValidationError::IdentifierMismatch { + signer_ctx: SignerContext::Issuer, + }); + }; + Ok(credential_token) + } + + /// Decode the credential into a [`JwsValidationItem`]. + pub(crate) fn decode(credential_jws: &str) -> Result, JwtValidationError> { + let decoder: Decoder = Decoder::new(); + + decoder + .decode_compact_serialization(credential_jws.as_bytes(), None) + .map_err(JwtValidationError::JwsDecodingError) + } + + pub(crate) fn verify_signature_raw<'a>( + decoded: JwsValidationItem<'a>, + traditional_pk: &Jwk, + pq_pk: &Jwk, + traditional_verifier: &TRV, + pq_verifier: &PQV, + ) -> Result, JwtValidationError> { + + decoded + .verify_hybrid(traditional_verifier, pq_verifier, traditional_pk, pq_pk) + .map_err(|err| JwtValidationError::Signature { + source: err, + signer_ctx: SignerContext::Issuer, + }) + } + + /// Verify the signature using the given `public_key` and `signature_verifier`. + fn verify_decoded_signature( + decoded: JwsValidationItem<'_>, + traditional_pk: &Jwk, + pq_pk: &Jwk, + traditional_verifier: &TRV, + pq_verifier: &PQV, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + // Verify the JWS signature and obtain the decoded token containing the protected header and raw claims + let DecodedJws { protected, claims, .. } = Self::verify_signature_raw(decoded, traditional_pk, pq_pk, traditional_verifier, pq_verifier)?; + + let credential_claims: CredentialJwtClaims<'_, T> = + CredentialJwtClaims::from_json_slice(&claims).map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + let custom_claims = credential_claims.custom.clone(); + + // Construct the credential token containing the credential and the protected header. + let credential: Credential = credential_claims + .try_into_credential() + .map_err(JwtValidationError::CredentialStructure)?; + + Ok(DecodedJwtCredential { + credential, + header: Box::new(protected), + custom_claims, + }) + } +} \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_credential_validation/mod.rs b/identity_credential/src/validator/jwt_credential_validation/mod.rs index 25c0d4f494..50c1b1a46f 100644 --- a/identity_credential/src/validator/jwt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jwt_credential_validation/mod.rs @@ -7,9 +7,11 @@ mod error; mod jwt_credential_validation_options; mod jwt_credential_validator; mod jwt_credential_validator_utils; +mod jwt_credential_validator_hybrid; pub use decoded_jwt_credential::*; pub use error::*; pub use jwt_credential_validation_options::*; pub use jwt_credential_validator::*; pub use jwt_credential_validator_utils::*; +pub use jwt_credential_validator_hybrid::*; \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs new file mode 100644 index 0000000000..a0472eaf09 --- /dev/null +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -0,0 +1,168 @@ + +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::convert::FromJson; +use identity_did::CoreDID; +use identity_document::document::CoreDocument; +use identity_document::utils::DIDUrlQuery; +use identity_verification::jws::DecodedJws; +use identity_verification::jws::Decoder; +use identity_verification::jws::JwsVerifier; +use std::str::FromStr; + +use crate::credential::Jwt; +use crate::presentation::Presentation; +use crate::presentation::PresentationJwtClaims; +use crate::validator::jwt_credential_validation::JwtValidationError; +use crate::validator::jwt_credential_validation::SignerContext; + +use super::CompoundJwtPresentationValidationError; +use super::DecodedJwtPresentation; +use super::JwtPresentationValidationOptions; + +/// Struct for validating [`Presentation`]. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct JwtPresentationValidatorHybrid(TRV, PQV); + +impl JwtPresentationValidatorHybrid +where + TRV: JwsVerifier, + PQV: JwsVerifier +{ + /// Creates a new [`JwtPresentationValidator`] using a specific [`JwsVerifier`]. + pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self { + Self(traditional_signature_verifier, pq_signature_verifier) + } + + /// Validates a [`Presentation`]. + /// + /// The following properties are validated according to `options`: + /// - the JWT can be decoded into a semantically valid presentation. + /// - the expiration and issuance date contained in the JWT claims. + /// - the holder's signature. + /// + /// Validation is done with respect to the properties set in `options`. + /// + /// # Warning + /// + /// * This method does NOT validate the constituent credentials and therefore also not the relationship between the + /// credentials' subjects and the presentation holder. This can be done with + /// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions). + /// * The lack of an error returned from this method is in of itself not enough to conclude that the presentation can + /// be trusted. This section contains more information on additional checks that should be carried out before and + /// after calling this method. + /// + /// ## The state of the supplied DID Documents. + /// + /// The caller must ensure that the DID Documents in `holder` and `issuers` are up-to-date. + /// + /// # Errors + /// + /// An error is returned whenever a validated condition is not satisfied or when decoding fails. + pub fn validate( + &self, + presentation: &Jwt, + holder: &HDOC, + options: &JwtPresentationValidationOptions, + ) -> Result, CompoundJwtPresentationValidationError> + where + HDOC: AsRef + ?Sized, + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + CRED: ToOwned + serde::Serialize + serde::de::DeserializeOwned + Clone, + { + // Verify JWS. + let decoded_jws: DecodedJws<'_> = holder + .as_ref() + .verify_jws_hybrid( + presentation.as_str(), + None, + &self.0, + &self.1, + &options.presentation_verifier_options, + ) + .map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationJwsError(err)) + })?; + + let claims: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::from_json_slice(&decoded_jws.claims) + .map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure( + crate::Error::JwtClaimsSetDeserializationError(err.into()), + )) + })?; + + // Verify that holder document matches holder in presentation. + let holder_did: CoreDID = CoreDID::from_str(claims.iss.as_str()).map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Holder, + source: err.into(), + }) + })?; + + if &holder_did != ::id(holder.as_ref()) { + return Err(CompoundJwtPresentationValidationError::one_presentation_error( + JwtValidationError::DocumentMismatch(SignerContext::Holder), + )); + } + + // Check the expiration date. + let expiration_date: Option = claims + .exp + .map(|exp| { + Timestamp::from_unix(exp).map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure( + crate::Error::JwtClaimsSetDeserializationError(err.into()), + )) + }) + }) + .transpose()?; + + (expiration_date.is_none() || expiration_date >= Some(options.earliest_expiry_date.unwrap_or_default())) + .then_some(()) + .ok_or(CompoundJwtPresentationValidationError::one_presentation_error( + JwtValidationError::ExpirationDate, + ))?; + + // Check issuance date. + let issuance_date: Option = match claims.issuance_date { + Some(iss) => { + if iss.iat.is_some() || iss.nbf.is_some() { + Some(iss.to_issuance_date().map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure( + crate::Error::JwtClaimsSetDeserializationError(err.into()), + )) + })?) + } else { + None + } + } + None => None, + }; + + (issuance_date.is_none() || issuance_date <= Some(options.latest_issuance_date.unwrap_or_default())) + .then_some(()) + .ok_or(CompoundJwtPresentationValidationError::one_presentation_error( + JwtValidationError::IssuanceDate, + ))?; + + let aud: Option = claims.aud.clone(); + let custom_claims: Option = claims.custom.clone(); + + let presentation: Presentation = claims.try_into_presentation().map_err(|err| { + CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(err)) + })?; + + let decoded_jwt_presentation: DecodedJwtPresentation = DecodedJwtPresentation { + presentation, + header: Box::new(decoded_jws.protected), + expiration_date, + issuance_date, + aud, + custom_claims, + }; + + Ok(decoded_jwt_presentation) + } +} \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_presentation_validation/mod.rs b/identity_credential/src/validator/jwt_presentation_validation/mod.rs index a9bb144080..37866c58a8 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/mod.rs @@ -6,9 +6,11 @@ mod error; mod jwt_presentation_validation_options; mod jwt_presentation_validator; mod jwt_presentation_validator_utils; +mod jwt_presentation_validator_hybrid; pub use decoded_jwt_presentation::*; pub use error::*; pub use jwt_presentation_validation_options::*; pub use jwt_presentation_validator::*; pub use jwt_presentation_validator_utils::*; +pub use jwt_presentation_validator_hybrid::*; diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 1b226f9585..379ff8cd3c 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -11,6 +11,7 @@ use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::DecodedJws; use identity_verification::jose::jws::Decoder; use identity_verification::jose::jws::JwsVerifier; +use identity_verification::CompositePublicKey; use serde::Serialize; use identity_core::common::Object; @@ -982,6 +983,50 @@ impl CoreDocument { .verify(signature_verifier, public_key) .map_err(Error::JwsVerificationError) } + + pub fn verify_jws_hybrid<'jws, TRV: JwsVerifier, PQV: JwsVerifier>( + &self, + jws: &'jws str, + detached_payload: Option<&'jws [u8]>, + traditional_verifier: &TRV, + pq_verifier: &PQV, + options: &JwsVerificationOptions, + ) -> Result> { + let validation_item = Decoder::new() + .decode_compact_serialization(jws.as_bytes(), detached_payload) + .map_err(Error::JwsVerificationError)?; + + let nonce: Option<&str> = options.nonce.as_deref(); + // Validate the nonce + if validation_item.nonce() != nonce { + return Err(Error::JwsVerificationError( + identity_verification::jose::error::Error::InvalidParam("invalid nonce value"), + )); + } + + let method_url_query: DIDUrlQuery<'_> = match &options.method_id { + Some(method_id) => method_id.into(), + None => validation_item + .kid() + .ok_or(Error::JwsVerificationError( + identity_verification::jose::error::Error::InvalidParam("missing kid value"), + ))? + .into(), + }; + + let composite_public_key = self + .resolve_method(method_url_query, options.method_scope) + .ok_or(Error::MethodNotFound)? + .data() + .try_composite_public_key() + .map_err(Error::InvalidKeyMaterial)?; + + validation_item + .verify_hybrid(traditional_verifier, pq_verifier, composite_public_key.traditional_public_key(), composite_public_key.pq_public_key()) + .map_err(Error::JwsVerificationError) + } + + } #[cfg(test)] diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index c83b2828be..884037d20e 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -92,7 +92,13 @@ pub enum JwsAlgorithm { SLH_DSA_SHAKE_256f, FALCON512, - FALCON1024 + FALCON1024, + + #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] + IdMldsa44Ed25519Sha512, + + #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] + IdMldsa65Ed25519Sha512 } impl JwsAlgorithm { @@ -133,6 +139,10 @@ impl JwsAlgorithm { Self::FALCON512, Self::FALCON1024, + + Self::IdMldsa44Ed25519Sha512, + Self::IdMldsa65Ed25519Sha512, + ]; /// Returns the JWS algorithm as a `str` slice. @@ -172,6 +182,9 @@ impl JwsAlgorithm { Self::FALCON512 => "FALCON512", Self::FALCON1024 => "FALCON1024", + + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", + Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", } } } @@ -215,6 +228,9 @@ impl FromStr for JwsAlgorithm { "FALCON512" => Ok(Self::FALCON512), "FALCON1024" => Ok(Self::FALCON1024), + + "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), + "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), _ => Err(Error::JwsAlgorithmParsingError), } } diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 6b93488acf..8cf889fe0d 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -3,7 +3,11 @@ use core::str; use std::borrow::Cow; +use std::ops::Deref; +use crypto::signatures::ed25519::Signature; +use identity_core::common::SingleStructError; +use crypto::hashes::Digest; use crate::error::Error; use crate::error::Result; use crate::jwk::Jwk; @@ -175,6 +179,80 @@ impl<'a> JwsValidationItem<'a> { claims, }) } + + + //TODO: hybrid - verify_hybrid + pub fn verify_hybrid(self, traditional_verifier: &TRV, pq_verifier: &PQV, traditional_pk: &Jwk, pq_pk: &Jwk) -> Result> + where + TRV: JwsVerifier, + PQV: JwsVerifier + { + // Destructure data + let JwsValidationItem { + headers, + claims, + signing_input, + decoded_signature, + } = self; + + let (protected, unprotected): (JwsHeader, Option>) = match headers { + DecodedHeaders::Protected(protected) => (protected, None), + DecodedHeaders::Both { protected, unprotected } => (protected, Some(unprotected)), + DecodedHeaders::Unprotected(_) => return Err(Error::MissingHeader("missing protected header")), + }; + + // Extract and validate alg from the protected header. + let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?; + let (t_alg, pq_alg, signing_input, traditional_signature_len) = match alg { + JwsAlgorithm::IdMldsa44Ed25519Sha512 => { + //TODO: hybrid - DER OID + let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03]; + input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + (JwsAlgorithm::EdDSA, JwsAlgorithm::ML_DSA_44, input, crypto::signatures::ed25519::Signature::LENGTH) + }, + JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A]; + input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + (JwsAlgorithm::EdDSA, JwsAlgorithm::ML_DSA_65, input, crypto::signatures::ed25519::Signature::LENGTH) + }, + _ => return Err(Error::JwsAlgorithmParsingError) + }; + + traditional_pk.check_alg(t_alg.name())?; + pq_pk.check_alg(pq_alg.name())?; + + let extracted_signature_t = &decoded_signature[..traditional_signature_len]; + let extracted_signature_pq = &decoded_signature[traditional_signature_len..]; + + // println!("SIGN 2 = {:#?}", signing_input); + // Construct verification input + let input1 = VerificationInput { + alg: t_alg, + signing_input: signing_input.clone().into(), + decoded_signature: extracted_signature_t.into(), + }; + + // Call verifier + traditional_verifier + .verify(input1, traditional_pk) + .map_err(Error::SignatureVerificationError)?; + + let input2 = VerificationInput { + alg: pq_alg, + signing_input: signing_input.into(), + decoded_signature: extracted_signature_pq.into(), + }; + // Call verifier + pq_verifier + .verify(input2, pq_pk) + .map_err(Error::SignatureVerificationError)?; + + Ok(DecodedJws { + protected, + unprotected, + claims, + }) + } } // ============================================================================================= diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 8ed75fdecf..7a24106273 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -17,7 +17,6 @@ impl OQSVerifier { /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { - // Obtain an ML-DSA-44 public key. let params: &JwkParamsPQ = public_key .try_pq_params() diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 431b06b8f0..4ee01878ad 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -22,7 +22,7 @@ identity_did = { version = "=1.3.1", path = "../identity_did", default-features identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false, optional = true } identity_verification = { version = "=1.3.1", path = "../identity_verification", default_features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"], optional = true } +iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } seahash = { version = "4.1.0", default_features = false } diff --git a/identity_storage/src/key_id_storage/method_digest.rs b/identity_storage/src/key_id_storage/method_digest.rs index c6eb4fd59d..31a04da15d 100644 --- a/identity_storage/src/key_id_storage/method_digest.rs +++ b/identity_storage/src/key_id_storage/method_digest.rs @@ -1,12 +1,15 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_core::convert::ToJson; +use identity_verification::CustomMethodData; use identity_verification::MethodData; use identity_verification::VerificationMethod; use seahash::SeaHasher; use std::fmt::Display; use std::hash::Hasher; + use super::KeyIdStorageError; /// Error that may occur when constructing a [`MethodDigest`]. @@ -22,6 +25,7 @@ pub enum MethodDigestConstructionErrorKind { MissingIdFragment, /// Caused by a failure to decode a method's [key material](identity_verification::MethodData). DataDecodingFailure, + } impl Display for MethodDigestConstructionErrorKind { @@ -56,6 +60,13 @@ impl MethodDigest { match method_data { MethodData::PublicKeyJwk(jwk) => hasher.write(jwk.thumbprint_sha256().as_ref()), + // MethodData::Custom(e) => hasher.write(&e.to_json_vec().unwrap()), //TODO: Hybrid - to be changed + MethodData::CompositePublicKey(composite) => { + let algid = composite.alg_id().to_json_vec().map_err(|err| MethodDigestConstructionError::new(DataDecodingFailure).with_source(err))?; + hasher.write(&algid); + hasher.write(composite.traditional_public_key().thumbprint_sha256().as_ref()); + hasher.write(composite.pq_public_key().thumbprint_sha256().as_ref()); + } _ => hasher.write( &method_data .try_decode() diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 851fa00992..6d56b09914 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -96,4 +96,4 @@ pub trait JwkStoragePQ : JwkStorage { /// Sign the provided `data` using a PQ algorithm async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult>; -} \ No newline at end of file +} diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index a5d8d11185..b22fbf24b6 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -24,6 +24,9 @@ pub enum JwkStorageDocumentError { /// Caused by the usage of a non-JWK method where a JWK method is expected. #[error("invalid method data format: expected publicKeyJwk")] NotPublicKeyJwk, + /// Caused by the usage of a non-Composite method where a Composite method is expected. + #[error("invalid method data format: expected compositePublicKey")] + NotCompositePublicKey, //TODO: hybrid - new error /// Caused by an invalid JWS algorithm. #[error("invalid JWS algorithm")] InvalidJwsAlgorithm, diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs new file mode 100644 index 0000000000..43e2018828 --- /dev/null +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -0,0 +1,512 @@ +use std::borrow::Cow; +use std::ops::Deref; + +use crypto::hashes::Digest; +use identity_core::common::Object; +use identity_core::convert::ToJson; +use identity_credential::credential::{Credential, Jws, Jwt}; +use identity_credential::presentation::{JwtPresentationOptions, Presentation}; +use identity_did::{DIDUrl, DID}; +use identity_document::document::{self, CoreDocument}; +use identity_verification::jws::{CharSet, CompactJwsEncoder, CompactJwsEncodingOptions, JwsHeader}; +use identity_verification::{jwk::Jwk, jws::JwsAlgorithm, CustomMethodData, MethodBuilder, MethodScope, MethodType, VerificationMethod}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json::json; + +use crate::{JwsSignatureOptions, KeyIdStorageError, KeyIdStorageErrorKind}; +use crate::{try_undo_key_generation, JwkGenOutput, JwkMemStore, JwkStorage, JwkStoragePQ, KeyId, KeyIdStorage, KeyType, MethodDigest, Storage}; +use async_trait::async_trait; +use super::JwkStorageDocumentError as Error; +use identity_verification::{CompositeAlgId, MethodData}; +use identity_verification::CompositePublicKey; + +pub type StorageResultHybrid = Result; + + + + +macro_rules! generate_method_hybrid_for_document_type { + ($t:ty, $name:ident) => { +async fn $name( + document: &mut $t, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, +{ + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { + CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_44, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA + ), + CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_65, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA + ), + }; + + let JwkGenOutput { key_id: t_key_id, jwk: t_jwk } = K::generate(storage.key_storage(), trad_key_type, trad_alg) + .await + .map_err(Error::KeyStorageError)?; + + let JwkGenOutput { key_id: pq_key_id, jwk: pq_jwk } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; + + // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) + let composite_fragment = t_jwk.kid().map(|s| s.to_string()).or_else(|| pq_jwk.kid().map(|s| s.to_string())).map(|s| { + if let (Some(str1), Some(str2)) = (t_jwk.kid(), pq_jwk.kid()) { + format!("{}~{}", str1, str2) + } else { + s + } + }); + + let composite_kid = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + + let fragment: Cow<'_, str> = { + let given_fragment: &str = fragment + .or_else(|| composite_fragment.as_deref()) + .ok_or(identity_verification::Error::InvalidMethod( + "an explicit fragment or JWK kid is required", + )) + .map_err(Error::VerificationMethodConstructionError)?; + // Make sure the fragment starts with "#" + if given_fragment.starts_with('#') { + Cow::Borrowed(given_fragment) + } else { + Cow::Owned(format!("#{given_fragment}")) + } + }; + + let id: DIDUrl = document.id().to_url().join(fragment) + .map_err(identity_verification::Error::DIDUrlConstructionError) + .map_err(Error::VerificationMethodConstructionError)?; + + + let composite_pk = CompositePublicKey::new(alg_id, t_jwk, pq_jwk ); + + // Produce a new verification method containing the generated JWK. If this operation fails we handle the error + // by attempting to revert key generation before returning an error. + let method: VerificationMethod = { + match MethodBuilder::default() + .id(id) + .type_(MethodType::custom("CompositeSignaturePublicKey")) + .controller(document.id().clone().into()) + .data(identity_verification::MethodData::CompositePublicKey(composite_pk)) + .build() + .map_err(Error::VerificationMethodConstructionError) + { + Ok(method) => method, + Err(source) => { + let error = try_undo_key_generation(storage, &t_key_id, source).await; + let error = try_undo_key_generation(storage, &pq_key_id, error).await; + return Err(error) + } + } + }; + + + + + // Extract data from method before inserting it into the DID document. + let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; + let method_id: DIDUrl = method.id().clone(); + + // The fragment is always set on a method, so this error will never occur. + let fragment: String = method_id + .fragment() + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)? + .to_owned(); + + // Insert method into document and handle error upon failure. + if let Err(error) = document + .insert_method(method, scope) + .map_err(|_| Error::FragmentAlreadyExists) + { + let error = try_undo_key_generation(storage, &t_key_id, error).await; + let error = try_undo_key_generation(storage, &pq_key_id, error).await; + return Err(error) + }; + + // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the + // operation fails. + if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, composite_kid) + .await + .map_err(Error::KeyIdStorageError) + { + // Remove the method from the document as it can no longer be used. + let _ = document.remove_method(&method_id); + let error = try_undo_key_generation(storage, &t_key_id, error).await; + let error = try_undo_key_generation(storage, &pq_key_id, error).await; + return Err(error) + } + + Ok(fragment) +} + } +} + + +///New trait to handle JWP-based operations on DID Documents +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwkDocumentExtHybrid { + + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. This support BBS+ keys. + async fn generate_method_hybrid( + &mut self, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage; + + async fn create_jws( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage; + + async fn create_credential_jwt_hybrid( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync; + + + async fn create_presentation_jwt_hybrid( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + signature_options: &JwsSignatureOptions, + presentation_options: &JwtPresentationOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync; + +} + +generate_method_hybrid_for_document_type!(CoreDocument, generate_method_hybrid_core_document); + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwkDocumentExtHybrid for CoreDocument { + async fn generate_method_hybrid( + &mut self, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + { + generate_method_hybrid_core_document(self, storage, alg_id, fragment, scope).await + } + + async fn create_jws( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::CompositePublicKey(ref composite) = method.data() else { + return Err(Error::NotCompositePublicKey); + }; + + let alg_id = composite.alg_id(); + let t_jwk = composite.traditional_public_key(); + let pq_jwk = composite.pq_public_key(); + + // Extract JwsAlgorithm. + let alg: JwsAlgorithm = alg_id + .name() + .parse() + .map_err(|_| Error::InvalidJwsAlgorithm)?; + + // Create JWS header in accordance with options. + let header: JwsHeader = { + let mut header = JwsHeader::new(); + + header.set_alg(alg); + if let Some(custom) = &options.custom_header_parameters { + header.set_custom(custom.clone()) + } + + if let Some(ref kid) = options.kid { + header.set_kid(kid.clone()); + } else { + header.set_kid(method.id().to_string()); + } + + if let Some(b64) = options.b64 { + // Follow recommendation in https://datatracker.ietf.org/doc/html/rfc7797#section-7. + if !b64 { + header.set_b64(b64); + header.set_crit(["b64"]); + } + }; + + if let Some(typ) = &options.typ { + header.set_typ(typ.clone()) + } else { + // https://www.w3.org/TR/vc-data-model/#jwt-encoding + header.set_typ("JWT") + } + + if let Some(cty) = &options.cty { + header.set_cty(cty.clone()) + }; + + if let Some(url) = &options.url { + header.set_url(url.clone()) + }; + + if let Some(nonce) = &options.nonce { + header.set_nonce(nonce.clone()) + }; + + header + }; + + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id: KeyId = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + let (t_key_id, pq_key_id) = key_id.as_str().split_once("~") + .map(|v| (KeyId::new(v.0), KeyId::new(v.1))).ok_or(Error::KeyIdStorageError(KeyIdStorageErrorKind::Unspecified.into()))?; + + // Extract Compact JWS encoding options. + let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload { + // We use this as a default and don't provide the extra UrlSafe check for now. + // Applications that require such checks can easily do so after JWS creation. + CompactJwsEncodingOptions::NonDetached { + charset_requirements: CharSet::Default, + } + } else { + CompactJwsEncodingOptions::Detached + }; + + let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) + .map_err(|err| Error::EncodingError(err.into()))?; + + let signing_input = match alg { + JwsAlgorithm::IdMldsa44Ed25519Sha512 => { + //TODO: hybrid - DER OID + let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03]; + input.extend(crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()).deref().to_vec()); + input + }, + JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A]; + input.extend(crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()).deref().to_vec()); + input + }, + _ => return Err(Error::InvalidJwsAlgorithm) + }; + + let signature_t = ::sign(storage.key_storage(), &t_key_id, &signing_input, t_jwk) + .await + .map_err(Error::KeyStorageError)?; + + let signature_pq = ::pq_sign(storage.key_storage(), &pq_key_id, &signing_input, pq_jwk) + .await + .map_err(Error::KeyStorageError)?; + + let signature = [signature_t, signature_pq].concat(); + + Ok(Jws::new(jws_encoder.into_jws(&signature))) + } + + async fn create_credential_jwt_hybrid( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync { + if options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for credential signing", + ))); + } + + if !options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } + + let payload = credential + .serialize_jwt(custom_claims) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws(storage, fragment, payload.as_bytes(), options) + .await + .map(|jws| Jwt::new(jws.into())) + } + + + async fn create_presentation_jwt_hybrid( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + jws_options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync + { + if jws_options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for presentation signing", + ))); + } + + if !jws_options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } + let payload = presentation + .serialize_jwt(jwt_options) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws(storage, fragment, payload.as_bytes(), jws_options) + .await + .map(|jws| Jwt::new(jws.into())) + } +} + + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use crate::StorageResult; + + use super::*; + use identity_credential::credential::Jwt; + use identity_iota_core::IotaDocument; + + generate_method_hybrid_for_document_type!(IotaDocument, generate_method_hybrid_iota_document); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwkDocumentExtHybrid for IotaDocument { + async fn generate_method_hybrid( + &mut self, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + { + generate_method_hybrid_iota_document(self, storage, alg_id, fragment, scope).await + } + + async fn create_jws( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage { + self + .core_document() + .create_jws(storage, fragment, payload, options) + .await + } + + async fn create_credential_jwt_hybrid( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync { + self + .core_document() + .create_credential_jwt_hybrid(credential, storage, fragment, options, custom_claims) + .await + } + + async fn create_presentation_jwt_hybrid( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResultHybrid + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync + { + self + .core_document() + .create_presentation_jwt_hybrid(presentation, storage, fragment, options, jwt_options) + .await + } + + } +} diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index 340eca2e78..e319a57e8a 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -529,6 +529,8 @@ where } } + + // ==================================================================================================================== // IotaDocument // ==================================================================================================================== diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index b93c747b95..2ab8e94cc0 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -13,6 +13,7 @@ mod signature_options; mod timeframe_revocation_ext; mod pqc_jws_document_ext; +mod hybrid_jws_document_ext; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -27,6 +28,7 @@ pub use signature_options::*; pub use timeframe_revocation_ext::*; pub use pqc_jws_document_ext::*; +pub use hybrid_jws_document_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from diff --git a/identity_verification/src/error.rs b/identity_verification/src/error.rs index 97070de3bf..f613b43672 100644 --- a/identity_verification/src/error.rs +++ b/identity_verification/src/error.rs @@ -39,4 +39,8 @@ pub enum Error { /// Caused by key material that is not a JSON Web Key. #[error("verification material format is not publicKeyJwk")] NotPublicKeyJwk, + //TODO: hybrid - new error + /// Caused by key material that is not a Composite Public Key. + #[error("verification material format is not compositePublicKey")] + NotCompositePublicKey } diff --git a/identity_verification/src/verification_method/composite_public_key.rs b/identity_verification/src/verification_method/composite_public_key.rs new file mode 100644 index 0000000000..407af4b851 --- /dev/null +++ b/identity_verification/src/verification_method/composite_public_key.rs @@ -0,0 +1,69 @@ +//TODO: hybrid - composite public key + +use std::fmt::Display; + +use identity_jose::{jwk::Jwk, jws::JwsAlgorithm}; + +//TODO: hybrid - move to identity_jose + + +//TODO: hybrid - to be removed +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub enum CompositeAlgId { + #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] + IdMldsa44Ed25519Sha512, + + #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] + IdMldsa65Ed25519Sha512 +} + +impl CompositeAlgId { + pub fn der_oid(&self) -> &'static [u8] { + match self { + CompositeAlgId::IdMldsa44Ed25519Sha512 => &[ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, + ], + CompositeAlgId::IdMldsa65Ed25519Sha512 => &[ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, + ], + } + } + + + /// Returns the JWS algorithm as a `str` slice. + pub const fn name(self) -> &'static str { + match self { + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", + Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub struct CompositePublicKey { + #[serde(rename = "algId")] + alg_id: CompositeAlgId, + #[serde(rename = "traditionalPublicKey")] + traditional_public_key: Jwk, + #[serde(rename = "pqPublicKey")] + pq_public_key: Jwk, +} + +impl CompositePublicKey { + pub fn new(alg_id: CompositeAlgId, traditional_public_key: Jwk, pq_public_key: Jwk) -> Self { + Self { alg_id, traditional_public_key, pq_public_key } + } + + pub fn alg_id(&self) -> CompositeAlgId { + self.alg_id + } + + pub fn pq_public_key(&self) -> &Jwk { + &self.pq_public_key + } + + pub fn traditional_public_key(&self) -> &Jwk { + &self.traditional_public_key + } + +} \ No newline at end of file diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 8e881253c5..2558e11c74 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::jose::jwk::Jwk; +use crate::CompositePublicKey; use core::fmt::Debug; use core::fmt::Formatter; use identity_core::convert::BaseEncoding; @@ -27,6 +28,8 @@ pub enum MethodData { PublicKeyBase58(String), /// Verification Material in the JSON Web Key format. PublicKeyJwk(Jwk), + /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new MethodData + CompositePublicKey(CompositePublicKey), /// Arbitrary verification material. #[serde(untagged)] Custom(CustomMethodData), @@ -59,7 +62,7 @@ impl MethodData { /// represented as a vector of bytes. pub fn try_decode(&self) -> Result> { match self { - Self::PublicKeyJwk(_) | Self::Custom(_) => Err(Error::InvalidMethodDataTransformation( + Self::PublicKeyJwk(_) | Self::Custom(_) | Self::CompositePublicKey(_)=> Err(Error::InvalidMethodDataTransformation( "method data is not base encoded", )), Self::PublicKeyMultibase(input) => { @@ -69,6 +72,22 @@ impl MethodData { } } + + //TODO: hybrid - return CompositePublicKey + /// Returns the wrapped `CompositePublicKey` if the format is [`MethodData::CompositePublicKey`]. + pub fn composite_public_key(&self) -> Option<&CompositePublicKey> { + if let Self::CompositePublicKey(ref c) = self { + Some(c) + } else { + None + } + } + + /// Fallible version of [`Self::composite_public_key`](Self::composite_public_key()). + pub fn try_composite_public_key(&self) -> Result<&CompositePublicKey> { + self.composite_public_key().ok_or(Error::NotCompositePublicKey) + } + /// Returns the wrapped `Jwk` if the format is [`MethodData::PublicKeyJwk`]. pub fn public_key_jwk(&self) -> Option<&Jwk> { if let Self::PublicKeyJwk(ref jwk) = self { @@ -99,6 +118,7 @@ impl Debug for MethodData { Self::PublicKeyJwk(inner) => f.write_fmt(format_args!("PublicKeyJwk({inner:#?})")), Self::PublicKeyMultibase(inner) => f.write_fmt(format_args!("PublicKeyMultibase({inner})")), Self::PublicKeyBase58(inner) => f.write_fmt(format_args!("PublicKeyBase58({inner})")), + Self::CompositePublicKey(inner) => f.write_fmt(format_args!("CompositePublicKey({inner:#?})")), Self::Custom(CustomMethodData { name, data }) => f.write_fmt(format_args!("{name}({data})")), } } diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 65c838639f..a1fe4d8811 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -276,6 +276,7 @@ impl From<_VerificationMethod> for VerificationMethod { MethodData::PublicKeyBase58(_) => "publicKeyBase58", MethodData::PublicKeyJwk(_) => "publicKeyJwk", MethodData::PublicKeyMultibase(_) => "publicKeyMultibase", + MethodData::CompositePublicKey(_) => "compositePublicKey", MethodData::Custom(CustomMethodData { name, .. }) => name.as_str(), }; properties.remove(key); diff --git a/identity_verification/src/verification_method/mod.rs b/identity_verification/src/verification_method/mod.rs index 585b58639c..eda10926e9 100644 --- a/identity_verification/src/verification_method/mod.rs +++ b/identity_verification/src/verification_method/mod.rs @@ -13,6 +13,7 @@ mod method_ref; mod method_relationship; mod method_scope; mod method_type; +mod composite_public_key; pub use self::builder::MethodBuilder; pub use self::material::CustomMethodData; @@ -22,3 +23,4 @@ pub use self::method_ref::MethodRef; pub use self::method_relationship::MethodRelationship; pub use self::method_scope::MethodScope; pub use self::method_type::MethodType; +pub use composite_public_key::*; From 1aaa59e34495ae6933da73f9f630aed3de76f8ea Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 18 Jul 2024 15:16:17 +0200 Subject: [PATCH 042/163] minor fix and code cleaning --- examples/1_advanced/11_did_web.rs | 10 +- identity_did/src/did_web.rs | 35 ++---- identity_iota/src/lib.rs | 12 -- identity_resolver/Cargo.toml | 3 +- identity_resolver/src/error.rs | 1 + identity_resolver/src/resolution/resolver.rs | 3 +- .../src/storage/jwk_document_ext.rs | 110 +----------------- 7 files changed, 18 insertions(+), 156 deletions(-) diff --git a/examples/1_advanced/11_did_web.rs b/examples/1_advanced/11_did_web.rs index 89c4477055..727be0cd4f 100644 --- a/examples/1_advanced/11_did_web.rs +++ b/examples/1_advanced/11_did_web.rs @@ -5,7 +5,7 @@ use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::IotaDocument, resolver::Resolver, storage::{JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; use identity_iota::storage::JwkDocumentExt; use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; -use reqwest::{Certificate, ClientBuilder}; +use reqwest::ClientBuilder; use serde_json::json; pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { @@ -22,15 +22,12 @@ async fn main() -> anyhow::Result<()> { let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; // Create a new client to make HTTPS requests. - // let client = WebClient::default()?; let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) //TODO: fix problem cannot build WebClient after calling function of inner structure + .danger_accept_invalid_certs(true) .build()?; // Create a new Web DID document. let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - // let doc: WebDocument = client.get(Url::parse(did_url)?).send().await?.json().await?; - // println!("PPPPP: {}", doc); // Insert a new Ed25519 verification method in the DID document. let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); @@ -47,7 +44,6 @@ async fn main() -> anyhow::Result<()> { write_to_file(&issuer_document, Some(path_did_file))?; println!("Web DID Document: {:#}", issuer_document); - // Create a new client to interact with the IOTA ledger. let iota_client: Client = Client::builder() .with_primary_node(API_ENDPOINT, None)? @@ -190,7 +186,7 @@ async fn main() -> anyhow::Result<()> { let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; let mut resolver_web: Resolver = Resolver::new(); - resolver_web.attach_web_handler(client); + let _ = resolver_web.attach_web_handler(client)?; let issuers: Vec = jwt_credentials .iter() diff --git a/identity_did/src/did_web.rs b/identity_did/src/did_web.rs index 94f38bdec5..f1217faa81 100644 --- a/identity_did/src/did_web.rs +++ b/identity_did/src/did_web.rs @@ -27,18 +27,6 @@ impl WebDID { /// The IOTA DID method name (`"iota"`). pub const METHOD: &'static str = "web"; -// /// Convert a `CoreDID` reference to an `WebDID` reference without checking the referenced value. -// /// -// /// # Warning -// /// This method should only be called on [`CoreDIDs`](CoreDID) that -// /// are known to satisfy the requirements of the Web DID Method specification. -// /// -// /// # Memory safety -// /// -// /// The `ref-cast` crate ensures a memory safe implementation. -// #[ref_cast_custom] -// pub(crate) const fn from_inner_ref_unchecked(did: &CoreDID) -> &Self; - /// Create a new valid Web DID. pub fn new(url: &str) -> Result { let parsed_url: Url = Url::parse(url).map_err(|_| Error::Other("Not a valid Url"))?; @@ -48,7 +36,6 @@ impl WebDID { let port = parsed_url.port().map_or(String::new(), |p| format!("%3a{}", p)); - let path = parsed_url.path_segments().map_or(String::new(), |p| { format!("{}{}", ":", p.into_iter().collect::>().join(":")) }); @@ -140,14 +127,14 @@ impl WebDID { .map_or(Ok(None), |r| r.map(Some) .map_err(|_| Error::InvalidMethodId))?; - let mut url = Url::parse(&format!("https://{}", domain))//TODO: change to HTTPS + let mut url = Url::parse(&format!("https://{}", domain)) .map_err(|_| Error::InvalidMethodId)?; url.set_port(port).map_err(|_| Error::InvalidMethodId)?; path.and_then(|p| Some(url.set_path(&p))); - // url.domain().ok_or(Error::InvalidMethodId)?; //TODO: Web DID - Disabled just for testing with 127.0.0.1 + url.domain().ok_or(Error::InvalidMethodId)?; Ok(url) } @@ -169,17 +156,17 @@ impl WebDID { None => (domain.to_owned(), Some(tail[3..].to_owned()), None), } - }, - None => { - match input.find(":") { - Some(i) => { - let (domain, path) = input.split_at(i); - (domain.to_owned(), None, Some(path.replace(":", "/"))) - }, - None => (input.to_owned(), None, None), + }, + None => { + match input.find(":") { + Some(i) => { + let (domain, path) = input.split_at(i); + (domain.to_owned(), None, Some(path.replace(":", "/"))) + }, + None => (input.to_owned(), None, None), + } } } - } } diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index f5e2ef78e2..9ab2e53805 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -69,18 +69,6 @@ pub mod iota { pub use identity_iota_core::*; } -#[cfg(feature = "web-method")] -pub mod web { - //! The Web DID method implementation for the IOTA ledger. - - // pub use identity_did_methods::WebDID; - pub use identity_did_methods::WebDocument; - pub use identity_did_methods::WebClient; - pub use identity_did_methods::WebClientBuilder; -} - -//TODO: Web DID - IOTA principal module extension - pub mod prelude { //! Prelude of commonly used types diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index 0dda49f9d2..7a841f8c0a 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -38,11 +38,10 @@ iota-sdk = { version = "1.1.5" } tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } [features] -default = ["revocation-bitmap", "iota", "web"] +default = ["revocation-bitmap", "iota"] revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_core?/revocation-bitmap"] # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] -web = [] [lints] workspace = true diff --git a/identity_resolver/src/error.rs b/identity_resolver/src/error.rs index 231c79a18a..b37e6c9ecb 100644 --- a/identity_resolver/src/error.rs +++ b/identity_resolver/src/error.rs @@ -71,6 +71,7 @@ pub enum ErrorCause { /// No client attached to the specific network. #[error("none of the attached clients support the network {0}")] UnsupportedNetwork(String), + /// Resolved DID is different from the DID value inside the DID Document #[error("resolved DID different from the DID Document id")] DidNotMatching } diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index e6a6751aac..f03595eb71 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -334,8 +334,7 @@ mod iota_handler { } } -//TODO: Web - resolver handler -#[cfg(feature = "web")] + mod web_handler { use crate::ErrorCause; use super::Resolver; diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index 3b08f3c99e..d42b3b2d0f 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -142,10 +142,6 @@ mod private { impl Sealed for identity_document::document::CoreDocument {} #[cfg(feature = "iota-document")] impl Sealed for identity_iota_core::IotaDocument {} - - //TODO: Web DID - Sealed - #[cfg(feature = "web-document")] - impl Sealed for identity_did_methods::WebDocument {} } // ==================================================================================================================== @@ -631,108 +627,4 @@ mod iota_document { .await } } -} - - -// // ==================================================================================================================== -// // WebDocument -// // ==================================================================================================================== - -// // TODO: Web DID - JwkDocumentExt for WebDocument -// #[cfg(feature = "web-document")] -// mod web_document { -// use super::*; -// use identity_credential::credential::Jwt; -// use identity_did_methods::WebDocument; - -// generate_method_for_document_type!( -// WebDocument, -// JwsAlgorithm, -// JwkStorage, -// JwkStorage::generate, -// generate_method_iota_document -// ); -// purge_method_for_document_type!(WebDocument, purge_method_iota_document); - -// #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -// #[cfg_attr(feature = "send-sync-storage", async_trait)] -// impl JwkDocumentExt for WebDocument { -// async fn generate_method( -// &mut self, -// storage: &Storage, -// key_type: KeyType, -// alg: JwsAlgorithm, -// fragment: Option<&str>, -// scope: MethodScope, -// ) -> StorageResult -// where -// K: JwkStorage, -// I: KeyIdStorage, -// { -// generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await -// } - -// async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> -// where -// K: JwkStorage, -// I: KeyIdStorage, -// { -// purge_method_iota_document(self, storage, id).await -// } - -// async fn create_jws( -// &self, -// storage: &Storage, -// fragment: &str, -// payload: &[u8], -// options: &JwsSignatureOptions, -// ) -> StorageResult -// where -// K: JwkStorage, -// I: KeyIdStorage, -// { -// self -// .core_document() -// .create_jws(storage, fragment, payload, options) -// .await -// } - -// async fn create_credential_jwt( -// &self, -// credential: &Credential, -// storage: &Storage, -// fragment: &str, -// options: &JwsSignatureOptions, -// custom_claims: Option, -// ) -> StorageResult -// where -// K: JwkStorage, -// I: KeyIdStorage, -// T: ToOwned + Serialize + DeserializeOwned + Sync, -// { -// self -// .core_document() -// .create_credential_jwt(credential, storage, fragment, options, custom_claims) -// .await -// } -// async fn create_presentation_jwt( -// &self, -// presentation: &Presentation, -// storage: &Storage, -// fragment: &str, -// options: &JwsSignatureOptions, -// jwt_options: &JwtPresentationOptions, -// ) -> StorageResult -// where -// K: JwkStorage, -// I: KeyIdStorage, -// T: ToOwned + Serialize + DeserializeOwned + Sync, -// CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, -// { -// self -// .core_document() -// .create_presentation_jwt(presentation, storage, fragment, options, jwt_options) -// .await -// } -// } -// } +} \ No newline at end of file From 86dca1f62eda6211ecea9399934697ef0b747516 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 29 Jul 2024 12:20:35 +0200 Subject: [PATCH 043/163] fix and code cleaning --- examples/0_basic/0_create_did.rs | 1 - examples/1_advanced/11_pqc.rs | 92 ++- examples/1_advanced/12_hybrid.rs | 102 ++- examples/utils/utils.rs | 1 - identity_core/src/common/mod.rs | 1 - identity_core/src/common/timestamp.rs | 1 - .../src/credential/jwt_serialization.rs | 54 -- identity_credential/src/credential/mod.rs | 9 +- .../jwt_credential_validation/error.rs | 2 +- .../jwt_credential_validator_hybrid.rs | 21 +- .../jwt_credential_validation/mod.rs | 4 +- .../jwt_presentation_validator_hybrid.rs | 11 +- .../jwt_presentation_validation/mod.rs | 4 +- .../src/document/core_document.rs | 22 +- identity_document/src/verifiable/mod.rs | 2 +- identity_jose/src/jwk/jwk_pq.rs | 5 +- identity_jose/src/jwk/key_params.rs | 4 +- identity_jose/src/jwk/mod.rs | 4 +- identity_jose/src/jws/algorithm.rs | 13 +- identity_jose/src/jws/decoder.rs | 61 +- identity_pqc_verifier/src/lib.rs | 2 +- identity_pqc_verifier/src/oqs_verifier.rs | 46 +- identity_pqc_verifier/src/pqc_verifier.rs | 17 +- .../src/key_id_storage/method_digest.rs | 8 +- .../src/key_storage/jwk_storage.rs | 28 +- identity_storage/src/key_storage/memstore.rs | 93 ++- .../src/storage/hybrid_jws_document_ext.rs | 738 +++++++++--------- .../src/storage/jwk_document_ext.rs | 3 - identity_storage/src/storage/mod.rs | 4 +- .../src/storage/pqc_jws_document_ext.rs | 73 +- identity_verification/src/error.rs | 2 +- .../composite_public_key.rs | 93 +-- .../src/verification_method/material.rs | 10 +- .../src/verification_method/mod.rs | 2 +- 34 files changed, 761 insertions(+), 772 deletions(-) diff --git a/examples/0_basic/0_create_did.rs b/examples/0_basic/0_create_did.rs index e797e16f9b..61f157cb37 100644 --- a/examples/0_basic/0_create_did.rs +++ b/examples/0_basic/0_create_did.rs @@ -20,7 +20,6 @@ use iota_sdk::client::Password; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; - /// Demonstrates how to create a DID Document and publish it in a new Alias Output. /// /// In this example we connect to a locally running private network, but it can be adapted diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/11_pqc.rs index e23bef2c04..0992971930 100644 --- a/examples/1_advanced/11_pqc.rs +++ b/examples/1_advanced/11_pqc.rs @@ -1,4 +1,3 @@ - use std::collections::HashMap; use examples::get_address_with_funds; @@ -55,27 +54,27 @@ const API_ENDPOINT: &str = "http://localhost"; // The faucet endpoint allows requesting funds for testing purposes. const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; - -async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, key_type: KeyType, alg: JwsAlgorithm) -> anyhow::Result<(Address, IotaDocument, String)> { - +async fn create_did( + client: &Client, + secret_manager: &SecretManager, + storage: &MemStorage, + key_type: KeyType, + alg: JwsAlgorithm, +) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; - + // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); // New Verification Method containing a PQC key - let fragment = document.generate_method_pqc( - &storage, - key_type, - alg, - None, - MethodScope::VerificationMethod - ).await?; + let fragment = document + .generate_method_pqc(&storage, key_type, alg, None, MethodScope::VerificationMethod) + .await?; // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. @@ -88,7 +87,6 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M Ok((address, document, fragment)) } - /// Demonstrates how to create a Post-Quantum Verifiable Credential. #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -102,27 +100,39 @@ async fn main() -> anyhow::Result<()> { .finish() .await?; - - let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?); + let mut secret_manager_issuer = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?, + ); let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer, JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_87).await?; - + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( + &client, + &mut secret_manager_issuer, + &storage_issuer, + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_87, + ) + .await?; - let mut secret_manager_holder = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?); + let mut secret_manager_holder = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?, + ); - let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_holder, &storage_holder, JwkMemStore::SLH_DSA_KEY_TYPE, JwsAlgorithm::SLH_DSA_SHA2_128s).await?; - + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( + &client, + &mut secret_manager_holder, + &storage_holder, + JwkMemStore::SLH_DSA_KEY_TYPE, + JwsAlgorithm::SLH_DSA_SHA2_128s, + ) + .await?; // ====================================================================================== // Step 2: Issuer creates and signs a Verifiable Credential with a Post-Quantum algorithm. @@ -157,25 +167,27 @@ async fn main() -> anyhow::Result<()> { ) .await?; - // Before sending this credential to the holder the issuer wants to validate that some properties // of the credential satisfy their expectations. JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); println!("VC successfully validated"); // =========================================================================== // Step 3: Issuer sends the Verifiable Credential to the holder. // =========================================================================== - println!("Sending credential (as JWT) to the holder: {}\n", credential_jwt.as_str()); + println!( + "Sending credential (as JWT) to the holder: {}\n", + credential_jwt.as_str() + ); // =========================================================================== // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. @@ -213,8 +225,10 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== // Step 6: Holder sends a verifiable presentation to the verifier. // =========================================================================== - println!("Sending presentation (as JWT) to the verifier: {}\n", presentation_jwt.as_str()); - + println!( + "Sending presentation (as JWT) to the verifier: {}\n", + presentation_jwt.as_str() + ); // =========================================================================== // Step 7: Verifier receives the Verifiable Presentation and verifies it. @@ -273,4 +287,4 @@ async fn main() -> anyhow::Result<()> { println!("VP successfully validated: {:#?}", presentation.presentation); Ok(()) -} \ No newline at end of file +} diff --git a/examples/1_advanced/12_hybrid.rs b/examples/1_advanced/12_hybrid.rs index 936bd1fbd4..9ef883b1a8 100644 --- a/examples/1_advanced/12_hybrid.rs +++ b/examples/1_advanced/12_hybrid.rs @@ -16,12 +16,10 @@ use identity_iota::credential::DecodedJwtPresentation; use identity_iota::credential::FailFast; use identity_iota::credential::Jwt; use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; use identity_iota::credential::JwtCredentialValidatorHybrid; use identity_iota::credential::JwtCredentialValidatorUtils; use identity_iota::credential::JwtPresentationOptions; use identity_iota::credential::JwtPresentationValidationOptions; -use identity_iota::credential::JwtPresentationValidator; use identity_iota::credential::JwtPresentationValidatorHybrid; use identity_iota::credential::JwtPresentationValidatorUtils; use identity_iota::credential::Presentation; @@ -29,7 +27,6 @@ use identity_iota::credential::PresentationBuilder; use identity_iota::credential::Subject; use identity_iota::credential::SubjectHolderRelationship; use identity_iota::did::CoreDID; -use identity_iota::did::DIDUrl; use identity_iota::did::DID; use identity_iota::document::verifiable::JwsVerificationOptions; use identity_iota::iota::IotaClientExt; @@ -37,17 +34,10 @@ use identity_iota::iota::IotaDocument; use identity_iota::iota::IotaIdentityClientExt; use identity_iota::iota::NetworkName; use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwkDocumentExtHybrid; -use identity_iota::storage::JwkGenOutput; use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwkStorage; use identity_iota::storage::JwsSignatureOptions; use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::KeyIdStorage; -use identity_iota::storage::Storage; -use identity_iota::verification::jwk::Jwk; -use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::CompositeAlgId; use identity_iota::verification::MethodScope; use identity_pqc_verifier::PQCJwsVerifier; @@ -55,7 +45,6 @@ use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::secret::SecretManager; use iota_sdk::client::Client; use iota_sdk::client::Password; -use iota_sdk::types::api::core::response::WhiteFlagResponse; use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; use serde_json::json; @@ -65,26 +54,26 @@ const API_ENDPOINT: &str = "http://localhost"; // The faucet endpoint allows requesting funds for testing purposes. const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; - -async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &MemStorage, alg_id: CompositeAlgId) -> anyhow::Result<(Address, IotaDocument, String)> { - +async fn create_did( + client: &Client, + secret_manager: &SecretManager, + storage: &MemStorage, + alg_id: CompositeAlgId, +) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; - + // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. let mut document: IotaDocument = IotaDocument::new(&network_name); // New Verification Method containing a PQC key - let fragment = document.generate_method_hybrid( - &storage, - alg_id, - None, - MethodScope::VerificationMethod - ).await?; + let fragment = document + .generate_method_hybrid(&storage, alg_id, None, MethodScope::VerificationMethod) + .await?; // Construct an Alias Output containing the DID document, with the wallet address // set as both the state controller and governor. @@ -97,7 +86,6 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M Ok((address, document, fragment)) } - /// Demonstrates how to create a DID Document and publish it in a new Alias Output. /// /// In this example we connect to a locally running private network, but it can be adapted @@ -107,33 +95,43 @@ async fn create_did(client: &Client, secret_manager: &SecretManager, storage: &M /// https://github.com/iotaledger/hornet/tree/develop/private_tangle #[tokio::main] async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. let client: Client = Client::builder() .with_primary_node(API_ENDPOINT, None)? .finish() .await?; - let mut secret_manager_issuer = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?); - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_issuer, &storage_issuer, CompositeAlgId::IdMldsa65Ed25519Sha512).await?; - - - let mut secret_manager_holder = SecretManager::Stronghold(StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?); - - - let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = - create_did(&client, &mut secret_manager_holder, &storage_holder, CompositeAlgId::IdMldsa65Ed25519Sha512).await?; - + let mut secret_manager_issuer = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( + &client, + &mut secret_manager_issuer, + &storage_issuer, + CompositeAlgId::IdMldsa65Ed25519Sha512, + ) + .await?; + + let mut secret_manager_holder = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( + &client, + &mut secret_manager_holder, + &storage_holder, + CompositeAlgId::IdMldsa65Ed25519Sha512, + ) + .await?; // Create a credential subject indicating the degree earned by Alice. let subject: Subject = Subject::from_json_value(json!({ @@ -185,8 +183,6 @@ async fn main() -> anyhow::Result<()> { println!("Credential JSON > {:#}", decoded_credential.credential); - - // =========================================================================== // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. // =========================================================================== @@ -223,8 +219,10 @@ async fn main() -> anyhow::Result<()> { // =========================================================================== // Step 6: Holder sends a verifiable presentation to the verifier. // =========================================================================== - println!("Sending presentation (as JWT) to the verifier: {}", presentation_jwt.as_str()); - + println!( + "Sending presentation (as JWT) to the verifier: {}", + presentation_jwt.as_str() + ); // =========================================================================== // Step 7: Verifier receives the Verifiable Presentation and verifies it. @@ -249,11 +247,9 @@ async fn main() -> anyhow::Result<()> { // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( - EdDSAJwsVerifier::default(), - PQCJwsVerifier::default() - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + let presentation: DecodedJwtPresentation = + JwtPresentationValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; // Concurrently resolve the issuers' documents. let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; @@ -283,6 +279,6 @@ async fn main() -> anyhow::Result<()> { // Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check // that the credentials do not have an issuance date in the future which is a default check. - + Ok(()) } diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index 40fb472b34..a79a74312e 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -88,7 +88,6 @@ pub async fn get_address_with_funds( ) -> anyhow::Result
{ let address: Bech32Address = get_address(client, stronghold).await?; - request_faucet_funds(client, address, faucet_endpoint) .await .context("failed to request faucet funds")?; diff --git a/identity_core/src/common/mod.rs b/identity_core/src/common/mod.rs index 226a573b18..8d6be52251 100644 --- a/identity_core/src/common/mod.rs +++ b/identity_core/src/common/mod.rs @@ -15,7 +15,6 @@ pub use self::timestamp::Duration; pub use self::timestamp::Timestamp; pub use self::url::Url; - mod context; mod key_comparable; mod object; diff --git a/identity_core/src/common/timestamp.rs b/identity_core/src/common/timestamp.rs index 73df11e949..8de1832409 100644 --- a/identity_core/src/common/timestamp.rs +++ b/identity_core/src/common/timestamp.rs @@ -112,7 +112,6 @@ impl Timestamp { .checked_sub(duration.0) .and_then(|offset_date_time| Self::from_unix(offset_date_time.unix_timestamp()).ok()) } - } impl Default for Timestamp { diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index cb0cde95e6..6ce3c60b67 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -729,57 +729,3 @@ mod tests { )); } } - - -// //TODO: ZKP - convert to JptClaims structure which basically contains the same claim names -// impl<'credential, T> From> for JptClaims -// where -// T: ToOwned + Serialize, -// ::Owned: DeserializeOwned, -// { -// fn from(item: CredentialJwtClaims<'credential, T>) -> Self { - -// let CredentialJwtClaims { -// exp, -// iss, -// issuance_date, -// jti, -// sub, -// vc, -// custom -// } = item; - - -// let mut claims = JptClaims::new(); - -// exp.map(|v| { -// claims.set_exp(v); -// }); - -// claims.set_iss(iss.url().to_string()); - -// issuance_date.iat.map(|v| { -// claims.set_iat(v); -// }); - -// issuance_date.nbf.map(|v| { -// claims.set_nbf(v); -// }); - -// jti.map(|v| { -// claims.set_jti(v.to_string()); -// }); - -// sub.map(|v| { -// claims.set_sub(v.to_string()); -// }); - -// claims.set_claim(Some("vc"), vc, true); - -// custom.map(|v| { -// claims.set_claim(None, v, true); -// }); - -// claims -// } -// } \ No newline at end of file diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 710e97ef22..ff07fdcf1a 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -26,7 +26,6 @@ mod schema; mod status; mod subject; - pub use self::builder::CredentialBuilder; pub use self::credential::Credential; pub use self::evidence::Evidence; @@ -37,6 +36,10 @@ pub use self::jpt::Jpt; pub use self::jwp_credential_options::JwpCredentialOptions; pub use self::jws::Jws; pub use self::jwt::Jwt; +#[cfg(feature = "validator")] +pub(crate) use self::jwt_serialization::CredentialJwtClaims; +#[cfg(feature = "presentation")] +pub(crate) use self::jwt_serialization::IssuanceDateClaims; pub use self::linked_domain_service::LinkedDomainService; pub use self::policy::Policy; pub use self::proof::Proof; @@ -48,7 +51,3 @@ pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; -#[cfg(feature = "validator")] -pub(crate) use self::jwt_serialization::CredentialJwtClaims; -#[cfg(feature = "presentation")] -pub(crate) use self::jwt_serialization::IssuanceDateClaims; diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index 19e10d8498..a531f088d7 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -100,7 +100,7 @@ pub enum JwtValidationError { ServiceLookupError, /// Indicates that the credential has been revoked. #[error("credential has been revoked")] - Revoked, + Revoked, /// Indicates that the credential has been suspended. #[error("credential has been suspended")] Suspended, diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs index 517200d6f8..a5ba4cfe18 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -1,4 +1,3 @@ - use identity_core::convert::FromJson; use identity_did::CoreDID; use identity_did::DIDUrl; @@ -181,7 +180,6 @@ impl JwtCredentialValidatorHybrid } } - pub(crate) fn parse_composite_pk<'a, 'i, DOC>( jws: &JwsValidationItem<'a>, trusted_issuers: &'i [DOC], @@ -260,11 +258,16 @@ impl JwtCredentialValidatorHybrid // Start decoding the credential let decoded: JwsValidationItem<'_> = Self::decode(credential.as_str())?; - + let (composite, method_id) = Self::parse_composite_pk(&decoded, trusted_issuers, options)?; - - - let credential_token = Self::verify_decoded_signature(decoded, composite.traditional_public_key(), composite.pq_public_key(), traditional_signature_verifier, pq_signature_verifier)?; + + let credential_token = Self::verify_decoded_signature( + decoded, + composite.traditional_public_key(), + composite.pq_public_key(), + traditional_signature_verifier, + pq_signature_verifier, + )?; // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before // returning. @@ -293,7 +296,6 @@ impl JwtCredentialValidatorHybrid traditional_verifier: &TRV, pq_verifier: &PQV, ) -> Result, JwtValidationError> { - decoded .verify_hybrid(traditional_verifier, pq_verifier, traditional_pk, pq_pk) .map_err(|err| JwtValidationError::Signature { @@ -314,7 +316,8 @@ impl JwtCredentialValidatorHybrid T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, { // Verify the JWS signature and obtain the decoded token containing the protected header and raw claims - let DecodedJws { protected, claims, .. } = Self::verify_signature_raw(decoded, traditional_pk, pq_pk, traditional_verifier, pq_verifier)?; + let DecodedJws { protected, claims, .. } = + Self::verify_signature_raw(decoded, traditional_pk, pq_pk, traditional_verifier, pq_verifier)?; let credential_claims: CredentialJwtClaims<'_, T> = CredentialJwtClaims::from_json_slice(&claims).map_err(|err| { @@ -334,4 +337,4 @@ impl JwtCredentialValidatorHybrid custom_claims, }) } -} \ No newline at end of file +} diff --git a/identity_credential/src/validator/jwt_credential_validation/mod.rs b/identity_credential/src/validator/jwt_credential_validation/mod.rs index 50c1b1a46f..7a1b06d3ee 100644 --- a/identity_credential/src/validator/jwt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jwt_credential_validation/mod.rs @@ -6,12 +6,12 @@ mod decoded_jwt_credential; mod error; mod jwt_credential_validation_options; mod jwt_credential_validator; -mod jwt_credential_validator_utils; mod jwt_credential_validator_hybrid; +mod jwt_credential_validator_utils; pub use decoded_jwt_credential::*; pub use error::*; pub use jwt_credential_validation_options::*; pub use jwt_credential_validator::*; +pub use jwt_credential_validator_hybrid::*; pub use jwt_credential_validator_utils::*; -pub use jwt_credential_validator_hybrid::*; \ No newline at end of file diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs index a0472eaf09..6da78fb11f 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -1,13 +1,10 @@ - use identity_core::common::Object; use identity_core::common::Timestamp; use identity_core::common::Url; use identity_core::convert::FromJson; use identity_did::CoreDID; use identity_document::document::CoreDocument; -use identity_document::utils::DIDUrlQuery; use identity_verification::jws::DecodedJws; -use identity_verification::jws::Decoder; use identity_verification::jws::JwsVerifier; use std::str::FromStr; @@ -28,14 +25,14 @@ pub struct JwtPresentationValidatorHybrid(TR impl JwtPresentationValidatorHybrid where - TRV: JwsVerifier, - PQV: JwsVerifier + TRV: JwsVerifier, + PQV: JwsVerifier, { /// Creates a new [`JwtPresentationValidator`] using a specific [`JwsVerifier`]. pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self { Self(traditional_signature_verifier, pq_signature_verifier) } - + /// Validates a [`Presentation`]. /// /// The following properties are validated according to `options`: @@ -165,4 +162,4 @@ where Ok(decoded_jwt_presentation) } -} \ No newline at end of file +} diff --git a/identity_credential/src/validator/jwt_presentation_validation/mod.rs b/identity_credential/src/validator/jwt_presentation_validation/mod.rs index 37866c58a8..91acf4caf8 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/mod.rs @@ -5,12 +5,12 @@ mod decoded_jwt_presentation; mod error; mod jwt_presentation_validation_options; mod jwt_presentation_validator; -mod jwt_presentation_validator_utils; mod jwt_presentation_validator_hybrid; +mod jwt_presentation_validator_utils; pub use decoded_jwt_presentation::*; pub use error::*; pub use jwt_presentation_validation_options::*; pub use jwt_presentation_validator::*; -pub use jwt_presentation_validator_utils::*; pub use jwt_presentation_validator_hybrid::*; +pub use jwt_presentation_validator_utils::*; diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 379ff8cd3c..0fc5774c46 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -11,7 +11,6 @@ use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::DecodedJws; use identity_verification::jose::jws::Decoder; use identity_verification::jose::jws::JwsVerifier; -use identity_verification::CompositePublicKey; use serde::Serialize; use identity_core::common::Object; @@ -988,7 +987,7 @@ impl CoreDocument { &self, jws: &'jws str, detached_payload: Option<&'jws [u8]>, - traditional_verifier: &TRV, + traditional_verifier: &TRV, pq_verifier: &PQV, options: &JwsVerificationOptions, ) -> Result> { @@ -1015,18 +1014,21 @@ impl CoreDocument { }; let composite_public_key = self - .resolve_method(method_url_query, options.method_scope) - .ok_or(Error::MethodNotFound)? - .data() - .try_composite_public_key() - .map_err(Error::InvalidKeyMaterial)?; + .resolve_method(method_url_query, options.method_scope) + .ok_or(Error::MethodNotFound)? + .data() + .try_composite_public_key() + .map_err(Error::InvalidKeyMaterial)?; validation_item - .verify_hybrid(traditional_verifier, pq_verifier, composite_public_key.traditional_public_key(), composite_public_key.pq_public_key()) + .verify_hybrid( + traditional_verifier, + pq_verifier, + composite_public_key.traditional_public_key(), + composite_public_key.pq_public_key(), + ) .map_err(Error::JwsVerificationError) } - - } #[cfg(test)] diff --git a/identity_document/src/verifiable/mod.rs b/identity_document/src/verifiable/mod.rs index 02d55b155b..6f0386d3fb 100644 --- a/identity_document/src/verifiable/mod.rs +++ b/identity_document/src/verifiable/mod.rs @@ -7,4 +7,4 @@ pub use self::jwp_verification_options::JwpVerificationOptions; pub use self::jws_verification_options::JwsVerificationOptions; mod jwp_verification_options; -mod jws_verification_options; \ No newline at end of file +mod jws_verification_options; diff --git a/identity_jose/src/jwk/jwk_pq.rs b/identity_jose/src/jwk/jwk_pq.rs index cd7623ae5e..06a9301b13 100644 --- a/identity_jose/src/jwk/jwk_pq.rs +++ b/identity_jose/src/jwk/jwk_pq.rs @@ -4,7 +4,8 @@ use zeroize::Zeroize; -use super::{JwkParams, JwkType}; +use super::JwkParams; +use super::JwkType; // TODO: PQ - parameter for PQ keys @@ -14,7 +15,6 @@ use super::{JwkParams, JwkType}; #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize, Zeroize)] #[zeroize(drop)] pub struct JwkParamsPQ { - /// The public key as a base64url-encoded value. #[serde(rename = "pub")] pub public: String, // Public Key @@ -49,5 +49,4 @@ impl JwkParamsPQ { pub fn is_private(&self) -> bool { self.private.is_some() } - } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index 2eeea1d6c7..07d743c767 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -3,14 +3,14 @@ use zeroize::Zeroize; +use super::BlsCurve; +use super::JwkParamsPQ; use crate::error::Error; use crate::error::Result; use crate::jwk::EcCurve; use crate::jwk::EcxCurve; use crate::jwk::EdCurve; use crate::jwk::JwkType; -use super::JwkParamsPQ; -use super::BlsCurve; /// Algorithm-specific parameters for JSON Web Keys. /// diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index 5feea0898a..e7320ad470 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -5,19 +5,19 @@ mod curve; mod jwk_ext; +mod jwk_pq; mod key; mod key_operation; mod key_params; mod key_set; mod key_type; mod key_use; -mod jwk_pq; pub use self::curve::*; +pub use self::jwk_pq::*; pub use self::key::*; pub use self::key_operation::*; pub use self::key_params::*; pub use self::key_set::*; pub use self::key_type::*; pub use self::key_use::*; -pub use self::jwk_pq::*; diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index 884037d20e..b09b43f0d3 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -96,9 +96,9 @@ pub enum JwsAlgorithm { #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] IdMldsa44Ed25519Sha512, - + #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] - IdMldsa65Ed25519Sha512 + IdMldsa65Ed25519Sha512, } impl JwsAlgorithm { @@ -125,7 +125,6 @@ impl JwsAlgorithm { Self::SLH_DSA_SHA2_128s, Self::SLH_DSA_SHAKE_128s, Self::SLH_DSA_SHA2_128f, - Self::SLH_DSA_SHAKE_128f, Self::SLH_DSA_SHA2_192s, Self::SLH_DSA_SHAKE_192s, @@ -135,14 +134,10 @@ impl JwsAlgorithm { Self::SLH_DSA_SHAKE_256s, Self::SLH_DSA_SHA2_256f, Self::SLH_DSA_SHAKE_256f, - Self::FALCON512, Self::FALCON1024, - - Self::IdMldsa44Ed25519Sha512, Self::IdMldsa65Ed25519Sha512, - ]; /// Returns the JWS algorithm as a `str` slice. @@ -183,7 +178,7 @@ impl JwsAlgorithm { Self::FALCON512 => "FALCON512", Self::FALCON1024 => "FALCON1024", - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", } } @@ -229,7 +224,7 @@ impl FromStr for JwsAlgorithm { "FALCON512" => Ok(Self::FALCON512), "FALCON1024" => Ok(Self::FALCON1024), - "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), + "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), _ => Err(Error::JwsAlgorithmParsingError), } diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 8cf889fe0d..80f46ca354 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -5,9 +5,6 @@ use core::str; use std::borrow::Cow; use std::ops::Deref; -use crypto::signatures::ed25519::Signature; -use identity_core::common::SingleStructError; -use crypto::hashes::Digest; use crate::error::Error; use crate::error::Result; use crate::jwk::Jwk; @@ -19,6 +16,9 @@ use crate::jwu::decode_b64_json; use crate::jwu::filter_non_empty_bytes; use crate::jwu::parse_utf8; use crate::jwu::validate_jws_headers; +use crypto::hashes::Digest; +use crypto::signatures::ed25519::Signature; +use identity_core::common::SingleStructError; use super::JwsVerifier; use super::VerificationInput; @@ -180,12 +180,17 @@ impl<'a> JwsValidationItem<'a> { }) } - //TODO: hybrid - verify_hybrid - pub fn verify_hybrid(self, traditional_verifier: &TRV, pq_verifier: &PQV, traditional_pk: &Jwk, pq_pk: &Jwk) -> Result> + pub fn verify_hybrid( + self, + traditional_verifier: &TRV, + pq_verifier: &PQV, + traditional_pk: &Jwk, + pq_pk: &Jwk, + ) -> Result> where TRV: JwsVerifier, - PQV: JwsVerifier + PQV: JwsVerifier, { // Destructure data let JwsValidationItem { @@ -204,23 +209,37 @@ impl<'a> JwsValidationItem<'a> { // Extract and validate alg from the protected header. let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?; let (t_alg, pq_alg, signing_input, traditional_signature_len) = match alg { - JwsAlgorithm::IdMldsa44Ed25519Sha512 => { - //TODO: hybrid - DER OID - let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03]; - input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); - (JwsAlgorithm::EdDSA, JwsAlgorithm::ML_DSA_44, input, crypto::signatures::ed25519::Signature::LENGTH) - }, - JwsAlgorithm::IdMldsa65Ed25519Sha512 => { - let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A]; - input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); - (JwsAlgorithm::EdDSA, JwsAlgorithm::ML_DSA_65, input, crypto::signatures::ed25519::Signature::LENGTH) - }, - _ => return Err(Error::JwsAlgorithmParsingError) + JwsAlgorithm::IdMldsa44Ed25519Sha512 => { + //TODO: hybrid - DER OID + let mut input = vec![ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, + ]; + input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + ( + JwsAlgorithm::EdDSA, + JwsAlgorithm::ML_DSA_44, + input, + crypto::signatures::ed25519::Signature::LENGTH, + ) + } + JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + let mut input = vec![ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, + ]; + input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + ( + JwsAlgorithm::EdDSA, + JwsAlgorithm::ML_DSA_65, + input, + crypto::signatures::ed25519::Signature::LENGTH, + ) + } + _ => return Err(Error::JwsAlgorithmParsingError), }; - + traditional_pk.check_alg(t_alg.name())?; pq_pk.check_alg(pq_alg.name())?; - + let extracted_signature_t = &decoded_signature[..traditional_signature_len]; let extracted_signature_pq = &decoded_signature[traditional_signature_len..]; @@ -236,7 +255,7 @@ impl<'a> JwsValidationItem<'a> { traditional_verifier .verify(input1, traditional_pk) .map_err(Error::SignatureVerificationError)?; - + let input2 = VerificationInput { alg: pq_alg, signing_input: signing_input.into(), diff --git a/identity_pqc_verifier/src/lib.rs b/identity_pqc_verifier/src/lib.rs index eb06a82977..8e69ba856e 100644 --- a/identity_pqc_verifier/src/lib.rs +++ b/identity_pqc_verifier/src/lib.rs @@ -2,4 +2,4 @@ mod oqs_verifier; mod pqc_verifier; pub use oqs_verifier::*; -pub use pqc_verifier::*; \ No newline at end of file +pub use pqc_verifier::*; diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 7a24106273..193d14c3dc 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -1,5 +1,3 @@ - -use std::ops::Deref; use identity_jose::jwk::Jwk; use identity_jose::jwk::JwkParamsPQ; use identity_jose::jws::SignatureVerificationError; @@ -7,6 +5,7 @@ use identity_jose::jws::SignatureVerificationErrorKind; use identity_jose::jws::VerificationInput; use oqs::sig::Algorithm; use oqs::sig::Sig; +use std::ops::Deref; /// A verifier that can handle the [`Algorithm`] PQC algorithms. #[derive(Debug)] @@ -14,7 +13,6 @@ use oqs::sig::Sig; pub struct OQSVerifier; impl OQSVerifier { - /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { // Obtain an ML-DSA-44 public key. @@ -22,32 +20,32 @@ impl OQSVerifier { .try_pq_params() .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; - - let pk = - identity_jose::jwu::decode_b64(params.public.as_str()) - .map_err(|_| { - SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) - .with_custom_message("could not decode 'pub' parameter from jwk") + let pk = identity_jose::jwu::decode_b64(params.public.as_str()).map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) + .with_custom_message("could not decode 'pub' parameter from jwk") })?; - oqs::init(); //TODO: check what this function does - let scheme = Sig::new(alg) - .map_err(|_| { - SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified) + let scheme = Sig::new(alg).map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified) .with_custom_message("signature scheme init failed") })?; - let public_key = scheme.public_key_from_bytes(&pk).ok_or( - SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) - )?; - - let signature = scheme.signature_from_bytes(input.decoded_signature.deref()) - .ok_or(SignatureVerificationErrorKind::InvalidSignature)?; - - Ok(scheme.verify(&input.signing_input, signature, public_key) - .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?) - + let public_key = scheme + .public_key_from_bytes(&pk) + .ok_or(SignatureVerificationError::new( + SignatureVerificationErrorKind::KeyDecodingFailure, + ))?; + + let signature = scheme + .signature_from_bytes(input.decoded_signature.deref()) + .ok_or(SignatureVerificationErrorKind::InvalidSignature)?; + + Ok( + scheme + .verify(&input.signing_input, signature, public_key) + .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?, + ) } -} \ No newline at end of file +} diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 035e150aa6..905ec373cd 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -1,4 +1,3 @@ - use identity_jose::jwk::Jwk; use identity_jose::jws::JwsAlgorithm; use identity_jose::jws::JwsVerifier; @@ -10,8 +9,8 @@ use oqs::sig::Algorithm; use crate::OQSVerifier; /// An implementor of [`JwsVerifier`] that can handle the -/// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) -/// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) +/// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) +/// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) algorithms. #[derive(Debug)] #[non_exhaustive] @@ -26,11 +25,11 @@ impl Default for PQCJwsVerifier { impl JwsVerifier for PQCJwsVerifier { /// This implements verification of JWS signatures signed with the - /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) - /// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) - /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) - /// | [`JwsAlgorithm::SLH_DSA_SHA2_128s`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128s) - /// | [`JwsAlgorithm::SLH_DSA_SHA2_128f`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128f) + /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) + /// | [`JwsAlgorithm::ML_DSA_65`](identity_jose::jws::JwsAlgorithm::ML_DSA_65) + /// | [`JwsAlgorithm::ML_DSA_87`](identity_jose::jws::JwsAlgorithm::ML_DSA_87) + /// | [`JwsAlgorithm::SLH_DSA_SHA2_128s`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128s) + /// | [`JwsAlgorithm::SLH_DSA_SHA2_128f`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHA2_128f) /// | [`JwsAlgorithm::SLH_DSA_SHAKE_128s`](identity_jose::jws::JwsAlgorithm::SLH_DSA_SHAKE_128s) algorithms. // Allow unused variables in case of no-default-features. #[allow(unused_variables)] @@ -76,4 +75,4 @@ impl JwsVerifier for PQCJwsVerifier { _ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()), } } -} \ No newline at end of file +} diff --git a/identity_storage/src/key_id_storage/method_digest.rs b/identity_storage/src/key_id_storage/method_digest.rs index 31a04da15d..57b7883bb3 100644 --- a/identity_storage/src/key_id_storage/method_digest.rs +++ b/identity_storage/src/key_id_storage/method_digest.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use identity_core::convert::ToJson; -use identity_verification::CustomMethodData; use identity_verification::MethodData; use identity_verification::VerificationMethod; use seahash::SeaHasher; use std::fmt::Display; use std::hash::Hasher; - use super::KeyIdStorageError; /// Error that may occur when constructing a [`MethodDigest`]. @@ -25,7 +23,6 @@ pub enum MethodDigestConstructionErrorKind { MissingIdFragment, /// Caused by a failure to decode a method's [key material](identity_verification::MethodData). DataDecodingFailure, - } impl Display for MethodDigestConstructionErrorKind { @@ -62,7 +59,10 @@ impl MethodDigest { MethodData::PublicKeyJwk(jwk) => hasher.write(jwk.thumbprint_sha256().as_ref()), // MethodData::Custom(e) => hasher.write(&e.to_json_vec().unwrap()), //TODO: Hybrid - to be changed MethodData::CompositePublicKey(composite) => { - let algid = composite.alg_id().to_json_vec().map_err(|err| MethodDigestConstructionError::new(DataDecodingFailure).with_source(err))?; + let algid = composite + .alg_id() + .to_json_vec() + .map_err(|err| MethodDigestConstructionError::new(DataDecodingFailure).with_source(err))?; hasher.write(&algid); hasher.write(composite.traditional_public_key().thumbprint_sha256().as_ref()); hasher.write(composite.pq_public_key().thumbprint_sha256().as_ref()); diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 6d56b09914..76e9085fc5 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -5,14 +5,8 @@ use crate::key_storage::KeyId; use crate::key_storage::KeyStorageError; use crate::key_storage::KeyType; use async_trait::async_trait; -use identity_core::common::Timestamp; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::JwsAlgorithm; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use jsonprooftoken::jpt::claims::JptClaims; -use jsonprooftoken::jwp::header::IssuerProtectedHeader; -use oqs::sig::Algorithm; -use zkryptium::bbsplus::signature::BBSplusSignature; use super::jwk_gen_output::JwkGenOutput; @@ -69,29 +63,11 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { async fn exists(&self, key_id: &KeyId) -> KeyStorageResult; } - - -//TODO: ZKP - JwkStorageExt -/// Extension to the JwkStorage to handle BBS+ keys -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -pub trait JwkStorageExt : JwkStorage { - /// Generates a JWK representing a BBS+ signature - async fn generate_bbs_key(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult; - - /// Generate the JPT representing a JWP in the Issuer form - async fn generate_issuer_proof(&self, key_id: &KeyId, header: IssuerProtectedHeader, claims: JptClaims, public_key: &Jwk) -> KeyStorageResult; - - /// Update proof functionality for timeframe revocation mechanism - async fn update_proof(&self, key_id: &KeyId, public_key: &Jwk, proof: &[u8; 112], old_start_validity_timeframe: String, new_start_validity_timeframe: String, old_end_validity_timeframe: String, new_end_validity_timeframe: String, index_start_validity_timeframe: usize, index_end_validity_timeframe: usize, n_messages: usize ) -> KeyStorageResult<[u8; 112]>; -} - - /// Extension to the JwkStorage to handle post-quantum keys #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] -pub trait JwkStoragePQ : JwkStorage { - /// Generates a JWK representing a PQ key +pub trait JwkStoragePQ: JwkStorage { + /// Generates a JWK representing a PQ key async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult; /// Sign the provided `data` using a PQ algorithm diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index ebf2b82d0f..2d4da55cfd 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -8,17 +8,15 @@ use std::str::FromStr; use async_trait::async_trait; use crypto::signatures::ed25519::SecretKey; -use identity_core::common::Timestamp; use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jwk::BlsCurve; use identity_verification::jwk::JwkParams; -use identity_verification::jwk::JwkParamsPQ; use identity_verification::jwu; use oqs::sig::Algorithm; use oqs::sig::Sig; -use identity_verification::jwk::BlsCurve; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -218,6 +216,9 @@ impl JwkMemStore { /// SLH-DSA algorithms key types; pub const SLH_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::SLH_DSA); + const FALCON: &'static str = "FALCON"; + /// FALCON algorithms key types; + pub const FALCON_KEY_TYPE: KeyType = KeyType::from_static_str(Self::FALCON); } impl MemStoreKeyType { @@ -318,7 +319,6 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> } } - //TODO: PQ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { @@ -330,7 +330,6 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), - JwsAlgorithm::SLH_DSA_SHAKE_128f => Ok(Algorithm::SphincsShake128fSimple), JwsAlgorithm::SLH_DSA_SHA2_192s => Ok(Algorithm::SphincsSha2192sSimple), JwsAlgorithm::SLH_DSA_SHAKE_192s => Ok(Algorithm::SphincsShake192sSimple), @@ -348,7 +347,7 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) .with_custom_message(format!("{other} is not supported")), ); - } + } } } @@ -358,8 +357,16 @@ fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkStoragePQ for JwkMemStore { async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { + if key_type != JwkMemStore::ML_DSA_KEY_TYPE + && key_type != JwkMemStore::SLH_DSA_KEY_TYPE + && key_type != JwkMemStore::FALCON_KEY_TYPE + { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("unsupported key type {key_type}")), + ); + } - //TODO: maybe handle key_type let oqs_alg = check_pq_alg_compatibility(alg)?; oqs::init(); //TODO: check what this function does @@ -379,7 +386,6 @@ impl JwkStoragePQ for JwkMemStore { let public = jwu::encode_b64(pk.into_vec()); let private = jwu::encode_b64(sk.into_vec()); - let mut jwk_params = match alg { JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), @@ -415,13 +421,13 @@ impl JwkStoragePQ for JwkMemStore { params.public = public; params.private = Some(private); } - , - _ => return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message("Should NOT happen!"), - ), + _ => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_custom_message("Should NOT happen!"), + ) + } } - + let mut jwk = Jwk::from_params(jwk_params); jwk.set_alg(alg.name()); @@ -434,7 +440,6 @@ impl JwkStoragePQ for JwkMemStore { Ok(JwkGenOutput::new(kid, public_jwk)) } - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; @@ -450,18 +455,27 @@ impl JwkStoragePQ for JwkMemStore { // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. match alg { - JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 | JwsAlgorithm::ML_DSA_87 - | JwsAlgorithm::SLH_DSA_SHA2_128s | JwsAlgorithm::SLH_DSA_SHAKE_128s | JwsAlgorithm::SLH_DSA_SHA2_128f - | JwsAlgorithm::SLH_DSA_SHAKE_128f | JwsAlgorithm::SLH_DSA_SHA2_192s | JwsAlgorithm::SLH_DSA_SHAKE_192s - | JwsAlgorithm::SLH_DSA_SHA2_192f | JwsAlgorithm::SLH_DSA_SHAKE_192f | JwsAlgorithm::SLH_DSA_SHA2_256s - | JwsAlgorithm::SLH_DSA_SHAKE_256s | JwsAlgorithm::SLH_DSA_SHA2_256f | JwsAlgorithm::SLH_DSA_SHAKE_256f - | JwsAlgorithm::FALCON512 | JwsAlgorithm::FALCON1024 => { - public_key.try_pq_params().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) - .with_source(err) - })? - }, + JwsAlgorithm::ML_DSA_44 + | JwsAlgorithm::ML_DSA_65 + | JwsAlgorithm::ML_DSA_87 + | JwsAlgorithm::SLH_DSA_SHA2_128s + | JwsAlgorithm::SLH_DSA_SHAKE_128s + | JwsAlgorithm::SLH_DSA_SHA2_128f + | JwsAlgorithm::SLH_DSA_SHAKE_128f + | JwsAlgorithm::SLH_DSA_SHA2_192s + | JwsAlgorithm::SLH_DSA_SHAKE_192s + | JwsAlgorithm::SLH_DSA_SHA2_192f + | JwsAlgorithm::SLH_DSA_SHAKE_192f + | JwsAlgorithm::SLH_DSA_SHA2_256s + | JwsAlgorithm::SLH_DSA_SHAKE_256s + | JwsAlgorithm::SLH_DSA_SHA2_256f + | JwsAlgorithm::SLH_DSA_SHAKE_256f + | JwsAlgorithm::FALCON512 + | JwsAlgorithm::FALCON1024 => public_key.try_pq_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) + .with_source(err) + })?, other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -478,17 +492,18 @@ impl JwkStoragePQ for JwkMemStore { let params = jwk.try_pq_params().unwrap(); let sk = params - .private - .as_deref() - .map(jwu::decode_b64) - .ok_or_else(|| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("expected Jwk `pub` param to be present") - })? - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("unable to decode `d` param") - .with_source(err) - })?; + .private + .as_deref() + .map(jwu::decode_b64) + .ok_or_else(|| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("expected Jwk `pub` param to be present") + })? + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("unable to decode `d` param") + .with_source(err) + })?; oqs::init(); //TODO: check what this function does @@ -500,7 +515,7 @@ impl JwkStoragePQ for JwkMemStore { let secret_key = scheme.secret_key_from_bytes(&sk).ok_or( KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)) + .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)), )?; let signature = scheme.sign(&data, secret_key).map_err(|err| { diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 43e2018828..ef609c0d68 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -1,73 +1,103 @@ use std::borrow::Cow; use std::ops::Deref; +use super::JwkStorageDocumentError as Error; +use crate::try_undo_key_generation; +use crate::JwkGenOutput; +use crate::JwkMemStore; +use crate::JwkStorage; +use crate::JwkStoragePQ; +use crate::JwsSignatureOptions; +use crate::KeyId; +use crate::KeyIdStorage; +use crate::KeyIdStorageErrorKind; +use crate::MethodDigest; +use crate::Storage; +use crate::StorageResult; +use async_trait::async_trait; use crypto::hashes::Digest; use identity_core::common::Object; -use identity_core::convert::ToJson; -use identity_credential::credential::{Credential, Jws, Jwt}; -use identity_credential::presentation::{JwtPresentationOptions, Presentation}; -use identity_did::{DIDUrl, DID}; -use identity_document::document::{self, CoreDocument}; -use identity_verification::jws::{CharSet, CompactJwsEncoder, CompactJwsEncodingOptions, JwsHeader}; -use identity_verification::{jwk::Jwk, jws::JwsAlgorithm, CustomMethodData, MethodBuilder, MethodScope, MethodType, VerificationMethod}; +use identity_credential::credential::Credential; +use identity_credential::credential::Jws; +use identity_credential::credential::Jwt; +use identity_credential::presentation::JwtPresentationOptions; +use identity_credential::presentation::Presentation; +use identity_did::DIDUrl; +use identity_did::DID; +use identity_document::document::CoreDocument; +use identity_verification::jws::CharSet; +use identity_verification::jws::CompactJwsEncoder; +use identity_verification::jws::CompactJwsEncodingOptions; +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jws::JwsHeader; +use identity_verification::CompositeAlgId; +use identity_verification::CompositePublicKey; +use identity_verification::MethodBuilder; +use identity_verification::MethodData; +use identity_verification::MethodScope; +use identity_verification::MethodType; +use identity_verification::VerificationMethod; use serde::de::DeserializeOwned; use serde::Serialize; -use serde_json::json; - -use crate::{JwsSignatureOptions, KeyIdStorageError, KeyIdStorageErrorKind}; -use crate::{try_undo_key_generation, JwkGenOutput, JwkMemStore, JwkStorage, JwkStoragePQ, KeyId, KeyIdStorage, KeyType, MethodDigest, Storage}; -use async_trait::async_trait; -use super::JwkStorageDocumentError as Error; -use identity_verification::{CompositeAlgId, MethodData}; -use identity_verification::CompositePublicKey; - -pub type StorageResultHybrid = Result; - - - macro_rules! generate_method_hybrid_for_document_type { - ($t:ty, $name:ident) => { -async fn $name( - document: &mut $t, - storage: &Storage, - alg_id: CompositeAlgId, - fragment: Option<&str>, - scope: MethodScope, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage, -{ - let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { + ($t:ty, $name:ident) => { + async fn $name( + document: &mut $t, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + { + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { CompositeAlgId::IdMldsa44Ed25519Sha512 => ( - JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_44, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_44, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, ), CompositeAlgId::IdMldsa65Ed25519Sha512 => ( - JwkMemStore::ML_DSA_KEY_TYPE, JwsAlgorithm::ML_DSA_65, JwkMemStore::ED25519_KEY_TYPE, JwsAlgorithm::EdDSA + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_65, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, ), - }; + }; - let JwkGenOutput { key_id: t_key_id, jwk: t_jwk } = K::generate(storage.key_storage(), trad_key_type, trad_alg) - .await - .map_err(Error::KeyStorageError)?; + let JwkGenOutput { + key_id: t_key_id, + jwk: t_jwk, + } = K::generate(storage.key_storage(), trad_key_type, trad_alg) + .await + .map_err(Error::KeyStorageError)?; - let JwkGenOutput { key_id: pq_key_id, jwk: pq_jwk } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) - .await - .map_err(Error::KeyStorageError)?; + let JwkGenOutput { + key_id: pq_key_id, + jwk: pq_jwk, + } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; - // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) - let composite_fragment = t_jwk.kid().map(|s| s.to_string()).or_else(|| pq_jwk.kid().map(|s| s.to_string())).map(|s| { - if let (Some(str1), Some(str2)) = (t_jwk.kid(), pq_jwk.kid()) { + // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) + let composite_fragment = t_jwk + .kid() + .map(|s| s.to_string()) + .or_else(|| pq_jwk.kid().map(|s| s.to_string())) + .map(|s| { + if let (Some(str1), Some(str2)) = (t_jwk.kid(), pq_jwk.kid()) { format!("{}~{}", str1, str2) - } else { + } else { s - } - }); + } + }); - let composite_kid = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + let composite_kid = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); - let fragment: Cow<'_, str> = { + let fragment: Cow<'_, str> = { let given_fragment: &str = fragment .or_else(|| composite_fragment.as_deref()) .ok_or(identity_verification::Error::InvalidMethod( @@ -81,134 +111,131 @@ async fn $name( Cow::Owned(format!("#{given_fragment}")) } }; - - let id: DIDUrl = document.id().to_url().join(fragment) - .map_err(identity_verification::Error::DIDUrlConstructionError) - .map_err(Error::VerificationMethodConstructionError)?; + let id: DIDUrl = document + .id() + .to_url() + .join(fragment) + .map_err(identity_verification::Error::DIDUrlConstructionError) + .map_err(Error::VerificationMethodConstructionError)?; - let composite_pk = CompositePublicKey::new(alg_id, t_jwk, pq_jwk ); + let composite_pk = CompositePublicKey::new(alg_id, t_jwk, pq_jwk); - // Produce a new verification method containing the generated JWK. If this operation fails we handle the error - // by attempting to revert key generation before returning an error. - let method: VerificationMethod = { + // Produce a new verification method containing the generated JWK. If this operation fails we handle the error + // by attempting to revert key generation before returning an error. + let method: VerificationMethod = { match MethodBuilder::default() - .id(id) - .type_(MethodType::custom("CompositeSignaturePublicKey")) - .controller(document.id().clone().into()) - .data(identity_verification::MethodData::CompositePublicKey(composite_pk)) - .build() - .map_err(Error::VerificationMethodConstructionError) + .id(id) + .type_(MethodType::custom("CompositeSignaturePublicKey")) + .controller(document.id().clone().into()) + .data(identity_verification::MethodData::CompositePublicKey(composite_pk)) + .build() + .map_err(Error::VerificationMethodConstructionError) { - Ok(method) => method, - Err(source) => { - let error = try_undo_key_generation(storage, &t_key_id, source).await; - let error = try_undo_key_generation(storage, &pq_key_id, error).await; - return Err(error) - } + Ok(method) => method, + Err(source) => { + let error = try_undo_key_generation(storage, &t_key_id, source).await; + let error = try_undo_key_generation(storage, &pq_key_id, error).await; + return Err(error); + } } - }; - - - - - // Extract data from method before inserting it into the DID document. - let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; - let method_id: DIDUrl = method.id().clone(); - - // The fragment is always set on a method, so this error will never occur. - let fragment: String = method_id - .fragment() - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)? - .to_owned(); + }; - // Insert method into document and handle error upon failure. - if let Err(error) = document - .insert_method(method, scope) - .map_err(|_| Error::FragmentAlreadyExists) - { + // Extract data from method before inserting it into the DID document. + let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?; + let method_id: DIDUrl = method.id().clone(); + + // The fragment is always set on a method, so this error will never occur. + let fragment: String = method_id + .fragment() + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)? + .to_owned(); + + // Insert method into document and handle error upon failure. + if let Err(error) = document + .insert_method(method, scope) + .map_err(|_| Error::FragmentAlreadyExists) + { let error = try_undo_key_generation(storage, &t_key_id, error).await; let error = try_undo_key_generation(storage, &pq_key_id, error).await; - return Err(error) - }; + return Err(error); + }; - // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the - // operation fails. - if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, composite_kid) - .await - .map_err(Error::KeyIdStorageError) - { + // Insert the generated `KeyId` into storage under the computed method digest and handle the error if the + // operation fails. + if let Err(error) = ::insert_key_id(&storage.key_id_storage(), method_digest, composite_kid) + .await + .map_err(Error::KeyIdStorageError) + { // Remove the method from the document as it can no longer be used. let _ = document.remove_method(&method_id); let error = try_undo_key_generation(storage, &t_key_id, error).await; let error = try_undo_key_generation(storage, &pq_key_id, error).await; - return Err(error) - } + return Err(error); + } - Ok(fragment) -} + Ok(fragment) } + }; } - -///New trait to handle JWP-based operations on DID Documents +/// Extension trait to handle PQ/T hybrid operations. #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwkDocumentExtHybrid { - - /// Generate new key material in the given `storage` and insert a new verification method with the corresponding - /// public key material into the DID document. This support BBS+ keys. + /// Generate an Verification Method containing a PQ/T hybrid key. async fn generate_method_hybrid( &mut self, storage: &Storage, alg_id: CompositeAlgId, fragment: Option<&str>, scope: MethodScope, - ) -> StorageResultHybrid + ) -> StorageResult where K: JwkStorage + JwkStoragePQ, I: KeyIdStorage; - async fn create_jws( - &self, - storage: &Storage, - fragment: &str, - payload: &[u8], - options: &JwsSignatureOptions, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage; - - async fn create_credential_jwt_hybrid( - &self, - credential: &Credential, - storage: &Storage, - fragment: &str, - options: &JwsSignatureOptions, - custom_claims: Option, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync; - + /// Create a PQ/T hybrid JWS. + async fn create_jws( + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage; - async fn create_presentation_jwt_hybrid( - &self, - presentation: &Presentation, - storage: &Storage, - fragment: &str, - signature_options: &JwsSignatureOptions, - presentation_options: &JwtPresentationOptions, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync, - CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync; + /// Create a PQ/T hybrid Verifiable Credential. + async fn create_credential_jwt_hybrid( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync; + /// Create a PQ/T hybrid Verifiable Presentation. + async fn create_presentation_jwt_hybrid( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + signature_options: &JwsSignatureOptions, + presentation_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync; } generate_method_hybrid_for_document_type!(CoreDocument, generate_method_hybrid_core_document); @@ -222,7 +249,7 @@ impl JwkDocumentExtHybrid for CoreDocument { alg_id: CompositeAlgId, fragment: Option<&str>, scope: MethodScope, - ) -> StorageResultHybrid + ) -> StorageResult where K: JwkStorage + JwkStoragePQ, I: KeyIdStorage, @@ -236,193 +263,205 @@ impl JwkDocumentExtHybrid for CoreDocument { fragment: &str, payload: &[u8], options: &JwsSignatureOptions, - ) -> StorageResultHybrid + ) -> StorageResult where K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage { - // Obtain the method corresponding to the given fragment. - let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; - let MethodData::CompositePublicKey(ref composite) = method.data() else { - return Err(Error::NotCompositePublicKey); - }; - - let alg_id = composite.alg_id(); - let t_jwk = composite.traditional_public_key(); - let pq_jwk = composite.pq_public_key(); - - // Extract JwsAlgorithm. - let alg: JwsAlgorithm = alg_id - .name() - .parse() - .map_err(|_| Error::InvalidJwsAlgorithm)?; - - // Create JWS header in accordance with options. - let header: JwsHeader = { - let mut header = JwsHeader::new(); - - header.set_alg(alg); - if let Some(custom) = &options.custom_header_parameters { - header.set_custom(custom.clone()) - } + I: KeyIdStorage, + { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::CompositePublicKey(ref composite) = method.data() else { + return Err(Error::NotCompositePublicKey); + }; - if let Some(ref kid) = options.kid { - header.set_kid(kid.clone()); - } else { - header.set_kid(method.id().to_string()); - } + let alg_id = composite.alg_id(); + let t_jwk = composite.traditional_public_key(); + let pq_jwk = composite.pq_public_key(); - if let Some(b64) = options.b64 { - // Follow recommendation in https://datatracker.ietf.org/doc/html/rfc7797#section-7. - if !b64 { - header.set_b64(b64); - header.set_crit(["b64"]); - } - }; + // Extract JwsAlgorithm. + let alg: JwsAlgorithm = alg_id.name().parse().map_err(|_| Error::InvalidJwsAlgorithm)?; - if let Some(typ) = &options.typ { - header.set_typ(typ.clone()) - } else { - // https://www.w3.org/TR/vc-data-model/#jwt-encoding - header.set_typ("JWT") + // Create JWS header in accordance with options. + let header: JwsHeader = { + let mut header = JwsHeader::new(); + + header.set_alg(alg); + if let Some(custom) = &options.custom_header_parameters { + header.set_custom(custom.clone()) + } + + if let Some(ref kid) = options.kid { + header.set_kid(kid.clone()); + } else { + header.set_kid(method.id().to_string()); + } + + if let Some(b64) = options.b64 { + // Follow recommendation in https://datatracker.ietf.org/doc/html/rfc7797#section-7. + if !b64 { + header.set_b64(b64); + header.set_crit(["b64"]); } + }; - if let Some(cty) = &options.cty { - header.set_cty(cty.clone()) - }; + if let Some(typ) = &options.typ { + header.set_typ(typ.clone()) + } else { + // https://www.w3.org/TR/vc-data-model/#jwt-encoding + header.set_typ("JWT") + } - if let Some(url) = &options.url { - header.set_url(url.clone()) - }; + if let Some(cty) = &options.cty { + header.set_cty(cty.clone()) + }; - if let Some(nonce) = &options.nonce { - header.set_nonce(nonce.clone()) - }; + if let Some(url) = &options.url { + header.set_url(url.clone()) + }; - header - }; + if let Some(nonce) = &options.nonce { + header.set_nonce(nonce.clone()) + }; - // Get the key identifier corresponding to the given method from the KeyId storage. - let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; - let key_id: KeyId = ::get_key_id(storage.key_id_storage(), &method_digest) - .await - .map_err(Error::KeyIdStorageError)?; + header + }; - let (t_key_id, pq_key_id) = key_id.as_str().split_once("~") - .map(|v| (KeyId::new(v.0), KeyId::new(v.1))).ok_or(Error::KeyIdStorageError(KeyIdStorageErrorKind::Unspecified.into()))?; + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id: KeyId = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + let (t_key_id, pq_key_id) = key_id + .as_str() + .split_once("~") + .map(|v| (KeyId::new(v.0), KeyId::new(v.1))) + .ok_or(Error::KeyIdStorageError(KeyIdStorageErrorKind::Unspecified.into()))?; + + // Extract Compact JWS encoding options. + let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload { + // We use this as a default and don't provide the extra UrlSafe check for now. + // Applications that require such checks can easily do so after JWS creation. + CompactJwsEncodingOptions::NonDetached { + charset_requirements: CharSet::Default, + } + } else { + CompactJwsEncodingOptions::Detached + }; - // Extract Compact JWS encoding options. - let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload { - // We use this as a default and don't provide the extra UrlSafe check for now. - // Applications that require such checks can easily do so after JWS creation. - CompactJwsEncodingOptions::NonDetached { - charset_requirements: CharSet::Default, - } - } else { - CompactJwsEncodingOptions::Detached - }; - - let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) - .map_err(|err| Error::EncodingError(err.into()))?; - - let signing_input = match alg { - JwsAlgorithm::IdMldsa44Ed25519Sha512 => { - //TODO: hybrid - DER OID - let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03]; - input.extend(crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()).deref().to_vec()); - input - }, - JwsAlgorithm::IdMldsa65Ed25519Sha512 => { - let mut input = vec![0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A]; - input.extend(crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()).deref().to_vec()); - input - }, - _ => return Err(Error::InvalidJwsAlgorithm) - }; - - let signature_t = ::sign(storage.key_storage(), &t_key_id, &signing_input, t_jwk) - .await - .map_err(Error::KeyStorageError)?; + let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) + .map_err(|err| Error::EncodingError(err.into()))?; + + let signing_input = match alg { + JwsAlgorithm::IdMldsa44Ed25519Sha512 => { + //TODO: hybrid - DER OID + let mut input = vec![ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, + ]; + input.extend( + crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) + .deref() + .to_vec(), + ); + input + } + JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + let mut input = vec![ + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, + ]; + input.extend( + crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) + .deref() + .to_vec(), + ); + input + } + _ => return Err(Error::InvalidJwsAlgorithm), + }; - let signature_pq = ::pq_sign(storage.key_storage(), &pq_key_id, &signing_input, pq_jwk) - .await - .map_err(Error::KeyStorageError)?; + let signature_t = ::sign(storage.key_storage(), &t_key_id, &signing_input, t_jwk) + .await + .map_err(Error::KeyStorageError)?; - let signature = [signature_t, signature_pq].concat(); + let signature_pq = ::pq_sign(storage.key_storage(), &pq_key_id, &signing_input, pq_jwk) + .await + .map_err(Error::KeyStorageError)?; - Ok(Jws::new(jws_encoder.into_jws(&signature))) - } + let signature = [signature_t, signature_pq].concat(); - async fn create_credential_jwt_hybrid( - &self, - credential: &Credential, - storage: &Storage, - fragment: &str, - options: &JwsSignatureOptions, - custom_claims: Option, - ) -> StorageResultHybrid - where + Ok(Jws::new(jws_encoder.into_jws(&signature))) + } + + async fn create_credential_jwt_hybrid( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult + where K: JwkStorage + JwkStoragePQ, I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync { - if options.detached_payload { - return Err(Error::EncodingError(Box::::from( - "cannot use detached payload for credential signing", - ))); - } - - if !options.b64.unwrap_or(true) { - // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. - return Err(Error::EncodingError(Box::::from( - "cannot use `b64 = false` with JWTs", - ))); - } - - let payload = credential - .serialize_jwt(custom_claims) - .map_err(Error::ClaimsSerializationError)?; - self - .create_jws(storage, fragment, payload.as_bytes(), options) - .await - .map(|jws| Jwt::new(jws.into())) + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + if options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for credential signing", + ))); } + if !options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } - async fn create_presentation_jwt_hybrid( - &self, - presentation: &Presentation, - storage: &Storage, - fragment: &str, - jws_options: &JwsSignatureOptions, - jwt_options: &JwtPresentationOptions, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync, - CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync - { - if jws_options.detached_payload { - return Err(Error::EncodingError(Box::::from( - "cannot use detached payload for presentation signing", - ))); - } - - if !jws_options.b64.unwrap_or(true) { - // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. - return Err(Error::EncodingError(Box::::from( - "cannot use `b64 = false` with JWTs", - ))); - } - let payload = presentation - .serialize_jwt(jwt_options) - .map_err(Error::ClaimsSerializationError)?; - self - .create_jws(storage, fragment, payload.as_bytes(), jws_options) - .await - .map(|jws| Jwt::new(jws.into())) + let payload = credential + .serialize_jwt(custom_claims) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws(storage, fragment, payload.as_bytes(), options) + .await + .map(|jws| Jwt::new(jws.into())) + } + + async fn create_presentation_jwt_hybrid( + &self, + presentation: &Presentation, + storage: &Storage, + fragment: &str, + jws_options: &JwsSignatureOptions, + jwt_options: &JwtPresentationOptions, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, + { + if jws_options.detached_payload { + return Err(Error::EncodingError(Box::::from( + "cannot use detached payload for presentation signing", + ))); } -} + if !jws_options.b64.unwrap_or(true) { + // JWTs should not have `b64` set per https://datatracker.ietf.org/doc/html/rfc7797#section-7. + return Err(Error::EncodingError(Box::::from( + "cannot use `b64 = false` with JWTs", + ))); + } + let payload = presentation + .serialize_jwt(jwt_options) + .map_err(Error::ClaimsSerializationError)?; + self + .create_jws(storage, fragment, payload.as_bytes(), jws_options) + .await + .map(|jws| Jwt::new(jws.into())) + } +} // ==================================================================================================================== // IotaDocument @@ -431,7 +470,7 @@ impl JwkDocumentExtHybrid for CoreDocument { mod iota_document { use crate::StorageResult; - use super::*; + use super::*; use identity_credential::credential::Jwt; use identity_iota_core::IotaDocument; @@ -441,51 +480,53 @@ mod iota_document { #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkDocumentExtHybrid for IotaDocument { async fn generate_method_hybrid( - &mut self, - storage: &Storage, - alg_id: CompositeAlgId, - fragment: Option<&str>, - scope: MethodScope, + &mut self, + storage: &Storage, + alg_id: CompositeAlgId, + fragment: Option<&str>, + scope: MethodScope, ) -> StorageResult where K: JwkStorage + JwkStoragePQ, I: KeyIdStorage, { - generate_method_hybrid_iota_document(self, storage, alg_id, fragment, scope).await + generate_method_hybrid_iota_document(self, storage, alg_id, fragment, scope).await } async fn create_jws( - &self, - storage: &Storage, - fragment: &str, - payload: &[u8], - options: &JwsSignatureOptions, - ) -> StorageResultHybrid - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage { - self - .core_document() - .create_jws(storage, fragment, payload, options) - .await - } + &self, + storage: &Storage, + fragment: &str, + payload: &[u8], + options: &JwsSignatureOptions, + ) -> StorageResult + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + { + self + .core_document() + .create_jws(storage, fragment, payload, options) + .await + } async fn create_credential_jwt_hybrid( - &self, - credential: &Credential, - storage: &Storage, - fragment: &str, - options: &JwsSignatureOptions, - custom_claims: Option, - ) -> StorageResultHybrid + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwsSignatureOptions, + custom_claims: Option, + ) -> StorageResult where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage, - T: ToOwned + Serialize + DeserializeOwned + Sync { + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { self - .core_document() - .create_credential_jwt_hybrid(credential, storage, fragment, options, custom_claims) - .await + .core_document() + .create_credential_jwt_hybrid(credential, storage, fragment, options, custom_claims) + .await } async fn create_presentation_jwt_hybrid( @@ -495,18 +536,17 @@ mod iota_document { fragment: &str, options: &JwsSignatureOptions, jwt_options: &JwtPresentationOptions, - ) -> StorageResultHybrid + ) -> StorageResult where K: JwkStorage + JwkStoragePQ, I: KeyIdStorage, T: ToOwned + Serialize + DeserializeOwned + Sync, - CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync + CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync, { self - .core_document() - .create_presentation_jwt_hybrid(presentation, storage, fragment, options, jwt_options) - .await + .core_document() + .create_presentation_jwt_hybrid(presentation, storage, fragment, options, jwt_options) + .await } - } } diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index e319a57e8a..f9ee100986 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -329,7 +329,6 @@ impl JwkDocumentExt for CoreDocument { I: KeyIdStorage, { generate_method_core_document(self, storage, key_type, alg, fragment, scope).await - } async fn purge_method(&mut self, storage: &Storage, id: &DIDUrl) -> StorageResult<()> @@ -529,8 +528,6 @@ where } } - - // ==================================================================================================================== // IotaDocument // ==================================================================================================================== diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 2ab8e94cc0..b4230e92a2 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -12,8 +12,8 @@ mod signature_options; #[cfg(feature = "jpt-bbs-plus")] mod timeframe_revocation_ext; -mod pqc_jws_document_ext; mod hybrid_jws_document_ext; +mod pqc_jws_document_ext; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -27,8 +27,8 @@ pub use signature_options::*; #[cfg(feature = "jpt-bbs-plus")] pub use timeframe_revocation_ext::*; -pub use pqc_jws_document_ext::*; pub use hybrid_jws_document_ext::*; +pub use pqc_jws_document_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index 5ba389a521..fd68bf1b4d 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -1,25 +1,37 @@ +use super::JwkStorageDocumentError as Error; +use crate::key_id_storage::MethodDigest; +use crate::try_undo_key_generation; +use crate::JwkGenOutput; +use crate::JwkStoragePQ; +use crate::JwsSignatureOptions; +use crate::KeyIdStorage; +use crate::KeyType; +use crate::Storage; +use crate::StorageResult; +use async_trait::async_trait; use identity_core::common::Object; -use identity_credential::credential::{Credential, Jws, Jwt}; -use identity_credential::presentation::{JwtPresentationOptions, Presentation}; +use identity_credential::credential::Credential; +use identity_credential::credential::Jws; +use identity_credential::credential::Jwt; +use identity_credential::presentation::JwtPresentationOptions; +use identity_credential::presentation::Presentation; +use identity_did::DIDUrl; use identity_document::document::CoreDocument; -use identity_verification::jws::{CharSet, CompactJwsEncoder, CompactJwsEncodingOptions, JwsHeader}; -use identity_verification::{jws::JwsAlgorithm, MethodScope}; -use async_trait::async_trait; +use identity_verification::jws::CharSet; +use identity_verification::jws::CompactJwsEncoder; +use identity_verification::jws::CompactJwsEncodingOptions; +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jws::JwsHeader; +use identity_verification::MethodData; +use identity_verification::MethodScope; +use identity_verification::VerificationMethod; use serde::de::DeserializeOwned; use serde::Serialize; -use crate::{JwkStoragePQ, JwsSignatureOptions, KeyIdStorage, KeyType, Storage, StorageResult}; -use crate::JwkGenOutput; -use crate::key_id_storage::MethodDigest; -use super::JwkStorageDocumentError as Error; -use identity_did::DIDUrl; -use identity_verification::{MethodData, VerificationMethod}; -use crate::try_undo_key_generation; ///New trait to handle JWP-based operations on DID Documents #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwsDocumentExtPQC { - /// Generate new key material in the given `storage` and insert a new verification method with the corresponding /// public key material into the DID document. This support BBS+ keys. async fn generate_method_pqc( @@ -34,7 +46,6 @@ pub trait JwsDocumentExtPQC { K: JwkStoragePQ, I: KeyIdStorage; - /// Create a JWS using a PQC async fn create_jws_pqc( &self, @@ -68,7 +79,6 @@ pub trait JwsDocumentExtPQC { I: KeyIdStorage, T: ToOwned + Serialize + DeserializeOwned + Sync; - /// Produces a JWT using PQC algorithms where the payload is produced from the given `presentation` /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). /// @@ -88,19 +98,19 @@ pub trait JwsDocumentExtPQC { I: KeyIdStorage, T: ToOwned + Serialize + DeserializeOwned + Sync, CRED: ToOwned + Serialize + DeserializeOwned + Clone + Sync; - } - - - // ==================================================================================================================== // CoreDocument // ==================================================================================================================== - -generate_method_for_document_type!(CoreDocument, JwsAlgorithm, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_core_document); - +generate_method_for_document_type!( + CoreDocument, + JwsAlgorithm, + JwkStoragePQ, + JwkStoragePQ::generate_pq_key, + generate_method_core_document +); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] @@ -254,7 +264,6 @@ impl JwsDocumentExtPQC for CoreDocument { .map(|jws| Jwt::new(jws.into())) } - async fn create_presentation_jwt_pqc( &self, presentation: &Presentation, @@ -289,22 +298,24 @@ impl JwsDocumentExtPQC for CoreDocument { .await .map(|jws| Jwt::new(jws.into())) } - } - - - // ==================================================================================================================== // IotaDocument // ==================================================================================================================== #[cfg(feature = "iota-document")] mod iota_document { -use super::*; + use super::*; use identity_iota_core::IotaDocument; - generate_method_for_document_type!(IotaDocument, JwsAlgorithm, JwkStoragePQ, JwkStoragePQ::generate_pq_key, generate_method_iota_document); + generate_method_for_document_type!( + IotaDocument, + JwsAlgorithm, + JwkStoragePQ, + JwkStoragePQ::generate_pq_key, + generate_method_iota_document + ); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] @@ -360,7 +371,6 @@ use super::*; .await } - async fn create_presentation_jwt_pqc( &self, presentation: &Presentation, @@ -379,7 +389,6 @@ use super::*; .core_document() .create_presentation_jwt_pqc(presentation, storage, fragment, jws_options, jwt_options) .await - } } -} \ No newline at end of file +} diff --git a/identity_verification/src/error.rs b/identity_verification/src/error.rs index f613b43672..8f0e0b1685 100644 --- a/identity_verification/src/error.rs +++ b/identity_verification/src/error.rs @@ -42,5 +42,5 @@ pub enum Error { //TODO: hybrid - new error /// Caused by key material that is not a Composite Public Key. #[error("verification material format is not compositePublicKey")] - NotCompositePublicKey + NotCompositePublicKey, } diff --git a/identity_verification/src/verification_method/composite_public_key.rs b/identity_verification/src/verification_method/composite_public_key.rs index 407af4b851..b4d5ccd28d 100644 --- a/identity_verification/src/verification_method/composite_public_key.rs +++ b/identity_verification/src/verification_method/composite_public_key.rs @@ -1,69 +1,58 @@ //TODO: hybrid - composite public key -use std::fmt::Display; +use identity_jose::jwk::Jwk; -use identity_jose::{jwk::Jwk, jws::JwsAlgorithm}; - -//TODO: hybrid - move to identity_jose - - -//TODO: hybrid - to be removed +/// Mame of algorithms used to generate the hybrid signature. Values taken from [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-02#name-domain-separators). #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub enum CompositeAlgId { - #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] - IdMldsa44Ed25519Sha512, - - #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] - IdMldsa65Ed25519Sha512 + /// DER encoded value in hex = 060B6086480186FA6B50080103 + #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] + IdMldsa44Ed25519Sha512, + /// DER encoded value in hex = 060B6086480186FA6B5008010A + #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] + IdMldsa65Ed25519Sha512, } impl CompositeAlgId { - pub fn der_oid(&self) -> &'static [u8] { - match self { - CompositeAlgId::IdMldsa44Ed25519Sha512 => &[ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, - ], - CompositeAlgId::IdMldsa65Ed25519Sha512 => &[ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, - ], - } - } - - - /// Returns the JWS algorithm as a `str` slice. - pub const fn name(self) -> &'static str { - match self { - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", - Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", - } + /// Returns the JWS algorithm as a `str` slice. + pub const fn name(self) -> &'static str { + match self { + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", + Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", } + } } +/// Represent a combination of a traditional public key and a post-quantum public key both in Jwk format. #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub struct CompositePublicKey { - #[serde(rename = "algId")] - alg_id: CompositeAlgId, - #[serde(rename = "traditionalPublicKey")] - traditional_public_key: Jwk, - #[serde(rename = "pqPublicKey")] - pq_public_key: Jwk, + #[serde(rename = "algId")] + alg_id: CompositeAlgId, + #[serde(rename = "traditionalPublicKey")] + traditional_public_key: Jwk, + #[serde(rename = "pqPublicKey")] + pq_public_key: Jwk, } impl CompositePublicKey { - pub fn new(alg_id: CompositeAlgId, traditional_public_key: Jwk, pq_public_key: Jwk) -> Self { - Self { alg_id, traditional_public_key, pq_public_key } - } - - pub fn alg_id(&self) -> CompositeAlgId { - self.alg_id - } - - pub fn pq_public_key(&self) -> &Jwk { - &self.pq_public_key + /// Create a new CompositePublicKey structure. + pub fn new(alg_id: CompositeAlgId, traditional_public_key: Jwk, pq_public_key: Jwk) -> Self { + Self { + alg_id, + traditional_public_key, + pq_public_key, } - - pub fn traditional_public_key(&self) -> &Jwk { - &self.traditional_public_key - } - -} \ No newline at end of file + } + /// Get the `algId` value. + pub fn alg_id(&self) -> CompositeAlgId { + self.alg_id + } + /// Get the post-quantum public key in Jwk format. + pub fn pq_public_key(&self) -> &Jwk { + &self.pq_public_key + } + /// Get the traditional public key in Jwk format. + pub fn traditional_public_key(&self) -> &Jwk { + &self.traditional_public_key + } +} diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 2558e11c74..e7e5099538 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -28,7 +28,8 @@ pub enum MethodData { PublicKeyBase58(String), /// Verification Material in the JSON Web Key format. PublicKeyJwk(Jwk), - /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new MethodData + /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new + /// MethodData CompositePublicKey(CompositePublicKey), /// Arbitrary verification material. #[serde(untagged)] @@ -62,9 +63,9 @@ impl MethodData { /// represented as a vector of bytes. pub fn try_decode(&self) -> Result> { match self { - Self::PublicKeyJwk(_) | Self::Custom(_) | Self::CompositePublicKey(_)=> Err(Error::InvalidMethodDataTransformation( - "method data is not base encoded", - )), + Self::PublicKeyJwk(_) | Self::Custom(_) | Self::CompositePublicKey(_) => Err( + Error::InvalidMethodDataTransformation("method data is not base encoded"), + ), Self::PublicKeyMultibase(input) => { BaseEncoding::decode_multibase(input).map_err(|_| Error::InvalidKeyDataMultibase) } @@ -72,7 +73,6 @@ impl MethodData { } } - //TODO: hybrid - return CompositePublicKey /// Returns the wrapped `CompositePublicKey` if the format is [`MethodData::CompositePublicKey`]. pub fn composite_public_key(&self) -> Option<&CompositePublicKey> { diff --git a/identity_verification/src/verification_method/mod.rs b/identity_verification/src/verification_method/mod.rs index eda10926e9..cea4234ecf 100644 --- a/identity_verification/src/verification_method/mod.rs +++ b/identity_verification/src/verification_method/mod.rs @@ -7,13 +7,13 @@ //! `identity_iota_core_legacy` crate. mod builder; +mod composite_public_key; mod material; mod method; mod method_ref; mod method_relationship; mod method_scope; mod method_type; -mod composite_public_key; pub use self::builder::MethodBuilder; pub use self::material::CustomMethodData; From 1f7b71bcca20d9f67b17213d96aaaf96a4a06de7 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Tue, 30 Jul 2024 14:39:58 +0200 Subject: [PATCH 044/163] pqc features --- bindings/wasm/docs/api-reference.md | 172 ++++---- identity_iota/Cargo.toml | 6 + identity_storage/Cargo.toml | 9 +- .../src/key_storage/jwk_storage.rs | 10 - .../src/key_storage/jwk_storage_pqc.rs | 21 + identity_storage/src/key_storage/memstore.rs | 381 ++++++++++-------- identity_storage/src/key_storage/mod.rs | 4 + identity_storage/src/storage/mod.rs | 6 +- 8 files changed, 329 insertions(+), 280 deletions(-) create mode 100644 identity_storage/src/key_storage/jwk_storage_pqc.rs diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index db03dc07ec..5eecd01cbc 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -252,31 +252,20 @@ working with storage backed DID documents.

## Members
-
PresentationProofAlgorithm
-
-
ProofAlgorithm
-
-
StatusCheck
-

Controls validation behaviour when checking whether or not a credential has been revoked by its -credentialStatus.

+
StatusPurpose
+

Purpose of a StatusList2021.

-
Strict
-

Validate the status if supported, reject any unsupported -credentialStatus types.

-

Only RevocationBitmap2022 is currently supported.

-

This is the default.

+
PayloadType
+
+
FailFast
+

Declares when validation should return if an error occurs.

-
SkipUnsupported
-

Validate the status if supported, skip any unsupported -credentialStatus types.

+
AllErrors
+

Return all errors that occur during validation.

-
SkipAll
-

Skip all status checks.

+
FirstError
+

Return after the first error occurs.

-
SerializationType
-
-
MethodRelationship
-
SubjectHolderRelationship

Declares how credential subjects must relate to the presentation holder.

See also the Subject-Holder Relationship section of the specification.

@@ -291,28 +280,14 @@ This variant is the default.

Any

The holder is not required to have any kind of relationship to any credential subject.

-
CredentialStatus
-
-
StatusPurpose
-

Purpose of a StatusList2021.

-
-
StateMetadataEncoding
-
-
FailFast
-

Declares when validation should return if an error occurs.

-
-
AllErrors
-

Return all errors that occur during validation.

-
-
FirstError
-

Return after the first error occurs.

-
-
PayloadType
+
SerializationType
MethodRelationship
CredentialStatus
+
PresentationProofAlgorithm
+
StatusCheck

Controls validation behaviour when checking whether or not a credential has been revoked by its credentialStatus.

@@ -330,6 +305,10 @@ This variant is the default.

SkipAll

Skip all status checks.

+
StateMetadataEncoding
+
+
ProofAlgorithm
+
## Functions @@ -343,9 +322,6 @@ This variant is the default.

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this prior to calling the function.

-
start()
-

Initializes the console error panic hook for better error messages

-
encodeB64(data) ⇒ string

Encode the given bytes in url-safe base64.

@@ -3224,7 +3200,7 @@ Utility functions for validating JPT credentials. ### JptCredentialValidatorUtils.extractIssuer(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a [`Credential`](`Credential`) as a DID. +Utility for extracting the issuer field of a [Credential](#Credential) as a DID. # Errors Fails if the issuer field is not a valid DID. @@ -5450,7 +5426,8 @@ Supported verification method types. * _static_ * [.Ed25519VerificationKey2018()](#MethodType.Ed25519VerificationKey2018) ⇒ [MethodType](#MethodType) * [.X25519KeyAgreementKey2019()](#MethodType.X25519KeyAgreementKey2019) ⇒ [MethodType](#MethodType) - * [.JsonWebKey()](#MethodType.JsonWebKey) ⇒ [MethodType](#MethodType) + * ~~[.JsonWebKey()](#MethodType.JsonWebKey)~~ + * [.JsonWebKey2020()](#MethodType.JsonWebKey2020) ⇒ [MethodType](#MethodType) * [.custom(type_)](#MethodType.custom) ⇒ [MethodType](#MethodType) * [.fromJSON(json)](#MethodType.fromJSON) ⇒ [MethodType](#MethodType) @@ -5482,7 +5459,13 @@ Deep clones the object. **Kind**: static method of [MethodType](#MethodType) -### MethodType.JsonWebKey() ⇒ [MethodType](#MethodType) +### ~~MethodType.JsonWebKey()~~ +***Deprecated*** + +**Kind**: static method of [MethodType](#MethodType) + + +### MethodType.JsonWebKey2020() ⇒ [MethodType](#MethodType) A verification method for use with JWT verification as prescribed by the [Jwk](#Jwk) in the `publicKeyJwk` entry. @@ -7529,46 +7512,33 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | + -**Kind**: global variable - - -## StatusCheck -Controls validation behaviour when checking whether or not a credential has been revoked by its -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). +## StatusPurpose +Purpose of a [StatusList2021](#StatusList2021). **Kind**: global variable - - -## Strict -Validate the status if supported, reject any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -Only `RevocationBitmap2022` is currently supported. - -This is the default. + +## PayloadType **Kind**: global variable - + -## SkipUnsupported -Validate the status if supported, skip any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. +## FailFast +Declares when validation should return if an error occurs. **Kind**: global variable - + -## SkipAll -Skip all status checks. +## AllErrors +Return all errors that occur during validation. **Kind**: global variable - + -## SerializationType -**Kind**: global variable - +## FirstError +Return after the first error occurs. -## MethodRelationship **Kind**: global variable @@ -7596,31 +7566,61 @@ The holder must match the subject only for credentials where the [`nonTransferab ## Any The holder is not required to have any kind of relationship to any credential subject. -## StateMetadataEncoding **Kind**: global variable - + -## StateMetadataEncoding +## SerializationType **Kind**: global variable - + -## FailFast -Declares when validation should return if an error occurs. +## MethodRelationship +**Kind**: global variable + +## CredentialStatus **Kind**: global variable - + -## AllErrors -Return all errors that occur during validation. +## PresentationProofAlgorithm +**Kind**: global variable + + +## StatusCheck +Controls validation behaviour when checking whether or not a credential has been revoked by its +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). **Kind**: global variable - + -## FirstError -Return after the first error occurs. +## Strict +Validate the status if supported, reject any unsupported +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. + +Only `RevocationBitmap2022` is currently supported. + +This is the default. + +**Kind**: global variable + + +## SkipUnsupported +Validate the status if supported, skip any unsupported +[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. **Kind**: global variable + + +## SkipAll +Skip all status checks. +**Kind**: global variable + + +## StateMetadataEncoding +**Kind**: global variable + + +## ProofAlgorithm **Kind**: global variable @@ -7644,12 +7644,6 @@ prior to calling the function. | decodedSignature | Uint8Array | | publicKey | [Jwk](#Jwk) | - - -## start() -Initializes the console error panic hook for better error messages - -**Kind**: global function ## encodeB64(data) ⇒ string diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 4e32790288..7acb25c071 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -67,6 +67,12 @@ sd-jwt = ["identity_credential/sd-jwt"] # Enables zero knowledge selective disclosurable VCs jpt-bbs-plus = ["identity_storage/jpt-bbs-plus", "identity_credential/jpt-bbs-plus"] +# Enables PQC +pqc = ["identity_storage/pqc"] + +# Enables liboqs +pqc-liboqs = ["identity_storage/pqc-liboqs"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 4ee01878ad..a9c6af47bf 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -31,7 +31,8 @@ serde_json.workspace = true thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } zkryptium = { workspace = true, optional = true } -oqs.workspace = true +oqs = { workspace = true, optional = true } + @@ -42,7 +43,7 @@ once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } [features] -default = ["iota-document", "memstore"] +default = ["iota-document", "memstore", "pqc-liboqs"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto"] # Enables `Send` + `Sync` bounds for the storage traits. @@ -51,6 +52,10 @@ send-sync-storage = [] iota-document = ["dep:identity_iota_core"] # Enables JSON Proof Token & BBS+ related features jpt-bbs-plus = ["identity_credential/jpt-bbs-plus", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] +# Enables PQC (JwkStoragePQ implementation needed) +pqc = [] +# Enables liboqs +pqc-liboqs = ["pqc", "dep:oqs"] [lints] workspace = true diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 76e9085fc5..7141e51aba 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -63,13 +63,3 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { async fn exists(&self, key_id: &KeyId) -> KeyStorageResult; } -/// Extension to the JwkStorage to handle post-quantum keys -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -pub trait JwkStoragePQ: JwkStorage { - /// Generates a JWK representing a PQ key - async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult; - - /// Sign the provided `data` using a PQ algorithm - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult>; -} diff --git a/identity_storage/src/key_storage/jwk_storage_pqc.rs b/identity_storage/src/key_storage/jwk_storage_pqc.rs new file mode 100644 index 0000000000..0f9192cc39 --- /dev/null +++ b/identity_storage/src/key_storage/jwk_storage_pqc.rs @@ -0,0 +1,21 @@ + +use crate::key_storage::KeyId; +use crate::key_storage::KeyType; +use async_trait::async_trait; +use identity_verification::jose::jwk::Jwk; +use identity_verification::jose::jws::JwsAlgorithm; + +use super::jwk_gen_output::JwkGenOutput; +use super::JwkStorage; +use super::KeyStorageResult; + +/// Extension to the JwkStorage to handle post-quantum keys +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwkStoragePQ: JwkStorage { + /// Generates a JWK representing a PQ key + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult; + + /// Sign the provided `data` using a PQ algorithm + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult>; +} \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 2d4da55cfd..b3e1ed0e83 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -32,7 +32,7 @@ use super::KeyStorageResult; use super::KeyType; use crate::key_storage::JwkStorage; -use crate::JwkStoragePQ; +use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; /// The map from key ids to JWKs. type JwkKeyStore = HashMap; @@ -321,210 +321,237 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> //TODO: PQ -fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { - match alg { - JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), - JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::Dilithium3), - JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::Dilithium5), - JwsAlgorithm::SLH_DSA_SHA2_128s => Ok(Algorithm::SphincsSha2128sSimple), - JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), - JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), - - JwsAlgorithm::SLH_DSA_SHAKE_128f => Ok(Algorithm::SphincsShake128fSimple), - JwsAlgorithm::SLH_DSA_SHA2_192s => Ok(Algorithm::SphincsSha2192sSimple), - JwsAlgorithm::SLH_DSA_SHAKE_192s => Ok(Algorithm::SphincsShake192sSimple), - JwsAlgorithm::SLH_DSA_SHA2_192f => Ok(Algorithm::SphincsSha2192fSimple), - JwsAlgorithm::SLH_DSA_SHAKE_192f => Ok(Algorithm::SphincsShake192fSimple), - JwsAlgorithm::SLH_DSA_SHA2_256s => Ok(Algorithm::SphincsSha2256sSimple), - JwsAlgorithm::SLH_DSA_SHAKE_256s => Ok(Algorithm::SphincsShake256sSimple), - JwsAlgorithm::SLH_DSA_SHA2_256f => Ok(Algorithm::SphincsSha2256fSimple), - JwsAlgorithm::SLH_DSA_SHAKE_256f => Ok(Algorithm::SphincsShake256fSimple), - - JwsAlgorithm::FALCON512 => Ok(Algorithm::Falcon512), - JwsAlgorithm::FALCON1024 => Ok(Algorithm::Falcon1024), - other => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .with_custom_message(format!("{other} is not supported")), - ); - } - } -} - -//TODO: PQ - JwkStoragePQ -/// JwkStoragePQ implementation for JwkMemStore -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -impl JwkStoragePQ for JwkMemStore { - async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { - if key_type != JwkMemStore::ML_DSA_KEY_TYPE - && key_type != JwkMemStore::SLH_DSA_KEY_TYPE - && key_type != JwkMemStore::FALCON_KEY_TYPE - { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message(format!("unsupported key type {key_type}")), - ); - } - - let oqs_alg = check_pq_alg_compatibility(alg)?; - oqs::init(); //TODO: check what this function does - - let scheme = Sig::new(oqs_alg).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature scheme init failed")) - .with_source(err) - })?; - let (pk, sk) = scheme.keypair().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("keypair generation failed!")) - .with_source(err) - })?; +#[cfg(feature = "pqc-liboqs")] +mod pqc_liboqs { + use std::str::FromStr; + use async_trait::async_trait; + use crypto::signatures::ed25519::SecretKey; + use identity_verification::jose::jwk::Jwk; + use identity_verification::jose::jwk::JwkType; + use identity_verification::jose::jws::JwsAlgorithm; + use identity_verification::jwk::JwkParams; + use identity_verification::jwu; + use oqs::sig::Algorithm; + use oqs::sig::Sig; + use tokio::sync::RwLockReadGuard; + use tokio::sync::RwLockWriteGuard; - let kid: KeyId = random_key_id(); + use super::random_key_id; + use super::JwkKeyStore; + use super::JwkMemStore; + use super::KeyId; + use super::KeyStorageError; + use super::KeyStorageErrorKind; + use super::KeyStorageResult; + use super::KeyType; + use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; + use crate::JwkGenOutput; - let public = jwu::encode_b64(pk.into_vec()); - let private = jwu::encode_b64(sk.into_vec()); - - let mut jwk_params = match alg { - JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_128f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_192s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_192s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_192f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_192f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_256s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_256s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_256f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_256f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::FALCON512 => JwkParams::new(JwkType::FALCON), - JwsAlgorithm::FALCON1024 => JwkParams::new(JwkType::FALCON), + fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { + match alg { + JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), + JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::Dilithium3), + JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::Dilithium5), + JwsAlgorithm::SLH_DSA_SHA2_128s => Ok(Algorithm::SphincsSha2128sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), + JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), + + JwsAlgorithm::SLH_DSA_SHAKE_128f => Ok(Algorithm::SphincsShake128fSimple), + JwsAlgorithm::SLH_DSA_SHA2_192s => Ok(Algorithm::SphincsSha2192sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_192s => Ok(Algorithm::SphincsShake192sSimple), + JwsAlgorithm::SLH_DSA_SHA2_192f => Ok(Algorithm::SphincsSha2192fSimple), + JwsAlgorithm::SLH_DSA_SHAKE_192f => Ok(Algorithm::SphincsShake192fSimple), + JwsAlgorithm::SLH_DSA_SHA2_256s => Ok(Algorithm::SphincsSha2256sSimple), + JwsAlgorithm::SLH_DSA_SHAKE_256s => Ok(Algorithm::SphincsShake256sSimple), + JwsAlgorithm::SLH_DSA_SHA2_256f => Ok(Algorithm::SphincsSha2256fSimple), + JwsAlgorithm::SLH_DSA_SHAKE_256f => Ok(Algorithm::SphincsShake256fSimple), + + JwsAlgorithm::FALCON512 => Ok(Algorithm::Falcon512), + JwsAlgorithm::FALCON1024 => Ok(Algorithm::Falcon1024), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) .with_custom_message(format!("{other} is not supported")), ); } - }; + } + } - match jwk_params { - JwkParams::MLDSA(ref mut params) => { - params.public = public; - params.private = Some(private); - } - JwkParams::SLHDSA(ref mut params) => { - params.public = public; - params.private = Some(private); - } - _ => { + //TODO: PQ - JwkStoragePQ + /// JwkStoragePQ implementation for JwkMemStore + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwkStoragePQ for JwkMemStore { + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { + if key_type != JwkMemStore::ML_DSA_KEY_TYPE + && key_type != JwkMemStore::SLH_DSA_KEY_TYPE + && key_type != JwkMemStore::FALCON_KEY_TYPE + { return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_custom_message("Should NOT happen!"), - ) + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("unsupported key type {key_type}")), + ); } - } - let mut jwk = Jwk::from_params(jwk_params); + let oqs_alg = check_pq_alg_compatibility(alg)?; + oqs::init(); //TODO: check what this function does - jwk.set_alg(alg.name()); - jwk.set_kid(jwk.thumbprint_sha256_b64()); - let public_jwk: Jwk = jwk.to_public().expect("should only panic if kty == oct"); + let scheme = Sig::new(oqs_alg).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature scheme init failed")) + .with_source(err) + })?; + let (pk, sk) = scheme.keypair().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("keypair generation failed!")) + .with_source(err) + })?; - let mut jwk_store: RwLockWriteGuard<'_, JwkKeyStore> = self.jwk_store.write().await; - jwk_store.insert(kid.clone(), jwk); + let kid: KeyId = random_key_id(); - Ok(JwkGenOutput::new(kid, public_jwk)) - } + let public = jwu::encode_b64(pk.into_vec()); + let private = jwu::encode_b64(sk.into_vec()); + + let mut jwk_params = match alg { + JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), + JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_128f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_192s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_192s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_192f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_192f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_256s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_256s => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHA2_256f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::SLH_DSA_SHAKE_256f => JwkParams::new(JwkType::SLHDSA), + JwsAlgorithm::FALCON512 => JwkParams::new(JwkType::FALCON), + JwsAlgorithm::FALCON1024 => JwkParams::new(JwkType::FALCON), + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }; - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { - let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; + match jwk_params { + JwkParams::MLDSA(ref mut params) => { + params.public = public; + params.private = Some(private); + } + JwkParams::SLHDSA(ref mut params) => { + params.public = public; + params.private = Some(private); + } + _ => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_custom_message("Should NOT happen!"), + ) + } + } - // Extract the required alg from the given public key - let alg = public_key - .alg() - .ok_or(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .and_then(|alg_str| { - JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - })?; + let mut jwk = Jwk::from_params(jwk_params); - let oqs_alg = check_pq_alg_compatibility(alg)?; + jwk.set_alg(alg.name()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + let public_jwk: Jwk = jwk.to_public().expect("should only panic if kty == oct"); - // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. - match alg { - JwsAlgorithm::ML_DSA_44 - | JwsAlgorithm::ML_DSA_65 - | JwsAlgorithm::ML_DSA_87 - | JwsAlgorithm::SLH_DSA_SHA2_128s - | JwsAlgorithm::SLH_DSA_SHAKE_128s - | JwsAlgorithm::SLH_DSA_SHA2_128f - | JwsAlgorithm::SLH_DSA_SHAKE_128f - | JwsAlgorithm::SLH_DSA_SHA2_192s - | JwsAlgorithm::SLH_DSA_SHAKE_192s - | JwsAlgorithm::SLH_DSA_SHA2_192f - | JwsAlgorithm::SLH_DSA_SHAKE_192f - | JwsAlgorithm::SLH_DSA_SHA2_256s - | JwsAlgorithm::SLH_DSA_SHAKE_256s - | JwsAlgorithm::SLH_DSA_SHA2_256f - | JwsAlgorithm::SLH_DSA_SHAKE_256f - | JwsAlgorithm::FALCON512 - | JwsAlgorithm::FALCON1024 => public_key.try_pq_params().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) - .with_source(err) - })?, - other => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .with_custom_message(format!("{other} is not supported")), - ); - } - }; + let mut jwk_store: RwLockWriteGuard<'_, JwkKeyStore> = self.jwk_store.write().await; + jwk_store.insert(kid.clone(), jwk); - // Obtain the corresponding private key and sign `data`. - let jwk: &Jwk = jwk_store - .get(key_id) - .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; + Ok(JwkGenOutput::new(kid, public_jwk)) + } - let params = jwk.try_pq_params().unwrap(); + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; - let sk = params - .private - .as_deref() - .map(jwu::decode_b64) - .ok_or_else(|| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("expected Jwk `pub` param to be present") - })? - .map_err(|err| { + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .and_then(|alg_str| { + JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + })?; + + let oqs_alg = check_pq_alg_compatibility(alg)?; + + // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. + match alg { + JwsAlgorithm::ML_DSA_44 + | JwsAlgorithm::ML_DSA_65 + | JwsAlgorithm::ML_DSA_87 + | JwsAlgorithm::SLH_DSA_SHA2_128s + | JwsAlgorithm::SLH_DSA_SHAKE_128s + | JwsAlgorithm::SLH_DSA_SHA2_128f + | JwsAlgorithm::SLH_DSA_SHAKE_128f + | JwsAlgorithm::SLH_DSA_SHA2_192s + | JwsAlgorithm::SLH_DSA_SHAKE_192s + | JwsAlgorithm::SLH_DSA_SHA2_192f + | JwsAlgorithm::SLH_DSA_SHAKE_192f + | JwsAlgorithm::SLH_DSA_SHA2_256s + | JwsAlgorithm::SLH_DSA_SHAKE_256s + | JwsAlgorithm::SLH_DSA_SHA2_256f + | JwsAlgorithm::SLH_DSA_SHAKE_256f + | JwsAlgorithm::FALCON512 + | JwsAlgorithm::FALCON1024 => public_key.try_pq_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) + .with_source(err) + })?, + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }; + + // Obtain the corresponding private key and sign `data`. + let jwk: &Jwk = jwk_store + .get(key_id) + .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; + + let params = jwk.try_pq_params().unwrap(); + + let sk = params + .private + .as_deref() + .map(jwu::decode_b64) + .ok_or_else(|| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("expected Jwk `pub` param to be present") + })? + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("unable to decode `d` param") + .with_source(err) + })?; + + oqs::init(); //TODO: check what this function does + + let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("unable to decode `d` param") + .with_custom_message(format!("signature scheme init failed")) .with_source(err) })?; - oqs::init(); //TODO: check what this function does - - let scheme = Sig::new(oqs_alg).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature scheme init failed")) - .with_source(err) - })?; - - let secret_key = scheme.secret_key_from_bytes(&sk).ok_or( - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)), - )?; + let secret_key = scheme.secret_key_from_bytes(&sk).ok_or( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)), + )?; - let signature = scheme.sign(&data, secret_key).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature computation failed")) - .with_source(err) - })?; + let signature = scheme.sign(&data, secret_key).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature computation failed")) + .with_source(err) + })?; - Ok(signature.into_vec()) + Ok(signature.into_vec()) + } } } diff --git a/identity_storage/src/key_storage/mod.rs b/identity_storage/src/key_storage/mod.rs index f54f9d5233..b5f86a57b9 100644 --- a/identity_storage/src/key_storage/mod.rs +++ b/identity_storage/src/key_storage/mod.rs @@ -15,6 +15,8 @@ mod jwk_gen_output; mod jwk_storage; #[cfg(feature = "jpt-bbs-plus")] mod jwk_storage_bbs_plus_ext; +#[cfg(feature = "pqc")] +mod jwk_storage_pqc; mod key_id; mod key_storage_error; mod key_type; @@ -30,6 +32,8 @@ pub mod public_modules { pub use super::jwk_storage::*; #[cfg(feature = "jpt-bbs-plus")] pub use super::jwk_storage_bbs_plus_ext::*; + #[cfg(feature = "pqc")] + pub use super::jwk_storage_pqc::*; pub use super::key_id::*; pub use super::key_storage_error::*; pub use super::key_type::*; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index b4230e92a2..d31ea06bf4 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -11,8 +11,9 @@ mod jwp_document_ext; mod signature_options; #[cfg(feature = "jpt-bbs-plus")] mod timeframe_revocation_ext; - +#[cfg(feature = "pqc")] mod hybrid_jws_document_ext; +#[cfg(feature = "pqc")] mod pqc_jws_document_ext; #[cfg(all(test, feature = "memstore"))] @@ -26,8 +27,9 @@ pub use jwp_document_ext::*; pub use signature_options::*; #[cfg(feature = "jpt-bbs-plus")] pub use timeframe_revocation_ext::*; - +#[cfg(feature = "pqc")] pub use hybrid_jws_document_ext::*; +#[cfg(feature = "pqc")] pub use pqc_jws_document_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and From fc6c9c401c2dc08faad083d4208c3041e64c2da0 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Fri, 30 Aug 2024 10:39:44 +0200 Subject: [PATCH 045/163] fix pqc/hybrid features --- examples/Cargo.toml | 2 +- identity_credential/Cargo.toml | 1 + .../src/validator/jwt_credential_validation/mod.rs | 2 ++ .../src/validator/jwt_presentation_validation/mod.rs | 2 ++ identity_iota/Cargo.toml | 6 ++++-- identity_storage/Cargo.toml | 11 ++++------- .../src/storage/hybrid_jws_document_ext.rs | 10 +++++----- identity_storage/src/storage/mod.rs | 4 ++-- 8 files changed, 21 insertions(+), 17 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a41ac3b4b3..0ab36b2b6e 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,7 +9,7 @@ publish = false anyhow = "1.0.62" bls12_381_plus.workspace = true identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false } -identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus"] } +identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "hybrid-liboqs"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } json-proof-token.workspace = true diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 2a4b11d09c..bd8ffcd678 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -60,6 +60,7 @@ domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] +hybrid = ["credential", "validator"] [lints] workspace = true diff --git a/identity_credential/src/validator/jwt_credential_validation/mod.rs b/identity_credential/src/validator/jwt_credential_validation/mod.rs index 7a1b06d3ee..6a31084313 100644 --- a/identity_credential/src/validator/jwt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jwt_credential_validation/mod.rs @@ -6,6 +6,7 @@ mod decoded_jwt_credential; mod error; mod jwt_credential_validation_options; mod jwt_credential_validator; +#[cfg(feature = "hybrid")] mod jwt_credential_validator_hybrid; mod jwt_credential_validator_utils; @@ -13,5 +14,6 @@ pub use decoded_jwt_credential::*; pub use error::*; pub use jwt_credential_validation_options::*; pub use jwt_credential_validator::*; +#[cfg(feature = "hybrid")] pub use jwt_credential_validator_hybrid::*; pub use jwt_credential_validator_utils::*; diff --git a/identity_credential/src/validator/jwt_presentation_validation/mod.rs b/identity_credential/src/validator/jwt_presentation_validation/mod.rs index 91acf4caf8..76000bd223 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/mod.rs @@ -5,6 +5,7 @@ mod decoded_jwt_presentation; mod error; mod jwt_presentation_validation_options; mod jwt_presentation_validator; +#[cfg(feature = "hybrid")] mod jwt_presentation_validator_hybrid; mod jwt_presentation_validator_utils; @@ -12,5 +13,6 @@ pub use decoded_jwt_presentation::*; pub use error::*; pub use jwt_presentation_validation_options::*; pub use jwt_presentation_validator::*; +#[cfg(feature = "hybrid")] pub use jwt_presentation_validator_hybrid::*; pub use jwt_presentation_validator_utils::*; diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 7acb25c071..413cbf1547 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -69,10 +69,12 @@ jpt-bbs-plus = ["identity_storage/jpt-bbs-plus", "identity_credential/jpt-bbs-pl # Enables PQC pqc = ["identity_storage/pqc"] - -# Enables liboqs pqc-liboqs = ["identity_storage/pqc-liboqs"] +# Enables PQ/T Hybrid +hybrid = ["identity_storage/hybrid", "identity_credential/hybrid"] +hybrid-liboqs = ["identity_storage/hybrid-liboqs", "identity_credential/hybrid"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index a9c6af47bf..5629000bd2 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -32,10 +32,6 @@ thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } zkryptium = { workspace = true, optional = true } oqs = { workspace = true, optional = true } - - - - [dev-dependencies] identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } @@ -43,7 +39,7 @@ once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } [features] -default = ["iota-document", "memstore", "pqc-liboqs"] +default = ["iota-document", "memstore", "hybrid-liboqs"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto"] # Enables `Send` + `Sync` bounds for the storage traits. @@ -54,8 +50,9 @@ iota-document = ["dep:identity_iota_core"] jpt-bbs-plus = ["identity_credential/jpt-bbs-plus", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] # Enables PQC (JwkStoragePQ implementation needed) pqc = [] -# Enables liboqs -pqc-liboqs = ["pqc", "dep:oqs"] +pqc-liboqs = ["pqc", "memstore", "dep:oqs"] +hybrid = ["pqc", "dep:iota-crypto"] +hybrid-liboqs = ["hybrid", "pqc-liboqs"] [lints] workspace = true diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index ef609c0d68..8c81465f9f 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use super::JwkStorageDocumentError as Error; use crate::try_undo_key_generation; use crate::JwkGenOutput; -use crate::JwkMemStore; +use crate::KeyType; use crate::JwkStorage; use crate::JwkStoragePQ; use crate::JwsSignatureOptions; @@ -55,15 +55,15 @@ macro_rules! generate_method_hybrid_for_document_type { { let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { CompositeAlgId::IdMldsa44Ed25519Sha512 => ( - JwkMemStore::ML_DSA_KEY_TYPE, + KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_44, - JwkMemStore::ED25519_KEY_TYPE, + KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), CompositeAlgId::IdMldsa65Ed25519Sha512 => ( - JwkMemStore::ML_DSA_KEY_TYPE, + KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_65, - JwkMemStore::ED25519_KEY_TYPE, + KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), }; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index d31ea06bf4..b723cdbe6a 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -11,7 +11,7 @@ mod jwp_document_ext; mod signature_options; #[cfg(feature = "jpt-bbs-plus")] mod timeframe_revocation_ext; -#[cfg(feature = "pqc")] +#[cfg(feature = "hybrid")] mod hybrid_jws_document_ext; #[cfg(feature = "pqc")] mod pqc_jws_document_ext; @@ -27,7 +27,7 @@ pub use jwp_document_ext::*; pub use signature_options::*; #[cfg(feature = "jpt-bbs-plus")] pub use timeframe_revocation_ext::*; -#[cfg(feature = "pqc")] +#[cfg(feature = "hybrid")] pub use hybrid_jws_document_ext::*; #[cfg(feature = "pqc")] pub use pqc_jws_document_ext::*; From 6476cc54f66dbc1c99a8d4928dc1e1d9c2d2fe36 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 12 Sep 2024 11:24:21 +0200 Subject: [PATCH 046/163] implement pqc bindings --- bindings/wasm/Cargo.toml | 3 +- bindings/wasm/docs/api-reference.md | 195 ++++++++++--------- bindings/wasm/lib/jose/jwk_type.ts | 2 + bindings/wasm/lib/jose/jws_algorithm.ts | 5 + bindings/wasm/lib/jwk_storage.ts | 3 +- bindings/wasm/src/iota/iota_document.rs | 29 +++ bindings/wasm/src/jose/jwk.rs | 24 ++- bindings/wasm/src/jose/types.rs | 2 + bindings/wasm/src/storage/jwk_storage.rs | 4 + bindings/wasm/src/storage/jwk_storage_pqc.rs | 62 ++++++ bindings/wasm/src/storage/mod.rs | 2 + 11 files changed, 240 insertions(+), 91 deletions(-) create mode 100644 bindings/wasm/src/storage/jwk_storage_pqc.rs diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 8648bd3f4e..6cae55401b 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -31,12 +31,13 @@ serde_repr = { version = "0.1", default-features = false } tokio = { version = "1.29", default-features = false, features = ["sync"] } wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } +web-sys = { version = "0.3", features = ["console"]} zkryptium = "0.2.2" [dependencies.identity_iota] path = "../../identity_iota" default-features = false -features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus"] +features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus", "pqc"] [dev-dependencies] rand = "0.8.5" diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index 5eecd01cbc..b5fc84c07e 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -252,20 +252,12 @@ working with storage backed DID documents.

## Members
-
StatusPurpose
-

Purpose of a StatusList2021.

-
-
PayloadType
+
StateMetadataEncoding
+
+
SerializationType
+
+
PresentationProofAlgorithm
-
FailFast
-

Declares when validation should return if an error occurs.

-
-
AllErrors
-

Return all errors that occur during validation.

-
-
FirstError
-

Return after the first error occurs.

-
SubjectHolderRelationship

Declares how credential subjects must relate to the presentation holder.

See also the Subject-Holder Relationship section of the specification.

@@ -280,14 +272,26 @@ This variant is the default.

Any

The holder is not required to have any kind of relationship to any credential subject.

-
SerializationType
-
MethodRelationship
-
CredentialStatus
+
PayloadType
-
PresentationProofAlgorithm
+
StatusPurpose
+

Purpose of a StatusList2021.

+
+
ProofAlgorithm
+
CredentialStatus
+
+
FailFast
+

Declares when validation should return if an error occurs.

+
+
AllErrors
+

Return all errors that occur during validation.

+
+
FirstError
+

Return after the first error occurs.

+
StatusCheck

Controls validation behaviour when checking whether or not a credential has been revoked by its credentialStatus.

@@ -305,22 +309,13 @@ This variant is the default.

SkipAll

Skip all status checks.

-
StateMetadataEncoding
-
-
ProofAlgorithm
-
## Functions
-
verifyEd25519(alg, signingInput, decodedSignature, publicKey)
-

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

-

This function is useful when one is composing a IJwsVerifier that delegates -EdDSA verification with curve Ed25519 to this function.

-

Warning

-

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this -prior to calling the function.

+
start()
+

Initializes the console error panic hook for better error messages

encodeB64(data) ⇒ string

Encode the given bytes in url-safe base64.

@@ -328,8 +323,13 @@ prior to calling the function.

decodeB64(data) ⇒ Uint8Array

Decode the given url-safe base64-encoded slice into its raw bytes.

-
start()
-

Initializes the console error panic hook for better error messages

+
verifyEd25519(alg, signingInput, decodedSignature, publicKey)
+

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

+

This function is useful when one is composing a IJwsVerifier that delegates +EdDSA verification with curve Ed25519 to this function.

+

Warning

+

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this +prior to calling the function.

@@ -2201,6 +2201,7 @@ if the object is being concurrently modified. * [.createPresentedJwp(presentation, method_id, options)](#IotaDocument+createPresentedJwp) ⇒ Promise.<string> * [.createCredentialJpt(credential, storage, fragment, options, [custom_claims])](#IotaDocument+createCredentialJpt) ⇒ [Promise.<Jpt>](#Jpt) * [.createPresentationJpt(presentation, method_id, options)](#IotaDocument+createPresentationJpt) ⇒ [Promise.<Jpt>](#Jpt) + * [.generateMethodPQC(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethodPQC) ⇒ Promise.<string> * _static_ * [.newWithId(id)](#IotaDocument.newWithId) ⇒ [IotaDocument](#IotaDocument) * [.unpackFromOutput(did, aliasOutput, allowEmpty)](#IotaDocument.unpackFromOutput) ⇒ [IotaDocument](#IotaDocument) @@ -2765,6 +2766,19 @@ private key backed by the `storage` in accordance with the passed `options`. | method_id | string | | options | [JwpPresentationOptions](#JwpPresentationOptions) | + + +### iotaDocument.generateMethodPQC(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> +**Kind**: instance method of [IotaDocument](#IotaDocument) + +| Param | Type | +| --- | --- | +| storage | [Storage](#Storage) | +| keyType | string | +| alg | JwsAlgorithm | +| fragment | string \| undefined | +| scope | [MethodScope](#MethodScope) | + ### IotaDocument.newWithId(id) ⇒ [IotaDocument](#IotaDocument) @@ -3394,6 +3408,7 @@ Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`. * [.paramsOkp()](#Jwk+paramsOkp) ⇒ JwkParamsOkp \| undefined * [.paramsOct()](#Jwk+paramsOct) ⇒ JwkParamsOct \| undefined * [.paramsRsa()](#Jwk+paramsRsa) ⇒ JwkParamsRsa \| undefined + * [.paramsMldsa()](#Jwk+paramsMldsa) ⇒ JwkParamsPQ \| undefined * [.toPublic()](#Jwk+toPublic) ⇒ [Jwk](#Jwk) \| undefined * [.isPublic()](#Jwk+isPublic) ⇒ boolean * [.isPrivate()](#Jwk+isPrivate) ⇒ boolean @@ -3485,6 +3500,10 @@ If this JWK is of kty OCT, returns those parameters. ### jwk.paramsRsa() ⇒ JwkParamsRsa \| undefined If this JWK is of kty RSA, returns those parameters. +**Kind**: instance method of [Jwk](#Jwk) + + +### jwk.paramsMldsa() ⇒ JwkParamsPQ \| undefined **Kind**: instance method of [Jwk](#Jwk) @@ -7512,33 +7531,17 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | - - -## StatusPurpose -Purpose of a [StatusList2021](#StatusList2021). - -**Kind**: global variable - - -## PayloadType -**Kind**: global variable - - -## FailFast -Declares when validation should return if an error occurs. + +## StateMetadataEncoding **Kind**: global variable - - -## AllErrors -Return all errors that occur during validation. + +## SerializationType **Kind**: global variable - - -## FirstError -Return after the first error occurs. + +## PresentationProofAlgorithm **Kind**: global variable @@ -7566,22 +7569,46 @@ The holder must match the subject only for credentials where the [`nonTransferab ## Any The holder is not required to have any kind of relationship to any credential subject. -**Kind**: global variable - - -## SerializationType **Kind**: global variable ## MethodRelationship **Kind**: global variable + + +## PayloadType +**Kind**: global variable + + +## StatusPurpose +Purpose of a [StatusList2021](#StatusList2021). + +**Kind**: global variable + + +## ProofAlgorithm +**Kind**: global variable ## CredentialStatus **Kind**: global variable - + + +## FailFast +Declares when validation should return if an error occurs. + +**Kind**: global variable + + +## AllErrors +Return all errors that occur during validation. + +**Kind**: global variable + + +## FirstError +Return after the first error occurs. -## PresentationProofAlgorithm **Kind**: global variable @@ -7614,36 +7641,12 @@ Validate the status if supported, skip any unsupported Skip all status checks. **Kind**: global variable - - -## StateMetadataEncoding -**Kind**: global variable - - -## ProofAlgorithm -**Kind**: global variable - - -## verifyEd25519(alg, signingInput, decodedSignature, publicKey) -Verify a JWS signature secured with the `EdDSA` algorithm and curve `Ed25519`. - -This function is useful when one is composing a `IJwsVerifier` that delegates -`EdDSA` verification with curve `Ed25519` to this function. - -# Warning + -This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this -prior to calling the function. +## start() +Initializes the console error panic hook for better error messages **Kind**: global function - -| Param | Type | -| --- | --- | -| alg | JwsAlgorithm | -| signingInput | Uint8Array | -| decodedSignature | Uint8Array | -| publicKey | [Jwk](#Jwk) | - ## encodeB64(data) ⇒ string @@ -7666,9 +7669,25 @@ Decode the given url-safe base64-encoded slice into its raw bytes. | --- | --- | | data | Uint8Array | - + -## start() -Initializes the console error panic hook for better error messages +## verifyEd25519(alg, signingInput, decodedSignature, publicKey) +Verify a JWS signature secured with the `EdDSA` algorithm and curve `Ed25519`. + +This function is useful when one is composing a `IJwsVerifier` that delegates +`EdDSA` verification with curve `Ed25519` to this function. + +# Warning + +This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this +prior to calling the function. **Kind**: global function + +| Param | Type | +| --- | --- | +| alg | JwsAlgorithm | +| signingInput | Uint8Array | +| decodedSignature | Uint8Array | +| publicKey | [Jwk](#Jwk) | + diff --git a/bindings/wasm/lib/jose/jwk_type.ts b/bindings/wasm/lib/jose/jwk_type.ts index 1707c3295f..50c21a3de2 100644 --- a/bindings/wasm/lib/jose/jwk_type.ts +++ b/bindings/wasm/lib/jose/jwk_type.ts @@ -10,4 +10,6 @@ export const enum JwkType { Oct = "oct", /** Octet string key pairs. */ Okp = "OKP", + + MLDSA = "ML-DSA", } diff --git a/bindings/wasm/lib/jose/jws_algorithm.ts b/bindings/wasm/lib/jose/jws_algorithm.ts index 54cd306a27..4d832b174f 100644 --- a/bindings/wasm/lib/jose/jws_algorithm.ts +++ b/bindings/wasm/lib/jose/jws_algorithm.ts @@ -32,4 +32,9 @@ export const enum JwsAlgorithm { NONE = "none", /** EdDSA signature algorithms */ EdDSA = "EdDSA", + + MLDSA44 = "ML-DSA-44", + MLDSA65 = "ML-DSA-65", + MLDSA87 = "ML-DSA-87", + } diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 235abcc8ce..3afcfebe0e 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -130,6 +130,7 @@ function decodeJwk(jwk: Jwk): [Ed25519PrivateKey, Ed25519PublicKey] { } } +//TODO: non sembra servire a nulla export interface JwkStorageBBSPlusExt { // Generate a new BLS12381 key represented as a JSON Web Key. generateBBS: (algorithm: ProofAlgorithm) => Promise; @@ -162,4 +163,4 @@ function randomKeyId(): string { } return encodeB64(randomness); -} +} \ No newline at end of file diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index 777a00e679..6b28f9478d 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -82,6 +82,7 @@ use crate::verification::WasmMethodRelationship; use crate::verification::WasmMethodScope; use crate::verification::WasmVerificationMethod; use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwsDocumentExtPQC; pub(crate) struct IotaDocumentLock(tokio::sync::RwLock); @@ -993,6 +994,34 @@ impl WasmIotaDocument { Ok(promise.unchecked_into()) } + + #[wasm_bindgen(js_name = generateMethodPQC)] + #[allow(non_snake_case)] + pub fn generate_method_pqc( + &self, + storage: &WasmStorage, + keyType: String, + alg: WasmJwsAlgorithm, + fragment: Option, + scope: WasmMethodScope, + ) -> Result { + let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; + let document_lock_clone: Rc = self.0.clone(); + let storage_clone: Rc = storage.0.clone(); + let scope: MethodScope = scope.0; + web_sys::console::log_1(&"PPPPPPPPPPPPPPP".into()); + let promise: Promise = future_to_promise(async move { + let method_fragment: String = document_lock_clone + .write() + .await + .generate_method_pqc(&storage_clone, KeyType::from(keyType), alg, fragment.as_deref(), scope) + .await + .wasm_result()?; + web_sys::console::log_1(&method_fragment.clone().into()); + Ok(JsValue::from(method_fragment)) + }); + Ok(promise.unchecked_into()) + } } impl From for WasmIotaDocument { diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 877ee30481..80e49f2af6 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -15,6 +15,7 @@ use crate::jose::WasmJwkParamsEc; use crate::jose::WasmJwkParamsOct; use crate::jose::WasmJwkParamsOkp; use crate::jose::WasmJwkParamsRsa; +use crate::jose::WasmJwkParamsMLDSA; use crate::jose::WasmJwkType; use crate::jose::WasmJwkUse; use crate::jose::WasmJwsAlgorithm; @@ -164,6 +165,16 @@ impl WasmJwk { } } + #[wasm_bindgen(js_name = paramsMldsa)] + pub fn params_mldsa(&self) -> crate::error::Result> { + if let JwkParams::MLDSA(params_mldsa) = self.0.params() { + // WARNING: this does not validate the return type. Check carefully. + Ok(Some(JsValue::from_serde(params_mldsa).wasm_result()?.unchecked_into())) + } else { + Ok(None) + } + } + /// Returns a clone of the {@link Jwk} with _all_ private key components unset. /// Nothing is returned when `kty = oct` as this key type is not considered public by this library. #[wasm_bindgen(js_name = toPublic)] @@ -201,7 +212,7 @@ impl_wasm_clone!(WasmJwk, Jwk); #[wasm_bindgen(typescript_custom_section)] const I_JWK: &'static str = r#" -type IJwkParams = IJwkEc | IJwkRsa | IJwkOkp | IJwkOct +type IJwkParams = IJwkEc | IJwkRsa | IJwkOkp | IJwkOct | IJwkMLDSA /** A JSON Web Key with EC params. */ export interface IJwkEc extends IJwk, JwkParamsEc { kty: JwkType.Ec @@ -218,6 +229,9 @@ export interface IJwkOkp extends IJwk, JwkParamsOkp { export interface IJwkOct extends IJwk, JwkParamsOct { kty: JwkType.Oct } +export interface IJwkMLDSA extends IJwk, JwkParamsPQ { + kty: JwkType.MLDSA +} "#; #[wasm_bindgen(typescript_custom_section)] @@ -401,3 +415,11 @@ interface JwkParamsOct { * [More Info](https://tools.ietf.org/html/rfc7518#section-6.4.1) */ k: string }"#; + + +#[wasm_bindgen(typescript_custom_section)] +const IJWK_PARAMS_PQ: &str = r#" +interface JwkParamsPQ { + pub: string, + priv?: string +}"#; \ No newline at end of file diff --git a/bindings/wasm/src/jose/types.rs b/bindings/wasm/src/jose/types.rs index b069dd5bb3..76d693bed3 100644 --- a/bindings/wasm/src/jose/types.rs +++ b/bindings/wasm/src/jose/types.rs @@ -25,6 +25,8 @@ extern "C" { pub type WasmJwkParamsRsa; #[wasm_bindgen(typescript_type = "JwkParamsOct")] pub type WasmJwkParamsOct; + #[wasm_bindgen(typescript_type = "JwkParamsPQ")] + pub type WasmJwkParamsMLDSA; } impl TryFrom for JwsAlgorithm { diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 6adf78845b..95d12a1d53 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -10,6 +10,7 @@ use crate::jose::WasmJwk; use identity_iota::storage::key_storage::JwkGenOutput; use identity_iota::storage::key_storage::JwkStorage; +use identity_iota::storage::key_storage::JwkStoragePQ; use identity_iota::storage::key_storage::KeyId; use identity_iota::storage::key_storage::KeyStorageError; use identity_iota::storage::key_storage::KeyStorageErrorKind; @@ -38,6 +39,8 @@ extern "C" { #[wasm_bindgen(method)] pub fn generate(this: &WasmJwkStorage, key_type: String, algorithm: String) -> PromiseJwkGenOutput; + #[wasm_bindgen(method)] + pub fn generate_pq_key(this: &WasmJwkStorage, key_type: String, algorithm: String) -> PromiseJwkGenOutput; #[wasm_bindgen(method)] pub fn insert(this: &WasmJwkStorage, jwk: WasmJwk) -> PromiseString; @@ -64,6 +67,7 @@ impl JwkStorage for WasmJwkStorage { } async fn insert(&self, jwk: Jwk) -> KeyStorageResult { + web_sys::console::log_1(&"WWWWWWWWWWW".into()); let promise: Promise = Promise::resolve(&WasmJwkStorage::insert(self, WasmJwk::from(jwk))); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs new file mode 100644 index 0000000000..95b5a8e001 --- /dev/null +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -0,0 +1,62 @@ +use std::str::FromStr; + +use crate::error::Result as WasmResult; +use crate::error::WasmResult as _; +use crate::jose::WasmJwk; +use crate::jose::WasmJwsAlgorithm; + +use super::WasmJwkGenOutput; +use super::WasmJwkStorage; +use super::WasmProofUpdateCtx; + +use identity_iota::storage::bls::encode_bls_jwk; +use identity_iota::storage::bls::expand_bls_jwk; +use identity_iota::storage::bls::generate_bbs_keypair; +use identity_iota::storage::bls::sign_bbs; +use identity_iota::storage::bls::update_bbs_signature; +use identity_iota::storage::JwkGenOutput; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::JwkStoragePQ; +use identity_iota::storage::KeyId; +use identity_iota::storage::KeyStorageError; +use identity_iota::storage::KeyStorageErrorKind; +use identity_iota::storage::KeyStorageResult; +use identity_iota::storage::KeyType; +use identity_iota::storage::ProofUpdateCtx; +use identity_iota::verification::jwk::Jwk; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use wasm_bindgen::prelude::*; +use identity_iota::verification::jose::jws::JwsAlgorithm; +use crate::error::JsValueResult; +use js_sys::Array; +use js_sys::Promise; +use js_sys::Uint8Array; +use wasm_bindgen_futures::JsFuture; + +#[async_trait::async_trait(?Send)] +impl JwkStoragePQ for WasmJwkStorage { + async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { + web_sys::console::log_1(&"EEEEEEEEEEEEET".into()); + let promise: Promise = Promise::resolve(&WasmJwkStorage::generate_pq_key(self, key_type.into(), alg.name().to_owned())); + let result: JsValueResult = JsFuture::from(promise).await.into(); + result.into() + } + + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + todo!(); + } + + +} + + + +#[wasm_bindgen(typescript_custom_section)] +const JWK_STORAGE_PQ: &'static str = r#" +/** Secure storage for cryptographic keys represented as JWKs. */ +interface JwkStoragePQ { + /** Generate a new key represented as a JSON Web Key. + * + * It's recommend that the implementer exposes constants for the supported key type string. */ + generate_pq_key: (keyType: string, algorithm: JwsAlgorithm) => Promise; +}"#; \ No newline at end of file diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index fe54110e9d..493a9eb29c 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -10,6 +10,7 @@ mod key_id_storage; mod method_digest; mod signature_options; mod wasm_storage; +mod jwk_storage_pqc; pub use jpt_timeframe_revocation_ext::*; pub use jwk_gen_output::*; @@ -19,3 +20,4 @@ pub use key_id_storage::*; pub use method_digest::*; pub use signature_options::*; pub use wasm_storage::*; +pub use jwk_storage_pqc::*; From 0b9e633d6585e935aab42cb142f03e1cd21a902e Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 18 Sep 2024 15:15:25 +0200 Subject: [PATCH 047/163] update cargo --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e83cc92240..6c776e541b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = { version = "0.3.5" } -zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } +json-proof-token = { path = "../json-proof-token-tmp" } +zkryptium = { path = "../zkryptium-jed", default-features = false, features = ["bbsplus"] } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } From d71af7b425aaa15b08213ec4b6809f469c4285bb Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 18 Sep 2024 15:44:39 +0200 Subject: [PATCH 048/163] fix example names --- Cargo.toml | 4 ++-- examples/1_advanced/{11_pqc.rs => 12_pqc.rs} | 0 examples/1_advanced/{12_hybrid.rs => 13_hybrid.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename examples/1_advanced/{11_pqc.rs => 12_pqc.rs} (100%) rename examples/1_advanced/{12_hybrid.rs => 13_hybrid.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 8547ededf6..9e03c17106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = { path = "../json-proof-token-tmp" } -zkryptium = { path = "../zkryptium-jed", default-features = false, features = ["bbsplus"] } +json-proof-token = { version = "0.3.5" } +zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } diff --git a/examples/1_advanced/11_pqc.rs b/examples/1_advanced/12_pqc.rs similarity index 100% rename from examples/1_advanced/11_pqc.rs rename to examples/1_advanced/12_pqc.rs diff --git a/examples/1_advanced/12_hybrid.rs b/examples/1_advanced/13_hybrid.rs similarity index 100% rename from examples/1_advanced/12_hybrid.rs rename to examples/1_advanced/13_hybrid.rs From 84b1670ca946151460c6a3157401ff8c393f78e1 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 9 Oct 2024 11:20:34 +0200 Subject: [PATCH 049/163] change example name --- examples/1_advanced/{11_did_web.rs => 14_did_web.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/1_advanced/{11_did_web.rs => 14_did_web.rs} (100%) diff --git a/examples/1_advanced/11_did_web.rs b/examples/1_advanced/14_did_web.rs similarity index 100% rename from examples/1_advanced/11_did_web.rs rename to examples/1_advanced/14_did_web.rs From 161853c5ad28f8854fe06234e8edc09cb14f7c50 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 10 Oct 2024 15:24:29 +0200 Subject: [PATCH 050/163] introduce DidJwkDocumentExt for DIDJwk creation --- examples/Cargo.toml | 7 + examples/demo/hybrid.rs | 0 examples/demo/pqc.rs | 0 examples/demo/traditional.rs | 221 ++++++++++++++++++ examples/demo/traditional_zk.rs | 0 identity_did/src/did.rs | 1 + .../src/storage/did_jwk_document_ext.rs | 116 +++++++++ identity_storage/src/storage/mod.rs | 4 + 8 files changed, 349 insertions(+) create mode 100644 examples/demo/hybrid.rs create mode 100644 examples/demo/pqc.rs create mode 100644 examples/demo/traditional.rs create mode 100644 examples/demo/traditional_zk.rs create mode 100644 identity_storage/src/storage/did_jwk_document_ext.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a38f4b19dd..e683fd1706 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,6 +21,8 @@ serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } serde.workspace = true +log = "0.4.0" +env_logger = "0.10.0" [lib] path = "utils/utils.rs" @@ -120,3 +122,8 @@ name = "13_hybrid" [[example]] path = "1_advanced/14_did_web.rs" name = "14_did_web" + +[[example]] +path = "demo/traditional.rs" +name = "traditional" + diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs new file mode 100644 index 0000000000..7eab2db3f5 --- /dev/null +++ b/examples/demo/traditional.rs @@ -0,0 +1,221 @@ +use std::{collections::HashMap, fs::File, path::Path}; +use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; +use identity_iota::storage::JwkDocumentExt; +use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; +use reqwest::ClientBuilder; +use serde_json::json; + + +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} + + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + + env_logger::init(); + + let did_url: &str = "https://localhost:4443/.well-known/did.json"; + let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; + + // Create a new client to make HTTPS requests. + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + // Create a new Web DID document. + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + // Insert a new Ed25519 verification method in the DID document. + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method( + &storage_issuer, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod, + ) + .await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + println!("Web DID Document: {:#}", issuer_document); + + + // ---------------------------- + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( + &storage_alice, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA + ).await?; + + // ---------------------------- + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!("Sending credential (as JWT) to the holder: {credential:#}"); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!("Sending presentation (as JWT) to the verifier: {presentation:#}"); + + println!("Presentation JWT: {}", presentation_jwt.as_str()); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_jwk_handler(); + + + // Resolve the holder's document. + let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + + Ok(()) +} diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/identity_did/src/did.rs b/identity_did/src/did.rs index de9cf61183..6899df3156 100644 --- a/identity_did/src/did.rs +++ b/identity_did/src/did.rs @@ -160,6 +160,7 @@ impl CoreDID { /// Checks if the given `did` is valid according to the base [`DID`] specification. pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> { + println!("did: {}", did); // Validate basic DID constraints. Self::valid_method_name(did.method())?; Self::valid_method_id(did.method_id())?; diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs new file mode 100644 index 0000000000..e91612c9df --- /dev/null +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -0,0 +1,116 @@ +use identity_did::DIDJwk; +use identity_document::document::{self, CoreDocument}; +use identity_verification::{jws::JwsAlgorithm, jwu::encode_b64_json, verification_method}; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use async_trait::async_trait; + +use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentError as Error, JwkStoragePQ, KeyIdStorage, KeyType, MethodDigest}; + +use super::{try_undo_key_generation, Storage, StorageResult}; + + + +/// a +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait DidJwkDocumentExt{ +/// a + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage; +/// a + #[cfg(feature = "pqc")] + async fn new_did_jwk_pqc( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> Self + where + K: JwkStoragePQ, + I: KeyIdStorage; +/// a + #[cfg(feature = "jpt-bbs-plus")] + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> Self + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage; +} + + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl DidJwkDocumentExt for CoreDocument { + + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage { + + let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_pqc( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> Self + where + K: JwkStoragePQ, + I: KeyIdStorage { + todo!() + } + + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> Self + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage { + todo!() + } +} \ No newline at end of file diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index b723cdbe6a..5c7f2f2a8c 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -16,6 +16,8 @@ mod hybrid_jws_document_ext; #[cfg(feature = "pqc")] mod pqc_jws_document_ext; +mod did_jwk_document_ext; + #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; @@ -32,6 +34,8 @@ pub use hybrid_jws_document_ext::*; #[cfg(feature = "pqc")] pub use pqc_jws_document_ext::*; +pub use did_jwk_document_ext::*; + /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from /// [`JwkDocumentExt`](crate::storage::JwkDocumentExt). From 1b240f04e34d3aecf8ba1cc5e5a512667498c6b6 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 10 Oct 2024 18:32:22 +0200 Subject: [PATCH 051/163] pqc demo --- examples/Cargo.toml | 5 + examples/demo/pqc.rs | 199 ++++++++++++++++++ examples/demo/traditional.rs | 77 +++---- .../src/storage/did_jwk_document_ext.rs | 73 ++++++- 4 files changed, 298 insertions(+), 56 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e683fd1706..625c4b0200 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -23,6 +23,7 @@ identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = serde.workspace = true log = "0.4.0" env_logger = "0.10.0" +colored = "2.1.0" [lib] path = "utils/utils.rs" @@ -127,3 +128,7 @@ name = "14_did_web" path = "demo/traditional.rs" name = "traditional" +[[example]] +path = "demo/pqc.rs" +name = "pqc" + diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs index e69de29bb2..eb1a9493cb 100644 --- a/examples/demo/pqc.rs +++ b/examples/demo/pqc.rs @@ -0,0 +1,199 @@ +use std::{collections::HashMap, fs::File, path::Path}; +use env_logger::Env; +use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; +use identity_iota::storage::JwkDocumentExt; +use identity_pqc_verifier::PQCJwsVerifier; +use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; +use reqwest::ClientBuilder; +use serde_json::json; + + +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} + + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + let did_url: &str = "https://localhost:4443/.well-known/did_pqc.json"; + let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_pqc.json"; + + log::info!("[Issuer]: Create DID (with did:web method) and publish the DID Document at {}", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method_pqc( + &storage_issuer, + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_44, + None, + MethodScope::VerificationMethod, + ) + .await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_pqc( + &storage_alice, + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_87 + ).await?; + + log::info!("[Holder]: Create DID Jwk: {}", alice_document.id().as_str()); + + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + log::info!("[Holder]: Inserted Credential subject information: {}", serde_json::to_string_pretty(&subject)?); + + log::info!("[Holder] <-> [Issuer]: Challenge-response protocol to authenticate Holder's DID"); + + + log::info!("[Issuer]: Construct VC"); + + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_pqc( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + + log::info!("[Issuer] -> [Holder]: Sending VC (as JWT): {}", credential_jwt.as_str()); + + log::info!("[Holder]: Resolve Issuer's DID: {}", issuer_document.id().as_str()); + + log::info!("[Holder]: Validate VC"); + + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + log::info!("[Verifier] -> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + log::info!("[Holder]: Construct VP"); + + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt_pqc( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + log::info!("[Holder] -> [Verifier]: Sending VP (as JWT): {}", presentation_jwt.as_str()); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_jwk_handler(); + + + // Resolve the holder's document. + let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + PQCJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + + Ok(()) +} diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 7eab2db3f5..818451d71a 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -1,4 +1,5 @@ use std::{collections::HashMap, fs::File, path::Path}; +use env_logger::Env; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; @@ -19,21 +20,19 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - - env_logger::init(); + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; + + log::info!("[Issuer]: Create DID (with did:web method) and publish the DID Document at {}", did_url); - // Create a new client to make HTTPS requests. let client= ClientBuilder::new() .danger_accept_invalid_certs(true) .build()?; - // Create a new Web DID document. let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - // Insert a new Ed25519 verification method in the DID document. + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); let fragment_issuer = issuer_document .generate_method( @@ -46,10 +45,6 @@ async fn main() -> anyhow::Result<()> { .await?; write_to_file(&issuer_document, Some(path_did_file))?; - println!("Web DID Document: {:#}", issuer_document); - - - // ---------------------------- let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); @@ -59,13 +54,9 @@ async fn main() -> anyhow::Result<()> { JwsAlgorithm::EdDSA ).await?; - // ---------------------------- - - // =========================================================================== - // Step 2: Issuer creates and signs a Verifiable Credential. - // =========================================================================== - - // Create a credential subject indicating the degree earned by Alice. + log::info!("[Holder]: Create DID Jwk: {}", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), "name": "Alice", @@ -75,8 +66,14 @@ async fn main() -> anyhow::Result<()> { }, "GPA": "4.0", }))?; + + log::info!("[Holder]: Inserted Credential subject information: {}", serde_json::to_string_pretty(&subject)?); + + log::info!("[Holder] <-> [Issuer]: Challenge-response protocol to authenticate Holder's DID"); + - // Build credential using subject above and issuer. + log::info!("[Issuer]: Construct VC"); + let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) .issuer(Url::parse(issuer_document.id().as_str())?) @@ -93,12 +90,14 @@ async fn main() -> anyhow::Result<()> { None, ) .await?; - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, - // that the issuance date is not in the future and that the expiration date is not in the past: + + + log::info!("[Issuer] -> [Holder]: Sending VC (as JWT): {}", credential_jwt.as_str()); + + log::info!("[Holder]: Resolve Issuer's DID: {}", issuer_document.id().as_str()); + + log::info!("[Holder]: Validate VC"); + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) .validate::<_, Object>( &credential_jwt, @@ -108,36 +107,19 @@ async fn main() -> anyhow::Result<()> { ) .unwrap(); - println!("VC successfully validated"); - - // =========================================================================== - // Step 3: Issuer sends the Verifiable Credential to the holder. - // =========================================================================== - println!("Sending credential (as JWT) to the holder: {credential:#}"); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== + log::info!("[Verifier] -> [Holder]: Send challenge"); - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - // The verifier and holder also agree that the signature should have an expiry date - // 10 minutes from now. let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - // =========================================================================== - // Step 5: Holder creates and signs a verifiable presentation from the issued credential. - // =========================================================================== - - // Create an unsigned Presentation from the previously issued Verifiable Credential. + log::info!("[Holder]: Construct VP"); + let presentation: Presentation = PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) .credential(credential_jwt) .build()?; - // Create a JWT verifiable presentation using the holder's verification method - // and include the requested challenge and expiry timestamp. let presentation_jwt: Jwt = alice_document .create_presentation_jwt( &presentation, @@ -147,13 +129,8 @@ async fn main() -> anyhow::Result<()> { &JwtPresentationOptions::default().expiration_date(expires), ) .await?; - - // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. - // =========================================================================== - println!("Sending presentation (as JWT) to the verifier: {presentation:#}"); - println!("Presentation JWT: {}", presentation_jwt.as_str()); + log::info!("[Holder] -> [Verifier]: Sending VP (as JWT): {}", presentation_jwt.as_str()); // =========================================================================== // Step 7: Verifier receives the Verifiable Presentation and verifies it. diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index e91612c9df..c8f81c91da 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -29,7 +29,7 @@ pub trait DidJwkDocumentExt{ storage: &Storage, key_type: KeyType, alg: JwsAlgorithm, - ) -> Self + ) -> StorageResult<(CoreDocument, String)> where K: JwkStoragePQ, I: KeyIdStorage; @@ -39,7 +39,7 @@ pub trait DidJwkDocumentExt{ storage: &Storage, key_type: KeyType, alg: ProofAlgorithm, - ) -> Self + ) -> StorageResult<(CoreDocument, String)> where K: JwkStorageBbsPlusExt, I: KeyIdStorage; @@ -96,21 +96,82 @@ impl DidJwkDocumentExt for CoreDocument { storage: &Storage, key_type: KeyType, alg: JwsAlgorithm, - ) -> Self + ) -> StorageResult<(CoreDocument, String)> where K: JwkStoragePQ, I: KeyIdStorage { - todo!() + + let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) } async fn new_did_jwk_zk( storage: &Storage, key_type: KeyType, alg: ProofAlgorithm, - ) -> Self + ) -> StorageResult<(CoreDocument, String)> where K: JwkStorageBbsPlusExt, I: KeyIdStorage { - todo!() + let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) } } \ No newline at end of file From 9071cc2c71b26fa391a38420b74fd25a0dce7b12 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:27:56 +0200 Subject: [PATCH 052/163] change print format --- examples/demo/pqc.rs | 27 ++++++++++++--------------- examples/demo/traditional.rs | 28 ++++++++++++++-------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs index eb1a9493cb..a367500052 100644 --- a/examples/demo/pqc.rs +++ b/examples/demo/pqc.rs @@ -8,7 +8,7 @@ use identity_pqc_verifier::PQCJwsVerifier; use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::ClientBuilder; use serde_json::json; - +use colored::Colorize; pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { let path = Path::new(path.unwrap_or_else(|| "did.json")); @@ -26,7 +26,7 @@ async fn main() -> anyhow::Result<()> { let did_url: &str = "https://localhost:4443/.well-known/did_pqc.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_pqc.json"; - log::info!("[Issuer]: Create DID (with did:web method) and publish the DID Document at {}", did_url); + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); let client= ClientBuilder::new() .danger_accept_invalid_certs(true) @@ -55,8 +55,7 @@ async fn main() -> anyhow::Result<()> { JwsAlgorithm::ML_DSA_87 ).await?; - log::info!("[Holder]: Create DID Jwk: {}", alice_document.id().as_str()); - + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), @@ -68,12 +67,11 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - log::info!("[Holder]: Inserted Credential subject information: {}", serde_json::to_string_pretty(&subject)?); - - log::info!("[Holder] <-> [Issuer]: Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - log::info!("[Issuer]: Construct VC"); + println!("{} {} ","[Issuer]".red(), ": Construct VC"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -92,12 +90,11 @@ async fn main() -> anyhow::Result<()> { ) .await?; + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - log::info!("[Issuer] -> [Holder]: Sending VC (as JWT): {}", credential_jwt.as_str()); - - log::info!("[Holder]: Resolve Issuer's DID: {}", issuer_document.id().as_str()); + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - log::info!("[Holder]: Validate VC"); + println!("{} {}", "[Holder]".blue(), ": Validate VC"); JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) .validate::<_, Object>( @@ -108,13 +105,13 @@ async fn main() -> anyhow::Result<()> { ) .unwrap(); - log::info!("[Verifier] -> [Holder]: Send challenge"); + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - log::info!("[Holder]: Construct VP"); + println!("{} {}", "[Holder]".blue(), ": Construct VP"); let presentation: Presentation = PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) @@ -131,7 +128,7 @@ async fn main() -> anyhow::Result<()> { ) .await?; - log::info!("[Holder] -> [Verifier]: Sending VP (as JWT): {}", presentation_jwt.as_str()); + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); // =========================================================================== // Step 7: Verifier receives the Verifiable Presentation and verifies it. diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 818451d71a..1af7ab84d0 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -7,6 +7,8 @@ use identity_iota::storage::JwkDocumentExt; use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::ClientBuilder; use serde_json::json; +use colored::Colorize; + pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { @@ -20,12 +22,12 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + //env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; - log::info!("[Issuer]: Create DID (with did:web method) and publish the DID Document at {}", did_url); + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); let client= ClientBuilder::new() .danger_accept_invalid_certs(true) @@ -54,8 +56,7 @@ async fn main() -> anyhow::Result<()> { JwsAlgorithm::EdDSA ).await?; - log::info!("[Holder]: Create DID Jwk: {}", alice_document.id().as_str()); - + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), @@ -67,12 +68,11 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - log::info!("[Holder]: Inserted Credential subject information: {}", serde_json::to_string_pretty(&subject)?); + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); - log::info!("[Holder] <-> [Issuer]: Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - log::info!("[Issuer]: Construct VC"); + println!("{} {} ","[Issuer]".red(), ": Construct VC"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -92,11 +92,11 @@ async fn main() -> anyhow::Result<()> { .await?; - log::info!("[Issuer] -> [Holder]: Sending VC (as JWT): {}", credential_jwt.as_str()); + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - log::info!("[Holder]: Resolve Issuer's DID: {}", issuer_document.id().as_str()); + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - log::info!("[Holder]: Validate VC"); + println!("{} {}", "[Holder]".blue(), ": Validate VC"); JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) .validate::<_, Object>( @@ -107,13 +107,13 @@ async fn main() -> anyhow::Result<()> { ) .unwrap(); - log::info!("[Verifier] -> [Holder]: Send challenge"); + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - log::info!("[Holder]: Construct VP"); + println!("{} {}", "[Holder]".blue(), ": Construct VP"); let presentation: Presentation = PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) @@ -130,7 +130,7 @@ async fn main() -> anyhow::Result<()> { ) .await?; - log::info!("[Holder] -> [Verifier]: Sending VP (as JWT): {}", presentation_jwt.as_str()); + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); // =========================================================================== // Step 7: Verifier receives the Verifiable Presentation and verifies it. From 1c7bd0e220296754c5d8d112b2909c34467aa1b4 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 16 Oct 2024 11:01:17 +0200 Subject: [PATCH 053/163] impl compositeJwk and hybrid demo --- examples/1_advanced/13_hybrid.rs | 13 +- examples/Cargo.toml | 4 + examples/demo/hybrid.rs | 195 ++++++++++++++++++ .../jwt_credential_validator_hybrid.rs | 6 +- identity_did/src/did_compositejwk.rs | 85 ++++++++ identity_did/src/lib.rs | 4 +- .../src/document/core_document.rs | 20 ++ .../src/jwk/composite_jwk.rs | 6 +- identity_jose/src/jwk/mod.rs | 2 + identity_resolver/src/resolution/resolver.rs | 17 ++ .../src/key_id_storage/method_digest.rs | 2 +- .../src/storage/did_jwk_document_ext.rs | 83 +++++++- .../src/storage/hybrid_jws_document_ext.rs | 52 +---- .../src/verification_method/material.rs | 17 +- .../src/verification_method/method.rs | 54 ++++- .../src/verification_method/mod.rs | 2 - 16 files changed, 486 insertions(+), 76 deletions(-) create mode 100644 identity_did/src/did_compositejwk.rs rename identity_verification/src/verification_method/composite_public_key.rs => identity_jose/src/jwk/composite_jwk.rs (95%) diff --git a/examples/1_advanced/13_hybrid.rs b/examples/1_advanced/13_hybrid.rs index 9ef883b1a8..632b1534d6 100644 --- a/examples/1_advanced/13_hybrid.rs +++ b/examples/1_advanced/13_hybrid.rs @@ -38,7 +38,7 @@ use identity_iota::storage::JwkDocumentExtHybrid; use identity_iota::storage::JwkMemStore; use identity_iota::storage::JwsSignatureOptions; use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::CompositeAlgId; +use identity_iota::verification::jwk::CompositeAlgId; use identity_iota::verification::MethodScope; use identity_pqc_verifier::PQCJwsVerifier; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; @@ -49,10 +49,13 @@ use iota_sdk::types::block::address::Address; use iota_sdk::types::block::output::AliasOutput; use serde_json::json; -// The API endpoint of an IOTA node, e.g. Hornet. -const API_ENDPOINT: &str = "http://localhost"; -// The faucet endpoint allows requesting funds for testing purposes. -const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; +// // The API endpoint of an IOTA node, e.g. Hornet. +// const API_ENDPOINT: &str = "http://localhost"; +// // The faucet endpoint allows requesting funds for testing purposes. +// const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; + +const API_ENDPOINT: &str = "https://api.testnet.shimmer.network"; +const FAUCET_ENDPOINT: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; async fn create_did( client: &Client, diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 625c4b0200..013ed76c8c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -132,3 +132,7 @@ name = "traditional" path = "demo/pqc.rs" name = "pqc" +[[example]] +path = "demo/hybrid.rs" +name = "hybrid" + diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index e69de29bb2..329096d2f6 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -0,0 +1,195 @@ +use std::{collections::HashMap, fs::File, path::Path}; +use env_logger::Env; +use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorHybrid, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorHybrid, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDCompositeJwk, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExtHybrid, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jwk::CompositeAlgId, jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; +use identity_iota::storage::JwkDocumentExt; +use identity_pqc_verifier::PQCJwsVerifier; +use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; +use reqwest::ClientBuilder; +use serde_json::json; +use colored::Colorize; + +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} + + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + let did_url: &str = "https://localhost:4443/.well-known/did_hybrid.json"; + let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_hybrid.json"; + + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method_hybrid( + &storage_issuer, + CompositeAlgId::IdMldsa44Ed25519Sha512, + None, + MethodScope::VerificationMethod, + ) + .await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (alice_document, fragment_alice) = CoreDocument::new_did_compositejwk( + &storage_alice, + CompositeAlgId::IdMldsa44Ed25519Sha512 + ).await?; + + println!("{} {} {}", "[Holder]".blue(), ": Create DID compositeJwk:", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); + + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_hybrid( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Construct VP"); + + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt_hybrid( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_compositejwk_handler(); + + + // Resolve the holder's document. + let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( + EdDSAJwsVerifier::default(), + PQCJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidatorHybrid = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + + Ok(()) +} diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs index a5ba4cfe18..7b68591ef0 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -8,7 +8,7 @@ use identity_verification::jws::DecodedJws; use identity_verification::jws::Decoder; use identity_verification::jws::JwsValidationItem; use identity_verification::jws::JwsVerifier; -use identity_verification::CompositePublicKey; +use identity_verification::jwk::CompositeJwk; use super::CompoundCredentialValidationError; use super::DecodedJwtCredential; @@ -184,7 +184,7 @@ impl JwtCredentialValidatorHybrid jws: &JwsValidationItem<'a>, trusted_issuers: &'i [DOC], options: &JwsVerificationOptions, - ) -> Result<(&'a CompositePublicKey, DIDUrl), JwtValidationError> + ) -> Result<(&'a CompositeJwk, DIDUrl), JwtValidationError> where DOC: AsRef, 'i: 'a, @@ -236,7 +236,7 @@ impl JwtCredentialValidatorHybrid message: "could not extract CompositePublicKey from a method identified by kid", signer_ctx: SignerContext::Issuer, }) - .map(move |c: &CompositePublicKey| (c, method_id)) + .map(move |c: &CompositeJwk| (c, method_id)) } /// Stateless version of [`Self::verify_signature`] diff --git a/identity_did/src/did_compositejwk.rs b/identity_did/src/did_compositejwk.rs new file mode 100644 index 0000000000..665fca262a --- /dev/null +++ b/identity_did/src/did_compositejwk.rs @@ -0,0 +1,85 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Debug; +use std::fmt::Display; +use std::str::FromStr; + +use identity_jose::jwk::CompositeJwk; +use identity_jose::jwk::Jwk; +use identity_jose::jwu::decode_b64_json; + +use crate::CoreDID; +use crate::Error; +use crate::DID; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)] +#[repr(transparent)] +#[serde(into = "CoreDID", try_from = "CoreDID")] +/// A type representing a `did:compositejwk` DID. +pub struct DIDCompositeJwk(CoreDID); + +impl DIDCompositeJwk { + /// [`DIDCompositeJwk`]'s method. + pub const METHOD: &'static str = "compositejwk"; + + /// Tries to parse a [`DIDCompositeJwk`] from a string. + pub fn parse(s: &str) -> Result { + s.parse() + } + + /// Returns the JWK encoded inside this did:jwk. + pub fn composite_jwk(&self) -> CompositeJwk { + decode_b64_json(self.method_id()).expect("did:compositejwk encodes a valid compositeJwk") + } +} + +impl AsRef for DIDCompositeJwk { + fn as_ref(&self) -> &CoreDID { + &self.0 + } +} + +impl From for CoreDID { + fn from(value: DIDCompositeJwk) -> Self { + value.0 + } +} + +impl<'a> TryFrom<&'a str> for DIDCompositeJwk { + type Error = Error; + fn try_from(value: &'a str) -> Result { + value.parse() + } +} + +impl Display for DIDCompositeJwk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for DIDCompositeJwk { + type Err = Error; + fn from_str(s: &str) -> Result { + s.parse::().and_then(TryFrom::try_from) + } +} + +impl From for String { + fn from(value: DIDCompositeJwk) -> Self { + value.to_string() + } +} + +impl TryFrom for DIDCompositeJwk { + type Error = Error; + fn try_from(value: CoreDID) -> Result { + let Self::METHOD = value.method() else { + return Err(Error::InvalidMethodName); + }; + decode_b64_json::(value.method_id()) + .map(|_| Self(value)) + .map_err(|_| Error::InvalidMethodId) + } +} \ No newline at end of file diff --git a/identity_did/src/lib.rs b/identity_did/src/lib.rs index 83277d4fe7..9b07857312 100644 --- a/identity_did/src/lib.rs +++ b/identity_did/src/lib.rs @@ -22,6 +22,7 @@ mod did_jwk; mod did_url; mod error; mod did_web; +mod did_compositejwk; pub use crate::did_url::DIDUrl; pub use crate::did_url::RelativeDIDUrl; @@ -30,4 +31,5 @@ pub use did::CoreDID; pub use did::DID; pub use did_jwk::*; pub use error::Error; -pub use did_web::*; \ No newline at end of file +pub use did_web::*; +pub use did_compositejwk::*; \ No newline at end of file diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 25bcaeb3ae..302013e7c1 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -7,6 +7,7 @@ use core::fmt::Formatter; use std::collections::HashMap; use std::convert::Infallible; +use identity_did::DIDCompositeJwk; use identity_did::DIDJwk; use identity_did::WebDID; use identity_verification::jose::jwk::Jwk; @@ -1051,6 +1052,25 @@ impl CoreDocument { } } + +//TODO: expand_composite_jwk +impl CoreDocument { + /// Creates a [`CoreDocument`] from a did:jwk DID. + pub fn expand_did_compositejwk(did_compositejwk: DIDCompositeJwk) -> Result { + let verification_method = VerificationMethod::try_from(did_compositejwk.clone()).map_err(Error::InvalidKeyMaterial)?; + let verification_method_id = verification_method.id().clone(); + + DocumentBuilder::default() + .id(did_compositejwk.into()) + .verification_method(verification_method) + .assertion_method(verification_method_id.clone()) + .authentication(verification_method_id.clone()) + .capability_invocation(verification_method_id.clone()) + .capability_delegation(verification_method_id.clone()) + .build() + } +} + //TODO: Web - impl CoreDocument (WebDID) /// DID web impl CoreDocument { diff --git a/identity_verification/src/verification_method/composite_public_key.rs b/identity_jose/src/jwk/composite_jwk.rs similarity index 95% rename from identity_verification/src/verification_method/composite_public_key.rs rename to identity_jose/src/jwk/composite_jwk.rs index b4d5ccd28d..90001516cb 100644 --- a/identity_verification/src/verification_method/composite_public_key.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -1,6 +1,6 @@ //TODO: hybrid - composite public key -use identity_jose::jwk::Jwk; +use crate::jwk::Jwk; /// Mame of algorithms used to generate the hybrid signature. Values taken from [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-02#name-domain-separators). #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] @@ -25,7 +25,7 @@ impl CompositeAlgId { /// Represent a combination of a traditional public key and a post-quantum public key both in Jwk format. #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -pub struct CompositePublicKey { +pub struct CompositeJwk { #[serde(rename = "algId")] alg_id: CompositeAlgId, #[serde(rename = "traditionalPublicKey")] @@ -34,7 +34,7 @@ pub struct CompositePublicKey { pq_public_key: Jwk, } -impl CompositePublicKey { +impl CompositeJwk { /// Create a new CompositePublicKey structure. pub fn new(alg_id: CompositeAlgId, traditional_public_key: Jwk, pq_public_key: Jwk) -> Self { Self { diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index e7320ad470..9392176bdd 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -12,6 +12,7 @@ mod key_params; mod key_set; mod key_type; mod key_use; +mod composite_jwk; pub use self::curve::*; pub use self::jwk_pq::*; @@ -21,3 +22,4 @@ pub use self::key_params::*; pub use self::key_set::*; pub use self::key_type::*; pub use self::key_use::*; +pub use self::composite_jwk::*; diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index f977383e80..3d1a39ba25 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -4,6 +4,7 @@ use core::future::Future; use futures::stream::FuturesUnordered; use futures::TryStreamExt; +use identity_did::DIDCompositeJwk; use identity_did::DIDJwk; use identity_did::DID; use std::collections::HashSet; @@ -264,6 +265,22 @@ impl + 'static> Resolver> { } } +impl + 'static> Resolver> { + /// Attaches a handler capable of resolving `did:compositejwk` DIDs. + pub fn attach_did_compositejwk_handler(&mut self) { + let handler = |did_compositejwk: DIDCompositeJwk| async move { CoreDocument::expand_did_compositejwk(did_compositejwk) }; + self.attach_handler(DIDCompositeJwk::METHOD.to_string(), handler) + } +} + +impl + 'static> Resolver> { + /// Attaches a handler capable of resolving `did:compositejwk` DIDs. + pub fn attach_did_compositejwk_handler(&mut self) { + let handler = |did_compositejwk: DIDCompositeJwk| async move { CoreDocument::expand_did_compositejwk(did_compositejwk) }; + self.attach_handler(DIDCompositeJwk::METHOD.to_string(), handler) + } +} + #[cfg(feature = "iota")] mod iota_handler { use crate::ErrorCause; diff --git a/identity_storage/src/key_id_storage/method_digest.rs b/identity_storage/src/key_id_storage/method_digest.rs index 57b7883bb3..f4ce3dde1b 100644 --- a/identity_storage/src/key_id_storage/method_digest.rs +++ b/identity_storage/src/key_id_storage/method_digest.rs @@ -58,7 +58,7 @@ impl MethodDigest { match method_data { MethodData::PublicKeyJwk(jwk) => hasher.write(jwk.thumbprint_sha256().as_ref()), // MethodData::Custom(e) => hasher.write(&e.to_json_vec().unwrap()), //TODO: Hybrid - to be changed - MethodData::CompositePublicKey(composite) => { + MethodData::CompositeJwk(composite) => { let algid = composite .alg_id() .to_json_vec() diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index c8f81c91da..cfd6b87916 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -1,16 +1,16 @@ -use identity_did::DIDJwk; +use identity_did::{DIDCompositeJwk, DIDJwk}; use identity_document::document::{self, CoreDocument}; -use identity_verification::{jws::JwsAlgorithm, jwu::encode_b64_json, verification_method}; +use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json, verification_method}; use jsonprooftoken::jpa::algs::ProofAlgorithm; use async_trait::async_trait; -use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentError as Error, JwkStoragePQ, KeyIdStorage, KeyType, MethodDigest}; +use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentError as Error, JwkStoragePQ, KeyId, KeyIdStorage, KeyType, MethodDigest}; use super::{try_undo_key_generation, Storage, StorageResult}; -/// a +/// Handle both DID Jwk and DID compositeJwk methods #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait DidJwkDocumentExt{ @@ -43,6 +43,16 @@ pub trait DidJwkDocumentExt{ where K: JwkStorageBbsPlusExt, I: KeyIdStorage; + +/// a + #[cfg(feature = "hybrid")] + async fn new_did_compositejwk( + storage: &Storage, + alg: identity_verification::jwk::CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage; } @@ -174,4 +184,69 @@ impl DidJwkDocumentExt for CoreDocument { Ok((document, fragment.to_string())) } + + async fn new_did_compositejwk( + storage: &Storage, + alg: CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage { + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { + CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_44, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_65, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + }; + + let JwkGenOutput { + key_id: t_key_id, + jwk: t_jwk, + } = K::generate(storage.key_storage(), trad_key_type, trad_alg) + .await + .map_err(Error::KeyStorageError)?; + + let JwkGenOutput { + key_id: pq_key_id, + jwk: pq_jwk, + } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; + + let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + + let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); + + let b64 = encode_b64_json(&composite_pk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_compositejwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } } \ No newline at end of file diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 8c81465f9f..d625e4e571 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -30,8 +30,8 @@ use identity_verification::jws::CompactJwsEncoder; use identity_verification::jws::CompactJwsEncodingOptions; use identity_verification::jws::JwsAlgorithm; use identity_verification::jws::JwsHeader; -use identity_verification::CompositeAlgId; -use identity_verification::CompositePublicKey; +use identity_verification::jwk::CompositeAlgId; +use identity_verification::jwk::CompositeJwk; use identity_verification::MethodBuilder; use identity_verification::MethodData; use identity_verification::MethodScope; @@ -82,54 +82,12 @@ macro_rules! generate_method_hybrid_for_document_type { .await .map_err(Error::KeyStorageError)?; - // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) - let composite_fragment = t_jwk - .kid() - .map(|s| s.to_string()) - .or_else(|| pq_jwk.kid().map(|s| s.to_string())) - .map(|s| { - if let (Some(str1), Some(str2)) = (t_jwk.kid(), pq_jwk.kid()) { - format!("{}~{}", str1, str2) - } else { - s - } - }); - let composite_kid = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); - let fragment: Cow<'_, str> = { - let given_fragment: &str = fragment - .or_else(|| composite_fragment.as_deref()) - .ok_or(identity_verification::Error::InvalidMethod( - "an explicit fragment or JWK kid is required", - )) - .map_err(Error::VerificationMethodConstructionError)?; - // Make sure the fragment starts with "#" - if given_fragment.starts_with('#') { - Cow::Borrowed(given_fragment) - } else { - Cow::Owned(format!("#{given_fragment}")) - } - }; - - let id: DIDUrl = document - .id() - .to_url() - .join(fragment) - .map_err(identity_verification::Error::DIDUrlConstructionError) - .map_err(Error::VerificationMethodConstructionError)?; - - let composite_pk = CompositePublicKey::new(alg_id, t_jwk, pq_jwk); + let composite_pk = CompositeJwk::new(alg_id, t_jwk, pq_jwk); - // Produce a new verification method containing the generated JWK. If this operation fails we handle the error - // by attempting to revert key generation before returning an error. let method: VerificationMethod = { - match MethodBuilder::default() - .id(id) - .type_(MethodType::custom("CompositeSignaturePublicKey")) - .controller(document.id().clone().into()) - .data(identity_verification::MethodData::CompositePublicKey(composite_pk)) - .build() + match VerificationMethod::new_from_compositejwk(document.id().clone(), composite_pk, fragment) .map_err(Error::VerificationMethodConstructionError) { Ok(method) => method, @@ -270,7 +228,7 @@ impl JwkDocumentExtHybrid for CoreDocument { { // Obtain the method corresponding to the given fragment. let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; - let MethodData::CompositePublicKey(ref composite) = method.data() else { + let MethodData::CompositeJwk(ref composite) = method.data() else { return Err(Error::NotCompositePublicKey); }; diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index e7e5099538..982cb3754b 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::jose::jwk::Jwk; -use crate::CompositePublicKey; +use identity_jose::jwk::CompositeJwk; use core::fmt::Debug; use core::fmt::Formatter; use identity_core::convert::BaseEncoding; @@ -28,9 +28,8 @@ pub enum MethodData { PublicKeyBase58(String), /// Verification Material in the JSON Web Key format. PublicKeyJwk(Jwk), - /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new - /// MethodData - CompositePublicKey(CompositePublicKey), + /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new MethodData + CompositeJwk(CompositeJwk), /// Arbitrary verification material. #[serde(untagged)] Custom(CustomMethodData), @@ -63,7 +62,7 @@ impl MethodData { /// represented as a vector of bytes. pub fn try_decode(&self) -> Result> { match self { - Self::PublicKeyJwk(_) | Self::Custom(_) | Self::CompositePublicKey(_) => Err( + Self::PublicKeyJwk(_) | Self::Custom(_) | Self::CompositeJwk(_) => Err( Error::InvalidMethodDataTransformation("method data is not base encoded"), ), Self::PublicKeyMultibase(input) => { @@ -75,8 +74,8 @@ impl MethodData { //TODO: hybrid - return CompositePublicKey /// Returns the wrapped `CompositePublicKey` if the format is [`MethodData::CompositePublicKey`]. - pub fn composite_public_key(&self) -> Option<&CompositePublicKey> { - if let Self::CompositePublicKey(ref c) = self { + pub fn composite_public_key(&self) -> Option<&CompositeJwk> { + if let Self::CompositeJwk(ref c) = self { Some(c) } else { None @@ -84,7 +83,7 @@ impl MethodData { } /// Fallible version of [`Self::composite_public_key`](Self::composite_public_key()). - pub fn try_composite_public_key(&self) -> Result<&CompositePublicKey> { + pub fn try_composite_public_key(&self) -> Result<&CompositeJwk> { self.composite_public_key().ok_or(Error::NotCompositePublicKey) } @@ -118,7 +117,7 @@ impl Debug for MethodData { Self::PublicKeyJwk(inner) => f.write_fmt(format_args!("PublicKeyJwk({inner:#?})")), Self::PublicKeyMultibase(inner) => f.write_fmt(format_args!("PublicKeyMultibase({inner})")), Self::PublicKeyBase58(inner) => f.write_fmt(format_args!("PublicKeyBase58({inner})")), - Self::CompositePublicKey(inner) => f.write_fmt(format_args!("CompositePublicKey({inner:#?})")), + Self::CompositeJwk(inner) => f.write_fmt(format_args!("CompositePublicKey({inner:#?})")), Self::Custom(CustomMethodData { name, data }) => f.write_fmt(format_args!("{name}({data})")), } } diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index c39b3ca56d..2c43d10558 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -5,7 +5,9 @@ use core::fmt::Display; use core::fmt::Formatter; use std::borrow::Cow; +use identity_did::DIDCompositeJwk; use identity_did::DIDJwk; +use identity_jose::jwk::CompositeJwk; use identity_jose::jwk::Jwk; use serde::de; use serde::Deserialize; @@ -227,6 +229,48 @@ impl VerificationMethod { } } + +impl VerificationMethod { + pub fn new_from_compositejwk(did: D, key: CompositeJwk, fragment: Option<&str>) -> Result { + // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) + let composite_fragment = key.traditional_public_key() + .kid() + .map(|s| s.to_string()) + .or_else(|| key.pq_public_key().kid().map(|s| s.to_string())) + .map(|s| { + if let (Some(str1), Some(str2)) = (key.traditional_public_key().kid(), key.pq_public_key().kid()) { + format!("{}~{}", str1, str2) + } else { + s + } + }); + + + let fragment: Cow<'_, str> = { + let given_fragment: &str = fragment + .or_else(|| composite_fragment.as_deref()) + .ok_or(Error::InvalidMethod( + "an explicit fragment or kid is required", + ))?; + // Make sure the fragment starts with "#" + if given_fragment.starts_with('#') { + Cow::Borrowed(given_fragment) + } else { + Cow::Owned(format!("#{given_fragment}")) + } + }; + + let id: DIDUrl = did.to_url().join(fragment).map_err(Error::DIDUrlConstructionError)?; + + MethodBuilder::default() + .id(id) + .type_(MethodType::custom("CompositeSignaturePublicKey")) + .controller(did.into()) + .data(MethodData::CompositeJwk(key)) + .build() + } +} + impl Display for VerificationMethod { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { self.fmt_json(f) @@ -256,6 +300,14 @@ impl TryFrom for VerificationMethod { } } +impl TryFrom for VerificationMethod { + type Error = Error; + fn try_from(did: DIDCompositeJwk) -> Result { + let jwk = did.composite_jwk(); + Self::new_from_compositejwk(did, jwk, Some("0")) + } +} + // Horrible workaround for a tracked serde issue https://github.com/serde-rs/serde/issues/2200. Serde doesn't "consume" // the input when deserializing flattened enums (MethodData in this case) causing duplication of data (in this case // it ends up in the properties object). This workaround simply removes the duplication. @@ -285,7 +337,7 @@ impl From<_VerificationMethod> for VerificationMethod { MethodData::PublicKeyBase58(_) => "publicKeyBase58", MethodData::PublicKeyJwk(_) => "publicKeyJwk", MethodData::PublicKeyMultibase(_) => "publicKeyMultibase", - MethodData::CompositePublicKey(_) => "compositePublicKey", + MethodData::CompositeJwk(_) => "compositePublicKey", MethodData::Custom(CustomMethodData { name, .. }) => name.as_str(), }; properties.remove(key); diff --git a/identity_verification/src/verification_method/mod.rs b/identity_verification/src/verification_method/mod.rs index cea4234ecf..585b58639c 100644 --- a/identity_verification/src/verification_method/mod.rs +++ b/identity_verification/src/verification_method/mod.rs @@ -7,7 +7,6 @@ //! `identity_iota_core_legacy` crate. mod builder; -mod composite_public_key; mod material; mod method; mod method_ref; @@ -23,4 +22,3 @@ pub use self::method_ref::MethodRef; pub use self::method_relationship::MethodRelationship; pub use self::method_scope::MethodScope; pub use self::method_type::MethodType; -pub use composite_public_key::*; From 2251d1b62efaa360ae7723af4069473e4a86a580 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Wed, 16 Oct 2024 12:14:23 +0200 Subject: [PATCH 054/163] new demo traditional_zk --- examples/Cargo.toml | 4 + examples/demo/hybrid.rs | 6 +- examples/demo/pqc.rs | 5 +- examples/demo/traditional.rs | 6 +- examples/demo/traditional_zk.rs | 161 ++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 11 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 013ed76c8c..dbbee69c8f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -136,3 +136,7 @@ name = "pqc" path = "demo/hybrid.rs" name = "hybrid" +[[example]] +path = "demo/traditional_zk.rs" +name = "traditional_zk" + diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index 329096d2f6..b0d5ef3562 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -137,6 +137,8 @@ async fn main() -> anyhow::Result<()> { // - JWT verification of the credentials. // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property // - The issuance date must not be in the future. + + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); @@ -187,9 +189,5 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - Ok(()) } diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs index a367500052..6782215b05 100644 --- a/examples/demo/pqc.rs +++ b/examples/demo/pqc.rs @@ -140,6 +140,8 @@ async fn main() -> anyhow::Result<()> { // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property // - The issuance date must not be in the future. + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); @@ -188,9 +190,6 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - Ok(()) } diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 1af7ab84d0..4fdee7225a 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -141,6 +141,8 @@ async fn main() -> anyhow::Result<()> { // - JWT verification of the credentials. // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property // - The issuance date must not be in the future. + + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); @@ -190,9 +192,5 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - Ok(()) } diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index e69de29bb2..eeb51636d1 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -0,0 +1,161 @@ +use std::{collections::HashMap, fs::File, path::Path}; +use env_logger::Env; +use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, SelectiveDisclosurePresentation, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwpDocumentExt, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; +use identity_iota::storage::JwkDocumentExt; +use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use reqwest::ClientBuilder; +use serde_json::json; +use colored::Colorize; + + + +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} + + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + //env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + let did_url: &str = "https://localhost:4443/.well-known/did_zk.json"; + let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_zk.json"; + + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method_jwp( + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + None, + MethodScope::VerificationMethod, + ) + .await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_zk( + &storage_alice, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256 + ).await?; + + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); + + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); + + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + println!("{}: Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + + let method_id = decoded_jpt + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and construct the Presentation JPT"); + + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); + + println!("{}: Resolve Issuer's DID and verifies the Presentation JPT","[Verifier]".green()); + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + Ok(()) +} From 11ad65b1f9e0733048001ff86d87e1d829dc2c2c Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 17 Oct 2024 12:18:39 +0200 Subject: [PATCH 055/163] code cleaning --- examples/Cargo.toml | 2 -- examples/demo/hybrid.rs | 7 ++----- examples/demo/pqc.rs | 7 ++----- examples/demo/traditional.rs | 8 +++----- examples/demo/traditional_zk.rs | 8 +++----- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index dbbee69c8f..67253e232f 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,8 +21,6 @@ serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } serde.workspace = true -log = "0.4.0" -env_logger = "0.10.0" colored = "2.1.0" [lib] diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index b0d5ef3562..d8219d95a4 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, fs::File, path::Path}; -use env_logger::Env; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorHybrid, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorHybrid, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDCompositeJwk, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExtHybrid, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jwk::CompositeAlgId, jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; @@ -21,7 +20,6 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did_hybrid.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_hybrid.json"; @@ -140,9 +138,6 @@ async fn main() -> anyhow::Result<()> { println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - let mut resolver: Resolver = Resolver::new(); resolver.attach_did_compositejwk_handler(); @@ -151,6 +146,8 @@ async fn main() -> anyhow::Result<()> { let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs index 6782215b05..d9e6d058d5 100644 --- a/examples/demo/pqc.rs +++ b/examples/demo/pqc.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, fs::File, path::Path}; -use env_logger::Env; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; @@ -21,7 +20,6 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did_pqc.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_pqc.json"; @@ -142,9 +140,6 @@ async fn main() -> anyhow::Result<()> { println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); @@ -153,6 +148,8 @@ async fn main() -> anyhow::Result<()> { let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 4fdee7225a..2d83c6c931 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, fs::File, path::Path}; -use env_logger::Env; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; @@ -22,7 +21,6 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - //env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; @@ -144,9 +142,6 @@ async fn main() -> anyhow::Result<()> { println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); @@ -155,10 +150,13 @@ async fn main() -> anyhow::Result<()> { let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( EdDSAJwsVerifier::default(), ) diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index eeb51636d1..5876d08b1d 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -1,5 +1,4 @@ use std::{collections::HashMap, fs::File, path::Path}; -use env_logger::Env; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, SelectiveDisclosurePresentation, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwpDocumentExt, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; @@ -23,7 +22,6 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( #[tokio::main] async fn main() -> anyhow::Result<()> { - //env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let did_url: &str = "https://localhost:4443/.well-known/did_zk.json"; let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_zk.json"; @@ -51,10 +49,10 @@ async fn main() -> anyhow::Result<()> { let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_zk( + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( &storage_alice, - JwkMemStore::BLS12381G2_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256 + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA ).await?; println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); From 6be4f64de68ab2ca9fdac0d2e073af5977e339d8 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:26:12 +0200 Subject: [PATCH 056/163] remove unused imports --- identity_did/src/did_compositejwk.rs | 1 - identity_jose/src/jwk/jwk_pq.rs | 2 -- identity_jose/src/jwk/key_type.rs | 1 + identity_jose/src/jws/decoder.rs | 3 +-- identity_storage/src/key_storage/memstore.rs | 6 +----- identity_storage/src/storage/did_jwk_document_ext.rs | 6 +++--- identity_storage/src/storage/hybrid_jws_document_ext.rs | 4 ---- 7 files changed, 6 insertions(+), 17 deletions(-) diff --git a/identity_did/src/did_compositejwk.rs b/identity_did/src/did_compositejwk.rs index 665fca262a..ac09ddaeef 100644 --- a/identity_did/src/did_compositejwk.rs +++ b/identity_did/src/did_compositejwk.rs @@ -6,7 +6,6 @@ use std::fmt::Display; use std::str::FromStr; use identity_jose::jwk::CompositeJwk; -use identity_jose::jwk::Jwk; use identity_jose::jwu::decode_b64_json; use crate::CoreDID; diff --git a/identity_jose/src/jwk/jwk_pq.rs b/identity_jose/src/jwk/jwk_pq.rs index 06a9301b13..ee29f4cf2b 100644 --- a/identity_jose/src/jwk/jwk_pq.rs +++ b/identity_jose/src/jwk/jwk_pq.rs @@ -4,8 +4,6 @@ use zeroize::Zeroize; -use super::JwkParams; -use super::JwkType; // TODO: PQ - parameter for PQ keys diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index 9dd60122a6..9af2d9cb15 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -33,6 +33,7 @@ pub enum JwkType { #[serde(rename = "SLH-DSA")] SLHDSA, + ///Falcon FALCON, } diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 80f46ca354..e67f11ebe5 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -17,8 +17,7 @@ use crate::jwu::filter_non_empty_bytes; use crate::jwu::parse_utf8; use crate::jwu::validate_jws_headers; use crypto::hashes::Digest; -use crypto::signatures::ed25519::Signature; -use identity_core::common::SingleStructError; + use super::JwsVerifier; use super::VerificationInput; diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 32c3fa1487..3016b3cfae 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -13,10 +13,6 @@ use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; use identity_verification::jwk::BlsCurve; -use identity_verification::jwk::JwkParams; -use identity_verification::jwu; -use oqs::sig::Algorithm; -use oqs::sig::Sig; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -32,7 +28,7 @@ use super::KeyStorageResult; use super::KeyType; use crate::key_storage::JwkStorage; -use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; + /// The map from key ids to JWKs. type JwkKeyStore = HashMap; diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index cfd6b87916..75efb33419 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -1,12 +1,12 @@ use identity_did::{DIDCompositeJwk, DIDJwk}; -use identity_document::document::{self, CoreDocument}; -use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json, verification_method}; +use identity_document::document::CoreDocument; +use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json}; use jsonprooftoken::jpa::algs::ProofAlgorithm; use async_trait::async_trait; use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentError as Error, JwkStoragePQ, KeyId, KeyIdStorage, KeyType, MethodDigest}; -use super::{try_undo_key_generation, Storage, StorageResult}; +use super::{Storage, StorageResult}; diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index d625e4e571..654c1ea797 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::ops::Deref; use super::JwkStorageDocumentError as Error; @@ -23,7 +22,6 @@ use identity_credential::credential::Jwt; use identity_credential::presentation::JwtPresentationOptions; use identity_credential::presentation::Presentation; use identity_did::DIDUrl; -use identity_did::DID; use identity_document::document::CoreDocument; use identity_verification::jws::CharSet; use identity_verification::jws::CompactJwsEncoder; @@ -32,10 +30,8 @@ use identity_verification::jws::JwsAlgorithm; use identity_verification::jws::JwsHeader; use identity_verification::jwk::CompositeAlgId; use identity_verification::jwk::CompositeJwk; -use identity_verification::MethodBuilder; use identity_verification::MethodData; use identity_verification::MethodScope; -use identity_verification::MethodType; use identity_verification::VerificationMethod; use serde::de::DeserializeOwned; use serde::Serialize; From c9f16eacab902010b48f4bef4f06aeeda779c21b Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:50:33 +0200 Subject: [PATCH 057/163] cleanup examples --- examples/demo/hybrid.rs | 310 +++++++++++++++---------------- examples/demo/pqc.rs | 317 +++++++++++++++----------------- examples/demo/traditional.rs | 310 +++++++++++++++---------------- examples/demo/traditional_zk.rs | 255 ++++++++++++------------- examples/utils/utils.rs | 3 + 5 files changed, 578 insertions(+), 617 deletions(-) diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index d8219d95a4..9665bcc3f2 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -1,10 +1,8 @@ use std::{collections::HashMap, fs::File, path::Path}; -use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorHybrid, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorHybrid, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDCompositeJwk, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExtHybrid, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jwk::CompositeAlgId, jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; -use identity_iota::storage::JwkDocumentExt; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidatorHybrid, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidatorHybrid, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDCompositeJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExtHybrid, JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jwk::CompositeAlgId, MethodScope}}; use identity_pqc_verifier::PQCJwsVerifier; -use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::ClientBuilder; use serde_json::json; use colored::Colorize; @@ -16,175 +14,169 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( Ok(()) } - - #[tokio::main] async fn main() -> anyhow::Result<()> { - let did_url: &str = "https://localhost:4443/.well-known/did_hybrid.json"; - let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_hybrid.json"; + let binding = DID_URL.to_owned() + "did_hybrid.json"; + let did_url: &str = binding.as_str(); + let binding = PATH_DID_FILE.to_owned() + "did_hybrid.json"; + let path_did_file: &str = binding.as_str(); - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method_hybrid( - &storage_issuer, - CompositeAlgId::IdMldsa44Ed25519Sha512, - None, - MethodScope::VerificationMethod, - ) - .await?; - - write_to_file(&issuer_document, Some(path_did_file))?; + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; - let (alice_document, fragment_alice) = CoreDocument::new_did_compositejwk( - &storage_alice, - CompositeAlgId::IdMldsa44Ed25519Sha512 - ).await?; + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - println!("{} {} {}", "[Holder]".blue(), ": Create DID compositeJwk:", alice_document.id().as_str()); + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method_hybrid( + &storage_issuer, + CompositeAlgId::IdMldsa44Ed25519Sha512, + None, + MethodScope::VerificationMethod, + ).await?; - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; + write_to_file(&issuer_document, Some(path_did_file))?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - println!("{} {} ","[Issuer]".red(), ": Construct VC"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt_hybrid( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Construct VP"); - - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt_hybrid( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. + let (alice_document, fragment_alice) = CoreDocument::new_did_compositejwk( + &storage_alice, + CompositeAlgId::IdMldsa44Ed25519Sha512 + ).await?; - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_compositejwk_handler(); - - - // Resolve the holder's document. - let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; + println!("{} {} {}", "[Holder]".blue(), ": Create DID compositeJwk:", alice_document.id().as_str()); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( - EdDSAJwsVerifier::default(), - PQCJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidatorHybrid = - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_hybrid( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ).await?; + + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Construct VP"); - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } + let presentation: Presentation =PresentationBuilder::new( + alice_document.id().to_url().into(), + Default::default() + ).credential(credential_jwt).build()?; + + let presentation_jwt: Jwt = alice_document.create_presentation_jwt_hybrid( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ).await?; + + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_compositejwk_handler(); - Ok(()) + // Resolve the holder's document. + let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( + EdDSAJwsVerifier::default(), + PQCJwsVerifier::default(), + ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidatorHybrid = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + + Ok(()) } diff --git a/examples/demo/pqc.rs b/examples/demo/pqc.rs index d9e6d058d5..1b8382f25a 100644 --- a/examples/demo/pqc.rs +++ b/examples/demo/pqc.rs @@ -1,10 +1,7 @@ use std::{collections::HashMap, fs::File, path::Path}; -use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; -use identity_iota::storage::JwkDocumentExt; +use examples::{MemStorage, DID_URL, PATH_DID_FILE}; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; use identity_pqc_verifier::PQCJwsVerifier; -use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::ClientBuilder; use serde_json::json; use colored::Colorize; @@ -16,177 +13,169 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( Ok(()) } - - #[tokio::main] async fn main() -> anyhow::Result<()> { + let binding = DID_URL.to_owned() + "did_pqc.json"; + let did_url: &str = binding.as_str(); + let binding = PATH_DID_FILE.to_owned() + "did_pqc.json"; + let path_did_file: &str = binding.as_str(); + + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - let did_url: &str = "https://localhost:4443/.well-known/did_pqc.json"; - let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_pqc.json"; + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method_pqc( - &storage_issuer, - JwkMemStore::ML_DSA_KEY_TYPE, - JwsAlgorithm::ML_DSA_44, - None, - MethodScope::VerificationMethod, - ) - .await?; - - write_to_file(&issuer_document, Some(path_did_file))?; + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document + .generate_method_pqc( + &storage_issuer, + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_44, + None, + MethodScope::VerificationMethod, + ).await?; - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_pqc( - &storage_alice, - JwkMemStore::ML_DSA_KEY_TYPE, - JwsAlgorithm::ML_DSA_87 - ).await?; + write_to_file(&issuer_document, Some(path_did_file))?; - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_pqc( + &storage_alice, + JwkMemStore::ML_DSA_KEY_TYPE, + JwsAlgorithm::ML_DSA_87 + ).await?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - println!("{} {} ","[Issuer]".red(), ": Construct VC"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt_pqc( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Construct VP"); - - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt_pqc( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. - - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_jwk_handler(); - - - // Resolve the holder's document. - let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - PQCJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document.create_credential_jwt_pqc( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ).await?; + + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Construct VP"); + + let presentation: Presentation =PresentationBuilder::new( + alice_document.id().to_url().into(), + Default::default() + ).credential(credential_jwt).build()?; + + let presentation_jwt: Jwt = alice_document.create_presentation_jwt_pqc( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ).await?; + + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - - Ok(()) + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_jwk_handler(); + + // Resolve the holder's document. + let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + PQCJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + + Ok(()) } diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 2d83c6c931..2f4171ac3c 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -1,15 +1,12 @@ use std::{collections::HashMap, fs::File, path::Path}; -use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; +use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; use identity_iota::storage::JwkDocumentExt; -use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; use reqwest::ClientBuilder; use serde_json::json; use colored::Colorize; - - pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { let path = Path::new(path.unwrap_or_else(|| "did.json")); let file = File::create(path)?; @@ -17,178 +14,171 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( Ok(()) } - - #[tokio::main] async fn main() -> anyhow::Result<()> { + let binding = DID_URL.to_owned() + "did.json"; + let did_url: &str = binding.as_str(); + let binding = PATH_DID_FILE.to_owned() + "did.json"; + let path_did_file: &str = binding.as_str(); + + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document.generate_method( + &storage_issuer, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod, + ).await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( + &storage_alice, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA + ).await?; + + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; - let did_url: &str = "https://localhost:4443/.well-known/did.json"; - let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method( - &storage_issuer, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let credential_jwt: Jwt = issuer_document.create_credential_jwt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ).await?; - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( - &storage_alice, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA - ).await?; + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; + println!("{} {}", "[Holder]".blue(), ": Validate VC"); - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); - println!("{} {} ","[Issuer]".red(), ": Construct VC"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Construct VP"); - - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_jwk_handler(); - - - // Resolve the holder's document. - let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); + println!("{} {}", "[Holder]".blue(), ": Construct VP"); - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - EdDSAJwsVerifier::default(), + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + .await?; + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_did_jwk_handler(); - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - Ok(()) + // Resolve the holder's document. + let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; + let holder: CoreDocument = resolver.resolve(&holder_did).await?; + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + + Ok(()) } diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index 5876d08b1d..29225efaa7 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -1,16 +1,11 @@ -use std::{collections::HashMap, fs::File, path::Path}; -use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, SelectiveDisclosurePresentation, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::{IotaDocument, NetworkName}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkGenOutput, JwkMemStore, JwkStorage, JwpDocumentExt, JwsSignatureOptions, KeyIdMemstore, KeyIdStorage, KeyStorageResult, MethodDigest}, verification::{jws::JwsAlgorithm, jwu::encode_b64_json, MethodScope}}; -use identity_iota::storage::JwkDocumentExt; -use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; +use std::{fs::File, path::Path}; +use examples::{MemStorage, DID_URL, PATH_DID_FILE}; +use identity_iota::{core::{FromJson, Object, Url}, credential::{Credential, CredentialBuilder, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, SelectiveDisclosurePresentation, Subject}, did::{CoreDID, DID}, document::CoreDocument, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwpDocumentExt, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; use jsonprooftoken::jpa::algs::ProofAlgorithm; use reqwest::ClientBuilder; use serde_json::json; use colored::Colorize; - - pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { let path = Path::new(path.unwrap_or_else(|| "did.json")); let file = File::create(path)?; @@ -18,142 +13,134 @@ pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<( Ok(()) } - - #[tokio::main] async fn main() -> anyhow::Result<()> { + let binding = DID_URL.to_owned() + "did_zk.json"; + let did_url: &str = binding.as_str(); + let binding = PATH_DID_FILE.to_owned() + "did_zk.json"; + let path_did_file: &str = binding.as_str(); + + println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document.generate_method_jwp( + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + None, + MethodScope::VerificationMethod, + ).await?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let did_url: &str = "https://localhost:4443/.well-known/did_zk.json"; - let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did_zk.json"; + let (alice_document, _fragment_alice) = CoreDocument::new_did_jwk( + &storage_alice, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA + ).await?; - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Construct VC"); - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method_jwp( - &storage_issuer, - JwkMemStore::BLS12381G2_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, - None, - MethodScope::VerificationMethod, - ) - .await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( - &storage_alice, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - println!("{} {} ","[Issuer]".red(), ": Construct VC"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jpt: Jpt = issuer_document - .create_credential_jpt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwpCredentialOptions::default(), - None, - ) - .await?; - - - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - let decoded_jpt = JptCredentialValidator::validate::<_, Object>( - &credential_jpt, - &issuer_document, - &JptCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - println!("{}: Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + let credential_jpt: Jpt = issuer_document.create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ).await?; - let method_id = decoded_jpt - .decoded_jwp - .get_issuer_protected_header() - .kid() - .unwrap(); + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); - let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); - selective_disclosure_presentation - .conceal_in_subject("degree.name") - .unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and construct the Presentation JPT"); - - let presentation_jpt: Jpt = issuer_document - .create_presentation_jpt( - &mut selective_disclosure_presentation, - method_id, - &JwpPresentationOptions::default().nonce(challenge), - ) - .await?; - - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); - - println!("{}: Resolve Issuer's DID and verifies the Presentation JPT","[Verifier]".green()); - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); - let issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + println!("{}: Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + + let method_id = decoded_jpt + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and construct the Presentation JPT"); - let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); - - // Verifier validate the Presented Credential and retrieve the JwpPresented - let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( - &presentation_jpt, - &issuer_document, - &presentation_validation_options, - FailFast::FirstError, + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), ) - .unwrap(); + .await?; + + println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); + + println!("{}: Resolve Issuer's DID and verifies the Presentation JPT","[Verifier]".green()); - Ok(()) + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let _decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ).unwrap(); + + println!("{}: Presentation JPT successfully verified", "[Verifier]".green()); + + Ok(()) } diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index a79a74312e..e20dd9abe3 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -31,6 +31,9 @@ use serde_json::Value; pub static API_ENDPOINT: &str = "http://localhost"; pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; +pub static DID_URL: &str = "https://localhost:4443/.well-known/"; +pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; + pub type MemStorage = Storage; /// Creates a DID Document and publishes it in a new Alias Output. From 02dcd7de4a95a34882e35bce0bd169eb06e81031 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 10:34:42 +0200 Subject: [PATCH 058/163] raname demos --- examples/Cargo.toml | 4 ++-- examples/demo/{pqc.rs => pq.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/demo/{pqc.rs => pq.rs} (100%) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 67253e232f..1a6da1ae50 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -127,8 +127,8 @@ path = "demo/traditional.rs" name = "traditional" [[example]] -path = "demo/pqc.rs" -name = "pqc" +path = "demo/pq.rs" +name = "pq" [[example]] path = "demo/hybrid.rs" diff --git a/examples/demo/pqc.rs b/examples/demo/pq.rs similarity index 100% rename from examples/demo/pqc.rs rename to examples/demo/pq.rs From da974f6462377ce76f7e005814d5a25e3355171f Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 10:38:58 +0200 Subject: [PATCH 059/163] Update README.md --- README.md | 251 +----------------------------------------------------- 1 file changed, 3 insertions(+), 248 deletions(-) diff --git a/README.md b/README.md index e8001e6788..2727840e5d 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,3 @@ -![banner](https://github.com/iotaledger/identity.rs/raw/HEAD/.github/banner_identity.svg) - -

- StackExchange - Discord - Discord - Apache 2.0 license - Dependencies - Coverage Status - -

- -

- Introduction â—ˆ - Bindings â—ˆ - Documentation & Resources â—ˆ - Getting Started â—ˆ - Example â—ˆ - Roadmap â—ˆ - Contributing -

- ---- - -## Introduction - -IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. - -## Bindings - -[Foreign Function Interface (FFI)](https://en.wikipedia.org/wiki/Foreign_function_interface) Bindings of this [Rust](https://www.rust-lang.org/) library to other programming languages: - -- [Web Assembly](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/wasm/) (JavaScript/TypeScript) - -## gRPC - -We provide a collection of experimental [gRPC services](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/grpc/) -## Documentation and Resources - -- API References: - - [Rust API Reference](https://docs.rs/identity_iota/latest/identity_iota/): Package documentation (cargo docs). - - [Wasm API Reference](https://wiki.iota.org/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. -- [Identity Documentation Pages](https://wiki.iota.org/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. -- [Examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples): Practical code snippets to get you started with the library. - -## Prerequisites - -- [Rust](https://www.rust-lang.org/) (>= 1.65) -- [Cargo](https://doc.rust-lang.org/cargo/) (>= 1.65) - -## Getting Started - -If you want to include IOTA Identity in your project, simply add it as a dependency in your `Cargo.toml`: - -```toml -[dependencies] -identity_iota = { version = "1.3.1" } -``` - -To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: - -1. Clone the repository, e.g. through `git clone https://github.com/iotaledger/identity.rs` -2. Start IOTA Sandbox as described in the [next section](#example-creating-an-identity) -3. Run the example to create a DID using `cargo run --release --example 0_create_did` - -## Example: Creating an Identity - -The following code creates and publishes a new IOTA DID Document to a locally running private network. -See the [instructions](https://github.com/iotaledger/iota-sandbox) on running your own private network for development. - -_Cargo.toml_ - - - - -```toml -[package] -name = "iota_identity_example" -version = "1.0.0" -edition = "2021" - -[dependencies] -identity_iota = { version = "1.3.1", features = ["memstore"] } -iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } -tokio = { version = "1", features = ["full"] } -anyhow = "1.0.62" -rand = "0.8.5" -``` - -_main.__rs_ - - - - - -```rust,no_run -use identity_iota::core::ToJson; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::storage::JwkDocumentExt; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::Storage; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; -use iota_sdk::client::api::GetAddressesOptions; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::crypto::keys::bip39; -use iota_sdk::types::block::address::Bech32Address; -use iota_sdk::types::block::output::AliasOutput; -use iota_sdk::types::block::output::dto::AliasOutputDto; -use tokio::io::AsyncReadExt; - -// The endpoint of the IOTA node to use. -static API_ENDPOINT: &str = "http://localhost"; - -/// Demonstrates how to create a DID Document and publish it in a new Alias Output. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create a new Stronghold. - let stronghold = StrongholdSecretManager::builder() - .password("secure_password".to_owned()) - .build("./example-strong.hodl")?; - - // Generate a mnemonic and store it in the Stronghold. - let random: [u8; 32] = rand::random(); - let mnemonic = - bip39::wordlist::encode(random.as_ref(), &bip39::wordlist::ENGLISH).map_err(|err| anyhow::anyhow!("{err:?}"))?; - stronghold.store_mnemonic(mnemonic).await?; - - // Create a new secret manager backed by the Stronghold. - let secret_manager: SecretManager = SecretManager::Stronghold(stronghold); - - // Get the Bech32 human-readable part (HRP) of the network. - let network_name: NetworkName = client.network_name().await?; - - // Get an address from the secret manager. - let address: Bech32Address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_range(0..1) - .with_bech32_hrp((&network_name).try_into()?), - ) - .await?[0]; - - println!("Your wallet address is: {}", address); - println!("Please request funds from http://localhost/faucet/, wait for a couple of seconds and then press Enter."); - tokio::io::stdin().read_u8().await?; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // Insert a new Ed25519 verification method in the DID document. - let storage: Storage = Storage::new(JwkMemStore::new(), KeyIdMemstore::new()); - document - .generate_method( - &storage, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address.into(), document, None).await?; - println!("Alias Output: {}", AliasOutputDto::from(&alias_output).to_json_pretty()?); - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; - println!("Published DID document: {:#}", document); - - Ok(()) -} -``` - -_Example output_ - -```json -{ - "doc": { - "id": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d", - "verificationMethod": [ - { - "id": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d#9KdQCWcvR8kmGPLFOYnTzypsDWsoUIvR", - "controller": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d", - "type": "JsonWebKey", - "publicKeyJwk": { - "kty": "OKP", - "alg": "EdDSA", - "kid": "9KdQCWcvR8kmGPLFOYnTzypsDWsoUIvR", - "crv": "Ed25519", - "x": "JJoYoeFWU7jWvdQmOKDvM4nZJ2cUbP9yhWZzFgd044I" - } - } - ] - }, - "meta": { - "created": "2023-08-29T14:47:26Z", - "updated": "2023-08-29T14:47:26Z", - "governorAddress": "tst1qqd7kyu8xadzx9vutznu72336npqpj92jtp27uyu2tj2sa5hx6n3k0vrzwv", - "stateControllerAddress": "tst1qqd7kyu8xadzx9vutznu72336npqpj92jtp27uyu2tj2sa5hx6n3k0vrzwv" - } -} -``` - -## Roadmap and Milestones - -For detailed development progress, see the IOTA Identity development [kanban board](https://github.com/orgs/iotaledger/projects/8/views/5). - -## Contributing - -We would love to have you help us with the development of IOTA Identity. Each and every contribution is greatly valued! - -Please review the [contribution](https://wiki.iota.org/identity.rs/contribute) and [workflow](https://wiki.iota.org/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). - -To contribute directly to the repository, simply fork the project, push your changes to your fork and create a pull request to get them included! - -The best place to get involved in discussions about this library or to look for support at is the `#identity` channel on the [IOTA Discord](https://discord.iota.org). You can also ask questions on our [Stack Exchange](https://iota.stackexchange.com/). +# ZK +# PQ/T Hybrid +# Examples From 79b316e8a5823006565ce2f73a7271e125f73dd7 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 11:47:40 +0200 Subject: [PATCH 060/163] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2727840e5d..59836c8dcb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ -# ZK +# Zero-Knowledge (ZK) + +The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: + +* **BBS+ Signature Scheme**: This scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library, allowing for secure and privacy-preserving credential management. +* **JSON Web Proof Representation**: The [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. + +For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). + # PQ/T Hybrid # Examples From 0ec3de9500a4d41f471e4b03d57e4cf8a45261f7 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 17:09:34 +0200 Subject: [PATCH 061/163] Update README.md --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59836c8dcb..5b327438f4 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,14 @@ The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). # PQ/T Hybrid -# Examples + +### Example + +To test the above functionalities, you can refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. + +> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/). To run these examples, you must have a server instance that hosts the DID Document. Additionally, ensure that the following variables in `utils.rs` are correctly configured to point to your server instance: +> ```rust +> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; +> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; +> ``` +Make sure your server is set up before running the examples to avoid any configuration issues. From 06566bbc3e48f6972ed9dd97d5d4eca400e794f6 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 19:03:09 +0200 Subject: [PATCH 062/163] implement server for did web --- Cargo.toml | 2 +- examples/1_advanced/{12_pqc.rs => 12_pq.rs} | 0 examples/Cargo.toml | 4 +- examples/demo/server/.gitignore | 7 ++ examples/demo/server/Cargo.toml | 12 +++ examples/demo/server/src/main.rs | 111 ++++++++++++++++++++ examples/utils/utils.rs | 2 +- 7 files changed, 134 insertions(+), 4 deletions(-) rename examples/1_advanced/{12_pqc.rs => 12_pq.rs} (100%) create mode 100644 examples/demo/server/.gitignore create mode 100644 examples/demo/server/Cargo.toml create mode 100644 examples/demo/server/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9e03c17106..93857fb64e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "examples", ] -exclude = ["bindings/wasm", "bindings/grpc"] +exclude = ["bindings/wasm", "bindings/grpc", "examples/demo/server"] [workspace.dependencies] bls12_381_plus = { version = "0.8.17" } diff --git a/examples/1_advanced/12_pqc.rs b/examples/1_advanced/12_pq.rs similarity index 100% rename from examples/1_advanced/12_pqc.rs rename to examples/1_advanced/12_pq.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1a6da1ae50..db85317c0c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -111,8 +111,8 @@ path = "1_advanced/11_linked_verifiable_presentation.rs" name = "11_linked_verifiable_presentation" [[example]] -path = "1_advanced/12_pqc.rs" -name = "12_pqc" +path = "1_advanced/12_pq.rs" +name = "12_pq" [[example]] path = "1_advanced/13_hybrid.rs" diff --git a/examples/demo/server/.gitignore b/examples/demo/server/.gitignore new file mode 100644 index 0000000000..ff2f869996 --- /dev/null +++ b/examples/demo/server/.gitignore @@ -0,0 +1,7 @@ +/target/ +target/ + +Cargo.lock + +.well-known/* +/.well-known/* \ No newline at end of file diff --git a/examples/demo/server/Cargo.toml b/examples/demo/server/Cargo.toml new file mode 100644 index 0000000000..740859d90f --- /dev/null +++ b/examples/demo/server/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +warp = {version = "0.3.7", features = ["tls"]} + + diff --git a/examples/demo/server/src/main.rs b/examples/demo/server/src/main.rs new file mode 100644 index 0000000000..563102b66d --- /dev/null +++ b/examples/demo/server/src/main.rs @@ -0,0 +1,111 @@ + +use warp::Filter; + +const CERT_PEM: &str = r#" +-----BEGIN CERTIFICATE----- +MIIFfTCCA2WgAwIBAgIUKRmy+fJlEoA6QuOV9cDgjapPtHIwDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCSVQxEzARBgNVBAgMClNvbWUtU3RhdGUxFjAUBgNVBAoM +DUlzc3VlciBTZXJ2ZXIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNDA1MzAxNzEy +MjhaFw0yNTA1MzAxNzEyMjhaME4xCzAJBgNVBAYTAklUMRMwEQYDVQQIDApTb21l +LVN0YXRlMRYwFAYDVQQKDA1Jc3N1ZXIgU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhv +c3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0NgJZ4NAx0/Aho4/A +y+eq8XC8n8DUvQQ7kPpbHBzo0m3EFJS2I9cZg3m4UjZsDeX5hy+6hyA2cMTwY2rG +X4tz1FK+ObiM4MFgj0oI6UC9SrLrUdBP3dazdLEKbAjgLLgJwKMQKiPn8DNx+/Jw +pH9OhmqEiK3mOBjifKr9ib7rvRBfgar7xzyv+oLgjs9jR9KU2QO+jw15bp/8+aI8 +q3yIGkxP5lp/tmo1JzBEwfEgGz5X3LY+h5Z9EjQZPH1ZWp2WV6epk4WZVKMeSTEe +Rmv0ztG2Ib42GkIX4Ne1TNKWt/mHGwrfXCDkBk+PDqsMCvVk2ZoRPpriOU/1+dWv +Nbt69wqIxDMG+EvdlCEoOdFPJt+Wk0kO+k0MDHmXtEkyUNnRNds1yQ/+6/WuuZ+1 ++UPOs51QA/uSnhh9t2ZrpyVQYfAb6dlKYtB82aXwF0AF2yNVearmZuXqqm15EwBe +Ra3lFfbrQ1EfC1biaLrXaa6AR+xhNyTlJbjiN3hVUV9Z/0vLWMKfuKBWEB0rgEPp +3opTxyZj1MQQu5EkCABHu68yDPwRazd4pGsjcnwDTLC7JMp33vZCvOTkIL09yW4v +Dh6Um5vRtlB9UnoyT8h6+rvPzH6rnX+CURDgOFS4GP2jj+9KxtDdfwopZXYsEK9e +P0kYIszhaHhn5KE8bQOeafpWXwIDAQABo1MwUTAdBgNVHQ4EFgQUevGkLEz/xQ3q +cN/N9+5qLbLZfrMwHwYDVR0jBBgwFoAUevGkLEz/xQ3qcN/N9+5qLbLZfrMwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAhoa+2JoI0RX5uB4w2Lxz +q1XrIMShU5lj9PwyrwSAHR1A6UkPefnMk9aLybJhCgakfttgTETGChZoOQZdyn53 +QzZf66z2e6nzrQgMRiv3DqEPOtXb9CnbXWMeHGV2B7M0OOIvwQLabzzryT1RawJz +NIu8q8bEyo95/fdVtV/OPFP8/4LTiqGJCfQ4avH62NUFmxjVO9RpU2ULCSeAHWHG +5g8On/hTeI9eWugqkjH7UrSFm/aDQH0Qf+glnUZBFPHYd38oQQd0PvBYnlKccw6r +uzU1ekgM/bQl8C3BcXt/zFlT1Dtp6EY1pz5sKjEBMDjfWxnLGNhDbCYG7+w3ki/w +zTZjLyOh9R4rzXSTPACki8CcCz6rZqPCm5AIyPnPspeSgYLAIgHjrWKsUiX9Gl6m +D03Wk8YJLabRiz16l7FK/RN+vNlX8URuWZYOcdxHVz8ZU6tD+VAWh9vS+LS5a/1E +Hw3JobssPUA7ZsF9PYRpNv9RckMPbAnct9tmA9s6NUOuxzFfcNf13DGwkUBImqF3 +e38wWAchOydQ0vghbiCpMOwD6IrssQBTQMRYOlrol6osrRxuXJvb15dpUkq64Q6/ +gfi1O7mVEkNhDjpv8ebBLqy8qnQZmOc+h7JMPI7fHsFLTenJqKjawx6C3WVp/B0J +K/HioaQNxyYc+7hW1bWvwIU= +-----END CERTIFICATE----- +"#; + +const KEY_PEM: &str = r#" +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC0NgJZ4NAx0/Ah +o4/Ay+eq8XC8n8DUvQQ7kPpbHBzo0m3EFJS2I9cZg3m4UjZsDeX5hy+6hyA2cMTw +Y2rGX4tz1FK+ObiM4MFgj0oI6UC9SrLrUdBP3dazdLEKbAjgLLgJwKMQKiPn8DNx ++/JwpH9OhmqEiK3mOBjifKr9ib7rvRBfgar7xzyv+oLgjs9jR9KU2QO+jw15bp/8 ++aI8q3yIGkxP5lp/tmo1JzBEwfEgGz5X3LY+h5Z9EjQZPH1ZWp2WV6epk4WZVKMe +STEeRmv0ztG2Ib42GkIX4Ne1TNKWt/mHGwrfXCDkBk+PDqsMCvVk2ZoRPpriOU/1 ++dWvNbt69wqIxDMG+EvdlCEoOdFPJt+Wk0kO+k0MDHmXtEkyUNnRNds1yQ/+6/Wu +uZ+1+UPOs51QA/uSnhh9t2ZrpyVQYfAb6dlKYtB82aXwF0AF2yNVearmZuXqqm15 +EwBeRa3lFfbrQ1EfC1biaLrXaa6AR+xhNyTlJbjiN3hVUV9Z/0vLWMKfuKBWEB0r +gEPp3opTxyZj1MQQu5EkCABHu68yDPwRazd4pGsjcnwDTLC7JMp33vZCvOTkIL09 +yW4vDh6Um5vRtlB9UnoyT8h6+rvPzH6rnX+CURDgOFS4GP2jj+9KxtDdfwopZXYs +EK9eP0kYIszhaHhn5KE8bQOeafpWXwIDAQABAoICACk76GrPTWPxVhMkXSqknVQY +XY7TUOh8fWDxyc8aDiVZrML+YI6Sgc+1LR83NHuh7Hqda5bW/1BlmjJU1SQhzYNJ +1ErxYyBc9vJUpwCF+om1c13zwOHdZRh3vWCgmqFMqimVGdqDgea6p9LPbshGY1jA +2dOMgJnaQRtloixUHcrL3li0Dz7/9gtgGx0toBdb4jursjcifVdJMDNOnUgbhumP +duGbWB67yu5xkpJQ/A64HRo2hr6lAJKKnUAe/qF44fD6CoN7HUkZvB8caTA/tcj0 +n7h4XRD9Dh1vlXvG4bKZ0u8OiXzVFDOInCCkHxAo28BhJ+cFN6RGr9yc9z9ZYJWe +QzzAG7kLW179ZMOt24CSnA+rjNOq8J7yyeA2KnrKHE1aVDXKALANc8SK69gRqhG0 +8Kf37Pa74IMn4hwJY40MnGZCn8Bc72WEOu8Q/j3JKlXuvQ+2Juy3j0qeemb0sPhI +GDjEh0Osm+VvFK3MnQLjJ9fNis3FBnOkqemv7yXrbsDnLmbtKz1pVGXlt9WStWuf +1jxcP7vGRuxhI2ydJiDqny19eSYc+ze9ZdMGsinc6oiVtv96Kabrj83CzPxtMn4W +KyWVjq4H0Nvg3CbbIkQyrmIIxr7/9dcFxRZJj7Qp+7W4/hnEQJDFbijWsm8anu4j +bJqARy3qF7rhyhdVmIRlAoIBAQDkKXiDYkh80BCBB0CzQweHeQbC9KbGQ34spoAh +9C3iFeEukB4QbCYRzrC4BsE4FAkA5ZjkRFFuwtUQUfXFmCN4vvMKLEJ7knUZ4GtP +kKe8kUWiRLNX8NgO5Kp6U8eNwH9mCGfAG81FzYps0KXBKLdHyN8D3/XHBL1P77Sx +3f8U3N/8PeSkL5J7tAAPH1AT4dMW5Yyd35/nst0LgbhsBMoSZO9yoy40DCXc/PZa +XA8UyhzJ9brBDWLqTFhnH9FY0nDcBErn+k3Iy1yt+AtjzHkozc+8UgMebymETISg +hHuFkI+lCOOSalhSa+xaQ5mCxnug0RvamH8s7gJS7hmqgRI7AoIBAQDKMs/ABIu6 +AH6Bd0kslZiiiiBF+uD9esAm+5hKzEx0/rmVleqSPvgMZ/wOjGIDkWHlcuRYfT3x +qJfN+TTsRWO+I0qBN637pa61gyN0evyU0yNLHtTpk4kGul809033J2qI1lV5ZDZQ +VmRndxS5o+q258Q+k9gUEhH8pootIIskZce+Ap/1yQYU6dX8tZyxb0xIQRxHUoTr +wcGWF7TeLUKjet5STDQkFrJAsxqPnlGdZEbmIUwVq1JQFDmQLq1LMphsPCMRsorl +x1IXGMytW0t1Uo8oAPgLokkvlCVkcr36lK7PqztCs/7CMHFB4mDOQ6ebXXdeD+Cj +UozDXk1QCkYtAoIBAGQucWP++6USFq0Q6i/L9rpdQcLIeZIEgJpKYWiHNr5WrREs +5oZ/dhfLkXy8OorNAUcLiR5xgPAJTFRmKGwiaTDTZxPIa0im/hPmnjfJZF0/zDt7 +vuOWNMgVT2V28+Ah7c53Ulbf3joQDf/JfX5ayl8SrBmfdON2CUnYDpcqyp32fcLb +ylDtMnxuhLgfH6IbPfyHvj5zulqaBsTOqJOFZtS8zQag7+CalOV7jRAP6+9M72ce +Jmot3ojSDN768Yj2ned9WwaQuSa5ZHBewWGInop0FAq0V/o9BRW6I2H3yeTTnL7y +MDpRx4YpLRZVKEprrEzohDpvSygHOa4ALfTXx5ECggEAHdDgUbBZ6lOoU+8DG0/z +8gPsPmWAR+CR9S2kh2MY4yestiIlNOuUKEAtKvgIe/40MWF/yoytVbVGrVjSNQUG +cEND+Un8nd8QpDNyRvrbxBZeXfVt8AORcSyKXYUaOhfV3de9QulEGRSkOZ2VsMo7 +Ej2a8p9afZssi9UDCySBYByiF6LLc7Lw8qSIqXseYAvJc94mDV5du2YW5csZIKoL +6Y90uS4Dgk7WejPu9r6hylO/ILBIR+m7eZOB6YD4jN4pG8zCXxm04a1CraU6iVWt +Ct/hvERoT07e++UcnnNNEGZcYMMfOfbQ2lHKYazAGapgTMi7kWNF/M9vcl+1htf9 +AQKCAQBBHqBp7Td7W5az8cXN2OmMu3rf//cKod/XLutFDOqIcWF3hez72VJwdgWt +A5CWBdJs6Z6Te+R9bvvh6ejx18r+W+g28MmhqkhWpIL+wsS8k61HAB8yU3049e72 +0kAfr1KLOOGu7AkAZwosCUDM5QXSOI5DyPtV0zt/zZaJW1dgmnQWZOLVLFIw4+SN +nXlzM+holKXA/mFOMcC6vln7F6XcIp1eYWsoq8KpQcmYIFzXjQh9O6zpoecBZgD3 +jvEj1PHuYUJjxCxwtr27GymxWtFuuOTDA91H8cZSnUmW7MWnkFa/VlLHEkSP5fTY +DcdbW3ZurxzQIr/OYo8xwUOR9USF +-----END PRIVATE KEY----- +"#; + + +#[tokio::main] +async fn main() { + const PORT: u16 = 4443; + println!("Server is listening on port {} ...", PORT); + + // Match any request and return hello world! + let route = warp::path(".well-known") + .and(warp::fs::dir("./.well-known")); + + warp::serve(route) + .tls() + .cert(CERT_PEM.as_bytes()) + .key(KEY_PEM.as_bytes()) + .run(([127, 0, 0, 1], PORT)) + .await; + +} \ No newline at end of file diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index e20dd9abe3..d63b2580e5 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -32,7 +32,7 @@ pub static API_ENDPOINT: &str = "http://localhost"; pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; +pub static PATH_DID_FILE: &str = "./examples/demo/server/.well-known/"; pub type MemStorage = Storage; From a5db02ac44af00b6126d21479209ca50d161583b Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 24 Oct 2024 19:05:24 +0200 Subject: [PATCH 063/163] fix --- examples/demo/server/.gitignore | 4 +++- examples/demo/server/.well-known/.gitkeep | 0 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 examples/demo/server/.well-known/.gitkeep diff --git a/examples/demo/server/.gitignore b/examples/demo/server/.gitignore index ff2f869996..061045c066 100644 --- a/examples/demo/server/.gitignore +++ b/examples/demo/server/.gitignore @@ -4,4 +4,6 @@ target/ Cargo.lock .well-known/* -/.well-known/* \ No newline at end of file +/.well-known/* + +!.well-known/.gitkeep \ No newline at end of file diff --git a/examples/demo/server/.well-known/.gitkeep b/examples/demo/server/.well-known/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 5901885ba50def7d87213e530aeb300a7417c226 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Mon, 28 Oct 2024 10:03:05 +0100 Subject: [PATCH 064/163] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b327438f4..fe5200c40e 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@ For more details on the implementation and how to use these features, you can fi # PQ/T Hybrid -### Example +# Examples To test the above functionalities, you can refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. - -> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/). To run these examples, you must have a server instance that hosts the DID Document. Additionally, ensure that the following variables in `utils.rs` are correctly configured to point to your server instance: +> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/). To run these examples, you must +> have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, +> ensure that the following variables in `utils.rs` are correctly set to point to your server instance: > ```rust > pub static DID_URL: &str = "https://localhost:4443/.well-known/"; > pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; From 43b4515d151a5c9265da2fa3ed10d9c6e4d74fb8 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 31 Oct 2024 16:05:47 +0100 Subject: [PATCH 065/163] fix pqc bindings --- bindings/wasm/docs/api-reference.md | 120 +++++++++---------- bindings/wasm/src/storage/jwk_storage.rs | 5 +- bindings/wasm/src/storage/jwk_storage_pqc.rs | 16 ++- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index b5fc84c07e..f4e2d64d91 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -252,11 +252,16 @@ working with storage backed DID documents.

## Members
-
StateMetadataEncoding
-
-
SerializationType
-
-
PresentationProofAlgorithm
+
FailFast
+

Declares when validation should return if an error occurs.

+
+
AllErrors
+

Return all errors that occur during validation.

+
+
FirstError
+

Return after the first error occurs.

+
+
PayloadType
SubjectHolderRelationship

Declares how credential subjects must relate to the presentation holder.

@@ -272,26 +277,15 @@ This variant is the default.

Any

The holder is not required to have any kind of relationship to any credential subject.

-
MethodRelationship
-
-
PayloadType
-
StatusPurpose

Purpose of a StatusList2021.

-
ProofAlgorithm
+
MethodRelationship
+
+
PresentationProofAlgorithm
CredentialStatus
-
FailFast
-

Declares when validation should return if an error occurs.

-
-
AllErrors
-

Return all errors that occur during validation.

-
-
FirstError
-

Return after the first error occurs.

-
StatusCheck

Controls validation behaviour when checking whether or not a credential has been revoked by its credentialStatus.

@@ -309,20 +303,26 @@ This variant is the default.

SkipAll

Skip all status checks.

+
SerializationType
+
+
ProofAlgorithm
+
+
StateMetadataEncoding
+
## Functions
-
start()
-

Initializes the console error panic hook for better error messages

-
encodeB64(data) ⇒ string

Encode the given bytes in url-safe base64.

decodeB64(data) ⇒ Uint8Array

Decode the given url-safe base64-encoded slice into its raw bytes.

+
start()
+

Initializes the console error panic hook for better error messages

+
verifyEd25519(alg, signingInput, decodedSignature, publicKey)

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

This function is useful when one is composing a IJwsVerifier that delegates @@ -7531,17 +7531,27 @@ Deserializes an instance from a JSON object. | --- | --- | | json | any | - + + +## FailFast +Declares when validation should return if an error occurs. -## StateMetadataEncoding **Kind**: global variable - + + +## AllErrors +Return all errors that occur during validation. -## SerializationType **Kind**: global variable - + -## PresentationProofAlgorithm +## FirstError +Return after the first error occurs. + +**Kind**: global variable + + +## PayloadType **Kind**: global variable @@ -7569,14 +7579,6 @@ The holder must match the subject only for credentials where the [`nonTransferab ## Any The holder is not required to have any kind of relationship to any credential subject. -**Kind**: global variable - - -## MethodRelationship -**Kind**: global variable - - -## PayloadType **Kind**: global variable @@ -7584,31 +7586,17 @@ The holder is not required to have any kind of relationship to any credential su Purpose of a [StatusList2021](#StatusList2021). **Kind**: global variable - - -## ProofAlgorithm -**Kind**: global variable - - -## CredentialStatus -**Kind**: global variable - - -## FailFast -Declares when validation should return if an error occurs. + +## MethodRelationship **Kind**: global variable - - -## AllErrors -Return all errors that occur during validation. + +## PresentationProofAlgorithm **Kind**: global variable - - -## FirstError -Return after the first error occurs. + +## CredentialStatus **Kind**: global variable @@ -7641,12 +7629,18 @@ Validate the status if supported, skip any unsupported Skip all status checks. **Kind**: global variable - + -## start() -Initializes the console error panic hook for better error messages +## SerializationType +**Kind**: global variable + -**Kind**: global function +## ProofAlgorithm +**Kind**: global variable + + +## StateMetadataEncoding +**Kind**: global variable ## encodeB64(data) ⇒ string @@ -7669,6 +7663,12 @@ Decode the given url-safe base64-encoded slice into its raw bytes. | --- | --- | | data | Uint8Array | + + +## start() +Initializes the console error panic hook for better error messages + +**Kind**: global function ## verifyEd25519(alg, signingInput, decodedSignature, publicKey) diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 95d12a1d53..8df839af52 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -39,9 +39,7 @@ extern "C" { #[wasm_bindgen(method)] pub fn generate(this: &WasmJwkStorage, key_type: String, algorithm: String) -> PromiseJwkGenOutput; - #[wasm_bindgen(method)] - pub fn generate_pq_key(this: &WasmJwkStorage, key_type: String, algorithm: String) -> PromiseJwkGenOutput; - + #[wasm_bindgen(method)] pub fn insert(this: &WasmJwkStorage, jwk: WasmJwk) -> PromiseString; @@ -60,6 +58,7 @@ extern "C" { #[async_trait::async_trait(?Send)] impl JwkStorage for WasmJwkStorage { + async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { let promise: Promise = Promise::resolve(&WasmJwkStorage::generate(self, key_type.into(), alg.name().to_owned())); let result: JsValueResult = JsFuture::from(promise).await.into(); diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index 95b5a8e001..59dd51ee95 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -32,12 +32,22 @@ use js_sys::Array; use js_sys::Promise; use js_sys::Uint8Array; use wasm_bindgen_futures::JsFuture; +use super::jwk_storage::PromiseJwkGenOutput; + +#[wasm_bindgen] +extern "C" { + + #[wasm_bindgen(method, js_name = generatePQKey)] + pub fn _generate_pq_key(this: &WasmJwkStorage, key_type: String, alg: String) -> PromiseJwkGenOutput; + +} + #[async_trait::async_trait(?Send)] impl JwkStoragePQ for WasmJwkStorage { async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { - web_sys::console::log_1(&"EEEEEEEEEEEEET".into()); - let promise: Promise = Promise::resolve(&WasmJwkStorage::generate_pq_key(self, key_type.into(), alg.name().to_owned())); + web_sys::console::log_1(&"YYYYYYYYYYYYYYY".into()); + let promise: Promise = Promise::resolve(&WasmJwkStorage::_generate_pq_key(self, key_type.into(), alg.name().to_owned())); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() } @@ -58,5 +68,5 @@ interface JwkStoragePQ { /** Generate a new key represented as a JSON Web Key. * * It's recommend that the implementer exposes constants for the supported key type string. */ - generate_pq_key: (keyType: string, algorithm: JwsAlgorithm) => Promise; + generatePQKey: (keyType: string, algorithm: JwsAlgorithm) => Promise; }"#; \ No newline at end of file From 08ae148e2c12cee34204013c691c2e8036ebcd62 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 31 Oct 2024 18:04:50 +0100 Subject: [PATCH 066/163] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index fe5200c40e..5f1606fb72 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). # PQ/T Hybrid +This repository extends the IOTA Identity framework by implementing both **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) Hybrid** cryptographic approaches. These approaches address emerging threats to traditional cryptography posed by quantum computing. + +### Overview + +1. **PQ Approach**: To transition to quantum-resistant cryptography, the framework has been updated to support selected PQ signature algorithms, such as [**ML-DSA**](https://csrc.nist.gov/pubs/fips/204/final), [**SLH-DSA**](https://csrc.nist.gov/pubs/fips/205/final) and [**FALCON**](https://falcon-sign.info/). The implementation of these algorithms is provided by [**liboqs**](https://github.com/open-quantum-safe/liboqs-rust). + +2. **PQ/T Hybrid Approach**: To mitigate risks associated with the relative immaturity of certain PQ algorithms, the PQ/T Hybrid combines a PQ algorithm with a traditional one in a composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. + - **Hybrid Signatures and Composite Key**: In the PQ/T Hybrid approach, both PQ and traditional keys are managed and verified using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of keys within the DID document. This setup enforces the non-separability of signatures, protecting against stripping attacks. + - **Supported Algorithms**: Currently, there are two supported algorithms: **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512**. The first combines ML-DSA-44 with Ed25519, while the second combines ML-DSA-65 with Ed25519. # Examples From 4790387c05a967cdda0324f4817d5d02dd3a75e5 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:30:32 +0100 Subject: [PATCH 067/163] fix wasm compilation --- bindings/wasm/Cargo.toml | 6 +++--- examples/Cargo.toml | 2 ++ identity_resolver/Cargo.toml | 1 + identity_resolver/src/resolution/resolver.rs | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 0d3d78ce36..a5208ec94b 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -30,15 +30,15 @@ serde_json = { version = "1.0", default-features = false } serde_repr = { version = "0.1", default-features = false } # Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads. tokio = { version = "1.29", default-features = false, features = ["sync"] } -wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] } -wasm-bindgen-futures = { version = "0.4", default-features = false } +wasm-bindgen = { version = "=0.2.92", features = ["serde-serialize"] } +wasm-bindgen-futures = { version = "=0.4", default-features = false } web-sys = { version = "0.3", features = ["console"]} zkryptium = "0.2.2" [dependencies.identity_iota] path = "../../identity_iota" default-features = false -features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus", "pqc"] +features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus", "pqc", "hybrid"] [dev-dependencies] rand = "0.8.5" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index db85317c0c..12fdcec7f9 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -23,6 +23,8 @@ identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = serde.workspace = true colored = "2.1.0" +identity_resolver = { path = "../identity_resolver", default-features = false, features = ["did-web"] } + [lib] path = "utils/utils.rs" diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index 7a841f8c0a..cf9cc8425f 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -42,6 +42,7 @@ default = ["revocation-bitmap", "iota"] revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_core?/revocation-bitmap"] # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] +did-web = [] [lints] workspace = true diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 3d1a39ba25..18a1ba50a0 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -368,7 +368,7 @@ mod iota_handler { } } - +#[cfg(feature = "did-web")] mod web_handler { use crate::ErrorCause; use super::Resolver; From defdbd77f20560a9cc5287dbe5f8e611f0e0449e Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:27:41 +0100 Subject: [PATCH 068/163] add ts examples --- bindings/wasm/examples/src/main.ts | 12 + .../examples/src/wallet/did_jwk_hybrid.ts | 33 + .../wasm/examples/src/wallet/did_jwk_pq.ts | 31 + .../src/wallet/did_jwk_traditional.ts | 33 + .../wasm/examples/src/wallet/did_jwk_zk.ts | 33 + bindings/wasm/package-lock.json | 6772 +++-------------- 6 files changed, 1166 insertions(+), 5748 deletions(-) create mode 100644 bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts create mode 100644 bindings/wasm/examples/src/wallet/did_jwk_pq.ts create mode 100644 bindings/wasm/examples/src/wallet/did_jwk_traditional.ts create mode 100644 bindings/wasm/examples/src/wallet/did_jwk_zk.ts diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 0a074d3fd2..0ad5497b85 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -19,6 +19,10 @@ import { sdJwt } from "./1_advanced/6_sd_jwt"; import { statusList2021 } from "./1_advanced/7_status_list_2021"; import { zkp } from "./1_advanced/8_zkp"; import { zkp_revocation } from "./1_advanced/9_zkp_revocation"; +import { createDidJwk } from "./wallet/did_jwk_traditional" +import { createDidJwkZk } from "./wallet/did_jwk_zk" +import { createDidJwkPq } from "./wallet/did_jwk_pq" +import { createDidJwkHybrid } from "./wallet/did_jwk_hybrid" async function main() { // Extract example name. @@ -64,6 +68,14 @@ async function main() { return await zkp(); case "9_zkp_revocation": return await zkp_revocation(); + case "traditional": + return createDidJwk(); + case "zk": + return createDidJwkZk(); + case "pq": + return createDidJwkPq(); + case "hybrid": + return createDidJwkHybrid(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts new file mode 100644 index 0000000000..69b4b6bb5a --- /dev/null +++ b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + IotaDID, + IotaDocument, + IotaIdentityClient, + JwkMemStore, + JwsAlgorithm, + KeyIdMemStore, + MethodScope, + Storage, + CoreDocument +} from "@iota/identity-wasm/node"; +import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +export async function createDidJwkHybrid(){ + + const mnemonicSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); + const document = await CoreDocument.newDidJwk( + storage, + JwkMemStore.ed25519KeyType(), + JwsAlgorithm.EdDSA,) + + console.log(JSON.stringify(document, null, 2)); +} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts new file mode 100644 index 0000000000..26561d56fc --- /dev/null +++ b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + IotaDID, + IotaDocument, + IotaIdentityClient, + JwkMemStore, + JwsAlgorithm, + KeyIdMemStore, + MethodScope, + Storage, + CoreDocument +} from "@iota/identity-wasm/node"; +import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Demonstrate how to create a DID JWK Document */ +export async function createDidJwkPq(){ + + const mnemonicSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + + const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); + const document = await CoreDocument.newDidJwk( + storage, + JwkMemStore.ed25519KeyType(), + JwsAlgorithm.EdDSA,) + + console.log(JSON.stringify(document, null, 2)); +} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts new file mode 100644 index 0000000000..eb7f07bb49 --- /dev/null +++ b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + IotaDID, + IotaDocument, + IotaIdentityClient, + JwkMemStore, + JwsAlgorithm, + KeyIdMemStore, + MethodScope, + Storage, + CoreDocument +} from "@iota/identity-wasm/node"; +import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +export async function createDidJwk(){ + + const mnemonicSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); + const document = await CoreDocument.newDidJwk( + storage, + JwkMemStore.ed25519KeyType(), + JwsAlgorithm.EdDSA,) + + console.log(JSON.stringify(document, null, 2)); +} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts new file mode 100644 index 0000000000..d16e315e6a --- /dev/null +++ b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + IotaDID, + IotaDocument, + IotaIdentityClient, + JwkMemStore, + JwsAlgorithm, + KeyIdMemStore, + MethodScope, + Storage, + CoreDocument +} from "@iota/identity-wasm/node"; +import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +export async function createDidJwkZk(){ + + const mnemonicSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); + const document = await CoreDocument.newDidJwk( + storage, + JwkMemStore.ed25519KeyType(), + JwsAlgorithm.EdDSA,) + + console.log(JSON.stringify(document, null, 2)); +} diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index f70949c608..80432907bc 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -1,7 +1,7 @@ { "name": "@iota/identity-wasm", "version": "1.3.1", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -41,11 +41,32 @@ "@iota/sdk-wasm": "^1.0.4" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz", - "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -53,6 +74,19 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -75,9 +109,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "dev": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -86,16 +120,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.10.4", + "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -103,20 +137,6 @@ "node": ">= 6" } }, - "node_modules/@cypress/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -176,9 +196,9 @@ } }, "node_modules/@iota/sdk-wasm": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@iota/sdk-wasm/-/sdk-wasm-1.0.4.tgz", - "integrity": "sha512-sS+9avq4GFgFbDYNX2+sn8H3+rFYRhJiB7aa22T+xIixt4/VuMaHkWCzZSTnTUXeY92M7D0ABYKCF+OlHSLzxg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@iota/sdk-wasm/-/sdk-wasm-1.1.3.tgz", + "integrity": "sha512-piyl0B6gcoo7mbmX3QUCyEYtqk6UoCS2cqBYiV7FFz3fmT2DPcQJmcaDvW0nmNh5BbRR9MhPkp3MEerPm6mezA==", "peer": true, "dependencies": { "class-transformer": "^0.5.1", @@ -195,49 +215,45 @@ "fsevents": "^2.3.2" } }, - "node_modules/@iota/sdk-wasm/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, "peer": true, "dependencies": { - "side-channel": "^1.0.4" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "peer": true, "engines": { @@ -245,20 +261,31 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -271,6 +298,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", + "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@noble/ed25519": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", @@ -409,9 +448,9 @@ "dev": true }, "node_modules/@transmute/did-key-common": { - "version": "0.3.0-unstable.9", - "resolved": "https://registry.npmjs.org/@transmute/did-key-common/-/did-key-common-0.3.0-unstable.9.tgz", - "integrity": "sha512-qGzFJA615Gu/UnAvuSNrRvtCCKRVvxCmuDawenudyGR8X8WkkJ19uD8kI0GaCzZazpX1SWiairM87nKJ9aH7Tw==", + "version": "0.3.0-unstable.10", + "resolved": "https://registry.npmjs.org/@transmute/did-key-common/-/did-key-common-0.3.0-unstable.10.tgz", + "integrity": "sha512-Iryh/HcGIvmTtWFTRaG/JEgbUsqI5OqKqkR2676yQWK4ajLMsyNattz5n0ZfFQk/4U7Ee6pJvvKRduFDAqqV0Q==", "dev": true, "dependencies": { "@did-core/data-model": "^0.1.1-unstable.13", @@ -477,9 +516,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true }, "node_modules/@tsconfig/node12": { @@ -495,24 +534,24 @@ "dev": true }, "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "dependencies": { "@types/ms": "*" } }, "node_modules/@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "peer": true, "dependencies": { @@ -521,9 +560,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "peer": true, "dependencies": { @@ -532,16 +571,16 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "peer": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -552,34 +591,34 @@ "optional": true }, "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true }, "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "dev": true, "dependencies": { - "@types/unist": "*" + "@types/unist": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, "node_modules/@types/mocha": { @@ -589,23 +628,26 @@ "dev": true }, "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "node_modules/@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + "version": "22.8.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz", + "integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==", + "dependencies": { + "undici-types": "~6.19.8" + } }, "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" + "form-data": "^4.0.0" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -615,15 +657,15 @@ "dev": true }, "node_modules/@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", "dev": true }, "node_modules/@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true }, "node_modules/@types/yauzl": { @@ -643,73 +685,73 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "peer": true, "dependencies": { @@ -717,9 +759,9 @@ } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "peer": true, "dependencies": { @@ -727,79 +769,79 @@ } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -830,9 +872,9 @@ } }, "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -841,21 +883,14 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peer": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -956,8 +991,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/ansi-styles": { "version": "4.3.0", @@ -975,9 +1009,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -1074,9 +1108,9 @@ } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, "node_modules/asynckit": { @@ -1103,9 +1137,9 @@ } }, "node_modules/aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, "node_modules/bail": { @@ -1162,9 +1196,9 @@ } }, "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", "dev": true, "engines": { "node": ">=0.6" @@ -1180,12 +1214,15 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/blob-util": { @@ -1211,12 +1248,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1229,9 +1266,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -1241,14 +1278,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -1329,12 +1370,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1353,9 +1400,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001457", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", - "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", "dev": true, "funding": [ { @@ -1365,6 +1412,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "peer": true @@ -1477,9 +1528,9 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "peer": true, "engines": { @@ -1529,9 +1580,9 @@ } }, "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "dependencies": { "string-width": "^4.2.0" @@ -1560,14 +1611,28 @@ } }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/clone-deep": { @@ -1824,13 +1889,13 @@ } }, "node_modules/cypress": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", - "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", + "version": "13.15.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.1.tgz", + "integrity": "sha512-DwUFiKXo4lef9kA0M4iEhixFqoqp2hw8igr0lTqafRb9qtU3X0XGxKbkSYsUFdkrAkphc7MPDxoNPhk5pj9PVg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^3.0.0", + "@cypress/request": "^3.0.4", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -1869,7 +1934,8 @@ "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", - "tmp": "~0.2.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -1920,142 +1986,6 @@ "cypress-multi-reporters": "^1.5.0" } }, - "node_modules/cypress-parallel/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cypress-parallel/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/cypress-parallel/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cypress-parallel/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cypress-parallel/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/cypress-parallel/node_modules/yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/cypress/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2093,18 +2023,18 @@ } }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2116,15 +2046,12 @@ } }, "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/decode-named-character-reference": { @@ -2149,6 +2076,22 @@ "node": ">=4.0.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2194,17 +2137,17 @@ } }, "node_modules/dmd": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.1.0.tgz", - "integrity": "sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.2.3.tgz", + "integrity": "sha512-SIEkjrG7cZ9GWZQYk/mH+mWtcRPly/3ibVuXO/tP/MFoWz6KiRK77tSMq6YQBPl7RljPtXPQ/JhxbNuCdi1bNw==", "dev": true, "dependencies": { "array-back": "^6.2.2", "cache-point": "^2.0.0", "common-sequence": "^2.0.2", "file-set": "^4.0.2", - "handlebars": "^4.7.7", - "marked": "^4.0.12", + "handlebars": "^4.7.8", + "marked": "^4.3.0", "object-get": "^2.1.1", "reduce-flatten": "^3.0.1", "reduce-unique": "^2.0.1", @@ -2240,9 +2183,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.304", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.304.tgz", - "integrity": "sha512-6c8M+ojPgDIXN2NyfGn8oHASXYnayj+gSEnGeLMKb9zjsySeVB/j7KkNAAG9yDcv8gNlhvFg5REa1N/kQU6pgA==", + "version": "1.5.50", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz", + "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==", "dev": true, "peer": true }, @@ -2271,9 +2214,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "peer": true, "dependencies": { @@ -2298,25 +2241,47 @@ } }, "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true, "peer": true }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -2502,9 +2467,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2524,9 +2489,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2593,9 +2558,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2660,9 +2625,9 @@ } }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2698,6 +2663,18 @@ "node": ">= 8" } }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs-then-native": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", @@ -2714,9 +2691,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -2727,9 +2704,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -2741,14 +2721,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2791,6 +2775,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -2870,10 +2855,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/growl": { @@ -2886,13 +2882,13 @@ } }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -2906,17 +2902,6 @@ "uglify-js": "^3.1.4" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2926,10 +2911,21 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -2948,6 +2944,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2958,14 +2965,14 @@ } }, "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "sshpk": "^1.18.0" }, "engines": { "node": ">=0.10" @@ -3001,9 +3008,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -3022,6 +3029,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3284,25 +3292,25 @@ "dev": true }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, "dependencies": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -3313,9 +3321,9 @@ } }, "node_modules/jsdoc-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.1.1.tgz", - "integrity": "sha512-0pkuPCzVXiqsDAsVrNFXCkHzlyNepBIDVtwwehry4RJAnZmXtlAz7rh8F9FRz53u3NeynGbex+bpYWwi8lE66A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.2.0.tgz", + "integrity": "sha512-93YDnlm/OYTlLOFeNs4qAv0RBCJ0kGj67xQaWy8wrbk97Rw1EySitoOTHsTHXPEs3uyx2IStPKGrbE7LTnZXbA==", "dev": true, "dependencies": { "array-back": "^6.2.2", @@ -3323,7 +3331,7 @@ "collect-all": "^1.0.4", "file-set": "^4.0.2", "fs-then-native": "^2.0.0", - "jsdoc": "^3.6.10", + "jsdoc": "^4.0.0", "object-to-spawn-args": "^2.0.1", "temp-path": "^1.0.0", "walk-back": "^5.1.0" @@ -3333,22 +3341,37 @@ } }, "node_modules/jsdoc-parse": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.1.0.tgz", - "integrity": "sha512-n/hDGQJa69IBun1yZAjqzV4gVR41+flZ3bIlm9fKvNe2Xjsd1/+zCo2+R9ls8LxtePgIWbpA1jU7xkB2lRdLLg==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.4.tgz", + "integrity": "sha512-MQA+lCe3ioZd0uGbyB3nDCDZcKgKC7m/Ivt0LgKZdUoOlMJxUWJQ3WI6GeyHp9ouznKaCjlp7CU9sw5k46yZTw==", "dev": true, "dependencies": { "array-back": "^6.2.2", + "find-replace": "^5.0.1", "lodash.omit": "^4.5.0", - "lodash.pick": "^4.4.0", - "reduce-extract": "^1.0.0", - "sort-array": "^4.1.4", - "test-value": "^3.0.0" + "sort-array": "^5.0.0" }, "engines": { "node": ">=12" } }, + "node_modules/jsdoc-parse/node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/jsdoc-to-markdown": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-7.1.1.tgz", @@ -3420,8 +3443,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jsonfile": { "version": "6.1.0", @@ -3569,12 +3591,12 @@ } }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/listr2": { @@ -3673,12 +3695,6 @@ "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", "dev": true }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", - "dev": true - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3748,6 +3764,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3759,8 +3776,7 @@ "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/make-error": { "version": "1.3.6", @@ -3769,25 +3785,26 @@ "dev": true }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true, "peerDependencies": { "@types/markdown-it": "*", @@ -3799,7 +3816,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, - "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -3808,9 +3824,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -3832,19 +3848,22 @@ } }, "node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "node_modules/merge-stream": { @@ -3863,9 +3882,9 @@ } }, "node_modules/micromark": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", - "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", "dev": true, "funding": [ { @@ -3898,9 +3917,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", "dev": true, "funding": [ { @@ -3932,9 +3951,9 @@ } }, "node_modules/micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", "dev": true, "funding": [ { @@ -3953,9 +3972,9 @@ } }, "node_modules/micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", "dev": true, "funding": [ { @@ -3975,9 +3994,9 @@ } }, "node_modules/micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", "dev": true, "funding": [ { @@ -3995,9 +4014,9 @@ } }, "node_modules/micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", "dev": true, "funding": [ { @@ -4013,14 +4032,13 @@ "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-types": "^1.0.0" } }, "node_modules/micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", "dev": true, "funding": [ { @@ -4040,9 +4058,9 @@ } }, "node_modules/micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "dev": true, "funding": [ { @@ -4060,9 +4078,9 @@ } }, "node_modules/micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", "dev": true, "funding": [ { @@ -4079,9 +4097,9 @@ } }, "node_modules/micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", "dev": true, "funding": [ { @@ -4100,9 +4118,9 @@ } }, "node_modules/micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", "dev": true, "funding": [ { @@ -4120,9 +4138,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", "dev": true, "funding": [ { @@ -4139,9 +4157,9 @@ } }, "node_modules/micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", "dev": true, "funding": [ { @@ -4161,9 +4179,9 @@ } }, "node_modules/micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", "dev": true, "funding": [ { @@ -4177,9 +4195,9 @@ ] }, "node_modules/micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", "dev": true, "funding": [ { @@ -4193,9 +4211,9 @@ ] }, "node_modules/micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", "dev": true, "funding": [ { @@ -4212,9 +4230,9 @@ } }, "node_modules/micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", "dev": true, "funding": [ { @@ -4231,9 +4249,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", - "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", "dev": true, "funding": [ { @@ -4252,9 +4270,9 @@ } }, "node_modules/micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", "dev": true, "funding": [ { @@ -4274,9 +4292,9 @@ } }, "node_modules/micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "dev": true, "funding": [ { @@ -4290,9 +4308,9 @@ ] }, "node_modules/micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "dev": true, "funding": [ { @@ -4306,12 +4324,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4368,13 +4386,10 @@ } }, "node_modules/minipass": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", - "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { "node": ">=8" } @@ -4392,6 +4407,18 @@ "node": ">= 8" } }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4462,6 +4489,17 @@ "node": ">=6" } }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/mocha/node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -4501,6 +4539,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -4541,12 +4580,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -4556,6 +4589,15 @@ "randombytes": "^2.1.0" } }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4584,9 +4626,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/nanoid": { @@ -4608,9 +4650,9 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4627,9 +4669,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true, "peer": true }, @@ -4661,9 +4703,12 @@ "dev": true }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4810,9 +4855,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "peer": true }, @@ -4864,16 +4909,10 @@ "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "dev": true }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, "dependencies": { "end-of-stream": "^1.1.0", @@ -4881,21 +4920,29 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -4904,12 +4951,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4940,9 +4981,9 @@ } }, "node_modules/rdf-canonize": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-3.3.0.tgz", - "integrity": "sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-3.4.0.tgz", + "integrity": "sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==", "dev": true, "dependencies": { "setimmediate": "^1.0.5" @@ -4963,43 +5004,6 @@ "node": ">=8.10.0" } }, - "node_modules/reduce-extract": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", - "integrity": "sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==", - "dev": true, - "dependencies": { - "test-value": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/reduce-extract/node_modules/array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "dependencies": { - "typical": "^2.6.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/reduce-extract/node_modules/test-value": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", - "integrity": "sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==", - "dev": true, - "dependencies": { - "array-back": "^1.0.2", - "typical": "^2.4.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/reduce-flatten": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", @@ -5056,15 +5060,15 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", "peer": true }, "node_modules/remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -5100,19 +5104,13 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "dev": true, "dependencies": { - "lodash": "^4.17.14" + "lodash": "^4.17.21" } }, "node_modules/restore-cursor": { @@ -5168,9 +5166,9 @@ } }, "node_modules/rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -5215,9 +5213,9 @@ "dev": true }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -5233,12 +5231,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -5261,6 +5256,22 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -5305,7 +5316,6 @@ "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", "dev": true, - "license": "MIT", "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", @@ -5314,13 +5324,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5356,34 +5370,33 @@ } }, "node_modules/sort-array": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", - "integrity": "sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.0.0.tgz", + "integrity": "sha512-Sg9MzajSGprcSrMIxsXyNT0e0JB47RJRfJspC+7co4Z5BdNsNl8FmWI+lXEpyKq+vkMG6pHgAhqyCO+bkDTfFQ==", "dev": true, "dependencies": { - "array-back": "^5.0.0", - "typical": "^6.0.1" + "array-back": "^6.2.2", + "typical": "^7.1.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/sort-array/node_modules/array-back": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", - "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">=12.17" + }, + "peerDependencies": { + "@75lb/nature": "^0.1.1" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } } }, "node_modules/sort-array/node_modules/typical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", - "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz", + "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12.17" } }, "node_modules/source-map": { @@ -5434,6 +5447,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "dependencies": { "array-back": "^1.0.2" @@ -5562,12 +5576,6 @@ "node": ">=4" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "dev": true - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -5579,20 +5587,20 @@ } }, "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">=10" } }, "node_modules/temp-path": { @@ -5602,14 +5610,14 @@ "dev": true }, "node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -5621,17 +5629,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -5656,20 +5664,20 @@ } }, "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "peer": true, "dependencies": { @@ -5730,6 +5738,24 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tldts": { + "version": "6.1.58", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", + "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", + "dev": true, + "dependencies": { + "tldts-core": "^6.1.58" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.58", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", + "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==", + "dev": true + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -5752,27 +5778,15 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tr46": { @@ -5780,10 +5794,19 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -5868,14 +5891,14 @@ } }, "node_modules/ts-mocha/node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "optional": true, "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } @@ -5890,9 +5913,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -5942,12 +5965,12 @@ } }, "node_modules/tsconfig-paths": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", - "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { - "json5": "^2.2.1", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, @@ -5956,9 +5979,9 @@ } }, "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/tunnel-agent": { @@ -5980,9 +6003,9 @@ "dev": true }, "node_modules/txm": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/txm/-/txm-8.1.0.tgz", - "integrity": "sha512-mVDmoN13jYX3igNcnS+TEJJmMIRLjn0wch/wOI23z5IkCKiw9xinv1WkugB55j57W8MfuEk/psVVO4BWMfZxfA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/txm/-/txm-8.2.0.tgz", + "integrity": "sha512-ALNu1KIbUMXlsl/UacjfhbtG4CWK07JYtf0KNf8aXhoixmtgMj2MdOit5er6V8qh5eTIvtB6+AexIQMLZhSz6Q==", "dev": true, "dependencies": { "async": "^3.2.1", @@ -6000,9 +6023,9 @@ } }, "node_modules/txm/node_modules/supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, "engines": { "node": ">=12" @@ -6028,7 +6051,6 @@ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", @@ -6050,7 +6072,6 @@ "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", "dev": true, - "license": "MIT", "dependencies": { "handlebars": "^4.7.7" }, @@ -6063,7 +6084,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -6073,7 +6093,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6085,9 +6104,9 @@ } }, "node_modules/typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6104,15 +6123,15 @@ "dev": true }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "node_modules/uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "optional": true, "bin": { @@ -6123,11 +6142,16 @@ } }, "node_modules/underscore": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", - "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "dev": true }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -6148,9 +6172,9 @@ } }, "node_modules/unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0" @@ -6161,9 +6185,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -6179,9 +6203,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -6191,15 +6215,19 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "peer": true, "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -6214,16 +6242,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -6272,9 +6290,9 @@ } }, "node_modules/vfile": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", - "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -6288,9 +6306,9 @@ } }, "node_modules/vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -6305,43 +6323,41 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/vscode-textmate": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/walk-back": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", - "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", + "integrity": "sha512-e/FRLDVdZQWFrAzU6Hdvpm7D7m2ina833gIKLptQykRK49mmCYHLHq7UqjPDbxbKLZkTkW1rFqbengdE3sLfdw==", "dev": true, "engines": { "node": ">=12.17" } }, "node_modules/wasm-opt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.3.0.tgz", - "integrity": "sha512-24+IOboX4Sav0bI8Krwf0Y6dnpN4KxYtqpl0qWt86qVLsmayUqx1KMBrJTlQWNC+/dsqzQJjK6QvxNJsZYOgJg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.4.0.tgz", + "integrity": "sha512-wIsxxp0/FOSphokH4VOONy1zPkVREQfALN+/JTvJPK8gFSKbsmrcfECu2hT7OowqPfb4WEMSMceHgNL0ipFRyw==", "dev": true, "hasInstallScript": true, "dependencies": { - "node-fetch": "^2.6.7", - "tar": "^6.1.11" + "node-fetch": "^2.6.9", + "tar": "^6.1.13" }, "bin": { "wasm-opt": "bin/wasm-opt.js" } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "peer": true, "dependencies": { @@ -6358,35 +6374,34 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.76.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", - "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "dev": true, "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -6509,18 +6524,38 @@ "dev": true }, "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/yargs-parser": { "version": "20.2.4", @@ -6546,6 +6581,18 @@ "node": ">=10" } }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yargs-unparser/node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6555,6 +6602,80 @@ "node": ">=8" } }, + "node_modules/yargs/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -6586,4850 +6707,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@babel/parser": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz", - "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==", - "dev": true - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, - "@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@did-core/data-model": { - "version": "0.1.1-unstable.15", - "resolved": "https://registry.npmjs.org/@did-core/data-model/-/data-model-0.1.1-unstable.15.tgz", - "integrity": "sha512-l7gxLxegcXW7389G+j6o+S24lS8uasmJx5txWpW3QadNvOawKwvWn8bV59SdHSK806xNzIZaCLKmXKxebs8yAQ==", - "dev": true, - "requires": { - "factory.ts": "^0.5.1" - } - }, - "@did-core/did-ld-json": { - "version": "0.1.1-unstable.15", - "resolved": "https://registry.npmjs.org/@did-core/did-ld-json/-/did-ld-json-0.1.1-unstable.15.tgz", - "integrity": "sha512-p2jKRxSU+eJJqd+ewCklYp/XZ6ysISk8VU2/kANCoB/WwUy/kVgw2rUNScRDXw2utr9Qj36P8EZTYi4aj7vRCQ==", - "dev": true, - "requires": { - "@transmute/did-context": "^0.6.1-unstable.25", - "jsonld-checker": "^0.1.6" - } - }, - "@digitalbazaar/http-client": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-1.2.0.tgz", - "integrity": "sha512-W9KQQ5pUJcaR0I4c2HPJC0a7kRbZApIorZgPnEDwMBgj16iQzutGLrCXYaZOmxqVLVNqqlQ4aUJh+HBQZy4W6Q==", - "dev": true, - "requires": { - "esm": "^3.2.22", - "ky": "^0.25.1", - "ky-universal": "^0.8.2" - } - }, - "@iota/sdk-wasm": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@iota/sdk-wasm/-/sdk-wasm-1.0.4.tgz", - "integrity": "sha512-sS+9avq4GFgFbDYNX2+sn8H3+rFYRhJiB7aa22T+xIixt4/VuMaHkWCzZSTnTUXeY92M7D0ABYKCF+OlHSLzxg==", - "peer": true, - "requires": { - "class-transformer": "^0.5.1", - "fsevents": "^2.3.2", - "node-fetch": "^2.6.7", - "qs": "^6.9.7", - "reflect-metadata": "^0.1.13", - "semver": "^7.5.2", - "text-encoding": "^0.7.0" - }, - "dependencies": { - "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "peer": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "peer": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@noble/ed25519": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", - "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@stablelib/binary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz", - "integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==", - "dev": true, - "requires": { - "@stablelib/int": "^1.0.1" - } - }, - "@stablelib/bytes": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/bytes/-/bytes-1.0.1.tgz", - "integrity": "sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==", - "dev": true - }, - "@stablelib/ed25519": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stablelib/ed25519/-/ed25519-1.0.3.tgz", - "integrity": "sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==", - "dev": true, - "requires": { - "@stablelib/random": "^1.0.2", - "@stablelib/sha512": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "@stablelib/hash": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-1.0.1.tgz", - "integrity": "sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==", - "dev": true - }, - "@stablelib/int": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", - "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==", - "dev": true - }, - "@stablelib/keyagreement": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz", - "integrity": "sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==", - "dev": true, - "requires": { - "@stablelib/bytes": "^1.0.1" - } - }, - "@stablelib/random": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", - "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", - "dev": true, - "requires": { - "@stablelib/binary": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "@stablelib/sha512": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/sha512/-/sha512-1.0.1.tgz", - "integrity": "sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==", - "dev": true, - "requires": { - "@stablelib/binary": "^1.0.1", - "@stablelib/hash": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "@stablelib/wipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", - "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==", - "dev": true - }, - "@stablelib/x25519": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stablelib/x25519/-/x25519-1.0.3.tgz", - "integrity": "sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==", - "dev": true, - "requires": { - "@stablelib/keyagreement": "^1.0.1", - "@stablelib/random": "^1.0.2", - "@stablelib/wipe": "^1.0.1" - } - }, - "@transmute/did-context": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/did-context/-/did-context-0.6.1-unstable.37.tgz", - "integrity": "sha512-p/QnG3QKS4218hjIDgdvJOFATCXsAnZKgy4egqRrJLlo3Y6OaDBg7cA73dixOwUPoEKob0K6rLIGcsCI/L1acw==", - "dev": true - }, - "@transmute/did-key-common": { - "version": "0.3.0-unstable.9", - "resolved": "https://registry.npmjs.org/@transmute/did-key-common/-/did-key-common-0.3.0-unstable.9.tgz", - "integrity": "sha512-qGzFJA615Gu/UnAvuSNrRvtCCKRVvxCmuDawenudyGR8X8WkkJ19uD8kI0GaCzZazpX1SWiairM87nKJ9aH7Tw==", - "dev": true, - "requires": { - "@did-core/data-model": "^0.1.1-unstable.13", - "@did-core/did-ld-json": "^0.1.1-unstable.13", - "@transmute/did-context": "^0.6.1-unstable.36", - "@transmute/ld-key-pair": "^0.6.1-unstable.36", - "@transmute/security-context": "^0.6.1-unstable.36" - } - }, - "@transmute/did-key-ed25519": { - "version": "0.3.0-unstable.9", - "resolved": "https://registry.npmjs.org/@transmute/did-key-ed25519/-/did-key-ed25519-0.3.0-unstable.9.tgz", - "integrity": "sha512-mFTTL1IHp26JweHN/SCj2Re5iBr5sWbyctd5LRoHRU1DQB0XmBBFX5ZzCCtnEiBGvDF55Eyx1vpkwUHIcD3QGg==", - "dev": true, - "requires": { - "@transmute/did-key-common": "^0.3.0-unstable.9", - "@transmute/ed25519-key-pair": "^0.6.1-unstable.37" - } - }, - "@transmute/ed25519-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/ed25519-key-pair/-/ed25519-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-l34yzE/QnQwmdk5xY9g2kD55e4XPp/jTZQzPu7I6J4Ar+bMaL/0RLL/pgvwyI7qUpsddxRf4WPZCCcZveqPcdA==", - "dev": true, - "requires": { - "@stablelib/ed25519": "^1.0.1", - "@transmute/ld-key-pair": "^0.6.1-unstable.37", - "@transmute/x25519-key-pair": "^0.6.1-unstable.37" - } - }, - "@transmute/ld-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/ld-key-pair/-/ld-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-DcTpEruAQBfOd2laZkg3uCQ+67Y7dw2hsvo42NAQ5tItCIx5AClP7zccri7T2JUcfDUFaE32z/BLTMEKYt3XZQ==", - "dev": true - }, - "@transmute/security-context": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/security-context/-/security-context-0.6.1-unstable.37.tgz", - "integrity": "sha512-GtLmG65qlORrz/2S4I74DT+vA4+qXsFxrMr0cNOXjUqZBd/AW1PTrFnryLF9907BfoiD58HC9qb1WVGWjSlBYw==", - "dev": true - }, - "@transmute/x25519-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/x25519-key-pair/-/x25519-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-j6zR9IoJmgVhUCVH8YVGpsgQf99SxPKZ00LGnUheBAQzgj2lULGBQ44G+GqBCdzfT0qweptTfp1RjqqHEpizeA==", - "dev": true, - "requires": { - "@stablelib/x25519": "^1.0.0", - "@transmute/ld-key-pair": "^0.6.1-unstable.37" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, - "@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", - "dev": true, - "peer": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "optional": true - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", - "dev": true, - "requires": { - "@types/unist": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", - "dev": true - }, - "@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", - "dev": true - }, - "@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peer": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escape-sequences": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", - "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", - "dev": true, - "requires": { - "array-back": "^3.0.1" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - } - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", - "dev": true - }, - "bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cache-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz", - "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "fs-then-native": "^2.0.0", - "mkdirp2": "^1.0.4" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true - } - } - }, - "cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001457", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", - "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", - "dev": true, - "peer": true - }, - "canonicalize": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", - "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true - }, - "check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true - }, - "ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true - }, - "class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "peer": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "string-width": "^4.2.0" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "collect-all": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz", - "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==", - "dev": true, - "requires": { - "stream-connect": "^1.0.2", - "stream-via": "^1.0.4" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "requires": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - }, - "typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true - } - } - }, - "command-line-tool": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz", - "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==", - "dev": true, - "requires": { - "ansi-escape-sequences": "^4.0.0", - "array-back": "^2.0.0", - "command-line-args": "^5.0.0", - "command-line-usage": "^4.1.0", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "command-line-usage": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz", - "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==", - "dev": true, - "requires": { - "ansi-escape-sequences": "^4.0.0", - "array-back": "^2.0.0", - "table-layout": "^0.4.2", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true - }, - "common-sequence": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz", - "integrity": "sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==", - "dev": true - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "config-master": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", - "integrity": "sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==", - "dev": true, - "requires": { - "walk-back": "^2.0.1" - }, - "dependencies": { - "walk-back": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-2.0.1.tgz", - "integrity": "sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==", - "dev": true - } - } - }, - "copy-webpack-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz", - "integrity": "sha512-SLjQNa5iE3BoCP76ESU9qYo9ZkEWtXoZxDurHoqPchAFRblJ9g96xTeC560UXBMre1Nx6ixIIUfiY3VcjpJw3g==", - "dev": true, - "requires": { - "fast-glob": "^3.2.4", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cypress": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", - "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", - "dev": true, - "requires": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - } - } - }, - "cypress-multi-reporters": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", - "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", - "dev": true, - "peer": true, - "requires": { - "debug": "^4.3.4", - "lodash": "^4.17.21" - } - }, - "cypress-parallel": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.14.0.tgz", - "integrity": "sha512-Lsh28G70vxjL0cjR820BdaVQHnGc17Vvb+tYmjbRPmfC+XEzwvUzhcaD0E1zCztBSYhw+b1/1JLmW4Y0qE/EDA==", - "dev": true, - "requires": { - "@colors/colors": "^1.5.0", - "cli-table3": "^0.6.0", - "cross-spawn": "^7.0.3", - "fs-extra": "^10.0.0", - "glob-escape": "^0.0.2", - "is-npm": "^5.0.0", - "lodash.camelcase": "^4.3.0", - "mocha": "~9.2.0", - "yargs": "15.3.1" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "dev": true - }, - "dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dev": true, - "requires": { - "character-entities": "^2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dmd": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.1.0.tgz", - "integrity": "sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "cache-point": "^2.0.0", - "common-sequence": "^2.0.2", - "file-set": "^4.0.2", - "handlebars": "^4.7.7", - "marked": "^4.0.12", - "object-get": "^2.1.1", - "reduce-flatten": "^3.0.1", - "reduce-unique": "^2.0.1", - "reduce-without": "^1.0.1", - "test-value": "^3.0.0", - "walk-back": "^5.1.0" - } - }, - "dprint": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/dprint/-/dprint-0.33.0.tgz", - "integrity": "sha512-VploASP7wL1HAYe5xWZKRwp8gW5zTdcG3Tb60DASv6QLnGKsl+OS+bY7wsXFrS4UcIbUNujXdsNG5FxBfRJIQg==", - "dev": true, - "requires": { - "yauzl": "=2.10.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "electron-to-chromium": { - "version": "1.4.304", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.304.tgz", - "integrity": "sha512-6c8M+ojPgDIXN2NyfGn8oHASXYnayj+gSEnGeLMKb9zjsySeVB/j7KkNAAG9yDcv8gNlhvFg5REa1N/kQU6pgA==", - "dev": true, - "peer": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - } - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "peer": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "peer": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "requires": { - "pify": "^2.2.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "factory.ts": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/factory.ts/-/factory.ts-0.5.2.tgz", - "integrity": "sha512-I4YDKuyMW+s2PocnWh/Ekv9wSStt/MNN1ZRb1qhy0Kv056ndlzbLHDsW9KEmTAqMpLI3BtjSqEdZ7ZfdnaXn9w==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "source-map-support": "^0.5.19" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "fetch-blob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz", - "integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-set": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz", - "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==", - "dev": true, - "requires": { - "array-back": "^5.0.0", - "glob": "^7.1.6" - }, - "dependencies": { - "array-back": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", - "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", - "dev": true - } - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "requires": { - "array-back": "^3.0.1" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-then-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", - "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "requires": { - "async": "^3.2.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-escape": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/glob-escape/-/glob-escape-0.0.2.tgz", - "integrity": "sha512-L/cXYz8x7qer1HAyUQ+mbjcUsJVdpRxpAf7CwqHoNBs9vTpABlGfNN4tzkDxt+u3Z7ZncVyKlCNPtzb0R/7WbA==", - "dev": true - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.4" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "jsdoc-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.1.1.tgz", - "integrity": "sha512-0pkuPCzVXiqsDAsVrNFXCkHzlyNepBIDVtwwehry4RJAnZmXtlAz7rh8F9FRz53u3NeynGbex+bpYWwi8lE66A==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "cache-point": "^2.0.0", - "collect-all": "^1.0.4", - "file-set": "^4.0.2", - "fs-then-native": "^2.0.0", - "jsdoc": "^3.6.10", - "object-to-spawn-args": "^2.0.1", - "temp-path": "^1.0.0", - "walk-back": "^5.1.0" - } - }, - "jsdoc-parse": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.1.0.tgz", - "integrity": "sha512-n/hDGQJa69IBun1yZAjqzV4gVR41+flZ3bIlm9fKvNe2Xjsd1/+zCo2+R9ls8LxtePgIWbpA1jU7xkB2lRdLLg==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "lodash.omit": "^4.5.0", - "lodash.pick": "^4.4.0", - "reduce-extract": "^1.0.0", - "sort-array": "^4.1.4", - "test-value": "^3.0.0" - } - }, - "jsdoc-to-markdown": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-7.1.1.tgz", - "integrity": "sha512-CI86d63xAVNO+ENumWwmJ034lYe5iGU5GwjtTA11EuphP9tpnoi4hrKgR/J8uME0D+o4KUpVfwX1fjZhc8dEtg==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "command-line-tool": "^0.8.0", - "config-master": "^3.1.0", - "dmd": "^6.1.0", - "jsdoc-api": "^7.1.1", - "jsdoc-parse": "^6.1.0", - "walk-back": "^5.1.0" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "peer": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsonld": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-5.2.0.tgz", - "integrity": "sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw==", - "dev": true, - "requires": { - "@digitalbazaar/http-client": "^1.1.0", - "canonicalize": "^1.0.1", - "lru-cache": "^6.0.0", - "rdf-canonize": "^3.0.0" - } - }, - "jsonld-checker": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/jsonld-checker/-/jsonld-checker-0.1.8.tgz", - "integrity": "sha512-jclmnPRrm5SEpaIV6IiSTJxplRAqIWHduQLsUfrYpZM41Ng48m1RN2/aUyHze/ynfO0D2UhlJBt8SdObsH5GBw==", - "dev": true, - "requires": { - "jsonld": "^5.2.0", - "node-fetch": "^2.6.1" - } - }, - "jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true - }, - "ky": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz", - "integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==", - "dev": true - }, - "ky-universal": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz", - "integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "node-fetch": "3.0.0-beta.9" - }, - "dependencies": { - "node-fetch": { - "version": "3.0.0-beta.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", - "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^2.1.1" - } - } - } - }, - "lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true - }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - } - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "peer": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "lodash.omit": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", - "dev": true - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, - "lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dev": true, - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "dev": true, - "requires": {} - }, - "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true - }, - "mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - } - }, - "mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromark": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", - "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", - "dev": true, - "requires": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", - "dev": true, - "requires": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", - "dev": true, - "requires": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", - "dev": true, - "requires": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", - "dev": true, - "requires": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", - "dev": true, - "requires": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", - "dev": true - }, - "micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", - "dev": true - }, - "micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", - "dev": true, - "requires": { - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-sanitize-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", - "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", - "dev": true, - "requires": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", - "dev": true - }, - "micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "minipass": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", - "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz", - "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==", - "dev": true - }, - "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - } - } - }, - "mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-get": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz", - "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-to-spawn-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", - "integrity": "sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "rdf-canonize": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-3.3.0.tgz", - "integrity": "sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.5" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "reduce-extract": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", - "integrity": "sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==", - "dev": true, - "requires": { - "test-value": "^1.0.1" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - }, - "test-value": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", - "integrity": "sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==", - "dev": true, - "requires": { - "array-back": "^1.0.2", - "typical": "^2.4.2" - } - } - } - }, - "reduce-flatten": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", - "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==", - "dev": true - }, - "reduce-unique": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz", - "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==", - "dev": true - }, - "reduce-without": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz", - "integrity": "sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==", - "dev": true, - "requires": { - "test-value": "^2.0.0" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - }, - "test-value": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", - "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==", - "dev": true, - "requires": { - "array-back": "^1.0.3", - "typical": "^2.6.0" - } - } - } - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "peer": true - }, - "remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - } - }, - "request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "requires": { - "mri": "^1.1.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", - "dev": true, - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "sort-array": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", - "integrity": "sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==", - "dev": true, - "requires": { - "array-back": "^5.0.0", - "typical": "^6.0.1" - }, - "dependencies": { - "array-back": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", - "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", - "dev": true - }, - "typical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", - "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stream-connect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", - "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", - "dev": true, - "requires": { - "array-back": "^1.0.2" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - } - } - }, - "stream-via": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", - "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "table-layout": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", - "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "deep-extend": "~0.6.0", - "lodash.padend": "^4.6.1", - "typical": "^2.6.1", - "wordwrapjs": "^3.0.0" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true - }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "temp-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", - "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==", - "dev": true - }, - "terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - } - } - }, - "test-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", - "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "peer": true - }, - "throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", - "dev": true - }, - "ts-mocha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", - "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", - "dev": true, - "requires": { - "ts-node": "7.0.1", - "tsconfig-paths": "^3.5.0" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "ts-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", - "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", - "dev": true, - "requires": { - "arrify": "^1.0.0", - "buffer-from": "^1.1.0", - "diff": "^3.1.0", - "make-error": "^1.1.1", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "source-map-support": "^0.5.6", - "yn": "^2.0.0" - } - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "optional": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", - "dev": true - } - } - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tsconfig-paths": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", - "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", - "dev": true, - "requires": { - "json5": "^2.2.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "txm": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/txm/-/txm-8.1.0.tgz", - "integrity": "sha512-mVDmoN13jYX3igNcnS+TEJJmMIRLjn0wch/wOI23z5IkCKiw9xinv1WkugB55j57W8MfuEk/psVVO4BWMfZxfA==", - "dev": true, - "requires": { - "async": "^3.2.1", - "diff-match-patch": "^1.0.5", - "kleur": "^4.1.4", - "remark-parse": "^10.0.1", - "supports-color": "^9.1.0", - "unified": "^10.1.1" - }, - "dependencies": { - "supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", - "dev": true - } - } - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typedoc": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", - "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", - "dev": true, - "requires": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.0", - "shiki": "^0.14.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "typedoc-plugin-markdown": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", - "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", - "dev": true, - "requires": { - "handlebars": "^4.7.7" - } - }, - "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", - "dev": true - }, - "typical": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", - "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", - "dev": true, - "optional": true - }, - "underscore": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", - "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", - "dev": true - }, - "unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" - } - }, - "unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "peer": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "dev": true, - "requires": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - } - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vfile": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", - "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - } - }, - "vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - } - }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "walk-back": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", - "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", - "dev": true - }, - "wasm-opt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.3.0.tgz", - "integrity": "sha512-24+IOboX4Sav0bI8Krwf0Y6dnpN4KxYtqpl0qWt86qVLsmayUqx1KMBrJTlQWNC+/dsqzQJjK6QvxNJsZYOgJg==", - "dev": true, - "requires": { - "node-fetch": "^2.6.7", - "tar": "^6.1.11" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "peer": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "webpack": { - "version": "5.76.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", - "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wordwrapjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", - "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", - "dev": true, - "requires": { - "reduce-flatten": "^1.0.1", - "typical": "^2.6.1" - }, - "dependencies": { - "reduce-flatten": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", - "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==", - "dev": true - } - } - }, - "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } From 9f892cd1093011c8e9c2b59179eccf6fa2f997f5 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:28:34 +0100 Subject: [PATCH 069/163] add binding for did jwk --- bindings/wasm/src/did/mod.rs | 1 + .../wasm/src/did/wasm_did_jwk_document_ext.rs | 106 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 bindings/wasm/src/did/wasm_did_jwk_document_ext.rs diff --git a/bindings/wasm/src/did/mod.rs b/bindings/wasm/src/did/mod.rs index ae2e89bc0c..797e71cdd5 100644 --- a/bindings/wasm/src/did/mod.rs +++ b/bindings/wasm/src/did/mod.rs @@ -7,6 +7,7 @@ mod service; mod wasm_core_did; mod wasm_core_document; mod wasm_did_url; +mod wasm_did_jwk_document_ext; pub use self::service::IService; pub use self::service::UServiceEndpoint; diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs new file mode 100644 index 0000000000..f544a9882a --- /dev/null +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -0,0 +1,106 @@ +use std::str::FromStr; +use std::rc::Rc; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jose::WasmJwk; +use identity_iota::storage::DidJwkDocumentExt; +use identity_iota::document::CoreDocument; +use identity_iota::storage::key_storage::KeyType; +use identity_iota::verification::jws::JwsAlgorithm; +use crate::storage::WasmStorageInner; +use crate::jose::WasmJwsAlgorithm; +use crate::storage::WasmStorage; +use super::CoreDocumentLock; +use super::WasmCoreDocument; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::future_to_promise; + +#[wasm_bindgen(js_class = CoreDocument)] +impl WasmCoreDocument { + + #[wasm_bindgen(js_name = newDidJwk)] + pub async fn _new_did_jwk( + storage: &WasmStorage, + key_type: String, + alg: WasmJwsAlgorithm, + //) -> Result<(WasmCoreDocument, String)>{ + ) -> Result{ + let storage_clone: Rc = storage.0.clone(); + let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; + + CoreDocument::new_did_jwk( + &storage_clone, + KeyType::from(key_type), + alg) + .await + .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) + .wasm_result() + } + + + + + + +} + + + +/* impl DidJwkDocumentExt for WasmCoreDocument{ + + + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage { + todo!() + } +/// a + #[cfg(feature = "pqc")] + async fn new_did_jwk_pqc( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> { + todo!() + } + + #[cfg(feature = "jpt-bbs-plus")] + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> StorageResult<(CoreDocument, String)>{ + todo!() + } + +/// a + #[cfg(feature = "hybrid")] + async fn new_did_compositejwk( + storage: &Storage, + alg: identity_verification::jwk::CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> { + todo!() + } +/// a + + + + + + pub async fn new_did_jwk( + storage: &WasmStorage, + key_type: String, + alg: WasmJwsAlgorithm, + //) -> Result<(WasmCoreDocument, String)>{ + ) -> Result{ + + + } +} + */ \ No newline at end of file From e989cf62ec344af8782e40da52fccc8c37706958 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:36:51 +0100 Subject: [PATCH 070/163] clean up warning --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 4 - bindings/wasm/src/storage/jwk_storage.rs | 1 - bindings/wasm/src/storage/jwk_storage_pqc.rs | 26 +- bindings/wasm/src/storage/mod.rs | 2 +- .../src/storage/did_jwk_document_ext.rs | 337 +++++++++--------- 5 files changed, 169 insertions(+), 201 deletions(-) diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index f544a9882a..35f43e724a 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -1,8 +1,6 @@ -use std::str::FromStr; use std::rc::Rc; use crate::error::Result; use crate::error::WasmResult; -use crate::jose::WasmJwk; use identity_iota::storage::DidJwkDocumentExt; use identity_iota::document::CoreDocument; use identity_iota::storage::key_storage::KeyType; @@ -13,8 +11,6 @@ use crate::storage::WasmStorage; use super::CoreDocumentLock; use super::WasmCoreDocument; use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::future_to_promise; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 8df839af52..b89c9462b6 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -10,7 +10,6 @@ use crate::jose::WasmJwk; use identity_iota::storage::key_storage::JwkGenOutput; use identity_iota::storage::key_storage::JwkStorage; -use identity_iota::storage::key_storage::JwkStoragePQ; use identity_iota::storage::key_storage::KeyId; use identity_iota::storage::key_storage::KeyStorageError; use identity_iota::storage::key_storage::KeyStorageErrorKind; diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index 59dd51ee95..ff0abd3db6 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -1,36 +1,15 @@ -use std::str::FromStr; - -use crate::error::Result as WasmResult; -use crate::error::WasmResult as _; -use crate::jose::WasmJwk; -use crate::jose::WasmJwsAlgorithm; - -use super::WasmJwkGenOutput; use super::WasmJwkStorage; -use super::WasmProofUpdateCtx; -use identity_iota::storage::bls::encode_bls_jwk; -use identity_iota::storage::bls::expand_bls_jwk; -use identity_iota::storage::bls::generate_bbs_keypair; -use identity_iota::storage::bls::sign_bbs; -use identity_iota::storage::bls::update_bbs_signature; use identity_iota::storage::JwkGenOutput; -use identity_iota::storage::JwkStorage; use identity_iota::storage::JwkStoragePQ; use identity_iota::storage::KeyId; -use identity_iota::storage::KeyStorageError; -use identity_iota::storage::KeyStorageErrorKind; use identity_iota::storage::KeyStorageResult; use identity_iota::storage::KeyType; -use identity_iota::storage::ProofUpdateCtx; use identity_iota::verification::jwk::Jwk; -use jsonprooftoken::jpa::algs::ProofAlgorithm; use wasm_bindgen::prelude::*; use identity_iota::verification::jose::jws::JwsAlgorithm; use crate::error::JsValueResult; -use js_sys::Array; use js_sys::Promise; -use js_sys::Uint8Array; use wasm_bindgen_futures::JsFuture; use super::jwk_storage::PromiseJwkGenOutput; @@ -52,15 +31,12 @@ impl JwkStoragePQ for WasmJwkStorage { result.into() } - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + async fn pq_sign(&self, _key_id: &KeyId, _data: &[u8], _public_key: &Jwk) -> KeyStorageResult> { todo!(); } - } - - #[wasm_bindgen(typescript_custom_section)] const JWK_STORAGE_PQ: &'static str = r#" /** Secure storage for cryptographic keys represented as JWKs. */ diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 493a9eb29c..a2cd4b664b 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -20,4 +20,4 @@ pub use key_id_storage::*; pub use method_digest::*; pub use signature_options::*; pub use wasm_storage::*; -pub use jwk_storage_pqc::*; +//pub use jwk_storage_pqc::*; diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 75efb33419..643e1e9496 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -8,8 +8,6 @@ use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentEr use super::{Storage, StorageResult}; - - /// Handle both DID Jwk and DID compositeJwk methods #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] @@ -60,193 +58,192 @@ pub trait DidJwkDocumentExt{ #[cfg_attr(feature = "send-sync-storage", async_trait)] impl DidJwkDocumentExt for CoreDocument { - async fn new_did_jwk( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage, - I: KeyIdStorage { - - let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage { + let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - async fn new_did_jwk_pqc( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStoragePQ, - I: KeyIdStorage { - - let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - async fn new_did_jwk_zk( - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorageBbsPlusExt, - I: KeyIdStorage { - let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; - async fn new_did_compositejwk( + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_pqc( storage: &Storage, - alg: CompositeAlgId, + key_type: KeyType, + alg: JwsAlgorithm, ) -> StorageResult<(CoreDocument, String)> where - K: JwkStorage + JwkStoragePQ, + K: JwkStoragePQ, I: KeyIdStorage { - let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { - CompositeAlgId::IdMldsa44Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_44, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - CompositeAlgId::IdMldsa65Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_65, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - }; - - let JwkGenOutput { - key_id: t_key_id, - jwk: t_jwk, - } = K::generate(storage.key_storage(), trad_key_type, trad_alg) - .await + + let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), + key_type, + alg + ).await .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - let JwkGenOutput { - key_id: pq_key_id, - jwk: pq_jwk, - } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) .await - .map_err(Error::KeyStorageError)?; - - let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); - - let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } - let b64 = encode_b64_json(&composite_pk) + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage { + let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let document = CoreDocument::expand_did_compositejwk(did) + + + let document = CoreDocument::expand_did_jwk(did) .map_err(|err| Error::EncodingError(Box::new(err)))?; - let fragment = "0"; - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_compositejwk( + storage: &Storage, + alg: CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage { + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { + CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_44, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_65, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + }; + + let JwkGenOutput { + key_id: t_key_id, + jwk: t_jwk, + } = K::generate(storage.key_storage(), trad_key_type, trad_alg) .await - .map_err(Error::KeyIdStorageError)?; + .map_err(Error::KeyStorageError)?; - Ok((document, fragment.to_string())) - } + let JwkGenOutput { + key_id: pq_key_id, + jwk: pq_jwk, + } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; + + let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + + let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); + + let b64 = encode_b64_json(&composite_pk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_compositejwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } } \ No newline at end of file From 106457b9e2813bab610a60094137e4fcc11faf0b Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:50:38 +0100 Subject: [PATCH 071/163] remove warning --- identity_jose/src/jws/algorithm.rs | 15 +++++++++++++-- identity_jose/src/jws/decoder.rs | 2 +- .../src/verification_method/method.rs | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index ffbff252f6..1c004fad5c 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -71,31 +71,42 @@ pub enum JwsAlgorithm { #[serde(rename = "SLH-DSA-SHA2-128f")] SLH_DSA_SHA2_128f, + ///SLH_DSA_SHAKE_128f #[serde(rename = "SLH-DSA-SHAKE-128f")] SLH_DSA_SHAKE_128f, + ///SLH_DSA_SHA2_192s #[serde(rename = "SLH-DSA-SHA2-192s")] SLH_DSA_SHA2_192s, + ///SLH_DSA_SHAKE_192s #[serde(rename = "SLH-DSA-SHAKE-192s")] SLH_DSA_SHAKE_192s, + ///SLH-DSA-SHA2-192f #[serde(rename = "SLH-DSA-SHA2-192f")] SLH_DSA_SHA2_192f, + ///SLH-DSA-SHAKE-192f #[serde(rename = "SLH-DSA-SHAKE-192f")] SLH_DSA_SHAKE_192f, + ///SLH-DSA-SHA2-256s #[serde(rename = "SLH-DSA-SHA2-256s")] SLH_DSA_SHA2_256s, + ///SLH-DSA-SHA2-256s #[serde(rename = "SLH-DSA-SHAKE-256s")] SLH_DSA_SHAKE_256s, + ///SLH-DSA-SHA2-256f #[serde(rename = "SLH-DSA-SHA2-256f")] SLH_DSA_SHA2_256f, + ///SLH-DSA-SHAKE-256f #[serde(rename = "SLH-DSA-SHAKE-256f")] SLH_DSA_SHAKE_256f, + ///FALCON512 FALCON512, + ///FALCON1024 FALCON1024, - + ///id-MLDSA44-Ed25519-SHA512 #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] IdMldsa44Ed25519Sha512, - + ///id-MLDSA65-Ed25519-SHA512 #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] IdMldsa65Ed25519Sha512, /// Custom algorithm diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index e67f11ebe5..fdae5b1ed6 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -179,7 +179,7 @@ impl<'a> JwsValidationItem<'a> { }) } - //TODO: hybrid - verify_hybrid + ///TODO: hybrid - verify_hybrid pub fn verify_hybrid( self, traditional_verifier: &TRV, diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 2c43d10558..eb468712d4 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -231,6 +231,20 @@ impl VerificationMethod { impl VerificationMethod { + // =========================================================================== + // Constructors + // =========================================================================== + + /// Creates a new [`VerificationMethod`] from the given `did` and [`CompositeJwk`]. If `fragment` is not given + /// the `kid` value of the given `key` will be used, if present, otherwise an error is returned. + /// + /// # Recommendations + /// The following recommendations are essentially taken from the `publicKeyJwk` description from + /// the [DID specification](https://www.w3.org/TR/did-core/#dfn-publickeyjwk): + /// - It is recommended that verification methods that use [`CompositeJwk`](CompositeJwk) to represent their public keys use the value + /// of `kid` as their fragment identifier. This is done automatically if `None` is passed in as the fragment. + /// - It is recommended that [`CompositeJwk`] kid values are set to the public key fingerprint. See + /// [`CompositeJwk::thumbprint_sha256_b64`](CompositeJwk::thumbprint_sha256_b64). pub fn new_from_compositejwk(did: D, key: CompositeJwk, fragment: Option<&str>) -> Result { // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) let composite_fragment = key.traditional_public_key() From 374a79735a770e9efd188df3783b01f3b56ac621 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:12:22 +0100 Subject: [PATCH 072/163] add fragment fn --- .../src/wallet/did_jwk_traditional.ts | 3 +- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 85 +++++++++---------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts index eb7f07bb49..34653cfadb 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts @@ -24,10 +24,11 @@ export async function createDidJwk(){ // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwk( + const document: CoreDocument = await CoreDocument.newDidJwk( storage, JwkMemStore.ed25519KeyType(), JwsAlgorithm.EdDSA,) console.log(JSON.stringify(document, null, 2)); + console.log("fragment" + document.fragmentJwk()); } diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 35f43e724a..6e480ff95c 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -14,57 +14,54 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { - - #[wasm_bindgen(js_name = newDidJwk)] - pub async fn _new_did_jwk( - storage: &WasmStorage, - key_type: String, - alg: WasmJwsAlgorithm, - //) -> Result<(WasmCoreDocument, String)>{ - ) -> Result{ - let storage_clone: Rc = storage.0.clone(); - let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; - - CoreDocument::new_did_jwk( - &storage_clone, - KeyType::from(key_type), - alg) - .await - .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) - .wasm_result() - } - - - - + #[wasm_bindgen(js_name = newDidJwk)] + pub async fn _new_did_jwk( + storage: &WasmStorage, + key_type: String, + alg: WasmJwsAlgorithm, + ) -> Result{ + let storage_clone: Rc = storage.0.clone(); + let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; + CoreDocument::new_did_jwk( + &storage_clone, + KeyType::from(key_type), + alg + ).await + .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) + .wasm_result() + } + + +/* #[cfg(feature = "pqc")] + async fn _new_did_jwk_pqc( + storage: &WasmStorage, + key_type: String, + alg: WasmJwsAlgorithm, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; + CoreDocument::new_did_jwk_pqc( + &storage_clone, + KeyType::from(key_type), + alg + ).await + .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) + .wasm_result() + + } */ + + #[wasm_bindgen(js_name = fragmentJwk)] + pub fn _fragment(self) -> String { + "0".to_string() + } } - - /* impl DidJwkDocumentExt for WasmCoreDocument{ - async fn new_did_jwk( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage, - I: KeyIdStorage { - todo!() - } -/// a - #[cfg(feature = "pqc")] - async fn new_did_jwk_pqc( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> { - todo!() - } + #[cfg(feature = "jpt-bbs-plus")] async fn new_did_jwk_zk( From f10ce34b542eafac406d925090d46559e17ec1b0 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:20:33 +0100 Subject: [PATCH 073/163] more cleanup --- .../src/storage/did_jwk_document_ext.rs | 390 +++++++++--------- 1 file changed, 195 insertions(+), 195 deletions(-) diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 643e1e9496..b6b5ddf7ee 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -8,56 +8,12 @@ use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentEr use super::{Storage, StorageResult}; -/// Handle both DID Jwk and DID compositeJwk methods +/// Extension trait for creating JWK-based DID documents for traditional, zk, PQ and hybrid keys #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait DidJwkDocumentExt{ -/// a - async fn new_did_jwk( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage, - I: KeyIdStorage; -/// a - #[cfg(feature = "pqc")] - async fn new_did_jwk_pqc( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStoragePQ, - I: KeyIdStorage; -/// a - #[cfg(feature = "jpt-bbs-plus")] - async fn new_did_jwk_zk( - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorageBbsPlusExt, - I: KeyIdStorage; - -/// a - #[cfg(feature = "hybrid")] - async fn new_did_compositejwk( - storage: &Storage, - alg: identity_verification::jwk::CompositeAlgId, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage; -} - - -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -impl DidJwkDocumentExt for CoreDocument { +/// Create a JWK-based DID documents with traditional keys. Returns the DID document and the fragment async fn new_did_jwk( storage: &Storage, key_type: KeyType, @@ -65,40 +21,9 @@ impl DidJwkDocumentExt for CoreDocument { ) -> StorageResult<(CoreDocument, String)> where K: JwkStorage, - I: KeyIdStorage { - let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } - + I: KeyIdStorage; +/// Create a JWK-based DID documents with PQ keys. Returns the DID document and the fragment + #[cfg(feature = "pqc")] async fn new_did_jwk_pqc( storage: &Storage, key_type: KeyType, @@ -106,41 +31,9 @@ impl DidJwkDocumentExt for CoreDocument { ) -> StorageResult<(CoreDocument, String)> where K: JwkStoragePQ, - I: KeyIdStorage { - - let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } - + I: KeyIdStorage; +/// Create a JWK-based DID documents with zk keys. Returns the DID document and the fragment + #[cfg(feature = "jpt-bbs-plus")] async fn new_did_jwk_zk( storage: &Storage, key_type: KeyType, @@ -148,38 +41,144 @@ impl DidJwkDocumentExt for CoreDocument { ) -> StorageResult<(CoreDocument, String)> where K: JwkStorageBbsPlusExt, - I: KeyIdStorage { - let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + I: KeyIdStorage; + +/// Create a JWK-based DID documents with hybrid keys. Returns the DID document and the fragment + #[cfg(feature = "hybrid")] + async fn new_did_compositejwk( + storage: &Storage, + alg: identity_verification::jwk::CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage; +} + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl DidJwkDocumentExt for CoreDocument { - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage + { + let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_pqc( + + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStoragePQ, + I: KeyIdStorage + + { - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; + let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; - Ok((document, fragment.to_string())) + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage + { + let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) } async fn new_did_compositejwk( @@ -188,62 +187,63 @@ impl DidJwkDocumentExt for CoreDocument { ) -> StorageResult<(CoreDocument, String)> where K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage { - let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { - CompositeAlgId::IdMldsa44Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_44, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - CompositeAlgId::IdMldsa65Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_65, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - }; - - let JwkGenOutput { - key_id: t_key_id, - jwk: t_jwk, - } = K::generate(storage.key_storage(), trad_key_type, trad_alg) - .await - .map_err(Error::KeyStorageError)?; - - let JwkGenOutput { - key_id: pq_key_id, - jwk: pq_jwk, - } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) - .await - .map_err(Error::KeyStorageError)?; - - let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); - - let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); - - let b64 = encode_b64_json(&composite_pk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + I: KeyIdStorage + { + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { + CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_44, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_65, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + }; + + let JwkGenOutput { + key_id: t_key_id, + jwk: t_jwk, + } = K::generate(storage.key_storage(), trad_key_type, trad_alg) + .await + .map_err(Error::KeyStorageError)?; + + let JwkGenOutput { + key_id: pq_key_id, + jwk: pq_jwk, + } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; + + let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); - let document = CoreDocument::expand_did_compositejwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); - let fragment = "0"; + let b64 = encode_b64_json(&composite_pk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; + let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_compositejwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; - Ok((document, fragment.to_string())) + Ok((document, fragment.to_string())) } } \ No newline at end of file From 752907f3ae97fafab1d1dd25d3b13f591d14f82c Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:08:00 +0100 Subject: [PATCH 074/163] add pq stprage for examples --- bindings/wasm/lib/jwk_storage.ts | 140 ++++++++++++++++++++++++------- bindings/wasm/package.json | 1 + 2 files changed, 112 insertions(+), 29 deletions(-) diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 3afcfebe0e..6c9d12310d 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -1,11 +1,13 @@ import * as ed from "@noble/ed25519"; -import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx } from "~identity_wasm"; +import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx, JwkStoragePQ } from "~identity_wasm"; import { EdCurve, JwkType, JwsAlgorithm } from "./jose"; +import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; +//import { } from "../node/jose"; type Ed25519PrivateKey = Uint8Array; type Ed25519PublicKey = Uint8Array; -export class JwkMemStore implements JwkStorage { +export class JwkMemStore implements JwkStorage, JwkStoragePQ{ /** The map from key identifiers to Jwks. */ private _keys: Map; @@ -14,10 +16,48 @@ export class JwkMemStore implements JwkStorage { this._keys = new Map(); } + public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { + console.log("Inside generatePQ"); + if (keyType !== JwkMemStore.mldsaKeyType()) { + throw new Error(`unsupported key type ${keyType}`); + } + + const seed = new TextEncoder().encode(randomKeyId()) + let keys; + if (algorithm === JwsAlgorithm.MLDSA44) { + keys = ml_dsa44.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA65) { + keys = ml_dsa65.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA87) { + keys = ml_dsa87.keygen(seed); + } else { + throw new Error(`unsupported algorithm`); + } + + const keyId = randomKeyId(); + const jwk = await encodeJwk(keys.secretKey, keys.publicKey, algorithm); + + if(jwk == undefined) + throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); + + this._keys.set(keyId, jwk); + + const publicJWK = jwk?.toPublic(); + if (!publicJWK) { + throw new Error(`JWK is not a public key`); + } + return new JwkGenOutput(keyId, publicJWK); + + } + public static ed25519KeyType(): string { return "Ed25519"; } + public static mldsaKeyType(): string { + return "ML-DSA"; + } + private _get_key(keyId: string): Jwk | undefined { return this._keys.get(keyId); } @@ -33,7 +73,11 @@ export class JwkMemStore implements JwkStorage { const keyId = randomKeyId(); const privKey: Ed25519PrivateKey = ed.utils.randomPrivateKey(); - const jwk = await encodeJwk(privKey, algorithm); + const publicKey = await ed.getPublicKey(privKey); + + const jwk = await encodeJwk(privKey, publicKey, algorithm); + if(jwk == undefined) + throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); this._keys.set(keyId, jwk); @@ -46,7 +90,7 @@ export class JwkMemStore implements JwkStorage { } public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { - if (publicKey.alg() !== JwsAlgorithm.EdDSA) { + if (!publicKey.alg() || publicKey.alg() !== JwsAlgorithm.EdDSA) { throw new Error("unsupported JWS algorithm"); } else { if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) { @@ -91,42 +135,80 @@ export class JwkMemStore implements JwkStorage { public count(): number { return this._keys.size; } + + } // Encodes a Ed25519 keypair into a Jwk. -async function encodeJwk(privateKey: Ed25519PrivateKey, alg: JwsAlgorithm): Promise { - const publicKey = await ed.getPublicKey(privateKey); - let x = encodeB64(publicKey); - let d = encodeB64(privateKey); - - return new Jwk({ - "kty": JwkType.Okp, - "crv": "Ed25519", - d, - x, - alg, - }); +async function encodeJwk( + privateKey: Uint8Array, + publicKey: Uint8Array, + alg: JwsAlgorithm +): Promise { + // const publicKey = await ed25519.getPublicKey(privateKey); + console.log("Inside encodeJwk"); + const x = encodeB64(publicKey); + const d = encodeB64(privateKey); + + if (alg === JwsAlgorithm.EdDSA) { + return new Jwk({ + kty: JwkType.Okp, + crv: "Ed25519", + d, + x, + alg, + }); + } else if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.MLDSA87) { + return new Jwk({ + "kty": JwkType.MLDSA, + pub: x, + priv: d, + alg, + }); + } + + return undefined; } -function decodeJwk(jwk: Jwk): [Ed25519PrivateKey, Ed25519PublicKey] { - if (jwk.alg() !== JwsAlgorithm.EdDSA) { +function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { + if (jwk.alg()! !== JwsAlgorithm.EdDSA) { throw new Error("unsupported `alg`"); } - const paramsOkp = jwk.paramsOkp(); - if (paramsOkp) { - const d = paramsOkp.d; - - if (d) { - let textEncoder = new TextEncoder(); - const privateKey = decodeB64(textEncoder.encode(d)); - const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); - return [privateKey, publicKey]; + if (jwk.alg()! === JwsAlgorithm.EdDSA) { + const paramsOkp = jwk.paramsOkp(); + if (paramsOkp) { + const d = paramsOkp.d; + + if (d) { + const textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(d)); + const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } } else { - throw new Error("missing private key component"); + throw new Error("expected Okp params"); + } + } else if (jwk.alg()! === JwsAlgorithm.MLDSA44 || jwk.alg()! === JwsAlgorithm.MLDSA65 || jwk.alg()! === JwsAlgorithm.MLDSA87) { + const paramsPQ = jwk.paramsMldsa(); + if (paramsPQ) { + const priv = paramsPQ.priv; + + if (priv) { + const textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(priv)); + const publicKey = decodeB64(textEncoder.encode(paramsPQ.pub)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } + } else { + throw new Error("expected Okp params"); } } else { - throw new Error("expected Okp params"); + throw new Error("unsupported `alg`"); } } diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 085abe22a7..004f2f2292 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -78,6 +78,7 @@ }, "dependencies": { "@noble/ed25519": "^1.7.3", + "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "node-fetch": "^2.6.7" From c783e09cc946f6daf64fa96a268a35b9b8b33fd2 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:08:49 +0100 Subject: [PATCH 075/163] bindings for new_did_jwk_pqc --- .../wasm/examples/src/wallet/did_jwk_pq.ts | 6 +++--- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 19 ++++++------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts index 26561d56fc..c9e9861a3a 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts @@ -22,10 +22,10 @@ export async function createDidJwkPq(){ }; const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwk( + const document = await CoreDocument.newDidJwkPq( storage, - JwkMemStore.ed25519KeyType(), - JwsAlgorithm.EdDSA,) + JwkMemStore.mldsaKeyType(), + JwsAlgorithm.MLDSA44) console.log(JSON.stringify(document, null, 2)); } diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 6e480ff95c..66f0e001de 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -12,6 +12,8 @@ use super::CoreDocumentLock; use super::WasmCoreDocument; use wasm_bindgen::prelude::*; +//use crate::jpt::WasmProofAlgorithm; + #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { @@ -32,9 +34,8 @@ impl WasmCoreDocument { .wasm_result() } - -/* #[cfg(feature = "pqc")] - async fn _new_did_jwk_pqc( + #[wasm_bindgen(js_name = newDidJwkPq)] + pub async fn _new_did_jwk_pqc( storage: &WasmStorage, key_type: String, alg: WasmJwsAlgorithm, @@ -48,8 +49,7 @@ impl WasmCoreDocument { ).await .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) .wasm_result() - - } */ + } #[wasm_bindgen(js_name = fragmentJwk)] pub fn _fragment(self) -> String { @@ -63,14 +63,7 @@ impl WasmCoreDocument { - #[cfg(feature = "jpt-bbs-plus")] - async fn new_did_jwk_zk( - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - ) -> StorageResult<(CoreDocument, String)>{ - todo!() - } + /// a #[cfg(feature = "hybrid")] From 1bee110a6bf272c6c49dbac6e735feb34af90c63 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:48:27 +0100 Subject: [PATCH 076/163] bindings for new_did_compositejwk --- .../examples/src/wallet/did_jwk_hybrid.ts | 11 ++--- bindings/wasm/lib/jose/composite_jwk.ts | 8 ++++ bindings/wasm/lib/jose/index.ts | 1 + .../wasm/src/did/wasm_did_jwk_document_ext.rs | 47 ++++++++++++++++++- bindings/wasm/src/jose/types.rs | 2 + bindings/wasm/src/lib.rs | 2 +- 6 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 bindings/wasm/lib/jose/composite_jwk.ts diff --git a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts index 69b4b6bb5a..a5693dd26f 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 import { - IotaDID, + CompositeAlgId, IotaDocument, IotaIdentityClient, JwkMemStore, @@ -17,17 +17,12 @@ import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; /** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ export async function createDidJwkHybrid(){ - const mnemonicSecretManager: MnemonicSecretManager = { - mnemonic: Utils.generateMnemonic(), - }; - // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwk( + const document = await CoreDocument.newDidCompositeJwk( storage, - JwkMemStore.ed25519KeyType(), - JwsAlgorithm.EdDSA,) + CompositeAlgId.IdMldsa44Ed25519Sha512) console.log(JSON.stringify(document, null, 2)); } diff --git a/bindings/wasm/lib/jose/composite_jwk.ts b/bindings/wasm/lib/jose/composite_jwk.ts new file mode 100644 index 0000000000..f66cbbb864 --- /dev/null +++ b/bindings/wasm/lib/jose/composite_jwk.ts @@ -0,0 +1,8 @@ +export const enum CompositeAlgId { + /** DER encoded value in hex = 060B6086480186FA6B50080103 */ + IdMldsa44Ed25519Sha512 = "id-MLDSA44-Ed25519-SHA512", + + /** /// DER encoded value in hex = 060B6086480186FA6B5008010A */ + IdMldsa65Ed25519Sha512 = "id-MLDSA65-Ed25519-SHA512" + +} \ No newline at end of file diff --git a/bindings/wasm/lib/jose/index.ts b/bindings/wasm/lib/jose/index.ts index f033c4f110..44c7c2c890 100644 --- a/bindings/wasm/lib/jose/index.ts +++ b/bindings/wasm/lib/jose/index.ts @@ -4,3 +4,4 @@ export * from "./jwk_operation"; export * from "./jwk_type"; export * from "./jwk_use"; export * from "./jws_algorithm"; +export * from "./composite_jwk"; \ No newline at end of file diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 66f0e001de..f01595de60 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -5,8 +5,10 @@ use identity_iota::storage::DidJwkDocumentExt; use identity_iota::document::CoreDocument; use identity_iota::storage::key_storage::KeyType; use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::jwk::CompositeAlgId; use crate::storage::WasmStorageInner; use crate::jose::WasmJwsAlgorithm; +use crate::jose::WasmCompositeAlgId; use crate::storage::WasmStorage; use super::CoreDocumentLock; use super::WasmCoreDocument; @@ -49,7 +51,50 @@ impl WasmCoreDocument { ).await .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) .wasm_result() - } + } + + + +/* pub enum CompositeAlgId { + /// DER encoded value in hex = 060B6086480186FA6B50080103 + #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] + IdMldsa44Ed25519Sha512, + /// DER encoded value in hex = 060B6086480186FA6B5008010A + #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] + IdMldsa65Ed25519Sha512, + } + + impl CompositeAlgId { + /// Returns the JWS algorithm as a `str` slice. + pub const fn name(self) -> &'static str { + match self { + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", + Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", + } + } + } */ + + + + #[wasm_bindgen(js_name = newDidCompositeJwk)] + pub async fn _new_did_compositejwk( + storage: &WasmStorage, + //alg: identity_verification::jwk::CompositeAlgId, + alg: WasmCompositeAlgId + ) -> Result{ + let storage_clone: Rc = storage.0.clone(); + let alg: CompositeAlgId = alg.into_serde().wasm_result()?; + CoreDocument::new_did_compositejwk( + &storage_clone, + alg + ).await + .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) + .wasm_result() + } + + + + #[wasm_bindgen(js_name = fragmentJwk)] pub fn _fragment(self) -> String { diff --git a/bindings/wasm/src/jose/types.rs b/bindings/wasm/src/jose/types.rs index 76d693bed3..8786677387 100644 --- a/bindings/wasm/src/jose/types.rs +++ b/bindings/wasm/src/jose/types.rs @@ -27,6 +27,8 @@ extern "C" { pub type WasmJwkParamsOct; #[wasm_bindgen(typescript_type = "JwkParamsPQ")] pub type WasmJwkParamsMLDSA; + #[wasm_bindgen(typescript_type = "CompositeAlgId")] + pub type WasmCompositeAlgId; } impl TryFrom for JwsAlgorithm { diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index cf8344925a..0fec7dcfd6 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -43,5 +43,5 @@ pub fn start() -> Result<(), JsValue> { // It appears the import path must be relative to `src`. #[wasm_bindgen(typescript_custom_section)] const CUSTOM_IMPORTS: &'static str = r#" -import { JwsAlgorithm, JwkOperation, JwkUse, JwkType } from '../lib/jose/index'; +import { JwsAlgorithm, JwkOperation, JwkUse, JwkType, CompositeAlgId} from '../lib/jose/index'; "#; From 489489a42f8140abd095a0873e5c588b5641e068 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:44:31 +0100 Subject: [PATCH 077/163] binding for new_did_jwk_zk --- .../wasm/examples/src/wallet/did_jwk_zk.ts | 12 +-- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 78 ++++--------------- 2 files changed, 21 insertions(+), 69 deletions(-) diff --git a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts index d16e315e6a..9a0c6fc356 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts @@ -3,7 +3,7 @@ import { IotaDID, IotaDocument, - IotaIdentityClient, + ProofAlgorithm, JwkMemStore, JwsAlgorithm, KeyIdMemStore, @@ -17,17 +17,13 @@ import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; /** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ export async function createDidJwkZk(){ - const mnemonicSecretManager: MnemonicSecretManager = { - mnemonic: Utils.generateMnemonic(), - }; - // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwk( + const document = await CoreDocument.newDidJwkZk( storage, - JwkMemStore.ed25519KeyType(), - JwsAlgorithm.EdDSA,) + ProofAlgorithm.BLS12381_SHA256, + ) console.log(JSON.stringify(document, null, 2)); } diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index f01595de60..6a742ed9db 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -14,7 +14,8 @@ use super::CoreDocumentLock; use super::WasmCoreDocument; use wasm_bindgen::prelude::*; -//use crate::jpt::WasmProofAlgorithm; +use crate::jpt::WasmProofAlgorithm; +use jsonprooftoken::jpa::algs::ProofAlgorithm; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { @@ -53,33 +54,9 @@ impl WasmCoreDocument { .wasm_result() } - - -/* pub enum CompositeAlgId { - /// DER encoded value in hex = 060B6086480186FA6B50080103 - #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] - IdMldsa44Ed25519Sha512, - /// DER encoded value in hex = 060B6086480186FA6B5008010A - #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] - IdMldsa65Ed25519Sha512, - } - - impl CompositeAlgId { - /// Returns the JWS algorithm as a `str` slice. - pub const fn name(self) -> &'static str { - match self { - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", - Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", - } - } - } */ - - - #[wasm_bindgen(js_name = newDidCompositeJwk)] pub async fn _new_did_compositejwk( storage: &WasmStorage, - //alg: identity_verification::jwk::CompositeAlgId, alg: WasmCompositeAlgId ) -> Result{ let storage_clone: Rc = storage.0.clone(); @@ -92,9 +69,21 @@ impl WasmCoreDocument { .wasm_result() } - - - + #[wasm_bindgen(js_name = newDidJwkZk)] + pub async fn _new_did_jwk_zk( + storage: &WasmStorage, + alg: WasmProofAlgorithm, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let alg: ProofAlgorithm = alg.into(); + CoreDocument::new_did_jwk_zk( + &storage_clone, + KeyType::from_static_str("BLS12381"), + alg + ).await + .map(|doc| WasmCoreDocument(Rc::new(CoreDocumentLock::new(doc.0)))) + .wasm_result() + } #[wasm_bindgen(js_name = fragmentJwk)] pub fn _fragment(self) -> String { @@ -102,36 +91,3 @@ impl WasmCoreDocument { } } - -/* impl DidJwkDocumentExt for WasmCoreDocument{ - - - - - - -/// a - #[cfg(feature = "hybrid")] - async fn new_did_compositejwk( - storage: &Storage, - alg: identity_verification::jwk::CompositeAlgId, - ) -> StorageResult<(CoreDocument, String)> { - todo!() - } -/// a - - - - - - pub async fn new_did_jwk( - storage: &WasmStorage, - key_type: String, - alg: WasmJwsAlgorithm, - //) -> Result<(WasmCoreDocument, String)>{ - ) -> Result{ - - - } -} - */ \ No newline at end of file From 63d4a5369360e60f59e47f298c09a87c38d468fd Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:39:19 +0100 Subject: [PATCH 078/163] update demos --- examples/demo/hybrid.rs | 48 +++++++++++++++---------------- examples/demo/pq.rs | 50 +++++++++++++++------------------ examples/demo/traditional.rs | 48 +++++++++++++++---------------- examples/demo/traditional_zk.rs | 34 ++++++++++++++-------- 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index 9665bcc3f2..9fd4108fd4 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::{collections::HashMap, fs::File, path::Path}; use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_eddsa_verifier::EdDSAJwsVerifier; @@ -60,11 +63,11 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} ","[Issuer]".red(), ": Construct VC"); + println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -82,11 +85,15 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); + + println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - println!("{} {}", "[Holder]".blue(), ": Validate VC"); + println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); + + println!("{} {}", "[Holder]".blue(), ": Verify VC"); JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) .validate::<_, Object>( @@ -96,13 +103,15 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); - println!("{} {}", "[Holder]".blue(), ": Construct VP"); + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); let presentation: Presentation =PresentationBuilder::new( alice_document.id().to_url().into(), @@ -117,31 +126,21 @@ async fn main() -> anyhow::Result<()> { &JwtPresentationOptions::default().expiration_date(expires), ).await?; - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. + println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_compositejwk_handler(); - // Resolve the holder's document. let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); - // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( @@ -149,7 +148,6 @@ async fn main() -> anyhow::Result<()> { PQCJwsVerifier::default(), ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; - // Concurrently resolve the issuers' documents. let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; let mut resolver_web: Resolver = Resolver::new(); @@ -161,14 +159,12 @@ async fn main() -> anyhow::Result<()> { .collect::, _>>()?; let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - // Validate the credentials in the presentation. let credential_validator: JwtCredentialValidatorHybrid = JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; let _decoded_credential: DecodedJwtCredential = credential_validator @@ -176,7 +172,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + println!("{}: VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/pq.rs b/examples/demo/pq.rs index 1b8382f25a..e0881936e7 100644 --- a/examples/demo/pq.rs +++ b/examples/demo/pq.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::{collections::HashMap, fs::File, path::Path}; use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; @@ -45,7 +48,7 @@ async fn main() -> anyhow::Result<()> { let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_pqc( &storage_alice, JwkMemStore::ML_DSA_KEY_TYPE, - JwsAlgorithm::ML_DSA_87 + JwsAlgorithm::ML_DSA_44 ).await?; println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); @@ -60,11 +63,11 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} ","[Issuer]".red(), ": Construct VC"); + println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -81,11 +84,15 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); + + println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - println!("{} {}", "[Holder]".blue(), ": Validate VC"); + println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); + + println!("{} {}", "[Holder]".blue(), ": Verify VC"); JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) .validate::<_, Object>( @@ -95,13 +102,15 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); - println!("{} {}", "[Holder]".blue(), ": Construct VP"); + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); let presentation: Presentation =PresentationBuilder::new( alice_document.id().to_url().into(), @@ -116,31 +125,21 @@ async fn main() -> anyhow::Result<()> { &JwtPresentationOptions::default().expiration_date(expires), ).await?; - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. + println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); - // Resolve the holder's document. let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); - // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( @@ -148,7 +147,6 @@ async fn main() -> anyhow::Result<()> { ) .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - // Concurrently resolve the issuers' documents. let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; let mut resolver_web: Resolver = Resolver::new(); @@ -160,14 +158,12 @@ async fn main() -> anyhow::Result<()> { .collect::, _>>()?; let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - // Validate the credentials in the presentation. let credential_validator: JwtCredentialValidator = JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; let _decoded_credential: DecodedJwtCredential = credential_validator @@ -175,7 +171,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + println!("{}: VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index 2f4171ac3c..c99104a26e 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::{collections::HashMap, fs::File, path::Path}; use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_eddsa_verifier::EdDSAJwsVerifier; @@ -60,11 +63,11 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} ","[Issuer]".red(), ": Construct VC"); + println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -81,11 +84,15 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JWT):", credential_jwt.as_str()); + println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); + + println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - println!("{} {}", "[Holder]".blue(), ": Validate VC"); + println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); + + println!("{} {}", "[Holder]".blue(), ": Verify VC"); JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) .validate::<_, Object>( @@ -95,14 +102,16 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - println!("{} {}", "[Holder]".blue(), ": Construct VP"); - let presentation: Presentation = PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) .credential(credential_jwt) @@ -118,31 +127,21 @@ async fn main() -> anyhow::Result<()> { ) .await?; - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending VP (as JWT):", presentation_jwt.as_str()); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. + println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and verifies the Verifiable Presentation", "[Verifier]".green()); + println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); - // Resolve the holder's document. let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; let holder: CoreDocument = resolver.resolve(&holder_did).await?; let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); - // Validate presentation. Note that this doesn't validate the included credentials. let presentation_validation_options = JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); @@ -151,7 +150,6 @@ async fn main() -> anyhow::Result<()> { ) .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - // Concurrently resolve the issuers' documents. let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; let mut resolver_web: Resolver = Resolver::new(); @@ -163,14 +161,12 @@ async fn main() -> anyhow::Result<()> { .collect::, _>>()?; let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - // Validate the credentials in the presentation. let credential_validator: JwtCredentialValidator = JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; let _decoded_credential: DecodedJwtCredential = credential_validator @@ -178,7 +174,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green()); + println!("{}: VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index 29225efaa7..e6b858f535 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::{fs::File, path::Path}; use examples::{MemStorage, DID_URL, PATH_DID_FILE}; use identity_iota::{core::{FromJson, Object, Url}, credential::{Credential, CredentialBuilder, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, SelectiveDisclosurePresentation, Subject}, did::{CoreDID, DID}, document::CoreDocument, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwpDocumentExt, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; @@ -59,11 +62,13 @@ async fn main() -> anyhow::Result<()> { "GPA": "4.0", }))?; - println!("{} {} {}", "[Holder]".blue(), ": Inserted Credential subject information: ", serde_json::to_string_pretty(&subject)?); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); + + println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {} {}", "[Holder]".blue(), " <->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - println!("{} {} ","[Issuer]".red(), ": Construct VC"); + println!("{} {} ","[Issuer]".red(), ": Generate VC"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) @@ -84,6 +89,8 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); + println!("{} {}", "[Holder]".blue(), ": Validate VC"); let decoded_jpt = JptCredentialValidator::validate::<_, Object>( @@ -93,11 +100,15 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{} {}", "[Verifier]".green(), "-> [Holder]: Send challenge"); + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access with Selective Disclosure of VC attributes"); let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - println!("{}: Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); + + println!("{} : Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); let method_id = decoded_jpt .decoded_jwp @@ -107,10 +118,12 @@ async fn main() -> anyhow::Result<()> { let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); selective_disclosure_presentation - .conceal_in_subject("degree.name") + .conceal_in_subject("GPA") .unwrap(); - println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and construct the Presentation JPT"); + selective_disclosure_presentation.conceal_in_subject("name").unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded)"); let presentation_jpt: Jpt = issuer_document .create_presentation_jpt( @@ -120,9 +133,9 @@ async fn main() -> anyhow::Result<()> { ) .await?; - println!("{} {} {} {}", "[Holder]".blue(), " -> [Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); + println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); - println!("{}: Resolve Issuer's DID and verifies the Presentation JPT","[Verifier]".green()); + println!("{}: Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); let mut resolver_web: Resolver = Resolver::new(); let _ = resolver_web.attach_web_handler(client)?; @@ -132,7 +145,6 @@ async fn main() -> anyhow::Result<()> { let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); - // Verifier validate the Presented Credential and retrieve the JwpPresented let _decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( &presentation_jpt, &issuer_document, @@ -140,7 +152,7 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{}: Presentation JPT successfully verified", "[Verifier]".green()); + println!("{}: JPT successfully verified, access granted", "[Verifier]".green()); Ok(()) } From d0915e1c0c2b14a906ab88b2b7ff83f6eacc1b6c Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:34:51 +0100 Subject: [PATCH 079/163] add license or modification license --- examples/1_advanced/12_pq.rs | 4 +- examples/1_advanced/13_hybrid.rs | 4 +- examples/1_advanced/14_did_web.rs | 3 + examples/demo/server/src/main.rs | 2 + examples/utils/utils.rs | 3 + .../jwt_credential_validator_hybrid.rs | 3 + .../jwt_presentation_validator_hybrid.rs | 3 + identity_did/src/did.rs | 1 - identity_did/src/did_compositejwk.rs | 2 +- identity_did/src/did_web.rs | 9 +- identity_did/src/lib.rs | 4 + .../src/document/core_document.rs | 10 +- identity_jose/src/jwk/composite_jwk.rs | 3 +- identity_jose/src/jwk/jwk_pq.rs | 4 + identity_jose/src/jwk/key.rs | 4 + identity_jose/src/jwk/key_params.rs | 5 +- identity_jose/src/jwk/key_type.rs | 4 + identity_jose/src/jwk/mod.rs | 4 + identity_jose/src/jws/algorithm.rs | 19 +- identity_jose/src/jws/decoder.rs | 6 +- identity_pqc_verifier/src/lib.rs | 3 + identity_pqc_verifier/src/oqs_verifier.rs | 3 + identity_pqc_verifier/src/pqc_verifier.rs | 3 + identity_resolver/src/resolution/resolver.rs | 4 + .../src/key_id_storage/method_digest.rs | 5 +- .../src/key_storage/jwk_storage_pqc.rs | 2 + identity_storage/src/key_storage/memstore.rs | 4 + identity_storage/src/key_storage/mod.rs | 4 + .../src/storage/did_jwk_document_ext.rs | 454 +++++++++--------- .../src/storage/hybrid_jws_document_ext.rs | 3 + identity_storage/src/storage/mod.rs | 4 + .../src/storage/pqc_jws_document_ext.rs | 4 +- identity_verification/src/error.rs | 5 +- .../src/verification_method/material.rs | 5 +- .../src/verification_method/method.rs | 10 + 35 files changed, 359 insertions(+), 251 deletions(-) diff --git a/examples/1_advanced/12_pq.rs b/examples/1_advanced/12_pq.rs index 0992971930..24de5c14b6 100644 --- a/examples/1_advanced/12_pq.rs +++ b/examples/1_advanced/12_pq.rs @@ -1,5 +1,7 @@ -use std::collections::HashMap; +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; diff --git a/examples/1_advanced/13_hybrid.rs b/examples/1_advanced/13_hybrid.rs index 632b1534d6..2057482727 100644 --- a/examples/1_advanced/13_hybrid.rs +++ b/examples/1_advanced/13_hybrid.rs @@ -1,5 +1,7 @@ -use std::collections::HashMap; +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; use examples::get_address_with_funds; use examples::random_stronghold_path; use examples::MemStorage; diff --git a/examples/1_advanced/14_did_web.rs b/examples/1_advanced/14_did_web.rs index 727be0cd4f..25eed51cdc 100644 --- a/examples/1_advanced/14_did_web.rs +++ b/examples/1_advanced/14_did_web.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::{collections::HashMap, fs::File, path::Path}; use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; diff --git a/examples/demo/server/src/main.rs b/examples/demo/server/src/main.rs index 563102b66d..8c333538f8 100644 --- a/examples/demo/server/src/main.rs +++ b/examples/demo/server/src/main.rs @@ -1,3 +1,5 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 use warp::Filter; diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index d63b2580e5..4b110162aa 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ use std::path::PathBuf; diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs index 7b68591ef0..a3c85f2e5d 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use identity_core::convert::FromJson; use identity_did::CoreDID; use identity_did::DIDUrl; diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs index 6da78fb11f..d50031a870 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use identity_core::common::Object; use identity_core::common::Timestamp; use identity_core::common::Url; diff --git a/identity_did/src/did.rs b/identity_did/src/did.rs index 6899df3156..de9cf61183 100644 --- a/identity_did/src/did.rs +++ b/identity_did/src/did.rs @@ -160,7 +160,6 @@ impl CoreDID { /// Checks if the given `did` is valid according to the base [`DID`] specification. pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> { - println!("did: {}", did); // Validate basic DID constraints. Self::valid_method_name(did.method())?; Self::valid_method_id(did.method_id())?; diff --git a/identity_did/src/did_compositejwk.rs b/identity_did/src/did_compositejwk.rs index ac09ddaeef..4689351287 100644 --- a/identity_did/src/did_compositejwk.rs +++ b/identity_did/src/did_compositejwk.rs @@ -1,4 +1,4 @@ -// Copyright 2020-2024 IOTA Stiftung +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 use std::fmt::Debug; diff --git a/identity_did/src/did_web.rs b/identity_did/src/did_web.rs index f1217faa81..072bd6f93a 100644 --- a/identity_did/src/did_web.rs +++ b/identity_did/src/did_web.rs @@ -1,4 +1,5 @@ -//TODO: Web - WebDID +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 use std::{fmt::{Display, Formatter}, str::FromStr}; @@ -41,9 +42,7 @@ impl WebDID { }); let did_web = format!("did:{}:{}{}{}", Self::METHOD, domain, port, path); - println!("DID Web: {}", did_web); let core_did = CoreDID::parse(did_web).map_err(|_| Error::Other("Cannot convert to CoreDID"))?; - println!("{}",core_did); Ok(Self(core_did)) } else { @@ -139,9 +138,7 @@ impl WebDID { Ok(url) } - // did:web:cybersecurity-links.github.io%3A3000:did-web-server:.well-known:did.json - - /// cybersecurity-links.github.io%3A3000:did-web-server:.well-known:did.json -> https:://cybersecurity-links.github.io:3000/did-web-server/.well-known/did.json + /// example.github.io%3A3000:did-web-server:.well-known:did.json -> https:://example.github.io:3000/did-web-server/.well-known/did.json #[inline(always)] fn denormalized_components(input: &str) -> (String, Option, Option) { diff --git a/identity_did/src/lib.rs b/identity_did/src/lib.rs index 9b07857312..1dc5285bcf 100644 --- a/identity_did/src/lib.rs +++ b/identity_did/src/lib.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + #![forbid(unsafe_code)] #![doc = include_str!("./../README.md")] #![allow(clippy::upper_case_acronyms)] diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 302013e7c1..34c53b9044 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::convert::TryInto as _; use core::fmt::Display; use core::fmt::Formatter; @@ -37,7 +41,6 @@ use identity_verification::MethodRelationship; use identity_verification::MethodScope; use identity_verification::VerificationMethod; - #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[rustfmt::skip] pub(crate) struct CoreDocumentData @@ -1052,8 +1055,6 @@ impl CoreDocument { } } - -//TODO: expand_composite_jwk impl CoreDocument { /// Creates a [`CoreDocument`] from a did:jwk DID. pub fn expand_did_compositejwk(did_compositejwk: DIDCompositeJwk) -> Result { @@ -1071,9 +1072,8 @@ impl CoreDocument { } } -//TODO: Web - impl CoreDocument (WebDID) -/// DID web impl CoreDocument { + /// Creates a [`CoreDocument`] from a url following the did:web method. pub fn new_from_url(url: &str) -> Result{ let id = WebDID::new(url).map_err(|_| Error::InvalidDocument("Invalid DID Web", None))?; let document: CoreDocument = CoreDocument::builder(Object::default()) diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index 90001516cb..b92138d8e9 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -1,4 +1,5 @@ -//TODO: hybrid - composite public key +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 use crate::jwk::Jwk; diff --git a/identity_jose/src/jwk/jwk_pq.rs b/identity_jose/src/jwk/jwk_pq.rs index ee29f4cf2b..b97f00594a 100644 --- a/identity_jose/src/jwk/jwk_pq.rs +++ b/identity_jose/src/jwk/jwk_pq.rs @@ -1,3 +1,7 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + + // ============================================================================= // Post-quantum algorithm key parameters // ============================================================================= diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index 97112ef536..fddde6b412 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use crypto::hashes::sha::SHA256; use crypto::hashes::sha::SHA256_LEN; use identity_core::common::Url; diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index 07d743c767..36d4ccfd24 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use zeroize::Zeroize; use super::BlsCurve; @@ -29,7 +33,6 @@ pub enum JwkParams { /// Octet Key Pairs parameters. Okp(JwkParamsOkp), - //TODO: PQ - new JwkParams /// ML-DSA parameters MLDSA(JwkParamsPQ), /// SLH-DSA parameters diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index 9af2d9cb15..e753a0c994 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::fmt::Display; use core::fmt::Formatter; use core::fmt::Result; diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index 9392176bdd..4c96589276 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + //! JSON Web Keys ([JWK](https://tools.ietf.org/html/rfc7517)) mod curve; diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index ffbff252f6..599e3bbf48 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::fmt::Display; use core::fmt::Formatter; use core::fmt::Result; @@ -71,31 +75,42 @@ pub enum JwsAlgorithm { #[serde(rename = "SLH-DSA-SHA2-128f")] SLH_DSA_SHA2_128f, + ///SLH_DSA_SHAKE_128f #[serde(rename = "SLH-DSA-SHAKE-128f")] SLH_DSA_SHAKE_128f, + ///SLH_DSA_SHA2_192s #[serde(rename = "SLH-DSA-SHA2-192s")] SLH_DSA_SHA2_192s, + ///SLH_DSA_SHAKE_192s #[serde(rename = "SLH-DSA-SHAKE-192s")] SLH_DSA_SHAKE_192s, + ///SLH-DSA-SHA2-192f #[serde(rename = "SLH-DSA-SHA2-192f")] SLH_DSA_SHA2_192f, + ///SLH-DSA-SHAKE-192f #[serde(rename = "SLH-DSA-SHAKE-192f")] SLH_DSA_SHAKE_192f, + ///SLH-DSA-SHA2-256s #[serde(rename = "SLH-DSA-SHA2-256s")] SLH_DSA_SHA2_256s, + ///SLH-DSA-SHA2-256s #[serde(rename = "SLH-DSA-SHAKE-256s")] SLH_DSA_SHAKE_256s, + ///SLH-DSA-SHA2-256f #[serde(rename = "SLH-DSA-SHA2-256f")] SLH_DSA_SHA2_256f, + ///SLH-DSA-SHAKE-256f #[serde(rename = "SLH-DSA-SHAKE-256f")] SLH_DSA_SHAKE_256f, + ///FALCON512 FALCON512, + ///FALCON1024 FALCON1024, - + ///id-MLDSA44-Ed25519-SHA512 #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] IdMldsa44Ed25519Sha512, - + ///id-MLDSA65-Ed25519-SHA512 #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] IdMldsa65Ed25519Sha512, /// Custom algorithm diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index e67f11ebe5..7ef55d02a9 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::str; use std::borrow::Cow; use std::ops::Deref; @@ -179,7 +183,7 @@ impl<'a> JwsValidationItem<'a> { }) } - //TODO: hybrid - verify_hybrid + ///Hybrid verify pub fn verify_hybrid( self, traditional_verifier: &TRV, diff --git a/identity_pqc_verifier/src/lib.rs b/identity_pqc_verifier/src/lib.rs index 8e69ba856e..a4cbc5cbed 100644 --- a/identity_pqc_verifier/src/lib.rs +++ b/identity_pqc_verifier/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + mod oqs_verifier; mod pqc_verifier; diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 193d14c3dc..e85db05c29 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use identity_jose::jwk::Jwk; use identity_jose::jwk::JwkParamsPQ; use identity_jose::jws::SignatureVerificationError; diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 905ec373cd..5d8bcb3e9c 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use identity_jose::jwk::Jwk; use identity_jose::jws::JwsAlgorithm; use identity_jose::jws::JwsVerifier; diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 3d1a39ba25..707b5bd5f8 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::future::Future; use futures::stream::FuturesUnordered; use futures::TryStreamExt; diff --git a/identity_storage/src/key_id_storage/method_digest.rs b/identity_storage/src/key_id_storage/method_digest.rs index f4ce3dde1b..262843d5d7 100644 --- a/identity_storage/src/key_id_storage/method_digest.rs +++ b/identity_storage/src/key_id_storage/method_digest.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use identity_core::convert::ToJson; use identity_verification::MethodData; use identity_verification::VerificationMethod; @@ -57,7 +61,6 @@ impl MethodDigest { match method_data { MethodData::PublicKeyJwk(jwk) => hasher.write(jwk.thumbprint_sha256().as_ref()), - // MethodData::Custom(e) => hasher.write(&e.to_json_vec().unwrap()), //TODO: Hybrid - to be changed MethodData::CompositeJwk(composite) => { let algid = composite .alg_id() diff --git a/identity_storage/src/key_storage/jwk_storage_pqc.rs b/identity_storage/src/key_storage/jwk_storage_pqc.rs index 0f9192cc39..3d52304e44 100644 --- a/identity_storage/src/key_storage/jwk_storage_pqc.rs +++ b/identity_storage/src/key_storage/jwk_storage_pqc.rs @@ -1,3 +1,5 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 use crate::key_storage::KeyId; use crate::key_storage::KeyType; diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 3016b3cfae..58236434a5 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung, Fondazione Links // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::fmt::Debug; use std::collections::HashMap; use std::fmt::Display; diff --git a/identity_storage/src/key_storage/mod.rs b/identity_storage/src/key_storage/mod.rs index b5f86a57b9..90cd700ecb 100644 --- a/identity_storage/src/key_storage/mod.rs +++ b/identity_storage/src/key_storage/mod.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + //! A Key Storage is used to securely store private keys. //! //! This module provides the [`JwkStorage`] trait that diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 75efb33419..abc27c704c 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use identity_did::{DIDCompositeJwk, DIDJwk}; use identity_document::document::CoreDocument; use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json}; @@ -8,245 +11,242 @@ use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentEr use super::{Storage, StorageResult}; - - -/// Handle both DID Jwk and DID compositeJwk methods +/// Extension trait for creating JWK-based DID documents for traditional, zk, PQ and hybrid keys #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait DidJwkDocumentExt{ -/// a - async fn new_did_jwk( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage, - I: KeyIdStorage; -/// a - #[cfg(feature = "pqc")] - async fn new_did_jwk_pqc( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStoragePQ, - I: KeyIdStorage; -/// a - #[cfg(feature = "jpt-bbs-plus")] - async fn new_did_jwk_zk( - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorageBbsPlusExt, - I: KeyIdStorage; - -/// a - #[cfg(feature = "hybrid")] - async fn new_did_compositejwk( - storage: &Storage, - alg: identity_verification::jwk::CompositeAlgId, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage; -} - -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -impl DidJwkDocumentExt for CoreDocument { +/// Create a JWK-based DID documents with traditional keys. Returns the DID document and the fragment + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage; +/// Create a JWK-based DID documents with PQ keys. Returns the DID document and the fragment + #[cfg(feature = "pqc")] + async fn new_did_jwk_pqc( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStoragePQ, + I: KeyIdStorage; +/// Create a JWK-based DID documents with zk keys. Returns the DID document and the fragment + #[cfg(feature = "jpt-bbs-plus")] + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage; - async fn new_did_jwk( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorage, - I: KeyIdStorage { - - let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } - - async fn new_did_jwk_pqc( - storage: &Storage, - key_type: KeyType, - alg: JwsAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStoragePQ, - I: KeyIdStorage { - - let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } - - async fn new_did_jwk_zk( - storage: &Storage, - key_type: KeyType, - alg: ProofAlgorithm, - ) -> StorageResult<(CoreDocument, String)> - where - K: JwkStorageBbsPlusExt, - I: KeyIdStorage { - let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), - key_type, - alg - ).await - .map_err(Error::KeyStorageError)?; - - let b64 = encode_b64_json(&jwk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let document = CoreDocument::expand_did_jwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; - - Ok((document, fragment.to_string())) - } - - async fn new_did_compositejwk( +/// Create a JWK-based DID documents with hybrid keys. Returns the DID document and the fragment + #[cfg(feature = "hybrid")] + async fn new_did_compositejwk( storage: &Storage, - alg: CompositeAlgId, + alg: identity_verification::jwk::CompositeAlgId, ) -> StorageResult<(CoreDocument, String)> where K: JwkStorage + JwkStoragePQ, - I: KeyIdStorage { - let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { - CompositeAlgId::IdMldsa44Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_44, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - CompositeAlgId::IdMldsa65Ed25519Sha512 => ( - KeyType::from_static_str("ML-DSA"), - JwsAlgorithm::ML_DSA_65, - KeyType::from_static_str("Ed25519"), - JwsAlgorithm::EdDSA, - ), - }; - - let JwkGenOutput { - key_id: t_key_id, - jwk: t_jwk, - } = K::generate(storage.key_storage(), trad_key_type, trad_alg) - .await - .map_err(Error::KeyStorageError)?; - - let JwkGenOutput { - key_id: pq_key_id, - jwk: pq_jwk, - } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) - .await - .map_err(Error::KeyStorageError)?; - - let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + I: KeyIdStorage; +} + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl DidJwkDocumentExt for CoreDocument { - let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); + async fn new_did_jwk( + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage, + I: KeyIdStorage + { + let JwkGenOutput { key_id, jwk } = K::generate(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_pqc( + + storage: &Storage, + key_type: KeyType, + alg: JwsAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStoragePQ, + I: KeyIdStorage + + { + + let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_jwk_zk( + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage + { + let JwkGenOutput { key_id, jwk } = K::generate_bbs(storage.key_storage(), + key_type, + alg + ).await + .map_err(Error::KeyStorageError)?; + + let b64 = encode_b64_json(&jwk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDJwk::parse(&("did:jwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let document = CoreDocument::expand_did_jwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; + + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; + + Ok((document, fragment.to_string())) + } + + async fn new_did_compositejwk( + storage: &Storage, + alg: CompositeAlgId, + ) -> StorageResult<(CoreDocument, String)> + where + K: JwkStorage + JwkStoragePQ, + I: KeyIdStorage + { + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { + CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_44, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + KeyType::from_static_str("ML-DSA"), + JwsAlgorithm::ML_DSA_65, + KeyType::from_static_str("Ed25519"), + JwsAlgorithm::EdDSA, + ), + }; + + let JwkGenOutput { + key_id: t_key_id, + jwk: t_jwk, + } = K::generate(storage.key_storage(), trad_key_type, trad_alg) + .await + .map_err(Error::KeyStorageError)?; + + let JwkGenOutput { + key_id: pq_key_id, + jwk: pq_jwk, + } = K::generate_pq_key(storage.key_storage(), pq_key_type, pq_alg) + .await + .map_err(Error::KeyStorageError)?; + + let key_id = KeyId::new(format!("{}~{}", t_key_id.as_str(), pq_key_id.as_str())); + + let composite_pk = CompositeJwk::new(alg, t_jwk, pq_jwk); + + let b64 = encode_b64_json(&composite_pk) + .map_err(|err| Error::EncodingError(Box::new(err)))?; + + let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - let b64 = encode_b64_json(&composite_pk) - .map_err(|err| Error::EncodingError(Box::new(err)))?; - - let did = DIDCompositeJwk::parse(&("did:compositejwk:".to_string() + &b64)) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + let document = CoreDocument::expand_did_compositejwk(did) + .map_err(|err| Error::EncodingError(Box::new(err)))?; - let document = CoreDocument::expand_did_compositejwk(did) - .map_err(|err| Error::EncodingError(Box::new(err)))?; + let fragment = "0"; + + let verification_method = document.resolve_method(fragment, None) + .ok_or(identity_verification::Error::MissingIdFragment) + .map_err(Error::VerificationMethodConstructionError)?; - let fragment = "0"; - - let verification_method = document.resolve_method(fragment, None) - .ok_or(identity_verification::Error::MissingIdFragment) - .map_err(Error::VerificationMethodConstructionError)?; - - let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; - - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) - .await - .map_err(Error::KeyIdStorageError)?; + let method_digest = MethodDigest::new(verification_method) + .map_err(|err| Error::MethodDigestConstructionError(err))?; + + I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + .await + .map_err(Error::KeyIdStorageError)?; - Ok((document, fragment.to_string())) - } + Ok((document, fragment.to_string())) + } } \ No newline at end of file diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 654c1ea797..568d1594d1 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::ops::Deref; use super::JwkStorageDocumentError as Error; diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index 5c7f2f2a8c..e8ec970a5a 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + //! This module provides a type wrapping a key and key id storage. mod error; diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index fd68bf1b4d..ddeb64866b 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use super::JwkStorageDocumentError as Error; use crate::key_id_storage::MethodDigest; use crate::try_undo_key_generation; @@ -127,7 +130,6 @@ impl JwsDocumentExtPQC for CoreDocument { K: JwkStoragePQ, I: KeyIdStorage, { - // todo!() generate_method_core_document(self, storage, key_type, alg, fragment, scope).await } diff --git a/identity_verification/src/error.rs b/identity_verification/src/error.rs index 8f0e0b1685..dedc53d1b0 100644 --- a/identity_verification/src/error.rs +++ b/identity_verification/src/error.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + //! Errors that may occur when working with Decentralized Identifiers. /// Alias for a [`Result`][::core::result::Result] with the error type [Error]. @@ -39,7 +43,6 @@ pub enum Error { /// Caused by key material that is not a JSON Web Key. #[error("verification material format is not publicKeyJwk")] NotPublicKeyJwk, - //TODO: hybrid - new error /// Caused by key material that is not a Composite Public Key. #[error("verification material format is not compositePublicKey")] NotCompositePublicKey, diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 982cb3754b..478695db72 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use crate::jose::jwk::Jwk; use identity_jose::jwk::CompositeJwk; use core::fmt::Debug; @@ -72,7 +76,6 @@ impl MethodData { } } - //TODO: hybrid - return CompositePublicKey /// Returns the wrapped `CompositePublicKey` if the format is [`MethodData::CompositePublicKey`]. pub fn composite_public_key(&self) -> Option<&CompositeJwk> { if let Self::CompositeJwk(ref c) = self { diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 2c43d10558..59cc34e6de 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use core::fmt::Display; use core::fmt::Formatter; use std::borrow::Cow; @@ -231,6 +235,12 @@ impl VerificationMethod { impl VerificationMethod { + // =========================================================================== + // Constructors + // =========================================================================== + + /// Creates a new [`VerificationMethod`] from the given `did` and [`CompositeJwk`]. If `fragment` is not given + /// the `kid` value of the given `key` will be used, if present, otherwise an error is returned. pub fn new_from_compositejwk(did: D, key: CompositeJwk, fragment: Option<&str>) -> Result { // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) let composite_fragment = key.traditional_public_key() From 09ff6ea33289460ca48d526fe2e9df8af3a0c479 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:56:47 +0100 Subject: [PATCH 080/163] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5f1606fb72..15be3ad7d1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,3 @@ -# Zero-Knowledge (ZK) - -The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: - -* **BBS+ Signature Scheme**: This scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library, allowing for secure and privacy-preserving credential management. -* **JSON Web Proof Representation**: The [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. - -For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). - # PQ/T Hybrid This repository extends the IOTA Identity framework by implementing both **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) Hybrid** cryptographic approaches. These approaches address emerging threats to traditional cryptography posed by quantum computing. @@ -29,3 +20,12 @@ To test the above functionalities, you can refer to practical code snippets avai > pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; > ``` Make sure your server is set up before running the examples to avoid any configuration issues. + +# Zero-Knowledge (ZK) + +The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: + +* **BBS+ Signature Scheme**: This scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library, allowing for secure and privacy-preserving credential management. +* **JSON Web Proof Representation**: The [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. + +For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). From 748683ee57481acb862a4c359bfd3cedc178a5f7 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:59:33 +0100 Subject: [PATCH 081/163] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15be3ad7d1..abf2dc712a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# PQ/T Hybrid -This repository extends the IOTA Identity framework by implementing both **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) Hybrid** cryptographic approaches. These approaches address emerging threats to traditional cryptography posed by quantum computing. +# Post-Quantum/Traditional (PQ/T) hybrid VCs +This repository extends IOTA Identity by implementing both pure **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) hybrid** VCs with a crypto-agility approach. ### Overview From 4e879033ae0d79cc80282d3e09445cefb25e8f26 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:29:30 +0100 Subject: [PATCH 082/163] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index abf2dc712a..93f6403613 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -# Post-Quantum/Traditional (PQ/T) hybrid VCs -This repository extends IOTA Identity by implementing both pure **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) hybrid** VCs with a crypto-agility approach. +# Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid signatures for VCs +This repository extends IOTA Identity by implementing both pure **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) hybrid** signatures and JWT encoding for VCs with a crypto-agility approach. ### Overview -1. **PQ Approach**: To transition to quantum-resistant cryptography, the framework has been updated to support selected PQ signature algorithms, such as [**ML-DSA**](https://csrc.nist.gov/pubs/fips/204/final), [**SLH-DSA**](https://csrc.nist.gov/pubs/fips/205/final) and [**FALCON**](https://falcon-sign.info/). The implementation of these algorithms is provided by [**liboqs**](https://github.com/open-quantum-safe/liboqs-rust). +1. **PQ Signatures**: IOTA Identity extends its support for selected PQ signature algorithms, such as [ML-DSA](https://csrc.nist.gov/pubs/fips/204/final), [SLH-DSA](https://csrc.nist.gov/pubs/fips/205/final) and [FALCON](https://falcon-sign.info/). The implementation of these algorithms is provided by [liboqs](https://github.com/open-quantum-safe/liboqs-rust). -2. **PQ/T Hybrid Approach**: To mitigate risks associated with the relative immaturity of certain PQ algorithms, the PQ/T Hybrid combines a PQ algorithm with a traditional one in a composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. - - **Hybrid Signatures and Composite Key**: In the PQ/T Hybrid approach, both PQ and traditional keys are managed and verified using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of keys within the DID document. This setup enforces the non-separability of signatures, protecting against stripping attacks. - - **Supported Algorithms**: Currently, there are two supported algorithms: **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512**. The first combines ML-DSA-44 with Ed25519, while the second combines ML-DSA-65 with Ed25519. +2. **PQ/T hybrid Signatures**: mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), IOTA Identity extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. + - **Supported Algorithms**: Currently, the implmentation supports **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512** algorithms. The first combines ML-DSA-44 with Ed25519 signatures, while the second combines ML-DSA-65 with Ed25519 signatures. # Examples From ea2ea1677b08a4ab58e14ff799929c84f2a6dbaa Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:41:50 +0100 Subject: [PATCH 083/163] Update README.md --- README.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 93f6403613..8ab25af982 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,24 @@ This repository extends IOTA Identity by implementing both pure **Post-Quantum ( 1. **PQ Signatures**: IOTA Identity extends its support for selected PQ signature algorithms, such as [ML-DSA](https://csrc.nist.gov/pubs/fips/204/final), [SLH-DSA](https://csrc.nist.gov/pubs/fips/205/final) and [FALCON](https://falcon-sign.info/). The implementation of these algorithms is provided by [liboqs](https://github.com/open-quantum-safe/liboqs-rust). 2. **PQ/T hybrid Signatures**: mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), IOTA Identity extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. - - **Supported Algorithms**: Currently, the implmentation supports **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512** algorithms. The first combines ML-DSA-44 with Ed25519 signatures, while the second combines ML-DSA-65 with Ed25519 signatures. -# Examples +```json +"compositeJwk": { + "algId": ".. composite key OID ..", + "pqPublicKey": { + ".. PQ JWK encoded key .." + }, + "traditionalPublicKey": { + ".. Traditional JWK encoded key .." + } +} +``` -To test the above functionalities, you can refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. -> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/). To run these examples, you must -> have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, -> ensure that the following variables in `utils.rs` are correctly set to point to your server instance: -> ```rust -> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; -> ``` -Make sure your server is set up before running the examples to avoid any configuration issues. +**Supported Algorithms**: Currently, the implmentation supports **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512** algorithms. The first combines ML-DSA-44 with Ed25519 signatures, while the second combines ML-DSA-65 with Ed25519 signatures. + +# did:compositejwk + +Refer to [did:compositejwk](https://github.com/Cybersecurity-LINKS/zkryptium) specification for all details. # Zero-Knowledge (ZK) @@ -28,3 +33,16 @@ The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks * **JSON Web Proof Representation**: The [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). + +# Examples + +To test all the above functionalities, refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. +> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/) for the Issuer. To run these examples, you must +> have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, +> ensure that the following variables in `utils.rs` are correctly set to point to your server instance: +> ```rust +> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; +> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; +> ``` +Make sure your server is set up before running the examples to avoid any configuration issues. + From 09ca21f26499120423d66cf894496dd6bf3f2134 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:07:04 +0100 Subject: [PATCH 084/163] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8ab25af982..f2878c483b 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,18 @@ This repository extends IOTA Identity by implementing both pure **Post-Quantum ( # did:compositejwk -Refer to [did:compositejwk](https://github.com/Cybersecurity-LINKS/zkryptium) specification for all details. +The transition to PQC is a delicate and lengthy process. Today, the Distributed Ledger Technologies (DLT) that underpin decentralised identity are not yet quantum-secure, so this repository extends the IOTA Identity library with a new DID method called `did:compositejwk` for Holders to use PQ/T hybrid signatures. Refer to [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) specification for the details. + +**Note**: this repository also extends the existing `did:jwk` method to deal with pure PQ keys and signatures (ML-DSA, SLH-DSA and FALCON), and adds a simple `did:web` method for the Issuers. # Zero-Knowledge (ZK) -The IOTA Identity Framework now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: +The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: -* **BBS+ Signature Scheme**: This scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library, allowing for secure and privacy-preserving credential management. -* **JSON Web Proof Representation**: The [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. +* **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. +* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. -For more details on the implementation and how to use these features, you can find the full documentation [here](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). +For more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). # Examples From ea21e9146b505b8955e3c1911e89072b73922878 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:08:06 +0100 Subject: [PATCH 085/163] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2878c483b..0b85328940 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The transition to PQC is a delicate and lengthy process. Today, the Distributed The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: * **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. -* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof specification, enabling verifiable claims with selective disclosure. +* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure. For more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). From 13be77c4210b3d2d5f338d95ff52c03bbabb9d0a Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:11:29 +0100 Subject: [PATCH 086/163] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b85328940..400a9ad4cd 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [in * **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. * **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure. -For more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). +**Note**: the BBS+ signature scheme uses traditional cryptography, hence it is not quantum-secure; for more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). # Examples From 6e9c561150cae749bd0d501d8b9b068e9a914176 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:07:24 +0100 Subject: [PATCH 087/163] typo --- examples/demo/hybrid.rs | 2 +- examples/demo/traditional_zk.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index 9fd4108fd4..1d90fb842c 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -67,7 +67,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} {}", "[Holder]".blue(), " <-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {}", "[Holder]".blue(), "<-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); let credential: Credential = CredentialBuilder::default() .id(Url::parse("https://example.edu/credentials/3732")?) diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index e6b858f535..358a08b2da 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -66,7 +66,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - println!("{} {} {} {}", "[Holder]".blue(), " <->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); println!("{} {} ","[Issuer]".red(), ": Generate VC"); From 0d067066a31349312db6c05e121ebf10b6bf3004 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:02:08 +0100 Subject: [PATCH 088/163] print --- examples/demo/traditional_zk.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index 358a08b2da..70a3fdc64b 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -108,7 +108,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); - println!("{} : Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + println!("{} : Resolve Issuer's Public Key to compute the Signature Proof of Knowledge", "[Holder]".blue()); let method_id = decoded_jpt .decoded_jwp @@ -116,6 +116,8 @@ async fn main() -> anyhow::Result<()> { .kid() .unwrap(); + println!("{} : Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); selective_disclosure_presentation .conceal_in_subject("GPA") From 39c7969694c7b1859b35caab91b4cb9ae6a2b874 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:58:30 +0100 Subject: [PATCH 089/163] typo --- examples/demo/hybrid.rs | 4 ++-- examples/demo/pq.rs | 4 ++-- examples/demo/traditional.rs | 4 ++-- examples/demo/traditional_zk.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/demo/hybrid.rs b/examples/demo/hybrid.rs index 1d90fb842c..6cd6975777 100644 --- a/examples/demo/hybrid.rs +++ b/examples/demo/hybrid.rs @@ -130,7 +130,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); + println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_compositejwk_handler(); @@ -172,7 +172,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: VP successfully verified, access granted", "[Verifier]".green()); + println!("{} : VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/pq.rs b/examples/demo/pq.rs index e0881936e7..b2ccb4f60e 100644 --- a/examples/demo/pq.rs +++ b/examples/demo/pq.rs @@ -129,7 +129,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); + println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); @@ -171,7 +171,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: VP successfully verified, access granted", "[Verifier]".green()); + println!("{} : VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/traditional.rs b/examples/demo/traditional.rs index c99104a26e..ebd0e743a7 100644 --- a/examples/demo/traditional.rs +++ b/examples/demo/traditional.rs @@ -131,7 +131,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - println!("{}: Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); + println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); let mut resolver: Resolver = Resolver::new(); resolver.attach_did_jwk_handler(); @@ -174,7 +174,7 @@ async fn main() -> anyhow::Result<()> { .unwrap(); } - println!("{}: VP successfully verified, access granted", "[Verifier]".green()); + println!("{} : VP successfully verified, access granted", "[Verifier]".green()); Ok(()) } diff --git a/examples/demo/traditional_zk.rs b/examples/demo/traditional_zk.rs index 70a3fdc64b..e2de9dab65 100644 --- a/examples/demo/traditional_zk.rs +++ b/examples/demo/traditional_zk.rs @@ -137,7 +137,7 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); - println!("{}: Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); + println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); let mut resolver_web: Resolver = Resolver::new(); let _ = resolver_web.attach_web_handler(client)?; @@ -154,7 +154,7 @@ async fn main() -> anyhow::Result<()> { FailFast::FirstError, ).unwrap(); - println!("{}: JPT successfully verified, access granted", "[Verifier]".green()); + println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); Ok(()) } From ea13ea69c46e70e70c710e5f2d5b6198c201e982 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:58:47 +0100 Subject: [PATCH 090/163] Add revocation zk example --- examples/Cargo.toml | 5 + examples/demo/revocation_zk.rs | 409 +++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 examples/demo/revocation_zk.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index db85317c0c..8fd1340bb9 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -138,3 +138,8 @@ name = "hybrid" path = "demo/traditional_zk.rs" name = "traditional_zk" +[[example]] +path = "demo/revocation_zk.rs" +name = "revocation_zk" + + diff --git a/examples/demo/revocation_zk.rs b/examples/demo/revocation_zk.rs new file mode 100644 index 0000000000..a592cf44d1 --- /dev/null +++ b/examples/demo/revocation_zk.rs @@ -0,0 +1,409 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::{fs::File, path::Path}; +use examples::{MemStorage, DID_URL, PATH_DID_FILE}; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtPresentation, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptCredentialValidatorUtils, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, Jwt, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, RevocationBitmap, RevocationDocumentExt, RevocationTimeframeStatus, SelectiveDisclosurePresentation, Status, StatusCheck, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDUrl, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument, Service}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExt, JwkMemStore, JwpDocumentExt, JwsSignatureOptions, KeyIdMemstore, TimeframeRevocationExtension}, verification::{jws::JwsAlgorithm, MethodScope}}; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use reqwest::ClientBuilder; +use serde_json::json; +use colored::Colorize; +use std::time::Duration as SleepDuration; +use std::thread; + +pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { + let path = Path::new(path.unwrap_or_else(|| "did.json")); + let file = File::create(path)?; + serde_json::to_writer_pretty(file, doc)?; + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let binding = DID_URL.to_owned() + "did_zk.json"; + let did_url: &str = binding.as_str(); + let binding = PATH_DID_FILE.to_owned() + "did_zk.json"; + let path_did_file: &str = binding.as_str(); + + println!("{} {} {}", "[Issuer]".red(), ": Create DID with the Revocationbitmap (with did:web method) and publish the DID Document at", did_url); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + let fragment_issuer = issuer_document.generate_method_jwp( + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + None, + MethodScope::VerificationMethod, + ).await?; + + let revocation_bitmap_issuer: RevocationBitmap = RevocationBitmap::new(); + + let service_id: DIDUrl = issuer_document.id().to_url().join("#my-revocation-service")?; + let service: Service = revocation_bitmap_issuer.to_service(service_id)?; + + issuer_document.insert_service(service).unwrap(); + + write_to_file(&issuer_document, Some(path_did_file))?; + + let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (mut alice_document, fragment_alice) = CoreDocument::new_did_jwk( + &storage_alice, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA + ).await?; + + let revocation_bitmap_holder: RevocationBitmap = RevocationBitmap::new(); + + let service_id: DIDUrl = alice_document.id().to_url().join("#my-revocation-service")?; + let service: Service = revocation_bitmap_holder.to_service(service_id)?; + + alice_document.insert_service(service).unwrap(); + + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk with the Revocationbitmap:", alice_document.id().as_str()); + + let subject: Subject = Subject::from_json_value(json!({ + "id": alice_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); + + println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); + + println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); + + println!("{} {} ","[Issuer]".red(), ": Create a new timeframe of 30 seconds"); + + let duration = Duration::seconds(30); + + let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; + let credential_index: u32 = 5; + + let start_validity_timeframe = Timestamp::now_utc(); + let status: Status = RevocationTimeframeStatus::new( + Some(start_validity_timeframe), + duration, + service_url.into(), + credential_index, + )? + .into(); + + println!("{} {} ","[Issuer]".red(), ": Generate VC with timeframe"); + + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .status(status) + .build()?; + + let credential_jpt: Jpt = issuer_document.create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ).await?; + + println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); + + println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); + + println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); + + println!("{} {}", "[Holder]".blue(), ": Validate VC"); + + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Validate Timeframe and revocation"); + + let _revocation_result = JptCredentialValidatorUtils::check_timeframes_and_revocation_with_validity_timeframe_2024( + &decoded_jpt.credential, + &issuer_document, + None, + StatusCheck::Strict, + ).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access with Selective Disclosure of VC attributes"); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); + + println!("{} : Resolve Issuer's Public Key to compute the Signature Proof of Knowledge", "[Holder]".blue()); + + let method_id = decoded_jpt + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + println!("{} : Engages in the Selective Disclosure of credential's attributes GPA and name", "[Holder]".blue()); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("GPA") + .unwrap(); + + selective_disclosure_presentation.conceal_in_subject("name").unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded)"); + + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); + + println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let resolved_issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &resolved_issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ).unwrap(); + + println!("{} : Verify the validity of timeframe","[Verifier]".green()); + + let _timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ).unwrap(); + + println!("{}: JPT successfully verified, access granted", "[Verifier]".green()); + + println!(""); + + println!("Waiting for the next validityTimeframe"); + + println!(""); + + thread::sleep(SleepDuration::from_secs(31)); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access after Timeframe expiration"); + + let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ); + + println!("{} {} {}", "[Verifier]".green(), ": Verify the validity of timeframe :", timeframe_result.unwrap_err()); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request update Timeframe"); + + println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending a challenge"); + + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + let presentation: Presentation = + PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) + .credential(credential_jpt) + .build()?; + + let presentation_jwt: Jwt = alice_document + .create_presentation_jwt( + &presentation, + &storage_alice, + &fragment_alice, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ).await?; + + println!("{} {} ","[Issuer]".red(), ": Generate a Verifiable Presentation (VP) from the expired VC including the challenge and a new expiry timestamp"); + + println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Sending VP (as JWT):", presentation_jwt.as_str()); + + println!("{} {} ","[Issuer]".red(), ": Resolve Issuer's DID and verify the VP"); + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver_jwk: Resolver = Resolver::new(); + let _ = resolver_jwk.attach_did_jwk_handler(); + + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder:CoreDocument = resolver_jwk.resolve(&holder_did).await?; + + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + println!("{} {} ","[Issuer]".red(), ": Verify the ZK VC inside the VP"); + + let validation_options: JptCredentialValidationOptions = JptCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + let jpt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + let jpt_vc = jpt_credentials.first().unwrap(); + + let mut verified_credential_result = + JptCredentialValidator::validate::<_, Object>(jpt_vc, &issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + + let _revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &verified_credential_result.credential, + &issuer_document, + StatusCheck::Strict, + ).unwrap(); + + println!("{} {} ","[Issuer]".red(), ": VP successfully validated"); + + println!("{} {} ","[Issuer]".red(), ": Update credential with new Timeframe"); + + let new_credential_jpt = issuer_document + .update( + &storage_issuer, + &fragment_issuer, + None, + duration, + &mut verified_credential_result.decoded_jwp, + ).await?; + + println!("{} {} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending updated credential (as JPT)", new_credential_jpt.as_str()); + + println!("{} {}", "[Holder]".blue(), ": Validate updated VC"); + + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &new_credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + let _ = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_jpt.credential, + None, + StatusCheck::Strict, + ).unwrap(); + + println!("{} {}", "[Holder]".blue(), ": Successfull verification"); + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); + + let challenge: &str = "7788554-2598-ff55-ef52-822888d464dd"; + + println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); + + println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded) from the updated VC"); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("GPA") + .unwrap(); + + selective_disclosure_presentation.conceal_in_subject("name").unwrap(); + + let method_id = decoded_jpt + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let updated_presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ).await?; + + println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", updated_presentation_jpt.as_str()); + + println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &updated_presentation_jpt, + &resolved_issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ).unwrap(); + + println!("{} : Verify the validity of timeframe","[Verifier]".green()); + + let _timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ).unwrap(); + + println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); + + println!("{} {} ","[Issuer]".red(), ": Decide to revoke the Holder's Credential"); + + println!("{} {} {}", "[Issuer]".red(), ": Update the Bitmap and publish the DID Document (with did:web method) at", did_url); + + issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; + + write_to_file(&issuer_document, Some(path_did_file))?; + + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access (after Issuer revoke)"); + + println!("{} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Resolve Issuer's DID and verifies the Presentation"); + + let client= ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + + let mut resolver_web: Resolver = Resolver::new(); + let _ = resolver_web.attach_web_handler(client)?; + + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&updated_presentation_jpt).unwrap(); + let resolved_issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; + + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &new_credential_jpt, + &resolved_issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ).unwrap(); + + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &decoded_jpt.credential, + &resolved_issuer_document, + StatusCheck::Strict, + ); + + println!("{} {} {}", "[Verifier]".green(), " : Error ", revocation_result.unwrap_err()); + + Ok(()) +} From 4b6872e5b81ae9ce3a2cb4f70300d59a1a3f3071 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:36:03 +0100 Subject: [PATCH 091/163] cleanup examples --- .../wasm/examples/src/wallet/did_jwk_hybrid.ts | 11 ++--------- bindings/wasm/examples/src/wallet/did_jwk_pq.ts | 13 ++----------- .../examples/src/wallet/did_jwk_traditional.ts | 15 ++------------- bindings/wasm/examples/src/wallet/did_jwk_zk.ts | 11 ++--------- 4 files changed, 8 insertions(+), 42 deletions(-) diff --git a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts index a5693dd26f..6d096a1418 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts @@ -1,24 +1,17 @@ +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 import { CompositeAlgId, - IotaDocument, - IotaIdentityClient, JwkMemStore, - JwsAlgorithm, KeyIdMemStore, - MethodScope, Storage, CoreDocument } from "@iota/identity-wasm/node"; -import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; -import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; -/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +/** Demonstrate how to create a DID CompositeJWK Document */ export async function createDidJwkHybrid(){ - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); const document = await CoreDocument.newDidCompositeJwk( storage, diff --git a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts index c9e9861a3a..8725c6d7fb 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts @@ -1,26 +1,17 @@ +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 import { - IotaDID, - IotaDocument, - IotaIdentityClient, JwkMemStore, JwsAlgorithm, KeyIdMemStore, - MethodScope, Storage, CoreDocument } from "@iota/identity-wasm/node"; -import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; -import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; -/** Demonstrate how to create a DID JWK Document */ +/** Demonstrate how to create a DID JWK PQ Document */ export async function createDidJwkPq(){ - const mnemonicSecretManager: MnemonicSecretManager = { - mnemonic: Utils.generateMnemonic(), - }; - const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); const document = await CoreDocument.newDidJwkPq( storage, diff --git a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts index 34653cfadb..4fd4606c26 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts @@ -1,28 +1,17 @@ +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 import { - IotaDID, - IotaDocument, - IotaIdentityClient, JwkMemStore, JwsAlgorithm, KeyIdMemStore, - MethodScope, Storage, CoreDocument } from "@iota/identity-wasm/node"; -import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; -import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; -/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +/** Demonstrate how to create a traditional DID JWK Document */ export async function createDidJwk(){ - const mnemonicSecretManager: MnemonicSecretManager = { - mnemonic: Utils.generateMnemonic(), - }; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); const document: CoreDocument = await CoreDocument.newDidJwk( storage, diff --git a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts index 9a0c6fc356..eae269ddbd 100644 --- a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts +++ b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts @@ -1,24 +1,17 @@ +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 import { - IotaDID, - IotaDocument, ProofAlgorithm, JwkMemStore, - JwsAlgorithm, KeyIdMemStore, - MethodScope, Storage, CoreDocument } from "@iota/identity-wasm/node"; -import { AliasOutput, Client, MnemonicSecretManager, SecretManager, Utils } from "@iota/sdk-wasm/node"; -import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; -/** Demonstrate how to create a DID Document and publish it in a new Alias Output. */ +/** Demonstrate how to create a ZK DID JWK Document */ export async function createDidJwkZk(){ - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); const document = await CoreDocument.newDidJwkZk( storage, From a333b17716377da6aaf9fcfde96824d0a3667c6a Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:43:27 +0100 Subject: [PATCH 092/163] cleanup --- bindings/wasm/lib/jwk_storage.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 6c9d12310d..9539ca18d9 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -1,3 +1,6 @@ +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ import * as ed from "@noble/ed25519"; import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx, JwkStoragePQ } from "~identity_wasm"; import { EdCurve, JwkType, JwsAlgorithm } from "./jose"; From c5be81e93e27c706f43a525b07c97401e36e9fc0 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:43:44 +0100 Subject: [PATCH 093/163] add license --- bindings/wasm/examples/src/main.ts | 4 +++- bindings/wasm/lib/jose/composite_jwk.ts | 3 +++ bindings/wasm/src/did/mod.rs | 4 +++- bindings/wasm/src/did/wasm_did_jwk_document_ext.rs | 3 +++ bindings/wasm/src/jose/types.rs | 3 +++ bindings/wasm/src/lib.rs | 3 +++ bindings/wasm/src/storage/jwk_storage.rs | 1 - bindings/wasm/src/storage/jwk_storage_pqc.rs | 4 +++- bindings/wasm/src/storage/mod.rs | 3 +++ 9 files changed, 24 insertions(+), 4 deletions(-) diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 0ad5497b85..604197994b 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -1,6 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ import { createIdentity } from "./0_basic/0_create_did"; import { updateIdentity } from "./0_basic/1_update_did"; import { resolveIdentity } from "./0_basic/2_resolve_did"; diff --git a/bindings/wasm/lib/jose/composite_jwk.ts b/bindings/wasm/lib/jose/composite_jwk.ts index f66cbbb864..7e1ff57904 100644 --- a/bindings/wasm/lib/jose/composite_jwk.ts +++ b/bindings/wasm/lib/jose/composite_jwk.ts @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + export const enum CompositeAlgId { /** DER encoded value in hex = 060B6086480186FA6B50080103 */ IdMldsa44Ed25519Sha512 = "id-MLDSA44-Ed25519-SHA512", diff --git a/bindings/wasm/src/did/mod.rs b/bindings/wasm/src/did/mod.rs index 797e71cdd5..a751162747 100644 --- a/bindings/wasm/src/did/mod.rs +++ b/bindings/wasm/src/did/mod.rs @@ -1,6 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ mod did_jwk; mod jws_verification_options; mod service; diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 6a742ed9db..2456e0286c 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + use std::rc::Rc; use crate::error::Result; use crate::error::WasmResult; diff --git a/bindings/wasm/src/jose/types.rs b/bindings/wasm/src/jose/types.rs index 8786677387..266e811239 100644 --- a/bindings/wasm/src/jose/types.rs +++ b/bindings/wasm/src/jose/types.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ use identity_iota::verification::jws::JwsAlgorithm; use js_sys::JsString; use std::str::FromStr; diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 0fec7dcfd6..6f65fa54be 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,5 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ #![forbid(unsafe_code)] #![allow(deprecated)] diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index b89c9462b6..9f1fa4685b 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -65,7 +65,6 @@ impl JwkStorage for WasmJwkStorage { } async fn insert(&self, jwk: Jwk) -> KeyStorageResult { - web_sys::console::log_1(&"WWWWWWWWWWW".into()); let promise: Promise = Promise::resolve(&WasmJwkStorage::insert(self, WasmJwk::from(jwk))); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index ff0abd3db6..cd4f388e1d 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -1,5 +1,7 @@ -use super::WasmJwkStorage; +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 +use super::WasmJwkStorage; use identity_iota::storage::JwkGenOutput; use identity_iota::storage::JwkStoragePQ; use identity_iota::storage::KeyId; diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index a2cd4b664b..5901aef513 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ mod jpt_timeframe_revocation_ext; mod jwk_gen_output; From f37f7217af36b75dccdc04459a593ab97ff88441 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:12:28 +0100 Subject: [PATCH 094/163] cleanup --- bindings/wasm/lib/jwk_storage.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 9539ca18d9..62c2b432d4 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -5,7 +5,6 @@ import * as ed from "@noble/ed25519"; import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx, JwkStoragePQ } from "~identity_wasm"; import { EdCurve, JwkType, JwsAlgorithm } from "./jose"; import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; -//import { } from "../node/jose"; type Ed25519PrivateKey = Uint8Array; type Ed25519PublicKey = Uint8Array; @@ -20,7 +19,6 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ } public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { - console.log("Inside generatePQ"); if (keyType !== JwkMemStore.mldsaKeyType()) { throw new Error(`unsupported key type ${keyType}`); } @@ -93,7 +91,7 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ } public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { - if (!publicKey.alg() || publicKey.alg() !== JwsAlgorithm.EdDSA) { + if (publicKey.alg()! !== JwsAlgorithm.EdDSA) { throw new Error("unsupported JWS algorithm"); } else { if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) { @@ -148,8 +146,6 @@ async function encodeJwk( publicKey: Uint8Array, alg: JwsAlgorithm ): Promise { - // const publicKey = await ed25519.getPublicKey(privateKey); - console.log("Inside encodeJwk"); const x = encodeB64(publicKey); const d = encodeB64(privateKey); @@ -215,7 +211,6 @@ function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { } } -//TODO: non sembra servire a nulla export interface JwkStorageBBSPlusExt { // Generate a new BLS12381 key represented as a JSON Web Key. generateBBS: (algorithm: ProofAlgorithm) => Promise; From 80a7aedf88bd1a557840703316a6fcff2c4d28fe Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:46:16 +0100 Subject: [PATCH 095/163] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 400a9ad4cd..e710501d30 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository extends IOTA Identity by implementing both pure **Post-Quantum ( 1. **PQ Signatures**: IOTA Identity extends its support for selected PQ signature algorithms, such as [ML-DSA](https://csrc.nist.gov/pubs/fips/204/final), [SLH-DSA](https://csrc.nist.gov/pubs/fips/205/final) and [FALCON](https://falcon-sign.info/). The implementation of these algorithms is provided by [liboqs](https://github.com/open-quantum-safe/liboqs-rust). -2. **PQ/T hybrid Signatures**: mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), IOTA Identity extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. +2. **PQ/T hybrid Signatures**: to mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), the IOTA Identity also extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. ```json "compositeJwk": { From 30723187f5580a6be998f0213cb3b39b04bb8ba2 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:57:29 +0100 Subject: [PATCH 096/163] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e710501d30..be1acde687 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,9 @@ The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [in # Examples -To test all the above functionalities, refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. -> **Note**: The examples in the `example/demo` directory are configured to use the [DID Web Method](https://w3c-ccg.github.io/did-method-web/) for the Issuer. To run these examples, you must -> have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, -> ensure that the following variables in `utils.rs` are correctly set to point to your server instance: +To test all the above quantum-secure functionalities, refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. +> **Note**: The examples in the `example/demo` directory are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. +> To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: > ```rust > pub static DID_URL: &str = "https://localhost:4443/.well-known/"; > pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; From 0de11010a2b377948b53da29d4c6049cf4ad02d4 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:23:16 +0100 Subject: [PATCH 097/163] Update README.md --- examples/README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/README.md b/examples/README.md index 8ea9ab2145..c86a65dbb8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,5 +1,3 @@ -![banner](./../documentation/static/img/Banner/banner_identity.svg) - # IOTA Identity Examples This folder provides code examples to learn how IOTA Identity can be used. @@ -16,8 +14,6 @@ For instance, to run the example `0_create_did`, use: cargo run --release --example 0_create_did ``` -### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. - ## Basic Examples The following basic CRUD (Create, Read, Update, Delete) examples are available: @@ -49,3 +45,19 @@ The following advanced examples are available: | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | + +## PQC Examples + +The following Post-Quantum (PQ) and Post-Quantum/Traditinal (PQ/T) hybrid examples are available: +| Name | Information | +| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | +| | | + +## ZK Examples + +The following Zero Knowledge(ZK) examples are available: +| Name | Information | +| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | +| | | + +### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. From 6041883d1178f3d807f825677c72f30df71b91e8 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:34:18 +0100 Subject: [PATCH 098/163] Update README.md --- examples/README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/README.md b/examples/README.md index c86a65dbb8..1d62e15f5e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,6 +44,8 @@ The following advanced examples are available: | [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| | | +| | | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | ## PQC Examples @@ -52,12 +54,5 @@ The following Post-Quantum (PQ) and Post-Quantum/Traditinal (PQ/T) hybrid exampl | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | | | - -## ZK Examples - -The following Zero Knowledge(ZK) examples are available: -| Name | Information | -| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| | | - -### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. + +#### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. From 04e12d675d6e240a41be1cd36b9f4366e591cc03 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:37:39 +0100 Subject: [PATCH 099/163] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be1acde687..fb16d76bb6 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [in # Examples -To test all the above quantum-secure functionalities, refer to practical code snippets available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. +To test all the above quantum-secure functionalities, refer to practical PQC examples available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. > **Note**: The examples in the `example/demo` directory are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. > To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: > ```rust From 70e34dc5bd977e22e90687e27f68930f8da79363 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:39:28 +0100 Subject: [PATCH 100/163] Update README.md --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 1d62e15f5e..71397af8b4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -50,7 +50,7 @@ The following advanced examples are available: ## PQC Examples -The following Post-Quantum (PQ) and Post-Quantum/Traditinal (PQ/T) hybrid examples are available: +The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | | | From 99735fd40ead7aff5c5d925ca33a25706f704ab6 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:08:30 +0100 Subject: [PATCH 101/163] update example names --- examples/{demo/pq.rs => 2_pqc/0_pq.rs} | 0 examples/{demo/hybrid.rs => 2_pqc/1_hybrid.rs} | 0 examples/{demo => 2_pqc}/revocation_zk.rs | 0 examples/{demo => 2_pqc}/server/.gitignore | 0 .../{demo => 2_pqc}/server/.well-known/.gitkeep | 0 examples/{demo => 2_pqc}/server/Cargo.toml | 0 examples/{demo => 2_pqc}/server/src/main.rs | 0 examples/{demo => 2_pqc}/traditional.rs | 0 examples/{demo => 2_pqc}/traditional_zk.rs | 0 examples/Cargo.toml | 16 ++++++++-------- examples/README.md | 5 ++--- examples/utils/utils.rs | 2 +- 12 files changed, 11 insertions(+), 12 deletions(-) rename examples/{demo/pq.rs => 2_pqc/0_pq.rs} (100%) rename examples/{demo/hybrid.rs => 2_pqc/1_hybrid.rs} (100%) rename examples/{demo => 2_pqc}/revocation_zk.rs (100%) rename examples/{demo => 2_pqc}/server/.gitignore (100%) rename examples/{demo => 2_pqc}/server/.well-known/.gitkeep (100%) rename examples/{demo => 2_pqc}/server/Cargo.toml (100%) rename examples/{demo => 2_pqc}/server/src/main.rs (100%) rename examples/{demo => 2_pqc}/traditional.rs (100%) rename examples/{demo => 2_pqc}/traditional_zk.rs (100%) diff --git a/examples/demo/pq.rs b/examples/2_pqc/0_pq.rs similarity index 100% rename from examples/demo/pq.rs rename to examples/2_pqc/0_pq.rs diff --git a/examples/demo/hybrid.rs b/examples/2_pqc/1_hybrid.rs similarity index 100% rename from examples/demo/hybrid.rs rename to examples/2_pqc/1_hybrid.rs diff --git a/examples/demo/revocation_zk.rs b/examples/2_pqc/revocation_zk.rs similarity index 100% rename from examples/demo/revocation_zk.rs rename to examples/2_pqc/revocation_zk.rs diff --git a/examples/demo/server/.gitignore b/examples/2_pqc/server/.gitignore similarity index 100% rename from examples/demo/server/.gitignore rename to examples/2_pqc/server/.gitignore diff --git a/examples/demo/server/.well-known/.gitkeep b/examples/2_pqc/server/.well-known/.gitkeep similarity index 100% rename from examples/demo/server/.well-known/.gitkeep rename to examples/2_pqc/server/.well-known/.gitkeep diff --git a/examples/demo/server/Cargo.toml b/examples/2_pqc/server/Cargo.toml similarity index 100% rename from examples/demo/server/Cargo.toml rename to examples/2_pqc/server/Cargo.toml diff --git a/examples/demo/server/src/main.rs b/examples/2_pqc/server/src/main.rs similarity index 100% rename from examples/demo/server/src/main.rs rename to examples/2_pqc/server/src/main.rs diff --git a/examples/demo/traditional.rs b/examples/2_pqc/traditional.rs similarity index 100% rename from examples/demo/traditional.rs rename to examples/2_pqc/traditional.rs diff --git a/examples/demo/traditional_zk.rs b/examples/2_pqc/traditional_zk.rs similarity index 100% rename from examples/demo/traditional_zk.rs rename to examples/2_pqc/traditional_zk.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8fd1340bb9..d6fdb15858 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -123,23 +123,23 @@ path = "1_advanced/14_did_web.rs" name = "14_did_web" [[example]] -path = "demo/traditional.rs" -name = "traditional" +path = "2_pqc/0_pq.rs" +name = "0_pq" [[example]] -path = "demo/pq.rs" -name = "pq" +path = "2_pqc/1_hybrid.rs" +name = "1_hybrid" [[example]] -path = "demo/hybrid.rs" -name = "hybrid" +path = "2_pqc/traditional.rs" +name = "traditional" [[example]] -path = "demo/traditional_zk.rs" +path = "2_pqc/traditional_zk.rs" name = "traditional_zk" [[example]] -path = "demo/revocation_zk.rs" +path = "2_pqc/revocation_zk.rs" name = "revocation_zk" diff --git a/examples/README.md b/examples/README.md index 71397af8b4..f3ab56e47b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,8 +44,6 @@ The following advanced examples are available: | [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | -| | | -| | | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | ## PQC Examples @@ -53,6 +51,7 @@ The following advanced examples are available: The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| | | +| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate and verify VC and VP with pure PQ signatures. This example uses did:jwk | +|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate and verify VC and VP with PQ/T hybrid signatures. This example uses did:compositejwk | #### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index 4b110162aa..fa9934ed6c 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -35,7 +35,7 @@ pub static API_ENDPOINT: &str = "http://localhost"; pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -pub static PATH_DID_FILE: &str = "./examples/demo/server/.well-known/"; +pub static PATH_DID_FILE: &str = "./examples/2_pqc/server/.well-known/"; pub type MemStorage = Storage; From 660326e4edda595098d6e9ca0aac248198e39f19 Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:11:07 +0100 Subject: [PATCH 102/163] Update README.md --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index f3ab56e47b..08064af439 100644 --- a/examples/README.md +++ b/examples/README.md @@ -52,6 +52,6 @@ The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examp | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate and verify VC and VP with pure PQ signatures. This example uses did:jwk | -|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate and verify VC and VP with PQ/T hybrid signatures. This example uses did:compositejwk | +|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate and verify VC and VP with PQ/T hybrid signatures. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | #### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. From 537e58f8c538669358b677e8c91caa0121d360f7 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:20:43 +0100 Subject: [PATCH 103/163] update Readme --- examples/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 08064af439..ce733bd199 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,6 +44,8 @@ The following advanced examples are available: | [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| [9_zpk](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC with Selective Disclosure. | +| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC. | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | ## PQC Examples @@ -51,7 +53,7 @@ The following advanced examples are available: The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate and verify VC and VP with pure PQ signatures. This example uses did:jwk | -|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate and verify VC and VP with PQ/T hybrid signatures. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | +| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. This example uses did:jwk | +|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | #### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. From 6c279bd066151e44ae6e3d47b31102db6718a3ae Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:23:18 +0100 Subject: [PATCH 104/163] Update README.md --- examples/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/README.md b/examples/README.md index ce733bd199..b059713473 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,8 +44,8 @@ The following advanced examples are available: | [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | -| [9_zpk](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC with Selective Disclosure. | -| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC. | +| [9_zpk](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | +| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | ## PQC Examples @@ -53,7 +53,7 @@ The following advanced examples are available: The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: | Name | Information | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. This example uses did:jwk | -|[1_hybrid](./2_pqc/1_hybrid.rs)| Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | +| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. This example uses did:jwk | +| [1_hybrid](./2_pqc/1_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | #### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. From 47523ba96307cc90519c9e60851224d24811ce4d Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:25:02 +0100 Subject: [PATCH 105/163] Update README.md --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index b059713473..0c5986d092 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,7 +44,7 @@ The following advanced examples are available: | [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | | [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | | [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | -| [9_zpk](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | +| [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | | [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | From 76b46d72444e3ffe402a5700927690feecaf0afe Mon Sep 17 00:00:00 2001 From: Andrea Vesco <109175919+andreavesco@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:27:51 +0100 Subject: [PATCH 106/163] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fb16d76bb6..1c734f33eb 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,6 @@ The transition to PQC is a delicate and lengthy process. Today, the Distributed **Note**: this repository also extends the existing `did:jwk` method to deal with pure PQ keys and signatures (ML-DSA, SLH-DSA and FALCON), and adds a simple `did:web` method for the Issuers. -# Zero-Knowledge (ZK) - -The IOTA Identity now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: - -* **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. -* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure. - -**Note**: the BBS+ signature scheme uses traditional cryptography, hence it is not quantum-secure; for more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). - # Examples To test all the above quantum-secure functionalities, refer to practical PQC examples available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. @@ -47,3 +38,12 @@ To test all the above quantum-secure functionalities, refer to practical PQC exa > ``` Make sure your server is set up before running the examples to avoid any configuration issues. +# Zero-Knowledge (ZK) + +The IOTA Identity already supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: + +* **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. +* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure. + +**Note**: the BBS+ signature scheme uses traditional cryptography, hence it is not quantum-secure; for more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). + From cbd30cccc9467dcc107731987786e8bc08aa8315 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:58:13 +0100 Subject: [PATCH 107/163] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c734f33eb..585740fbde 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ The transition to PQC is a delicate and lengthy process. Today, the Distributed # Examples To test all the above quantum-secure functionalities, refer to practical PQC examples available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. -> **Note**: The examples in the `example/demo` directory are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. -> To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/demo/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: +> **Note**: The examples in the `example/2_pqc` directory are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. +> To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/2_pqc/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: > ```rust > pub static DID_URL: &str = "https://localhost:4443/.well-known/"; > pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; From cf58d2dcebd5a42543ff5dc3b4d5f9eb877444df Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:01:10 +0100 Subject: [PATCH 108/163] Update README.md --- examples/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 0c5986d092..dfdcc518da 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,6 +48,8 @@ The following advanced examples are available: | [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | +#### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. + ## PQC Examples The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: @@ -56,4 +58,8 @@ The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examp | [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. This example uses did:jwk | | [1_hybrid](./2_pqc/1_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | -#### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. +#### **Note**: The PQC examples are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/2_pqc/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: +> ```rust +> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; +> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; +> ``` From 87e42a48b2645d2bd761fb624dde5843ec2f32df Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:41:26 +0100 Subject: [PATCH 109/163] fix import --- bindings/wasm/src/storage/jwk_storage_pqc.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index 6b1a3ad966..39a509539d 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -9,7 +9,6 @@ use identity_iota::storage::KeyStorageResult; use identity_iota::storage::KeyType; use identity_iota::verification::jwk::Jwk; use wasm_bindgen::prelude::*; -use identity_iota::verification::jose::jws::JwsAlgorithm; use crate::error::JsValueResult; use js_sys::Promise; use std::str::FromStr; @@ -20,7 +19,6 @@ use crate::jose::WasmJwk; use crate::jose::WasmJwsAlgorithm; use super::WasmJwkGenOutput; -use super::WasmJwkStorage; use super::WasmProofUpdateCtx; use identity_iota::storage::bls::encode_bls_jwk; @@ -28,22 +26,14 @@ use identity_iota::storage::bls::expand_bls_jwk; use identity_iota::storage::bls::generate_bbs_keypair; use identity_iota::storage::bls::sign_bbs; use identity_iota::storage::bls::update_bbs_signature; -use identity_iota::storage::JwkGenOutput; use identity_iota::storage::JwkStorage; -use identity_iota::storage::JwkStoragePQ; -use identity_iota::storage::KeyId; use identity_iota::storage::KeyStorageError; use identity_iota::storage::KeyStorageErrorKind; -use identity_iota::storage::KeyStorageResult; -use identity_iota::storage::KeyType; use identity_iota::storage::ProofUpdateCtx; -use identity_iota::verification::jwk::Jwk; use jsonprooftoken::jpa::algs::ProofAlgorithm; use wasm_bindgen::prelude::*; use identity_iota::verification::jose::jws::JwsAlgorithm; -use crate::error::JsValueResult; use js_sys::Array; -use js_sys::Promise; use js_sys::Uint8Array; use wasm_bindgen_futures::JsFuture; From 66ab227b1a57be2164a2c565d33056175b8f5a27 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:55:20 +0100 Subject: [PATCH 110/163] pq sign --- bindings/wasm/src/storage/jwk_storage.rs | 1 - bindings/wasm/src/storage/jwk_storage_pqc.rs | 55 +++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 8df839af52..b89c9462b6 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -10,7 +10,6 @@ use crate::jose::WasmJwk; use identity_iota::storage::key_storage::JwkGenOutput; use identity_iota::storage::key_storage::JwkStorage; -use identity_iota::storage::key_storage::JwkStoragePQ; use identity_iota::storage::key_storage::KeyId; use identity_iota::storage::key_storage::KeyStorageError; use identity_iota::storage::key_storage::KeyStorageErrorKind; diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index 39a509539d..76036a7052 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -11,33 +11,14 @@ use identity_iota::verification::jwk::Jwk; use wasm_bindgen::prelude::*; use crate::error::JsValueResult; use js_sys::Promise; -use std::str::FromStr; - -use crate::error::Result as WasmResult; -use crate::error::WasmResult as _; -use crate::jose::WasmJwk; -use crate::jose::WasmJwsAlgorithm; - -use super::WasmJwkGenOutput; -use super::WasmProofUpdateCtx; - -use identity_iota::storage::bls::encode_bls_jwk; -use identity_iota::storage::bls::expand_bls_jwk; -use identity_iota::storage::bls::generate_bbs_keypair; -use identity_iota::storage::bls::sign_bbs; -use identity_iota::storage::bls::update_bbs_signature; -use identity_iota::storage::JwkStorage; -use identity_iota::storage::KeyStorageError; use identity_iota::storage::KeyStorageErrorKind; -use identity_iota::storage::ProofUpdateCtx; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use wasm_bindgen::prelude::*; use identity_iota::verification::jose::jws::JwsAlgorithm; -use js_sys::Array; -use js_sys::Uint8Array; - +use identity_iota::storage::KeyStorageError; use wasm_bindgen_futures::JsFuture; use super::jwk_storage::PromiseJwkGenOutput; +use js_sys::Array; +use crate::jose::WasmJwk; +use js_sys::Uint8Array; #[wasm_bindgen] extern "C" { @@ -57,8 +38,17 @@ impl JwkStoragePQ for WasmJwkStorage { result.into() } - async fn pq_sign(&self, _key_id: &KeyId, _data: &[u8], _public_key: &Jwk) -> KeyStorageResult> { - todo!(); + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + web_sys::console::log_1(&"pq_sign from rust".into()); + println!("pq_sign from rust"); + let promise: Promise = Promise::resolve(&WasmJwkStorage::sign( + self, + key_id.clone().into(), + data.to_owned(), + WasmJwk(public_key.clone()), + )); + let result: JsValueResult = JsFuture::from(promise).await.into(); + result.to_key_storage_error().map(uint8array_to_bytes)? } } @@ -71,4 +61,17 @@ interface JwkStoragePQ { * * It's recommend that the implementer exposes constants for the supported key type string. */ generatePQKey: (keyType: string, algorithm: JwsAlgorithm) => Promise; -}"#; \ No newline at end of file +}"#; + +fn uint8array_to_bytes(value: JsValue) -> KeyStorageResult> { + if !JsCast::is_instance_of::(&value) { + return Err( + KeyStorageError::new(KeyStorageErrorKind::SerializationError) + .with_custom_message("expected Uint8Array".to_owned()), + ); + } + let array_js_value = JsValue::from(Array::from(&value)); + array_js_value + .into_serde() + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::SerializationError).with_custom_message(e.to_string())) +} \ No newline at end of file From 0db936ca212aa0fe501920174735d2ef1bd3ec23 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:23:29 +0100 Subject: [PATCH 111/163] implement expand_did_compositejwk --- bindings/wasm/src/did/did_compositejwk.rs | 105 ++++++++++++++++++++ bindings/wasm/src/did/mod.rs | 3 +- bindings/wasm/src/did/wasm_core_document.rs | 11 ++ bindings/wasm/src/jose/compositejwk.rs | 61 ++++++++++++ bindings/wasm/src/jose/jwk.rs | 4 +- bindings/wasm/src/jose/mod.rs | 6 ++ bindings/wasm/src/jose/types.rs | 13 +++ identity_jose/src/jwk/composite_jwk.rs | 15 +++ 8 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 bindings/wasm/src/did/did_compositejwk.rs create mode 100644 bindings/wasm/src/jose/compositejwk.rs diff --git a/bindings/wasm/src/did/did_compositejwk.rs b/bindings/wasm/src/did/did_compositejwk.rs new file mode 100644 index 0000000000..2e71a1cccf --- /dev/null +++ b/bindings/wasm/src/did/did_compositejwk.rs @@ -0,0 +1,105 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::did::DIDCompositeJwk; +use identity_iota::did::DID as _; +use wasm_bindgen::prelude::*; + +use super::wasm_core_did::get_core_did_clone; +use super::IToCoreDID; +use super::WasmCoreDID; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jose::WasmCompositeJwk; + +/// `did:compositejwk` DID. +#[wasm_bindgen(js_name = DIDCompositeJwk)] +pub struct WasmDIDCompositeJwk(pub(crate) DIDCompositeJwk); + +#[wasm_bindgen(js_class = DIDCompositeJwk)] +impl WasmDIDCompositeJwk { + #[wasm_bindgen(constructor)] + /// Creates a new {@link DIDCompositeJwk} from a {@link CoreDID}. + /// + /// ### Errors + /// Throws an error if the given did is not a valid `did:compositejwk` DID. + pub fn new(did: IToCoreDID) -> Result { + let did = get_core_did_clone(&did).0; + DIDCompositeJwk::try_from(did).wasm_result().map(Self) + } + /// Parses a {@link DIDCompositeJwk} from the given `input`. + /// + /// ### Errors + /// + /// Throws an error if the input is not a valid {@link DIDCompositeJwk}. + #[wasm_bindgen] + pub fn parse(input: &str) -> Result { + DIDCompositeJwk::parse(input).wasm_result().map(Self) + } + + /// Returns the JSON WEB KEY (JWK) encoded inside this `did:jwk`. + #[wasm_bindgen] + pub fn composite_jwk(&self) -> WasmCompositeJwk { + self.0.composite_jwk().into() + } + + // =========================================================================== + // DID trait + // =========================================================================== + + /// Returns the {@link CoreDID} scheme. + /// + /// E.g. + /// - `"did:example:12345678" -> "did"` + /// - `"did:iota:smr:12345678" -> "did"` + #[wasm_bindgen] + pub fn scheme(&self) -> String { + self.0.scheme().to_owned() + } + + /// Returns the {@link CoreDID} authority: the method name and method-id. + /// + /// E.g. + /// - `"did:example:12345678" -> "example:12345678"` + /// - `"did:iota:smr:12345678" -> "iota:smr:12345678"` + #[wasm_bindgen] + pub fn authority(&self) -> String { + self.0.authority().to_owned() + } + + /// Returns the {@link CoreDID} method name. + /// + /// E.g. + /// - `"did:example:12345678" -> "example"` + /// - `"did:iota:smr:12345678" -> "iota"` + #[wasm_bindgen] + pub fn method(&self) -> String { + self.0.method().to_owned() + } + + /// Returns the {@link CoreDID} method-specific ID. + /// + /// E.g. + /// - `"did:example:12345678" -> "12345678"` + /// - `"did:iota:smr:12345678" -> "smr:12345678"` + #[wasm_bindgen(js_name = methodId)] + pub fn method_id(&self) -> String { + self.0.method_id().to_owned() + } + + /// Returns the {@link CoreDID} as a string. + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + // Only intended to be called internally. + #[wasm_bindgen(js_name = toCoreDid, skip_typescript)] + pub fn to_core_did(&self) -> WasmCoreDID { + WasmCoreDID(self.0.clone().into()) + } +} + +impl_wasm_json!(WasmDIDCompositeJwk, DIDCompositeJwk); +impl_wasm_clone!(WasmDIDCompositeJwk, DIDCompositeJwk); diff --git a/bindings/wasm/src/did/mod.rs b/bindings/wasm/src/did/mod.rs index a751162747..3209672f3c 100644 --- a/bindings/wasm/src/did/mod.rs +++ b/bindings/wasm/src/did/mod.rs @@ -4,6 +4,7 @@ * Modifications Copyright 2024 Fondazione LINKS. */ mod did_jwk; +mod did_compositejwk; mod jws_verification_options; mod service; mod wasm_core_did; @@ -24,5 +25,5 @@ pub use self::wasm_core_document::PromiseJwt; pub use self::wasm_core_document::WasmCoreDocument; pub use self::wasm_did_url::WasmDIDUrl; pub use did_jwk::*; - +pub use did_compositejwk::*; pub use self::jws_verification_options::*; diff --git a/bindings/wasm/src/did/wasm_core_document.rs b/bindings/wasm/src/did/wasm_core_document.rs index fd66c4e7ca..1b9158f83c 100644 --- a/bindings/wasm/src/did/wasm_core_document.rs +++ b/bindings/wasm/src/did/wasm_core_document.rs @@ -1,6 +1,10 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + use std::rc::Rc; use super::WasmCoreDID; @@ -25,6 +29,7 @@ use crate::credential::WasmPresentation; use crate::did::service::WasmService; use crate::did::wasm_did_url::WasmDIDUrl; use crate::did::WasmDIDJwk; +use crate::did::WasmDIDCompositeJwk; use crate::error::Result; use crate::error::WasmResult; use crate::jose::WasmDecodedJws; @@ -773,6 +778,12 @@ impl WasmCoreDocument { pub fn expand_did_jwk(did: WasmDIDJwk) -> Result { CoreDocument::expand_did_jwk(did.0).wasm_result().map(Self::from) } + + /// Creates a {@link CoreDocument} from the given {@link DIDCompositeJwk}. + #[wasm_bindgen(js_name = expandDIDCompositeJwk)] + pub fn expand_did_compositejwk(did: WasmDIDCompositeJwk) -> Result { + CoreDocument::expand_did_compositejwk(did.0).wasm_result().map(Self::from) + } } #[wasm_bindgen] diff --git a/bindings/wasm/src/jose/compositejwk.rs b/bindings/wasm/src/jose/compositejwk.rs new file mode 100644 index 0000000000..d1029e1cb4 --- /dev/null +++ b/bindings/wasm/src/jose/compositejwk.rs @@ -0,0 +1,61 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::verification::jose::jwk::CompositeJwk; +use identity_iota::verification::jose::jwk::CompositeAlgId; +use wasm_bindgen::prelude::*; +use crate::jose::WasmCompositeAlgId; +use crate::jose::WasmJwk; +use crate::jose::IJwkParams; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[wasm_bindgen(js_name = CompositeJwk, inspectable)] +pub struct WasmCompositeJwk(pub(crate) CompositeJwk); + +#[wasm_bindgen(js_class = CompositeJwk)] +impl WasmCompositeJwk { + #[wasm_bindgen(constructor)] + pub fn new(jwk: IJwkParams) -> Self { + let jwk: CompositeJwk = jwk.into_serde().unwrap(); + Self(jwk) + } + + #[wasm_bindgen] + /// Get the `algId` value. + pub fn alg_id(&self) -> WasmCompositeAlgId { + //let alg: CompositeAlgId = alg.into_serde().wasm_result()?; + //JsValue::from(self.0.alg_id()).unchecked_into() + JsValue::from_serde(&self.0.alg_id()).unwrap() + .unchecked_into() + + + } + + /// Get the post-quantum public key in Jwk format. + #[wasm_bindgen] + pub fn pq_public_key(&self) -> WasmJwk { + //JsValue::from(self.0.pq_public_key()).unchecked_into() + todo!() + } + + #[wasm_bindgen] + /// Get the traditional public key in Jwk format. + pub fn traditional_public_key(&self) -> WasmJwk { + //self.0.traditional_public_key().map(JsValue::from) + todo!() + } + +} + +impl From for CompositeJwk { + fn from(value: WasmCompositeJwk) -> Self { + value.0 + } +} + + +impl From for WasmCompositeJwk { + fn from(value: CompositeJwk) -> Self { + WasmCompositeJwk(value) + } +} \ No newline at end of file diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 80e49f2af6..0c50c67c8e 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -1,6 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ use identity_iota::verification::jose::jwk::Jwk; use identity_iota::verification::jose::jwk::JwkOperation; use identity_iota::verification::jose::jwk::JwkParams; diff --git a/bindings/wasm/src/jose/mod.rs b/bindings/wasm/src/jose/mod.rs index 9d6075dbb3..24a7024abd 100644 --- a/bindings/wasm/src/jose/mod.rs +++ b/bindings/wasm/src/jose/mod.rs @@ -1,14 +1,20 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ + mod decoded_jws; mod jwk; +mod compositejwk; mod jws_header; mod jwu; mod types; pub use decoded_jws::*; pub use jwk::*; +pub use compositejwk::*; pub use jws_header::*; pub use jwu::*; pub use types::*; diff --git a/bindings/wasm/src/jose/types.rs b/bindings/wasm/src/jose/types.rs index 266e811239..b9679da794 100644 --- a/bindings/wasm/src/jose/types.rs +++ b/bindings/wasm/src/jose/types.rs @@ -7,6 +7,7 @@ use identity_iota::verification::jws::JwsAlgorithm; use js_sys::JsString; use std::str::FromStr; use wasm_bindgen::prelude::*; +use identity_iota::verification::jwk::CompositeAlgId; #[wasm_bindgen] extern "C" { @@ -45,3 +46,15 @@ impl TryFrom for JwsAlgorithm { } } } + +impl TryFrom for CompositeAlgId { + type Error = JsValue; + fn try_from(value: WasmCompositeAlgId) -> Result { + if let Ok(js_string) = value.dyn_into::() { + CompositeAlgId::from_str(String::from(js_string).as_ref()) + .map_err(|err| js_sys::Error::new(&err.to_string()).into()) + } else { + Err(js_sys::Error::new("invalid CompositeAlgId").into()) + } + } +} diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index b92138d8e9..d96e1be84d 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -1,6 +1,8 @@ // Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 +use std::str::FromStr; + use crate::jwk::Jwk; /// Mame of algorithms used to generate the hybrid signature. Values taken from [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-02#name-domain-separators). @@ -57,3 +59,16 @@ impl CompositeJwk { &self.traditional_public_key } } + +impl FromStr for CompositeAlgId { + type Err = crate::error::Error; + + fn from_str(string: &str) -> std::result::Result { + match string { + "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), + "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), + #[cfg(not(feature = "custom_alg"))] + _ => Err(crate::error::Error::JwsAlgorithmParsingError), + } + } +} From 634026c56daff9891ab2b3925849fd395fb9243d Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:30:08 +0100 Subject: [PATCH 112/163] bind create jws for hybrid --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 2456e0286c..5cb419becd 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -16,9 +16,15 @@ use crate::storage::WasmStorage; use super::CoreDocumentLock; use super::WasmCoreDocument; use wasm_bindgen::prelude::*; - +use identity_iota::storage::storage::JwkDocumentExtHybrid; use crate::jpt::WasmProofAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; +use js_sys::Promise; +use identity_iota::storage::JwsSignatureOptions; +use crate::did::PromiseJws; +use crate::storage::WasmJwsSignatureOptions; +use crate::credential::WasmJws; +use wasm_bindgen_futures::future_to_promise; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { @@ -93,4 +99,28 @@ impl WasmCoreDocument { "0".to_string() } + #[wasm_bindgen(js_name = createHybridJws)] + pub fn create_hybrid_jws( + &self, + storage: &WasmStorage, + fragment: String, + payload: String, + options: &WasmJwsSignatureOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_jws(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .await + .wasm_result() + .map(WasmJws::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + } From 787274f13f2ec0b439518540dc87d8d1f12e125d Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:08:23 +0100 Subject: [PATCH 113/163] create bindings fro create_jws_pqc --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 5cb419becd..72fa3f9eb2 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -16,6 +16,7 @@ use crate::storage::WasmStorage; use super::CoreDocumentLock; use super::WasmCoreDocument; use wasm_bindgen::prelude::*; +use identity_iota::storage::storage::JwsDocumentExtPQC; use identity_iota::storage::storage::JwkDocumentExtHybrid; use crate::jpt::WasmProofAlgorithm; use jsonprooftoken::jpa::algs::ProofAlgorithm; @@ -123,4 +124,28 @@ impl WasmCoreDocument { Ok(promise.unchecked_into()) } + #[wasm_bindgen(js_name = createPqJws)] + pub fn _create_pq_jws( + &self, + storage: &WasmStorage, + fragment: String, + payload: String, + options: &WasmJwsSignatureOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_jws_pqc(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .await + .wasm_result() + .map(WasmJws::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + } From 2071dd533119b6c24e29164fb5b00f897e7469b4 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:50:22 +0100 Subject: [PATCH 114/163] add ml dsa 44 verifier --- identity_jose/Cargo.toml | 2 +- identity_pqc_verifier/Cargo.toml | 5 +- identity_pqc_verifier/src/lib.rs | 2 + identity_pqc_verifier/src/oqs_verifier.rs | 28 ++++++++++ identity_pqc_verifier/src/pqc_verifier.rs | 3 +- identity_pqc_verifier/src/pqclean_verifier.rs | 53 +++++++++++++++++++ 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 identity_pqc_verifier/src/pqclean_verifier.rs diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 622ac4f347..14061f3059 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -14,7 +14,7 @@ description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true identity_core = { version = "=1.3.1", path = "../identity_core" } -iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha", "ed25519" ] } json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 7698e6cfad..097b4558cf 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -12,8 +12,9 @@ rust-version.workspace = true description = "JWS PQC signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = true } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } +pqcrypto = "0.18.0" [features] ML_DSA_44 = [] @@ -56,4 +57,4 @@ default = [ "FALCON512", "FALCON1024", - ] \ No newline at end of file + ] diff --git a/identity_pqc_verifier/src/lib.rs b/identity_pqc_verifier/src/lib.rs index a4cbc5cbed..676f0c8998 100644 --- a/identity_pqc_verifier/src/lib.rs +++ b/identity_pqc_verifier/src/lib.rs @@ -3,6 +3,8 @@ mod oqs_verifier; mod pqc_verifier; +mod pqclean_verifier; pub use oqs_verifier::*; pub use pqc_verifier::*; +pub use pqclean_verifier::*; diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index e85db05c29..4db6c9a270 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -52,3 +52,31 @@ impl OQSVerifier { ) } } + +#[cfg(test)] +mod tests { + use oqs::sig::{Algorithm, Sig}; + + + #[test] + fn sig_and_verify(){ + // generate random keypair + + oqs::init(); + let scheme = Sig::new(Algorithm::Dilithium2).unwrap(); + + let (pk, sk) = scheme.keypair().unwrap(); + + println!("Public key: {:?}", pk); + + let message = b"prova"; + + let signature = scheme.sign(message, &sk).unwrap(); + println!("Signature: {:?}", signature); + + scheme.verify(message, &signature, &pk).unwrap(); + + assert!(true) + } +} + diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 5d8bcb3e9c..2ebef19758 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -10,6 +10,7 @@ use identity_jose::jws::VerificationInput; use oqs::sig::Algorithm; use crate::OQSVerifier; +use crate::PQCleanVerifier; /// An implementor of [`JwsVerifier`] that can handle the /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) @@ -39,7 +40,7 @@ impl JwsVerifier for PQCJwsVerifier { fn verify(&self, input: VerificationInput, public_key: &Jwk) -> std::result::Result<(), SignatureVerificationError> { match input.alg { #[cfg(feature = "ML_DSA_44")] - JwsAlgorithm::ML_DSA_44 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium2), + JwsAlgorithm::ML_DSA_44 => PQCleanVerifier::verify(input, public_key), #[cfg(feature = "ML_DSA_65")] JwsAlgorithm::ML_DSA_65 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), #[cfg(feature = "ML_DSA_87")] diff --git a/identity_pqc_verifier/src/pqclean_verifier.rs b/identity_pqc_verifier/src/pqclean_verifier.rs new file mode 100644 index 0000000000..f045bb2793 --- /dev/null +++ b/identity_pqc_verifier/src/pqclean_verifier.rs @@ -0,0 +1,53 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; + +use identity_jose::jwk::Jwk; +use identity_jose::jwk::JwkParamsPQ; +use identity_jose::jws::SignatureVerificationError; +use identity_jose::jws::SignatureVerificationErrorKind; +use identity_jose::jws::VerificationInput; +use pqcrypto::sign::mldsa44::{PublicKey, DetachedSignature, verify_detached_signature}; +use pqcrypto::traits::sign::{PublicKey as PkTrait, DetachedSignature as DTSTrait}; + + +/// A verifier that can handle the PQC algorithms. +#[derive(Debug)] +#[non_exhaustive] +pub struct PQCleanVerifier; + +impl PQCleanVerifier { + /// Verify a JWS signature secured with the on the ML-DSA-44 defined in pqcrypto. + pub fn verify(input: VerificationInput, public_key: &Jwk) -> Result<(), SignatureVerificationError> { + + // Obtain an ML-DSA-44 public key. + let params: &JwkParamsPQ = public_key + .try_pq_params() + .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; + let pk = identity_jose::jwu::decode_b64(params.public.as_str()).map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) + .with_custom_message("could not decode 'pub' parameter from jwk") + })?; + + let public_key = PublicKey::from_bytes(&pk) + .map_err(|_| SignatureVerificationError::new( + SignatureVerificationErrorKind::KeyDecodingFailure, + ))?; + + let signature = DetachedSignature::from_bytes(input.decoded_signature.deref()) + .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?; + + verify_detached_signature(&signature, &input.signing_input, &public_key) + .map_err(|_|SignatureVerificationErrorKind::InvalidSignature)?; + + Ok(()) + +} +} + +#[cfg(test)] +mod tests { + +} + From ec1a20ed76c4acc09387fcd56db0db335fdd6f50 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:20:13 +0100 Subject: [PATCH 115/163] typo --- examples/2_pqc/revocation_zk.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/2_pqc/revocation_zk.rs b/examples/2_pqc/revocation_zk.rs index a592cf44d1..b46bdba5b9 100644 --- a/examples/2_pqc/revocation_zk.rs +++ b/examples/2_pqc/revocation_zk.rs @@ -119,7 +119,7 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); + println!("{} {} {} {} {}", "[Issuer]".red(), " -> ", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); @@ -205,7 +205,7 @@ async fn main() -> anyhow::Result<()> { StatusCheck::Strict, ).unwrap(); - println!("{}: JPT successfully verified, access granted", "[Verifier]".green()); + println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); println!(""); From 6e3aa7058f59976b34a799a15932321cced44293 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:32:43 +0100 Subject: [PATCH 116/163] minimal update to generate and sign mldsa44, tbc --- Cargo.toml | 2 +- identity_storage/Cargo.toml | 1 + identity_storage/src/key_storage/memstore.rs | 55 +++++++++----------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 93857fb64e..c4e62f6993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ serde_json = { version = "1.0", default-features = false } json-proof-token = { version = "0.3.5" } zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } - +pqcrypto = "0.18.0" [workspace.package] diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index ec4c73b807..13beb40d0c 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -32,6 +32,7 @@ thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } zkryptium = { workspace = true, optional = true } oqs = { workspace = true, optional = true } +pqcrypto = { workspace = true } [dev-dependencies] identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 58236434a5..2343d932a7 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -325,7 +325,7 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: &JwsAlgorithm) -> mod pqc_liboqs { use std::str::FromStr; use async_trait::async_trait; - use crypto::signatures::ed25519::SecretKey; + //use crypto::signatures::ed25519::SecretKey; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; @@ -347,6 +347,10 @@ mod pqc_liboqs { use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; use crate::JwkGenOutput; + //TODO mod pqcrypto + use pqcrypto::sign::mldsa44::{SecretKey, DetachedSignature, detached_sign, PublicKey, keypair}; + use pqcrypto::traits::sign::{SecretKey as SKTrait, DetachedSignature as DTSTrait, PublicKey as PKTrait}; + fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { match alg { JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), @@ -377,7 +381,6 @@ mod pqc_liboqs { } } - //TODO: PQ - JwkStoragePQ /// JwkStoragePQ implementation for JwkMemStore #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] @@ -393,10 +396,10 @@ mod pqc_liboqs { ); } - let oqs_alg = check_pq_alg_compatibility(alg)?; - oqs::init(); //TODO: check what this function does + //let oqs_alg = check_pq_alg_compatibility(alg)?; + // oqs::init(); //TODO: check what this function does - let scheme = Sig::new(oqs_alg).map_err(|err| { +/* let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("signature scheme init failed")) .with_source(err) @@ -405,12 +408,14 @@ mod pqc_liboqs { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("keypair generation failed!")) .with_source(err) - })?; + })?; */ + + let (pk, sk) = keypair(); let kid: KeyId = random_key_id(); - let public = jwu::encode_b64(pk.into_vec()); - let private = jwu::encode_b64(sk.into_vec()); + let public = jwu::encode_b64(pk.as_bytes()); + let private = jwu::encode_b64(sk.as_bytes()); let mut jwk_params = match alg { JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), @@ -477,7 +482,7 @@ mod pqc_liboqs { JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) })?; - let oqs_alg = check_pq_alg_compatibility(alg)?; + // let oqs_alg = check_pq_alg_compatibility(alg)?; // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. match alg { @@ -517,7 +522,7 @@ mod pqc_liboqs { let params = jwk.try_pq_params().unwrap(); - let sk = params + let sk_bytes = params .private .as_deref() .map(jwu::decode_b64) @@ -530,27 +535,15 @@ mod pqc_liboqs { .with_custom_message("unable to decode `d` param") .with_source(err) })?; - - oqs::init(); //TODO: check what this function does - - let scheme = Sig::new(oqs_alg).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature scheme init failed")) - .with_source(err) - })?; - - let secret_key = scheme.secret_key_from_bytes(&sk).ok_or( - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected key of length {}", SecretKey::LENGTH)), - )?; - - let signature = scheme.sign(&data, secret_key).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature computation failed")) - .with_source(err) - })?; - - Ok(signature.into_vec()) + println!("1111111111111111111111111111111111111111111111111111111111111111111"); + + let sk = SecretKey::from_bytes(&sk_bytes) + .map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; + println!("22222222222222222222222222222222222222222222222222222222222222222222222222222222"); + + let signature: DetachedSignature = detached_sign(&data, &sk); + println!("3333333333333333333333333333333333333333333333333333333333"); + Ok(signature.as_bytes().to_vec()) } } } From bdc6d2196d82f4c6de99de459a2aa9904049cf23 Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:24:16 +0100 Subject: [PATCH 117/163] typo --- examples/2_pqc/revocation_zk.rs | 22 +++++++++++----------- examples/2_pqc/traditional_zk.rs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/2_pqc/revocation_zk.rs b/examples/2_pqc/revocation_zk.rs index b46bdba5b9..7efc6a6292 100644 --- a/examples/2_pqc/revocation_zk.rs +++ b/examples/2_pqc/revocation_zk.rs @@ -67,7 +67,7 @@ async fn main() -> anyhow::Result<()> { alice_document.insert_service(service).unwrap(); - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk with the Revocationbitmap:", alice_document.id().as_str()); + println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); let subject: Subject = Subject::from_json_value(json!({ "id": alice_document.id().as_str(), @@ -119,7 +119,7 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {} {}", "[Issuer]".red(), " -> ", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); + println!("{} {} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); @@ -225,6 +225,8 @@ async fn main() -> anyhow::Result<()> { println!("{} {} {}", "[Verifier]".green(), ": Verify the validity of timeframe :", timeframe_result.unwrap_err()); + println!("{} : Access denied", "[Verifier]".green()); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request update Timeframe"); println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending a challenge"); @@ -245,11 +247,11 @@ async fn main() -> anyhow::Result<()> { &JwtPresentationOptions::default().expiration_date(expires), ).await?; - println!("{} {} ","[Issuer]".red(), ": Generate a Verifiable Presentation (VP) from the expired VC including the challenge and a new expiry timestamp"); + println!("{} {} ","[Holder]".blue(), ": Generate a Verifiable Presentation (VP) from the expired VC including the challenge and a new expiry timestamp"); - println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Sending VP (as JWT):", presentation_jwt.as_str()); + println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Sending VP (as JWT)"); - println!("{} {} ","[Issuer]".red(), ": Resolve Issuer's DID and verify the VP"); + println!("{} {} ","[Issuer]".red(), ": Resolve Holder's DID and verify the VP"); let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default().nonce(challenge.to_owned()); @@ -266,7 +268,7 @@ async fn main() -> anyhow::Result<()> { EdDSAJwsVerifier::default(), ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; - println!("{} {} ","[Issuer]".red(), ": Verify the ZK VC inside the VP"); + println!("{} {} ","[Issuer]".red(), ": Verify the VC inside the VP"); let validation_options: JptCredentialValidationOptions = JptCredentialValidationOptions::default() .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); @@ -370,15 +372,13 @@ async fn main() -> anyhow::Result<()> { println!("{} {} ","[Issuer]".red(), ": Decide to revoke the Holder's Credential"); - println!("{} {} {}", "[Issuer]".red(), ": Update the Bitmap and publish the DID Document (with did:web method) at", did_url); + println!("{} {} {}", "[Issuer]".red(), ": Update the Bitmap and publish the updated DID Document (with did:web method) at", did_url); issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; write_to_file(&issuer_document, Some(path_did_file))?; - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access (after Issuer revoke)"); - - println!("{} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Resolve Issuer's DID and verifies the Presentation"); + println!("{} {}", "[Holder]".blue(), ": Check the revocations status of his credentias"); let client= ClientBuilder::new() .danger_accept_invalid_certs(true) @@ -403,7 +403,7 @@ async fn main() -> anyhow::Result<()> { StatusCheck::Strict, ); - println!("{} {} {}", "[Verifier]".green(), " : Error ", revocation_result.unwrap_err()); + println!("{} {} {}", "[Holder]".blue(), ": Revocation status:", revocation_result.unwrap_err()); Ok(()) } diff --git a/examples/2_pqc/traditional_zk.rs b/examples/2_pqc/traditional_zk.rs index e2de9dab65..31515be137 100644 --- a/examples/2_pqc/traditional_zk.rs +++ b/examples/2_pqc/traditional_zk.rs @@ -85,7 +85,7 @@ async fn main() -> anyhow::Result<()> { None, ).await?; - println!("{} {} {} {}", "[Issuer]".red(), " -> [Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); + println!("{} {} {} {} {}", "[Issuer]".red(), " -> ", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); @@ -116,7 +116,7 @@ async fn main() -> anyhow::Result<()> { .kid() .unwrap(); - println!("{} : Engages in the Selective Disclosure of credential's attributes", "[Holder]".blue()); + println!("{} : Engages in the Selective Disclosure of credential's attributes (Name and GPA)", "[Holder]".blue()); let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); selective_disclosure_presentation From 9786a7b76a34ace17acd602f36a937aa5e0bc7d6 Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Tue, 14 Jan 2025 11:11:46 +0100 Subject: [PATCH 118/163] pqc verifier depending only on liboqs --- Cargo.toml | 2 +- identity_pqc_verifier/Cargo.toml | 3 +- identity_pqc_verifier/src/lib.rs | 2 - identity_pqc_verifier/src/oqs_verifier.rs | 18 +++++++ identity_pqc_verifier/src/pqc_verifier.rs | 7 ++- identity_pqc_verifier/src/pqclean_verifier.rs | 53 ------------------- 6 files changed, 23 insertions(+), 62 deletions(-) delete mode 100644 identity_pqc_verifier/src/pqclean_verifier.rs diff --git a/Cargo.toml b/Cargo.toml index c4e62f6993..7b52dcc87a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ strum = { version = "0.25", default-features = false, features = ["std", "derive serde_json = { version = "1.0", default-features = false } json-proof-token = { version = "0.3.5" } zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } -oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } +oqs = {version = "0.10.0", default-features = false, features = ["sigs", "std", "vendored"] } pqcrypto = "0.18.0" diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 097b4558cf..019394cfae 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -13,8 +13,7 @@ description = "JWS PQC signature verification for IOTA Identity" [dependencies] identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = true } -oqs = {version = "0.9.0", default-features = false, features = ["sigs", "std", "vendored"] } -pqcrypto = "0.18.0" +oqs.workspace = true [features] ML_DSA_44 = [] diff --git a/identity_pqc_verifier/src/lib.rs b/identity_pqc_verifier/src/lib.rs index 676f0c8998..a4cbc5cbed 100644 --- a/identity_pqc_verifier/src/lib.rs +++ b/identity_pqc_verifier/src/lib.rs @@ -3,8 +3,6 @@ mod oqs_verifier; mod pqc_verifier; -mod pqclean_verifier; pub use oqs_verifier::*; pub use pqc_verifier::*; -pub use pqclean_verifier::*; diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 4db6c9a270..cd81a160af 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -78,5 +78,23 @@ mod tests { assert!(true) } + + const NOBLE_PUB_KEY: &[u8] = &[209,131,252,5,180,196,210,99,214,113,221,246,97,94,45,90,147,37,94,209,79,92,237,114,51,54,130,154,79,33,98,96,191,71,153,75,190,253,62,58,150,79,104,91,158,229,105,8,225,119,116,50,82,210,92,222,12,144,125,161,148,117,26,164,102,25,66,244,178,254,2,102,55,30,117,234,109,151,178,151,210,207,39,49,117,70,253,178,235,237,183,189,145,197,116,235,132,236,147,66,175,236,234,202,65,218,212,200,75,101,214,178,48,207,197,174,75,224,183,47,3,2,14,192,88,205,166,60,19,219,67,158,147,148,229,16,85,244,8,123,51,44,42,0,18,179,136,193,62,255,100,129,228,14,125,53,191,127,8,32,109,223,155,223,98,60,46,159,40,134,144,253,121,89,107,74,32,58,88,181,237,6,76,223,215,193,87,212,210,190,236,243,72,107,209,66,245,239,133,99,145,174,59,151,2,93,88,145,240,105,46,158,213,210,204,71,209,221,62,105,113,75,189,63,120,125,151,124,58,79,133,104,200,87,63,33,60,142,21,37,3,102,254,135,210,13,208,155,32,222,162,63,127,17,10,60,46,228,234,59,75,57,97,117,229,245,89,9,55,52,69,73,223,50,70,70,16,64,118,51,105,196,18,96,247,43,24,139,146,137,67,31,101,82,42,214,177,41,125,216,202,5,212,40,97,244,145,180,236,206,170,124,192,126,227,254,86,45,6,221,200,235,143,228,162,88,248,155,36,169,45,147,111,2,249,186,179,123,150,221,223,176,131,170,216,129,241,222,5,195,255,241,1,39,129,119,100,217,175,249,18,3,131,89,231,46,70,21,147,72,89,162,194,25,113,78,213,130,66,218,42,149,228,205,36,59,108,57,189,171,200,57,246,37,6,143,159,84,129,205,132,132,146,41,237,76,93,103,1,3,130,61,116,171,16,82,255,107,113,223,89,37,69,184,161,83,11,92,160,201,154,5,10,200,17,197,39,25,91,32,129,143,6,226,146,172,97,191,197,204,29,128,226,60,17,9,171,178,78,148,17,30,96,223,77,180,156,31,236,59,236,168,238,113,252,59,209,154,227,149,204,167,63,210,45,75,173,247,126,247,216,232,74,216,15,158,109,89,88,17,54,117,165,4,52,2,10,148,27,185,24,124,182,175,145,38,45,224,236,106,94,156,195,120,200,94,114,17,62,64,188,50,11,161,82,115,12,147,222,94,90,123,227,88,129,58,140,138,120,241,168,35,42,78,236,9,196,213,180,60,229,99,127,252,154,61,45,174,1,148,92,215,9,130,50,239,143,181,3,34,13,79,51,63,49,157,64,86,121,88,213,225,116,159,155,48,155,84,199,254,148,247,93,48,119,249,71,241,109,144,82,8,14,116,4,41,146,112,20,251,30,211,8,123,164,59,217,231,4,66,70,103,247,224,232,37,131,212,51,212,82,131,166,252,184,190,115,238,165,212,37,53,156,173,97,254,90,43,179,232,165,38,99,72,174,70,247,163,73,162,225,233,73,98,89,225,128,148,172,206,211,249,0,160,242,242,121,5,148,202,251,62,17,220,89,101,80,183,204,186,241,28,17,231,43,2,71,167,92,111,240,166,110,0,47,188,75,251,35,72,148,115,23,157,35,165,132,252,232,57,12,60,41,104,192,57,143,227,45,199,13,146,33,176,234,160,246,21,65,195,231,98,255,146,118,50,49,11,119,175,129,12,63,52,162,90,143,152,32,58,137,61,169,23,247,85,228,85,157,238,57,61,40,220,183,240,171,169,241,246,112,59,255,221,9,19,204,39,80,105,96,85,199,183,91,137,180,52,29,194,5,32,45,51,30,197,26,28,81,130,141,28,138,35,123,236,36,202,72,0,40,109,12,61,175,252,60,216,42,178,95,12,73,188,129,191,235,69,0,153,88,10,103,248,210,20,238,197,94,213,186,32,55,80,194,128,199,45,83,199,51,237,18,88,217,38,102,231,4,106,208,209,170,54,103,189,97,148,141,218,114,196,178,40,66,238,75,43,130,86,183,10,39,228,225,69,91,39,11,93,181,61,157,149,2,32,23,67,78,212,117,45,26,226,21,220,169,201,41,114,44,130,240,47,152,115,54,243,240,98,186,195,120,110,151,182,10,19,24,71,168,162,216,225,5,68,39,12,189,26,37,196,25,151,61,242,247,121,107,32,6,72,44,147,186,133,50,93,255,157,186,147,161,163,219,99,35,236,180,226,11,40,154,56,132,19,106,252,209,231,85,234,80,244,211,234,128,123,99,130,210,16,51,13,65,212,230,196,113,152,47,206,7,166,37,129,105,80,40,85,12,246,204,117,162,178,65,17,185,7,135,216,102,108,125,233,133,227,96,100,142,100,16,189,148,47,17,87,47,154,170,55,9,178,118,231,61,253,138,204,161,195,95,99,211,120,67,255,26,134,227,191,126,112,16,72,148,240,198,215,40,29,229,32,167,153,78,105,56,166,56,23,28,175,132,68,198,172,123,192,192,245,201,203,223,108,207,192,180,105,148,123,149,88,229,80,134,130,190,195,124,107,184,28,106,76,234,125,104,250,159,134,226,23,74,66,110,132,164,32,130,121,223,89,5,8,55,196,210,91,44,196,17,209,86,53,65,193,179,108,118,87,232,92,2,4,214,191,235,127,189,53,209,145,40,234,27,70,105,50,129,147,39,153,42,211,146,76,243,203,146,86,7,116,195,77,27,24,106,243,190,3,23,174,109,219,52,45,190,245,47,164,11,201,63,170,3,117,59,59,254,232,197,248,79,40,157,131,3,30,30,103,179,167,25,11,207,122,215,31,190,0,109,81,151,4,200,209,178,48,29,115,103,159,211,54,220,124,70,205,216,178,215,114,37,249,169,40,252,137,7,21,222,205,211,57,117,210,17,253,1,100,80,33,184,110,205,29,7,225,235,122,53,214,95,169,215,185,53,24,199,155]; + const NOBLE_MSG: &[u8] = b"pq-idenity.rs"; + const NOBLE_SIGNATURE: &[u8] = &[141,47,4,215,33,246,2,43,59,126,229,138,192,102,222,241,120,4,117,30,149,66,16,134,102,28,160,233,112,241,187,171,114,202,3,252,199,61,39,69,107,64,81,26,220,52,23,34,173,156,62,184,110,116,105,44,158,190,112,146,115,116,50,76,102,245,132,34,127,218,40,133,194,120,5,91,11,36,36,120,162,172,25,158,9,232,162,158,120,29,19,134,220,139,62,2,173,225,163,108,85,166,142,72,18,134,116,4,45,143,232,32,134,107,10,249,252,149,156,136,24,10,21,184,109,132,239,193,100,37,206,215,19,23,253,112,193,97,147,117,236,253,49,230,50,225,2,219,40,91,189,131,206,55,70,18,170,160,32,178,95,244,163,228,34,232,66,240,106,56,44,212,220,221,152,56,169,247,40,215,231,200,142,81,137,68,84,248,216,238,237,201,253,36,150,97,152,189,183,238,51,194,123,102,129,115,10,176,184,25,151,152,107,182,91,3,57,4,234,102,85,67,219,76,197,42,156,210,70,189,245,154,25,55,146,25,254,40,124,31,222,237,165,203,136,133,188,201,62,61,76,137,238,209,16,242,45,130,239,29,161,57,73,48,73,43,91,246,131,56,34,48,101,55,182,160,8,158,226,64,32,144,104,82,166,71,153,246,230,216,112,50,186,83,156,98,4,2,32,28,85,5,235,190,5,9,225,31,117,90,235,49,222,8,223,218,229,173,106,196,67,202,198,6,17,239,146,30,31,197,232,198,215,206,219,246,250,54,57,217,195,179,142,165,177,2,48,217,42,42,213,37,134,252,2,65,234,182,26,53,191,39,93,137,75,25,54,223,68,1,53,74,40,214,83,9,255,89,140,94,111,197,83,225,244,10,64,185,193,44,10,121,231,231,131,185,84,83,181,181,222,118,224,30,116,121,124,56,170,86,108,94,131,56,53,126,5,150,75,48,229,142,91,36,21,137,17,81,186,52,162,192,230,9,113,129,85,224,140,72,14,252,154,153,52,62,49,38,131,81,98,224,24,54,42,59,139,234,215,170,107,69,74,196,142,66,75,5,253,124,34,239,152,234,10,79,90,203,139,3,49,159,6,10,93,92,46,160,7,197,155,107,190,183,251,135,54,159,153,124,100,94,173,36,62,147,155,88,197,21,66,61,94,87,238,14,240,65,86,192,20,217,93,33,28,131,103,74,226,30,60,158,50,132,169,79,140,8,120,1,20,165,30,23,211,46,103,122,212,212,63,213,5,23,79,37,42,100,239,7,246,110,119,253,152,141,228,164,138,165,114,163,149,198,169,130,195,210,229,130,45,107,108,244,11,168,129,110,179,130,203,240,53,221,217,213,183,97,215,58,8,139,48,26,184,8,98,103,173,185,186,242,203,107,145,115,128,154,251,38,226,96,201,233,39,237,234,80,118,100,94,52,202,114,150,56,10,207,66,61,103,15,7,231,62,153,214,196,207,1,183,199,120,88,141,60,104,147,208,123,242,245,141,106,39,227,25,41,24,9,154,195,252,104,118,243,194,230,114,234,119,32,18,104,186,54,210,218,167,100,86,13,83,135,114,163,166,218,200,162,78,241,167,215,172,113,199,197,68,21,92,28,87,36,89,121,19,255,104,14,147,14,76,132,50,116,24,139,199,154,31,220,32,76,185,130,217,219,178,0,248,132,186,133,192,208,119,26,235,164,90,240,128,207,15,18,160,46,86,236,79,108,90,232,64,27,15,186,189,139,81,193,105,220,9,248,239,50,219,237,186,218,66,229,25,194,28,4,78,229,179,36,252,142,20,200,67,118,33,68,51,187,177,165,227,18,155,133,140,145,144,33,44,36,174,5,74,201,171,70,213,129,123,53,212,240,173,91,238,170,62,195,110,208,14,185,195,201,213,113,26,107,26,164,168,71,209,21,104,241,41,52,190,8,223,222,66,8,183,154,121,207,172,35,98,204,133,146,221,225,126,170,80,133,192,28,106,194,149,228,217,220,99,140,43,135,45,154,243,118,196,33,241,0,48,128,90,82,169,218,157,170,31,228,134,249,218,12,22,150,191,171,4,27,213,97,85,255,215,71,25,253,236,70,43,16,178,155,44,121,161,177,102,46,213,217,136,53,230,97,225,89,201,87,107,147,231,133,12,34,76,122,175,189,233,216,78,183,1,95,207,53,131,111,177,63,150,18,78,8,248,19,35,224,52,196,165,37,107,80,131,62,55,251,34,182,112,233,136,87,116,95,235,50,200,226,14,24,165,143,114,53,37,186,4,237,159,9,88,49,29,79,12,84,143,224,165,254,72,53,100,64,157,74,10,13,122,222,208,170,42,123,211,16,200,73,145,202,85,145,82,241,110,90,200,221,187,109,125,251,29,50,157,141,169,46,178,102,58,235,48,68,28,59,102,119,237,177,95,232,180,34,241,65,88,69,188,52,176,75,99,145,65,189,47,63,57,140,82,142,210,52,44,252,228,201,163,6,220,91,247,170,90,205,248,198,138,105,190,214,168,191,225,253,32,254,54,125,15,9,27,75,26,5,115,132,23,252,81,160,78,112,185,245,127,229,83,164,21,105,207,29,3,58,164,220,85,102,27,87,99,92,44,170,189,139,129,155,1,22,50,54,176,119,122,64,231,251,82,26,113,14,32,150,72,122,159,223,115,232,52,236,41,5,224,188,248,195,8,139,76,200,214,188,39,169,73,90,150,206,218,97,192,228,215,115,202,42,253,121,10,47,191,145,35,145,66,120,36,105,11,84,132,31,98,154,48,17,102,58,162,43,90,139,62,146,90,92,138,69,231,20,96,236,233,6,227,58,21,160,253,27,138,167,39,37,181,167,131,1,146,237,31,134,66,119,144,206,27,109,183,117,86,208,241,180,168,234,143,100,26,166,67,168,75,52,221,128,51,111,118,25,168,88,240,61,97,180,112,2,162,34,198,136,147,126,159,116,174,109,60,25,141,11,51,217,35,121,115,241,179,56,90,135,138,5,126,223,252,105,155,126,173,61,198,121,112,168,111,147,85,80,87,43,210,242,102,14,106,156,3,55,31,29,219,149,154,161,67,193,37,70,126,167,64,21,53,144,202,53,177,246,223,6,205,107,5,180,93,44,63,96,189,183,22,55,217,121,64,41,103,94,185,247,100,41,130,99,200,248,68,62,178,8,174,55,34,241,121,239,137,53,229,187,152,30,53,12,182,128,167,243,72,114,250,19,161,212,183,151,3,140,78,222,251,86,112,66,188,16,111,188,106,215,126,228,24,100,141,65,58,52,188,170,82,41,155,80,182,95,73,237,133,137,246,15,60,88,43,250,112,110,25,38,173,58,88,218,101,234,45,52,159,73,58,40,94,20,107,75,181,142,151,182,117,90,3,229,96,195,59,57,153,141,135,235,213,177,167,151,153,164,216,46,121,236,221,229,170,207,3,118,17,138,214,151,132,91,209,154,200,122,50,6,47,121,84,186,62,51,136,124,189,174,24,248,35,137,158,11,79,170,183,160,61,47,179,52,170,15,8,5,156,63,53,140,139,77,76,165,97,143,253,255,192,219,128,6,57,15,134,3,19,214,107,206,145,74,104,58,248,176,197,100,73,173,189,16,240,74,115,198,51,43,100,185,63,125,83,200,187,123,118,81,23,168,55,163,92,13,193,56,12,176,147,84,151,243,105,230,61,78,166,225,34,132,221,201,225,57,203,162,98,181,62,206,137,205,100,162,181,123,41,16,40,242,212,212,18,162,166,170,26,222,19,122,125,22,62,206,182,62,234,12,59,96,41,61,215,254,84,144,20,53,229,75,25,196,28,118,3,19,161,252,186,238,147,53,204,10,153,144,48,45,148,33,7,216,98,209,236,194,147,76,111,80,100,225,184,75,163,98,167,25,169,58,151,183,60,71,200,104,208,161,192,70,184,99,176,216,128,28,25,205,252,106,176,107,13,157,77,32,234,60,114,119,111,237,250,179,138,159,160,90,143,82,29,183,117,190,122,4,245,164,125,145,24,128,31,215,226,74,234,59,122,184,247,222,64,96,113,169,250,190,176,68,158,215,231,173,221,41,59,229,120,184,218,139,127,202,145,154,157,85,219,48,102,193,141,148,245,212,94,231,47,171,203,78,147,95,207,102,51,180,27,244,59,251,128,171,222,227,141,85,226,244,7,44,206,123,108,69,85,193,158,98,228,5,18,192,70,35,86,255,5,136,214,117,192,200,97,251,171,183,233,163,30,172,68,231,41,175,84,94,166,126,91,145,157,196,190,145,197,144,176,247,97,53,139,185,131,211,106,14,248,167,119,112,243,234,64,19,202,135,115,245,86,89,3,118,44,123,70,64,69,202,158,218,132,84,183,41,87,106,241,152,76,238,208,106,179,154,231,163,236,43,139,162,113,189,216,247,177,139,54,145,19,193,207,225,180,67,108,16,110,60,88,238,35,109,246,53,60,134,200,21,151,117,131,31,174,223,32,227,244,105,53,207,195,25,249,181,177,80,122,101,200,48,1,237,79,242,172,134,164,222,66,28,173,130,86,153,193,154,60,86,107,30,157,222,109,203,120,102,229,27,184,1,27,201,225,18,15,59,117,11,62,36,243,214,17,38,51,229,156,189,66,138,80,220,27,164,170,115,147,181,254,178,160,249,197,228,18,2,165,143,67,226,214,217,69,210,116,79,15,3,71,232,237,100,228,106,141,94,181,118,85,17,125,238,45,182,125,69,155,255,31,207,210,193,115,98,92,66,131,241,173,147,246,125,107,105,21,150,8,32,175,72,168,21,21,222,141,236,117,183,98,44,2,124,166,150,232,132,226,41,144,96,27,234,3,61,113,69,52,18,171,1,29,41,33,29,189,146,155,15,37,252,33,174,143,32,95,157,39,142,98,194,86,76,224,124,173,237,23,217,9,111,115,244,22,100,230,229,100,183,96,76,146,105,90,194,61,165,89,206,57,187,212,60,154,224,223,102,19,243,161,141,195,208,200,43,111,226,107,162,13,116,170,181,197,128,230,9,65,34,31,240,167,151,107,90,130,50,94,140,251,176,20,174,247,23,129,78,175,85,233,124,216,45,147,76,125,123,180,216,238,217,188,153,109,88,223,150,124,9,39,180,83,59,99,230,108,235,182,213,8,48,235,110,236,152,190,226,214,158,190,17,140,175,219,139,133,15,134,244,229,254,89,115,104,6,234,210,158,235,201,125,50,95,39,183,102,215,101,171,146,84,167,120,150,150,43,42,114,41,178,214,22,61,239,214,28,84,46,220,17,26,45,51,73,106,162,197,205,209,212,218,223,9,35,39,82,83,93,95,103,110,111,115,129,155,169,180,188,190,196,209,216,243,34,41,66,90,99,100,116,133,144,159,181,185,190,219,239,250,7,13,18,29,69,71,72,76,91,93,157,163,174,177,226,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,34,50,66]; + + #[test] + fn verify_noble_pq_signature(){ + + oqs::init(); + let scheme = Sig::new(Algorithm::MlDsa44).unwrap(); + + let pk = scheme.public_key_from_bytes(NOBLE_PUB_KEY).unwrap(); + let signature = scheme.signature_from_bytes(NOBLE_SIGNATURE).unwrap(); + + scheme.verify(NOBLE_MSG, &signature, &pk).unwrap(); + + assert!(true) + } } diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 2ebef19758..4789d168d4 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -10,7 +10,6 @@ use identity_jose::jws::VerificationInput; use oqs::sig::Algorithm; use crate::OQSVerifier; -use crate::PQCleanVerifier; /// An implementor of [`JwsVerifier`] that can handle the /// [`JwsAlgorithm::ML_DSA_44`](identity_jose::jws::JwsAlgorithm::ML_DSA_44) @@ -40,11 +39,11 @@ impl JwsVerifier for PQCJwsVerifier { fn verify(&self, input: VerificationInput, public_key: &Jwk) -> std::result::Result<(), SignatureVerificationError> { match input.alg { #[cfg(feature = "ML_DSA_44")] - JwsAlgorithm::ML_DSA_44 => PQCleanVerifier::verify(input, public_key), + JwsAlgorithm::ML_DSA_44 => OQSVerifier::verify(input, public_key, Algorithm::MlDsa44), #[cfg(feature = "ML_DSA_65")] - JwsAlgorithm::ML_DSA_65 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium3), + JwsAlgorithm::ML_DSA_65 => OQSVerifier::verify(input, public_key, Algorithm::MlDsa65), #[cfg(feature = "ML_DSA_87")] - JwsAlgorithm::ML_DSA_87 => OQSVerifier::verify(input, public_key, Algorithm::Dilithium5), + JwsAlgorithm::ML_DSA_87 => OQSVerifier::verify(input, public_key, Algorithm::MlDsa87), #[cfg(feature = "SLH_DSA_SHA2_128s")] JwsAlgorithm::SLH_DSA_SHA2_128s => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128sSimple), diff --git a/identity_pqc_verifier/src/pqclean_verifier.rs b/identity_pqc_verifier/src/pqclean_verifier.rs deleted file mode 100644 index f045bb2793..0000000000 --- a/identity_pqc_verifier/src/pqclean_verifier.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Deref; - -use identity_jose::jwk::Jwk; -use identity_jose::jwk::JwkParamsPQ; -use identity_jose::jws::SignatureVerificationError; -use identity_jose::jws::SignatureVerificationErrorKind; -use identity_jose::jws::VerificationInput; -use pqcrypto::sign::mldsa44::{PublicKey, DetachedSignature, verify_detached_signature}; -use pqcrypto::traits::sign::{PublicKey as PkTrait, DetachedSignature as DTSTrait}; - - -/// A verifier that can handle the PQC algorithms. -#[derive(Debug)] -#[non_exhaustive] -pub struct PQCleanVerifier; - -impl PQCleanVerifier { - /// Verify a JWS signature secured with the on the ML-DSA-44 defined in pqcrypto. - pub fn verify(input: VerificationInput, public_key: &Jwk) -> Result<(), SignatureVerificationError> { - - // Obtain an ML-DSA-44 public key. - let params: &JwkParamsPQ = public_key - .try_pq_params() - .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; - let pk = identity_jose::jwu::decode_b64(params.public.as_str()).map_err(|_| { - SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) - .with_custom_message("could not decode 'pub' parameter from jwk") - })?; - - let public_key = PublicKey::from_bytes(&pk) - .map_err(|_| SignatureVerificationError::new( - SignatureVerificationErrorKind::KeyDecodingFailure, - ))?; - - let signature = DetachedSignature::from_bytes(input.decoded_signature.deref()) - .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?; - - verify_detached_signature(&signature, &input.signing_input, &public_key) - .map_err(|_|SignatureVerificationErrorKind::InvalidSignature)?; - - Ok(()) - -} -} - -#[cfg(test)] -mod tests { - -} - From 60d1c72a7da1f2538d2052e459640d4ad9032af0 Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Tue, 14 Jan 2025 11:42:50 +0100 Subject: [PATCH 119/163] remove pqclean from memstore --- Cargo.toml | 1 - examples/Cargo.toml | 2 +- identity_storage/Cargo.toml | 1 - identity_storage/src/key_storage/memstore.rs | 55 +++++++++++--------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7b52dcc87a..1c12798acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ serde_json = { version = "1.0", default-features = false } json-proof-token = { version = "0.3.5" } zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } oqs = {version = "0.10.0", default-features = false, features = ["sigs", "std", "vendored"] } -pqcrypto = "0.18.0" [workspace.package] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 71868eaeab..2cbeb96ac5 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,7 +9,7 @@ publish = false anyhow = "1.0.62" bls12_381_plus.workspace = true identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false } -identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "hybrid-liboqs"] } +identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "hybrid-liboqs", "resolver"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 13beb40d0c..ec4c73b807 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -32,7 +32,6 @@ thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } zkryptium = { workspace = true, optional = true } oqs = { workspace = true, optional = true } -pqcrypto = { workspace = true } [dev-dependencies] identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 2343d932a7..4e81a9e6d7 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -347,15 +347,11 @@ mod pqc_liboqs { use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; use crate::JwkGenOutput; - //TODO mod pqcrypto - use pqcrypto::sign::mldsa44::{SecretKey, DetachedSignature, detached_sign, PublicKey, keypair}; - use pqcrypto::traits::sign::{SecretKey as SKTrait, DetachedSignature as DTSTrait, PublicKey as PKTrait}; - fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { match alg { - JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::Dilithium2), - JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::Dilithium3), - JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::Dilithium5), + JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::MlDsa44), + JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::MlDsa65), + JwsAlgorithm::ML_DSA_87 => Ok(Algorithm::MlDsa87), JwsAlgorithm::SLH_DSA_SHA2_128s => Ok(Algorithm::SphincsSha2128sSimple), JwsAlgorithm::SLH_DSA_SHAKE_128s => Ok(Algorithm::SphincsShake128sSimple), JwsAlgorithm::SLH_DSA_SHA2_128f => Ok(Algorithm::SphincsSha2128fSimple), @@ -396,10 +392,10 @@ mod pqc_liboqs { ); } - //let oqs_alg = check_pq_alg_compatibility(alg)?; - // oqs::init(); //TODO: check what this function does + let oqs_alg = check_pq_alg_compatibility(alg)?; + oqs::init(); //TODO: check what this function does -/* let scheme = Sig::new(oqs_alg).map_err(|err| { + let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("signature scheme init failed")) .with_source(err) @@ -408,14 +404,12 @@ mod pqc_liboqs { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("keypair generation failed!")) .with_source(err) - })?; */ - - let (pk, sk) = keypair(); + })?; let kid: KeyId = random_key_id(); - let public = jwu::encode_b64(pk.as_bytes()); - let private = jwu::encode_b64(sk.as_bytes()); + let public = jwu::encode_b64(pk.into_vec()); + let private = jwu::encode_b64(sk.into_vec()); let mut jwk_params = match alg { JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), @@ -482,7 +476,7 @@ mod pqc_liboqs { JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) })?; - // let oqs_alg = check_pq_alg_compatibility(alg)?; + let oqs_alg = check_pq_alg_compatibility(alg)?; // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. match alg { @@ -535,15 +529,26 @@ mod pqc_liboqs { .with_custom_message("unable to decode `d` param") .with_source(err) })?; - println!("1111111111111111111111111111111111111111111111111111111111111111111"); - - let sk = SecretKey::from_bytes(&sk_bytes) - .map_err(|_| KeyStorageError::new(KeyStorageErrorKind::Unspecified))?; - println!("22222222222222222222222222222222222222222222222222222222222222222222222222222222"); - - let signature: DetachedSignature = detached_sign(&data, &sk); - println!("3333333333333333333333333333333333333333333333333333333333"); - Ok(signature.as_bytes().to_vec()) + oqs::init(); //TODO: check what this function does + + let scheme = Sig::new(oqs_alg).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature scheme init failed")) + .with_source(err) + })?; + + let secret_key = scheme.secret_key_from_bytes(&sk_bytes).ok_or( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("wrong key length")), + )?; + + let signature = scheme.sign(&data, secret_key).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature computation failed")) + .with_source(err) + })?; + + Ok(signature.into_vec()) } } } From b459e4df33f41ba7ddf95e9882117ec258b0842b Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:03:30 +0100 Subject: [PATCH 120/163] remove useless print --- bindings/wasm/src/storage/jwk_storage.rs | 1 - bindings/wasm/src/storage/jwk_storage_pqc.rs | 3 --- bindings/wasm/src/storage/mod.rs | 1 - 3 files changed, 5 deletions(-) diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index b89c9462b6..9f1fa4685b 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -65,7 +65,6 @@ impl JwkStorage for WasmJwkStorage { } async fn insert(&self, jwk: Jwk) -> KeyStorageResult { - web_sys::console::log_1(&"WWWWWWWWWWW".into()); let promise: Promise = Promise::resolve(&WasmJwkStorage::insert(self, WasmJwk::from(jwk))); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index 76036a7052..a97a284a83 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -32,15 +32,12 @@ extern "C" { #[async_trait::async_trait(?Send)] impl JwkStoragePQ for WasmJwkStorage { async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { - web_sys::console::log_1(&"YYYYYYYYYYYYYYY".into()); let promise: Promise = Promise::resolve(&WasmJwkStorage::_generate_pq_key(self, key_type.into(), alg.name().to_owned())); let result: JsValueResult = JsFuture::from(promise).await.into(); result.into() } async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { - web_sys::console::log_1(&"pq_sign from rust".into()); - println!("pq_sign from rust"); let promise: Promise = Promise::resolve(&WasmJwkStorage::sign( self, key_id.clone().into(), diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 257113d861..53e954d044 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -24,6 +24,5 @@ pub use method_digest::*; pub use signature_options::*; pub use wasm_storage::*; -//pub use jwk_storage_pqc::*; From 96999918d4890407b1e1b48f684df4540b8f94ea Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:37:03 +0100 Subject: [PATCH 121/163] bind hybrid JWT credential validator --- bindings/wasm/package-lock.json | 23 ++ .../jwt_credential_validator_hybrid.rs | 211 ++++++++++++++++++ .../jwt_credential_validation/mod.rs | 2 + bindings/wasm/src/jose/compositejwk.rs | 2 +- 4 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index 80432907bc..58751bd2ec 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", + "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "node-fetch": "^2.6.7" @@ -321,6 +322,28 @@ } ] }, + "node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs new file mode 100644 index 0000000000..68683fc1b3 --- /dev/null +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -0,0 +1,211 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::credential::JwtCredentialValidatorHybrid; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::StatusCheck; +use identity_iota::did::CoreDID; + +use super::options::WasmJwtCredentialValidationOptions; +use crate::common::ImportedDocumentLock; +use crate::common::ImportedDocumentReadGuard; +use crate::common::WasmTimestamp; +use crate::credential::options::WasmStatusCheck; +use crate::credential::revocation::status_list_2021::WasmStatusList2021Credential; +use crate::credential::WasmCredential; +use crate::credential::WasmDecodedJwtCredential; +use crate::credential::WasmFailFast; +use crate::credential::WasmJwt; +use crate::credential::WasmSubjectHolderRelationship; +use crate::did::ArrayIToCoreDocument; +use crate::did::IToCoreDocument; +use crate::did::WasmCoreDID; +use crate::did::WasmJwsVerificationOptions; +use crate::error::Result; +use crate::error::WasmResult; +use crate::verification::IJwsVerifier; +use crate::verification::WasmJwsVerifier; + +use wasm_bindgen::prelude::*; + +/// A type for decoding and validating {@link Credential}. +#[wasm_bindgen(js_name = JwtCredentialValidatorHybrid)] +pub struct WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid); + +#[wasm_bindgen(js_class = JwtCredentialValidatorHybrid)] +impl WasmJwtCredentialValidatorHybrid { + /// Creates a new {@link JwtCredentialValidatorHybrid}. + #[wasm_bindgen(constructor)] + #[allow(non_snake_case)] + pub fn new(traditionalSignatureVerifier: Option, pqSignatureVerifier: Option) -> WasmJwtCredentialValidatorHybrid { + let traditional_signature_verifier = WasmJwsVerifier::new(traditionalSignatureVerifier); + let pq_signature_verifier = WasmJwsVerifier::new(pqSignatureVerifier); + WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid::with_signature_verifiers(traditional_signature_verifier, pq_signature_verifier)) + } + + /// Decodes and validates a {@link Credential} issued as a JWS. A {@link DecodedJwtCredential} is returned upon + /// success. + /// + /// The following properties are validated according to `options`: + /// - the issuer's signature on the JWS, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + /// + /// # Warning + /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be + /// trusted. This section contains more information on additional checks that should be carried out before and after + /// calling this method. + /// + /// ## The state of the issuer's DID Document + /// The caller must ensure that `issuer` represents an up-to-date DID Document. + /// + /// ## Properties that are not validated + /// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: + /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. + /// These should be manually checked after validation, according to your requirements. + /// + /// # Errors + /// An error is returned whenever a validated condition is not satisfied. + #[wasm_bindgen] + pub fn validate( + &self, + credential_jwt: &WasmJwt, + issuer: &IToCoreDocument, + options: &WasmJwtCredentialValidationOptions, + fail_fast: WasmFailFast, + ) -> Result { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + + self + .0 + .validate(&credential_jwt.0, &issuer_guard, &options.0, fail_fast.into()) + .wasm_result() + .map(WasmDecodedJwtCredential) + } + + /// Decode and verify the JWS signature of a {@link Credential} issued as a JWT using the DID Document of a trusted + /// issuer. + /// + /// A {@link DecodedJwtCredential} is returned upon success. + /// + /// # Warning + /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date. + /// + /// ## Proofs + /// Only the JWS signature is verified. If the {@link Credential} contains a `proof` property this will not be + /// verified by this method. + /// + /// # Errors + /// This method immediately returns an error if + /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt + /// to verify the credential's signature will be made and an error is returned upon failure. + #[wasm_bindgen(js_name = verifySignature)] + #[allow(non_snake_case)] + pub fn verify_signature( + &self, + credential: &WasmJwt, + trustedIssuers: &ArrayIToCoreDocument, + options: &WasmJwsVerificationOptions, + ) -> Result { + let issuer_locks: Vec = trustedIssuers.into(); + let trusted_issuers: Vec> = issuer_locks + .iter() + .map(ImportedDocumentLock::try_read) + .collect::>>>( + )?; + + self + .0 + .verify_signature(&credential.0, &trusted_issuers, &options.0) + .wasm_result() + .map(WasmDecodedJwtCredential) + } + + /// Validate that the credential expires on or after the specified timestamp. + #[wasm_bindgen(js_name = checkExpiresOnOrAfter)] + pub fn check_expires_on_or_after(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { + JwtCredentialValidatorUtils::check_expires_on_or_after(&credential.0, timestamp.0).wasm_result() + } + + /// Validate that the credential is issued on or before the specified timestamp. + #[wasm_bindgen(js_name = checkIssuedOnOrBefore)] + pub fn check_issued_on_or_before(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { + JwtCredentialValidatorUtils::check_issued_on_or_before(&credential.0, timestamp.0).wasm_result() + } + + /// Validate that the relationship between the `holder` and the credential subjects is in accordance with + /// `relationship`. The `holder` parameter is expected to be the URL of the holder. + #[wasm_bindgen(js_name = checkSubjectHolderRelationship)] + pub fn check_subject_holder_relationship( + credential: &WasmCredential, + holder: &str, + relationship: WasmSubjectHolderRelationship, + ) -> Result<()> { + let holder: Url = Url::parse(holder).wasm_result()?; + JwtCredentialValidatorUtils::check_subject_holder_relationship(&credential.0, &holder, relationship.into()) + .wasm_result() + } + + /// Checks whether the credential status has been revoked. + /// + /// Only supports `RevocationBitmap2022`. + #[wasm_bindgen(js_name = checkStatus)] + #[allow(non_snake_case)] + pub fn check_status( + credential: &WasmCredential, + trustedIssuers: &ArrayIToCoreDocument, + statusCheck: WasmStatusCheck, + ) -> Result<()> { + let issuer_locks: Vec = trustedIssuers.into(); + let trusted_issuers: Vec> = issuer_locks + .iter() + .map(ImportedDocumentLock::try_read) + .collect::>>>( + )?; + let status_check: StatusCheck = statusCheck.into(); + JwtCredentialValidatorUtils::check_status(&credential.0, &trusted_issuers, status_check).wasm_result() + } + + /// Checks wheter the credential status has been revoked using `StatusList2021`. + #[wasm_bindgen(js_name = checkStatusWithStatusList2021)] + pub fn check_status_with_status_list_2021( + credential: &WasmCredential, + status_list: &WasmStatusList2021Credential, + status_check: WasmStatusCheck, + ) -> Result<()> { + JwtCredentialValidatorUtils::check_status_with_status_list_2021( + &credential.0, + &status_list.inner, + status_check.into(), + ) + .wasm_result() + } + + /// Utility for extracting the issuer field of a {@link Credential} as a DID. + /// + /// ### Errors + /// + /// Fails if the issuer field is not a valid DID. + #[wasm_bindgen(js_name = extractIssuer)] + pub fn extract_issuer(credential: &WasmCredential) -> Result { + JwtCredentialValidatorUtils::extract_issuer::(&credential.0) + .map(WasmCoreDID::from) + .wasm_result() + } + + /// Utility for extracting the issuer field of a credential in JWT representation as DID. + /// + /// # Errors + /// + /// If the JWT decoding fails or the issuer field is not a valid DID. + #[wasm_bindgen(js_name = extractIssuerFromJwt)] + pub fn extract_issuer_from_jwt(credential: &WasmJwt) -> Result { + JwtCredentialValidatorUtils::extract_issuer_from_jwt::(&credential.0) + .map(WasmCoreDID::from) + .wasm_result() + } +} diff --git a/bindings/wasm/src/credential/jwt_credential_validation/mod.rs b/bindings/wasm/src/credential/jwt_credential_validation/mod.rs index 826d0388d9..5021f82f1a 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/mod.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/mod.rs @@ -7,9 +7,11 @@ mod kb_validation_options; mod options; mod sd_jwt_validator; mod unknown_credential; +mod jwt_credential_validator_hybrid; pub use self::decoded_jwt_credential::*; pub use self::jwt_credential_validator::*; +pub use self::jwt_credential_validator_hybrid::*; pub use self::kb_validation_options::*; pub use self::options::*; pub use self::sd_jwt_validator::*; diff --git a/bindings/wasm/src/jose/compositejwk.rs b/bindings/wasm/src/jose/compositejwk.rs index d1029e1cb4..8f307ad41b 100644 --- a/bindings/wasm/src/jose/compositejwk.rs +++ b/bindings/wasm/src/jose/compositejwk.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use identity_iota::verification::jose::jwk::CompositeJwk; -use identity_iota::verification::jose::jwk::CompositeAlgId; + use wasm_bindgen::prelude::*; use crate::jose::WasmCompositeAlgId; use crate::jose::WasmJwk; From 76aaaf8b1268cee7d931d71f8ea2f34b5d6f7a8d Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:38:58 +0100 Subject: [PATCH 122/163] add methods to create JWT presentations for PQC and hybrid --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index 72fa3f9eb2..a30fd3c5e3 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -26,6 +26,14 @@ use crate::did::PromiseJws; use crate::storage::WasmJwsSignatureOptions; use crate::credential::WasmJws; use wasm_bindgen_futures::future_to_promise; +use crate::credential::WasmPresentation; +use crate::storage::WasmJwtPresentationOptions; +use crate::did::PromiseJwt; +use identity_iota::credential::Presentation; +use crate::credential::UnknownCredential; +use identity_iota::credential::JwtPresentationOptions; +use crate::credential::WasmJwt; + #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { @@ -148,4 +156,71 @@ impl WasmCoreDocument { Ok(promise.unchecked_into()) } + #[wasm_bindgen(js_name = createPresentationJwtPqc)] + pub fn _create_presentation_jwt_pqc( + &self, + storage: &WasmStorage, + fragment: String, + presentation: &WasmPresentation, + signature_options: &WasmJwsSignatureOptions, + presentation_options: &WasmJwtPresentationOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = signature_options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let presentation_clone: Presentation = presentation.0.clone(); + let presentation_options_clone: JwtPresentationOptions = presentation_options.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_presentation_jwt_pqc( + &presentation_clone, + &storage_clone, + &fragment, + &options_clone, + &presentation_options_clone, + ) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + #[wasm_bindgen(js_name = createPresentationJwtHybrid)] + pub fn _create_presentation_jwt_hybrid( + &self, + storage: &WasmStorage, + fragment: String, + presentation: &WasmPresentation, + signature_options: &WasmJwsSignatureOptions, + presentation_options: &WasmJwtPresentationOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = signature_options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let presentation_clone: Presentation = presentation.0.clone(); + let presentation_options_clone: JwtPresentationOptions = presentation_options.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_presentation_jwt_hybrid( + &presentation_clone, + &storage_clone, + &fragment, + &options_clone, + &presentation_options_clone, + ) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + } + From bb9d762075d022b7161ca67531809cb1d05c4896 Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Thu, 23 Jan 2025 11:00:42 +0100 Subject: [PATCH 123/163] Revert "Merge pull request #9 from Cybersecurity-LINKS/upstream_merge" This reverts commit c445b6e9bc1d2f35c389730b6cbd045a8c1a7c55, reversing changes made to 76aaaf8b1268cee7d931d71f8ea2f34b5d6f7a8d. --- .github/workflows/shared-release.yml | 3 - .github/workflows/upload-docs.yml | 3 - CHANGELOG.md | 32 +- Cargo.toml | 1 + README.md | 1 - bindings/wasm/CHANGELOG.md | 24 +- bindings/wasm/Cargo.toml | 18 +- .../examples/src/1_advanced/10_sd_jwt_vc.ts | 167 - bindings/wasm/examples/src/main.ts | 5 - bindings/wasm/package-lock.json | 4822 +---------------- bindings/wasm/package.json | 4 +- bindings/wasm/rust-toolchain.toml | 3 +- .../wasm/src/common/imported_document_lock.rs | 2 +- bindings/wasm/src/error.rs | 32 +- bindings/wasm/src/jose/jwk.rs | 3 +- bindings/wasm/src/lib.rs | 2 +- bindings/wasm/src/macros.rs | 6 +- bindings/wasm/src/sd_jwt_vc/builder.rs | 134 - bindings/wasm/src/sd_jwt_vc/claims.rs | 25 - bindings/wasm/src/sd_jwt_vc/metadata/claim.rs | 75 - .../wasm/src/sd_jwt_vc/metadata/issuer.rs | 63 - bindings/wasm/src/sd_jwt_vc/metadata/mod.rs | 10 - .../wasm/src/sd_jwt_vc/metadata/vc_type.rs | 84 - bindings/wasm/src/sd_jwt_vc/mod.rs | 17 - bindings/wasm/src/sd_jwt_vc/presentation.rs | 53 - bindings/wasm/src/sd_jwt_vc/resolver.rs | 74 - .../wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs | 85 - .../src/sd_jwt_vc/sd_jwt_v2/disclosure.rs | 64 - .../wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs | 74 - .../wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs | 139 - bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs | 16 - .../wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs | 150 - .../wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs | 53 - bindings/wasm/src/sd_jwt_vc/status.rs | 20 - bindings/wasm/src/sd_jwt_vc/token.rs | 172 - examples/Cargo.toml | 2 +- identity_core/Cargo.toml | 3 +- identity_core/src/common/mod.rs | 2 - identity_core/src/common/string_or_url.rs | 151 - identity_core/src/common/url.rs | 8 +- identity_credential/Cargo.toml | 37 +- .../src/credential/jwt_serialization.rs | 4 +- .../domain_linkage_validator.rs | 1 + identity_credential/src/error.rs | 5 - identity_credential/src/lib.rs | 7 - .../src/presentation/jwt_serialization.rs | 2 +- .../src/revocation/status_list_2021/entry.rs | 24 +- .../revocation_timeframe_status.rs | 2 +- identity_credential/src/sd_jwt_vc/builder.rs | 386 -- identity_credential/src/sd_jwt_vc/claims.rs | 217 - identity_credential/src/sd_jwt_vc/error.rs | 57 - .../src/sd_jwt_vc/metadata/claim.rs | 286 - .../src/sd_jwt_vc/metadata/display.rs | 23 - .../src/sd_jwt_vc/metadata/integrity.rs | 121 - .../src/sd_jwt_vc/metadata/issuer.rs | 94 - .../src/sd_jwt_vc/metadata/mod.rs | 14 - .../src/sd_jwt_vc/metadata/vc_type.rs | 268 - identity_credential/src/sd_jwt_vc/mod.rs | 25 - .../src/sd_jwt_vc/presentation.rs | 54 - identity_credential/src/sd_jwt_vc/resolver.rs | 29 - identity_credential/src/sd_jwt_vc/status.rs | 52 - .../src/sd_jwt_vc/tests/mod.rs | 113 - .../src/sd_jwt_vc/tests/validation.rs | 172 - identity_credential/src/sd_jwt_vc/token.rs | 476 -- .../jpt_credential_validator_utils.rs | 13 +- .../jwt_credential_validator.rs | 2 +- identity_did/Cargo.toml | 8 +- identity_did/src/did_url.rs | 48 +- identity_document/Cargo.toml | 9 +- .../benches/deserialize_document.rs | 4 +- .../src/document/core_document.rs | 4 +- identity_document/src/utils/did_url_query.rs | 4 +- identity_ecdsa_verifier/Cargo.toml | 5 +- identity_eddsa_verifier/Cargo.toml | 5 +- identity_iota/Cargo.toml | 25 +- identity_iota/README.md | 7 +- identity_iota/src/lib.rs | 8 +- identity_iota_core/Cargo.toml | 13 +- .../src/document/iota_document.rs | 4 +- .../src/state_metadata/document.rs | 5 +- identity_jose/Cargo.toml | 8 +- identity_jose/src/jwk/key_set.rs | 1 + identity_jose/src/jws/decoder.rs | 2 +- identity_jose/src/jws/encoding/utils.rs | 4 +- identity_jose/src/jws/recipient.rs | 2 +- identity_resolver/Cargo.toml | 13 +- identity_storage/Cargo.toml | 21 +- identity_stronghold/Cargo.toml | 11 +- identity_verification/Cargo.toml | 9 +- 89 files changed, 146 insertions(+), 9160 deletions(-) delete mode 100644 bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts delete mode 100644 bindings/wasm/src/sd_jwt_vc/builder.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/claims.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/claim.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/mod.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/mod.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/presentation.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/resolver.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/status.rs delete mode 100644 bindings/wasm/src/sd_jwt_vc/token.rs delete mode 100644 identity_core/src/common/string_or_url.rs delete mode 100644 identity_credential/src/sd_jwt_vc/builder.rs delete mode 100644 identity_credential/src/sd_jwt_vc/claims.rs delete mode 100644 identity_credential/src/sd_jwt_vc/error.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/claim.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/display.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/integrity.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/issuer.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/mod.rs delete mode 100644 identity_credential/src/sd_jwt_vc/metadata/vc_type.rs delete mode 100644 identity_credential/src/sd_jwt_vc/mod.rs delete mode 100644 identity_credential/src/sd_jwt_vc/presentation.rs delete mode 100644 identity_credential/src/sd_jwt_vc/resolver.rs delete mode 100644 identity_credential/src/sd_jwt_vc/status.rs delete mode 100644 identity_credential/src/sd_jwt_vc/tests/mod.rs delete mode 100644 identity_credential/src/sd_jwt_vc/tests/validation.rs delete mode 100644 identity_credential/src/sd_jwt_vc/token.rs diff --git a/.github/workflows/shared-release.yml b/.github/workflows/shared-release.yml index a4b4403bf0..86a4d1ab94 100644 --- a/.github/workflows/shared-release.yml +++ b/.github/workflows/shared-release.yml @@ -155,9 +155,6 @@ jobs: if: ${{env.IS_RELEASE && inputs.create-github-release}} uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 with: - # Token expires Jan 16, 2026 - # This is needed because of https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#triggering-a-workflow-from-a-workflow - token: ${{ secrets.GH_RELEASE_PUBLISH_PAT }} body_path: RELEASE_CHANGELOG.md prerelease: ${{env.IS_PRE_RELEASE}} tag_name: ${{env.CURRENT_VERSION}} diff --git a/.github/workflows/upload-docs.yml b/.github/workflows/upload-docs.yml index 7c98b7cb7e..5ce1429767 100644 --- a/.github/workflows/upload-docs.yml +++ b/.github/workflows/upload-docs.yml @@ -9,9 +9,6 @@ on: description: 'Version to publish docs under (e.g. `v1.2.3-dev.1`)' required: true -env: - GH_TOKEN: ${{ github.token }} - permissions: actions: 'write' diff --git a/CHANGELOG.md b/CHANGELOG.md index f51e2936c3..45d3fa510e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,5 @@ # Changelog -## [v1.5.0](https://github.com/iotaledger/identity.rs/tree/v1.5.0) (2025-01-20) - -[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.4.0...v1.5.0) - -### Added - -- SD-JWT VC implementation [\#1413](https://github.com/iotaledger/identity.rs/pull/1413) - -### Patch - -- Support %-encoded characters in DID URL [\#1496](https://github.com/iotaledger/identity.rs/pull/1496) -- fix: serialization of status list [\#1423](https://github.com/iotaledger/identity.rs/pull/1423) - -## [v1.4.0](https://github.com/iotaledger/identity.rs/tree/v1.4.0) (2024-09-23) - -[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.1...v1.4.0) - -### Added - -- Add support for custom JWS algorithms [\#1410](https://github.com/iotaledger/identity.rs/pull/1410) -- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) -- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) -- Add feature to support custom `now_utc` implementations [\#1397](https://github.com/iotaledger/identity.rs/pull/1397) - -### Patch - -- Remove dependency on `identity_core` default features [\#1408](https://github.com/iotaledger/identity.rs/pull/1408) -- Mark `js-sys` as optional for identity\_core [\#1405](https://github.com/iotaledger/identity.rs/pull/1405) -- Make `bls12_381_plus` dependency more flexible again [\#1393](https://github.com/iotaledger/identity.rs/pull/1393) - ## [v1.3.1](https://github.com/iotaledger/identity.rs/tree/v1.3.1) (2024-06-12) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.0...v1.3.1) @@ -38,6 +8,8 @@ - Pin and bump `bls12_381_plus` dependency [\#1378](https://github.com/iotaledger/identity.rs/pull/1378) +# Changelog + ## [v1.3.0](https://github.com/iotaledger/identity.rs/tree/v1.3.0) (2024-05-28) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.2.0...v1.3.0) diff --git a/Cargo.toml b/Cargo.toml index efb1fe687b..1c12798acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ edition = "2021" homepage = "https://www.iota.org" license = "Apache-2.0" repository = "https://github.com/iotaledger/identity.rs" +rust-version = "1.65" [workspace.lints.clippy] result_large_err = "allow" diff --git a/README.md b/README.md index 177a07d3cf..585740fbde 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ This repository extends IOTA Identity by implementing both pure **Post-Quantum ( 2. **PQ/T hybrid Signatures**: to mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), the IOTA Identity also extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. - ```json "compositeJwk": { "algId": ".. composite key OID ..", diff --git a/bindings/wasm/CHANGELOG.md b/bindings/wasm/CHANGELOG.md index 01b3f5c312..cd49c3874f 100644 --- a/bindings/wasm/CHANGELOG.md +++ b/bindings/wasm/CHANGELOG.md @@ -1,28 +1,6 @@ # Changelog -## [wasm-v1.5.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.5.0) (2025-01-20) - -[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.4.0...wasm-v1.5.0) - -### Added - -- SD-JWT VC implementation [\#1413](https://github.com/iotaledger/identity.rs/pull/1413) - -### Patch - -- Support %-encoded characters in DID URL [\#1496](https://github.com/iotaledger/identity.rs/pull/1496) -- fix: serialization of status list [\#1423](https://github.com/iotaledger/identity.rs/pull/1423) - -## [wasm-v1.4.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.4.0) (2024-09-23) - -[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.1...wasm-v1.4.0) - -### Added - -- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) -- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) - -## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-28) +## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-27) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.0...wasm-v1.3.1) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 3e5769b075..a5208ec94b 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_wasm" -version = "1.5.0" +version = "1.3.1" authors = ["IOTA Stiftung"] edition = "2021" homepage = "https://www.iota.org" @@ -16,7 +16,6 @@ description = "Web Assembly bindings for the identity-rs crate." crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = { version = "1.0.94", features = ["std"] } async-trait = { version = "0.1", default-features = false } bls12_381_plus = "0.8.17" console_error_panic_hook = { version = "0.1" } @@ -27,7 +26,6 @@ js-sys = { version = "0.3.61" } json-proof-token = "0.3.4" proc_typescript = { version = "0.1.0", path = "./proc_typescript" } serde = { version = "1.0", features = ["derive"] } -serde-wasm-bindgen = "0.6.5" serde_json = { version = "1.0", default-features = false } serde_repr = { version = "0.1", default-features = false } # Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads. @@ -40,19 +38,7 @@ zkryptium = "0.2.2" [dependencies.identity_iota] path = "../../identity_iota" default-features = false - -features = [ - "client", - "revocation-bitmap", - "resolver", - "domain-linkage", - "sd-jwt", - "status-list-2021", - "jpt-bbs-plus", - "sd-jwt-vc", - "pqc", - "hybrid" -] +features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus", "pqc", "hybrid"] [dev-dependencies] rand = "0.8.5" diff --git a/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts b/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts deleted file mode 100644 index 8eef36c198..0000000000 --- a/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { - IJwk, - IJwkParams, - IResolver, - IssuerMetadata, - Jwk, - JwkType, - JwsVerificationOptions, - KeyBindingJwtBuilder, - KeyBindingJWTValidationOptions, - SdJwtVcBuilder, - Sha256Hasher, - Timestamp, - TypeMetadataHelper, -} from "@iota/identity-wasm/node"; -import { exportJWK, generateKeyPair, JWK, JWTHeaderParameters, JWTPayload, SignJWT } from "jose"; - -const vc_metadata: TypeMetadataHelper = JSON.parse(`{ - "vct": "https://example.com/education_credential", - "name": "Betelgeuse Education Credential - Preliminary Version", - "description": "This is our development version of the education credential. Don't panic.", - "claims": [ - { - "path": ["name"], - "display": [ - { - "lang": "de-DE", - "label": "Vor- und Nachname", - "description": "Der Name des Studenten" - }, - { - "lang": "en-US", - "label": "Name", - "description": "The name of the student" - } - ], - "sd": "allowed" - }, - { - "path": ["address"], - "display": [ - { - "lang": "de-DE", - "label": "Adresse", - "description": "Adresse zum Zeitpunkt des Abschlusses" - }, - { - "lang": "en-US", - "label": "Address", - "description": "Address at the time of graduation" - } - ], - "sd": "always" - }, - { - "path": ["address", "street_address"], - "display": [ - { - "lang": "de-DE", - "label": "Straße" - }, - { - "lang": "en-US", - "label": "Street Address" - } - ], - "sd": "always", - "svg_id": "address_street_address" - }, - { - "path": ["degrees", null], - "display": [ - { - "lang": "de-DE", - "label": "Abschluss", - "description": "Der Abschluss des Studenten" - }, - { - "lang": "en-US", - "label": "Degree", - "description": "Degree earned by the student" - } - ], - "sd": "allowed" - } - ] -}`); - -const keypair_jwk = async (): Promise<[JWK, JWK]> => { - const [sk, pk] = await generateKeyPair("ES256").then(res => [res.privateKey, res.publicKey]); - const sk_jwk = await exportJWK(sk); - const pk_jwk = await exportJWK(pk); - - return [sk_jwk, pk_jwk]; -}; - -const signer = async (header: object, payload: object, sk_jwk: JWK) => { - return new SignJWT(payload as JWTPayload) - .setProtectedHeader(header as JWTHeaderParameters) - .sign(sk_jwk) - .then(jws => new TextEncoder().encode(jws)); -}; - -export async function sdJwtVc() { - const hasher = new Sha256Hasher(); - const issuer = "https://example.com/"; - const [sk_jwk, pk_jwk] = await keypair_jwk(); - const issuer_public_jwk = { ...pk_jwk, kty: JwkType.Ec, kid: "key1" } as IJwk; - const issuer_signer = (header: object, payload: object) => signer(header, payload, sk_jwk); - const issuer_metadata = new IssuerMetadata(issuer, { jwks: { keys: [issuer_public_jwk] } }); - const dummy_resolver = { - resolve: async (input: string) => { - if (input == "https://example.com/.well-known/jwt-vc-issuer/") { - return new TextEncoder().encode(JSON.stringify(issuer_metadata.toJSON())); - } - if (input == "https://example.com/.well-known/vct/education_credential") { - return new TextEncoder().encode(JSON.stringify(vc_metadata)); - } - }, - } as IResolver; - const [holder_sk, holder_pk] = await keypair_jwk(); - const holder_public_jwk = { ...holder_pk, kty: JwkType.Ec, kid: "key2" } as IJwk; - const holder_signer = (header: object, payload: object) => signer(header, payload, holder_sk); - - /// Issuer creates an SD-JWT VC. - let sd_jwt_vc = await new SdJwtVcBuilder({ - name: "John Doe", - address: { - street_address: "A random street", - number: "3a", - }, - degree: [], - }, hasher) - .header({ kid: "key1" }) - .vct("https://example.com/education_credential") - .iat(Timestamp.nowUTC()) - .iss(issuer) - .requireKeyBinding({ kid: holder_public_jwk.kid }) - .makeConcealable("/address/street_address") - .makeConcealable("/address") - .finish({ sign: issuer_signer }, "ES256"); - - console.log(`issued SD-JWT VC: ${sd_jwt_vc.toString()}`); - - // Holder receives its SD-JWT VC and attaches its keybinding JWT. - const kb_jwt = await new KeyBindingJwtBuilder() - .iat(Timestamp.nowUTC()) - .header({ kid: holder_public_jwk.kid }) - .nonce("abcdefghi") - .aud("https://example.com/verify") - .finish(sd_jwt_vc.asSdJwt(), "ES256", { sign: holder_signer }); - const { disclosures, sdJwtVc } = sd_jwt_vc.intoPresentation(hasher).attachKeyBindingJwt(kb_jwt).finish(); - console.log(`presented SD-JWT VC: ${sdJwtVc}`); - - // Verifier checks the presented sdJwtVc. - await sdJwtVc.validate(dummy_resolver, hasher); - sdJwtVc.validateKeyBinding( - new Jwk(holder_public_jwk as IJwkParams), - hasher, - new KeyBindingJWTValidationOptions({ nonce: "abcdefghi", jwsOptions: new JwsVerificationOptions() }), - ); - - console.log("The presented SdJwtVc is valid!"); -} diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 72d49c51e0..604197994b 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -12,7 +12,6 @@ import { createVC } from "./0_basic/5_create_vc"; import { createVP } from "./0_basic/6_create_vp"; import { revokeVC } from "./0_basic/7_revoke_vc"; import { didControlsDid } from "./1_advanced/0_did_controls_did"; -import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc"; import { didIssuesNft } from "./1_advanced/1_did_issues_nft"; import { nftOwnsDid } from "./1_advanced/2_nft_owns_did"; import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens"; @@ -71,9 +70,6 @@ async function main() { return await zkp(); case "9_zkp_revocation": return await zkp_revocation(); - - case "10_sd_jwt_vc": - return await sdJwtVc(); case "traditional": return createDidJwk(); case "zk": @@ -82,7 +78,6 @@ async function main() { return createDidJwkPq(); case "hybrid": return createDidJwkHybrid(); - default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index eda18baada..58751bd2ec 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -1,25 +1,22 @@ { "name": "@iota/identity-wasm", - "version": "1.5.0", - "lockfileVersion": 2, + "version": "1.3.1", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@iota/identity-wasm", - "version": "1.5.0", + "version": "1.3.1", "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", - "jose": "^5.9.6", - "jsonwebtoken": "^9.0.2", "node-fetch": "^2.6.7" }, "devDependencies": { "@transmute/did-key-ed25519": "0.3.0-unstable.9", - "@types/jsonwebtoken": "^9.0.7", "@types/mocha": "^9.1.0", "big-integer": "^1.6.51", "copy-webpack-plugin": "^7.0.0", @@ -616,16 +613,6 @@ "dev": true, "optional": true }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", - "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1367,12 +1354,6 @@ "node": "*" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2224,15 +2205,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.50", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz", @@ -3315,15 +3287,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jose": { - "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3545,28 +3508,6 @@ "node": ">=10" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -3582,27 +3523,6 @@ "verror": "1.10.0" } }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3780,42 +3700,6 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, "node_modules/lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", @@ -3825,7 +3709,8 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true }, "node_modules/lodash.padend": { "version": "4.6.1", @@ -4764,9 +4649,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/nanoid": { "version": "3.3.1", @@ -5327,6 +5213,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -6842,4695 +6729,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - - }, - "@stablelib/hash": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-1.0.1.tgz", - "integrity": "sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==", - "dev": true - }, - "@stablelib/int": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", - "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==", - "dev": true - }, - "@stablelib/keyagreement": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz", - "integrity": "sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==", - "dev": true, - "requires": { - "@stablelib/bytes": "^1.0.1" - } - }, - "@stablelib/random": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", - "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", - "dev": true, - "requires": { - "@stablelib/binary": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "@stablelib/sha512": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/sha512/-/sha512-1.0.1.tgz", - "integrity": "sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==", - "dev": true, - "requires": { - "@stablelib/binary": "^1.0.1", - "@stablelib/hash": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "@stablelib/wipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", - "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==", - "dev": true - }, - "@stablelib/x25519": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stablelib/x25519/-/x25519-1.0.3.tgz", - "integrity": "sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==", - "dev": true, - "requires": { - "@stablelib/keyagreement": "^1.0.1", - "@stablelib/random": "^1.0.2", - "@stablelib/wipe": "^1.0.1" - } - }, - "@transmute/did-context": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/did-context/-/did-context-0.6.1-unstable.37.tgz", - "integrity": "sha512-p/QnG3QKS4218hjIDgdvJOFATCXsAnZKgy4egqRrJLlo3Y6OaDBg7cA73dixOwUPoEKob0K6rLIGcsCI/L1acw==", - "dev": true - }, - "@transmute/did-key-common": { - "version": "0.3.0-unstable.9", - "resolved": "https://registry.npmjs.org/@transmute/did-key-common/-/did-key-common-0.3.0-unstable.9.tgz", - "integrity": "sha512-qGzFJA615Gu/UnAvuSNrRvtCCKRVvxCmuDawenudyGR8X8WkkJ19uD8kI0GaCzZazpX1SWiairM87nKJ9aH7Tw==", - "dev": true, - "requires": { - "@did-core/data-model": "^0.1.1-unstable.13", - "@did-core/did-ld-json": "^0.1.1-unstable.13", - "@transmute/did-context": "^0.6.1-unstable.36", - "@transmute/ld-key-pair": "^0.6.1-unstable.36", - "@transmute/security-context": "^0.6.1-unstable.36" - } - }, - "@transmute/did-key-ed25519": { - "version": "0.3.0-unstable.9", - "resolved": "https://registry.npmjs.org/@transmute/did-key-ed25519/-/did-key-ed25519-0.3.0-unstable.9.tgz", - "integrity": "sha512-mFTTL1IHp26JweHN/SCj2Re5iBr5sWbyctd5LRoHRU1DQB0XmBBFX5ZzCCtnEiBGvDF55Eyx1vpkwUHIcD3QGg==", - "dev": true, - "requires": { - "@transmute/did-key-common": "^0.3.0-unstable.9", - "@transmute/ed25519-key-pair": "^0.6.1-unstable.37" - } - }, - "@transmute/ed25519-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/ed25519-key-pair/-/ed25519-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-l34yzE/QnQwmdk5xY9g2kD55e4XPp/jTZQzPu7I6J4Ar+bMaL/0RLL/pgvwyI7qUpsddxRf4WPZCCcZveqPcdA==", - "dev": true, - "requires": { - "@stablelib/ed25519": "^1.0.1", - "@transmute/ld-key-pair": "^0.6.1-unstable.37", - "@transmute/x25519-key-pair": "^0.6.1-unstable.37" - } - }, - "@transmute/ld-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/ld-key-pair/-/ld-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-DcTpEruAQBfOd2laZkg3uCQ+67Y7dw2hsvo42NAQ5tItCIx5AClP7zccri7T2JUcfDUFaE32z/BLTMEKYt3XZQ==", - "dev": true - }, - "@transmute/security-context": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/security-context/-/security-context-0.6.1-unstable.37.tgz", - "integrity": "sha512-GtLmG65qlORrz/2S4I74DT+vA4+qXsFxrMr0cNOXjUqZBd/AW1PTrFnryLF9907BfoiD58HC9qb1WVGWjSlBYw==", - "dev": true - }, - "@transmute/x25519-key-pair": { - "version": "0.6.1-unstable.37", - "resolved": "https://registry.npmjs.org/@transmute/x25519-key-pair/-/x25519-key-pair-0.6.1-unstable.37.tgz", - "integrity": "sha512-j6zR9IoJmgVhUCVH8YVGpsgQf99SxPKZ00LGnUheBAQzgj2lULGBQ44G+GqBCdzfT0qweptTfp1RjqqHEpizeA==", - "dev": true, - "requires": { - "@stablelib/x25519": "^1.0.0", - "@transmute/ld-key-pair": "^0.6.1-unstable.37" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, - "@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", - "dev": true, - "peer": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "optional": true - }, - "@types/jsonwebtoken": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", - "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", - "dev": true, - "requires": { - "@types/unist": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", - "dev": true - }, - "@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", - "dev": true - }, - "@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peer": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escape-sequences": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", - "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", - "dev": true, - "requires": { - "array-back": "^3.0.1" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - } - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", - "dev": true - }, - "bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cache-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz", - "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "fs-then-native": "^2.0.0", - "mkdirp2": "^1.0.4" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true - } - } - }, - "cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001457", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", - "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", - "dev": true, - "peer": true - }, - "canonicalize": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", - "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true - }, - "check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true - }, - "ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true - }, - "class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "peer": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "string-width": "^4.2.0" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "collect-all": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz", - "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==", - "dev": true, - "requires": { - "stream-connect": "^1.0.2", - "stream-via": "^1.0.4" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "requires": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - }, - "typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true - } - } - }, - "command-line-tool": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz", - "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==", - "dev": true, - "requires": { - "ansi-escape-sequences": "^4.0.0", - "array-back": "^2.0.0", - "command-line-args": "^5.0.0", - "command-line-usage": "^4.1.0", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "command-line-usage": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz", - "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==", - "dev": true, - "requires": { - "ansi-escape-sequences": "^4.0.0", - "array-back": "^2.0.0", - "table-layout": "^0.4.2", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true - }, - "common-sequence": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz", - "integrity": "sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==", - "dev": true - }, - "common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "config-master": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", - "integrity": "sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==", - "dev": true, - "requires": { - "walk-back": "^2.0.1" - }, - "dependencies": { - "walk-back": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-2.0.1.tgz", - "integrity": "sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==", - "dev": true - } - } - }, - "copy-webpack-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz", - "integrity": "sha512-SLjQNa5iE3BoCP76ESU9qYo9ZkEWtXoZxDurHoqPchAFRblJ9g96xTeC560UXBMre1Nx6ixIIUfiY3VcjpJw3g==", - "dev": true, - "requires": { - "fast-glob": "^3.2.4", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cypress": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", - "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", - "dev": true, - "requires": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - } - } - }, - "cypress-multi-reporters": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", - "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", - "dev": true, - "peer": true, - "requires": { - "debug": "^4.3.4", - "lodash": "^4.17.21" - } - }, - "cypress-parallel": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.14.0.tgz", - "integrity": "sha512-Lsh28G70vxjL0cjR820BdaVQHnGc17Vvb+tYmjbRPmfC+XEzwvUzhcaD0E1zCztBSYhw+b1/1JLmW4Y0qE/EDA==", - "dev": true, - "requires": { - "@colors/colors": "^1.5.0", - "cli-table3": "^0.6.0", - "cross-spawn": "^7.0.3", - "fs-extra": "^10.0.0", - "glob-escape": "^0.0.2", - "is-npm": "^5.0.0", - "lodash.camelcase": "^4.3.0", - "mocha": "~9.2.0", - "yargs": "15.3.1" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "dev": true - }, - "dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dev": true, - "requires": { - "character-entities": "^2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dmd": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.1.0.tgz", - "integrity": "sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "cache-point": "^2.0.0", - "common-sequence": "^2.0.2", - "file-set": "^4.0.2", - "handlebars": "^4.7.7", - "marked": "^4.0.12", - "object-get": "^2.1.1", - "reduce-flatten": "^3.0.1", - "reduce-unique": "^2.0.1", - "reduce-without": "^1.0.1", - "test-value": "^3.0.0", - "walk-back": "^5.1.0" - } - }, - "dprint": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/dprint/-/dprint-0.33.0.tgz", - "integrity": "sha512-VploASP7wL1HAYe5xWZKRwp8gW5zTdcG3Tb60DASv6QLnGKsl+OS+bY7wsXFrS4UcIbUNujXdsNG5FxBfRJIQg==", - "dev": true, - "requires": { - "yauzl": "=2.10.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "electron-to-chromium": { - "version": "1.4.304", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.304.tgz", - "integrity": "sha512-6c8M+ojPgDIXN2NyfGn8oHASXYnayj+gSEnGeLMKb9zjsySeVB/j7KkNAAG9yDcv8gNlhvFg5REa1N/kQU6pgA==", - "dev": true, - "peer": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - } - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "peer": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "peer": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "requires": { - "pify": "^2.2.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "factory.ts": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/factory.ts/-/factory.ts-0.5.2.tgz", - "integrity": "sha512-I4YDKuyMW+s2PocnWh/Ekv9wSStt/MNN1ZRb1qhy0Kv056ndlzbLHDsW9KEmTAqMpLI3BtjSqEdZ7ZfdnaXn9w==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "source-map-support": "^0.5.19" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "fetch-blob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz", - "integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-set": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz", - "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==", - "dev": true, - "requires": { - "array-back": "^5.0.0", - "glob": "^7.1.6" - }, - "dependencies": { - "array-back": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", - "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", - "dev": true - } - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "requires": { - "array-back": "^3.0.1" - }, - "dependencies": { - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-then-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", - "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "requires": { - "async": "^3.2.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-escape": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/glob-escape/-/glob-escape-0.0.2.tgz", - "integrity": "sha512-L/cXYz8x7qer1HAyUQ+mbjcUsJVdpRxpAf7CwqHoNBs9vTpABlGfNN4tzkDxt+u3Z7ZncVyKlCNPtzb0R/7WbA==", - "dev": true - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "jose": { - "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.4" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.2" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "jsdoc-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.1.1.tgz", - "integrity": "sha512-0pkuPCzVXiqsDAsVrNFXCkHzlyNepBIDVtwwehry4RJAnZmXtlAz7rh8F9FRz53u3NeynGbex+bpYWwi8lE66A==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "cache-point": "^2.0.0", - "collect-all": "^1.0.4", - "file-set": "^4.0.2", - "fs-then-native": "^2.0.0", - "jsdoc": "^3.6.10", - "object-to-spawn-args": "^2.0.1", - "temp-path": "^1.0.0", - "walk-back": "^5.1.0" - } - }, - "jsdoc-parse": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.1.0.tgz", - "integrity": "sha512-n/hDGQJa69IBun1yZAjqzV4gVR41+flZ3bIlm9fKvNe2Xjsd1/+zCo2+R9ls8LxtePgIWbpA1jU7xkB2lRdLLg==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "lodash.omit": "^4.5.0", - "lodash.pick": "^4.4.0", - "reduce-extract": "^1.0.0", - "sort-array": "^4.1.4", - "test-value": "^3.0.0" - } - }, - "jsdoc-to-markdown": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-7.1.1.tgz", - "integrity": "sha512-CI86d63xAVNO+ENumWwmJ034lYe5iGU5GwjtTA11EuphP9tpnoi4hrKgR/J8uME0D+o4KUpVfwX1fjZhc8dEtg==", - "dev": true, - "requires": { - "array-back": "^6.2.2", - "command-line-tool": "^0.8.0", - "config-master": "^3.1.0", - "dmd": "^6.1.0", - "jsdoc-api": "^7.1.1", - "jsdoc-parse": "^6.1.0", - "walk-back": "^5.1.0" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "peer": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsonld": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-5.2.0.tgz", - "integrity": "sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw==", - "dev": true, - "requires": { - "@digitalbazaar/http-client": "^1.1.0", - "canonicalize": "^1.0.1", - "lru-cache": "^6.0.0", - "rdf-canonize": "^3.0.0" - } - }, - "jsonld-checker": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/jsonld-checker/-/jsonld-checker-0.1.8.tgz", - "integrity": "sha512-jclmnPRrm5SEpaIV6IiSTJxplRAqIWHduQLsUfrYpZM41Ng48m1RN2/aUyHze/ynfO0D2UhlJBt8SdObsH5GBw==", - "dev": true, - "requires": { - "jsonld": "^5.2.0", - "node-fetch": "^2.6.1" - } - }, - "jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - } - }, - "jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true - }, - "ky": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz", - "integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==", - "dev": true - }, - "ky-universal": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz", - "integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "node-fetch": "3.0.0-beta.9" - }, - "dependencies": { - "node-fetch": { - "version": "3.0.0-beta.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", - "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^3.0.1", - "fetch-blob": "^2.1.1" - } - } - } - }, - "lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true - }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - } - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "peer": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "lodash.omit": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", - "dev": true - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dev": true, - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", - "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", - "dev": true, - "requires": {} - }, - "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true - }, - "mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - } - }, - "mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromark": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", - "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", - "dev": true, - "requires": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", - "dev": true, - "requires": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", - "dev": true, - "requires": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", - "dev": true, - "requires": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", - "dev": true, - "requires": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", - "dev": true, - "requires": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", - "dev": true - }, - "micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", - "dev": true - }, - "micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", - "dev": true, - "requires": { - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", - "dev": true, - "requires": { - "micromark-util-types": "^1.0.0" - } - }, - "micromark-util-sanitize-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", - "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", - "dev": true, - "requires": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", - "dev": true, - "requires": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", - "dev": true - }, - "micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "minipass": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", - "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz", - "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==", - "dev": true - }, - "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - } - } - }, - "mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-get": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz", - "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-to-spawn-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", - "integrity": "sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "rdf-canonize": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-3.3.0.tgz", - "integrity": "sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.5" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "reduce-extract": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", - "integrity": "sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==", - "dev": true, - "requires": { - "test-value": "^1.0.1" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - }, - "test-value": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", - "integrity": "sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==", - "dev": true, - "requires": { - "array-back": "^1.0.2", - "typical": "^2.4.2" - } - } - } - }, - "reduce-flatten": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", - "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==", - "dev": true - }, - "reduce-unique": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz", - "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==", - "dev": true - }, - "reduce-without": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz", - "integrity": "sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==", - "dev": true, - "requires": { - "test-value": "^2.0.0" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - }, - "test-value": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", - "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==", - "dev": true, - "requires": { - "array-back": "^1.0.3", - "typical": "^2.6.0" - } - } - } - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "peer": true - }, - "remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", - "dev": true, - "requires": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - } - }, - "request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "requires": { - "mri": "^1.1.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", - "dev": true, - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "sort-array": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", - "integrity": "sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==", - "dev": true, - "requires": { - "array-back": "^5.0.0", - "typical": "^6.0.1" - }, - "dependencies": { - "array-back": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", - "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", - "dev": true - }, - "typical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", - "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stream-connect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", - "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", - "dev": true, - "requires": { - "array-back": "^1.0.2" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - } - } - }, - "stream-via": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", - "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "table-layout": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", - "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "deep-extend": "~0.6.0", - "lodash.padend": "^4.6.1", - "typical": "^2.6.1", - "wordwrapjs": "^3.0.0" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true - }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "temp-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", - "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==", - "dev": true - }, - "terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - } - } - }, - "test-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", - "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "typical": "^2.6.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "peer": true - }, - "throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", - "dev": true - }, - "ts-mocha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-9.0.2.tgz", - "integrity": "sha512-WyQjvnzwrrubl0JT7EC1yWmNpcsU3fOuBFfdps30zbmFBgKniSaSOyZMZx+Wq7kytUs5CY+pEbSYEbGfIKnXTw==", - "dev": true, - "requires": { - "ts-node": "7.0.1", - "tsconfig-paths": "^3.5.0" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "ts-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", - "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", - "dev": true, - "requires": { - "arrify": "^1.0.0", - "buffer-from": "^1.1.0", - "diff": "^3.1.0", - "make-error": "^1.1.1", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "source-map-support": "^0.5.6", - "yn": "^2.0.0" - } - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "optional": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", - "dev": true - } - } - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tsconfig-paths": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", - "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", - "dev": true, - "requires": { - "json5": "^2.2.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "txm": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/txm/-/txm-8.1.0.tgz", - "integrity": "sha512-mVDmoN13jYX3igNcnS+TEJJmMIRLjn0wch/wOI23z5IkCKiw9xinv1WkugB55j57W8MfuEk/psVVO4BWMfZxfA==", - "dev": true, - "requires": { - "async": "^3.2.1", - "diff-match-patch": "^1.0.5", - "kleur": "^4.1.4", - "remark-parse": "^10.0.1", - "supports-color": "^9.1.0", - "unified": "^10.1.1" - }, - "dependencies": { - "supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", - "dev": true - } - } - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typedoc": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", - "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", - "dev": true, - "requires": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.0", - "shiki": "^0.14.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "typedoc-plugin-markdown": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", - "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", - "dev": true, - "requires": { - "handlebars": "^4.7.7" - } - }, - "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", - "dev": true - }, - "typical": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", - "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", - "dev": true, - "optional": true - }, - "underscore": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", - "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", - "dev": true - }, - "unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" - } - }, - "unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "peer": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "dev": true, - "requires": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - } - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vfile": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", - "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - } - }, - "vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - } - }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "walk-back": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", - "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", - "dev": true - }, - "wasm-opt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.3.0.tgz", - "integrity": "sha512-24+IOboX4Sav0bI8Krwf0Y6dnpN4KxYtqpl0qWt86qVLsmayUqx1KMBrJTlQWNC+/dsqzQJjK6QvxNJsZYOgJg==", - "dev": true, - "requires": { - "node-fetch": "^2.6.7", - "tar": "^6.1.11" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "peer": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "webpack": { - "version": "5.76.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", - "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wordwrapjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", - "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", - "dev": true, - "requires": { - "reduce-flatten": "^1.0.1", - "typical": "^2.6.1" - }, - "dependencies": { - "reduce-flatten": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", - "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==", - "dev": true - } - } - }, - "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 5060bd54f6..004f2f2292 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@iota/identity-wasm", - "version": "1.5.0", + "version": "1.3.1", "description": "WASM bindings for IOTA Identity - A Self Sovereign Identity Framework implementing the DID and VC standards from W3C. To be used in Javascript/Typescript", "repository": { "type": "git", @@ -58,7 +58,6 @@ ], "devDependencies": { "@transmute/did-key-ed25519": "0.3.0-unstable.9", - "@types/jsonwebtoken": "^9.0.7", "@types/mocha": "^9.1.0", "big-integer": "^1.6.51", "copy-webpack-plugin": "^7.0.0", @@ -82,7 +81,6 @@ "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", - "jose": "^5.9.6", "node-fetch": "^2.6.7" }, "peerDependencies": { diff --git a/bindings/wasm/rust-toolchain.toml b/bindings/wasm/rust-toolchain.toml index eb46cc977d..825d39b571 100644 --- a/bindings/wasm/rust-toolchain.toml +++ b/bindings/wasm/rust-toolchain.toml @@ -1,6 +1,5 @@ [toolchain] -# @itsyaasir - Update to latest stable version when wasm-bindgen is updated -channel = "1.81" +channel = "stable" components = ["rustfmt"] targets = ["wasm32-unknown-unknown"] profile = "minimal" diff --git a/bindings/wasm/src/common/imported_document_lock.rs b/bindings/wasm/src/common/imported_document_lock.rs index 4452ce6dd2..4852ab216e 100644 --- a/bindings/wasm/src/common/imported_document_lock.rs +++ b/bindings/wasm/src/common/imported_document_lock.rs @@ -79,7 +79,7 @@ impl From<&ArrayIToCoreDocument> for Vec { pub(crate) struct ImportedDocumentReadGuard<'a>(tokio::sync::RwLockReadGuard<'a, CoreDocument>); -impl AsRef for ImportedDocumentReadGuard<'_> { +impl<'a> AsRef for ImportedDocumentReadGuard<'a> { fn as_ref(&self) -> &CoreDocument { self.0.as_ref() } diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs index 8c0effc4c7..035e7838bf 100644 --- a/bindings/wasm/src/error.rs +++ b/bindings/wasm/src/error.rs @@ -108,8 +108,7 @@ impl_wasm_error_from!( identity_iota::sd_jwt_payload::Error, identity_iota::credential::KeyBindingJwtError, identity_iota::credential::status_list_2021::StatusListError, - identity_iota::credential::status_list_2021::StatusList2021CredentialError, - identity_iota::sd_jwt_rework::Error + identity_iota::credential::status_list_2021::StatusList2021CredentialError ); // Similar to `impl_wasm_error_from`, but uses the types name instead of requiring/calling Into &'static str @@ -152,7 +151,7 @@ fn error_chain_fmt(e: &impl std::error::Error, f: &mut std::fmt::Formatter<'_>) struct ErrorMessage<'a, E: std::error::Error>(&'a E); -impl Display for ErrorMessage<'_, E> { +impl<'a, E: std::error::Error> Display for ErrorMessage<'a, E> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { error_chain_fmt(self.0, f) } @@ -176,15 +175,6 @@ impl From for WasmError<'_> { } } -impl From for WasmError<'_> { - fn from(value: anyhow::Error) -> Self { - Self { - name: Cow::Borrowed("Generic Error"), - message: Cow::Owned(value.to_string()), - } - } -} - impl From for WasmError<'_> { fn from(error: identity_iota::iota::block::Error) -> Self { Self { @@ -194,15 +184,6 @@ impl From for WasmError<'_> { } } -impl From for WasmError<'_> { - fn from(value: serde_wasm_bindgen::Error) -> Self { - Self { - name: Cow::Borrowed("JSConversionError"), - message: Cow::Owned(value.to_string()), - } - } -} - impl From for WasmError<'_> { fn from(error: identity_iota::credential::CompoundCredentialValidationError) -> Self { Self { @@ -284,15 +265,6 @@ impl From for WasmError<'_> { } } -impl From for WasmError<'_> { - fn from(error: identity_iota::credential::sd_jwt_vc::Error) -> Self { - Self { - name: Cow::Borrowed("SdJwtVcError"), - message: Cow::Owned(ErrorMessage(&error).to_string()), - } - } -} - /// Convenience struct to convert Result to errors in the Rust library. pub struct JsValueResult(pub(crate) Result); diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 25489c367c..0c50c67c8e 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -24,8 +24,7 @@ use crate::jose::WasmJwsAlgorithm; use core::ops::Deref; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(transparent)] -#[wasm_bindgen(js_name = Jwk)] +#[wasm_bindgen(js_name = Jwk, inspectable)] pub struct WasmJwk(pub(crate) Jwk); #[wasm_bindgen(js_class = Jwk)] diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 549516005d..6f65fa54be 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -4,6 +4,7 @@ * Modifications Copyright 2024 Fondazione LINKS. */ +#![forbid(unsafe_code)] #![allow(deprecated)] #![allow(clippy::upper_case_acronyms)] // wasm_bindgen calls drop on non-Drop types. When/If this is fixed, this can be removed (no issue to link here yet). @@ -30,7 +31,6 @@ pub mod jpt; pub mod resolver; pub mod revocation; pub mod sd_jwt; -pub mod sd_jwt_vc; pub mod storage; pub mod verification; diff --git a/bindings/wasm/src/macros.rs b/bindings/wasm/src/macros.rs index 26cb197993..ecc99a8082 100644 --- a/bindings/wasm/src/macros.rs +++ b/bindings/wasm/src/macros.rs @@ -29,14 +29,14 @@ macro_rules! impl_wasm_json { impl $wasm_class { /// Serializes this to a JSON object. #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> $crate::error::Result { + pub fn to_json(&self) -> $crate::error::Result { use $crate::error::WasmResult; - wasm_bindgen::JsValue::from_serde(&self.0).wasm_result() + JsValue::from_serde(&self.0).wasm_result() } /// Deserializes an instance from a JSON object. #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &wasm_bindgen::JsValue) -> $crate::error::Result<$wasm_class> { + pub fn from_json(json: &JsValue) -> $crate::error::Result<$wasm_class> { use $crate::error::WasmResult; json.into_serde().map(Self).wasm_result() } diff --git a/bindings/wasm/src/sd_jwt_vc/builder.rs b/bindings/wasm/src/sd_jwt_vc/builder.rs deleted file mode 100644 index b8606bccba..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/builder.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::core::StringOrUrl; -use identity_iota::core::Url; -use identity_iota::credential::sd_jwt_vc::SdJwtVcBuilder; -use wasm_bindgen::prelude::wasm_bindgen; - -use crate::common::WasmTimestamp; -use crate::credential::WasmCredential; -use crate::error::Result; -use crate::error::WasmResult; -use crate::sd_jwt_vc::WasmSdJwtVc; - -use super::sd_jwt_v2::WasmHasher; -use super::sd_jwt_v2::WasmJwsSigner; -use super::sd_jwt_v2::WasmRequiredKeyBinding; -use super::WasmStatus; - -#[wasm_bindgen(js_name = SdJwtVcBuilder)] -pub struct WasmSdJwtVcBuilder(pub(crate) SdJwtVcBuilder); - -#[wasm_bindgen(js_class = SdJwtVcBuilder)] -impl WasmSdJwtVcBuilder { - /// Creates a new {@link SdJwtVcBuilder} using `object` JSON representation and a given - /// hasher `hasher`. - #[wasm_bindgen(constructor)] - pub fn new(object: js_sys::Object, hasher: WasmHasher) -> Result { - let object = serde_wasm_bindgen::from_value::(object.into()).wasm_result()?; - SdJwtVcBuilder::new_with_hasher(object, hasher).map(Self).wasm_result() - } - - /// Creates a new [`SdJwtVcBuilder`] starting from a {@link Credential} that is converted to a JWT claim set. - #[wasm_bindgen(js_name = fromCredential)] - pub fn new_from_credential(credential: WasmCredential, hasher: WasmHasher) -> Result { - SdJwtVcBuilder::new_from_credential(credential.0, hasher) - .map(Self) - .wasm_result() - } - - /// Substitutes a value with the digest of its disclosure. - /// - /// ## Notes - /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - #[wasm_bindgen(js_name = makeConcealable)] - pub fn make_concealable(self, path: &str) -> Result { - self.0.make_concealable(path).map(Self).wasm_result() - } - - /// Sets the JWT header. - /// ## Notes - /// - if {@link SdJwtVcBuilder.header} is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": - /// "" } ``` - /// - `alg` is always replaced with the value passed to {@link SdJwtVcBuilder.finish}. - #[wasm_bindgen] - pub fn header(self, header: js_sys::Object) -> Self { - let header = serde_wasm_bindgen::from_value(header.into()).expect("JS object is a valid JSON object"); - Self(self.0.header(header)) - } - - /// Adds a decoy digest to the specified path. - /// - /// `path` indicates the pointer to the value that will be concealed using the syntax of - /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - /// - /// Use `path` = "" to add decoys to the top level. - #[wasm_bindgen(js_name = addDecoys)] - pub fn add_decoys(self, path: &str, number_of_decoys: usize) -> Result { - self.0.add_decoys(path, number_of_decoys).map(Self).wasm_result() - } - - /// Require a proof of possession of a given key from the holder. - /// - /// This operation adds a JWT confirmation (`cnf`) claim as specified in - /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). - #[wasm_bindgen(js_name = requireKeyBinding)] - pub fn require_key_binding(self, key_bind: WasmRequiredKeyBinding) -> Result { - let key_bind = serde_wasm_bindgen::from_value(key_bind.into()).wasm_result()?; - Ok(Self(self.0.require_key_binding(key_bind))) - } - - /// Inserts an `iss` claim. See {@link SdJwtVcClaim.iss}. - #[wasm_bindgen] - pub fn iss(self, issuer: &str) -> Result { - let url = Url::parse(issuer).wasm_result()?; - Ok(Self(self.0.iss(url))) - } - - /// Inserts a `nbf` claim. See {@link SdJwtVcClaims.nbf}. - #[wasm_bindgen] - pub fn nbf(self, nbf: WasmTimestamp) -> Self { - Self(self.0.nbf(nbf.0)) - } - - /// Inserts a `exp` claim. See {@link SdJwtVcClaims.exp}. - #[wasm_bindgen] - pub fn exp(self, exp: WasmTimestamp) -> Self { - Self(self.0.exp(exp.0)) - } - - /// Inserts a `iat` claim. See {@link SdJwtVcClaims.iat}. - #[wasm_bindgen] - pub fn iat(self, iat: WasmTimestamp) -> Self { - Self(self.0.iat(iat.0)) - } - - /// Inserts a `vct` claim. See {@link SdJwtVcClaims.vct}. - #[wasm_bindgen] - pub fn vct(self, vct: &str) -> Self { - let vct = StringOrUrl::parse(vct).unwrap(); - Self(self.0.vct(vct)) - } - - /// Inserts a `sub` claim. See {@link SdJwtVcClaims.sub}. - #[allow(clippy::should_implement_trait)] - #[wasm_bindgen] - pub fn sub(self, sub: &str) -> Self { - let sub = StringOrUrl::parse(sub).unwrap(); - Self(self.0.sub(sub)) - } - - /// Inserts a `status` claim. See {@link SdJwtVcClaims.status}. - #[wasm_bindgen] - pub fn status(self, status: WasmStatus) -> Result { - let status = serde_wasm_bindgen::from_value(status.into()).wasm_result()?; - Ok(Self(self.0.status(status))) - } - - /// Creates an {@link SdJwtVc} with the provided data. - #[wasm_bindgen] - pub async fn finish(self, signer: &WasmJwsSigner, alg: &str) -> Result { - self.0.finish(signer, alg).await.map(WasmSdJwtVc).wasm_result() - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/claims.rs b/bindings/wasm/src/sd_jwt_vc/claims.rs deleted file mode 100644 index 77c2dc3a89..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/claims.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(typescript_custom_section)] -const I_SD_JWT_VC_CLAIMS: &str = r#" -interface ISdJwtVcClaims { - iss: string; - vct: string; - status: SdJwtVcStatus; - nbf?: string; - exp?: string; - iat?: string; - sub?: string; -} - -type SdJwtVcClaims = ISdJwtVcClaims & SdJwtClaims; -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "SdJwtVcClaims")] - pub type WasmSdJwtVcClaims; -} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs b/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs deleted file mode 100644 index b99c2b0993..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::credential::sd_jwt_vc::metadata::ClaimDisplay; -use identity_iota::credential::sd_jwt_vc::metadata::ClaimMetadata; -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(typescript_custom_section)] -const T_CLAIM_PATH: &str = r#" -type ClaimPathSegment = string | number | null; -type ClaimPath = ClaimPathSegment[]; -"#; - -#[wasm_bindgen(typescript_custom_section)] -const T_DISCLOSABILITY: &str = r#" -type ClaimDisclosability = "always" | "allowed" | "never"; -"#; - -#[wasm_bindgen] -extern "C" { - #[derive(Clone)] - #[wasm_bindgen(typescript_type = ClaimPathSegment)] - pub type WasmClaimPathSegment; - - #[derive(Clone)] - #[wasm_bindgen(typescript_type = ClaimPath)] - pub type WasmClaimPath; - - #[derive(Clone)] - #[wasm_bindgen(typescript_type = ClaimDisclosability)] - pub type WasmClaimDisclosability; -} - -#[wasm_bindgen(js_name = ClaimMetadata, inspectable, getter_with_clone)] -pub struct WasmClaimMetadata { - pub path: WasmClaimPath, - pub display: Vec, - pub sd: Option, - pub svg_id: Option, -} - -impl From for ClaimMetadata { - fn from(value: WasmClaimMetadata) -> Self { - let path = serde_wasm_bindgen::from_value(value.path.into()).unwrap(); - let display = value.display.into_iter().map(ClaimDisplay::from).collect(); - let sd = value.sd.map(|sd| serde_wasm_bindgen::from_value(sd.into()).unwrap()); - Self { - path, - display, - sd, - svg_id: value.svg_id, - } - } -} - -#[derive(Clone)] -#[wasm_bindgen(js_name = ClaimDisplay, inspectable, getter_with_clone)] -pub struct WasmClaimDisplay { - /// A language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). - pub lang: String, - /// A human-readable label for the claim. - pub label: String, - /// A human-readable description for the claim. - pub description: Option, -} - -impl From for ClaimDisplay { - fn from(value: WasmClaimDisplay) -> Self { - Self { - lang: value.lang, - label: value.label, - description: value.description, - } - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs b/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs deleted file mode 100644 index 03c3e37e65..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::core::Url; -use identity_iota::credential::sd_jwt_vc::metadata::IssuerMetadata; -use serde::Serialize; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; - -use crate::error::Result; -use crate::error::WasmResult; -use crate::sd_jwt_vc::WasmSdJwtVc; - -#[wasm_bindgen(typescript_custom_section)] -pub const I_JWKS: &str = r#" -type Jwks = { jwks_uri: string } | { jwks: { keys: IJwk[] }}; -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = Jwks)] - pub type WasmJwks; -} - -#[wasm_bindgen(js_name = IssuerMetadata)] -pub struct WasmIssuerMetadata(pub(crate) IssuerMetadata); - -#[wasm_bindgen(js_class = IssuerMetadata)] -impl WasmIssuerMetadata { - #[wasm_bindgen(constructor)] - pub fn new(issuer: String, jwks: WasmJwks) -> Result { - let issuer = Url::parse(&issuer).wasm_result()?; - let jwks = serde_wasm_bindgen::from_value(jwks.into()).wasm_result()?; - - Ok(Self(IssuerMetadata { issuer, jwks })) - } - - #[wasm_bindgen] - pub fn issuer(&self) -> String { - self.0.issuer.to_string() - } - - #[wasm_bindgen] - pub fn jwks(&self) -> Result { - serde_wasm_bindgen::to_value(&self.0.jwks) - .wasm_result() - .map(JsCast::unchecked_into) - } - - /// Checks the validity of this {@link IssuerMetadata}. - /// {@link IssuerMetadata.issuer} must match `sd_jwt_vc`'s `iss` claim. - #[wasm_bindgen] - pub fn validate(&self, sd_jwt_vc: &WasmSdJwtVc) -> Result<()> { - self.0.validate(&sd_jwt_vc.0).wasm_result() - } - - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> Result { - let js_serializer = serde_wasm_bindgen::Serializer::default().serialize_maps_as_objects(true); - self.0.serialize(&js_serializer).wasm_result() - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs b/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs deleted file mode 100644 index eff8dbcf41..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod claim; -mod issuer; -mod vc_type; - -pub use claim::*; -pub use issuer::*; -pub use vc_type::*; diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs b/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs deleted file mode 100644 index 409cfdff07..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::credential::sd_jwt_vc::metadata::TypeMetadata; -use serde::Serialize; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; - -use crate::error::Result; -use crate::error::WasmResult; -use crate::sd_jwt_vc::resolver::ResolverUrlToValue; - -#[wasm_bindgen(typescript_custom_section)] -const I_TYPE_METADATA: &str = r#" -type SchemaByUri = { schema_uri: string, "schema_uri#integrity"?: string }; -type SchemaByObject = { schema: unknown, "schema#integrity"?: string }; -type NoSchema = {}; -type TypeSchema = SchemaByUri | SchemaByObject | NoSchema; - -type TypeMetadataHelper = { - name?: string; - description?: string; - extends?: string; - "extends#integrity"?: string; - display?: unknown[]; - claims?: ClaimMetadata[]; -} & TypeSchema; -"#; - -#[wasm_bindgen] -extern "C" { - pub type TypeMetadataHelper; -} - -#[derive(Serialize, Clone)] -#[serde(transparent)] -#[wasm_bindgen(js_name = TypeMetadata)] -pub struct WasmTypeMetadata(pub(crate) TypeMetadata); - -impl_wasm_json!(WasmTypeMetadata, TypeMetadata); - -#[wasm_bindgen(js_class = TypeMetadata)] -impl WasmTypeMetadata { - #[wasm_bindgen(constructor)] - pub fn new(helper: TypeMetadataHelper) -> Result { - serde_wasm_bindgen::from_value(helper.into()).map(Self).wasm_result() - } - - #[wasm_bindgen(js_name = intoInner)] - pub fn into_inner(&self) -> Result { - serde_wasm_bindgen::to_value(&self.0) - .wasm_result() - .and_then(JsCast::dyn_into) - } - - /// Uses this {@link TypeMetadata} to validate JSON object `credential`. This method fails - /// if the schema is referenced instead of embedded. - /// Use {@link TypeMetadata.validate_credential_with_resolver} for such cases. - /// ## Notes - /// This method ignores type extensions. - #[wasm_bindgen(js_name = validateCredential)] - pub fn validate_credential(&self, credential: JsValue) -> Result<()> { - let credential = serde_wasm_bindgen::from_value(credential).wasm_result()?; - self.0.validate_credential(&credential).wasm_result() - } - - /// Similar to {@link TypeMetadata.validate_credential}, but accepts a {@link Resolver} - /// {@link Url} -> {@link any} that is used to resolve any reference to either - /// another type or JSON schema. - #[wasm_bindgen(js_name = validateCredentialWithResolver)] - pub async fn validate_credential_with_resolver( - &self, - credential: JsValue, - resolver: &ResolverUrlToValue, - ) -> Result<()> { - let credential = serde_wasm_bindgen::from_value(credential).wasm_result()?; - self - .0 - .validate_credential_with_resolver(&credential, resolver) - .await - .wasm_result() - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/mod.rs b/bindings/wasm/src/sd_jwt_vc/mod.rs deleted file mode 100644 index 7396c14386..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod builder; -mod claims; -pub mod metadata; -mod presentation; -mod resolver; -pub mod sd_jwt_v2; -mod status; -mod token; - -pub use builder::*; -pub use claims::*; -pub use presentation::*; -pub use status::*; -pub use token::*; diff --git a/bindings/wasm/src/sd_jwt_vc/presentation.rs b/bindings/wasm/src/sd_jwt_vc/presentation.rs deleted file mode 100644 index 7b4c4da12e..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/presentation.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::credential::sd_jwt_vc::SdJwtVcPresentationBuilder; -use wasm_bindgen::prelude::wasm_bindgen; - -use super::sd_jwt_v2::WasmDisclosure; -use super::sd_jwt_v2::WasmHasher; -use super::sd_jwt_v2::WasmKeyBindingJwt; -use super::WasmSdJwtVc; -use crate::error::Result; -use crate::error::WasmResult; - -#[wasm_bindgen(js_name = SdJwtVcPresentationBuilder)] -pub struct WasmSdJwtVcPresentationBuilder(pub(crate) SdJwtVcPresentationBuilder); - -#[wasm_bindgen(js_class = SdJwtVcPresentationBuilder)] -impl WasmSdJwtVcPresentationBuilder { - /// Prepares a new presentation from a given {@link SdJwtVc}. - #[wasm_bindgen(constructor)] - pub fn new(token: WasmSdJwtVc, hasher: &WasmHasher) -> Result { - SdJwtVcPresentationBuilder::new(token.0, hasher).map(Self).wasm_result() - } - - #[wasm_bindgen] - pub fn conceal(self, path: &str) -> Result { - self.0.conceal(path).map(Self).wasm_result() - } - - #[wasm_bindgen(js_name = attachKeyBindingJwt)] - pub fn attach_key_binding_jwt(self, kb_jwt: WasmKeyBindingJwt) -> Self { - Self(self.0.attach_key_binding_jwt(kb_jwt.0)) - } - - #[wasm_bindgen] - pub fn finish(self) -> Result { - self - .0 - .finish() - .map(|(token, disclosures)| PresentationResult { - sd_jwt_vc: WasmSdJwtVc(token), - disclosures: disclosures.into_iter().map(WasmDisclosure::from).collect(), - }) - .wasm_result() - } -} - -#[wasm_bindgen(js_name = SdJwtVcPresentationResult, getter_with_clone)] -pub struct PresentationResult { - #[wasm_bindgen(js_name = sdJwtVc)] - pub sd_jwt_vc: WasmSdJwtVc, - pub disclosures: Vec, -} diff --git a/bindings/wasm/src/sd_jwt_vc/resolver.rs b/bindings/wasm/src/sd_jwt_vc/resolver.rs deleted file mode 100644 index b90a67b9d1..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/resolver.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::anyhow; -use async_trait::async_trait; -use identity_iota::core::Url; -use identity_iota::credential::sd_jwt_vc::resolver::Error as ErrorR; -use identity_iota::credential::sd_jwt_vc::Resolver; -use js_sys::Uint8Array; -use wasm_bindgen::prelude::*; - -#[wasm_bindgen(typescript_custom_section)] -const I_RESOLVER: &str = r#" -interface IResolver { - resolve: (input: I) => Promise; -} -"#; - -#[wasm_bindgen] -extern "C" { - // Resolver> - #[wasm_bindgen(typescript_type = "IResolver")] - pub type ResolverStringToUint8Array; - - #[wasm_bindgen(structural, method, catch)] - pub async fn resolve(this: &ResolverStringToUint8Array, input: &str) -> Result; - - // Resolver - #[wasm_bindgen(typescript_type = "IResolver")] - pub type ResolverUrlToValue; - - #[wasm_bindgen(structural, method, catch)] - pub async fn resolve(this: &ResolverUrlToValue, input: &str) -> Result; -} - -#[async_trait(?Send)] -impl Resolver> for ResolverStringToUint8Array -where - I: AsRef + Sync, -{ - async fn resolve(&self, input: &I) -> Result, ErrorR> { - self - .resolve(input.as_ref()) - .await - .map(|arr| arr.to_vec()) - .map_err(|e| ErrorR::Generic(anyhow::anyhow!("{}", e.to_string()))) - } -} - -#[async_trait(?Send)] -impl Resolver for ResolverStringToUint8Array -where - I: AsRef + Sync, -{ - async fn resolve(&self, input: &I) -> Result { - self - .resolve(input.as_ref()) - .await - .map(|arr| arr.to_vec()) - .map_err(|e| ErrorR::Generic(anyhow::anyhow!("{}", e.to_string()))) - .and_then(|bytes| serde_json::from_slice(&bytes).map_err(|e| ErrorR::ParsingFailure(e.into()))) - } -} - -#[async_trait(?Send)] -impl Resolver for ResolverUrlToValue { - async fn resolve(&self, input: &Url) -> Result { - self - .resolve(input.as_str()) - .await - .map(|js_value| serde_wasm_bindgen::from_value(js_value).expect("JS value is a JSON value")) - .map_err(|e| ErrorR::Generic(anyhow!("{}", e.to_string()))) - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs deleted file mode 100644 index e030bbbb94..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::sd_jwt_rework::SdJwtBuilder; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; - -use crate::error::Result; -use crate::error::WasmResult; -use crate::sd_jwt_vc::sd_jwt_v2::WasmSdJwt; - -use super::WasmHasher; -use super::WasmJwsSigner; -use super::WasmRequiredKeyBinding; - -#[wasm_bindgen(js_name = SdJwtBuilder)] -pub struct WasmSdJwtBuilder(pub(crate) SdJwtBuilder); - -#[wasm_bindgen(js_class = SdJwtBuilder)] -impl WasmSdJwtBuilder { - /// Creates a new {@link SdJwtVcBuilder} using `object` JSON representation and a given - /// hasher `hasher`. - #[wasm_bindgen(constructor)] - pub fn new(object: js_sys::Object, hasher: WasmHasher, salt_size: Option) -> Result { - let object = serde_wasm_bindgen::from_value::(object.into()).wasm_result()?; - let salt_size = salt_size.unwrap_or(30); - SdJwtBuilder::new_with_hasher_and_salt_size(object, hasher, salt_size) - .map(Self) - .wasm_result() - } - - /// Substitutes a value with the digest of its disclosure. - /// - /// ## Notes - /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - #[wasm_bindgen(js_name = makeConcealable)] - pub fn make_concealable(self, path: &str) -> Result { - self.0.make_concealable(path).map(Self).wasm_result() - } - - /// Sets the JWT header. - /// ## Notes - /// - if {@link SdJwtVcBuilder.header} is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": - /// "" } ``` - /// - `alg` is always replaced with the value passed to {@link SdJwtVcBuilder.finish}. - #[wasm_bindgen] - pub fn header(self, header: js_sys::Object) -> Self { - let header = serde_wasm_bindgen::from_value(header.into()).expect("JS object is a valid JSON object"); - Self(self.0.header(header)) - } - - /// Adds a new claim to the underlying object. - #[wasm_bindgen(js_name = insertClaim)] - pub fn insert_claim(self, key: String, value: JsValue) -> Result { - let value = serde_wasm_bindgen::from_value::(value).wasm_result()?; - self.0.insert_claim(key, value).map(Self).wasm_result() - } - - /// Adds a decoy digest to the specified path. - /// - /// `path` indicates the pointer to the value that will be concealed using the syntax of - /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - /// - /// Use `path` = "" to add decoys to the top level. - #[wasm_bindgen(js_name = addDecoys)] - pub fn add_decoys(self, path: &str, number_of_decoys: usize) -> Result { - self.0.add_decoys(path, number_of_decoys).map(Self).wasm_result() - } - - /// Require a proof of possession of a given key from the holder. - /// - /// This operation adds a JWT confirmation (`cnf`) claim as specified in - /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). - #[wasm_bindgen(js_name = requireKeyBinding)] - pub fn require_key_binding(self, key_bind: WasmRequiredKeyBinding) -> Result { - let key_bind = serde_wasm_bindgen::from_value(key_bind.into()).wasm_result()?; - Ok(Self(self.0.require_key_binding(key_bind))) - } - - /// Creates an {@link SdJwtVc} with the provided data. - #[wasm_bindgen] - pub async fn finish(self, signer: &WasmJwsSigner, alg: &str) -> Result { - self.0.finish(signer, alg).await.map(WasmSdJwt).wasm_result() - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs deleted file mode 100644 index b207fc0999..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::sd_jwt_rework::Disclosure; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; - -use crate::error::Result; -use crate::error::WasmResult; - -/// A disclosable value. -/// Both object properties and array elements disclosures are supported. -/// -/// See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures -#[derive(Clone)] -#[wasm_bindgen(js_name = DisclosureV2, inspectable, getter_with_clone)] -pub struct WasmDisclosure { - pub salt: String, - #[wasm_bindgen(js_name = claimName)] - pub claim_name: Option, - #[wasm_bindgen(js_name = claimValue)] - pub claim_value: JsValue, - unparsed: String, -} - -#[wasm_bindgen(js_class = DisclosureV2)] -impl WasmDisclosure { - #[wasm_bindgen] - pub fn parse(s: &str) -> Result { - Disclosure::parse(s).map(Self::from).wasm_result() - } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = toString)] - pub fn to_string(&self) -> String { - self.unparsed.clone() - } -} - -impl From for Disclosure { - fn from(value: WasmDisclosure) -> Self { - Disclosure::parse(&value.unparsed).expect("valid WasmDisclosure is a valid disclosure") - } -} - -impl From for WasmDisclosure { - fn from(value: Disclosure) -> Self { - let unparsed = value.to_string(); - let Disclosure { - salt, - claim_name, - claim_value, - .. - } = value; - let claim_value = serde_wasm_bindgen::to_value(&claim_value).expect("serde JSON Value is a valid JS Value"); - - Self { - salt, - claim_name, - claim_value, - unparsed, - } - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs deleted file mode 100644 index 18016713f3..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::OnceLock; - -use identity_iota::sd_jwt_rework::Hasher; -use identity_iota::sd_jwt_rework::Sha256Hasher; -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(typescript_custom_section)] -const I_HASHER: &str = r#" -interface Hasher { - digest: (input: Uint8Array) => Uint8Array; - algName: () => string; - encodedDigest: (data: string) => string; -} -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "Hasher")] - pub type WasmHasher; - - #[wasm_bindgen(structural, method)] - pub fn digest(this: &WasmHasher, input: &[u8]) -> Vec; - - #[wasm_bindgen(structural, method, js_name = "algName")] - pub fn alg_name(this: &WasmHasher) -> String; - - #[wasm_bindgen(structural, method, js_name = "encodedDigest")] - pub fn encoded_digest(this: &WasmHasher, data: &str) -> String; -} - -impl Hasher for WasmHasher { - fn alg_name(&self) -> &str { - static ALG: OnceLock = OnceLock::new(); - ALG.get_or_init(|| self.alg_name()) - } - - fn digest(&self, input: &[u8]) -> Vec { - self.digest(input) - } - - fn encoded_digest(&self, disclosure: &str) -> String { - self.encoded_digest(disclosure) - } -} - -#[wasm_bindgen(js_name = Sha256Hasher)] -pub struct WasmSha256Hasher(pub(crate) Sha256Hasher); - -#[wasm_bindgen(js_class = Sha256Hasher)] -impl WasmSha256Hasher { - #[allow(clippy::new_without_default)] - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - Self(Sha256Hasher) - } - - #[wasm_bindgen(js_name = algName)] - pub fn alg_name(&self) -> String { - self.0.alg_name().to_owned() - } - - #[wasm_bindgen] - pub fn digest(&self, input: &[u8]) -> Vec { - self.0.digest(input) - } - - #[wasm_bindgen(js_name = encodedDigest)] - pub fn encoded_digest(&self, data: &str) -> String { - self.0.encoded_digest(data) - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs deleted file mode 100644 index d483737237..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::credential; -use identity_iota::credential::sd_jwt_vc; -use identity_iota::sd_jwt_rework::KeyBindingJwt; -use identity_iota::sd_jwt_rework::KeyBindingJwtBuilder; -use identity_iota::sd_jwt_rework::Sha256Hasher; -use js_sys::Object; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; - -use crate::common::WasmTimestamp; -use crate::error::Result; -use crate::error::WasmResult; - -use super::WasmJwsSigner; -use super::WasmSdJwt; - -#[wasm_bindgen(typescript_custom_section)] -const T_REQUIRED_KB: &str = r#" -type RequiredKeyBinding = { jwk: Jwk } - | { jwe: string } - | { kid: string } - | { jwu: { jwu: string, kid: string }} - | unknown; -"#; - -#[wasm_bindgen(typescript_custom_section)] -const I_KB_JWT_CLAIMS: &str = r#" -interface KeyBindingJwtClaimsV2 { - iat: number; - aud: string; - nonce: string; - sd_hash: string; - [properties: string]: unknown; -} -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "RequiredKeyBinding")] - pub type WasmRequiredKeyBinding; - - #[wasm_bindgen(typescript_type = "KeyBindingJwtClaimsV2")] - pub type WasmKeyBindingJwtClaims; -} - -#[wasm_bindgen(js_name = KeyBindingJwt)] -pub struct WasmKeyBindingJwt(pub(crate) KeyBindingJwt); - -#[wasm_bindgen(js_class = KeyBindingJwt)] -impl WasmKeyBindingJwt { - #[wasm_bindgen] - pub fn parse(s: &str) -> Result { - s.parse::() - .map_err(sd_jwt_vc::Error::from) - .map(WasmKeyBindingJwt) - .wasm_result() - } - - #[wasm_bindgen] - pub fn claims(&self) -> WasmKeyBindingJwtClaims { - serde_wasm_bindgen::to_value(self.0.claims()).unwrap().unchecked_into() - } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = "toString")] - pub fn to_string(&self) -> String { - self.0.to_string() - } - - #[wasm_bindgen(js_name = "toJSON")] - pub fn to_json(&self) -> JsValue { - JsValue::from_str(&self.to_string()) - } -} - -#[wasm_bindgen(js_name = KeyBindingJwtBuilder)] -pub struct WasmKeyBindingJwtBuilder(pub(crate) KeyBindingJwtBuilder); - -#[wasm_bindgen(js_class = KeyBindingJwtBuilder)] -impl WasmKeyBindingJwtBuilder { - #[allow(clippy::new_without_default)] - #[wasm_bindgen(constructor)] - pub fn new() -> WasmKeyBindingJwtBuilder { - Self(KeyBindingJwtBuilder::default()) - } - - #[wasm_bindgen(js_name = "fromObject")] - pub fn from_object(obj: Object) -> Result { - serde_wasm_bindgen::from_value(obj.into()) - .map(KeyBindingJwtBuilder::from_object) - .map(Self) - .wasm_result() - } - - #[wasm_bindgen] - pub fn header(self, header: Object) -> Result { - serde_wasm_bindgen::from_value(header.into()) - .map(|obj| self.0.header(obj)) - .map(Self) - .wasm_result() - } - - #[wasm_bindgen] - pub fn iat(self, iat: WasmTimestamp) -> Self { - let iat = iat.0.to_unix(); - Self(self.0.iat(iat)) - } - - #[wasm_bindgen] - pub fn aud(self, aud: String) -> Self { - Self(self.0.aud(aud)) - } - - #[wasm_bindgen] - pub fn nonce(self, nonce: String) -> Self { - Self(self.0.nonce(nonce)) - } - - #[wasm_bindgen(js_name = "insertProperty")] - pub fn insert_property(self, name: String, value: JsValue) -> Result { - let value = serde_wasm_bindgen::from_value(value).wasm_result()?; - Ok(Self(self.0.insert_property(&name, value))) - } - - #[wasm_bindgen] - pub async fn finish(self, sd_jwt: &WasmSdJwt, alg: &str, signer: &WasmJwsSigner) -> Result { - self - .0 - .finish(&sd_jwt.0, &Sha256Hasher, alg, signer) - .await - .map(WasmKeyBindingJwt) - .map_err(|e| credential::Error::from(sd_jwt_vc::Error::SdJwt(e))) - .wasm_result() - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs deleted file mode 100644 index 5a526b5d62..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod builder; -mod disclosure; -mod hasher; -mod kb_jwt; -mod sd_jwt; -mod signer; - -pub use builder::*; -pub use disclosure::*; -pub use hasher::*; -pub use kb_jwt::*; -pub use sd_jwt::*; -pub use signer::*; diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs deleted file mode 100644 index f71f1b927a..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::credential::sd_jwt_vc::Error; -use identity_iota::sd_jwt_rework::SdJwt; -use identity_iota::sd_jwt_rework::SdJwtPresentationBuilder; -use identity_iota::sd_jwt_rework::Sha256Hasher; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; - -use crate::error::Result; -use crate::error::WasmResult; - -use super::WasmDisclosure; -use super::WasmHasher; -use super::WasmKeyBindingJwt; -use super::WasmRequiredKeyBinding; - -#[wasm_bindgen(typescript_custom_section)] -const I_SD_JWT_CLAIMS: &str = r#" -interface SdJwtClaims { - _sd: string[]; - _sd_alg?: string; - cnf?: RequiredKeyBinding; - [properties: string]: unknown; -} -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "SdJwtClaims")] - pub type WasmSdJwtClaims; -} - -#[derive(Clone)] -#[wasm_bindgen(js_name = SdJwtV2)] -pub struct WasmSdJwt(pub(crate) SdJwt); - -#[wasm_bindgen(js_class = SdJwtV2)] -impl WasmSdJwt { - #[wasm_bindgen] - pub fn parse(s: &str) -> Result { - SdJwt::parse(s).map(Self).map_err(Error::from).wasm_result() - } - - #[wasm_bindgen] - pub fn header(&self) -> JsValue { - serde_wasm_bindgen::to_value(self.0.header()).unwrap() - } - - #[wasm_bindgen] - pub fn claims(&self) -> Result { - serde_wasm_bindgen::to_value(self.0.claims()) - .wasm_result() - .map(JsCast::unchecked_into) - } - - #[wasm_bindgen] - pub fn disclosures(&self) -> Vec { - self.0.disclosures().iter().map(ToString::to_string).collect() - } - - #[wasm_bindgen(js_name = "requiredKeyBind")] - pub fn required_key_bind(&self) -> Option { - self.0.required_key_bind().map(|required_kb| { - serde_wasm_bindgen::to_value(required_kb) - .expect("RequiredKeyBinding can be turned into a JS value") - .unchecked_into() - }) - } - - /// Returns the JSON object obtained by replacing all disclosures into their - /// corresponding JWT concealable claims. - #[wasm_bindgen(js_name = "intoDisclosedObject")] - pub fn into_disclosed_object(self) -> Result { - self - .0 - .into_disclosed_object(&Sha256Hasher) - .map_err(Error::from) - .map(|obj| serde_wasm_bindgen::to_value(&obj).expect("obj can be turned into a JS value")) - .wasm_result() - } - - /// Serializes the components into the final SD-JWT. - #[wasm_bindgen] - pub fn presentation(&self) -> String { - self.0.presentation() - } - - #[wasm_bindgen(js_name = "toJSON")] - pub fn to_json(&self) -> JsValue { - JsValue::from_str(&self.presentation()) - } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = "toString")] - pub fn to_string(&self) -> JsValue { - JsValue::from_str(&self.presentation()) - } -} - -#[wasm_bindgen(js_name = SdJwtPresentationBuilder)] -pub struct WasmSdJwtPresentationBuilder(pub(crate) SdJwtPresentationBuilder); - -#[wasm_bindgen(js_class = SdJwtPresentationBuilder)] -impl WasmSdJwtPresentationBuilder { - #[wasm_bindgen(constructor)] - pub fn new(sd_jwt: WasmSdJwt, hasher: &WasmHasher) -> Result { - SdJwtPresentationBuilder::new(sd_jwt.0, hasher).map(Self).wasm_result() - } - - /// Removes the disclosure for the property at `path`, concealing it. - /// - /// ## Notes - /// - When concealing a claim more than one disclosure may be removed: the disclosure for the claim itself and the - /// disclosures for any concealable sub-claim. - #[wasm_bindgen] - pub fn conceal(self, path: &str) -> Result { - self.0.conceal(path).map(Self).wasm_result() - } - - /// Adds a {@link KeyBindingJwt} to this {@link SdJwt}'s presentation. - #[wasm_bindgen(js_name = attachKeyBindingJwt)] - pub fn attach_key_binding_jwt(self, kb_jwt: WasmKeyBindingJwt) -> Self { - Self(self.0.attach_key_binding_jwt(kb_jwt.0)) - } - - /// Returns the resulting {@link SdJwt} together with all removed disclosures. - /// ## Errors - /// - Fails with `Error::MissingKeyBindingJwt` if this {@link SdJwt} requires a key binding but none was provided. - #[wasm_bindgen] - pub fn finish(self) -> Result { - self - .0 - .finish() - .map(|(sd_jwt, disclosures)| SdJwtPresentationResult { - sd_jwt: WasmSdJwt(sd_jwt), - disclosures: disclosures.into_iter().map(WasmDisclosure::from).collect(), - }) - .wasm_result() - } -} - -#[wasm_bindgen(inspectable, getter_with_clone)] -pub struct SdJwtPresentationResult { - #[wasm_bindgen(js_name = sdJwt)] - pub sd_jwt: WasmSdJwt, - pub disclosures: Vec, -} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs deleted file mode 100644 index b22bd7f2fa..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use async_trait::async_trait; -use identity_iota::sd_jwt_rework::JsonObject; -use identity_iota::sd_jwt_rework::JwsSigner; -use js_sys::Error as JsError; -use js_sys::Object; -use js_sys::Uint8Array; -use serde::Serialize as _; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsCast; - -use crate::error::Result; - -#[wasm_bindgen(typescript_custom_section)] -const I_JWS_SIGNER: &str = r#" -interface JwsSigner { - sign: (header: object, payload: object) => Promise; -} -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "JwsSigner")] - pub type WasmJwsSigner; - - #[wasm_bindgen(structural, method, catch)] - pub async fn sign(this: &WasmJwsSigner, header: Object, payload: Object) -> Result; -} - -#[async_trait(?Send)] -impl JwsSigner for WasmJwsSigner { - type Error = String; - async fn sign(&self, header: &JsonObject, payload: &JsonObject) -> std::result::Result, Self::Error> { - assert!(!payload.is_empty()); - let js_serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true); - let header = header - .serialize(&js_serializer) - .map_err(|e| format!("{e:?}"))? - .unchecked_into(); - let payload = payload - .serialize(&js_serializer) - .map_err(|e| format!("{e:?}"))? - .unchecked_into(); - - self - .sign(header, payload) - .await - .map_err(|e| e.unchecked_into::().to_string().into()) - .map(|arr| arr.to_vec()) - } -} diff --git a/bindings/wasm/src/sd_jwt_vc/status.rs b/bindings/wasm/src/sd_jwt_vc/status.rs deleted file mode 100644 index 6d4e4ba9a8..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/status.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use wasm_bindgen::prelude::wasm_bindgen; - -#[wasm_bindgen(typescript_custom_section)] -const I_SD_JWT_VC_STATUS: &str = r#" -interface SdJwtVcStatusListRef { - uri: string; - idx: number; -} - -type SdJwtVcStatus = { status_list: SdJwtVcStatusListRef } | unknown; -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_name = "SdJwtVcStatus")] - pub type WasmStatus; -} diff --git a/bindings/wasm/src/sd_jwt_vc/token.rs b/bindings/wasm/src/sd_jwt_vc/token.rs deleted file mode 100644 index c5219773f6..0000000000 --- a/bindings/wasm/src/sd_jwt_vc/token.rs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Deref; - -use identity_iota::core::Url; -use identity_iota::credential::sd_jwt_vc::metadata::ClaimMetadata; -use identity_iota::credential::sd_jwt_vc::vct_to_url as vct_to_url_impl; -use identity_iota::credential::sd_jwt_vc::SdJwtVc; -use wasm_bindgen::prelude::*; - -use crate::credential::WasmKeyBindingJWTValidationOptions; -use crate::error::Result; -use crate::error::WasmResult; -use crate::jose::WasmJwk; -use crate::sd_jwt_vc::metadata::WasmTypeMetadata; -use crate::verification::IJwsVerifier; -use crate::verification::WasmJwsVerifier; - -use super::metadata::WasmClaimMetadata; -use super::metadata::WasmIssuerMetadata; -use super::resolver::ResolverStringToUint8Array; -use super::sd_jwt_v2::WasmHasher; -use super::sd_jwt_v2::WasmSdJwt; -use super::WasmSdJwtVcClaims; -use super::WasmSdJwtVcPresentationBuilder; - -#[derive(Clone)] -#[wasm_bindgen(js_name = SdJwtVc)] -pub struct WasmSdJwtVc(pub(crate) SdJwtVc); - -impl_wasm_clone!(WasmSdJwtVc, SdJwtVc); - -#[wasm_bindgen(js_class = "SdJwtVc")] -impl WasmSdJwtVc { - /// Parses a `string` into an {@link SdJwtVc}. - #[wasm_bindgen] - pub fn parse(s: &str) -> Result { - SdJwtVc::parse(s).map(WasmSdJwtVc).wasm_result() - } - - #[wasm_bindgen] - pub fn claims(&self) -> Result { - serde_wasm_bindgen::to_value(self.0.claims()) - .map(JsCast::unchecked_into) - .wasm_result() - } - - #[wasm_bindgen(js_name = "asSdJwt")] - pub fn as_sd_jwt(&self) -> WasmSdJwt { - WasmSdJwt(self.0.deref().clone()) - } - - #[wasm_bindgen(js_name = "issuerJwk")] - pub async fn issuer_jwk(&self, resolver: &ResolverStringToUint8Array) -> Result { - self.0.issuer_jwk(resolver).await.map(WasmJwk).wasm_result() - } - - #[wasm_bindgen(js_name = "issuerMetadata")] - pub async fn issuer_metadata(&self, resolver: &ResolverStringToUint8Array) -> Result> { - self - .0 - .issuer_metadata(resolver) - .await - .map(|maybe_metadata| maybe_metadata.map(WasmIssuerMetadata)) - .wasm_result() - } - - #[wasm_bindgen(js_name = "typeMetadata")] - pub async fn type_metadata(&self, resolver: &ResolverStringToUint8Array) -> Result { - self - .0 - .type_metadata(resolver) - .await - .map(|(metadata, _)| WasmTypeMetadata(metadata)) - .wasm_result() - } - - /// Verifies this {@link SdJwtVc} JWT's signature. - #[wasm_bindgen(js_name = "verifySignature")] - pub fn verify_signature(&self, jwk: &WasmJwk, jws_verifier: Option) -> Result<()> { - let verifier = WasmJwsVerifier::new(jws_verifier); - self.0.verify_signature(&verifier, &jwk.0).wasm_result() - } - - /// Checks the disclosability of this {@link SdJwtVc}'s claims against a list of {@link ClaimMetadata}. - /// ## Notes - /// This check should be performed by the token's holder in order to assert the issuer's compliance with - /// the credential's type. - #[wasm_bindgen(js_name = "validateClaimDisclosability")] - pub fn validate_claims_disclosability(&self, claims_metadata: Vec) -> Result<()> { - let claims_metadata = claims_metadata.into_iter().map(ClaimMetadata::from).collect::>(); - self.0.validate_claims_disclosability(&claims_metadata).wasm_result() - } - - /// Check whether this {@link SdJwtVc} is valid. - /// - /// This method checks: - /// - JWS signature - /// - credential's type - /// - claims' disclosability - #[wasm_bindgen] - pub async fn validate( - &self, - resolver: &ResolverStringToUint8Array, - hasher: &WasmHasher, - jws_verifier: Option, - ) -> Result<()> { - let jws_verifier = WasmJwsVerifier::new(jws_verifier); - self.0.validate(resolver, &jws_verifier, hasher).await.wasm_result() - } - - /// Verify the signature of this {@link SdJwtVc}'s {@link KeyBindingJwt}. - #[wasm_bindgen(js_name = "verifyKeyBinding")] - pub fn verify_key_binding(&self, jwk: &WasmJwk, jws_verifier: Option) -> Result<()> { - let verifier = WasmJwsVerifier::new(jws_verifier); - self.0.verify_key_binding(&verifier, &jwk.0).wasm_result() - } - - #[wasm_bindgen(js_name = "validateKeyBinding")] - pub fn validate_key_binding( - &self, - jwk: &WasmJwk, - hasher: &WasmHasher, - options: &WasmKeyBindingJWTValidationOptions, - jws_verifier: Option, - ) -> Result<()> { - let jws_verifier = WasmJwsVerifier::new(jws_verifier); - self - .0 - .validate_key_binding(&jws_verifier, &jwk.0, hasher, &options.0) - .wasm_result() - } - - #[wasm_bindgen(js_name = "intoSdJwt")] - pub fn into_sd_jwt(self) -> WasmSdJwt { - WasmSdJwt(self.0.into()) - } - - #[wasm_bindgen(js_name = "intoDisclosedObject")] - pub fn into_disclosed_object(&self, hasher: &WasmHasher) -> Result { - self - .0 - .clone() - .into_disclosed_object(hasher) - .map(|obj| serde_wasm_bindgen::to_value(&obj).expect("JSON object is a valid JS object")) - .map(JsCast::unchecked_into) - .wasm_result() - } - - #[wasm_bindgen(js_name = "intoPresentation")] - pub fn into_presentation(self, hasher: &WasmHasher) -> Result { - WasmSdJwtVcPresentationBuilder::new(self, hasher) - } - - #[wasm_bindgen(js_name = "toJSON")] - pub fn to_json(&self) -> JsValue { - JsValue::from_str(&self.0.to_string()) - } - - #[allow(clippy::inherent_to_string)] - #[wasm_bindgen(js_name = "toString")] - pub fn to_string(&self) -> JsValue { - JsValue::from_str(&self.0.to_string()) - } -} - -#[wasm_bindgen(js_name = "vctToUrl")] -pub fn vct_to_url(resource: &str) -> Option { - let url = resource.parse::().ok()?; - vct_to_url_impl(&url).map(|url| url.to_string()) -} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4a173d1315..2cbeb96ac5 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "examples" -version = "1.5.0" +version = "1.3.1" authors = ["IOTA Stiftung"] edition = "2021" publish = false diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index c8efda7236..239383c2f6 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_core" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ keywords = ["iota", "tangle", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "The core traits and types for the identity-rs library." [dependencies] diff --git a/identity_core/src/common/mod.rs b/identity_core/src/common/mod.rs index 04568e05b5..8d6be52251 100644 --- a/identity_core/src/common/mod.rs +++ b/identity_core/src/common/mod.rs @@ -14,7 +14,6 @@ pub use self::single_struct_error::*; pub use self::timestamp::Duration; pub use self::timestamp::Timestamp; pub use self::url::Url; -pub use string_or_url::StringOrUrl; mod context; mod key_comparable; @@ -23,6 +22,5 @@ mod one_or_many; mod one_or_set; mod ordered_set; mod single_struct_error; -mod string_or_url; mod timestamp; mod url; diff --git a/identity_core/src/common/string_or_url.rs b/identity_core/src/common/string_or_url.rs deleted file mode 100644 index 11e4e5dff2..0000000000 --- a/identity_core/src/common/string_or_url.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::convert::Infallible; -use std::fmt::Display; -use std::str::FromStr; - -use serde::Deserialize; -use serde::Serialize; - -use super::Url; - -/// A type that represents either an arbitrary string or a URL. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(untagged)] -pub enum StringOrUrl { - /// A well-formed URL. - Url(Url), - /// An arbitrary UTF-8 string. - String(String), -} - -impl StringOrUrl { - /// Parses a [`StringOrUrl`] from a string. - pub fn parse(s: &str) -> Result { - s.parse() - } - /// Returns a [`Url`] reference if `self` is [`StringOrUrl::Url`]. - pub fn as_url(&self) -> Option<&Url> { - match self { - Self::Url(url) => Some(url), - _ => None, - } - } - - /// Returns a [`str`] reference if `self` is [`StringOrUrl::String`]. - pub fn as_string(&self) -> Option<&str> { - match self { - Self::String(s) => Some(s), - _ => None, - } - } - - /// Returns whether `self` is a [`StringOrUrl::Url`]. - pub fn is_url(&self) -> bool { - matches!(self, Self::Url(_)) - } - - /// Returns whether `self` is a [`StringOrUrl::String`]. - pub fn is_string(&self) -> bool { - matches!(self, Self::String(_)) - } -} - -impl Default for StringOrUrl { - fn default() -> Self { - StringOrUrl::String(String::default()) - } -} - -impl Display for StringOrUrl { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Url(url) => write!(f, "{url}"), - Self::String(s) => write!(f, "{s}"), - } - } -} - -impl FromStr for StringOrUrl { - // Cannot fail. - type Err = Infallible; - fn from_str(s: &str) -> Result { - Ok( - s.parse::() - .map(Self::Url) - .unwrap_or_else(|_| Self::String(s.to_string())), - ) - } -} - -impl AsRef for StringOrUrl { - fn as_ref(&self) -> &str { - match self { - Self::String(s) => s, - Self::Url(url) => url.as_str(), - } - } -} - -impl From for StringOrUrl { - fn from(value: Url) -> Self { - Self::Url(value) - } -} - -impl From for StringOrUrl { - fn from(value: String) -> Self { - Self::String(value) - } -} - -impl From for String { - fn from(value: StringOrUrl) -> Self { - match value { - StringOrUrl::String(s) => s, - StringOrUrl::Url(url) => url.into_string(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug, Serialize, Deserialize)] - struct TestData { - string_or_url: StringOrUrl, - } - - impl Default for TestData { - fn default() -> Self { - Self { - string_or_url: StringOrUrl::Url(TEST_URL.parse().unwrap()), - } - } - } - - const TEST_URL: &str = "file:///tmp/file.txt"; - - #[test] - fn deserialization_works() { - let test_data: TestData = serde_json::from_value(serde_json::json!({ "string_or_url": TEST_URL })).unwrap(); - let target_url: Url = TEST_URL.parse().unwrap(); - assert_eq!(test_data.string_or_url.as_url(), Some(&target_url)); - } - - #[test] - fn serialization_works() { - assert_eq!( - serde_json::to_value(TestData::default()).unwrap(), - serde_json::json!({ "string_or_url": TEST_URL }) - ) - } - - #[test] - fn parsing_works() { - assert!(TEST_URL.parse::().unwrap().is_url()); - assert!("I'm a random string :)".parse::().unwrap().is_string()); - } -} diff --git a/identity_core/src/common/url.rs b/identity_core/src/common/url.rs index 57937c80e7..6736a4cac9 100644 --- a/identity_core/src/common/url.rs +++ b/identity_core/src/common/url.rs @@ -17,7 +17,7 @@ use crate::error::Error; use crate::error::Result; /// A parsed URL. -#[derive(Clone, Hash, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] pub struct Url(::url::Url); @@ -96,9 +96,3 @@ impl KeyComparable for Url { self } } - -impl AsRef for Url { - fn as_ref(&self) -> &str { - self.as_str() - } -} diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index f941f94616..9d63144d71 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_credential" -version = "1.5.0" +version = "1.3.1" authors = ["IOTA Stiftung"] edition = "2021" homepage.workspace = true @@ -8,27 +8,25 @@ keywords = ["iota", "tangle", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "An implementation of the Verifiable Credentials standard." [dependencies] -anyhow = { version = "1" } async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true } -futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +futures = { version = "0.3", default-features = false, optional = true } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true } json-proof-token = { workspace = true, optional = true } -jsonschema = { version = "0.19", optional = true, default-features = false } once_cell = { version = "1.18", default-features = false, features = ["std"] } reqwest = { version = "0.11", default-features = false, features = ["default-tls", "json", "stream"], optional = true } roaring = { version = "0.10.2", default-features = false, features = ["serde"], optional = true } sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"], optional = true } -sd-jwt-payload-rework = { package = "sd-jwt-payload", version = "0.3", features = ["sha"], optional = true } serde.workspace = true serde-aux = { version = "4.3.1", default-features = false } serde_json.workspace = true @@ -42,7 +40,6 @@ zkryptium = { workspace = true, optional = true } anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "random"] } -josekit = "0.8" proptest = { version = "1.4.0", default-features = false, features = ["std"] } tokio = { version = "1.35.0", default-features = false, features = ["rt-multi-thread", "macros"] } @@ -53,15 +50,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = [ - "revocation-bitmap", - "validator", - "credential", - "presentation", - "domain-linkage-fetch", - "sd-jwt", - "sd-jwt-vc", -] +default = ["revocation-bitmap", "validator", "credential", "presentation", "domain-linkage-fetch", "sd-jwt"] credential = [] presentation = ["credential"] revocation-bitmap = ["dep:flate2", "dep:roaring"] @@ -70,15 +59,7 @@ validator = ["dep:itertools", "dep:serde_repr", "credential", "presentation"] domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] -sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema", "dep:futures"] -jpt-bbs-plus = [ - "credential", - "validator", - "dep:zkryptium", - "dep:bls12_381_plus", - "dep:json-proof-token", - "dep:futures", -] +jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] hybrid = ["credential", "validator"] [lints] diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index feb3de531d..3f2a33f0a7 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -67,7 +67,7 @@ impl<'credential, T> CredentialJwtClaims<'credential, T> where T: ToOwned + Serialize + DeserializeOwned, { - pub(crate) fn new(credential: &'credential Credential, custom: Option) -> Result { + pub(super) fn new(credential: &'credential Credential, custom: Option) -> Result { let Credential { context, id, @@ -118,7 +118,7 @@ where } #[cfg(feature = "validator")] -impl CredentialJwtClaims<'_, T> +impl<'credential, T> CredentialJwtClaims<'credential, T> where T: ToOwned + Serialize + DeserializeOwned, { diff --git a/identity_credential/src/domain_linkage/domain_linkage_validator.rs b/identity_credential/src/domain_linkage/domain_linkage_validator.rs index 746a9b2f5a..be67c96832 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_validator.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_validator.rs @@ -21,6 +21,7 @@ use super::DomainLinkageValidationResult; use crate::utils::url_only_includes_origin; /// A validator for a Domain Linkage Configuration and Credentials. + pub struct JwtDomainLinkageValidator { validator: JwtCredentialValidator, } diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index 18b31d69c3..1c814c3899 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -79,9 +79,4 @@ pub enum Error { /// Cause by an invalid attribute path #[error("Attribute Not found")] SelectiveDisclosureError, - - /// Failure of an SD-JWT VC operation. - #[cfg(feature = "sd-jwt-vc")] - #[error(transparent)] - SdJwtVc(#[from] crate::sd_jwt_vc::Error), } diff --git a/identity_credential/src/lib.rs b/identity_credential/src/lib.rs index 236329ab4c..3111b72e0a 100644 --- a/identity_credential/src/lib.rs +++ b/identity_credential/src/lib.rs @@ -27,15 +27,8 @@ mod utils; #[cfg(feature = "validator")] pub mod validator; -/// Implementation of the SD-JWT VC token specification. -#[cfg(feature = "sd-jwt-vc")] -pub mod sd_jwt_vc; - pub use error::Error; pub use error::Result; #[cfg(feature = "sd-jwt")] pub use sd_jwt_payload; - -#[cfg(feature = "sd-jwt-vc")] -pub use sd_jwt_payload_rework as sd_jwt_v2; diff --git a/identity_credential/src/presentation/jwt_serialization.rs b/identity_credential/src/presentation/jwt_serialization.rs index 50aab3d428..d8bb18c238 100644 --- a/identity_credential/src/presentation/jwt_serialization.rs +++ b/identity_credential/src/presentation/jwt_serialization.rs @@ -136,7 +136,7 @@ where } #[cfg(feature = "validator")] -impl PresentationJwtClaims<'_, CRED, T> +impl<'presentation, CRED, T> PresentationJwtClaims<'presentation, CRED, T> where CRED: ToOwned + Serialize + DeserializeOwned + Clone, T: ToOwned + Serialize + DeserializeOwned, diff --git a/identity_credential/src/revocation/status_list_2021/entry.rs b/identity_credential/src/revocation/status_list_2021/entry.rs index 1108b5e7c1..7eecf2f28e 100644 --- a/identity_credential/src/revocation/status_list_2021/entry.rs +++ b/identity_credential/src/revocation/status_list_2021/entry.rs @@ -18,7 +18,7 @@ where D: serde::Deserializer<'de>, { struct ExactStrVisitor(&'static str); - impl Visitor<'_> for ExactStrVisitor { + impl<'a> Visitor<'a> for ExactStrVisitor { type Value = &'static str; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "the exact string \"{}\"", self.0) @@ -37,14 +37,6 @@ where .map(ToOwned::to_owned) } -/// Serialize usize as string. -fn serialize_number_as_string(value: &usize, serializer: S) -> Result -where - S: serde::Serializer, -{ - serializer.serialize_str(&value.to_string()) -} - /// [StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] #[serde(rename_all = "camelCase")] @@ -53,10 +45,7 @@ pub struct StatusList2021Entry { #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")] type_: String, status_purpose: StatusPurpose, - #[serde( - deserialize_with = "serde_aux::prelude::deserialize_number_from_string", - serialize_with = "serialize_number_as_string" - )] + #[serde(deserialize_with = "serde_aux::prelude::deserialize_number_from_string")] status_list_index: usize, status_list_credential: Url, } @@ -153,13 +142,4 @@ mod tests { }); serde_json::from_value::(status).expect("wrong type"); } - - #[test] - fn test_status_list_index_serialization() { - let base_url = Url::parse("https://example.com/credentials/status/3").unwrap(); - - let entry1 = StatusList2021Entry::new(base_url.clone(), StatusPurpose::Revocation, 94567, None); - let json1 = serde_json::to_value(&entry1).unwrap(); - assert_eq!(json1["statusListIndex"], "94567"); - } } diff --git a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs index 6ae6ea74f8..0a70589112 100644 --- a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs +++ b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs @@ -18,7 +18,7 @@ where D: serde::Deserializer<'de>, { struct ExactStrVisitor(&'static str); - impl Visitor<'_> for ExactStrVisitor { + impl<'a> Visitor<'a> for ExactStrVisitor { type Value = &'static str; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "the exact string \"{}\"", self.0) diff --git a/identity_credential/src/sd_jwt_vc/builder.rs b/identity_credential/src/sd_jwt_vc/builder.rs deleted file mode 100644 index 234f815bbd..0000000000 --- a/identity_credential/src/sd_jwt_vc/builder.rs +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#![allow(clippy::vec_init_then_push)] -use std::sync::LazyLock; - -use identity_core::common::StringOrUrl; -use identity_core::common::Timestamp; -use identity_core::common::Url; -use identity_core::convert::ToJson; -use sd_jwt_payload_rework::Hasher; -use sd_jwt_payload_rework::JsonObject; -use sd_jwt_payload_rework::JwsSigner; -use sd_jwt_payload_rework::RequiredKeyBinding; -use sd_jwt_payload_rework::SdJwtBuilder; -use sd_jwt_payload_rework::Sha256Hasher; -use serde::Serialize; -use serde_json::json; -use serde_json::Value; - -use crate::credential::Credential; -use crate::credential::CredentialJwtClaims; - -use super::Error; -use super::Result; -use super::SdJwtVc; -use super::Status; -use super::SD_JWT_VC_TYP; - -static DEFAULT_HEADER: LazyLock = LazyLock::new(|| { - let mut object = JsonObject::default(); - object.insert("typ".to_string(), SD_JWT_VC_TYP.into()); - object -}); - -macro_rules! claim_to_key_value_pair { - ( $( $claim:ident ),+ ) => { - { - let mut claim_list = Vec::<(&'static str, serde_json::Value)>::new(); - $( - claim_list.push((stringify!($claim), serde_json::to_value($claim).unwrap())); - )* - claim_list - } - }; -} - -/// A structure to ease the creation of an [`SdJwtVc`]. -#[derive(Debug)] -pub struct SdJwtVcBuilder { - inner_builder: SdJwtBuilder, - header: JsonObject, - iss: Option, - nbf: Option, - exp: Option, - iat: Option, - vct: Option, - sub: Option, - status: Option, -} - -impl Default for SdJwtVcBuilder { - fn default() -> Self { - Self { - inner_builder: SdJwtBuilder::::new(json!({})).unwrap(), - header: DEFAULT_HEADER.clone(), - iss: None, - nbf: None, - exp: None, - iat: None, - vct: None, - sub: None, - status: None, - } - } -} - -impl SdJwtVcBuilder { - /// Creates a new [`SdJwtVcBuilder`] using `object` JSON representation and default - /// `sha-256` hasher. - pub fn new(object: T) -> Result { - let inner_builder = SdJwtBuilder::::new(object)?; - Ok(Self { - header: DEFAULT_HEADER.clone(), - inner_builder, - ..Default::default() - }) - } -} - -impl SdJwtVcBuilder { - /// Creates a new [`SdJwtVcBuilder`] using `object` JSON representation and a given - /// hasher `hasher`. - pub fn new_with_hasher(object: T, hasher: H) -> Result { - let inner_builder = SdJwtBuilder::new_with_hasher(object, hasher)?; - Ok(Self { - inner_builder, - header: DEFAULT_HEADER.clone(), - iss: None, - nbf: None, - exp: None, - iat: None, - vct: None, - sub: None, - status: None, - }) - } - - /// Creates a new [`SdJwtVcBuilder`] starting from a [`Credential`] that is converted to a JWT claim set. - pub fn new_from_credential(credential: Credential, hasher: H) -> std::result::Result { - let mut vc_jwt_claims = CredentialJwtClaims::new(&credential, None)? - .to_json_value() - .map_err(|e| crate::Error::JwtClaimsSetSerializationError(Box::new(e)))?; - // When converting a VC to its JWT claims representation, some VC specific claims are putted into a `vc` object - // property. Flatten out `vc`, keeping the other JWT claims intact. - { - let claims = vc_jwt_claims.as_object_mut().expect("serialized VC is a JSON object"); - let Value::Object(vc_properties) = claims.remove("vc").expect("serialized VC has `vc` property") else { - unreachable!("`vc` property's value is a JSON object"); - }; - for (key, value) in vc_properties { - claims.insert(key, value); - } - } - Ok(Self::new_with_hasher(vc_jwt_claims, hasher)?) - } - - /// Substitutes a value with the digest of its disclosure. - /// - /// ## Notes - /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - /// - /// ## Example - /// ```rust - /// use serde_json::json; - /// use identity_credential::sd_jwt_vc::SdJwtVcBuilder; - /// - /// let obj = json!({ - /// "id": "did:value", - /// "claim1": { - /// "abc": true - /// }, - /// "claim2": ["val_1", "val_2"] - /// }); - /// let builder = SdJwtVcBuilder::new(obj) - /// .unwrap() - /// .make_concealable("/id").unwrap() //conceals "id": "did:value" - /// .make_concealable("/claim1/abc").unwrap() //"abc": true - /// .make_concealable("/claim2/0").unwrap(); //conceals "val_1" - /// ``` - pub fn make_concealable(mut self, path: &str) -> Result { - self.inner_builder = self.inner_builder.make_concealable(path)?; - Ok(self) - } - - /// Sets the JWT header. - /// ## Notes - /// - if [`SdJwtVcBuilder::header`] is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": - /// "" } ``` - /// - `alg` is always replaced with the value passed to [`SdJwtVcBuilder::finish`]. - pub fn header(mut self, header: JsonObject) -> Self { - self.header = header; - self - } - - /// Adds a decoy digest to the specified path. - /// - /// `path` indicates the pointer to the value that will be concealed using the syntax of - /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - /// - /// Use `path` = "" to add decoys to the top level. - pub fn add_decoys(mut self, path: &str, number_of_decoys: usize) -> Result { - self.inner_builder = self.inner_builder.add_decoys(path, number_of_decoys)?; - - Ok(self) - } - - /// Require a proof of possession of a given key from the holder. - /// - /// This operation adds a JWT confirmation (`cnf`) claim as specified in - /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). - pub fn require_key_binding(mut self, key_bind: RequiredKeyBinding) -> Self { - self.inner_builder = self.inner_builder.require_key_binding(key_bind); - self - } - - /// Inserts an `iss` claim. See [`super::SdJwtVcClaims::iss`]. - pub fn iss(mut self, issuer: Url) -> Self { - self.iss = Some(issuer); - self - } - - /// Inserts a `nbf` claim. See [`super::SdJwtVcClaims::nbf`]. - pub fn nbf(mut self, nbf: Timestamp) -> Self { - self.nbf = Some(nbf.to_unix()); - self - } - - /// Inserts a `exp` claim. See [`super::SdJwtVcClaims::exp`]. - pub fn exp(mut self, exp: Timestamp) -> Self { - self.exp = Some(exp.to_unix()); - self - } - - /// Inserts a `iat` claim. See [`super::SdJwtVcClaims::iat`]. - pub fn iat(mut self, iat: Timestamp) -> Self { - self.iat = Some(iat.to_unix()); - self - } - - /// Inserts a `vct` claim. See [`super::SdJwtVcClaims::vct`]. - pub fn vct(mut self, vct: impl Into) -> Self { - self.vct = Some(vct.into()); - self - } - - /// Inserts a `sub` claim. See [`super::SdJwtVcClaims::sub`]. - #[allow(clippy::should_implement_trait)] - pub fn sub(mut self, sub: impl Into) -> Self { - self.sub = Some(sub.into()); - self - } - - /// Inserts a `status` claim. See [`super::SdJwtVcClaims::status`]. - pub fn status(mut self, status: Status) -> Self { - self.status = Some(status); - self - } - - /// Creates an [`SdJwtVc`] with the provided data. - pub async fn finish(self, signer: &S, alg: &str) -> Result - where - S: JwsSigner, - { - let Self { - inner_builder, - mut header, - iss, - nbf, - exp, - iat, - vct, - sub, - status, - } = self; - // Check header. - header - .entry("typ") - .or_insert_with(|| SD_JWT_VC_TYP.to_owned().into()) - .as_str() - .filter(|typ| typ.contains(SD_JWT_VC_TYP)) - .ok_or_else(|| Error::InvalidJoseType(String::default()))?; - - let builder = inner_builder.header(header); - - // Insert SD-JWT VC claims into object. - let builder = claim_to_key_value_pair![iss, nbf, exp, iat, vct, sub, status] - .into_iter() - .filter(|(_, value)| !value.is_null()) - .fold(builder, |builder, (key, value)| { - builder.insert_claim(key, value).expect("value is a JSON Value") - }); - - let sd_jwt = builder.finish(signer, alg).await?; - SdJwtVc::try_from(sd_jwt) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use crate::credential::CredentialBuilder; - use crate::credential::Subject; - use crate::sd_jwt_vc::tests::TestSigner; - - #[tokio::test] - async fn building_valid_vc_works() -> anyhow::Result<()> { - let credential = json!({ - "name": "John Doe", - "birthdate": "1970-01-01" - }); - - SdJwtVcBuilder::new(credential)? - .vct("https://bmi.bund.example/credential/pid/1.0".parse::()?) - .iat(Timestamp::now_utc()) - .iss("https://example.com/".parse()?) - .make_concealable("/birthdate")? - .finish(&TestSigner, "HS256") - .await?; - - Ok(()) - } - - #[tokio::test] - async fn building_vc_with_missing_mandatory_claims_fails() -> anyhow::Result<()> { - let credential = json!({ - "name": "John Doe", - "birthdate": "1970-01-01" - }); - - let err = SdJwtVcBuilder::new(credential)? - .vct("https://bmi.bund.example/credential/pid/1.0".parse::()?) - .iat(Timestamp::now_utc()) - // issuer is missing. - .make_concealable("/birthdate")? - .finish(&TestSigner, "HS256") - .await - .unwrap_err(); - assert!(matches!(err, Error::MissingClaim("iss"))); - - Ok(()) - } - - #[tokio::test] - async fn building_vc_with_invalid_mandatory_claims_fails() -> anyhow::Result<()> { - let credential = json!({ - "name": "John Doe", - "birthdate": "1970-01-01", - "vct": { "id": 1234567890 } - }); - - let err = SdJwtVcBuilder::new(credential)? - .iat(Timestamp::now_utc()) - .iss("https://example.com".parse()?) - .make_concealable("/birthdate")? - .finish(&TestSigner, "HS256") - .await - .unwrap_err(); - - assert!(matches!(err, Error::InvalidClaimValue { name: "vct", .. })); - - Ok(()) - } - - #[tokio::test] - async fn building_vc_with_disclosed_mandatory_claim_fails() -> anyhow::Result<()> { - let credential = json!({ - "name": "John Doe", - "birthdate": "1970-01-01", - "vct": { "id": 1234567890 } - }); - - let err = SdJwtVcBuilder::new(credential)? - .iat(Timestamp::now_utc()) - .iss("https://example.com".parse()?) - .make_concealable("/birthdate")? - .make_concealable("/vct")? - .finish(&TestSigner, "HS256") - .await - .unwrap_err(); - - assert!(matches!(err, Error::DisclosedClaim("vct"))); - - Ok(()) - } - - #[tokio::test] - async fn building_sd_jwt_vc_from_credential_works() -> anyhow::Result<()> { - let credential = CredentialBuilder::default() - .id(Url::parse("https://example.com/credentials/42")?) - .issuance_date(Timestamp::now_utc()) - .issuer(Url::parse("https://example.com/issuers/42")?) - .subject(Subject::with_id(Url::parse("https://example.com/subjects/42")?)) - .build()?; - - let sd_jwt_vc = SdJwtVcBuilder::new_from_credential(credential.clone(), Sha256Hasher)? - .vct(Url::parse("https://example.com/types/0")?) - .finish(&TestSigner, "HS256") - .await?; - - assert_eq!(sd_jwt_vc.claims().nbf.as_ref().unwrap(), &credential.issuance_date); - assert_eq!(&sd_jwt_vc.claims().iss, credential.issuer.url()); - assert_eq!( - sd_jwt_vc.claims().sub.as_ref().unwrap().as_url(), - credential.credential_subject.first().unwrap().id.as_ref() - ); - assert_eq!( - sd_jwt_vc.claims().get("jti"), - Some(&json!(credential.id.as_ref().unwrap())) - ); - assert_eq!(sd_jwt_vc.claims().get("type"), Some(&json!("VerifiableCredential"))); - - Ok(()) - } -} diff --git a/identity_credential/src/sd_jwt_vc/claims.rs b/identity_credential/src/sd_jwt_vc/claims.rs deleted file mode 100644 index 9fcec11ce3..0000000000 --- a/identity_credential/src/sd_jwt_vc/claims.rs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::Deref; -use std::ops::DerefMut; - -use identity_core::common::StringOrUrl; -use identity_core::common::Timestamp; -use identity_core::common::Url; -use sd_jwt_payload_rework::Disclosure; -use sd_jwt_payload_rework::SdJwtClaims; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; - -use super::Error; -use super::Result; -use super::Status; - -/// JOSE payload claims for SD-JWT VC. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct SdJwtVcClaims { - /// Issuer. - pub iss: Url, - /// Not before. - /// See [RFC7519 section 4.1.5](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5) for more information. - pub nbf: Option, - /// Expiration. - /// See [RFC7519 section 4.1.4](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4) for more information. - pub exp: Option, - /// Verifiable credential type. - /// See [SD-JWT VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-04.html#type-claim) - /// for more information. - pub vct: StringOrUrl, - /// Token's status. - /// See [OAuth status list specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-02) - /// for more information. - pub status: Option, - /// Issued at. - /// See [RFC7519 section 4.1.6](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6) for more information. - pub iat: Option, - /// Subject. - /// See [RFC7519 section 4.1.2](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.2) for more information. - pub sub: Option, - #[serde(flatten)] - pub(crate) sd_jwt_claims: SdJwtClaims, -} - -impl Deref for SdJwtVcClaims { - type Target = SdJwtClaims; - fn deref(&self) -> &Self::Target { - &self.sd_jwt_claims - } -} - -impl DerefMut for SdJwtVcClaims { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.sd_jwt_claims - } -} - -impl SdJwtVcClaims { - pub(crate) fn try_from_sd_jwt_claims(mut claims: SdJwtClaims, disclosures: &[Disclosure]) -> Result { - let check_disclosed = |claim_name: &'static str| { - disclosures - .iter() - .any(|disclosure| disclosure.claim_name.as_deref() == Some(claim_name)) - .then_some(Error::DisclosedClaim(claim_name)) - }; - let iss = claims - .remove("iss") - .ok_or(Error::MissingClaim("iss")) - .map_err(|e| check_disclosed("iss").unwrap_or(e)) - .and_then(|value| { - value - .as_str() - .and_then(|s| Url::parse(s).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "iss", - expected: "URL", - found: value, - }) - })?; - let nbf = { - if let Some(value) = claims.remove("nbf") { - value - .as_number() - .and_then(|t| t.as_i64()) - .and_then(|t| Timestamp::from_unix(t).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "nbf", - expected: "unix timestamp", - found: value, - }) - .map(Some)? - } else { - if let Some(err) = check_disclosed("nbf") { - return Err(err); - } - None - } - }; - let exp = { - if let Some(value) = claims.remove("exp") { - value - .as_number() - .and_then(|t| t.as_i64()) - .and_then(|t| Timestamp::from_unix(t).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "exp", - expected: "unix timestamp", - found: value, - }) - .map(Some)? - } else { - if let Some(err) = check_disclosed("exp") { - return Err(err); - } - None - } - }; - let vct = claims - .remove("vct") - .ok_or(Error::MissingClaim("vct")) - .map_err(|e| check_disclosed("vct").unwrap_or(e)) - .and_then(|value| { - value - .as_str() - .and_then(|s| StringOrUrl::parse(s).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "vct", - expected: "String or URL", - found: value, - }) - })?; - let status = { - if let Some(value) = claims.remove("status") { - serde_json::from_value::(value.clone()) - .map_err(|_| Error::InvalidClaimValue { - name: "status", - expected: "credential's status object", - found: value, - }) - .map(Some)? - } else { - if let Some(err) = check_disclosed("status") { - return Err(err); - } - None - } - }; - let sub = claims - .remove("sub") - .map(|value| { - value - .as_str() - .and_then(|s| StringOrUrl::parse(s).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "sub", - expected: "String or URL", - found: value, - }) - }) - .transpose()?; - let iat = claims - .remove("iat") - .map(|value| { - value - .as_number() - .and_then(|t| t.as_i64()) - .and_then(|t| Timestamp::from_unix(t).ok()) - .ok_or_else(|| Error::InvalidClaimValue { - name: "iat", - expected: "unix timestamp", - found: value, - }) - }) - .transpose()?; - - Ok(Self { - iss, - nbf, - exp, - vct, - status, - iat, - sub, - sd_jwt_claims: claims, - }) - } -} - -impl From for SdJwtClaims { - fn from(claims: SdJwtVcClaims) -> Self { - let SdJwtVcClaims { - iss, - nbf, - exp, - vct, - status, - iat, - sub, - mut sd_jwt_claims, - } = claims; - - sd_jwt_claims.insert("iss".to_string(), Value::String(iss.into_string())); - nbf.and_then(|t| sd_jwt_claims.insert("nbf".to_string(), Value::Number(t.to_unix().into()))); - exp.and_then(|t| sd_jwt_claims.insert("exp".to_string(), Value::Number(t.to_unix().into()))); - sd_jwt_claims.insert("vct".to_string(), Value::String(vct.into())); - status.and_then(|status| sd_jwt_claims.insert("status".to_string(), serde_json::to_value(status).unwrap())); - iat.and_then(|t| sd_jwt_claims.insert("iat".to_string(), Value::Number(t.to_unix().into()))); - sub.and_then(|sub| sd_jwt_claims.insert("sub".to_string(), Value::String(sub.into()))); - - sd_jwt_claims - } -} diff --git a/identity_credential/src/sd_jwt_vc/error.rs b/identity_credential/src/sd_jwt_vc/error.rs deleted file mode 100644 index 13af8911a3..0000000000 --- a/identity_credential/src/sd_jwt_vc/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use serde_json::Value; -use thiserror::Error; - -/// Error type that represents failures that might arise when dealing -/// with `SdJwtVc`s. -#[derive(Error, Debug)] -pub enum Error { - /// A JWT claim required for an operation is missing. - #[error("missing required claim \"{0}\"")] - MissingClaim(&'static str), - /// A JWT claim that must not be disclosed was found among the disclosed values. - #[error("claim \"{0}\" cannot be disclosed")] - DisclosedClaim(&'static str), - /// Invalid value for a given JWT claim. - #[error("invalid value for claim \"{name}\"; expected value of type {expected}, but {found} was found")] - InvalidClaimValue { - /// Name of the invalid claim. - name: &'static str, - /// Type expected for the claim's value. - expected: &'static str, - /// The claim's value. - found: Value, - }, - /// A low level SD-JWT error. - #[error(transparent)] - SdJwt(#[from] sd_jwt_payload_rework::Error), - /// Value of header parameter `typ` is not valid. - #[error("invalid \"typ\" value; expected \"vc+sd-jwt\" (or a superset) but found \"{0}\"")] - InvalidJoseType(String), - /// Resolution error. - #[error("failed to resolve \"{input}\"")] - Resolution { - /// The resource's identifier. - input: String, - /// Low level error. - #[source] - source: super::resolver::Error, - }, - /// Invalid issuer Metadata object. - #[error("invalid Issuer Metadata: {0}")] - InvalidIssuerMetadata(#[source] anyhow::Error), - /// Invalid credential type metadata object. - #[error("invalid Type Metadata: {0}")] - InvalidTypeMetadata(#[source] anyhow::Error), - /// Credential validation failed. - #[error("credential validation failed: {0}")] - Validation(#[source] anyhow::Error), - /// SD-JWT VC signature verification failed. - #[error("verification failed: {0}")] - Verification(#[source] anyhow::Error), -} - -/// Either a value of type `T` or an [`Error`]. -pub type Result = std::result::Result; diff --git a/identity_credential/src/sd_jwt_vc/metadata/claim.rs b/identity_credential/src/sd_jwt_vc/metadata/claim.rs deleted file mode 100644 index bee5f86e65..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/claim.rs +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Display; -use std::ops::Deref; - -use anyhow::anyhow; -use anyhow::Context; -use itertools::Itertools; -use serde::Deserialize; -use serde::Serialize; -use serde::Serializer; -use serde_json::Value; - -use crate::sd_jwt_vc::Error; - -/// Information about a particular claim for displaying and validation purposes. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ClaimMetadata { - /// [`ClaimPath`] of the claim or claims that are being addressed. - pub path: ClaimPath, - /// Object containing display information for the claim. - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub display: Vec, - /// A string indicating whether the claim is selectively disclosable. - pub sd: Option, - /// A string defining the ID of the claim for reference in the SVG template. - pub svg_id: Option, -} - -impl ClaimMetadata { - /// Checks whether `value` is compliant with the disclosability policy imposed by this [`ClaimMetadata`]. - pub fn check_value_disclosability(&self, value: &Value) -> Result<(), Error> { - if self.sd.unwrap_or_default() == ClaimDisclosability::Allowed { - return Ok(()); - } - - let interested_claims = self.path.reverse_index(value); - if self.sd.unwrap_or_default() == ClaimDisclosability::Always && interested_claims.is_ok() { - return Err(Error::Validation(anyhow!( - "claim or claims with path {} must always be disclosable", - &self.path - ))); - } - - if self.sd.unwrap_or_default() == ClaimDisclosability::Never && interested_claims.is_err() { - return Err(Error::Validation(anyhow!( - "claim or claims with path {} must never be disclosable", - &self.path - ))); - } - - Ok(()) - } -} - -/// A non-empty list of string, `null` values, or non-negative integers. -/// It is used to select a particular claim in the credential or a -/// set of claims. See [Claim Path](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-05.html#name-claim-path) for more information. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(try_from = "Vec")] -pub struct ClaimPath(Vec); - -impl ClaimPath { - fn reverse_index<'v>(&self, value: &'v Value) -> anyhow::Result> { - let mut segments = self.iter(); - let first_segment = segments.next().context("empty claim path")?; - segments.try_fold(index_value(value, first_segment)?, |values, segment| { - values.get(segment) - }) - } -} - -impl TryFrom> for ClaimPath { - type Error = anyhow::Error; - fn try_from(value: Vec) -> Result { - if value.is_empty() { - Err(anyhow::anyhow!("`ClaimPath` cannot be empty")) - } else { - Ok(Self(value)) - } - } -} - -impl Display for ClaimPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let segments = self.iter().join(", "); - write!(f, "[{segments}]") - } -} - -impl Deref for ClaimPath { - type Target = [ClaimPathSegment]; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// A single segment of a [`ClaimPath`]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged, try_from = "Value")] -pub enum ClaimPathSegment { - /// JSON object property. - Name(String), - /// JSON array entry. - Position(usize), - /// All properties or entries. - #[serde(serialize_with = "serialize_all_variant")] - All, -} - -impl Display for ClaimPathSegment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::All => write!(f, "null"), - Self::Name(name) => write!(f, "\"{name}\""), - Self::Position(i) => write!(f, "{i}"), - } - } -} - -impl TryFrom for ClaimPathSegment { - type Error = anyhow::Error; - fn try_from(value: Value) -> Result { - match value { - Value::Null => Ok(ClaimPathSegment::All), - Value::String(s) => Ok(ClaimPathSegment::Name(s)), - Value::Number(n) => n - .as_u64() - .ok_or_else(|| anyhow::anyhow!("expected number greater or equal to 0")) - .map(|n| ClaimPathSegment::Position(n as usize)), - _ => Err(anyhow::anyhow!("expected either a string, number, or null")), - } - } -} - -fn serialize_all_variant(serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_none() -} - -/// Information about whether a given claim is selectively disclosable. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ClaimDisclosability { - /// The issuer **must** make the claim selectively disclosable. - Always, - /// The issuer **may** make the claim selectively disclosable. - #[default] - Allowed, - /// The issuer **must not** make the claim selectively disclosable. - Never, -} - -/// Display information for a given claim. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ClaimDisplay { - /// A language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). - pub lang: String, - /// A human-readable label for the claim. - pub label: String, - /// A human-readable description for the claim. - pub description: Option, -} - -enum OneOrManyValue<'v> { - One(&'v Value), - Many(Box + 'v>), -} - -impl<'v> OneOrManyValue<'v> { - fn get(self, segment: &ClaimPathSegment) -> anyhow::Result> { - match self { - Self::One(value) => index_value(value, segment), - Self::Many(values) => { - let new_values = values - .map(|value| index_value(value, segment)) - .collect::>>()? - .into_iter() - .flatten(); - - Ok(OneOrManyValue::Many(Box::new(new_values))) - } - } - } -} - -struct OneOrManyValueIter<'v>(Option>); - -impl<'v> OneOrManyValueIter<'v> { - fn new(value: OneOrManyValue<'v>) -> Self { - Self(Some(value)) - } -} - -impl<'v> IntoIterator for OneOrManyValue<'v> { - type IntoIter = OneOrManyValueIter<'v>; - type Item = &'v Value; - fn into_iter(self) -> Self::IntoIter { - OneOrManyValueIter::new(self) - } -} - -impl<'v> Iterator for OneOrManyValueIter<'v> { - type Item = &'v Value; - fn next(&mut self) -> Option { - match self.0.take()? { - OneOrManyValue::One(v) => Some(v), - OneOrManyValue::Many(mut values) => { - let value = values.next(); - self.0 = Some(OneOrManyValue::Many(values)); - - value - } - } - } -} - -fn index_value<'v>(value: &'v Value, segment: &ClaimPathSegment) -> anyhow::Result> { - match segment { - ClaimPathSegment::Name(name) => value.get(name).map(OneOrManyValue::One), - ClaimPathSegment::Position(i) => value.get(i).map(OneOrManyValue::One), - ClaimPathSegment::All => value - .as_array() - .map(|values| OneOrManyValue::Many(Box::new(values.iter()))), - } - .ok_or_else(|| anyhow::anyhow!("value {value:#} has no element {segment}")) -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - fn sample_obj() -> Value { - json!({ - "vct": "https://betelgeuse.example.com/education_credential", - "name": "Arthur Dent", - "address": { - "street_address": "42 Market Street", - "city": "Milliways", - "postal_code": "12345" - }, - "degrees": [ - { - "type": "Bachelor of Science", - "university": "University of Betelgeuse" - }, - { - "type": "Master of Science", - "university": "University of Betelgeuse" - } - ], - "nationalities": ["British", "Betelgeusian"] - }) - } - - #[test] - fn claim_path_works() { - let name_path = serde_json::from_value::(json!(["name"])).unwrap(); - let city_path = serde_json::from_value::(json!(["address", "city"])).unwrap(); - let first_degree_path = serde_json::from_value::(json!(["degrees", 0])).unwrap(); - let degrees_types_path = serde_json::from_value::(json!(["degrees", null, "type"])).unwrap(); - - assert!(matches!( - name_path.reverse_index(&sample_obj()).unwrap(), - OneOrManyValue::One(&Value::String(_)) - )); - assert!(matches!( - city_path.reverse_index(&sample_obj()).unwrap(), - OneOrManyValue::One(&Value::String(_)) - )); - assert!(matches!( - first_degree_path.reverse_index(&sample_obj()).unwrap(), - OneOrManyValue::One(&Value::Object(_)) - )); - let obj = &sample_obj(); - let mut degree_types = degrees_types_path.reverse_index(obj).unwrap().into_iter(); - assert_eq!(degree_types.next().unwrap().as_str(), Some("Bachelor of Science")); - assert_eq!(degree_types.next().unwrap().as_str(), Some("Master of Science")); - assert_eq!(degree_types.next(), None); - } -} diff --git a/identity_credential/src/sd_jwt_vc/metadata/display.rs b/identity_credential/src/sd_jwt_vc/metadata/display.rs deleted file mode 100644 index f10212d9fc..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/display.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; - -/// Credential type's display information of a given language. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct DisplayMetadata { - /// Language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). - pub lang: String, - /// VC type's human-readable name. - pub name: String, - /// VC type's human-readable description. - pub description: Option, - /// Optional rendering information. - pub rendering: Option, -} - -/// Information on how to render a given credential type. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct RenderingMetadata(serde_json::Map); diff --git a/identity_credential/src/sd_jwt_vc/metadata/integrity.rs b/identity_credential/src/sd_jwt_vc/metadata/integrity.rs deleted file mode 100644 index d41ca1f097..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/integrity.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Display; -use std::str::FromStr; - -use anyhow::anyhow; -use identity_core::convert::Base; -use identity_core::convert::BaseEncoding; -use serde::Deserialize; -use serde::Serialize; - -/// An integrity metadata string as defined in [W3C SRI](https://www.w3.org/TR/SRI/#integrity-metadata). -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(try_from = "String")] -pub struct IntegrityMetadata(String); - -impl IntegrityMetadata { - /// Parses an [`IntegrityMetadata`] from a string. - /// ## Example - /// ```rust - /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; - /// - /// let integrity_data = IntegrityMetadata::parse( - /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd", - /// ) - /// .unwrap(); - /// ``` - pub fn parse(s: &str) -> Result { - s.parse() - } - - /// Returns the digest algorithm's identifier string. - /// ## Example - /// ```rust - /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; - /// - /// let integrity_data: IntegrityMetadata = - /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" - /// .parse() - /// .unwrap(); - /// assert_eq!(integrity_data.alg(), "sha384"); - /// ``` - pub fn alg(&self) -> &str { - self.0.split_once('-').unwrap().0 - } - - /// Returns the base64 encoded digest part. - /// ## Example - /// ```rust - /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; - /// - /// let integrity_data: IntegrityMetadata = - /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" - /// .parse() - /// .unwrap(); - /// assert_eq!( - /// integrity_data.digest(), - /// "dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" - /// ); - /// ``` - pub fn digest(&self) -> &str { - self.0.split('-').nth(1).unwrap() - } - - /// Returns the digest's bytes. - pub fn digest_bytes(&self) -> Vec { - BaseEncoding::decode(self.digest(), Base::Base64).unwrap() - } - - /// Returns the option part. - /// ## Example - /// ```rust - /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; - /// - /// let integrity_data: IntegrityMetadata = - /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" - /// .parse() - /// .unwrap(); - /// assert!(integrity_data.options().is_none()); - /// ``` - pub fn options(&self) -> Option<&str> { - self.0.splitn(3, '-').nth(2) - } -} - -impl AsRef for IntegrityMetadata { - fn as_ref(&self) -> &str { - self.0.as_str() - } -} - -impl Display for IntegrityMetadata { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl FromStr for IntegrityMetadata { - type Err = anyhow::Error; - fn from_str(s: &str) -> Result { - Self::try_from(s.to_owned()) - } -} - -impl TryFrom for IntegrityMetadata { - type Error = anyhow::Error; - fn try_from(value: String) -> Result { - let mut metadata_parts = value.splitn(3, '-'); - let _alg = metadata_parts - .next() - .ok_or_else(|| anyhow!("invalid integrity metadata"))?; - let _digest = metadata_parts - .next() - .and_then(|digest| BaseEncoding::decode(digest, Base::Base64).ok()) - .ok_or_else(|| anyhow!("invalid integrity metadata"))?; - let _options = metadata_parts.next(); - - Ok(Self(value)) - } -} diff --git a/identity_credential/src/sd_jwt_vc/metadata/issuer.rs b/identity_credential/src/sd_jwt_vc/metadata/issuer.rs deleted file mode 100644 index 1b9838815c..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/issuer.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_core::common::Url; -use identity_verification::jwk::JwkSet; -use serde::Deserialize; -use serde::Serialize; - -use crate::sd_jwt_vc::Error; -use crate::sd_jwt_vc::SdJwtVc; -#[allow(unused_imports)] -use crate::sd_jwt_vc::SdJwtVcClaims; - -/// Path used to query [`IssuerMetadata`] for a given JWT VC issuer. -pub const WELL_KNOWN_VC_ISSUER: &str = "/.well-known/jwt-vc-issuer"; - -/// SD-JWT VC issuer's metadata. Contains information about one issuer's -/// public keys, either as an embedded JWK Set or a reference to one. -/// ## Notes -/// - [`IssuerMetadata::issuer`] must exactly match [`SdJwtVcClaims::iss`] in order to be considered valid. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -pub struct IssuerMetadata { - /// Issuer URI. - pub issuer: Url, - /// JWK Set containing the issuer's public keys. - #[serde(flatten)] - pub jwks: Jwks, -} - -impl IssuerMetadata { - /// Checks the validity of this [`IssuerMetadata`]. - /// [`IssuerMetadata::issuer`] must match `sd_jwt_vc`'s iss claim's value. - pub fn validate(&self, sd_jwt_vc: &SdJwtVc) -> Result<(), Error> { - let expected_issuer = &sd_jwt_vc.claims().iss; - let actual_issuer = &self.issuer; - if actual_issuer != expected_issuer { - Err(Error::InvalidIssuerMetadata(anyhow::anyhow!( - "expected issuer \"{expected_issuer}\", but found \"{actual_issuer}\"" - ))) - } else { - Ok(()) - } - } -} - -/// JWK Set containing the issuer's public keys or a URL string referencing them. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -pub enum Jwks { - /// Reference to a JWK set. - #[serde(rename = "jwks_uri")] - Uri(Url), - /// An embedded JWK set. - #[serde(rename = "jwks")] - Object(JwkSet), -} - -#[cfg(test)] -mod tests { - use super::*; - - const EXAMPLE_URI_ISSUER_METADATA: &str = r#" -{ - "issuer":"https://example.com", - "jwks_uri":"https://jwt-vc-issuer.example.org/my_public_keys.jwks" -} - "#; - const EXAMPLE_JWKS_ISSUER_METADATA: &str = r#" -{ - "issuer":"https://example.com", - "jwks":{ - "keys":[ - { - "kid":"doc-signer-05-25-2022", - "e":"AQAB", - "n":"nj3YJwsLUFl9BmpAbkOswCNVx17Eh9wMO-_AReZwBqfaWFcfGHrZXsIV2VMCNVNU8Tpb4obUaSXcRcQ-VMsfQPJm9IzgtRdAY8NN8Xb7PEcYyklBjvTtuPbpzIaqyiUepzUXNDFuAOOkrIol3WmflPUUgMKULBN0EUd1fpOD70pRM0rlp_gg_WNUKoW1V-3keYUJoXH9NztEDm_D2MQXj9eGOJJ8yPgGL8PAZMLe2R7jb9TxOCPDED7tY_TU4nFPlxptw59A42mldEmViXsKQt60s1SLboazxFKveqXC_jpLUt22OC6GUG63p-REw-ZOr3r845z50wMuzifQrMI9bQ", - "kty":"RSA" - } - ] - } -} - "#; - - #[test] - fn deserializing_uri_metadata_works() { - let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_URI_ISSUER_METADATA).unwrap(); - assert!(matches!(issuer_metadata.jwks, Jwks::Uri(_))); - } - - #[test] - fn deserializing_jwks_metadata_works() { - let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_JWKS_ISSUER_METADATA).unwrap(); - assert!(matches!(issuer_metadata.jwks, Jwks::Object { .. })); - } -} diff --git a/identity_credential/src/sd_jwt_vc/metadata/mod.rs b/identity_credential/src/sd_jwt_vc/metadata/mod.rs deleted file mode 100644 index 662c42032f..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod claim; -mod display; -mod integrity; -mod issuer; -mod vc_type; - -pub use claim::*; -pub use display::*; -pub use integrity::*; -pub use issuer::*; -pub use vc_type::*; diff --git a/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs b/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs deleted file mode 100644 index 04f3a87a11..0000000000 --- a/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use futures::future::FutureExt; -use futures::future::LocalBoxFuture; -use identity_core::common::Url; -use itertools::Itertools as _; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; - -use crate::sd_jwt_vc::Error; -use crate::sd_jwt_vc::Resolver; -use crate::sd_jwt_vc::Result; - -use super::ClaimMetadata; -use super::DisplayMetadata; -use super::IntegrityMetadata; - -/// Path used to retrieve VC Type Metadata. -pub const WELL_KNOWN_VCT: &str = "/.well-known/vct"; - -/// SD-JWT VC's credential type. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TypeMetadata { - /// A human-readable name for the type, intended for developers reading the JSON document. - pub name: Option, - /// A human-readable description for the type, intended for developers reading the JSON document. - pub description: Option, - /// A URI of another type that this type extends. - pub extends: Option, - /// Integrity metadata for the extended type. - #[serde(rename = "extends#integrity")] - pub extends_integrity: Option, - /// Either an embedded schema or a reference to one. - #[serde(flatten)] - pub schema: Option, - /// A list containing display information for the type. - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub display: Vec, - /// A list of [`ClaimMetadata`] containing information about particular claims. - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub claims: Vec, -} - -impl TypeMetadata { - /// Returns the name of this VC type, if any. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() - } - /// Returns the description of this VC type, if any. - pub fn description(&self) -> Option<&str> { - self.description.as_deref() - } - /// Returns the URI or string of the type this VC type extends, if any. - pub fn extends(&self) -> Option<&Url> { - self.extends.as_ref() - } - /// Returns the integrity string of the extended type object, if any. - pub fn extends_integrity(&self) -> Option<&str> { - self.extends_integrity.as_ref().map(|meta| meta.as_ref()) - } - /// Returns the [`ClaimMetadata`]s associated with this credential type. - pub fn claim_metadata(&self) -> &[ClaimMetadata] { - &self.claims - } - /// Returns the [`DisplayMetadata`]s associated with this credential type. - pub fn display_metadata(&self) -> &[DisplayMetadata] { - &self.display - } - /// Uses this [`TypeMetadata`] to validate JSON object `credential`. This method fails - /// if the schema is referenced instead of embedded. - /// Use [`TypeMetadata::validate_credential_with_resolver`] for such cases. - /// ## Notes - /// This method ignores type extensions. - pub fn validate_credential(&self, credential: &Value) -> Result<()> { - match &self.schema { - Some(TypeSchema::Object { schema, .. }) => validate_credential_with_schema(schema, credential), - Some(_) => Err(Error::Validation(anyhow::anyhow!( - "this credential type references a schema; resolution is required" - ))), - None => Ok(()), - } - } - - /// Similar to [`TypeMetadata::validate_credential`], but accepts a [`Resolver`] - /// [`Url`] -> [`Value`] that is used to resolve any reference to either - /// another type or JSON schema. - pub async fn validate_credential_with_resolver(&self, credential: &Value, resolver: &R) -> Result<()> - where - R: Resolver, - { - validate_credential_impl(self.clone(), credential, resolver, vec![]).await - } -} - -// Recursively validate a credential. -fn validate_credential_impl<'c, 'r, R>( - current_type: TypeMetadata, - credential: &'c Value, - resolver: &'r R, - mut passed_types: Vec, -) -> LocalBoxFuture<'c, Result<()>> -where - R: Resolver, - 'r: 'c, -{ - async move { - // Check if current type has already been checked. - let is_type_already_checked = passed_types.contains(¤t_type); - if is_type_already_checked { - // This is a dependency cycle! - return Err(Error::Validation(anyhow::anyhow!("dependency cycle detected"))); - } - - // Check if `validate_credential` should have been called instead. - let has_extend = current_type.extends.is_none(); - let is_immediate = current_type - .schema - .as_ref() - .map(|schema| matches!(schema, &TypeSchema::Object { .. })) - .unwrap_or(true); - - if is_immediate && !has_extend { - return current_type.validate_credential(credential); - } - - if !is_immediate { - // Fetch schema and validate `current_type`. - let TypeSchema::Uri { schema_uri, .. } = current_type.schema.as_ref().unwrap() else { - unreachable!("schema is provided through `schema_uri` as checked by `validate_credential`"); - }; - let schema = resolver.resolve(schema_uri).await.map_err(|e| Error::Resolution { - input: schema_uri.to_string(), - source: e, - })?; - validate_credential_with_schema(&schema, credential)?; - } - - // Check for extends. - if let Some(extends_uri) = current_type.extends() { - // Fetch the extended type metadata and parse it. - let raw_type_metadata = resolver.resolve(extends_uri).await.map_err(|e| Error::Resolution { - input: extends_uri.to_string(), - source: e, - })?; - let type_metadata = - serde_json::from_value(raw_type_metadata).map_err(|e| Error::InvalidTypeMetadata(e.into()))?; - // Forward validation of new type. - passed_types.push(current_type); - validate_credential_impl(type_metadata, credential, resolver, passed_types).await - } else { - Ok(()) - } - } - .boxed_local() -} - -fn validate_credential_with_schema(schema: &Value, credential: &Value) -> Result<()> { - let schema = jsonschema::compile(schema).map_err(|e| Error::Validation(anyhow::anyhow!(e.to_string())))?; - schema.validate(credential).map_err(|errors| { - let error_msg = errors.map(|e| e.to_string()).join("; "); - Error::Validation(anyhow::anyhow!(error_msg)) - }) -} - -/// Either a reference to or an embedded JSON Schema. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -#[serde(untagged)] -pub enum TypeSchema { - /// URI reference to a JSON schema. - Uri { - /// URI of the referenced JSON schema. - schema_uri: Url, - /// Integrity string for the referenced schema. - #[serde(rename = "schema_uri#integrity")] - schema_uri_integrity: Option, - }, - /// An embedded JSON schema. - Object { - /// The JSON schema. - schema: Value, - /// Integrity of the JSON schema. - #[serde(rename = "schema#integrity")] - schema_integrity: Option, - }, -} - -#[cfg(test)] -mod tests { - use std::sync::LazyLock; - - use async_trait::async_trait; - use serde_json::json; - - use crate::sd_jwt_vc::resolver; - - use super::*; - - static IMMEDIATE_TYPE_METADATA: LazyLock = LazyLock::new(|| TypeMetadata { - name: Some("immediate credential".to_string()), - description: None, - extends: None, - extends_integrity: None, - display: vec![], - claims: vec![], - schema: Some(TypeSchema::Object { - schema: json!({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "age": { - "type": "number" - } - }, - "required": ["name", "age"] - }), - schema_integrity: None, - }), - }); - static REFERENCED_TYPE_METADATA: LazyLock = LazyLock::new(|| TypeMetadata { - name: Some("immediate credential".to_string()), - description: None, - extends: None, - extends_integrity: None, - display: vec![], - claims: vec![], - schema: Some(TypeSchema::Uri { - schema_uri: Url::parse("https://example.com/vc_types/1").unwrap(), - schema_uri_integrity: None, - }), - }); - - struct SchemaResolver; - #[async_trait] - impl Resolver for SchemaResolver { - async fn resolve(&self, _input: &Url) -> resolver::Result { - Ok(serde_json::to_value(IMMEDIATE_TYPE_METADATA.clone().schema).unwrap()) - } - } - - #[test] - fn validation_of_immediate_type_metadata_works() { - IMMEDIATE_TYPE_METADATA - .validate_credential(&json!({ - "name": "John Doe", - "age": 42 - })) - .unwrap(); - } - - #[tokio::test] - async fn validation_of_referenced_type_metadata_works() { - REFERENCED_TYPE_METADATA - .validate_credential_with_resolver( - &json!({ - "name": "Aristide Zantedeschi", - "age": 90, - }), - &SchemaResolver, - ) - .await - .unwrap(); - } -} diff --git a/identity_credential/src/sd_jwt_vc/mod.rs b/identity_credential/src/sd_jwt_vc/mod.rs deleted file mode 100644 index 66f4b530a1..0000000000 --- a/identity_credential/src/sd_jwt_vc/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod builder; -mod claims; -mod error; -/// Additional metadata defined by the SD-JWT VC specification -/// such as issuer's metadata and credential type metadata. -pub mod metadata; -mod presentation; -/// Resolver trait. -pub mod resolver; -mod status; -#[cfg(test)] -pub(crate) mod tests; -mod token; - -pub use builder::*; -pub use claims::*; -pub use error::Error; -pub use error::Result; -pub use presentation::*; -pub use resolver::Resolver; -pub use status::*; -pub use token::*; diff --git a/identity_credential/src/sd_jwt_vc/presentation.rs b/identity_credential/src/sd_jwt_vc/presentation.rs deleted file mode 100644 index 06a2d2feac..0000000000 --- a/identity_credential/src/sd_jwt_vc/presentation.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use super::Error; -use super::Result; -use super::SdJwtVc; -use super::SdJwtVcClaims; - -use sd_jwt_payload_rework::Disclosure; -use sd_jwt_payload_rework::Hasher; -use sd_jwt_payload_rework::KeyBindingJwt; -use sd_jwt_payload_rework::SdJwtPresentationBuilder; - -/// Builder structure to create an SD-JWT VC presentation. -/// It allows users to conceal claims and attach a key binding JWT. -#[derive(Debug, Clone)] -pub struct SdJwtVcPresentationBuilder { - vc_claims: SdJwtVcClaims, - builder: SdJwtPresentationBuilder, -} - -impl SdJwtVcPresentationBuilder { - /// Prepare a presentation for a given [`SdJwtVc`]. - pub fn new(token: SdJwtVc, hasher: &dyn Hasher) -> Result { - let SdJwtVc { - sd_jwt, - parsed_claims: vc_claims, - } = token; - let builder = sd_jwt.into_presentation(hasher).map_err(Error::SdJwt)?; - - Ok(Self { vc_claims, builder }) - } - /// Removes the disclosure for the property at `path`, conceiling it. - /// - /// ## Notes - /// - When concealing a claim more than one disclosure may be removed: the disclosure for the claim itself and the - /// disclosures for any concealable sub-claim. - pub fn conceal(mut self, path: &str) -> Result { - self.builder = self.builder.conceal(path).map_err(Error::SdJwt)?; - Ok(self) - } - - /// Adds a [`KeyBindingJwt`] to this [`SdJwtVc`]'s presentation. - pub fn attach_key_binding_jwt(mut self, kb_jwt: KeyBindingJwt) -> Self { - self.builder = self.builder.attach_key_binding_jwt(kb_jwt); - self - } - - /// Returns the resulting [`SdJwtVc`] together with all removed disclosures. - pub fn finish(self) -> Result<(SdJwtVc, Vec)> { - let (sd_jwt, disclosures) = self.builder.finish()?; - Ok((SdJwtVc::new(sd_jwt, self.vc_claims), disclosures)) - } -} diff --git a/identity_credential/src/sd_jwt_vc/resolver.rs b/identity_credential/src/sd_jwt_vc/resolver.rs deleted file mode 100644 index 69bd74fc7a..0000000000 --- a/identity_credential/src/sd_jwt_vc/resolver.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use async_trait::async_trait; -use thiserror::Error; - -pub(crate) type Result = std::result::Result; - -/// [`Resolver`]'s errors. -#[derive(Debug, Error)] -pub enum Error { - /// The queried item doesn't exist. - #[error("The requested item \"{0}\" was not found.")] - NotFound(String), - /// Failed to parse input. - #[error("Failed to parse the provided input into a resolvable type: {0}")] - ParsingFailure(#[source] anyhow::Error), - /// Generic error. - #[error(transparent)] - Generic(#[from] anyhow::Error), -} - -/// A type capable of asynchronously producing values of type `T` from inputs of type `I`. -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -pub trait Resolver { - /// Fetch the resource of type [`Resolver::Target`] using `input`. - async fn resolve(&self, input: &I) -> Result; -} diff --git a/identity_credential/src/sd_jwt_vc/status.rs b/identity_credential/src/sd_jwt_vc/status.rs deleted file mode 100644 index 1c68db6d4c..0000000000 --- a/identity_credential/src/sd_jwt_vc/status.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_core::common::Url; -use serde::Deserialize; -use serde::Serialize; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -/// SD-JWT VC's `status` claim value. Used to retrieve the status of the token. -pub struct Status(StatusMechanism); - -/// Mechanism used for representing the status of an SD-JWT VC token. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum StatusMechanism { - /// Reference to a status list containing this token's status. - #[serde(rename = "status_list")] - StatusList(StatusListRef), - /// A non-standard status mechanism. - #[serde(untagged)] - Custom(serde_json::Value), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -/// A reference to an OAuth status list. -/// See [OAuth StatusList specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-02) -/// for more information. -pub struct StatusListRef { - /// URI of the status list. - pub uri: Url, - /// Index of the entry containing this token's status. - pub idx: usize, -} - -#[cfg(test)] -mod tests { - use super::*; - - use serde_json::json; - - #[test] - fn round_trip() { - let status_value = json!({ - "status_list": { - "idx": 420, - "uri": "https://example.com/statuslists/1" - } - }); - let status: Status = serde_json::from_value(status_value.clone()).unwrap(); - assert_eq!(serde_json::to_value(status).unwrap(), status_value); - } -} diff --git a/identity_credential/src/sd_jwt_vc/tests/mod.rs b/identity_credential/src/sd_jwt_vc/tests/mod.rs deleted file mode 100644 index f93fe20784..0000000000 --- a/identity_credential/src/sd_jwt_vc/tests/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; - -use async_trait::async_trait; -use identity_core::convert::Base; -use identity_core::convert::BaseEncoding; -use identity_verification::jwk::Jwk; -use identity_verification::jwk::JwkParamsOct; -use identity_verification::jws::JwsVerifier; -use josekit::jws::JwsHeader; -use josekit::jws::HS256; -use josekit::jwt::JwtPayload; -use josekit::jwt::{self}; -use sd_jwt_payload_rework::JsonObject; -use sd_jwt_payload_rework::JwsSigner; -use serde::Serialize; -use serde_json::Value; - -use super::resolver; -use super::Resolver; - -mod validation; - -pub(crate) const ISSUER_SECRET: &[u8] = b"0123456789ABCDEF0123456789ABCDEF"; - -/// A JWS signer that uses HS256 with a static secret string. -pub(crate) struct TestSigner; - -pub(crate) fn signer_secret_jwk() -> Jwk { - let mut params = JwkParamsOct::new(); - params.k = BaseEncoding::encode(ISSUER_SECRET, Base::Base64Url); - let mut jwk = Jwk::from_params(params); - jwk.set_kid("key1"); - - jwk -} - -#[async_trait] -impl JwsSigner for TestSigner { - type Error = josekit::JoseError; - async fn sign(&self, header: &JsonObject, payload: &JsonObject) -> std::result::Result, Self::Error> { - let signer = HS256.signer_from_bytes(ISSUER_SECRET)?; - let header = JwsHeader::from_map(header.clone())?; - let payload = JwtPayload::from_map(payload.clone())?; - let jws = jwt::encode_with_signer(&payload, &header, &signer)?; - - Ok(jws.into_bytes()) - } -} - -#[derive(Default, Debug, Clone)] -pub(crate) struct TestResolver(HashMap>); - -impl TestResolver { - pub(crate) fn new() -> Self { - Self::default() - } - - pub(crate) fn insert_resource(&mut self, id: K, value: V) - where - K: ToString, - V: Serialize, - { - let value = serde_json::to_vec(&value).unwrap(); - self.0.insert(id.to_string(), value); - } -} - -#[async_trait] -impl Resolver> for TestResolver -where - I: ToString + Sync, -{ - async fn resolve(&self, id: &I) -> Result, resolver::Error> { - let id = id.to_string(); - self.0.get(&id).cloned().ok_or_else(|| resolver::Error::NotFound(id)) - } -} - -#[async_trait] -impl Resolver for TestResolver -where - I: ToString + Sync, -{ - async fn resolve(&self, id: &I) -> Result { - let id = id.to_string(); - self - .0 - .get(&id) - .ok_or_else(|| resolver::Error::NotFound(id)) - .and_then(|bytes| serde_json::from_slice(bytes).map_err(|e| resolver::Error::ParsingFailure(e.into()))) - } -} - -pub(crate) struct TestJwsVerifier; - -impl JwsVerifier for TestJwsVerifier { - fn verify( - &self, - input: identity_verification::jws::VerificationInput, - public_key: &Jwk, - ) -> Result<(), identity_verification::jws::SignatureVerificationError> { - let key = serde_json::to_value(public_key.clone()) - .and_then(serde_json::from_value) - .unwrap(); - let verifier = HS256.verifier_from_jwk(&key).unwrap(); - verifier.verify(&input.signing_input, &input.decoded_signature).unwrap(); - - Ok(()) - } -} diff --git a/identity_credential/src/sd_jwt_vc/tests/validation.rs b/identity_credential/src/sd_jwt_vc/tests/validation.rs deleted file mode 100644 index bc17b33952..0000000000 --- a/identity_credential/src/sd_jwt_vc/tests/validation.rs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use identity_core::common::Timestamp; -use identity_core::common::Url; -use identity_verification::jwk::JwkSet; -use sd_jwt_payload_rework::Sha256Hasher; -use serde_json::json; - -use crate::sd_jwt_vc::metadata::IssuerMetadata; -use crate::sd_jwt_vc::metadata::Jwks; -use crate::sd_jwt_vc::metadata::TypeMetadata; -use crate::sd_jwt_vc::tests::TestJwsVerifier; -use crate::sd_jwt_vc::Error; -use crate::sd_jwt_vc::SdJwtVcBuilder; - -use super::TestResolver; -use super::TestSigner; - -fn issuer_metadata() -> IssuerMetadata { - let mut jwk_set = JwkSet::new(); - jwk_set.add(super::signer_secret_jwk()); - - IssuerMetadata { - issuer: "https://example.com".parse().unwrap(), - jwks: Jwks::Object(jwk_set), - } -} - -fn test_resolver() -> TestResolver { - let mut test_resolver = TestResolver::new(); - test_resolver.insert_resource("https://example.com/.well-known/jwt-vc-issuer/", issuer_metadata()); - test_resolver.insert_resource( - "https://example.com/.well-known/vct/education_credential", - vc_metadata(), - ); - - test_resolver -} - -#[tokio::test] -async fn validation_of_valid_token_works() -> anyhow::Result<()> { - let sd_jwt_credential = SdJwtVcBuilder::new(json!({ - "name": "John Doe", - "address": { - "street_address": "A random street", - "number": "3a" - }, - "degree": [] - }))? - .header(std::iter::once(("kid".to_string(), serde_json::Value::String("key1".to_string()))).collect()) - .vct("https://example.com/education_credential".parse::()?) - .iat(Timestamp::now_utc()) - .iss("https://example.com".parse()?) - .make_concealable("/address/street_address")? - .make_concealable("/address")? - .finish(&TestSigner, "HS256") - .await?; - - let resolver = test_resolver(); - sd_jwt_credential - .validate(&resolver, &TestJwsVerifier, &Sha256Hasher::new()) - .await?; - Ok(()) -} - -#[tokio::test] -async fn validation_of_invalid_token_fails() -> anyhow::Result<()> { - let sd_jwt_credential = SdJwtVcBuilder::new(json!({ - "name": "John Doe", - "address": { - "street_address": "A random street", - "number": "3a" - }, - "degree": [] - }))? - .header(std::iter::once(("kid".to_string(), serde_json::Value::String("invalid_key".to_string()))).collect()) - .vct("https://example.com/education_credential".parse::()?) - .iat(Timestamp::now_utc()) - .iss("https://example.com".parse()?) - .make_concealable("/address/street_address")? - .make_concealable("/address")? - .finish(&TestSigner, "HS256") - .await?; - - let resolver = test_resolver(); - let error = sd_jwt_credential - .validate(&resolver, &TestJwsVerifier, &Sha256Hasher::new()) - .await - .unwrap_err(); - assert!(matches!(error, Error::Verification(_))); - - Ok(()) -} - -fn vc_metadata() -> TypeMetadata { - serde_json::from_str( - r#"{ - "vct": "https://example.com/education_credential", - "name": "Betelgeuse Education Credential - Preliminary Version", - "description": "This is our development version of the education credential. Don't panic.", - "claims": [ - { - "path": ["name"], - "display": [ - { - "lang": "de-DE", - "label": "Vor- und Nachname", - "description": "Der Name des Studenten" - }, - { - "lang": "en-US", - "label": "Name", - "description": "The name of the student" - } - ], - "sd": "allowed" - }, - { - "path": ["address"], - "display": [ - { - "lang": "de-DE", - "label": "Adresse", - "description": "Adresse zum Zeitpunkt des Abschlusses" - }, - { - "lang": "en-US", - "label": "Address", - "description": "Address at the time of graduation" - } - ], - "sd": "always" - }, - { - "path": ["address", "street_address"], - "display": [ - { - "lang": "de-DE", - "label": "Straße" - }, - { - "lang": "en-US", - "label": "Street Address" - } - ], - "sd": "always", - "svg_id": "address_street_address" - }, - { - "path": ["degrees", null], - "display": [ - { - "lang": "de-DE", - "label": "Abschluss", - "description": "Der Abschluss des Studenten" - }, - { - "lang": "en-US", - "label": "Degree", - "description": "Degree earned by the student" - } - ], - "sd": "allowed" - } - ], - "schema_url": "https://example.com/credential-schema", - "schema_url#integrity": "sha256-o984vn819a48ui1llkwPmKjZ5t0WRL5ca_xGgX3c1VLmXfh" -}"#, - ) - .unwrap() -} diff --git a/identity_credential/src/sd_jwt_vc/token.rs b/identity_credential/src/sd_jwt_vc/token.rs deleted file mode 100644 index 0cc05514a1..0000000000 --- a/identity_credential/src/sd_jwt_vc/token.rs +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright 2020-2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Display; -use std::ops::Deref; -use std::str::FromStr; - -use super::claims::SdJwtVcClaims; -use super::metadata::ClaimMetadata; -use super::metadata::IssuerMetadata; -use super::metadata::Jwks; -use super::metadata::TypeMetadata; -use super::metadata::WELL_KNOWN_VCT; -use super::metadata::WELL_KNOWN_VC_ISSUER; -use super::resolver::Error as ResolverErr; -use super::Error; -use super::Resolver; -use super::Result; -use super::SdJwtVcPresentationBuilder; -use crate::validator::JwtCredentialValidator as JwsUtils; -use crate::validator::KeyBindingJWTValidationOptions; -use anyhow::anyhow; -use identity_core::common::StringOrUrl; -use identity_core::common::Timestamp; -use identity_core::common::Url; -use identity_core::convert::ToJson as _; -use identity_verification::jwk::Jwk; -use identity_verification::jwk::JwkSet; -use identity_verification::jws::JwsVerifier; -use sd_jwt_payload_rework::Hasher; -use sd_jwt_payload_rework::JsonObject; -use sd_jwt_payload_rework::RequiredKeyBinding; -use sd_jwt_payload_rework::SdJwt; -use sd_jwt_payload_rework::SHA_ALG_NAME; -use serde_json::Value; - -/// SD-JWT VC's JOSE header `typ`'s value. -pub const SD_JWT_VC_TYP: &str = "vc+sd-jwt"; - -#[derive(Debug, Clone, PartialEq, Eq)] -/// An SD-JWT carrying a verifiable credential as described in -/// [SD-JWT VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-04.html). -pub struct SdJwtVc { - pub(crate) sd_jwt: SdJwt, - pub(crate) parsed_claims: SdJwtVcClaims, -} - -impl Deref for SdJwtVc { - type Target = SdJwt; - fn deref(&self) -> &Self::Target { - &self.sd_jwt - } -} - -impl SdJwtVc { - pub(crate) fn new(sd_jwt: SdJwt, claims: SdJwtVcClaims) -> Self { - Self { - sd_jwt, - parsed_claims: claims, - } - } - - /// Parses a string into an [`SdJwtVc`]. - pub fn parse(s: &str) -> Result { - s.parse() - } - - /// Returns a reference to this [`SdJwtVc`]'s JWT claims. - pub fn claims(&self) -> &SdJwtVcClaims { - &self.parsed_claims - } - - /// Prepares this [`SdJwtVc`] for a presentation, returning an [`SdJwtVcPresentationBuilder`]. - /// ## Errors - /// - [`Error::SdJwt`] is returned if the provided `hasher`'s algorithm doesn't match the algorithm specified by - /// SD-JWT's `_sd_alg` claim. "sha-256" is used if the claim is missing. - pub fn into_presentation(self, hasher: &dyn Hasher) -> Result { - SdJwtVcPresentationBuilder::new(self, hasher) - } - - /// Returns the JSON object obtained by replacing all disclosures into their - /// corresponding JWT concealable claims. - pub fn into_disclosed_object(self, hasher: &dyn Hasher) -> Result { - SdJwt::from(self).into_disclosed_object(hasher).map_err(Error::SdJwt) - } - - /// Retrieves this SD-JWT VC's issuer's metadata by querying its default location. - /// ## Notes - /// This method doesn't perform any validation of the retrieved [`IssuerMetadata`] - /// besides its syntactical validity. - /// To check if the retrieved [`IssuerMetadata`] is valid use [`IssuerMetadata::validate`]. - pub async fn issuer_metadata(&self, resolver: &R) -> Result> - where - R: Resolver>, - { - let metadata_url = { - let origin = self.claims().iss.origin().ascii_serialization(); - let path = self.claims().iss.path(); - format!("{origin}{WELL_KNOWN_VC_ISSUER}{path}").parse().unwrap() - }; - match resolver.resolve(&metadata_url).await { - Err(ResolverErr::NotFound(_)) => Ok(None), - Err(e) => Err(Error::Resolution { - input: metadata_url.to_string(), - source: e, - }), - Ok(json_res) => serde_json::from_slice(&json_res) - .map_err(|e| Error::InvalidIssuerMetadata(e.into())) - .map(Some), - } - } - - /// Retrieve this SD-JWT VC credential's type metadata [`TypeMetadata`]. - /// ## Notes - /// `resolver` is fed with whatever value [`SdJwtVc`]'s `vct` might have. - /// If `vct` is a URI with scheme `https`, `resolver` must fetch the [`TypeMetadata`] - /// resource by combining `vct`'s value with [`WELL_KNOWN_VCT`]. To simplify this process - /// the utility function [`vct_to_url`] is provided. - /// - /// Returns the parsed [`TypeMetadata`] along with the raw [`Resolver`]'s response. - /// The latter can be used to validate the `vct#integrity` claim if present. - pub async fn type_metadata(&self, resolver: &R) -> Result<(TypeMetadata, Vec)> - where - R: Resolver>, - { - let vct = match self.claims().vct.clone() { - StringOrUrl::Url(url) => StringOrUrl::Url(vct_to_url(&url).unwrap_or(url)), - s => s, - }; - let raw = resolver.resolve(&vct).await.map_err(|e| Error::Resolution { - input: vct.to_string(), - source: e, - })?; - let metadata = serde_json::from_slice(&raw).map_err(|e| Error::InvalidTypeMetadata(e.into()))?; - - Ok((metadata, raw)) - } - - /// Resolves the issuer's public key in JWK format. - /// The issuer's JWK is first fetched through the issuer's metadata, - /// if this attempt fails `resolver` is used to query the key directly - /// through `kid`'s value. - pub async fn issuer_jwk(&self, resolver: &R) -> Result - where - R: Resolver>, - { - let kid = self - .header() - .get("kid") - .and_then(|value| value.as_str()) - .ok_or_else(|| Error::Verification(anyhow!("missing header claim `kid`")))?; - - // Try to find the key among issuer metadata jwk set. - if let jwk @ Ok(_) = self.issuer_jwk_from_iss_metadata(resolver, kid).await { - jwk - } else { - // Issuer has no metadata that can lead to its JWK. Let's see if it can be resolved directly. - let jwk_uri = kid.parse::().map_err(|_| { - Error::Verification(anyhow!( - "JWK's kid \"{kid}\" could not be found in JKW set and cannot be resolved" - )) - })?; - resolver - .resolve(&jwk_uri) - .await - .map_err(|e| Error::Resolution { - input: jwk_uri.to_string(), - source: e, - }) - .and_then(|bytes| { - serde_json::from_slice(&bytes).map_err(|e| Error::Verification(anyhow!("invalid JWK: {}", e))) - }) - } - } - - async fn issuer_jwk_from_iss_metadata(&self, resolver: &R, kid: &str) -> Result - where - R: Resolver>, - { - let metadata = self - .issuer_metadata(resolver) - .await? - .ok_or_else(|| Error::Verification(anyhow!("missing issuer metadata")))?; - metadata.validate(self)?; - - let jwks = match metadata.jwks { - Jwks::Object(jwks) => jwks, - Jwks::Uri(jwks_uri) => resolver - .resolve(&jwks_uri) - .await - .map_err(|e| Error::Resolution { - input: jwks_uri.into_string(), - source: e, - }) - .and_then(|bytes| serde_json::from_slice::(&bytes).map_err(|e| Error::Verification(e.into())))?, - }; - jwks - .iter() - .find(|jwk| jwk.kid() == Some(kid)) - .cloned() - .ok_or_else(|| Error::Verification(anyhow!("missing key \"{kid}\" in issuer JWK set"))) - } - - /// Verifies this [`SdJwtVc`] JWT's signature. - pub fn verify_signature(&self, jws_verifier: &V, jwk: &Jwk) -> Result<()> - where - V: JwsVerifier, - { - let sd_jwt_str = self.sd_jwt.to_string(); - let jws_input = { - let jwt_str = sd_jwt_str.split_once('~').unwrap().0; - JwsUtils::::decode(jwt_str).map_err(|e| Error::Verification(e.into()))? - }; - - JwsUtils::::verify_signature_raw(jws_input, jwk, jws_verifier) - .map_err(|e| Error::Verification(e.into())) - .and(Ok(())) - } - - /// Checks the disclosability of this [`SdJwtVc`]'s claims against a list of [`ClaimMetadata`]. - /// ## Notes - /// This check should be performed by the token's holder in order to assert the issuer's compliance with - /// the credential's type. - pub fn validate_claims_disclosability(&self, claims_metadata: &[ClaimMetadata]) -> Result<()> { - let claims = Value::Object(self.parsed_claims.sd_jwt_claims.deref().clone()); - claims_metadata - .iter() - .try_fold((), |_, meta| meta.check_value_disclosability(&claims)) - } - - /// Check whether this [`SdJwtVc`] is valid. - /// - /// This method checks: - /// - JWS signature - /// - credential's type - /// - claims' disclosability - pub async fn validate(&self, resolver: &R, jws_verifier: &V, hasher: &dyn Hasher) -> Result<()> - where - R: Resolver>, - R: Resolver>, - R: Resolver, - V: JwsVerifier, - { - // Signature verification. - // Fetch issuer's JWK. - let jwk = self.issuer_jwk(resolver).await?; - self.verify_signature(jws_verifier, &jwk)?; - - // Credential type. - // Fetch type metadata. Skip integrity check. - let fully_disclosed_token = self.clone().into_disclosed_object(hasher).map(Value::Object)?; - let (type_metadata, _) = self.type_metadata(resolver).await?; - type_metadata - .validate_credential_with_resolver(&fully_disclosed_token, resolver) - .await?; - - // Claims' disclosability. - self.validate_claims_disclosability(type_metadata.claim_metadata())?; - - Ok(()) - } - - /// Verify the signature of this [`SdJwtVc`]'s [sd_jwt_payload_rework::KeyBindingJwt]. - pub fn verify_key_binding(&self, jws_verifier: &V, jwk: &Jwk) -> Result<()> { - let Some(kb_jwt) = self.key_binding_jwt() else { - return Ok(()); - }; - let kb_jwt_str = kb_jwt.to_string(); - let jws_input = JwsUtils::::decode(&kb_jwt_str).map_err(|e| Error::Verification(e.into()))?; - - JwsUtils::::verify_signature_raw(jws_input, jwk, jws_verifier) - .map_err(|e| Error::Verification(e.into())) - .and(Ok(())) - } - - /// Check the validity of this [`SdJwtVc`]'s [sd_jwt_payload_rework::KeyBindingJwt]. - /// # Notes - /// Validation of the required key binding (specified through the `cnf` JWT's claim) - /// is only partially validated - custom and "jwe" requirement are not checked. - pub fn validate_key_binding( - &self, - jws_verifier: &V, - jwk: &Jwk, - hasher: &dyn Hasher, - options: &KeyBindingJWTValidationOptions, - ) -> Result<()> { - self.verify_key_binding(jws_verifier, jwk)?; - - if let Some(requirement) = self.required_key_bind() { - if self.key_binding_jwt().is_none() { - return Err(Error::Validation(anyhow!( - "a key binding was required but none was provided" - ))); - } - match requirement { - RequiredKeyBinding::Jwk(json_jwk) => { - if jwk.to_json_value().unwrap().as_object().unwrap() != json_jwk { - return Err(Error::Validation(anyhow!( - "key used for signing KB-JWT does not match the key required in this SD-JWT" - ))); - } - } - RequiredKeyBinding::Kid(kid) | RequiredKeyBinding::Jwu { kid, .. } => jwk - .kid() - .filter(|id| id == kid) - .ok_or_else(|| { - Error::Validation(anyhow::anyhow!( - "the provided JWK doesn't have required `kid` \"{kid}\"" - )) - }) - .map(|_| ())?, - _ => (), - } - } - - let Some(kb_jwt) = self.key_binding_jwt() else { - return Ok(()); - }; - let KeyBindingJWTValidationOptions { - nonce, - aud, - earliest_issuance_date, - latest_issuance_date, - .. - } = options; - - let issuance_date = - Timestamp::from_unix(kb_jwt.claims().iat).map_err(|_| Error::Validation(anyhow!("invalid `iat` value")))?; - - if let Some(earliest_issuance_date) = earliest_issuance_date { - if issuance_date < *earliest_issuance_date { - return Err(Error::Validation(anyhow!( - "this KB-JWT has been created earlier than `earliest_issuance_date`" - ))); - } - } - - if let Some(latest_issuance_date) = latest_issuance_date { - if issuance_date > *latest_issuance_date { - return Err(Error::Validation(anyhow!( - "this KB-JWT has been created later than `latest_issuance_date`" - ))); - } - } else if issuance_date > Timestamp::now_utc() { - return Err(Error::Validation(anyhow!("this KB-JWT has been created in the future"))); - } - - if let Some(nonce) = nonce { - if nonce != &kb_jwt.claims().nonce { - return Err(Error::Validation(anyhow!("invalid KB-JWT's nonce: expected {nonce}"))); - } - } - - if let Some(aud) = aud { - if aud != &kb_jwt.claims().aud { - return Err(Error::Validation(anyhow!("invalid KB-JWT's `aud`: expected \"{aud}\""))); - } - } - - // Validate SD-JWT digest. - if self.claims()._sd_alg.as_deref().unwrap_or(SHA_ALG_NAME) != hasher.alg_name() { - return Err(Error::Validation(anyhow!("invalid hasher"))); - } - let encoded_sd_jwt = self.to_string(); - let digest = { - let last_tilde_idx = encoded_sd_jwt.rfind('~').expect("SD-JWT has a '~'"); - let sd_jwt_no_kb = &encoded_sd_jwt[..=last_tilde_idx]; - - hasher.encoded_digest(sd_jwt_no_kb) - }; - if kb_jwt.claims().sd_hash != digest { - return Err(Error::Validation(anyhow!("invalid KB-JWT's `sd_hash`"))); - } - - Ok(()) - } -} - -/// Converts `vct` claim's URI value into the appropriate well-known URL. -/// ## Warnings -/// Returns an [`Option::None`] if the URI's scheme is not `https`. -pub fn vct_to_url(resource: &Url) -> Option { - if resource.scheme() != "https" { - None - } else { - let origin = resource.origin().ascii_serialization(); - let path = resource.path(); - Some(format!("{origin}{WELL_KNOWN_VCT}{path}").parse().unwrap()) - } -} - -impl TryFrom for SdJwtVc { - type Error = Error; - fn try_from(mut sd_jwt: SdJwt) -> std::result::Result { - // Validate claims. - let claims = { - let claims = std::mem::take(sd_jwt.claims_mut()); - SdJwtVcClaims::try_from_sd_jwt_claims(claims, sd_jwt.disclosures())? - }; - - // Validate Header's typ. - let typ = sd_jwt - .header() - .get("typ") - .and_then(Value::as_str) - .ok_or_else(|| Error::InvalidJoseType("null".to_string()))?; - if !typ.contains(SD_JWT_VC_TYP) { - return Err(Error::InvalidJoseType(typ.to_string())); - } - - Ok(Self { - sd_jwt, - parsed_claims: claims, - }) - } -} - -impl FromStr for SdJwtVc { - type Err = Error; - fn from_str(s: &str) -> std::result::Result { - s.parse::().map_err(Error::SdJwt).and_then(TryInto::try_into) - } -} - -impl Display for SdJwtVc { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.sd_jwt) - } -} - -impl From for SdJwt { - fn from(value: SdJwtVc) -> Self { - let SdJwtVc { - mut sd_jwt, - parsed_claims, - } = value; - // Put back `parsed_claims`. - *sd_jwt.claims_mut() = parsed_claims.into(); - - sd_jwt - } -} - -#[cfg(test)] -mod tests { - use std::sync::LazyLock; - - use identity_core::common::StringOrUrl; - use identity_core::common::Url; - - use super::*; - - const EXAMPLE_SD_JWT_VC: &str = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogInZjK3NkLWp3dCJ9.eyJfc2QiOiBbIjBIWm1uU0lQejMzN2tTV2U3QzM0bC0tODhnekppLWVCSjJWel9ISndBVGciLCAiOVpicGxDN1RkRVc3cWFsNkJCWmxNdHFKZG1lRU9pWGV2ZEpsb1hWSmRSUSIsICJJMDBmY0ZVb0RYQ3VjcDV5eTJ1anFQc3NEVkdhV05pVWxpTnpfYXdEMGdjIiwgIklFQllTSkdOaFhJbHJRbzU4eWtYbTJaeDN5bGw5WmxUdFRvUG8xN1FRaVkiLCAiTGFpNklVNmQ3R1FhZ1hSN0F2R1RyblhnU2xkM3o4RUlnX2Z2M2ZPWjFXZyIsICJodkRYaHdtR2NKUXNCQ0EyT3RqdUxBY3dBTXBEc2FVMG5rb3ZjS09xV05FIiwgImlrdXVyOFE0azhxM1ZjeUE3ZEMtbU5qWkJrUmVEVFUtQ0c0bmlURTdPVFUiLCAicXZ6TkxqMnZoOW80U0VYT2ZNaVlEdXZUeWtkc1dDTmcwd1RkbHIwQUVJTSIsICJ3elcxNWJoQ2t2a3N4VnZ1SjhSRjN4aThpNjRsbjFqb183NkJDMm9hMXVnIiwgInpPZUJYaHh2SVM0WnptUWNMbHhLdUVBT0dHQnlqT3FhMXoySW9WeF9ZRFEiXSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgInZjdCI6ICJodHRwczovL2JtaS5idW5kLmV4YW1wbGUvY3JlZGVudGlhbC9waWQvMS4wIiwgImFnZV9lcXVhbF9vcl9vdmVyIjogeyJfc2QiOiBbIkZjOElfMDdMT2NnUHdyREpLUXlJR085N3dWc09wbE1Makh2UkM0UjQtV2ciLCAiWEx0TGphZFVXYzl6Tl85aE1KUm9xeTQ2VXNDS2IxSXNoWnV1cVVGS1NDQSIsICJhb0NDenNDN3A0cWhaSUFoX2lkUkNTQ2E2NDF1eWNuYzh6UGZOV3o4bngwIiwgImYxLVAwQTJkS1dhdnYxdUZuTVgyQTctRVh4dmhveHY1YUhodUVJTi1XNjQiLCAiazVoeTJyMDE4dnJzSmpvLVZqZDZnNnl0N0Fhb25Lb25uaXVKOXplbDNqbyIsICJxcDdaX0t5MVlpcDBzWWdETzN6VnVnMk1GdVBOakh4a3NCRG5KWjRhSS1jIl19LCAiX3NkX2FsZyI6ICJzaGEtMjU2IiwgImNuZiI6IHsiandrIjogeyJrdHkiOiAiRUMiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiVENBRVIxOVp2dTNPSEY0ajRXNHZmU1ZvSElQMUlMaWxEbHM3dkNlR2VtYyIsICJ5IjogIlp4amlXV2JaTVFHSFZXS1ZRNGhiU0lpcnNWZnVlY0NFNnQ0alQ5RjJIWlEifX19.CaXec2NNooWAy4eTxYbGWI--UeUL0jpC7Zb84PP_09Z655BYcXUTvfj6GPk4mrNqZUU5GT6QntYR8J9rvcBjvA~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFIl1d~WyJNMEpiNTd0NDF1YnJrU3V5ckRUM3hBIiwgIjE4IiwgdHJ1ZV0~eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL2V4YW1wbGUuY29tL3ZlcmlmaWVyIiwgImlhdCI6IDE3MjA0NTQyOTUsICJzZF9oYXNoIjogIlZFejN0bEtqOVY0UzU3TTZoRWhvVjRIc19SdmpXZWgzVHN1OTFDbmxuZUkifQ.GqtiTKNe3O95GLpdxFK_2FZULFk6KUscFe7RPk8OeVLiJiHsGvtPyq89e_grBplvGmnDGHoy8JAt1wQqiwktSg"; - static EXAMPLE_ISSUER: LazyLock = LazyLock::new(|| "https://example.com/issuer".parse().unwrap()); - static EXAMPLE_VCT: LazyLock = LazyLock::new(|| { - "https://bmi.bund.example/credential/pid/1.0" - .parse::() - .unwrap() - .into() - }); - - #[test] - fn simple_sd_jwt_is_not_a_valid_sd_jwt_vc() { - let sd_jwt: SdJwt = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBbIkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3VldDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZGekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQTjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRNcFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFsU2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZdW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJUckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVzcyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBLbmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9IemciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIsICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQWUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hyU1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpDTkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0yNTYifQ.gR6rSL7urX79CNEvTQnP1MH5xthG11ucIV44SqKFZ4Pvlu_u16RfvXQd4k4CAIBZNKn2aTI18TfvFwV97gJFoA~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ~" - .parse().unwrap(); - let err = SdJwtVc::try_from(sd_jwt).unwrap_err(); - assert!(matches!(err, Error::MissingClaim("vct"))) - } - - #[test] - fn parsing_a_valid_sd_jwt_vc_works() { - let sd_jwt_vc: SdJwtVc = EXAMPLE_SD_JWT_VC.parse().unwrap(); - assert_eq!(sd_jwt_vc.claims().iss, *EXAMPLE_ISSUER); - assert_eq!(sd_jwt_vc.claims().vct, *EXAMPLE_VCT); - } -} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index e7de3fa8e4..258df619d4 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -168,12 +168,13 @@ impl JptCredentialValidatorUtils { issuer: &DOC, status: RevocationTimeframeStatus, ) -> ValidationUnitResult { - let issuer_service_url: identity_did::DIDUrl = identity_did::DIDUrl::parse(status.id()).map_err(|err| { - JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( - "could not convert status id to DIDUrl; {}", - err, - ))) - })?; + let issuer_service_url: identity_did::DIDUrl = + identity_did::DIDUrl::parse(status.id().to_string()).map_err(|err| { + JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "could not convert status id to DIDUrl; {}", + err, + ))) + })?; // Check whether index is revoked. let revocation_bitmap: crate::revocation::RevocationBitmap = issuer diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs index acaa991e45..c099d763ab 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs @@ -297,7 +297,7 @@ impl JwtCredentialValidator { } /// Verify the signature using the given `public_key` and `signature_verifier`. - pub(crate) fn verify_decoded_signature( + fn verify_decoded_signature( decoded: JwsValidationItem<'_>, public_key: &Jwk, signature_verifier: &S, diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index ee3b0f13ea..e1025a9fcd 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_did" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition = "2021" homepage.workspace = true @@ -11,10 +11,10 @@ repository.workspace = true description = "Agnostic implementation of the Decentralized Identifiers (DID) standard." [dependencies] -did_url_parser = { version = "0.3.0", features = ["std", "serde"] } +did_url_parser = { version = "0.2.0", features = ["std", "serde"] } form_urlencoded = { version = "1.2.0", default-features = false, features = ["alloc"] } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_jose = { version = "=1.5.0", path = "../identity_jose" } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_jose = { version = "=1.3.1", path = "../identity_jose" } serde.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/identity_did/src/did_url.rs b/identity_did/src/did_url.rs index 686d3b5b49..0e8eebcace 100644 --- a/identity_did/src/did_url.rs +++ b/identity_did/src/did_url.rs @@ -96,7 +96,7 @@ impl RelativeDIDUrl { self.path = value .filter(|s| !s.is_empty()) .map(|s| { - if s.starts_with('/') && is_valid_url_segment(s, is_char_path) { + if s.starts_with('/') && s.chars().all(is_char_path) { Ok(s.to_owned()) } else { Err(Error::InvalidPath) @@ -138,7 +138,7 @@ impl RelativeDIDUrl { .map(|mut s| { // Ignore leading '?' during validation. s = s.strip_prefix('?').unwrap_or(s); - if s.is_empty() || !is_valid_url_segment(s, is_char_query) { + if s.is_empty() || !s.chars().all(is_char_query) { return Err(Error::InvalidQuery); } Ok(format!("?{s}")) @@ -188,7 +188,7 @@ impl RelativeDIDUrl { .map(|mut s| { // Ignore leading '#' during validation. s = s.strip_prefix('#').unwrap_or(s); - if s.is_empty() || !is_valid_url_segment(s, is_char_fragment) { + if s.is_empty() || !s.chars().all(is_char_fragment) { return Err(Error::InvalidFragment); } Ok(format!("#{s}")) @@ -519,7 +519,8 @@ impl KeyComparable for DIDUrl { #[inline(always)] #[rustfmt::skip] pub(crate) const fn is_char_path(ch: char) -> bool { - is_char_method_id(ch) || matches!(ch, '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '@' | '/') + // Allow percent encoding or not? + is_char_method_id(ch) || matches!(ch, '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '@' | '/' /* | '%' */) } /// Checks whether a character satisfies DID Url query constraints. @@ -534,33 +535,6 @@ pub(crate) const fn is_char_fragment(ch: char) -> bool { is_char_path(ch) || ch == '?' } -pub(crate) fn is_valid_percent_encoded_char(s: &str) -> bool { - let mut chars = s.chars(); - let Some('%') = chars.next() else { return false }; - s.len() >= 3 && chars.take(2).all(|c| c.is_ascii_hexdigit()) -} - -pub(crate) fn is_valid_url_segment(segment: &str, char_predicate: F) -> bool -where - F: Fn(char) -> bool, -{ - let mut chars = segment.char_indices(); - while let Some((i, c)) = chars.next() { - if c == '%' { - if !is_valid_percent_encoded_char(&segment[i..]) { - return false; - } - // skip the two HEX digits - chars.next(); - chars.next(); - } else if !char_predicate(c) { - return false; - } - } - - true -} - #[cfg(test)] mod tests { use super::*; @@ -665,10 +639,6 @@ mod tests { assert!(relative_url.path().is_none()); assert!(relative_url.set_path(None).is_ok()); assert!(relative_url.path().is_none()); - - // Percent encoded path. - assert!(relative_url.set_path(Some("/p%AAth")).is_ok()); - assert_eq!(relative_url.path().unwrap(), "/p%AAth"); } #[rustfmt::skip] @@ -727,10 +697,6 @@ mod tests { assert_eq!(relative_url.query().unwrap(), "query"); assert!(relative_url.set_query(Some("name=value&name2=value2&3=true")).is_ok()); assert_eq!(relative_url.query().unwrap(), "name=value&name2=value2&3=true"); - - // With percent encoded char. - assert!(relative_url.set_query(Some("qu%EEry")).is_ok()); - assert_eq!(relative_url.query().unwrap(), "qu%EEry"); } #[rustfmt::skip] @@ -779,10 +745,6 @@ mod tests { assert!(relative_url.fragment().is_none()); assert!(relative_url.set_fragment(None).is_ok()); assert!(relative_url.fragment().is_none()); - - // Percent encoded fragment. - assert!(relative_url.set_fragment(Some("fr%AAgm%EEnt")).is_ok()); - assert_eq!(relative_url.fragment().unwrap(), "fr%AAgm%EEnt"); } #[rustfmt::skip] diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index 44bef94c6d..f87fc86c33 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_document" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,13 +8,14 @@ keywords = ["iota", "tangle", "identity", "did"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "Method-agnostic implementation of the Decentralized Identifiers (DID) standard." [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_did = { version = "=1.5.0", path = "../identity_did" } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_did = { version = "=1.3.1", path = "../identity_did" } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } serde.workspace = true strum.workspace = true diff --git a/identity_document/benches/deserialize_document.rs b/identity_document/benches/deserialize_document.rs index 0b5203b83c..1bd26ce870 100644 --- a/identity_document/benches/deserialize_document.rs +++ b/identity_document/benches/deserialize_document.rs @@ -220,9 +220,9 @@ fn deserialize_json_document(c: &mut Criterion) { (JSON_DOC_DID_KEY, "did:key document"), (JSON_DOCUMENT_LARGE, "large document"), ] { - group.throughput(Throughput::Bytes(json.len() as u64)); + group.throughput(Throughput::Bytes(json.as_bytes().len() as u64)); group.bench_with_input( - BenchmarkId::from_parameter(format!("{name}, document size: {} bytes", json.len())), + BenchmarkId::from_parameter(format!("{name}, document size: {} bytes", json.as_bytes().len())), json, |b, json| { b.iter(|| { diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 0c0a41a6c7..34c53b9044 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -696,7 +696,7 @@ impl CoreDocument { &'me self, method_query: Q, scope: Option, - ) -> Option<&'me VerificationMethod> + ) -> Option<&VerificationMethod> where Q: Into>, { @@ -779,7 +779,7 @@ impl CoreDocument { /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. // NOTE: This method demonstrates unexpected behavior in the edge cases where the document contains // services whose ids are of the form #. - pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&'me Service> + pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> where Q: Into>, { diff --git a/identity_document/src/utils/did_url_query.rs b/identity_document/src/utils/did_url_query.rs index d9399457e3..1af2b80b4c 100644 --- a/identity_document/src/utils/did_url_query.rs +++ b/identity_document/src/utils/did_url_query.rs @@ -13,7 +13,7 @@ use identity_did::DID; #[repr(transparent)] pub struct DIDUrlQuery<'query>(Cow<'query, str>); -impl DIDUrlQuery<'_> { +impl<'query> DIDUrlQuery<'query> { /// Returns whether this query matches the given DIDUrl. pub(crate) fn matches(&self, did_url: &DIDUrl) -> bool { // Ensure the DID matches if included in the query. @@ -81,7 +81,7 @@ impl<'query> From<&'query DIDUrl> for DIDUrlQuery<'query> { } } -impl From for DIDUrlQuery<'_> { +impl<'query> From for DIDUrlQuery<'query> { fn from(other: DIDUrl) -> Self { Self(Cow::Owned(other.to_string())) } diff --git a/identity_ecdsa_verifier/Cargo.toml b/identity_ecdsa_verifier/Cargo.toml index d9d7fca558..654b8aebe3 100644 --- a/identity_ecdsa_verifier/Cargo.toml +++ b/identity_ecdsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_ecdsa_verifier" -version = "1.5.0" +version = "1.3.1" authors = ["IOTA Stiftung", "Filancore GmbH"] edition.workspace = true homepage.workspace = true @@ -8,13 +8,14 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "JWS ECDSA signature verification for IOTA Identity" [lints] workspace = true [dependencies] -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } k256 = { version = "0.13.3", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } signature = { version = "2", default-features = false } diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index 8e4e7ee317..97308beebf 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_eddsa_verifier" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,10 +8,11 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.5.0", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["std"] } [features] diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 96841feb01..413cbf1547 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,17 +8,18 @@ keywords = ["iota", "tangle", "identity", "did", "ssi"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "Framework for Self-Sovereign Identity with IOTA DID." [dependencies] -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.5.0", path = "../identity_credential", features = ["validator"], default-features = false } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.5.0", path = "../identity_iota_core", default-features = false } -identity_resolver = { version = "=1.5.0", path = "../identity_resolver", default-features = false, optional = true } -identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["validator"], default-features = false } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false } +identity_resolver = { version = "=1.3.1", path = "../identity_resolver", default-features = false, optional = true } +identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["iota-document"] } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } [dev-dependencies] anyhow = "1.0.64" @@ -33,12 +34,13 @@ default = ["revocation-bitmap", "client", "iota-client", "resolver"] client = ["identity_iota_core/client"] # Enables the iota-client integration, the client trait implementations for it, and the `IotaClientExt` trait. -iota-client = ["identity_iota_core/iota-client", "identity_resolver/iota"] +iota-client = ["identity_iota_core/iota-client", "identity_resolver?/iota"] # Enables revocation with `RevocationBitmap2022`. revocation-bitmap = [ "identity_credential/revocation-bitmap", "identity_iota_core/revocation-bitmap", + "identity_resolver?/revocation-bitmap", ] # Enables revocation with `StatusList2021`. @@ -62,9 +64,6 @@ memstore = ["identity_storage/memstore"] # Enables selective disclosure features. sd-jwt = ["identity_credential/sd-jwt"] -# Enables selectively disclosable credentials. -sd-jwt-vc = ["identity_credential/sd-jwt-vc"] - # Enables zero knowledge selective disclosurable VCs jpt-bbs-plus = ["identity_storage/jpt-bbs-plus", "identity_credential/jpt-bbs-plus"] diff --git a/identity_iota/README.md b/identity_iota/README.md index 407314d782..e8001e6788 100644 --- a/identity_iota/README.md +++ b/identity_iota/README.md @@ -22,9 +22,6 @@ --- -> [!NOTE] -> This version of the library is compatible with IOTA Stardust networks, for a version of the library compatible with IOTA Rebased networks check [here](https://github.com/iotaledger/identity.rs/tree/feat/identity-rebased-alpha/) - ## Introduction IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. @@ -57,7 +54,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.5.0" } +identity_iota = { version = "1.3.1" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -91,7 +88,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.5.0", features = ["memstore"] } +identity_iota = { version = "1.3.1", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 2117a0867a..9ab2e53805 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -40,8 +40,6 @@ pub mod credential { pub use identity_credential::presentation::*; #[cfg(feature = "revocation-bitmap")] pub use identity_credential::revocation::*; - #[cfg(feature = "sd-jwt-vc")] - pub use identity_credential::sd_jwt_vc; pub use identity_credential::validator::*; } @@ -96,6 +94,7 @@ pub mod prelude { #[cfg_attr(docsrs, doc(cfg(feature = "resolver")))] pub mod resolver { //! DID resolution utilities + pub use identity_resolver::*; } @@ -129,8 +128,3 @@ pub mod sd_jwt_payload { //! Expose the selective disclosure crate. pub use identity_credential::sd_jwt_payload::*; } - -// Exposes the reworked version of the selective disclosure crate -// which is needed for selectively disclosable credentials. -#[cfg(feature = "sd-jwt-vc")] -pub use identity_credential::sd_jwt_v2 as sd_jwt_rework; diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index 34b637c1e4..f44a3ca27c 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota_core" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,16 +8,17 @@ keywords = ["iota", "tangle", "utxo", "shimmer", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "An IOTA Ledger integration for the IOTA DID Method." [dependencies] async-trait = { version = "0.1.56", default-features = false, optional = true } futures = { version = "0.3", default-features = false } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } iota-sdk = { version = "1.1.5", default-features = false, features = ["serde", "std"], optional = true } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["std"] } diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs index 169d6ddc21..45ba645fe8 100644 --- a/identity_iota_core/src/document/iota_document.rs +++ b/identity_iota_core/src/document/iota_document.rs @@ -332,7 +332,7 @@ impl IotaDocument { /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains // services whose ids are of the form #. - pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&'me Service> + pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> where Q: Into>, { @@ -347,7 +347,7 @@ impl IotaDocument { &'me self, method_query: Q, scope: Option, - ) -> Option<&'me VerificationMethod> + ) -> Option<&VerificationMethod> where Q: Into>, { diff --git a/identity_iota_core/src/state_metadata/document.rs b/identity_iota_core/src/state_metadata/document.rs index 197f9befb8..e14e381f5b 100644 --- a/identity_iota_core/src/state_metadata/document.rs +++ b/identity_iota_core/src/state_metadata/document.rs @@ -424,7 +424,10 @@ mod tests { // Encoding. assert_eq!(packed[4], StateMetadataEncoding::Json as u8); // JSON length. - assert_eq!(&packed[5..=6], (expected_payload.len() as u16).to_le_bytes().as_ref()); + assert_eq!( + &packed[5..=6], + (expected_payload.as_bytes().len() as u16).to_le_bytes().as_ref() + ); // JSON payload. assert_eq!(&packed[7..], expected_payload.as_bytes()); } diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 6a43429028..14061f3059 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_jose" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,13 +8,13 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true - -identity_core = { version = "=1.5.0", path = "../identity_core" } -iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha", "ed25519"] } +identity_core = { version = "=1.3.1", path = "../identity_core" } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha", "ed25519" ] } json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } diff --git a/identity_jose/src/jwk/key_set.rs b/identity_jose/src/jwk/key_set.rs index 22a629eaab..e1c9754a8a 100644 --- a/identity_jose/src/jwk/key_set.rs +++ b/identity_jose/src/jwk/key_set.rs @@ -14,6 +14,7 @@ use crate::jwk::Jwk; /// /// [More Info](https://tools.ietf.org/html/rfc7517#section-5) #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[repr(transparent)] pub struct JwkSet { /// An array of JWK values. /// diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 5605122cc7..7ef55d02a9 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -422,7 +422,7 @@ pub struct JwsValidationIter<'decoder, 'payload, 'signatures> { payload: &'payload [u8], } -impl<'payload> Iterator for JwsValidationIter<'_, 'payload, '_> { +impl<'decoder, 'payload, 'signatures> Iterator for JwsValidationIter<'decoder, 'payload, 'signatures> { type Item = Result>; fn next(&mut self) -> Option { diff --git a/identity_jose/src/jws/encoding/utils.rs b/identity_jose/src/jws/encoding/utils.rs index 2be2703488..b1d903e612 100644 --- a/identity_jose/src/jws/encoding/utils.rs +++ b/identity_jose/src/jws/encoding/utils.rs @@ -86,7 +86,7 @@ pub(super) struct Flatten<'payload, 'unprotected> { pub(super) signature: JwsSignature<'unprotected>, } -impl Flatten<'_, '_> { +impl<'payload, 'unprotected> Flatten<'payload, 'unprotected> { pub(super) fn to_json(&self) -> Result { serde_json::to_string(&self).map_err(Error::InvalidJson) } @@ -99,7 +99,7 @@ pub(super) struct General<'payload, 'unprotected> { pub(super) signatures: Vec>, } -impl General<'_, '_> { +impl<'payload, 'unprotected> General<'payload, 'unprotected> { pub(super) fn to_json(&self) -> Result { serde_json::to_string(&self).map_err(Error::InvalidJson) } diff --git a/identity_jose/src/jws/recipient.rs b/identity_jose/src/jws/recipient.rs index 96dd410fa0..602f1e6f3f 100644 --- a/identity_jose/src/jws/recipient.rs +++ b/identity_jose/src/jws/recipient.rs @@ -15,7 +15,7 @@ pub struct Recipient<'a> { pub unprotected: Option<&'a JwsHeader>, } -impl Default for Recipient<'_> { +impl<'a> Default for Recipient<'a> { fn default() -> Self { Self::new() } diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index a977c2b437..cf9cc8425f 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_resolver" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,16 +8,17 @@ keywords = ["iota", "did", "identity", "resolver", "resolution"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "DID Resolution utilities for the identity.rs library." [dependencies] # This is currently necessary for the ResolutionHandler trait. This can be made an optional dependency if alternative ways of attaching handlers are introduced. async-trait = { version = "0.1", default-features = false } futures = { version = "0.3" } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } @@ -25,7 +26,7 @@ reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tl [dependencies.identity_iota_core] -version = "=1.5.0" +version = "=1.3.1" path = "../identity_iota_core" default-features = false features = ["send-sync-client-ext", "iota-client"] diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 067b8ea857..ec4c73b807 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_storage" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,6 +8,7 @@ keywords = ["iota", "storage", "identity", "kms"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "Abstractions over storage for cryptographic keys used in DID Documents" [dependencies] @@ -15,13 +16,13 @@ anyhow = "1.0.82" async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } futures = { version = "0.3.27", default-features = false, features = ["async-await"] } -identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.5.0", path = "../identity_iota_core", default-features = false, optional = true } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } -iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "random", "sha"], optional = true } +identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false, optional = true } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default_features = false } +iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } seahash = { version = "4.1.0", default-features = false } @@ -32,8 +33,8 @@ tokio = { version = "1.29.0", default-features = false, features = ["macros", "s zkryptium = { workspace = true, optional = true } oqs = { workspace = true, optional = true } [dev-dependencies] -identity_credential = { version = "=1.5.0", path = "../identity_credential", features = ["revocation-bitmap"] } -identity_eddsa_verifier = { version = "=1.5.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } +identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } +identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index 098cc8bf20..d6b0825cba 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_stronghold" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -8,13 +8,14 @@ keywords = ["iota", "storage", "identity", "kms", "stronghold"] license.workspace = true readme = "./README.md" repository.workspace = true +rust-version.workspace = true description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } -identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false } +identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519"] } iota-sdk = { version = "1.1.5", default-features = false, features = ["client", "stronghold"] } iota_stronghold = { version = "2.1.0", default-features = false } @@ -27,8 +28,8 @@ zkryptium = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.82" bls12_381_plus = { workspace = true } -identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } -identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } +identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } +identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } json-proof-token = { workspace = true } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } zkryptium = { workspace = true } diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index c88aa86501..8990dd96e0 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "identity_verification" -version = "1.5.0" +version = "1.3.1" authors.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true +rust-version.workspace = true description = "Verification data types and functionality for identity.rs" [dependencies] -identity_core = { version = "=1.5.0", path = "./../identity_core" } -identity_did = { version = "=1.5.0", path = "./../identity_did", default-features = false } -identity_jose = { version = "=1.5.0", path = "./../identity_jose", default-features = false } +identity_core = { version = "=1.3.1", path = "./../identity_core" } +identity_did = { version = "=1.3.1", path = "./../identity_did", default-features = false } +identity_jose = { version = "=1.3.1", path = "./../identity_jose", default-features = false } serde.workspace = true serde_json.workspace = true strum.workspace = true From dc8337e027d510a30d6dbac68d04decc840d8ffe Mon Sep 17 00:00:00 2001 From: AleCla97 <66674252+AleCla97@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:05:50 +0100 Subject: [PATCH 124/163] Add createPresentationJpt bind --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 31 ++++++++++++++++++- bindings/wasm/src/iota/iota_document.rs | 5 ++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index a30fd3c5e3..d446f431d7 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -33,7 +33,11 @@ use identity_iota::credential::Presentation; use crate::credential::UnknownCredential; use identity_iota::credential::JwtPresentationOptions; use crate::credential::WasmJwt; - +use identity_iota::storage::JwpDocumentExt; +use crate::credential::WasmJpt; +use crate::credential::PromiseJpt; +use crate::credential::WasmJwpPresentationOptions; +use crate::jpt::WasmSelectiveDisclosurePresentation; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { @@ -222,5 +226,30 @@ impl WasmCoreDocument { Ok(promise.unchecked_into()) } + #[wasm_bindgen(js_name = createPresentationJpt)] + pub fn create_presentation_jpt( + &self, + presentation: WasmSelectiveDisclosurePresentation, + method_id: String, + options: WasmJwpPresentationOptions, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let options = options.try_into()?; + let promise: Promise = future_to_promise(async move { + let mut presentation = presentation.0; + let jpt = document_lock_clone + .write() + .await + .create_presentation_jpt(&mut presentation, method_id.as_str(), &options) + .await + .map(WasmJpt) + .wasm_result()?; + Ok(JsValue::from(jpt)) + }); + + Ok(promise.unchecked_into()) + } + + } diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index 4712fca44a..9ff047db38 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ use std::rc::Rc; @@ -1010,7 +1013,7 @@ impl WasmIotaDocument { let document_lock_clone: Rc = self.0.clone(); let storage_clone: Rc = storage.0.clone(); let scope: MethodScope = scope.0; - web_sys::console::log_1(&"PPPPPPPPPPPPPPP".into()); + let promise: Promise = future_to_promise(async move { let method_fragment: String = document_lock_clone .write() From 7e29b7de3ad9fb23f20e1c353df9e3925564fe70 Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Wed, 19 Feb 2025 16:19:16 +0100 Subject: [PATCH 125/163] remove pq from default features --- identity_storage/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index ec4c73b807..7d8b50416f 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -39,7 +39,7 @@ once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } [features] -default = ["iota-document", "memstore", "hybrid-liboqs"] +default = ["iota-document", "memstore"] # Exposes in-memory implementations of the storage traits intended exclusively for testing. memstore = ["dep:tokio", "dep:rand", "dep:iota-crypto"] # Enables `Send` + `Sync` bounds for the storage traits. From 046ab9b093ff6c3aa5e97346404ec2c85853a574 Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Wed, 19 Feb 2025 17:32:44 +0100 Subject: [PATCH 126/163] update storage --- identity_storage/Cargo.toml | 2 +- .../src/storage/did_jwk_document_ext.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 7d8b50416f..02e3c1746f 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -22,7 +22,7 @@ identity_did = { version = "=1.3.1", path = "../identity_did", default-features identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false, optional = true } identity_verification = { version = "=1.3.1", path = "../identity_verification", default_features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha"], optional = true } +iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha", "random"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } seahash = { version = "4.1.0", default-features = false } diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index abc27c704c..48382c2b30 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -4,10 +4,16 @@ use identity_did::{DIDCompositeJwk, DIDJwk}; use identity_document::document::CoreDocument; use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json}; -use jsonprooftoken::jpa::algs::ProofAlgorithm; use async_trait::async_trait; +#[cfg(feature = "jpt-bbs-plus")] +use jsonprooftoken::jpa::algs::ProofAlgorithm; + -use crate::{JwkGenOutput, JwkStorage, JwkStorageBbsPlusExt, JwkStorageDocumentError as Error, JwkStoragePQ, KeyId, KeyIdStorage, KeyType, MethodDigest}; +use crate::{JwkGenOutput, JwkStorage, JwkStorageDocumentError as Error, KeyId, KeyIdStorage, KeyType, MethodDigest}; +#[cfg(feature = "pqc")] +use crate::JwkStoragePQ; +#[cfg(feature = "hybrid")] +use crate::JwkStorageBbsPlusExt; use super::{Storage, StorageResult}; @@ -101,6 +107,7 @@ impl DidJwkDocumentExt for CoreDocument { Ok((document, fragment.to_string())) } + #[cfg(feature = "pqc")] async fn new_did_jwk_pqc( storage: &Storage, @@ -144,6 +151,7 @@ impl DidJwkDocumentExt for CoreDocument { Ok((document, fragment.to_string())) } + #[cfg(feature = "jpt-bbs-plus")] async fn new_did_jwk_zk( storage: &Storage, key_type: KeyType, @@ -184,6 +192,7 @@ impl DidJwkDocumentExt for CoreDocument { Ok((document, fragment.to_string())) } + #[cfg(feature = "hybrid")] async fn new_did_compositejwk( storage: &Storage, alg: CompositeAlgId, From 1bad5452536a7ef486357035c635e5f2c48d80bf Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Thu, 20 Feb 2025 11:02:39 +0100 Subject: [PATCH 127/163] fix feature for bbs trait and remove warnings --- .../src/storage/did_jwk_document_ext.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 48382c2b30..e2718b67e5 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -1,19 +1,23 @@ // Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 -use identity_did::{DIDCompositeJwk, DIDJwk}; +use identity_did::DIDJwk; use identity_document::document::CoreDocument; -use identity_verification::{jwk::{CompositeAlgId, CompositeJwk}, jws::JwsAlgorithm, jwu::encode_b64_json}; +use identity_verification::{jws::JwsAlgorithm, jwu::encode_b64_json}; use async_trait::async_trait; #[cfg(feature = "jpt-bbs-plus")] use jsonprooftoken::jpa::algs::ProofAlgorithm; -use crate::{JwkGenOutput, JwkStorage, JwkStorageDocumentError as Error, KeyId, KeyIdStorage, KeyType, MethodDigest}; +use crate::{JwkGenOutput, JwkStorage, JwkStorageDocumentError as Error, KeyIdStorage, KeyType, MethodDigest}; #[cfg(feature = "pqc")] -use crate::JwkStoragePQ; -#[cfg(feature = "hybrid")] +use crate::{JwkStoragePQ, KeyId}; +#[cfg(feature = "jpt-bbs-plus")] use crate::JwkStorageBbsPlusExt; +#[cfg(feature = "hybrid")] +use identity_verification::jwk::{CompositeAlgId, CompositeJwk}; +#[cfg(feature = "hybrid")] +use identity_did::DIDCompositeJwk; use super::{Storage, StorageResult}; From a1996925948367e2ea2ba1499ba0ac7097852b7a Mon Sep 17 00:00:00 2001 From: Michele Festa Date: Thu, 20 Feb 2025 15:13:49 +0100 Subject: [PATCH 128/163] Move import --- identity_storage/src/storage/did_jwk_document_ext.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index e2718b67e5..74e8adcf8f 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -11,7 +11,7 @@ use jsonprooftoken::jpa::algs::ProofAlgorithm; use crate::{JwkGenOutput, JwkStorage, JwkStorageDocumentError as Error, KeyIdStorage, KeyType, MethodDigest}; #[cfg(feature = "pqc")] -use crate::{JwkStoragePQ, KeyId}; +use crate::JwkStoragePQ; #[cfg(feature = "jpt-bbs-plus")] use crate::JwkStorageBbsPlusExt; #[cfg(feature = "hybrid")] @@ -205,6 +205,9 @@ impl DidJwkDocumentExt for CoreDocument { K: JwkStorage + JwkStoragePQ, I: KeyIdStorage { + + use crate::KeyId; + let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { CompositeAlgId::IdMldsa44Ed25519Sha512 => ( KeyType::from_static_str("ML-DSA"), From c0b2defabbd37d9b7f06c297e73c5a077c7d2c83 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Tue, 1 Apr 2025 12:41:37 +0200 Subject: [PATCH 129/163] Remove unwanted changes --- CHANGELOG.md | 32 +- README.md | 268 +- bindings/wasm/Cargo.toml | 2 +- bindings/wasm/docs/api-reference.md | 7693 ----------------- bindings/wasm/src/sd_jwt_vc/builder.rs | 134 + bindings/wasm/src/sd_jwt_vc/claims.rs | 25 + bindings/wasm/src/sd_jwt_vc/metadata/claim.rs | 75 + .../wasm/src/sd_jwt_vc/metadata/issuer.rs | 63 + bindings/wasm/src/sd_jwt_vc/metadata/mod.rs | 10 + .../wasm/src/sd_jwt_vc/metadata/vc_type.rs | 84 + bindings/wasm/src/sd_jwt_vc/mod.rs | 17 + bindings/wasm/src/sd_jwt_vc/presentation.rs | 53 + bindings/wasm/src/sd_jwt_vc/resolver.rs | 74 + .../wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs | 85 + .../src/sd_jwt_vc/sd_jwt_v2/disclosure.rs | 64 + .../wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs | 74 + .../wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs | 139 + bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs | 16 + .../wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs | 150 + .../wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs | 53 + bindings/wasm/src/sd_jwt_vc/status.rs | 20 + bindings/wasm/src/sd_jwt_vc/token.rs | 172 + examples/Cargo.toml | 2 +- identity_core/Cargo.toml | 2 +- identity_credential/Cargo.toml | 26 +- identity_credential/src/error.rs | 5 + identity_credential/src/lib.rs | 7 + identity_credential/src/sd_jwt_vc/builder.rs | 386 + identity_credential/src/sd_jwt_vc/claims.rs | 217 + identity_credential/src/sd_jwt_vc/error.rs | 57 + .../src/sd_jwt_vc/metadata/claim.rs | 286 + .../src/sd_jwt_vc/metadata/display.rs | 23 + .../src/sd_jwt_vc/metadata/integrity.rs | 121 + .../src/sd_jwt_vc/metadata/issuer.rs | 94 + .../src/sd_jwt_vc/metadata/mod.rs | 14 + .../src/sd_jwt_vc/metadata/vc_type.rs | 268 + identity_credential/src/sd_jwt_vc/mod.rs | 25 + .../src/sd_jwt_vc/presentation.rs | 54 + identity_credential/src/sd_jwt_vc/resolver.rs | 29 + identity_credential/src/sd_jwt_vc/status.rs | 52 + .../src/sd_jwt_vc/tests/mod.rs | 113 + .../src/sd_jwt_vc/tests/validation.rs | 172 + identity_credential/src/sd_jwt_vc/token.rs | 476 + identity_did/Cargo.toml | 6 +- identity_document/Cargo.toml | 8 +- identity_ecdsa_verifier/Cargo.toml | 4 +- identity_eddsa_verifier/Cargo.toml | 4 +- identity_iota/Cargo.toml | 18 +- identity_iota/src/lib.rs | 7 + identity_iota_core/Cargo.toml | 12 +- identity_jose/Cargo.toml | 4 +- identity_jose/src/jwk/composite_jwk.rs | 2 +- identity_jose/src/jws/algorithm.rs | 19 + identity_pqc_verifier/Cargo.toml | 4 +- identity_resolver/Cargo.toml | 12 +- identity_storage/Cargo.toml | 18 +- identity_stronghold/Cargo.toml | 10 +- identity_verification/Cargo.toml | 8 +- 58 files changed, 4075 insertions(+), 7793 deletions(-) delete mode 100644 bindings/wasm/docs/api-reference.md create mode 100644 bindings/wasm/src/sd_jwt_vc/builder.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/claims.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/claim.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/mod.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/mod.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/presentation.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/resolver.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/status.rs create mode 100644 bindings/wasm/src/sd_jwt_vc/token.rs create mode 100644 identity_credential/src/sd_jwt_vc/builder.rs create mode 100644 identity_credential/src/sd_jwt_vc/claims.rs create mode 100644 identity_credential/src/sd_jwt_vc/error.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/claim.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/display.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/integrity.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/issuer.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/mod.rs create mode 100644 identity_credential/src/sd_jwt_vc/metadata/vc_type.rs create mode 100644 identity_credential/src/sd_jwt_vc/mod.rs create mode 100644 identity_credential/src/sd_jwt_vc/presentation.rs create mode 100644 identity_credential/src/sd_jwt_vc/resolver.rs create mode 100644 identity_credential/src/sd_jwt_vc/status.rs create mode 100644 identity_credential/src/sd_jwt_vc/tests/mod.rs create mode 100644 identity_credential/src/sd_jwt_vc/tests/validation.rs create mode 100644 identity_credential/src/sd_jwt_vc/token.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d3fa510e..f51e2936c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## [v1.5.0](https://github.com/iotaledger/identity.rs/tree/v1.5.0) (2025-01-20) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.4.0...v1.5.0) + +### Added + +- SD-JWT VC implementation [\#1413](https://github.com/iotaledger/identity.rs/pull/1413) + +### Patch + +- Support %-encoded characters in DID URL [\#1496](https://github.com/iotaledger/identity.rs/pull/1496) +- fix: serialization of status list [\#1423](https://github.com/iotaledger/identity.rs/pull/1423) + +## [v1.4.0](https://github.com/iotaledger/identity.rs/tree/v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.1...v1.4.0) + +### Added + +- Add support for custom JWS algorithms [\#1410](https://github.com/iotaledger/identity.rs/pull/1410) +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) +- Add feature to support custom `now_utc` implementations [\#1397](https://github.com/iotaledger/identity.rs/pull/1397) + +### Patch + +- Remove dependency on `identity_core` default features [\#1408](https://github.com/iotaledger/identity.rs/pull/1408) +- Mark `js-sys` as optional for identity\_core [\#1405](https://github.com/iotaledger/identity.rs/pull/1405) +- Make `bls12_381_plus` dependency more flexible again [\#1393](https://github.com/iotaledger/identity.rs/pull/1393) + ## [v1.3.1](https://github.com/iotaledger/identity.rs/tree/v1.3.1) (2024-06-12) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.0...v1.3.1) @@ -8,8 +38,6 @@ - Pin and bump `bls12_381_plus` dependency [\#1378](https://github.com/iotaledger/identity.rs/pull/1378) -# Changelog - ## [v1.3.0](https://github.com/iotaledger/identity.rs/tree/v1.3.0) (2024-05-28) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.2.0...v1.3.0) diff --git a/README.md b/README.md index 585740fbde..407314d782 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,251 @@ -# Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid signatures for VCs -This repository extends IOTA Identity by implementing both pure **Post-Quantum (PQ)** and **Post-Quantum/Traditional (PQ/T) hybrid** signatures and JWT encoding for VCs with a crypto-agility approach. +![banner](https://github.com/iotaledger/identity.rs/raw/HEAD/.github/banner_identity.svg) -### Overview +

+ StackExchange + Discord + Discord + Apache 2.0 license + Dependencies + Coverage Status -1. **PQ Signatures**: IOTA Identity extends its support for selected PQ signature algorithms, such as [ML-DSA](https://csrc.nist.gov/pubs/fips/204/final), [SLH-DSA](https://csrc.nist.gov/pubs/fips/205/final) and [FALCON](https://falcon-sign.info/). The implementation of these algorithms is provided by [liboqs](https://github.com/open-quantum-safe/liboqs-rust). +

-2. **PQ/T hybrid Signatures**: to mitigate risks associated with the relative immaturity of Post-Quantum Cryptography (PQC), the IOTA Identity also extends its support for PQ/T hybrid signatures. The hybrid scheme combines a PQ signature with a Traditional signature in a single composite signature. This ensures secure authentication, even if one of the two algorithms becomes compromised. The PQ/T hybrid signature requires a PQ/T hybrid key pair; the PQ/T hybrid public key is handled using the newly introduced [verification material property](https://www.w3.org/TR/did-core/#verification-material) type called `compositeJwk`, which stores both types of public keys within the DID document. This setup enforces the `Weak Non-Separability` (WSN) property of signatures, protecting against stripping attack. +

+ Introduction â—ˆ + Bindings â—ˆ + Documentation & Resources â—ˆ + Getting Started â—ˆ + Example â—ˆ + Roadmap â—ˆ + Contributing +

-```json -"compositeJwk": { - "algId": ".. composite key OID ..", - "pqPublicKey": { - ".. PQ JWK encoded key .." - }, - "traditionalPublicKey": { - ".. Traditional JWK encoded key .." - } -} +--- + +> [!NOTE] +> This version of the library is compatible with IOTA Stardust networks, for a version of the library compatible with IOTA Rebased networks check [here](https://github.com/iotaledger/identity.rs/tree/feat/identity-rebased-alpha/) + +## Introduction + +IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. + +## Bindings + +[Foreign Function Interface (FFI)](https://en.wikipedia.org/wiki/Foreign_function_interface) Bindings of this [Rust](https://www.rust-lang.org/) library to other programming languages: + +- [Web Assembly](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/wasm/) (JavaScript/TypeScript) + +## gRPC + +We provide a collection of experimental [gRPC services](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/grpc/) +## Documentation and Resources + +- API References: + - [Rust API Reference](https://docs.rs/identity_iota/latest/identity_iota/): Package documentation (cargo docs). + - [Wasm API Reference](https://wiki.iota.org/identity.rs/libraries/wasm/api_reference/): Wasm Package documentation. +- [Identity Documentation Pages](https://wiki.iota.org/identity.rs/introduction): Supplementing documentation with context around identity and simple examples on library usage. +- [Examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples): Practical code snippets to get you started with the library. + +## Prerequisites + +- [Rust](https://www.rust-lang.org/) (>= 1.65) +- [Cargo](https://doc.rust-lang.org/cargo/) (>= 1.65) + +## Getting Started + +If you want to include IOTA Identity in your project, simply add it as a dependency in your `Cargo.toml`: + +```toml +[dependencies] +identity_iota = { version = "1.5.0" } ``` -**Supported Algorithms**: Currently, the implmentation supports **id-MLDSA44-Ed25519-SHA512** and **id-MLDSA65-Ed25519-SHA512** algorithms. The first combines ML-DSA-44 with Ed25519 signatures, while the second combines ML-DSA-65 with Ed25519 signatures. +To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: + +1. Clone the repository, e.g. through `git clone https://github.com/iotaledger/identity.rs` +2. Start IOTA Sandbox as described in the [next section](#example-creating-an-identity) +3. Run the example to create a DID using `cargo run --release --example 0_create_did` + +## Example: Creating an Identity -# did:compositejwk +The following code creates and publishes a new IOTA DID Document to a locally running private network. +See the [instructions](https://github.com/iotaledger/iota-sandbox) on running your own private network for development. + +_Cargo.toml_ + + + + +```toml +[package] +name = "iota_identity_example" +version = "1.0.0" +edition = "2021" + +[dependencies] +identity_iota = { version = "1.5.0", features = ["memstore"] } +iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } +tokio = { version = "1", features = ["full"] } +anyhow = "1.0.62" +rand = "0.8.5" +``` -The transition to PQC is a delicate and lengthy process. Today, the Distributed Ledger Technologies (DLT) that underpin decentralised identity are not yet quantum-secure, so this repository extends the IOTA Identity library with a new DID method called `did:compositejwk` for Holders to use PQ/T hybrid signatures. Refer to [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) specification for the details. +_main.__rs_ -**Note**: this repository also extends the existing `did:jwk` method to deal with pure PQ keys and signatures (ML-DSA, SLH-DSA and FALCON), and adds a simple `did:web` method for the Issuers. + + + + +```rust,no_run +use identity_iota::core::ToJson; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::Storage; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use iota_sdk::client::api::GetAddressesOptions; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::crypto::keys::bip39; +use iota_sdk::types::block::address::Bech32Address; +use iota_sdk::types::block::output::AliasOutput; +use iota_sdk::types::block::output::dto::AliasOutputDto; +use tokio::io::AsyncReadExt; + +// The endpoint of the IOTA node to use. +static API_ENDPOINT: &str = "http://localhost"; + +/// Demonstrates how to create a DID Document and publish it in a new Alias Output. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + // Create a new Stronghold. + let stronghold = StrongholdSecretManager::builder() + .password("secure_password".to_owned()) + .build("./example-strong.hodl")?; + + // Generate a mnemonic and store it in the Stronghold. + let random: [u8; 32] = rand::random(); + let mnemonic = + bip39::wordlist::encode(random.as_ref(), &bip39::wordlist::ENGLISH).map_err(|err| anyhow::anyhow!("{err:?}"))?; + stronghold.store_mnemonic(mnemonic).await?; + + // Create a new secret manager backed by the Stronghold. + let secret_manager: SecretManager = SecretManager::Stronghold(stronghold); + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Get an address from the secret manager. + let address: Bech32Address = secret_manager + .generate_ed25519_addresses( + GetAddressesOptions::default() + .with_range(0..1) + .with_bech32_hrp((&network_name).try_into()?), + ) + .await?[0]; + + println!("Your wallet address is: {}", address); + println!("Please request funds from http://localhost/faucet/, wait for a couple of seconds and then press Enter."); + tokio::io::stdin().read_u8().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // Insert a new Ed25519 verification method in the DID document. + let storage: Storage = Storage::new(JwkMemStore::new(), KeyIdMemstore::new()); + document + .generate_method( + &storage, + JwkMemStore::ED25519_KEY_TYPE, + JwsAlgorithm::EdDSA, + None, + MethodScope::VerificationMethod, + ) + .await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address.into(), document, None).await?; + println!("Alias Output: {}", AliasOutputDto::from(&alias_output).to_json_pretty()?); + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + println!("Published DID document: {:#}", document); + + Ok(()) +} +``` + +_Example output_ + +```json +{ + "doc": { + "id": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d", + "verificationMethod": [ + { + "id": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d#9KdQCWcvR8kmGPLFOYnTzypsDWsoUIvR", + "controller": "did:iota:tst:0xa947df036e78c2eada8b16e019d517c9e38d4b19cb0c1fa066e752c3074b715d", + "type": "JsonWebKey", + "publicKeyJwk": { + "kty": "OKP", + "alg": "EdDSA", + "kid": "9KdQCWcvR8kmGPLFOYnTzypsDWsoUIvR", + "crv": "Ed25519", + "x": "JJoYoeFWU7jWvdQmOKDvM4nZJ2cUbP9yhWZzFgd044I" + } + } + ] + }, + "meta": { + "created": "2023-08-29T14:47:26Z", + "updated": "2023-08-29T14:47:26Z", + "governorAddress": "tst1qqd7kyu8xadzx9vutznu72336npqpj92jtp27uyu2tj2sa5hx6n3k0vrzwv", + "stateControllerAddress": "tst1qqd7kyu8xadzx9vutznu72336npqpj92jtp27uyu2tj2sa5hx6n3k0vrzwv" + } +} +``` -# Examples +## Roadmap and Milestones -To test all the above quantum-secure functionalities, refer to practical PQC examples available in the [example](https://github.com/Cybersecurity-LINKS/pq-zk-identity/tree/PQ/T-Hybrid/examples) directory. -> **Note**: The examples in the `example/2_pqc` directory are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. -> To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/2_pqc/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: -> ```rust -> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; -> ``` -Make sure your server is set up before running the examples to avoid any configuration issues. +For detailed development progress, see the IOTA Identity development [kanban board](https://github.com/orgs/iotaledger/projects/8/views/5). -# Zero-Knowledge (ZK) +## Contributing -The IOTA Identity already supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components: +We would love to have you help us with the development of IOTA Identity. Each and every contribution is greatly valued! -* **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure. -* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure. +Please review the [contribution](https://wiki.iota.org/identity.rs/contribute) and [workflow](https://wiki.iota.org/identity.rs/workflow) sections in the [IOTA Wiki](https://wiki.iota.org/). -**Note**: the BBS+ signature scheme uses traditional cryptography, hence it is not quantum-secure; for more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/). +To contribute directly to the repository, simply fork the project, push your changes to your fork and create a pull request to get them included! +The best place to get involved in discussions about this library or to look for support at is the `#identity` channel on the [IOTA Discord](https://discord.iota.org). You can also ask questions on our [Stack Exchange](https://iota.stackexchange.com/). diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index a5208ec94b..91153ccbeb 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_wasm" -version = "1.3.1" +version = "1.5.0" authors = ["IOTA Stiftung"] edition = "2021" homepage = "https://www.iota.org" diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md deleted file mode 100644 index f4e2d64d91..0000000000 --- a/bindings/wasm/docs/api-reference.md +++ /dev/null @@ -1,7693 +0,0 @@ -## Classes - -
-
CoreDID
-

A method-agnostic Decentralized Identifier (DID).

-
-
CoreDocument
-

A method-agnostic DID Document.

-

Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified.

-
-
Credential
-
-
CustomMethodData
-

A custom verification method data format.

-
-
DIDUrl
-

A method agnostic DID Url.

-
-
DecodedJptCredential
-
-
DecodedJptPresentation
-
-
DecodedJws
-

A cryptographically verified decoded token from a JWS.

-

Contains the decoded headers and the raw claims.

-
-
DecodedJwtCredential
-

A cryptographically verified and decoded Credential.

-

Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the credential itself.

-
-
DecodedJwtPresentation
-

A cryptographically verified and decoded presentation.

-

Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the presentation itself.

-
-
Disclosure
-

Represents an elements constructing a disclosure. -Object properties and array elements disclosures are supported.

-

See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures

-
-
DomainLinkageConfiguration
-

DID Configuration Resource which contains Domain Linkage Credentials. -It can be placed in an origin's .well-known directory to prove linkage between the origin and a DID. -See: https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource

-

Note:

- -
-
Duration
-

A span of time.

-
-
EdDSAJwsVerifier
-

An implementor of IJwsVerifier that can handle the -EdDSA algorithm.

-
-
IotaDID
-

A DID conforming to the IOTA DID method specification.

-
-
IotaDocument
-

A DID Document adhering to the IOTA DID method specification.

-

Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified.

-
-
IotaDocumentMetadata
-

Additional attributes related to an IOTA DID Document.

-
-
IotaIdentityClientExt
-

An extension interface that provides helper functions for publication -and resolution of DID documents in Alias Outputs.

-
-
IssuerProtectedHeader
-
-
Jpt
-

A JSON Proof Token (JPT).

-
-
JptCredentialValidationOptions
-

Options to declare validation criteria for Jpt.

-
-
JptCredentialValidator
-
-
JptCredentialValidatorUtils
-

Utility functions for validating JPT credentials.

-
-
JptPresentationValidationOptions
-

Options to declare validation criteria for a Jpt presentation.

-
-
JptPresentationValidator
-
-
JptPresentationValidatorUtils
-

Utility functions for verifying JPT presentations.

-
-
Jwk
-
-
JwkGenOutput
-

The result of a key generation in JwkStorage.

-
-
JwpCredentialOptions
-
-
JwpIssued
-
-
JwpPresentationOptions
-

Options to be set in the JWT claims of a verifiable presentation.

-
-
JwpVerificationOptions
-
-
Jws
-

A wrapper around a JSON Web Signature (JWS).

-
-
JwsHeader
-
-
JwsSignatureOptions
-
-
JwsVerificationOptions
-
-
Jwt
-

A wrapper around a JSON Web Token (JWK).

-
-
JwtCredentialValidationOptions
-

Options to declare validation criteria when validating credentials.

-
-
JwtCredentialValidator
-

A type for decoding and validating Credential.

-
-
JwtDomainLinkageValidator
-

A validator for a Domain Linkage Configuration and Credentials.

-
-
JwtPresentationOptions
-
-
JwtPresentationValidationOptions
-

Options to declare validation criteria when validating presentation.

-
-
JwtPresentationValidator
-
-
KeyBindingJWTValidationOptions
-

Options to declare validation criteria when validating credentials.

-
-
KeyBindingJwtClaims
-

Claims set for key binding JWT.

-
-
LinkedDomainService
-
-
MethodData
-

Supported verification method data formats.

-
-
MethodDigest
-

Unique identifier of a VerificationMethod.

-

NOTE: -This class does not have a JSON representation, -use the methods pack and unpack instead.

-
-
MethodScope
-

Supported verification method types.

-
-
MethodType
-

Supported verification method types.

-
-
PayloadEntry
-
-
Payloads
-
-
Presentation
-
-
PresentationProtectedHeader
-
-
Proof
-

Represents a cryptographic proof that can be used to validate verifiable credentials and -presentations.

-

This representation does not inherently implement any standard; instead, it -can be utilized to implement standards or user-defined proofs. The presence of the -type field is necessary to accommodate different types of cryptographic proofs.

-

Note that this proof is not related to JWT and can be used in combination or as an alternative -to it.

-
-
ProofUpdateCtx
-
-
Resolver
-

Convenience type for resolving DID documents from different DID methods.

-

Also provides methods for resolving DID Documents associated with -verifiable Credentials and Presentations.

-

Configuration

-

The resolver will only be able to resolve DID documents for methods it has been configured for in the constructor.

-
-
RevocationBitmap
-

A compressed bitmap for managing credential revocation.

-
-
RevocationTimeframeStatus
-

Information used to determine the current status of a Credential.

-
-
SdJwt
-

Representation of an SD-JWT of the format -<Issuer-signed JWT>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>~<optional KB-JWT>.

-
-
SdJwtCredentialValidator
-

A type for decoding and validating Credential.

-
-
SdObjectDecoder
-

Substitutes digests in an SD-JWT object by their corresponding plaintext values provided by disclosures.

-
-
SdObjectEncoder
-

Transforms a JSON object into an SD-JWT object by substituting selected values -with their corresponding disclosure digests.

-

Note: digests are created using the sha-256 algorithm.

-
-
SelectiveDisclosurePresentation
-

Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes

-
    -
  • @context MUST NOT be blinded
  • -
  • id MUST be blinded
  • -
  • type MUST NOT be blinded
  • -
  • issuer MUST NOT be blinded
  • -
  • issuanceDate MUST be blinded (if Timeframe Revocation mechanism is used)
  • -
  • expirationDate MUST be blinded (if Timeframe Revocation mechanism is used)
  • -
  • credentialSubject (User have to choose which attribute must be blinded)
  • -
  • credentialSchema MUST NOT be blinded
  • -
  • credentialStatus MUST NOT be blinded
  • -
  • refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism)
  • -
  • termsOfUse NO reason to use it in ZK VC (will be in any case blinded)
  • -
  • evidence (User have to choose which attribute must be blinded)
  • -
-
-
Service
-

A DID Document Service used to enable trusted interactions associated with a DID subject.

-
-
StatusList2021
-

StatusList2021 data structure as described in W3C's VC status list 2021.

-
-
StatusList2021Credential
-

A parsed StatusList2021Credential.

-
-
StatusList2021CredentialBuilder
-

Builder type to construct valid StatusList2021Credential istances.

-
-
StatusList2021Entry
-

StatusList2021Entry implementation.

-
-
Storage
-

A type wrapping a JwkStorage and KeyIdStorage that should always be used together when -working with storage backed DID documents.

-
-
Timestamp
-
-
UnknownCredential
-
-
VerificationMethod
-

A DID Document Verification Method.

-
-
- -## Members - -
-
FailFast
-

Declares when validation should return if an error occurs.

-
-
AllErrors
-

Return all errors that occur during validation.

-
-
FirstError
-

Return after the first error occurs.

-
-
PayloadType
-
-
SubjectHolderRelationship
-

Declares how credential subjects must relate to the presentation holder.

-

See also the Subject-Holder Relationship section of the specification.

-
-
AlwaysSubject
-

The holder must always match the subject on all credentials, regardless of their nonTransferable property. -This variant is the default.

-
-
SubjectOnNonTransferable
-

The holder must match the subject only for credentials where the nonTransferable property is true.

-
-
Any
-

The holder is not required to have any kind of relationship to any credential subject.

-
-
StatusPurpose
-

Purpose of a StatusList2021.

-
-
MethodRelationship
-
-
PresentationProofAlgorithm
-
-
CredentialStatus
-
-
StatusCheck
-

Controls validation behaviour when checking whether or not a credential has been revoked by its -credentialStatus.

-
-
Strict
-

Validate the status if supported, reject any unsupported -credentialStatus types.

-

Only RevocationBitmap2022 is currently supported.

-

This is the default.

-
-
SkipUnsupported
-

Validate the status if supported, skip any unsupported -credentialStatus types.

-
-
SkipAll
-

Skip all status checks.

-
-
SerializationType
-
-
ProofAlgorithm
-
-
StateMetadataEncoding
-
-
- -## Functions - -
-
encodeB64(data) ⇒ string
-

Encode the given bytes in url-safe base64.

-
-
decodeB64(data) ⇒ Uint8Array
-

Decode the given url-safe base64-encoded slice into its raw bytes.

-
-
start()
-

Initializes the console error panic hook for better error messages

-
-
verifyEd25519(alg, signingInput, decodedSignature, publicKey)
-

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

-

This function is useful when one is composing a IJwsVerifier that delegates -EdDSA verification with curve Ed25519 to this function.

-

Warning

-

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this -prior to calling the function.

-
-
- - - -## CoreDID -A method-agnostic Decentralized Identifier (DID). - -**Kind**: global class - -* [CoreDID](#CoreDID) - * _instance_ - * [.setMethodName(value)](#CoreDID+setMethodName) - * [.setMethodId(value)](#CoreDID+setMethodId) - * [.scheme()](#CoreDID+scheme) ⇒ string - * [.authority()](#CoreDID+authority) ⇒ string - * [.method()](#CoreDID+method) ⇒ string - * [.methodId()](#CoreDID+methodId) ⇒ string - * [.join(segment)](#CoreDID+join) ⇒ [DIDUrl](#DIDUrl) - * [.toUrl()](#CoreDID+toUrl) ⇒ [DIDUrl](#DIDUrl) - * [.intoUrl()](#CoreDID+intoUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#CoreDID+toString) ⇒ string - * [.toCoreDid()](#CoreDID+toCoreDid) ⇒ [CoreDID](#CoreDID) - * [.toJSON()](#CoreDID+toJSON) ⇒ any - * [.clone()](#CoreDID+clone) ⇒ [CoreDID](#CoreDID) - * _static_ - * [.parse(input)](#CoreDID.parse) ⇒ [CoreDID](#CoreDID) - * [.validMethodName(value)](#CoreDID.validMethodName) ⇒ boolean - * [.validMethodId(value)](#CoreDID.validMethodId) ⇒ boolean - * [.fromJSON(json)](#CoreDID.fromJSON) ⇒ [CoreDID](#CoreDID) - - - -### coreDID.setMethodName(value) -Set the method name of the [CoreDID](#CoreDID). - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### coreDID.setMethodId(value) -Set the method-specific-id of the `DID`. - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### coreDID.scheme() ⇒ string -Returns the [CoreDID](#CoreDID) scheme. - -E.g. -- `"did:example:12345678" -> "did"` -- `"did:iota:smr:12345678" -> "did"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.authority() ⇒ string -Returns the [CoreDID](#CoreDID) authority: the method name and method-id. - -E.g. -- `"did:example:12345678" -> "example:12345678"` -- `"did:iota:smr:12345678" -> "iota:smr:12345678"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.method() ⇒ string -Returns the [CoreDID](#CoreDID) method name. - -E.g. -- `"did:example:12345678" -> "example"` -- `"did:iota:smr:12345678" -> "iota"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.methodId() ⇒ string -Returns the [CoreDID](#CoreDID) method-specific ID. - -E.g. -- `"did:example:12345678" -> "12345678"` -- `"did:iota:smr:12345678" -> "smr:12345678"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.join(segment) ⇒ [DIDUrl](#DIDUrl) -Construct a new [DIDUrl](#DIDUrl) by joining with a relative DID Url string. - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### coreDID.toUrl() ⇒ [DIDUrl](#DIDUrl) -Clones the [CoreDID](#CoreDID) into a [DIDUrl](#DIDUrl). - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.intoUrl() ⇒ [DIDUrl](#DIDUrl) -Converts the [CoreDID](#CoreDID) into a [DIDUrl](#DIDUrl), consuming it. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toString() ⇒ string -Returns the [CoreDID](#CoreDID) as a string. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toCoreDid() ⇒ [CoreDID](#CoreDID) -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.clone() ⇒ [CoreDID](#CoreDID) -Deep clones the object. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### CoreDID.parse(input) ⇒ [CoreDID](#CoreDID) -Parses a [CoreDID](#CoreDID) from the given `input`. - -### Errors - -Throws an error if the input is not a valid [CoreDID](#CoreDID). - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| input | string | - - - -### CoreDID.validMethodName(value) ⇒ boolean -Validates whether a string is a valid DID method name. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### CoreDID.validMethodId(value) ⇒ boolean -Validates whether a string is a valid `DID` method-id. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### CoreDID.fromJSON(json) ⇒ [CoreDID](#CoreDID) -Deserializes an instance from a JSON object. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| json | any | - - - -## CoreDocument -A method-agnostic DID Document. - -Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified. - -**Kind**: global class - -* [CoreDocument](#CoreDocument) - * [new CoreDocument(values)](#new_CoreDocument_new) - * _instance_ - * [.id()](#CoreDocument+id) ⇒ [CoreDID](#CoreDID) - * [.setId(id)](#CoreDocument+setId) - * [.controller()](#CoreDocument+controller) ⇒ [Array.<CoreDID>](#CoreDID) - * [.setController(controllers)](#CoreDocument+setController) - * [.alsoKnownAs()](#CoreDocument+alsoKnownAs) ⇒ Array.<string> - * [.setAlsoKnownAs(urls)](#CoreDocument+setAlsoKnownAs) - * [.verificationMethod()](#CoreDocument+verificationMethod) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.authentication()](#CoreDocument+authentication) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.assertionMethod()](#CoreDocument+assertionMethod) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.keyAgreement()](#CoreDocument+keyAgreement) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.capabilityDelegation()](#CoreDocument+capabilityDelegation) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.capabilityInvocation()](#CoreDocument+capabilityInvocation) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.properties()](#CoreDocument+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#CoreDocument+setPropertyUnchecked) - * [.service()](#CoreDocument+service) ⇒ [Array.<Service>](#Service) - * [.insertService(service)](#CoreDocument+insertService) - * [.removeService(didUrl)](#CoreDocument+removeService) ⇒ [Service](#Service) \| undefined - * [.resolveService(query)](#CoreDocument+resolveService) ⇒ [Service](#Service) \| undefined - * [.methods([scope])](#CoreDocument+methods) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.verificationRelationships()](#CoreDocument+verificationRelationships) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.insertMethod(method, scope)](#CoreDocument+insertMethod) - * [.removeMethod(did)](#CoreDocument+removeMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.resolveMethod(query, [scope])](#CoreDocument+resolveMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.attachMethodRelationship(didUrl, relationship)](#CoreDocument+attachMethodRelationship) ⇒ boolean - * [.detachMethodRelationship(didUrl, relationship)](#CoreDocument+detachMethodRelationship) ⇒ boolean - * [.verifyJws(jws, options, signatureVerifier, [detachedPayload])](#CoreDocument+verifyJws) ⇒ [DecodedJws](#DecodedJws) - * [.revokeCredentials(serviceQuery, indices)](#CoreDocument+revokeCredentials) - * [.unrevokeCredentials(serviceQuery, indices)](#CoreDocument+unrevokeCredentials) - * [.clone()](#CoreDocument+clone) ⇒ [CoreDocument](#CoreDocument) - * [._shallowCloneInternal()](#CoreDocument+_shallowCloneInternal) ⇒ [CoreDocument](#CoreDocument) - * [._strongCountInternal()](#CoreDocument+_strongCountInternal) ⇒ number - * [.toJSON()](#CoreDocument+toJSON) ⇒ any - * [.generateMethod(storage, keyType, alg, fragment, scope)](#CoreDocument+generateMethod) ⇒ Promise.<string> - * [.purgeMethod(storage, id)](#CoreDocument+purgeMethod) ⇒ Promise.<void> - * [.createJws(storage, fragment, payload, options)](#CoreDocument+createJws) ⇒ [Promise.<Jws>](#Jws) - * [.createCredentialJwt(storage, fragment, credential, options, [custom_claims])](#CoreDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) - * [.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options)](#CoreDocument+createPresentationJwt) ⇒ [Promise.<Jwt>](#Jwt) - * _static_ - * [.fromJSON(json)](#CoreDocument.fromJSON) ⇒ [CoreDocument](#CoreDocument) - - - -### new CoreDocument(values) -Creates a new [CoreDocument](#CoreDocument) with the given properties. - - -| Param | Type | -| --- | --- | -| values | ICoreDocument | - - - -### coreDocument.id() ⇒ [CoreDID](#CoreDID) -Returns a copy of the DID Document `id`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setId(id) -Sets the DID of the document. - -### Warning - -Changing the identifier can drastically alter the results of -`resolve_method`, `resolve_service` and the related -[DID URL dereferencing](https://w3c-ccg.github.io/did-resolution/#dereferencing) algorithm. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| id | [CoreDID](#CoreDID) | - - - -### coreDocument.controller() ⇒ [Array.<CoreDID>](#CoreDID) -Returns a copy of the document controllers. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setController(controllers) -Sets the controllers of the DID Document. - -Note: Duplicates will be ignored. -Use `null` to remove all controllers. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| controllers | [CoreDID](#CoreDID) \| [Array.<CoreDID>](#CoreDID) \| null | - - - -### coreDocument.alsoKnownAs() ⇒ Array.<string> -Returns a copy of the document's `alsoKnownAs` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setAlsoKnownAs(urls) -Sets the `alsoKnownAs` property in the DID document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| urls | string \| Array.<string> \| null | - - - -### coreDocument.verificationMethod() ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a copy of the document's `verificationMethod` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.authentication() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `authentication` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.assertionMethod() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `assertionMethod` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.keyAgreement() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `keyAgreement` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.capabilityDelegation() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `capabilityDelegation` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.capabilityInvocation() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `capabilityInvocation` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.properties() ⇒ Map.<string, any> -Returns a copy of the custom DID Document properties. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setPropertyUnchecked(key, value) -Sets a custom property in the DID Document. -If the value is set to `null`, the custom property will be removed. - -### WARNING - -This method can overwrite existing properties like `id` and result in an invalid document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### coreDocument.service() ⇒ [Array.<Service>](#Service) -Returns a set of all [Service](#Service) in the document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.insertService(service) -Add a new [Service](#Service) to the document. - -Errors if there already exists a service or verification method with the same id. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### coreDocument.removeService(didUrl) ⇒ [Service](#Service) \| undefined -Remove a [Service](#Service) identified by the given [DIDUrl](#DIDUrl) from the document. - -Returns `true` if the service was removed. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | - - - -### coreDocument.resolveService(query) ⇒ [Service](#Service) \| undefined -Returns the first [Service](#Service) with an `id` property matching the provided `query`, -if present. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | - - - -### coreDocument.methods([scope]) ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a list of all [VerificationMethod](#VerificationMethod) in the DID Document, -whose verification relationship matches `scope`. - -If `scope` is not set, a list over the **embedded** methods is returned. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### coreDocument.verificationRelationships() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns an array of all verification relationships. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.insertMethod(method, scope) -Adds a new `method` to the document in the given `scope`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| method | [VerificationMethod](#VerificationMethod) | -| scope | [MethodScope](#MethodScope) | - - - -### coreDocument.removeMethod(did) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Removes all references to the specified Verification Method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### coreDocument.resolveMethod(query, [scope]) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Returns a copy of the first verification method with an `id` property -matching the provided `query` and the verification relationship -specified by `scope`, if present. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### coreDocument.attachMethodRelationship(didUrl, relationship) ⇒ boolean -Attaches the relationship to the given method, if the method exists. - -Note: The method needs to be in the set of verification methods, -so it cannot be an embedded one. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### coreDocument.detachMethodRelationship(didUrl, relationship) ⇒ boolean -Detaches the given relationship from the given method, if the method exists. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### coreDocument.verifyJws(jws, options, signatureVerifier, [detachedPayload]) ⇒ [DecodedJws](#DecodedJws) -Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of -verifying EdDSA signatures. - -Regardless of which options are passed the following conditions must be met in order for a verification attempt to -take place. -- The JWS must be encoded according to the JWS compact serialization. -- The `kid` value in the protected header must be an identifier of a verification method in this DID document, -or set explicitly in the `options`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| jws | [Jws](#Jws) | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | -| signatureVerifier | IJwsVerifier | -| [detachedPayload] | string \| undefined | - - - -### coreDocument.revokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -revoke all specified `indices`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### coreDocument.unrevokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -unrevoke all specified `indices`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### coreDocument.clone() ⇒ [CoreDocument](#CoreDocument) -Deep clones the [CoreDocument](#CoreDocument). - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.\_shallowCloneInternal() ⇒ [CoreDocument](#CoreDocument) -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.\_strongCountInternal() ⇒ number -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.toJSON() ⇒ any -Serializes to a plain JS representation. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> -Generate new key material in the given `storage` and insert a new verification method with the corresponding -public key material into the DID document. - -- If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. -- The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants -for that use case. - -The fragment of the generated method is returned. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| keyType | string | -| alg | JwsAlgorithm | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### coreDocument.purgeMethod(storage, id) ⇒ Promise.<void> -Remove the method identified by the `fragment` from the document and delete the corresponding key material in -the `storage`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| id | [DIDUrl](#DIDUrl) | - - - -### coreDocument.createJws(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws) -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### coreDocument.createCredentialJwt(storage, fragment, credential, options, [custom_claims]) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given `credential` -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -The `custom_claims` can be used to set additional claims on the resulting JWT. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| credential | [Credential](#Credential) | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### coreDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given presentation. -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| presentation | [Presentation](#Presentation) | -| signature_options | [JwsSignatureOptions](#JwsSignatureOptions) | -| presentation_options | [JwtPresentationOptions](#JwtPresentationOptions) | - - - -### CoreDocument.fromJSON(json) ⇒ [CoreDocument](#CoreDocument) -Deserializes an instance from a plain JS representation. - -**Kind**: static method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Credential -**Kind**: global class - -* [Credential](#Credential) - * [new Credential(values)](#new_Credential_new) - * _instance_ - * [.context()](#Credential+context) ⇒ Array.<(string\|Record.<string, any>)> - * [.id()](#Credential+id) ⇒ string \| undefined - * [.type()](#Credential+type) ⇒ Array.<string> - * [.credentialSubject()](#Credential+credentialSubject) ⇒ Array.<Subject> - * [.issuer()](#Credential+issuer) ⇒ string \| Issuer - * [.issuanceDate()](#Credential+issuanceDate) ⇒ [Timestamp](#Timestamp) - * [.expirationDate()](#Credential+expirationDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.credentialStatus()](#Credential+credentialStatus) ⇒ Array.<Status> - * [.credentialSchema()](#Credential+credentialSchema) ⇒ Array.<Schema> - * [.refreshService()](#Credential+refreshService) ⇒ Array.<RefreshService> - * [.termsOfUse()](#Credential+termsOfUse) ⇒ Array.<Policy> - * [.evidence()](#Credential+evidence) ⇒ Array.<Evidence> - * [.nonTransferable()](#Credential+nonTransferable) ⇒ boolean \| undefined - * [.proof()](#Credential+proof) ⇒ [Proof](#Proof) \| undefined - * [.properties()](#Credential+properties) ⇒ Map.<string, any> - * [.setProof([proof])](#Credential+setProof) - * [.toJwtClaims([custom_claims])](#Credential+toJwtClaims) ⇒ Record.<string, any> - * [.toJSON()](#Credential+toJSON) ⇒ any - * [.clone()](#Credential+clone) ⇒ [Credential](#Credential) - * _static_ - * [.BaseContext()](#Credential.BaseContext) ⇒ string - * [.BaseType()](#Credential.BaseType) ⇒ string - * [.createDomainLinkageCredential(values)](#Credential.createDomainLinkageCredential) ⇒ [Credential](#Credential) - * [.fromJSON(json)](#Credential.fromJSON) ⇒ [Credential](#Credential) - - - -### new Credential(values) -Constructs a new [Credential](#Credential). - - -| Param | Type | -| --- | --- | -| values | ICredential | - - - -### credential.context() ⇒ Array.<(string\|Record.<string, any>)> -Returns a copy of the JSON-LD context(s) applicable to the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.id() ⇒ string \| undefined -Returns a copy of the unique `URI` identifying the [Credential](#Credential) . - -**Kind**: instance method of [Credential](#Credential) - - -### credential.type() ⇒ Array.<string> -Returns a copy of the URIs defining the type of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialSubject() ⇒ Array.<Subject> -Returns a copy of the [Credential](#Credential) subject(s). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.issuer() ⇒ string \| Issuer -Returns a copy of the issuer of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.issuanceDate() ⇒ [Timestamp](#Timestamp) -Returns a copy of the timestamp of when the [Credential](#Credential) becomes valid. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.expirationDate() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the [Credential](#Credential) should no longer be considered valid. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialStatus() ⇒ Array.<Status> -Returns a copy of the information used to determine the current status of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialSchema() ⇒ Array.<Schema> -Returns a copy of the information used to assist in the enforcement of a specific [Credential](#Credential) structure. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.refreshService() ⇒ Array.<RefreshService> -Returns a copy of the service(s) used to refresh an expired [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.termsOfUse() ⇒ Array.<Policy> -Returns a copy of the terms-of-use specified by the [Credential](#Credential) issuer. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.evidence() ⇒ Array.<Evidence> -Returns a copy of the human-readable evidence used to support the claims within the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.nonTransferable() ⇒ boolean \| undefined -Returns whether or not the [Credential](#Credential) must only be contained within a [Presentation](#Presentation) -with a proof issued from the [Credential](#Credential) subject. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.proof() ⇒ [Proof](#Proof) \| undefined -Optional cryptographic proof, unrelated to JWT. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.properties() ⇒ Map.<string, any> -Returns a copy of the miscellaneous properties on the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.setProof([proof]) -Sets the `proof` property of the [Credential](#Credential). - -Note that this proof is not related to JWT. - -**Kind**: instance method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| [proof] | [Proof](#Proof) \| undefined | - - - -### credential.toJwtClaims([custom_claims]) ⇒ Record.<string, any> -Serializes the `Credential` as a JWT claims set -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -The resulting object can be used as the payload of a JWS when issuing the credential. - -**Kind**: instance method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### credential.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.clone() ⇒ [Credential](#Credential) -Deep clones the object. - -**Kind**: instance method of [Credential](#Credential) - - -### Credential.BaseContext() ⇒ string -Returns the base JSON-LD context. - -**Kind**: static method of [Credential](#Credential) - - -### Credential.BaseType() ⇒ string -Returns the base type. - -**Kind**: static method of [Credential](#Credential) - - -### Credential.createDomainLinkageCredential(values) ⇒ [Credential](#Credential) -**Kind**: static method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| values | IDomainLinkageCredential | - - - -### Credential.fromJSON(json) ⇒ [Credential](#Credential) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## CustomMethodData -A custom verification method data format. - -**Kind**: global class - -* [CustomMethodData](#CustomMethodData) - * [new CustomMethodData(name, data)](#new_CustomMethodData_new) - * _instance_ - * [.clone()](#CustomMethodData+clone) ⇒ [CustomMethodData](#CustomMethodData) - * [.toJSON()](#CustomMethodData+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#CustomMethodData.fromJSON) ⇒ [CustomMethodData](#CustomMethodData) - - - -### new CustomMethodData(name, data) - -| Param | Type | -| --- | --- | -| name | string | -| data | any | - - - -### customMethodData.clone() ⇒ [CustomMethodData](#CustomMethodData) -Deep clones the object. - -**Kind**: instance method of [CustomMethodData](#CustomMethodData) - - -### customMethodData.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [CustomMethodData](#CustomMethodData) - - -### CustomMethodData.fromJSON(json) ⇒ [CustomMethodData](#CustomMethodData) -Deserializes an instance from a JSON object. - -**Kind**: static method of [CustomMethodData](#CustomMethodData) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DIDUrl -A method agnostic DID Url. - -**Kind**: global class - -* [DIDUrl](#DIDUrl) - * _instance_ - * [.did()](#DIDUrl+did) ⇒ [CoreDID](#CoreDID) - * [.urlStr()](#DIDUrl+urlStr) ⇒ string - * [.fragment()](#DIDUrl+fragment) ⇒ string \| undefined - * [.setFragment([value])](#DIDUrl+setFragment) - * [.path()](#DIDUrl+path) ⇒ string \| undefined - * [.setPath([value])](#DIDUrl+setPath) - * [.query()](#DIDUrl+query) ⇒ string \| undefined - * [.setQuery([value])](#DIDUrl+setQuery) - * [.join(segment)](#DIDUrl+join) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#DIDUrl+toString) ⇒ string - * [.toJSON()](#DIDUrl+toJSON) ⇒ any - * [.clone()](#DIDUrl+clone) ⇒ [DIDUrl](#DIDUrl) - * _static_ - * [.parse(input)](#DIDUrl.parse) ⇒ [DIDUrl](#DIDUrl) - * [.fromJSON(json)](#DIDUrl.fromJSON) ⇒ [DIDUrl](#DIDUrl) - - - -### didUrl.did() ⇒ [CoreDID](#CoreDID) -Return a copy of the [CoreDID](#CoreDID) section of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.urlStr() ⇒ string -Return a copy of the relative DID Url as a string, including only the path, query, and fragment. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.fragment() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) method fragment, if any. Excludes the leading '#'. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setFragment([value]) -Sets the `fragment` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.path() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) path. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setPath([value]) -Sets the `path` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.query() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) method query, if any. Excludes the leading '?'. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setQuery([value]) -Sets the `query` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.join(segment) ⇒ [DIDUrl](#DIDUrl) -Append a string representing a path, query, and/or fragment, returning a new [DIDUrl](#DIDUrl). - -Must begin with a valid delimiter character: '/', '?', '#'. Overwrites the existing URL -segment and any following segments in order of path, query, then fragment. - -I.e. -- joining a path will clear the query and fragment. -- joining a query will clear the fragment. -- joining a fragment will only overwrite the fragment. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### didUrl.toString() ⇒ string -Returns the [DIDUrl](#DIDUrl) as a string. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.clone() ⇒ [DIDUrl](#DIDUrl) -Deep clones the object. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### DIDUrl.parse(input) ⇒ [DIDUrl](#DIDUrl) -Parses a [DIDUrl](#DIDUrl) from the input string. - -**Kind**: static method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| input | string | - - - -### DIDUrl.fromJSON(json) ⇒ [DIDUrl](#DIDUrl) -Deserializes an instance from a JSON object. - -**Kind**: static method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DecodedJptCredential -**Kind**: global class - -* [DecodedJptCredential](#DecodedJptCredential) - * [.clone()](#DecodedJptCredential+clone) ⇒ [DecodedJptCredential](#DecodedJptCredential) - * [.credential()](#DecodedJptCredential+credential) ⇒ [Credential](#Credential) - * [.customClaims()](#DecodedJptCredential+customClaims) ⇒ Map.<string, any> - * [.decodedJwp()](#DecodedJptCredential+decodedJwp) ⇒ [JwpIssued](#JwpIssued) - - - -### decodedJptCredential.clone() ⇒ [DecodedJptCredential](#DecodedJptCredential) -Deep clones the object. - -**Kind**: instance method of [DecodedJptCredential](#DecodedJptCredential) - - -### decodedJptCredential.credential() ⇒ [Credential](#Credential) -Returns the [Credential](#Credential) embedded into this JPT. - -**Kind**: instance method of [DecodedJptCredential](#DecodedJptCredential) - - -### decodedJptCredential.customClaims() ⇒ Map.<string, any> -Returns the custom claims parsed from the JPT. - -**Kind**: instance method of [DecodedJptCredential](#DecodedJptCredential) - - -### decodedJptCredential.decodedJwp() ⇒ [JwpIssued](#JwpIssued) -**Kind**: instance method of [DecodedJptCredential](#DecodedJptCredential) - - -## DecodedJptPresentation -**Kind**: global class - -* [DecodedJptPresentation](#DecodedJptPresentation) - * [.clone()](#DecodedJptPresentation+clone) ⇒ [DecodedJptPresentation](#DecodedJptPresentation) - * [.credential()](#DecodedJptPresentation+credential) ⇒ [Credential](#Credential) - * [.customClaims()](#DecodedJptPresentation+customClaims) ⇒ Map.<string, any> - * [.aud()](#DecodedJptPresentation+aud) ⇒ string \| undefined - - - -### decodedJptPresentation.clone() ⇒ [DecodedJptPresentation](#DecodedJptPresentation) -Deep clones the object. - -**Kind**: instance method of [DecodedJptPresentation](#DecodedJptPresentation) - - -### decodedJptPresentation.credential() ⇒ [Credential](#Credential) -Returns the [Credential](#Credential) embedded into this JPT. - -**Kind**: instance method of [DecodedJptPresentation](#DecodedJptPresentation) - - -### decodedJptPresentation.customClaims() ⇒ Map.<string, any> -Returns the custom claims parsed from the JPT. - -**Kind**: instance method of [DecodedJptPresentation](#DecodedJptPresentation) - - -### decodedJptPresentation.aud() ⇒ string \| undefined -Returns the `aud` property parsed from the JWT claims. - -**Kind**: instance method of [DecodedJptPresentation](#DecodedJptPresentation) - - -## DecodedJws -A cryptographically verified decoded token from a JWS. - -Contains the decoded headers and the raw claims. - -**Kind**: global class - -* [DecodedJws](#DecodedJws) - * [.claims()](#DecodedJws+claims) ⇒ string - * [.claimsBytes()](#DecodedJws+claimsBytes) ⇒ Uint8Array - * [.protectedHeader()](#DecodedJws+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.clone()](#DecodedJws+clone) ⇒ [DecodedJws](#DecodedJws) - * [.toJSON()](#DecodedJws+toJSON) ⇒ any - - - -### decodedJws.claims() ⇒ string -Returns a copy of the parsed claims represented as a string. - -# Errors -An error is thrown if the claims cannot be represented as a string. - -This error can only occur if the Token was decoded from a detached payload. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.claimsBytes() ⇒ Uint8Array -Return a copy of the parsed claims represented as an array of bytes. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.clone() ⇒ [DecodedJws](#DecodedJws) -Deep clones the object. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -## DecodedJwtCredential -A cryptographically verified and decoded Credential. - -Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the credential itself. - -**Kind**: global class - -* [DecodedJwtCredential](#DecodedJwtCredential) - * [.credential()](#DecodedJwtCredential+credential) ⇒ [Credential](#Credential) - * [.protectedHeader()](#DecodedJwtCredential+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.customClaims()](#DecodedJwtCredential+customClaims) ⇒ Record.<string, any> \| undefined - * [.intoCredential()](#DecodedJwtCredential+intoCredential) ⇒ [Credential](#Credential) - - - -### decodedJwtCredential.credential() ⇒ [Credential](#Credential) -Returns a copy of the credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header parsed from the decoded JWS. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.customClaims() ⇒ Record.<string, any> \| undefined -The custom claims parsed from the JWT. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.intoCredential() ⇒ [Credential](#Credential) -Consumes the object and returns the decoded credential. - -### Warning - -This destroys the [DecodedJwtCredential](#DecodedJwtCredential) object. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -## DecodedJwtPresentation -A cryptographically verified and decoded presentation. - -Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the presentation itself. - -**Kind**: global class - -* [DecodedJwtPresentation](#DecodedJwtPresentation) - * [.presentation()](#DecodedJwtPresentation+presentation) ⇒ [Presentation](#Presentation) - * [.protectedHeader()](#DecodedJwtPresentation+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.intoPresentation()](#DecodedJwtPresentation+intoPresentation) ⇒ [Presentation](#Presentation) - * [.expirationDate()](#DecodedJwtPresentation+expirationDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.issuanceDate()](#DecodedJwtPresentation+issuanceDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.audience()](#DecodedJwtPresentation+audience) ⇒ string \| undefined - * [.customClaims()](#DecodedJwtPresentation+customClaims) ⇒ Record.<string, any> \| undefined - - - -### decodedJwtPresentation.presentation() ⇒ [Presentation](#Presentation) -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header parsed from the decoded JWS. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.intoPresentation() ⇒ [Presentation](#Presentation) -Consumes the object and returns the decoded presentation. - -### Warning -This destroys the [DecodedJwtPresentation](#DecodedJwtPresentation) object. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.expirationDate() ⇒ [Timestamp](#Timestamp) \| undefined -The expiration date parsed from the JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.issuanceDate() ⇒ [Timestamp](#Timestamp) \| undefined -The issuance date parsed from the JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.audience() ⇒ string \| undefined -The `aud` property parsed from JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.customClaims() ⇒ Record.<string, any> \| undefined -The custom claims parsed from the JWT. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -## Disclosure -Represents an elements constructing a disclosure. -Object properties and array elements disclosures are supported. - -See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures - -**Kind**: global class - -* [Disclosure](#Disclosure) - * [new Disclosure(salt, claim_name, claim_value)](#new_Disclosure_new) - * _instance_ - * [.disclosure()](#Disclosure+disclosure) ⇒ string - * [.toEncodedString()](#Disclosure+toEncodedString) ⇒ string - * [.toString()](#Disclosure+toString) ⇒ string - * [.salt()](#Disclosure+salt) ⇒ string - * [.claimName()](#Disclosure+claimName) ⇒ string \| undefined - * [.claimValue()](#Disclosure+claimValue) ⇒ any - * [.toJSON()](#Disclosure+toJSON) ⇒ any - * _static_ - * [.parse(disclosure)](#Disclosure.parse) ⇒ [Disclosure](#Disclosure) - * [.fromJSON(json)](#Disclosure.fromJSON) ⇒ [Disclosure](#Disclosure) - - - -### new Disclosure(salt, claim_name, claim_value) - -| Param | Type | -| --- | --- | -| salt | string | -| claim_name | string \| undefined | -| claim_value | any | - - - -### disclosure.disclosure() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toEncodedString() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toString() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.salt() ⇒ string -Returns a copy of the salt value. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.claimName() ⇒ string \| undefined -Returns a copy of the claim name, optional for array elements. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.claimValue() ⇒ any -Returns a copy of the claim Value which can be of any type. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### Disclosure.parse(disclosure) ⇒ [Disclosure](#Disclosure) -Parses a Base64 encoded disclosure into a `Disclosure`. - -## Error - -Returns an `InvalidDisclosure` if input is not a valid disclosure. - -**Kind**: static method of [Disclosure](#Disclosure) - -| Param | Type | -| --- | --- | -| disclosure | string | - - - -### Disclosure.fromJSON(json) ⇒ [Disclosure](#Disclosure) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Disclosure](#Disclosure) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DomainLinkageConfiguration -DID Configuration Resource which contains Domain Linkage Credentials. -It can be placed in an origin's `.well-known` directory to prove linkage between the origin and a DID. -See: - -Note: -- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - -**Kind**: global class - -* [DomainLinkageConfiguration](#DomainLinkageConfiguration) - * [new DomainLinkageConfiguration(linkedDids)](#new_DomainLinkageConfiguration_new) - * _instance_ - * [.linkedDids()](#DomainLinkageConfiguration+linkedDids) ⇒ [Array.<Jwt>](#Jwt) - * [.issuers()](#DomainLinkageConfiguration+issuers) ⇒ [Array.<CoreDID>](#CoreDID) - * [.toJSON()](#DomainLinkageConfiguration+toJSON) ⇒ any - * [.clone()](#DomainLinkageConfiguration+clone) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) - * _static_ - * [.fromJSON(json)](#DomainLinkageConfiguration.fromJSON) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - - -### new DomainLinkageConfiguration(linkedDids) -Constructs a new [DomainLinkageConfiguration](#DomainLinkageConfiguration). - - -| Param | Type | -| --- | --- | -| linkedDids | [Array.<Jwt>](#Jwt) | - - - -### domainLinkageConfiguration.linkedDids() ⇒ [Array.<Jwt>](#Jwt) -List of the Domain Linkage Credentials. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.issuers() ⇒ [Array.<CoreDID>](#CoreDID) -List of the issuers of the Domain Linkage Credentials. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.clone() ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) -Deep clones the object. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### DomainLinkageConfiguration.fromJSON(json) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) -Deserializes an instance from a JSON object. - -**Kind**: static method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Duration -A span of time. - -**Kind**: global class - -* [Duration](#Duration) - * _instance_ - * [.toJSON()](#Duration+toJSON) ⇒ any - * _static_ - * [.seconds(seconds)](#Duration.seconds) ⇒ [Duration](#Duration) - * [.minutes(minutes)](#Duration.minutes) ⇒ [Duration](#Duration) - * [.hours(hours)](#Duration.hours) ⇒ [Duration](#Duration) - * [.days(days)](#Duration.days) ⇒ [Duration](#Duration) - * [.weeks(weeks)](#Duration.weeks) ⇒ [Duration](#Duration) - * [.fromJSON(json)](#Duration.fromJSON) ⇒ [Duration](#Duration) - - - -### duration.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Duration](#Duration) - - -### Duration.seconds(seconds) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of seconds. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| seconds | number | - - - -### Duration.minutes(minutes) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of minutes. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| minutes | number | - - - -### Duration.hours(hours) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of hours. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| hours | number | - - - -### Duration.days(days) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of days. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| days | number | - - - -### Duration.weeks(weeks) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of weeks. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| weeks | number | - - - -### Duration.fromJSON(json) ⇒ [Duration](#Duration) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| json | any | - - - -## EdDSAJwsVerifier -An implementor of `IJwsVerifier` that can handle the -`EdDSA` algorithm. - -**Kind**: global class - -* [EdDSAJwsVerifier](#EdDSAJwsVerifier) - * [new EdDSAJwsVerifier()](#new_EdDSAJwsVerifier_new) - * [.verify(alg, signingInput, decodedSignature, publicKey)](#EdDSAJwsVerifier+verify) - - - -### new EdDSAJwsVerifier() -Constructs an EdDSAJwsVerifier. - - - -### edDSAJwsVerifier.verify(alg, signingInput, decodedSignature, publicKey) -Verify a JWS signature secured with the `EdDSA` algorithm. -Only the `Ed25519` curve is supported for now. - -This function is useful when one is building an `IJwsVerifier` that extends the default provided by -the IOTA Identity Framework. - -# Warning - -This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this -prior to calling the function. - -**Kind**: instance method of [EdDSAJwsVerifier](#EdDSAJwsVerifier) - -| Param | Type | -| --- | --- | -| alg | JwsAlgorithm | -| signingInput | Uint8Array | -| decodedSignature | Uint8Array | -| publicKey | [Jwk](#Jwk) | - - - -## IotaDID -A DID conforming to the IOTA DID method specification. - -**Kind**: global class - -* [IotaDID](#IotaDID) - * [new IotaDID(bytes, network)](#new_IotaDID_new) - * _instance_ - * [.network()](#IotaDID+network) ⇒ string - * [.tag()](#IotaDID+tag) ⇒ string - * [.toCoreDid()](#IotaDID+toCoreDid) ⇒ [CoreDID](#CoreDID) - * [.scheme()](#IotaDID+scheme) ⇒ string - * [.authority()](#IotaDID+authority) ⇒ string - * [.method()](#IotaDID+method) ⇒ string - * [.methodId()](#IotaDID+methodId) ⇒ string - * [.join(segment)](#IotaDID+join) ⇒ [DIDUrl](#DIDUrl) - * [.toUrl()](#IotaDID+toUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toAliasId()](#IotaDID+toAliasId) ⇒ string - * [.intoUrl()](#IotaDID+intoUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#IotaDID+toString) ⇒ string - * [.toJSON()](#IotaDID+toJSON) ⇒ any - * [.clone()](#IotaDID+clone) ⇒ [IotaDID](#IotaDID) - * _static_ - * [.METHOD](#IotaDID.METHOD) ⇒ string - * [.DEFAULT_NETWORK](#IotaDID.DEFAULT_NETWORK) ⇒ string - * [.fromAliasId(aliasId, network)](#IotaDID.fromAliasId) ⇒ [IotaDID](#IotaDID) - * [.placeholder(network)](#IotaDID.placeholder) ⇒ [IotaDID](#IotaDID) - * [.parse(input)](#IotaDID.parse) ⇒ [IotaDID](#IotaDID) - * [.fromJSON(json)](#IotaDID.fromJSON) ⇒ [IotaDID](#IotaDID) - - - -### new IotaDID(bytes, network) -Constructs a new [IotaDID](#IotaDID) from a byte representation of the tag and the given -network name. - -See also [placeholder](#IotaDID.placeholder). - - -| Param | Type | -| --- | --- | -| bytes | Uint8Array | -| network | string | - - - -### did.network() ⇒ string -Returns the Tangle network name of the [IotaDID](#IotaDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.tag() ⇒ string -Returns a copy of the unique tag of the [IotaDID](#IotaDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toCoreDid() ⇒ [CoreDID](#CoreDID) -Returns the DID represented as a [CoreDID](#CoreDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.scheme() ⇒ string -Returns the `DID` scheme. - -E.g. -- `"did:example:12345678" -> "did"` -- `"did:iota:main:12345678" -> "did"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.authority() ⇒ string -Returns the `DID` authority: the method name and method-id. - -E.g. -- `"did:example:12345678" -> "example:12345678"` -- `"did:iota:main:12345678" -> "iota:main:12345678"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.method() ⇒ string -Returns the `DID` method name. - -E.g. -- `"did:example:12345678" -> "example"` -- `"did:iota:main:12345678" -> "iota"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.methodId() ⇒ string -Returns the `DID` method-specific ID. - -E.g. -- `"did:example:12345678" -> "12345678"` -- `"did:iota:main:12345678" -> "main:12345678"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.join(segment) ⇒ [DIDUrl](#DIDUrl) -Construct a new [DIDUrl](#DIDUrl) by joining with a relative DID Url string. - -**Kind**: instance method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### did.toUrl() ⇒ [DIDUrl](#DIDUrl) -Clones the `DID` into a [DIDUrl](#DIDUrl). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toAliasId() ⇒ string -Returns the hex-encoded AliasId with a '0x' prefix, from the DID tag. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.intoUrl() ⇒ [DIDUrl](#DIDUrl) -Converts the `DID` into a [DIDUrl](#DIDUrl), consuming it. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toString() ⇒ string -Returns the `DID` as a string. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.clone() ⇒ [IotaDID](#IotaDID) -Deep clones the object. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### IotaDID.METHOD ⇒ string -The IOTA DID method name (`"iota"`). - -**Kind**: static property of [IotaDID](#IotaDID) - - -### IotaDID.DEFAULT\_NETWORK ⇒ string -The default Tangle network (`"iota"`). - -**Kind**: static property of [IotaDID](#IotaDID) - - -### IotaDID.fromAliasId(aliasId, network) ⇒ [IotaDID](#IotaDID) -Constructs a new [IotaDID](#IotaDID) from a hex representation of an Alias Id and the given -network name. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| aliasId | string | -| network | string | - - - -### IotaDID.placeholder(network) ⇒ [IotaDID](#IotaDID) -Creates a new placeholder [IotaDID](#IotaDID) with the given network name. - -E.g. `did:iota:smr:0x0000000000000000000000000000000000000000000000000000000000000000`. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| network | string | - - - -### IotaDID.parse(input) ⇒ [IotaDID](#IotaDID) -Parses a [IotaDID](#IotaDID) from the input string. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| input | string | - - - -### IotaDID.fromJSON(json) ⇒ [IotaDID](#IotaDID) -Deserializes an instance from a JSON object. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaDocument -A DID Document adhering to the IOTA DID method specification. - -Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified. - -**Kind**: global class - -* [IotaDocument](#IotaDocument) - * [new IotaDocument(network)](#new_IotaDocument_new) - * _instance_ - * [.id()](#IotaDocument+id) ⇒ [IotaDID](#IotaDID) - * [.controller()](#IotaDocument+controller) ⇒ [Array.<IotaDID>](#IotaDID) - * [.setController(controller)](#IotaDocument+setController) - * [.alsoKnownAs()](#IotaDocument+alsoKnownAs) ⇒ Array.<string> - * [.setAlsoKnownAs(urls)](#IotaDocument+setAlsoKnownAs) - * [.properties()](#IotaDocument+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#IotaDocument+setPropertyUnchecked) - * [.service()](#IotaDocument+service) ⇒ [Array.<Service>](#Service) - * [.insertService(service)](#IotaDocument+insertService) - * [.removeService(did)](#IotaDocument+removeService) ⇒ [Service](#Service) \| undefined - * [.resolveService(query)](#IotaDocument+resolveService) ⇒ [Service](#Service) \| undefined - * [.methods([scope])](#IotaDocument+methods) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.insertMethod(method, scope)](#IotaDocument+insertMethod) - * [.removeMethod(did)](#IotaDocument+removeMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.resolveMethod(query, [scope])](#IotaDocument+resolveMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.attachMethodRelationship(didUrl, relationship)](#IotaDocument+attachMethodRelationship) ⇒ boolean - * [.detachMethodRelationship(didUrl, relationship)](#IotaDocument+detachMethodRelationship) ⇒ boolean - * [.verifyJws(jws, options, signatureVerifier, [detachedPayload])](#IotaDocument+verifyJws) ⇒ [DecodedJws](#DecodedJws) - * [.pack()](#IotaDocument+pack) ⇒ Uint8Array - * [.packWithEncoding(encoding)](#IotaDocument+packWithEncoding) ⇒ Uint8Array - * [.metadata()](#IotaDocument+metadata) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - * [.metadataCreated()](#IotaDocument+metadataCreated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.setMetadataCreated(timestamp)](#IotaDocument+setMetadataCreated) - * [.metadataUpdated()](#IotaDocument+metadataUpdated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.setMetadataUpdated(timestamp)](#IotaDocument+setMetadataUpdated) - * [.metadataDeactivated()](#IotaDocument+metadataDeactivated) ⇒ boolean \| undefined - * [.setMetadataDeactivated([deactivated])](#IotaDocument+setMetadataDeactivated) - * [.metadataStateControllerAddress()](#IotaDocument+metadataStateControllerAddress) ⇒ string \| undefined - * [.metadataGovernorAddress()](#IotaDocument+metadataGovernorAddress) ⇒ string \| undefined - * [.setMetadataPropertyUnchecked(key, value)](#IotaDocument+setMetadataPropertyUnchecked) - * [.revokeCredentials(serviceQuery, indices)](#IotaDocument+revokeCredentials) - * [.unrevokeCredentials(serviceQuery, indices)](#IotaDocument+unrevokeCredentials) - * [.clone()](#IotaDocument+clone) ⇒ [IotaDocument](#IotaDocument) - * [._shallowCloneInternal()](#IotaDocument+_shallowCloneInternal) ⇒ [IotaDocument](#IotaDocument) - * [._strongCountInternal()](#IotaDocument+_strongCountInternal) ⇒ number - * [.toJSON()](#IotaDocument+toJSON) ⇒ any - * [.toCoreDocument()](#IotaDocument+toCoreDocument) ⇒ [CoreDocument](#CoreDocument) - * [.generateMethod(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethod) ⇒ Promise.<string> - * [.purgeMethod(storage, id)](#IotaDocument+purgeMethod) ⇒ Promise.<void> - * ~~[.createJwt(storage, fragment, payload, options)](#IotaDocument+createJwt) ⇒ [Promise.<Jws>](#Jws)~~ - * [.createJws(storage, fragment, payload, options)](#IotaDocument+createJws) ⇒ [Promise.<Jws>](#Jws) - * [.createCredentialJwt(storage, fragment, credential, options, [custom_claims])](#IotaDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) - * [.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options)](#IotaDocument+createPresentationJwt) ⇒ [Promise.<Jwt>](#Jwt) - * [.generateMethodJwp(storage, alg, fragment, scope)](#IotaDocument+generateMethodJwp) ⇒ Promise.<string> - * [.createIssuedJwp(storage, fragment, jpt_claims, options)](#IotaDocument+createIssuedJwp) ⇒ Promise.<string> - * [.createPresentedJwp(presentation, method_id, options)](#IotaDocument+createPresentedJwp) ⇒ Promise.<string> - * [.createCredentialJpt(credential, storage, fragment, options, [custom_claims])](#IotaDocument+createCredentialJpt) ⇒ [Promise.<Jpt>](#Jpt) - * [.createPresentationJpt(presentation, method_id, options)](#IotaDocument+createPresentationJpt) ⇒ [Promise.<Jpt>](#Jpt) - * [.generateMethodPQC(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethodPQC) ⇒ Promise.<string> - * _static_ - * [.newWithId(id)](#IotaDocument.newWithId) ⇒ [IotaDocument](#IotaDocument) - * [.unpackFromOutput(did, aliasOutput, allowEmpty)](#IotaDocument.unpackFromOutput) ⇒ [IotaDocument](#IotaDocument) - * [.unpackFromBlock(network, block)](#IotaDocument.unpackFromBlock) ⇒ [Array.<IotaDocument>](#IotaDocument) - * [.fromJSON(json)](#IotaDocument.fromJSON) ⇒ [IotaDocument](#IotaDocument) - - - -### new IotaDocument(network) -Constructs an empty IOTA DID Document with a [placeholder](#IotaDID.placeholder) identifier -for the given `network`. - - -| Param | Type | -| --- | --- | -| network | string | - - - -### iotaDocument.id() ⇒ [IotaDID](#IotaDID) -Returns a copy of the DID Document `id`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.controller() ⇒ [Array.<IotaDID>](#IotaDID) -Returns a copy of the list of document controllers. - -NOTE: controllers are determined by the `state_controller` unlock condition of the output -during resolution and are omitted when publishing. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setController(controller) -Sets the controllers of the document. - -Note: Duplicates will be ignored. -Use `null` to remove all controllers. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| controller | [Array.<IotaDID>](#IotaDID) \| null | - - - -### iotaDocument.alsoKnownAs() ⇒ Array.<string> -Returns a copy of the document's `alsoKnownAs` set. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setAlsoKnownAs(urls) -Sets the `alsoKnownAs` property in the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| urls | string \| Array.<string> \| null | - - - -### iotaDocument.properties() ⇒ Map.<string, any> -Returns a copy of the custom DID Document properties. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setPropertyUnchecked(key, value) -Sets a custom property in the DID Document. -If the value is set to `null`, the custom property will be removed. - -### WARNING - -This method can overwrite existing properties like `id` and result in an invalid document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### iotaDocument.service() ⇒ [Array.<Service>](#Service) -Return a set of all [Service](#Service) in the document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.insertService(service) -Add a new [Service](#Service) to the document. - -Returns `true` if the service was added. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### iotaDocument.removeService(did) ⇒ [Service](#Service) \| undefined -Remove a [Service](#Service) identified by the given [DIDUrl](#DIDUrl) from the document. - -Returns `true` if a service was removed. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### iotaDocument.resolveService(query) ⇒ [Service](#Service) \| undefined -Returns the first [Service](#Service) with an `id` property matching the provided `query`, -if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | - - - -### iotaDocument.methods([scope]) ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a list of all [VerificationMethod](#VerificationMethod) in the DID Document, -whose verification relationship matches `scope`. - -If `scope` is not set, a list over the **embedded** methods is returned. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### iotaDocument.insertMethod(method, scope) -Adds a new `method` to the document in the given `scope`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| method | [VerificationMethod](#VerificationMethod) | -| scope | [MethodScope](#MethodScope) | - - - -### iotaDocument.removeMethod(did) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Removes all references to the specified Verification Method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### iotaDocument.resolveMethod(query, [scope]) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Returns a copy of the first verification method with an `id` property -matching the provided `query` and the verification relationship -specified by `scope`, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### iotaDocument.attachMethodRelationship(didUrl, relationship) ⇒ boolean -Attaches the relationship to the given method, if the method exists. - -Note: The method needs to be in the set of verification methods, -so it cannot be an embedded one. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### iotaDocument.detachMethodRelationship(didUrl, relationship) ⇒ boolean -Detaches the given relationship from the given method, if the method exists. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### iotaDocument.verifyJws(jws, options, signatureVerifier, [detachedPayload]) ⇒ [DecodedJws](#DecodedJws) -Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of -verifying EdDSA signatures. - -Regardless of which options are passed the following conditions must be met in order for a verification attempt to -take place. -- The JWS must be encoded according to the JWS compact serialization. -- The `kid` value in the protected header must be an identifier of a verification method in this DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| jws | [Jws](#Jws) | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | -| signatureVerifier | IJwsVerifier | -| [detachedPayload] | string \| undefined | - - - -### iotaDocument.pack() ⇒ Uint8Array -Serializes the document for inclusion in an Alias Output's state metadata -with the default [StateMetadataEncoding](#StateMetadataEncoding). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.packWithEncoding(encoding) ⇒ Uint8Array -Serializes the document for inclusion in an Alias Output's state metadata. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| encoding | [StateMetadataEncoding](#StateMetadataEncoding) | - - - -### iotaDocument.metadata() ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Returns a copy of the metadata associated with this document. - -NOTE: Copies all the metadata. See also `metadataCreated`, `metadataUpdated`, -`metadataPreviousMessageId`, `metadataProof` if only a subset of the metadata required. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.metadataCreated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataCreated(timestamp) -Sets the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| timestamp | [Timestamp](#Timestamp) \| undefined | - - - -### iotaDocument.metadataUpdated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataUpdated(timestamp) -Sets the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| timestamp | [Timestamp](#Timestamp) \| undefined | - - - -### iotaDocument.metadataDeactivated() ⇒ boolean \| undefined -Returns a copy of the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataDeactivated([deactivated]) -Sets the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| [deactivated] | boolean \| undefined | - - - -### iotaDocument.metadataStateControllerAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded state controller address, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.metadataGovernorAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded governor address, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataPropertyUnchecked(key, value) -Sets a custom property in the document metadata. -If the value is set to `null`, the custom property will be removed. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### iotaDocument.revokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -revoke all specified `indices`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### iotaDocument.unrevokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -unrevoke all specified `indices`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### iotaDocument.clone() ⇒ [IotaDocument](#IotaDocument) -Returns a deep clone of the [IotaDocument](#IotaDocument). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.\_shallowCloneInternal() ⇒ [IotaDocument](#IotaDocument) -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.\_strongCountInternal() ⇒ number -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.toJSON() ⇒ any -Serializes to a plain JS representation. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.toCoreDocument() ⇒ [CoreDocument](#CoreDocument) -Transforms the [IotaDocument](#IotaDocument) to its [CoreDocument](#CoreDocument) representation. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> -Generate new key material in the given `storage` and insert a new verification method with the corresponding -public key material into the DID document. - -- If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. -- The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants -for that use case. - -The fragment of the generated method is returned. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| keyType | string | -| alg | JwsAlgorithm | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### iotaDocument.purgeMethod(storage, id) ⇒ Promise.<void> -Remove the method identified by the given fragment from the document and delete the corresponding key material in -the given `storage`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| id | [DIDUrl](#DIDUrl) | - - - -### ~~iotaDocument.createJwt(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws)~~ -***Deprecated*** - -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### iotaDocument.createJws(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws) -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### iotaDocument.createCredentialJwt(storage, fragment, credential, options, [custom_claims]) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWS where the payload is produced from the given `credential` -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -The `custom_claims` can be used to set additional claims on the resulting JWT. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| credential | [Credential](#Credential) | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### iotaDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given presentation. -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| presentation | [Presentation](#Presentation) | -| signature_options | [JwsSignatureOptions](#JwsSignatureOptions) | -| presentation_options | [JwtPresentationOptions](#JwtPresentationOptions) | - - - -### iotaDocument.generateMethodJwp(storage, alg, fragment, scope) ⇒ Promise.<string> -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| alg | [ProofAlgorithm](#ProofAlgorithm) | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### iotaDocument.createIssuedJwp(storage, fragment, jpt_claims, options) ⇒ Promise.<string> -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| jpt_claims | JptClaims | -| options | [JwpCredentialOptions](#JwpCredentialOptions) | - - - -### iotaDocument.createPresentedJwp(presentation, method_id, options) ⇒ Promise.<string> -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| presentation | [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) | -| method_id | string | -| options | [JwpPresentationOptions](#JwpPresentationOptions) | - - - -### iotaDocument.createCredentialJpt(credential, storage, fragment, options, [custom_claims]) ⇒ [Promise.<Jpt>](#Jpt) -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| storage | [Storage](#Storage) | -| fragment | string | -| options | [JwpCredentialOptions](#JwpCredentialOptions) | -| [custom_claims] | Map.<string, any> \| undefined | - - - -### iotaDocument.createPresentationJpt(presentation, method_id, options) ⇒ [Promise.<Jpt>](#Jpt) -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| presentation | [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) | -| method_id | string | -| options | [JwpPresentationOptions](#JwpPresentationOptions) | - - - -### iotaDocument.generateMethodPQC(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| keyType | string | -| alg | JwsAlgorithm | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### IotaDocument.newWithId(id) ⇒ [IotaDocument](#IotaDocument) -Constructs an empty DID Document with the given identifier. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| id | [IotaDID](#IotaDID) | - - - -### IotaDocument.unpackFromOutput(did, aliasOutput, allowEmpty) ⇒ [IotaDocument](#IotaDocument) -Deserializes the document from an Alias Output. - -If `allowEmpty` is true, this will return an empty DID document marked as `deactivated` -if `stateMetadata` is empty. - -The `tokenSupply` must be equal to the token supply of the network the DID is associated with. - -NOTE: `did` is required since it is omitted from the serialized DID Document and -cannot be inferred from the state metadata. It also indicates the network, which is not -encoded in the `AliasId` alone. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [IotaDID](#IotaDID) | -| aliasOutput | AliasOutputBuilderParams | -| allowEmpty | boolean | - - - -### IotaDocument.unpackFromBlock(network, block) ⇒ [Array.<IotaDocument>](#IotaDocument) -Returns all DID documents of the Alias Outputs contained in the block's transaction payload -outputs, if any. - -Errors if any Alias Output does not contain a valid or empty DID Document. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| network | string | -| block | Block | - - - -### IotaDocument.fromJSON(json) ⇒ [IotaDocument](#IotaDocument) -Deserializes an instance from a plain JS representation. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaDocumentMetadata -Additional attributes related to an IOTA DID Document. - -**Kind**: global class - -* [IotaDocumentMetadata](#IotaDocumentMetadata) - * _instance_ - * [.created()](#IotaDocumentMetadata+created) ⇒ [Timestamp](#Timestamp) \| undefined - * [.updated()](#IotaDocumentMetadata+updated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.deactivated()](#IotaDocumentMetadata+deactivated) ⇒ boolean \| undefined - * [.stateControllerAddress()](#IotaDocumentMetadata+stateControllerAddress) ⇒ string \| undefined - * [.governorAddress()](#IotaDocumentMetadata+governorAddress) ⇒ string \| undefined - * [.properties()](#IotaDocumentMetadata+properties) ⇒ Map.<string, any> - * [.toJSON()](#IotaDocumentMetadata+toJSON) ⇒ any - * [.clone()](#IotaDocumentMetadata+clone) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - * _static_ - * [.fromJSON(json)](#IotaDocumentMetadata.fromJSON) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - - - -### iotaDocumentMetadata.created() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.updated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.deactivated() ⇒ boolean \| undefined -Returns a copy of the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.stateControllerAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded state controller address, if present. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.governorAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded governor address, if present. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.properties() ⇒ Map.<string, any> -Returns a copy of the custom metadata properties. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.clone() ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Deep clones the object. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### IotaDocumentMetadata.fromJSON(json) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Deserializes an instance from a JSON object. - -**Kind**: static method of [IotaDocumentMetadata](#IotaDocumentMetadata) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaIdentityClientExt -An extension interface that provides helper functions for publication -and resolution of DID documents in Alias Outputs. - -**Kind**: global class - -* [IotaIdentityClientExt](#IotaIdentityClientExt) - * [.newDidOutput(client, address, document, [rentStructure])](#IotaIdentityClientExt.newDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.updateDidOutput(client, document)](#IotaIdentityClientExt.updateDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.deactivateDidOutput(client, did)](#IotaIdentityClientExt.deactivateDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.resolveDid(client, did)](#IotaIdentityClientExt.resolveDid) ⇒ [Promise.<IotaDocument>](#IotaDocument) - * [.resolveDidOutput(client, did)](#IotaIdentityClientExt.resolveDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - - - -### IotaIdentityClientExt.newDidOutput(client, address, document, [rentStructure]) ⇒ Promise.<AliasOutputBuilderParams> -Create a DID with a new Alias Output containing the given `document`. - -The `address` will be set as the state controller and governor unlock conditions. -The minimum required token deposit amount will be set according to the given -`rent_structure`, which will be fetched from the node if not provided. -The returned Alias Output can be further customised before publication, if desired. - -NOTE: this does *not* publish the Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| address | Address | -| document | [IotaDocument](#IotaDocument) | -| [rentStructure] | IRent \| undefined | - - - -### IotaIdentityClientExt.updateDidOutput(client, document) ⇒ Promise.<AliasOutputBuilderParams> -Fetches the associated Alias Output and updates it with `document` in its state metadata. -The storage deposit on the output is left unchanged. If the size of the document increased, -the amount should be increased manually. - -NOTE: this does *not* publish the updated Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| document | [IotaDocument](#IotaDocument) | - - - -### IotaIdentityClientExt.deactivateDidOutput(client, did) ⇒ Promise.<AliasOutputBuilderParams> -Removes the DID document from the state metadata of its Alias Output, -effectively deactivating it. The storage deposit on the output is left unchanged, -and should be reallocated manually. - -Deactivating does not destroy the output. Hence, it can be re-activated by publishing -an update containing a DID document. - -NOTE: this does *not* publish the updated Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -### IotaIdentityClientExt.resolveDid(client, did) ⇒ [Promise.<IotaDocument>](#IotaDocument) -Resolve a [IotaDocument](#IotaDocument). Returns an empty, deactivated document if the state metadata -of the Alias Output is empty. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -### IotaIdentityClientExt.resolveDidOutput(client, did) ⇒ Promise.<AliasOutputBuilderParams> -Fetches the `IAliasOutput` associated with the given DID. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -## IssuerProtectedHeader -**Kind**: global class - -* [IssuerProtectedHeader](#IssuerProtectedHeader) - * [.typ](#IssuerProtectedHeader+typ) ⇒ string \| undefined - * [.typ](#IssuerProtectedHeader+typ) - * [.alg](#IssuerProtectedHeader+alg) ⇒ [ProofAlgorithm](#ProofAlgorithm) - * [.alg](#IssuerProtectedHeader+alg) - * [.kid](#IssuerProtectedHeader+kid) ⇒ string \| undefined - * [.kid](#IssuerProtectedHeader+kid) - * [.cid](#IssuerProtectedHeader+cid) ⇒ string \| undefined - * [.cid](#IssuerProtectedHeader+cid) - * [.claims()](#IssuerProtectedHeader+claims) ⇒ Array.<string> - - - -### issuerProtectedHeader.typ ⇒ string \| undefined -JWP type (JPT). - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - - -### issuerProtectedHeader.typ -JWP type (JPT). - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### issuerProtectedHeader.alg ⇒ [ProofAlgorithm](#ProofAlgorithm) -Algorithm used for the JWP. - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - - -### issuerProtectedHeader.alg -Algorithm used for the JWP. - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - -| Param | Type | -| --- | --- | -| arg0 | [ProofAlgorithm](#ProofAlgorithm) | - - - -### issuerProtectedHeader.kid ⇒ string \| undefined -ID for the key used for the JWP. - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - - -### issuerProtectedHeader.kid -ID for the key used for the JWP. - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### issuerProtectedHeader.cid ⇒ string \| undefined -Not handled for now. Will be used in the future to resolve external claims - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - - -### issuerProtectedHeader.cid -Not handled for now. Will be used in the future to resolve external claims - -**Kind**: instance property of [IssuerProtectedHeader](#IssuerProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### issuerProtectedHeader.claims() ⇒ Array.<string> -**Kind**: instance method of [IssuerProtectedHeader](#IssuerProtectedHeader) - - -## Jpt -A JSON Proof Token (JPT). - -**Kind**: global class - -* [Jpt](#Jpt) - * [new Jpt(jpt_string)](#new_Jpt_new) - * [.toString()](#Jpt+toString) ⇒ string - * [.clone()](#Jpt+clone) ⇒ [Jpt](#Jpt) - - - -### new Jpt(jpt_string) -Creates a new [Jpt](#Jpt). - - -| Param | Type | -| --- | --- | -| jpt_string | string | - - - -### jpt.toString() ⇒ string -**Kind**: instance method of [Jpt](#Jpt) - - -### jpt.clone() ⇒ [Jpt](#Jpt) -Deep clones the object. - -**Kind**: instance method of [Jpt](#Jpt) - - -## JptCredentialValidationOptions -Options to declare validation criteria for [Jpt](#Jpt). - -**Kind**: global class - -* [JptCredentialValidationOptions](#JptCredentialValidationOptions) - * [new JptCredentialValidationOptions([opts])](#new_JptCredentialValidationOptions_new) - * _instance_ - * [.clone()](#JptCredentialValidationOptions+clone) ⇒ [JptCredentialValidationOptions](#JptCredentialValidationOptions) - * [.toJSON()](#JptCredentialValidationOptions+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#JptCredentialValidationOptions.fromJSON) ⇒ [JptCredentialValidationOptions](#JptCredentialValidationOptions) - - - -### new JptCredentialValidationOptions([opts]) -Creates a new default istance. - - -| Param | Type | -| --- | --- | -| [opts] | IJptCredentialValidationOptions \| undefined | - - - -### jptCredentialValidationOptions.clone() ⇒ [JptCredentialValidationOptions](#JptCredentialValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JptCredentialValidationOptions](#JptCredentialValidationOptions) - - -### jptCredentialValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JptCredentialValidationOptions](#JptCredentialValidationOptions) - - -### JptCredentialValidationOptions.fromJSON(json) ⇒ [JptCredentialValidationOptions](#JptCredentialValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JptCredentialValidationOptions](#JptCredentialValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JptCredentialValidator -**Kind**: global class - - -### JptCredentialValidator.validate(credential_jpt, issuer, options, fail_fast) ⇒ [DecodedJptCredential](#DecodedJptCredential) -**Kind**: static method of [JptCredentialValidator](#JptCredentialValidator) - -| Param | Type | -| --- | --- | -| credential_jpt | [Jpt](#Jpt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JptCredentialValidationOptions](#JptCredentialValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -## JptCredentialValidatorUtils -Utility functions for validating JPT credentials. - -**Kind**: global class - -* [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - * [.extractIssuer(credential)](#JptCredentialValidatorUtils.extractIssuer) ⇒ [CoreDID](#CoreDID) - * [.extractIssuerFromIssuedJpt(credential)](#JptCredentialValidatorUtils.extractIssuerFromIssuedJpt) ⇒ [CoreDID](#CoreDID) - * [.checkTimeframesWithValidityTimeframe2024(credential, validity_timeframe, status_check)](#JptCredentialValidatorUtils.checkTimeframesWithValidityTimeframe2024) - * [.checkRevocationWithValidityTimeframe2024(credential, issuer, status_check)](#JptCredentialValidatorUtils.checkRevocationWithValidityTimeframe2024) - * [.checkTimeframesAndRevocationWithValidityTimeframe2024(credential, issuer, validity_timeframe, status_check)](#JptCredentialValidatorUtils.checkTimeframesAndRevocationWithValidityTimeframe2024) - - - -### JptCredentialValidatorUtils.extractIssuer(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a [Credential](#Credential) as a DID. -# Errors -Fails if the issuer field is not a valid DID. - -**Kind**: static method of [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | - - - -### JptCredentialValidatorUtils.extractIssuerFromIssuedJpt(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a credential in JPT representation as DID. -# Errors -If the JPT decoding fails or the issuer field is not a valid DID. - -**Kind**: static method of [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Jpt](#Jpt) | - - - -### JptCredentialValidatorUtils.checkTimeframesWithValidityTimeframe2024(credential, validity_timeframe, status_check) -**Kind**: static method of [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| validity_timeframe | [Timestamp](#Timestamp) \| undefined | -| status_check | [StatusCheck](#StatusCheck) | - - - -### JptCredentialValidatorUtils.checkRevocationWithValidityTimeframe2024(credential, issuer, status_check) -Checks whether the credential status has been revoked. - -Only supports `RevocationTimeframe2024`. - -**Kind**: static method of [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| status_check | [StatusCheck](#StatusCheck) | - - - -### JptCredentialValidatorUtils.checkTimeframesAndRevocationWithValidityTimeframe2024(credential, issuer, validity_timeframe, status_check) -Checks whether the credential status has been revoked or the timeframe interval is INVALID - -Only supports `RevocationTimeframe2024`. - -**Kind**: static method of [JptCredentialValidatorUtils](#JptCredentialValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| validity_timeframe | [Timestamp](#Timestamp) \| undefined | -| status_check | [StatusCheck](#StatusCheck) | - - - -## JptPresentationValidationOptions -Options to declare validation criteria for a [Jpt](#Jpt) presentation. - -**Kind**: global class - -* [JptPresentationValidationOptions](#JptPresentationValidationOptions) - * [new JptPresentationValidationOptions([opts])](#new_JptPresentationValidationOptions_new) - * _instance_ - * [.clone()](#JptPresentationValidationOptions+clone) ⇒ [JptPresentationValidationOptions](#JptPresentationValidationOptions) - * [.toJSON()](#JptPresentationValidationOptions+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#JptPresentationValidationOptions.fromJSON) ⇒ [JptPresentationValidationOptions](#JptPresentationValidationOptions) - - - -### new JptPresentationValidationOptions([opts]) - -| Param | Type | -| --- | --- | -| [opts] | IJptPresentationValidationOptions \| undefined | - - - -### jptPresentationValidationOptions.clone() ⇒ [JptPresentationValidationOptions](#JptPresentationValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JptPresentationValidationOptions](#JptPresentationValidationOptions) - - -### jptPresentationValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JptPresentationValidationOptions](#JptPresentationValidationOptions) - - -### JptPresentationValidationOptions.fromJSON(json) ⇒ [JptPresentationValidationOptions](#JptPresentationValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JptPresentationValidationOptions](#JptPresentationValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JptPresentationValidator -**Kind**: global class - - -### JptPresentationValidator.validate(presentation_jpt, issuer, options, fail_fast) ⇒ [DecodedJptPresentation](#DecodedJptPresentation) -Decodes and validates a Presented [Credential](#Credential) issued as a JPT (JWP Presented Form). A -[DecodedJptPresentation](#DecodedJptPresentation) is returned upon success. - -The following properties are validated according to `options`: -- the holder's proof on the JWP, -- the expiration date, -- the issuance date, -- the semantic structure. - -**Kind**: static method of [JptPresentationValidator](#JptPresentationValidator) - -| Param | Type | -| --- | --- | -| presentation_jpt | [Jpt](#Jpt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JptPresentationValidationOptions](#JptPresentationValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -## JptPresentationValidatorUtils -Utility functions for verifying JPT presentations. - -**Kind**: global class - -* [JptPresentationValidatorUtils](#JptPresentationValidatorUtils) - * [.extractIssuerFromPresentedJpt(presentation)](#JptPresentationValidatorUtils.extractIssuerFromPresentedJpt) ⇒ [CoreDID](#CoreDID) - * [.checkTimeframesWithValidityTimeframe2024(credential, validity_timeframe, status_check)](#JptPresentationValidatorUtils.checkTimeframesWithValidityTimeframe2024) - - - -### JptPresentationValidatorUtils.extractIssuerFromPresentedJpt(presentation) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a credential in JPT representation as DID. -# Errors -If the JPT decoding fails or the issuer field is not a valid DID. - -**Kind**: static method of [JptPresentationValidatorUtils](#JptPresentationValidatorUtils) - -| Param | Type | -| --- | --- | -| presentation | [Jpt](#Jpt) | - - - -### JptPresentationValidatorUtils.checkTimeframesWithValidityTimeframe2024(credential, validity_timeframe, status_check) -Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`. - -**Kind**: static method of [JptPresentationValidatorUtils](#JptPresentationValidatorUtils) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| validity_timeframe | [Timestamp](#Timestamp) \| undefined | -| status_check | [StatusCheck](#StatusCheck) | - - - -## Jwk -**Kind**: global class - -* [Jwk](#Jwk) - * [new Jwk(jwk)](#new_Jwk_new) - * _instance_ - * [.kty()](#Jwk+kty) ⇒ JwkType - * [.use()](#Jwk+use) ⇒ JwkUse \| undefined - * [.keyOps()](#Jwk+keyOps) ⇒ Array.<JwkOperation> - * [.alg()](#Jwk+alg) ⇒ JwsAlgorithm \| undefined - * [.kid()](#Jwk+kid) ⇒ string \| undefined - * [.x5u()](#Jwk+x5u) ⇒ string \| undefined - * [.x5c()](#Jwk+x5c) ⇒ Array.<string> - * [.x5t()](#Jwk+x5t) ⇒ string \| undefined - * [.x5t256()](#Jwk+x5t256) ⇒ string \| undefined - * [.paramsEc()](#Jwk+paramsEc) ⇒ JwkParamsEc \| undefined - * [.paramsOkp()](#Jwk+paramsOkp) ⇒ JwkParamsOkp \| undefined - * [.paramsOct()](#Jwk+paramsOct) ⇒ JwkParamsOct \| undefined - * [.paramsRsa()](#Jwk+paramsRsa) ⇒ JwkParamsRsa \| undefined - * [.paramsMldsa()](#Jwk+paramsMldsa) ⇒ JwkParamsPQ \| undefined - * [.toPublic()](#Jwk+toPublic) ⇒ [Jwk](#Jwk) \| undefined - * [.isPublic()](#Jwk+isPublic) ⇒ boolean - * [.isPrivate()](#Jwk+isPrivate) ⇒ boolean - * [.toJSON()](#Jwk+toJSON) ⇒ any - * [.clone()](#Jwk+clone) ⇒ [Jwk](#Jwk) - * _static_ - * [.fromJSON(json)](#Jwk.fromJSON) ⇒ [Jwk](#Jwk) - - - -### new Jwk(jwk) - -| Param | Type | -| --- | --- | -| jwk | IJwkParams | - - - -### jwk.kty() ⇒ JwkType -Returns the value for the key type parameter (kty). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.use() ⇒ JwkUse \| undefined -Returns the value for the use property (use). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.keyOps() ⇒ Array.<JwkOperation> -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.alg() ⇒ JwsAlgorithm \| undefined -Returns the value for the algorithm property (alg). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.kid() ⇒ string \| undefined -Returns the value of the key ID property (kid). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5u() ⇒ string \| undefined -Returns the value of the X.509 URL property (x5u). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5c() ⇒ Array.<string> -Returns the value of the X.509 certificate chain property (x5c). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5t() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-1 thumbprint property (x5t). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5t256() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-256 thumbprint property (x5t#S256). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsEc() ⇒ JwkParamsEc \| undefined -If this JWK is of kty EC, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsOkp() ⇒ JwkParamsOkp \| undefined -If this JWK is of kty OKP, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsOct() ⇒ JwkParamsOct \| undefined -If this JWK is of kty OCT, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsRsa() ⇒ JwkParamsRsa \| undefined -If this JWK is of kty RSA, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsMldsa() ⇒ JwkParamsPQ \| undefined -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.toPublic() ⇒ [Jwk](#Jwk) \| undefined -Returns a clone of the [Jwk](#Jwk) with _all_ private key components unset. -Nothing is returned when `kty = oct` as this key type is not considered public by this library. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.isPublic() ⇒ boolean -Returns `true` if _all_ private key components of the key are unset, `false` otherwise. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.isPrivate() ⇒ boolean -Returns `true` if _all_ private key components of the key are set, `false` otherwise. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.clone() ⇒ [Jwk](#Jwk) -Deep clones the object. - -**Kind**: instance method of [Jwk](#Jwk) - - -### Jwk.fromJSON(json) ⇒ [Jwk](#Jwk) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Jwk](#Jwk) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwkGenOutput -The result of a key generation in `JwkStorage`. - -**Kind**: global class - -* [JwkGenOutput](#JwkGenOutput) - * [new JwkGenOutput(key_id, jwk)](#new_JwkGenOutput_new) - * _instance_ - * [.jwk()](#JwkGenOutput+jwk) ⇒ [Jwk](#Jwk) - * [.keyId()](#JwkGenOutput+keyId) ⇒ string - * [.toJSON()](#JwkGenOutput+toJSON) ⇒ any - * [.clone()](#JwkGenOutput+clone) ⇒ [JwkGenOutput](#JwkGenOutput) - * _static_ - * [.fromJSON(json)](#JwkGenOutput.fromJSON) ⇒ [JwkGenOutput](#JwkGenOutput) - - - -### new JwkGenOutput(key_id, jwk) - -| Param | Type | -| --- | --- | -| key_id | string | -| jwk | [Jwk](#Jwk) | - - - -### jwkGenOutput.jwk() ⇒ [Jwk](#Jwk) -Returns the generated public [Jwk](#Jwk). - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.keyId() ⇒ string -Returns the key id of the generated [Jwk](#Jwk). - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.clone() ⇒ [JwkGenOutput](#JwkGenOutput) -Deep clones the object. - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### JwkGenOutput.fromJSON(json) ⇒ [JwkGenOutput](#JwkGenOutput) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwkGenOutput](#JwkGenOutput) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwpCredentialOptions -**Kind**: global class - -* [JwpCredentialOptions](#JwpCredentialOptions) - * _instance_ - * [.kid](#JwpCredentialOptions+kid) ⇒ string \| undefined - * [.kid](#JwpCredentialOptions+kid) - * [.toJSON()](#JwpCredentialOptions+toJSON) ⇒ any - * _static_ - * [.fromJSON(value)](#JwpCredentialOptions.fromJSON) ⇒ [JwpCredentialOptions](#JwpCredentialOptions) - - - -### jwpCredentialOptions.kid ⇒ string \| undefined -**Kind**: instance property of [JwpCredentialOptions](#JwpCredentialOptions) - - -### jwpCredentialOptions.kid -**Kind**: instance property of [JwpCredentialOptions](#JwpCredentialOptions) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### jwpCredentialOptions.toJSON() ⇒ any -**Kind**: instance method of [JwpCredentialOptions](#JwpCredentialOptions) - - -### JwpCredentialOptions.fromJSON(value) ⇒ [JwpCredentialOptions](#JwpCredentialOptions) -**Kind**: static method of [JwpCredentialOptions](#JwpCredentialOptions) - -| Param | Type | -| --- | --- | -| value | any | - - - -## JwpIssued -**Kind**: global class - -* [JwpIssued](#JwpIssued) - * _instance_ - * [.toJSON()](#JwpIssued+toJSON) ⇒ any - * [.clone()](#JwpIssued+clone) ⇒ [JwpIssued](#JwpIssued) - * [.encode(serialization)](#JwpIssued+encode) ⇒ string - * [.setProof(proof)](#JwpIssued+setProof) - * [.getProof()](#JwpIssued+getProof) ⇒ Uint8Array - * [.getPayloads()](#JwpIssued+getPayloads) ⇒ [Payloads](#Payloads) - * [.setPayloads(payloads)](#JwpIssued+setPayloads) - * [.getIssuerProtectedHeader()](#JwpIssued+getIssuerProtectedHeader) ⇒ [IssuerProtectedHeader](#IssuerProtectedHeader) - * _static_ - * [.fromJSON(json)](#JwpIssued.fromJSON) ⇒ [JwpIssued](#JwpIssued) - - - -### jwpIssued.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwpIssued](#JwpIssued) - - -### jwpIssued.clone() ⇒ [JwpIssued](#JwpIssued) -Deep clones the object. - -**Kind**: instance method of [JwpIssued](#JwpIssued) - - -### jwpIssued.encode(serialization) ⇒ string -**Kind**: instance method of [JwpIssued](#JwpIssued) - -| Param | Type | -| --- | --- | -| serialization | [SerializationType](#SerializationType) | - - - -### jwpIssued.setProof(proof) -**Kind**: instance method of [JwpIssued](#JwpIssued) - -| Param | Type | -| --- | --- | -| proof | Uint8Array | - - - -### jwpIssued.getProof() ⇒ Uint8Array -**Kind**: instance method of [JwpIssued](#JwpIssued) - - -### jwpIssued.getPayloads() ⇒ [Payloads](#Payloads) -**Kind**: instance method of [JwpIssued](#JwpIssued) - - -### jwpIssued.setPayloads(payloads) -**Kind**: instance method of [JwpIssued](#JwpIssued) - -| Param | Type | -| --- | --- | -| payloads | [Payloads](#Payloads) | - - - -### jwpIssued.getIssuerProtectedHeader() ⇒ [IssuerProtectedHeader](#IssuerProtectedHeader) -**Kind**: instance method of [JwpIssued](#JwpIssued) - - -### JwpIssued.fromJSON(json) ⇒ [JwpIssued](#JwpIssued) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwpIssued](#JwpIssued) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwpPresentationOptions -Options to be set in the JWT claims of a verifiable presentation. - -**Kind**: global class - -* [JwpPresentationOptions](#JwpPresentationOptions) - * [.audience](#JwpPresentationOptions+audience) ⇒ string \| undefined - * [.audience](#JwpPresentationOptions+audience) - * [.nonce](#JwpPresentationOptions+nonce) ⇒ string \| undefined - * [.nonce](#JwpPresentationOptions+nonce) - - - -### jwpPresentationOptions.audience ⇒ string \| undefined -Sets the audience for presentation (`aud` property in JWP Presentation Header). - -**Kind**: instance property of [JwpPresentationOptions](#JwpPresentationOptions) - - -### jwpPresentationOptions.audience -Sets the audience for presentation (`aud` property in JWP Presentation Header). - -**Kind**: instance property of [JwpPresentationOptions](#JwpPresentationOptions) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### jwpPresentationOptions.nonce ⇒ string \| undefined -The nonce to be placed in the Presentation Protected Header. - -**Kind**: instance property of [JwpPresentationOptions](#JwpPresentationOptions) - - -### jwpPresentationOptions.nonce -The nonce to be placed in the Presentation Protected Header. - -**Kind**: instance property of [JwpPresentationOptions](#JwpPresentationOptions) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -## JwpVerificationOptions -**Kind**: global class - -* [JwpVerificationOptions](#JwpVerificationOptions) - * _instance_ - * [.clone()](#JwpVerificationOptions+clone) ⇒ [JwpVerificationOptions](#JwpVerificationOptions) - * [.toJSON()](#JwpVerificationOptions+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#JwpVerificationOptions.fromJSON) ⇒ [JwpVerificationOptions](#JwpVerificationOptions) - * [.new([opts])](#JwpVerificationOptions.new) ⇒ [JwpVerificationOptions](#JwpVerificationOptions) - - - -### jwpVerificationOptions.clone() ⇒ [JwpVerificationOptions](#JwpVerificationOptions) -Deep clones the object. - -**Kind**: instance method of [JwpVerificationOptions](#JwpVerificationOptions) - - -### jwpVerificationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwpVerificationOptions](#JwpVerificationOptions) - - -### JwpVerificationOptions.fromJSON(json) ⇒ [JwpVerificationOptions](#JwpVerificationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwpVerificationOptions](#JwpVerificationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -### JwpVerificationOptions.new([opts]) ⇒ [JwpVerificationOptions](#JwpVerificationOptions) -**Kind**: static method of [JwpVerificationOptions](#JwpVerificationOptions) - -| Param | Type | -| --- | --- | -| [opts] | IJwpVerificationOptions \| undefined | - - - -## Jws -A wrapper around a JSON Web Signature (JWS). - -**Kind**: global class - -* [Jws](#Jws) - * [new Jws(jws_string)](#new_Jws_new) - * [.toString()](#Jws+toString) ⇒ string - - - -### new Jws(jws_string) -Creates a new [Jws](#Jws) from the given string. - - -| Param | Type | -| --- | --- | -| jws_string | string | - - - -### jws.toString() ⇒ string -Returns a clone of the JWS string. - -**Kind**: instance method of [Jws](#Jws) - - -## JwsHeader -**Kind**: global class - -* [JwsHeader](#JwsHeader) - * [new JwsHeader()](#new_JwsHeader_new) - * _instance_ - * [.alg()](#JwsHeader+alg) ⇒ JwsAlgorithm \| undefined - * [.setAlg(value)](#JwsHeader+setAlg) - * [.b64()](#JwsHeader+b64) ⇒ boolean \| undefined - * [.setB64(value)](#JwsHeader+setB64) - * [.custom()](#JwsHeader+custom) ⇒ Record.<string, any> \| undefined - * [.has(claim)](#JwsHeader+has) ⇒ boolean - * [.isDisjoint(other)](#JwsHeader+isDisjoint) ⇒ boolean - * [.jku()](#JwsHeader+jku) ⇒ string \| undefined - * [.setJku(value)](#JwsHeader+setJku) - * [.jwk()](#JwsHeader+jwk) ⇒ [Jwk](#Jwk) \| undefined - * [.setJwk(value)](#JwsHeader+setJwk) - * [.kid()](#JwsHeader+kid) ⇒ string \| undefined - * [.setKid(value)](#JwsHeader+setKid) - * [.x5u()](#JwsHeader+x5u) ⇒ string \| undefined - * [.setX5u(value)](#JwsHeader+setX5u) - * [.x5c()](#JwsHeader+x5c) ⇒ Array.<string> - * [.setX5c(value)](#JwsHeader+setX5c) - * [.x5t()](#JwsHeader+x5t) ⇒ string \| undefined - * [.setX5t(value)](#JwsHeader+setX5t) - * [.x5tS256()](#JwsHeader+x5tS256) ⇒ string \| undefined - * [.setX5tS256(value)](#JwsHeader+setX5tS256) - * [.typ()](#JwsHeader+typ) ⇒ string \| undefined - * [.setTyp(value)](#JwsHeader+setTyp) - * [.cty()](#JwsHeader+cty) ⇒ string \| undefined - * [.setCty(value)](#JwsHeader+setCty) - * [.crit()](#JwsHeader+crit) ⇒ Array.<string> - * [.setCrit(value)](#JwsHeader+setCrit) - * [.url()](#JwsHeader+url) ⇒ string \| undefined - * [.setUrl(value)](#JwsHeader+setUrl) - * [.nonce()](#JwsHeader+nonce) ⇒ string \| undefined - * [.setNonce(value)](#JwsHeader+setNonce) - * [.toJSON()](#JwsHeader+toJSON) ⇒ any - * [.clone()](#JwsHeader+clone) ⇒ [JwsHeader](#JwsHeader) - * _static_ - * [.fromJSON(json)](#JwsHeader.fromJSON) ⇒ [JwsHeader](#JwsHeader) - - - -### new JwsHeader() -Create a new empty [JwsHeader](#JwsHeader). - - - -### jwsHeader.alg() ⇒ JwsAlgorithm \| undefined -Returns the value for the algorithm claim (alg). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setAlg(value) -Sets a value for the algorithm claim (alg). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | JwsAlgorithm | - - - -### jwsHeader.b64() ⇒ boolean \| undefined -Returns the value of the base64url-encode payload claim (b64). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setB64(value) -Sets a value for the base64url-encode payload claim (b64). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsHeader.custom() ⇒ Record.<string, any> \| undefined -Additional header parameters. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.has(claim) ⇒ boolean -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| claim | string | - - - -### jwsHeader.isDisjoint(other) ⇒ boolean -Returns `true` if none of the fields are set in both `self` and `other`. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| other | [JwsHeader](#JwsHeader) | - - - -### jwsHeader.jku() ⇒ string \| undefined -Returns the value of the JWK Set URL claim (jku). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setJku(value) -Sets a value for the JWK Set URL claim (jku). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.jwk() ⇒ [Jwk](#Jwk) \| undefined -Returns the value of the JWK claim (jwk). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setJwk(value) -Sets a value for the JWK claim (jwk). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | [Jwk](#Jwk) | - - - -### jwsHeader.kid() ⇒ string \| undefined -Returns the value of the key ID claim (kid). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setKid(value) -Sets a value for the key ID claim (kid). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5u() ⇒ string \| undefined -Returns the value of the X.509 URL claim (x5u). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5u(value) -Sets a value for the X.509 URL claim (x5u). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5c() ⇒ Array.<string> -Returns the value of the X.509 certificate chain claim (x5c). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5c(value) -Sets values for the X.509 certificate chain claim (x5c). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | Array.<string> | - - - -### jwsHeader.x5t() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-1 thumbprint claim (x5t). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5t(value) -Sets a value for the X.509 certificate SHA-1 thumbprint claim (x5t). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5tS256() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-256 thumbprint claim -(x5t#S256). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5tS256(value) -Sets a value for the X.509 certificate SHA-256 thumbprint claim -(x5t#S256). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.typ() ⇒ string \| undefined -Returns the value of the token type claim (typ). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setTyp(value) -Sets a value for the token type claim (typ). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.cty() ⇒ string \| undefined -Returns the value of the content type claim (cty). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setCty(value) -Sets a value for the content type claim (cty). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.crit() ⇒ Array.<string> -Returns the value of the critical claim (crit). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setCrit(value) -Sets values for the critical claim (crit). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | Array.<string> | - - - -### jwsHeader.url() ⇒ string \| undefined -Returns the value of the url claim (url). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setUrl(value) -Sets a value for the url claim (url). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.nonce() ⇒ string \| undefined -Returns the value of the nonce claim (nonce). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setNonce(value) -Sets a value for the nonce claim (nonce). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.clone() ⇒ [JwsHeader](#JwsHeader) -Deep clones the object. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### JwsHeader.fromJSON(json) ⇒ [JwsHeader](#JwsHeader) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwsSignatureOptions -**Kind**: global class - -* [JwsSignatureOptions](#JwsSignatureOptions) - * [new JwsSignatureOptions([options])](#new_JwsSignatureOptions_new) - * _instance_ - * [.setAttachJwk(value)](#JwsSignatureOptions+setAttachJwk) - * [.setB64(value)](#JwsSignatureOptions+setB64) - * [.setTyp(value)](#JwsSignatureOptions+setTyp) - * [.setCty(value)](#JwsSignatureOptions+setCty) - * [.serUrl(value)](#JwsSignatureOptions+serUrl) - * [.setNonce(value)](#JwsSignatureOptions+setNonce) - * [.setKid(value)](#JwsSignatureOptions+setKid) - * [.setDetachedPayload(value)](#JwsSignatureOptions+setDetachedPayload) - * [.setCustomHeaderParameters(value)](#JwsSignatureOptions+setCustomHeaderParameters) - * [.toJSON()](#JwsSignatureOptions+toJSON) ⇒ any - * [.clone()](#JwsSignatureOptions+clone) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) - * _static_ - * [.fromJSON(json)](#JwsSignatureOptions.fromJSON) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) - - - -### new JwsSignatureOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IJwsSignatureOptions \| undefined | - - - -### jwsSignatureOptions.setAttachJwk(value) -Replace the value of the `attachJwk` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setB64(value) -Replace the value of the `b64` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setTyp(value) -Replace the value of the `typ` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setCty(value) -Replace the value of the `cty` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.serUrl(value) -Replace the value of the `url` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setNonce(value) -Replace the value of the `nonce` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setKid(value) -Replace the value of the `kid` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setDetachedPayload(value) -Replace the value of the `detached_payload` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setCustomHeaderParameters(value) -Add additional header parameters. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | Record.<string, any> | - - - -### jwsSignatureOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - - -### jwsSignatureOptions.clone() ⇒ [JwsSignatureOptions](#JwsSignatureOptions) -Deep clones the object. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - - -### JwsSignatureOptions.fromJSON(json) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwsVerificationOptions -**Kind**: global class - -* [JwsVerificationOptions](#JwsVerificationOptions) - * [new JwsVerificationOptions([options])](#new_JwsVerificationOptions_new) - * _instance_ - * [.setNonce(value)](#JwsVerificationOptions+setNonce) - * [.setMethodScope(value)](#JwsVerificationOptions+setMethodScope) - * [.setMethodId(value)](#JwsVerificationOptions+setMethodId) - * [.toJSON()](#JwsVerificationOptions+toJSON) ⇒ any - * [.clone()](#JwsVerificationOptions+clone) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) - * _static_ - * [.fromJSON(json)](#JwsVerificationOptions.fromJSON) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) - - - -### new JwsVerificationOptions([options]) -Creates a new [JwsVerificationOptions](#JwsVerificationOptions) from the given fields. - - -| Param | Type | -| --- | --- | -| [options] | IJwsVerificationOptions \| undefined | - - - -### jwsVerificationOptions.setNonce(value) -Set the expected value for the `nonce` parameter of the protected header. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsVerificationOptions.setMethodScope(value) -Set the scope of the verification methods that may be used to verify the given JWS. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | [MethodScope](#MethodScope) | - - - -### jwsVerificationOptions.setMethodId(value) -Set the DID URl of the method, whose JWK should be used to verify the JWS. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | [DIDUrl](#DIDUrl) | - - - -### jwsVerificationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - - -### jwsVerificationOptions.clone() ⇒ [JwsVerificationOptions](#JwsVerificationOptions) -Deep clones the object. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - - -### JwsVerificationOptions.fromJSON(json) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Jwt -A wrapper around a JSON Web Token (JWK). - -**Kind**: global class - -* [Jwt](#Jwt) - * [new Jwt(jwt_string)](#new_Jwt_new) - * _instance_ - * [.toString()](#Jwt+toString) ⇒ string - * [.toJSON()](#Jwt+toJSON) ⇒ any - * [.clone()](#Jwt+clone) ⇒ [Jwt](#Jwt) - * _static_ - * [.fromJSON(json)](#Jwt.fromJSON) ⇒ [Jwt](#Jwt) - - - -### new Jwt(jwt_string) -Creates a new [Jwt](#Jwt) from the given string. - - -| Param | Type | -| --- | --- | -| jwt_string | string | - - - -### jwt.toString() ⇒ string -Returns a clone of the JWT string. - -**Kind**: instance method of [Jwt](#Jwt) - - -### jwt.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Jwt](#Jwt) - - -### jwt.clone() ⇒ [Jwt](#Jwt) -Deep clones the object. - -**Kind**: instance method of [Jwt](#Jwt) - - -### Jwt.fromJSON(json) ⇒ [Jwt](#Jwt) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Jwt](#Jwt) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtCredentialValidationOptions -Options to declare validation criteria when validating credentials. - -**Kind**: global class - -* [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - * [new JwtCredentialValidationOptions([options])](#new_JwtCredentialValidationOptions_new) - * _instance_ - * [.toJSON()](#JwtCredentialValidationOptions+toJSON) ⇒ any - * [.clone()](#JwtCredentialValidationOptions+clone) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - * _static_ - * [.fromJSON(json)](#JwtCredentialValidationOptions.fromJSON) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - - -### new JwtCredentialValidationOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IJwtCredentialValidationOptions \| undefined | - - - -### jwtCredentialValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - -### jwtCredentialValidationOptions.clone() ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - -### JwtCredentialValidationOptions.fromJSON(json) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtCredentialValidator -A type for decoding and validating [Credential](#Credential). - -**Kind**: global class - -* [JwtCredentialValidator](#JwtCredentialValidator) - * [new JwtCredentialValidator(signatureVerifier)](#new_JwtCredentialValidator_new) - * _instance_ - * [.validate(credential_jwt, issuer, options, fail_fast)](#JwtCredentialValidator+validate) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.verifySignature(credential, trustedIssuers, options)](#JwtCredentialValidator+verifySignature) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * _static_ - * [.checkExpiresOnOrAfter(credential, timestamp)](#JwtCredentialValidator.checkExpiresOnOrAfter) - * [.checkIssuedOnOrBefore(credential, timestamp)](#JwtCredentialValidator.checkIssuedOnOrBefore) - * [.checkSubjectHolderRelationship(credential, holder, relationship)](#JwtCredentialValidator.checkSubjectHolderRelationship) - * [.checkStatus(credential, trustedIssuers, statusCheck)](#JwtCredentialValidator.checkStatus) - * [.checkStatusWithStatusList2021(credential, status_list, status_check)](#JwtCredentialValidator.checkStatusWithStatusList2021) - * [.extractIssuer(credential)](#JwtCredentialValidator.extractIssuer) ⇒ [CoreDID](#CoreDID) - * [.extractIssuerFromJwt(credential)](#JwtCredentialValidator.extractIssuerFromJwt) ⇒ [CoreDID](#CoreDID) - - - -### new JwtCredentialValidator(signatureVerifier) -Creates a new [JwtCredentialValidator](#JwtCredentialValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtCredentialValidator.validate(credential_jwt, issuer, options, fail_fast) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decodes and validates a [Credential](#Credential) issued as a JWS. A [DecodedJwtCredential](#DecodedJwtCredential) is returned upon -success. - -The following properties are validated according to `options`: -- the issuer's signature on the JWS, -- the expiration date, -- the issuance date, -- the semantic structure. - -# Warning -The lack of an error returned from this method is in of itself not enough to conclude that the credential can be -trusted. This section contains more information on additional checks that should be carried out before and after -calling this method. - -## The state of the issuer's DID Document -The caller must ensure that `issuer` represents an up-to-date DID Document. - -## Properties that are not validated - There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: -`proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. -These should be manually checked after validation, according to your requirements. - -# Errors -An error is returned whenever a validated condition is not satisfied. - -**Kind**: instance method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential_jwt | [Jwt](#Jwt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -### jwtCredentialValidator.verifySignature(credential, trustedIssuers, options) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decode and verify the JWS signature of a [Credential](#Credential) issued as a JWT using the DID Document of a trusted -issuer. - -A [DecodedJwtCredential](#DecodedJwtCredential) is returned upon success. - -# Warning -The caller must ensure that the DID Documents of the trusted issuers are up-to-date. - -## Proofs - Only the JWS signature is verified. If the [Credential](#Credential) contains a `proof` property this will not be -verified by this method. - -# Errors -This method immediately returns an error if -the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt -to verify the credential's signature will be made and an error is returned upon failure. - -**Kind**: instance method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Jwt](#Jwt) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | - - - -### JwtCredentialValidator.checkExpiresOnOrAfter(credential, timestamp) -Validate that the credential expires on or after the specified timestamp. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| timestamp | [Timestamp](#Timestamp) | - - - -### JwtCredentialValidator.checkIssuedOnOrBefore(credential, timestamp) -Validate that the credential is issued on or before the specified timestamp. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| timestamp | [Timestamp](#Timestamp) | - - - -### JwtCredentialValidator.checkSubjectHolderRelationship(credential, holder, relationship) -Validate that the relationship between the `holder` and the credential subjects is in accordance with -`relationship`. The `holder` parameter is expected to be the URL of the holder. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| holder | string | -| relationship | [SubjectHolderRelationship](#SubjectHolderRelationship) | - - - -### JwtCredentialValidator.checkStatus(credential, trustedIssuers, statusCheck) -Checks whether the credential status has been revoked. - -Only supports `RevocationBitmap2022`. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| statusCheck | [StatusCheck](#StatusCheck) | - - - -### JwtCredentialValidator.checkStatusWithStatusList2021(credential, status_list, status_check) -Checks wheter the credential status has been revoked using `StatusList2021`. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| status_list | [StatusList2021Credential](#StatusList2021Credential) | -| status_check | [StatusCheck](#StatusCheck) | - - - -### JwtCredentialValidator.extractIssuer(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a [Credential](#Credential) as a DID. - -### Errors - -Fails if the issuer field is not a valid DID. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | - - - -### JwtCredentialValidator.extractIssuerFromJwt(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a credential in JWT representation as DID. - -# Errors - -If the JWT decoding fails or the issuer field is not a valid DID. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Jwt](#Jwt) | - - - -## JwtDomainLinkageValidator -A validator for a Domain Linkage Configuration and Credentials. - -**Kind**: global class - -* [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - * [new JwtDomainLinkageValidator(signatureVerifier)](#new_JwtDomainLinkageValidator_new) - * [.validateLinkage(issuer, configuration, domain, options)](#JwtDomainLinkageValidator+validateLinkage) - * [.validateCredential(issuer, credentialJwt, domain, options)](#JwtDomainLinkageValidator+validateCredential) - - - -### new JwtDomainLinkageValidator(signatureVerifier) -Creates a new [JwtDomainLinkageValidator](#JwtDomainLinkageValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtDomainLinkageValidator.validateLinkage(issuer, configuration, domain, options) -Validates the linkage between a domain and a DID. -[DomainLinkageConfiguration](#DomainLinkageConfiguration) is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). - -Linkage is valid if no error is thrown. - -# Note: -- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - is supported. -- Only the Credential issued by `issuer` is verified. - -# Errors - - - Semantic structure of `configuration` is invalid. - - `configuration` includes multiple credentials issued by `issuer`. - - Validation of the matched Domain Linkage Credential fails. - -**Kind**: instance method of [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - -| Param | Type | -| --- | --- | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| configuration | [DomainLinkageConfiguration](#DomainLinkageConfiguration) | -| domain | string | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | - - - -### jwtDomainLinkageValidator.validateCredential(issuer, credentialJwt, domain, options) -Validates a [Domain Linkage Credential](https://identity.foundation/.well-known/resources/did-configuration/#domain-linkage-credential). - -Error will be thrown in case the validation fails. - -**Kind**: instance method of [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - -| Param | Type | -| --- | --- | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| credentialJwt | [Jwt](#Jwt) | -| domain | string | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | - - - -## JwtPresentationOptions -**Kind**: global class - -* [JwtPresentationOptions](#JwtPresentationOptions) - * [new JwtPresentationOptions([options])](#new_JwtPresentationOptions_new) - * _instance_ - * [.toJSON()](#JwtPresentationOptions+toJSON) ⇒ any - * [.clone()](#JwtPresentationOptions+clone) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) - * _static_ - * [.fromJSON(json)](#JwtPresentationOptions.fromJSON) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) - - - -### new JwtPresentationOptions([options]) -Creates a new [JwtPresentationOptions](#JwtPresentationOptions) from the given fields. - -Throws an error if any of the options are invalid. - - -| Param | Type | -| --- | --- | -| [options] | IJwtPresentationOptions \| undefined | - - - -### jwtPresentationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtPresentationOptions](#JwtPresentationOptions) - - -### jwtPresentationOptions.clone() ⇒ [JwtPresentationOptions](#JwtPresentationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtPresentationOptions](#JwtPresentationOptions) - - -### JwtPresentationOptions.fromJSON(json) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtPresentationOptions](#JwtPresentationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtPresentationValidationOptions -Options to declare validation criteria when validating presentation. - -**Kind**: global class - -* [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - * [new JwtPresentationValidationOptions([options])](#new_JwtPresentationValidationOptions_new) - * _instance_ - * [.toJSON()](#JwtPresentationValidationOptions+toJSON) ⇒ any - * [.clone()](#JwtPresentationValidationOptions+clone) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - * _static_ - * [.fromJSON(json)](#JwtPresentationValidationOptions.fromJSON) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - - -### new JwtPresentationValidationOptions([options]) -Creates a new [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) from the given fields. - -Throws an error if any of the options are invalid. - - -| Param | Type | -| --- | --- | -| [options] | IJwtPresentationValidationOptions \| undefined | - - - -### jwtPresentationValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - -### jwtPresentationValidationOptions.clone() ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - -### JwtPresentationValidationOptions.fromJSON(json) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtPresentationValidator -**Kind**: global class - -* [JwtPresentationValidator](#JwtPresentationValidator) - * [new JwtPresentationValidator(signatureVerifier)](#new_JwtPresentationValidator_new) - * _instance_ - * [.validate(presentationJwt, holder, validation_options)](#JwtPresentationValidator+validate) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) - * _static_ - * [.checkStructure(presentation)](#JwtPresentationValidator.checkStructure) - * [.extractHolder(presentation)](#JwtPresentationValidator.extractHolder) ⇒ [CoreDID](#CoreDID) - - - -### new JwtPresentationValidator(signatureVerifier) -Creates a new [JwtPresentationValidator](#JwtPresentationValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtPresentationValidator.validate(presentationJwt, holder, validation_options) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) -Validates a [Presentation](#Presentation) encoded as a [Jwt](#Jwt). - -The following properties are validated according to `options`: -- the JWT can be decoded into a semantically valid presentation. -- the expiration and issuance date contained in the JWT claims. -- the holder's signature. - -Validation is done with respect to the properties set in `options`. - -# Warning - -* This method does NOT validate the constituent credentials and therefore also not the relationship between the -credentials' subjects and the presentation holder. This can be done with [JwtCredentialValidationOptions](#JwtCredentialValidationOptions). -* The lack of an error returned from this method is in of itself not enough to conclude that the presentation can -be trusted. This section contains more information on additional checks that should be carried out before and -after calling this method. - -## The state of the supplied DID Documents. - -The caller must ensure that the DID Documents in `holder` are up-to-date. - -# Errors - -An error is returned whenever a validated condition is not satisfied or when decoding fails. - -**Kind**: instance method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentationJwt | [Jwt](#Jwt) | -| holder | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| validation_options | [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) | - - - -### JwtPresentationValidator.checkStructure(presentation) -Validates the semantic structure of the [Presentation](#Presentation). - -**Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentation | [Presentation](#Presentation) | - - - -### JwtPresentationValidator.extractHolder(presentation) ⇒ [CoreDID](#CoreDID) -Attempt to extract the holder of the presentation. - -# Errors: -* If deserialization/decoding of the presentation fails. -* If the holder can't be parsed as DIDs. - -**Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentation | [Jwt](#Jwt) | - - - -## KeyBindingJWTValidationOptions -Options to declare validation criteria when validating credentials. - -**Kind**: global class - -* [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - * [new KeyBindingJWTValidationOptions([options])](#new_KeyBindingJWTValidationOptions_new) - * _instance_ - * [.toJSON()](#KeyBindingJWTValidationOptions+toJSON) ⇒ any - * [.clone()](#KeyBindingJWTValidationOptions+clone) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - * _static_ - * [.fromJSON(json)](#KeyBindingJWTValidationOptions.fromJSON) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - - -### new KeyBindingJWTValidationOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IKeyBindingJWTValidationOptions \| undefined | - - - -### keyBindingJWTValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - -### keyBindingJWTValidationOptions.clone() ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) -Deep clones the object. - -**Kind**: instance method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - -### KeyBindingJWTValidationOptions.fromJSON(json) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## KeyBindingJwtClaims -Claims set for key binding JWT. - -**Kind**: global class - -* [KeyBindingJwtClaims](#KeyBindingJwtClaims) - * [new KeyBindingJwtClaims(jwt, disclosures, nonce, aud, [issued_at], [custom_properties])](#new_KeyBindingJwtClaims_new) - * _instance_ - * [.toString()](#KeyBindingJwtClaims+toString) ⇒ string - * [.iat()](#KeyBindingJwtClaims+iat) ⇒ bigint - * [.aud()](#KeyBindingJwtClaims+aud) ⇒ string - * [.nonce()](#KeyBindingJwtClaims+nonce) ⇒ string - * [.sdHash()](#KeyBindingJwtClaims+sdHash) ⇒ string - * [.customProperties()](#KeyBindingJwtClaims+customProperties) ⇒ Record.<string, any> - * [.toJSON()](#KeyBindingJwtClaims+toJSON) ⇒ any - * [.clone()](#KeyBindingJwtClaims+clone) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - * _static_ - * [.keyBindingJwtHeaderTyp()](#KeyBindingJwtClaims.keyBindingJwtHeaderTyp) ⇒ string - * [.fromJSON(json)](#KeyBindingJwtClaims.fromJSON) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - - -### new KeyBindingJwtClaims(jwt, disclosures, nonce, aud, [issued_at], [custom_properties]) -Creates a new [`KeyBindingJwtClaims`]. -When `issued_at` is left as None, it will automatically default to the current time. - -# Error -When `issued_at` is set to `None` and the system returns time earlier than `SystemTime::UNIX_EPOCH`. - - -| Param | Type | -| --- | --- | -| jwt | string | -| disclosures | Array.<string> | -| nonce | string | -| aud | string | -| [issued_at] | [Timestamp](#Timestamp) \| undefined | -| [custom_properties] | Record.<string, any> \| undefined | - - - -### keyBindingJwtClaims.toString() ⇒ string -Returns a string representation of the claims. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.iat() ⇒ bigint -Returns a copy of the issued at `iat` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.aud() ⇒ string -Returns a copy of the audience `aud` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.nonce() ⇒ string -Returns a copy of the `nonce` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.sdHash() ⇒ string -Returns a copy of the `sd_hash` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.customProperties() ⇒ Record.<string, any> -Returns a copy of the custom properties. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.clone() ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Deep clones the object. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### KeyBindingJwtClaims.keyBindingJwtHeaderTyp() ⇒ string -Returns the value of the `typ` property of the JWT header according to -https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-key-binding-jwt - -**Kind**: static method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### KeyBindingJwtClaims.fromJSON(json) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Deserializes an instance from a JSON object. - -**Kind**: static method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - -| Param | Type | -| --- | --- | -| json | any | - - - -## LinkedDomainService -**Kind**: global class - -* [LinkedDomainService](#LinkedDomainService) - * [new LinkedDomainService(options)](#new_LinkedDomainService_new) - * _instance_ - * [.domains()](#LinkedDomainService+domains) ⇒ Array.<string> - * [.toService()](#LinkedDomainService+toService) ⇒ [Service](#Service) - * [.clone()](#LinkedDomainService+clone) ⇒ [LinkedDomainService](#LinkedDomainService) - * _static_ - * [.fromService(service)](#LinkedDomainService.fromService) ⇒ [LinkedDomainService](#LinkedDomainService) - * [.isValid(service)](#LinkedDomainService.isValid) ⇒ boolean - - - -### new LinkedDomainService(options) -Constructs a new [LinkedDomainService](#LinkedDomainService) that wraps a spec compliant [Linked Domain Service Endpoint](https://identity.foundation/.well-known/resources/did-configuration/#linked-domain-service-endpoint). - -Domain URLs must include the `https` scheme in order to pass the domain linkage validation. - - -| Param | Type | -| --- | --- | -| options | ILinkedDomainService | - - - -### linkedDomainService.domains() ⇒ Array.<string> -Returns the domains contained in the Linked Domain Service. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### linkedDomainService.toService() ⇒ [Service](#Service) -Returns the inner service which can be added to a DID Document. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### linkedDomainService.clone() ⇒ [LinkedDomainService](#LinkedDomainService) -Deep clones the object. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### LinkedDomainService.fromService(service) ⇒ [LinkedDomainService](#LinkedDomainService) -Creates a new [LinkedDomainService](#LinkedDomainService) from a [Service](#Service). - -# Error - -Errors if `service` is not a valid Linked Domain Service. - -**Kind**: static method of [LinkedDomainService](#LinkedDomainService) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### LinkedDomainService.isValid(service) ⇒ boolean -Returns `true` if a [Service](#Service) is a valid Linked Domain Service. - -**Kind**: static method of [LinkedDomainService](#LinkedDomainService) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -## MethodData -Supported verification method data formats. - -**Kind**: global class - -* [MethodData](#MethodData) - * _instance_ - * [.tryCustom()](#MethodData+tryCustom) ⇒ [CustomMethodData](#CustomMethodData) - * [.tryDecode()](#MethodData+tryDecode) ⇒ Uint8Array - * [.tryPublicKeyJwk()](#MethodData+tryPublicKeyJwk) ⇒ [Jwk](#Jwk) - * [.toJSON()](#MethodData+toJSON) ⇒ any - * [.clone()](#MethodData+clone) ⇒ [MethodData](#MethodData) - * _static_ - * [.newBase58(data)](#MethodData.newBase58) ⇒ [MethodData](#MethodData) - * [.newMultibase(data)](#MethodData.newMultibase) ⇒ [MethodData](#MethodData) - * [.newJwk(key)](#MethodData.newJwk) ⇒ [MethodData](#MethodData) - * [.newCustom(name, data)](#MethodData.newCustom) ⇒ [MethodData](#MethodData) - * [.fromJSON(json)](#MethodData.fromJSON) ⇒ [MethodData](#MethodData) - - - -### methodData.tryCustom() ⇒ [CustomMethodData](#CustomMethodData) -Returns the wrapped custom method data format is `Custom`. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.tryDecode() ⇒ Uint8Array -Returns a `Uint8Array` containing the decoded bytes of the [MethodData](#MethodData). - -This is generally a public key identified by a [MethodData](#MethodData) value. - -### Errors -Decoding can fail if [MethodData](#MethodData) has invalid content or cannot be -represented as a vector of bytes. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.tryPublicKeyJwk() ⇒ [Jwk](#Jwk) -Returns the wrapped [Jwk](#Jwk) if the format is `PublicKeyJwk`. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.clone() ⇒ [MethodData](#MethodData) -Deep clones the object. - -**Kind**: instance method of [MethodData](#MethodData) - - -### MethodData.newBase58(data) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant with Base58-BTC encoded content. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -### MethodData.newMultibase(data) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant with Multibase-encoded content. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -### MethodData.newJwk(key) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant consisting of the given `key`. - -### Errors -An error is thrown if the given `key` contains any private components. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| key | [Jwk](#Jwk) | - - - -### MethodData.newCustom(name, data) ⇒ [MethodData](#MethodData) -Creates a new custom [MethodData](#MethodData). - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| name | string | -| data | any | - - - -### MethodData.fromJSON(json) ⇒ [MethodData](#MethodData) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| json | any | - - - -## MethodDigest -Unique identifier of a [VerificationMethod](#VerificationMethod). - -NOTE: -This class does not have a JSON representation, -use the methods `pack` and `unpack` instead. - -**Kind**: global class - -* [MethodDigest](#MethodDigest) - * [new MethodDigest(verification_method)](#new_MethodDigest_new) - * _instance_ - * [.pack()](#MethodDigest+pack) ⇒ Uint8Array - * [.clone()](#MethodDigest+clone) ⇒ [MethodDigest](#MethodDigest) - * _static_ - * [.unpack(bytes)](#MethodDigest.unpack) ⇒ [MethodDigest](#MethodDigest) - - - -### new MethodDigest(verification_method) - -| Param | Type | -| --- | --- | -| verification_method | [VerificationMethod](#VerificationMethod) | - - - -### methodDigest.pack() ⇒ Uint8Array -Packs [MethodDigest](#MethodDigest) into bytes. - -**Kind**: instance method of [MethodDigest](#MethodDigest) - - -### methodDigest.clone() ⇒ [MethodDigest](#MethodDigest) -Deep clones the object. - -**Kind**: instance method of [MethodDigest](#MethodDigest) - - -### MethodDigest.unpack(bytes) ⇒ [MethodDigest](#MethodDigest) -Unpacks bytes into [MethodDigest](#MethodDigest). - -**Kind**: static method of [MethodDigest](#MethodDigest) - -| Param | Type | -| --- | --- | -| bytes | Uint8Array | - - - -## MethodScope -Supported verification method types. - -**Kind**: global class - -* [MethodScope](#MethodScope) - * _instance_ - * [.toString()](#MethodScope+toString) ⇒ string - * [.toJSON()](#MethodScope+toJSON) ⇒ any - * [.clone()](#MethodScope+clone) ⇒ [MethodScope](#MethodScope) - * _static_ - * [.VerificationMethod()](#MethodScope.VerificationMethod) ⇒ [MethodScope](#MethodScope) - * [.Authentication()](#MethodScope.Authentication) ⇒ [MethodScope](#MethodScope) - * [.AssertionMethod()](#MethodScope.AssertionMethod) ⇒ [MethodScope](#MethodScope) - * [.KeyAgreement()](#MethodScope.KeyAgreement) ⇒ [MethodScope](#MethodScope) - * [.CapabilityDelegation()](#MethodScope.CapabilityDelegation) ⇒ [MethodScope](#MethodScope) - * [.CapabilityInvocation()](#MethodScope.CapabilityInvocation) ⇒ [MethodScope](#MethodScope) - * [.fromJSON(json)](#MethodScope.fromJSON) ⇒ [MethodScope](#MethodScope) - - - -### methodScope.toString() ⇒ string -Returns the [MethodScope](#MethodScope) as a string. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### methodScope.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### methodScope.clone() ⇒ [MethodScope](#MethodScope) -Deep clones the object. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### MethodScope.VerificationMethod() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.Authentication() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.AssertionMethod() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.KeyAgreement() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.CapabilityDelegation() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.CapabilityInvocation() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.fromJSON(json) ⇒ [MethodScope](#MethodScope) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodScope](#MethodScope) - -| Param | Type | -| --- | --- | -| json | any | - - - -## MethodType -Supported verification method types. - -**Kind**: global class - -* [MethodType](#MethodType) - * _instance_ - * [.toString()](#MethodType+toString) ⇒ string - * [.toJSON()](#MethodType+toJSON) ⇒ any - * [.clone()](#MethodType+clone) ⇒ [MethodType](#MethodType) - * _static_ - * [.Ed25519VerificationKey2018()](#MethodType.Ed25519VerificationKey2018) ⇒ [MethodType](#MethodType) - * [.X25519KeyAgreementKey2019()](#MethodType.X25519KeyAgreementKey2019) ⇒ [MethodType](#MethodType) - * ~~[.JsonWebKey()](#MethodType.JsonWebKey)~~ - * [.JsonWebKey2020()](#MethodType.JsonWebKey2020) ⇒ [MethodType](#MethodType) - * [.custom(type_)](#MethodType.custom) ⇒ [MethodType](#MethodType) - * [.fromJSON(json)](#MethodType.fromJSON) ⇒ [MethodType](#MethodType) - - - -### methodType.toString() ⇒ string -Returns the [MethodType](#MethodType) as a string. - -**Kind**: instance method of [MethodType](#MethodType) - - -### methodType.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodType](#MethodType) - - -### methodType.clone() ⇒ [MethodType](#MethodType) -Deep clones the object. - -**Kind**: instance method of [MethodType](#MethodType) - - -### MethodType.Ed25519VerificationKey2018() ⇒ [MethodType](#MethodType) -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.X25519KeyAgreementKey2019() ⇒ [MethodType](#MethodType) -**Kind**: static method of [MethodType](#MethodType) - - -### ~~MethodType.JsonWebKey()~~ -***Deprecated*** - -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.JsonWebKey2020() ⇒ [MethodType](#MethodType) -A verification method for use with JWT verification as prescribed by the [Jwk](#Jwk) -in the `publicKeyJwk` entry. - -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.custom(type_) ⇒ [MethodType](#MethodType) -A custom method. - -**Kind**: static method of [MethodType](#MethodType) - -| Param | Type | -| --- | --- | -| type_ | string | - - - -### MethodType.fromJSON(json) ⇒ [MethodType](#MethodType) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodType](#MethodType) - -| Param | Type | -| --- | --- | -| json | any | - - - -## PayloadEntry -**Kind**: global class - -* [PayloadEntry](#PayloadEntry) - * [.1](#PayloadEntry+1) ⇒ [PayloadType](#PayloadType) - * [.1](#PayloadEntry+1) - * [.value](#PayloadEntry+value) - * [.value](#PayloadEntry+value) ⇒ any - - - -### payloadEntry.1 ⇒ [PayloadType](#PayloadType) -**Kind**: instance property of [PayloadEntry](#PayloadEntry) - - -### payloadEntry.1 -**Kind**: instance property of [PayloadEntry](#PayloadEntry) - -| Param | Type | -| --- | --- | -| arg0 | [PayloadType](#PayloadType) | - - - -### payloadEntry.value -**Kind**: instance property of [PayloadEntry](#PayloadEntry) - -| Param | Type | -| --- | --- | -| value | any | - - - -### payloadEntry.value ⇒ any -**Kind**: instance property of [PayloadEntry](#PayloadEntry) - - -## Payloads -**Kind**: global class - -* [Payloads](#Payloads) - * [new Payloads(entries)](#new_Payloads_new) - * _instance_ - * [.toJSON()](#Payloads+toJSON) ⇒ any - * [.clone()](#Payloads+clone) ⇒ [Payloads](#Payloads) - * [.getValues()](#Payloads+getValues) ⇒ Array.<any> - * [.getUndisclosedIndexes()](#Payloads+getUndisclosedIndexes) ⇒ Uint32Array - * [.getDisclosedIndexes()](#Payloads+getDisclosedIndexes) ⇒ Uint32Array - * [.getUndisclosedPayloads()](#Payloads+getUndisclosedPayloads) ⇒ Array.<any> - * [.getDisclosedPayloads()](#Payloads+getDisclosedPayloads) ⇒ [Payloads](#Payloads) - * [.setUndisclosed(index)](#Payloads+setUndisclosed) - * [.replacePayloadAtIndex(index, value)](#Payloads+replacePayloadAtIndex) ⇒ any - * _static_ - * [.fromJSON(json)](#Payloads.fromJSON) ⇒ [Payloads](#Payloads) - * [.newFromValues(values)](#Payloads.newFromValues) ⇒ [Payloads](#Payloads) - - - -### new Payloads(entries) - -| Param | Type | -| --- | --- | -| entries | [Array.<PayloadEntry>](#PayloadEntry) | - - - -### payloads.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.clone() ⇒ [Payloads](#Payloads) -Deep clones the object. - -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.getValues() ⇒ Array.<any> -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.getUndisclosedIndexes() ⇒ Uint32Array -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.getDisclosedIndexes() ⇒ Uint32Array -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.getUndisclosedPayloads() ⇒ Array.<any> -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.getDisclosedPayloads() ⇒ [Payloads](#Payloads) -**Kind**: instance method of [Payloads](#Payloads) - - -### payloads.setUndisclosed(index) -**Kind**: instance method of [Payloads](#Payloads) - -| Param | Type | -| --- | --- | -| index | number | - - - -### payloads.replacePayloadAtIndex(index, value) ⇒ any -**Kind**: instance method of [Payloads](#Payloads) - -| Param | Type | -| --- | --- | -| index | number | -| value | any | - - - -### Payloads.fromJSON(json) ⇒ [Payloads](#Payloads) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Payloads](#Payloads) - -| Param | Type | -| --- | --- | -| json | any | - - - -### Payloads.newFromValues(values) ⇒ [Payloads](#Payloads) -**Kind**: static method of [Payloads](#Payloads) - -| Param | Type | -| --- | --- | -| values | Array.<any> | - - - -## Presentation -**Kind**: global class - -* [Presentation](#Presentation) - * [new Presentation(values)](#new_Presentation_new) - * _instance_ - * [.context()](#Presentation+context) ⇒ Array.<(string\|Record.<string, any>)> - * [.id()](#Presentation+id) ⇒ string \| undefined - * [.type()](#Presentation+type) ⇒ Array.<string> - * [.verifiableCredential()](#Presentation+verifiableCredential) ⇒ [Array.<UnknownCredential>](#UnknownCredential) - * [.holder()](#Presentation+holder) ⇒ string - * [.refreshService()](#Presentation+refreshService) ⇒ Array.<RefreshService> - * [.termsOfUse()](#Presentation+termsOfUse) ⇒ Array.<Policy> - * [.proof()](#Presentation+proof) ⇒ [Proof](#Proof) \| undefined - * [.setProof([proof])](#Presentation+setProof) - * [.properties()](#Presentation+properties) ⇒ Map.<string, any> - * [.toJSON()](#Presentation+toJSON) ⇒ any - * [.clone()](#Presentation+clone) ⇒ [Presentation](#Presentation) - * _static_ - * [.BaseContext()](#Presentation.BaseContext) ⇒ string - * [.BaseType()](#Presentation.BaseType) ⇒ string - * [.fromJSON(json)](#Presentation.fromJSON) ⇒ [Presentation](#Presentation) - - - -### new Presentation(values) -Constructs a new presentation. - - -| Param | Type | -| --- | --- | -| values | IPresentation | - - - -### presentation.context() ⇒ Array.<(string\|Record.<string, any>)> -Returns a copy of the JSON-LD context(s) applicable to the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.id() ⇒ string \| undefined -Returns a copy of the unique `URI` identifying the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.type() ⇒ Array.<string> -Returns a copy of the URIs defining the type of the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.verifiableCredential() ⇒ [Array.<UnknownCredential>](#UnknownCredential) -Returns the JWT credentials expressing the claims of the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.holder() ⇒ string -Returns a copy of the URI of the entity that generated the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.refreshService() ⇒ Array.<RefreshService> -Returns a copy of the service(s) used to refresh an expired [Credential](#Credential) in the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.termsOfUse() ⇒ Array.<Policy> -Returns a copy of the terms-of-use specified by the presentation holder - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.proof() ⇒ [Proof](#Proof) \| undefined -Optional cryptographic proof, unrelated to JWT. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.setProof([proof]) -Sets the proof property of the [Presentation](#Presentation). - -Note that this proof is not related to JWT. - -**Kind**: instance method of [Presentation](#Presentation) - -| Param | Type | -| --- | --- | -| [proof] | [Proof](#Proof) \| undefined | - - - -### presentation.properties() ⇒ Map.<string, any> -Returns a copy of the miscellaneous properties on the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.clone() ⇒ [Presentation](#Presentation) -Deep clones the object. - -**Kind**: instance method of [Presentation](#Presentation) - - -### Presentation.BaseContext() ⇒ string -Returns the base JSON-LD context. - -**Kind**: static method of [Presentation](#Presentation) - - -### Presentation.BaseType() ⇒ string -Returns the base type. - -**Kind**: static method of [Presentation](#Presentation) - - -### Presentation.fromJSON(json) ⇒ [Presentation](#Presentation) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Presentation](#Presentation) - -| Param | Type | -| --- | --- | -| json | any | - - - -## PresentationProtectedHeader -**Kind**: global class - -* [PresentationProtectedHeader](#PresentationProtectedHeader) - * [.alg](#PresentationProtectedHeader+alg) ⇒ [PresentationProofAlgorithm](#PresentationProofAlgorithm) - * [.alg](#PresentationProtectedHeader+alg) - * [.kid](#PresentationProtectedHeader+kid) ⇒ string \| undefined - * [.kid](#PresentationProtectedHeader+kid) - * [.aud](#PresentationProtectedHeader+aud) ⇒ string \| undefined - * [.aud](#PresentationProtectedHeader+aud) - * [.nonce](#PresentationProtectedHeader+nonce) ⇒ string \| undefined - * [.nonce](#PresentationProtectedHeader+nonce) - - - -### presentationProtectedHeader.alg ⇒ [PresentationProofAlgorithm](#PresentationProofAlgorithm) -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - - -### presentationProtectedHeader.alg -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - -| Param | Type | -| --- | --- | -| arg0 | [PresentationProofAlgorithm](#PresentationProofAlgorithm) | - - - -### presentationProtectedHeader.kid ⇒ string \| undefined -ID for the key used for the JWP. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - - -### presentationProtectedHeader.kid -ID for the key used for the JWP. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### presentationProtectedHeader.aud ⇒ string \| undefined -Who have received the JPT. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - - -### presentationProtectedHeader.aud -Who have received the JPT. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -### presentationProtectedHeader.nonce ⇒ string \| undefined -For replay attacks. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - - -### presentationProtectedHeader.nonce -For replay attacks. - -**Kind**: instance property of [PresentationProtectedHeader](#PresentationProtectedHeader) - -| Param | Type | -| --- | --- | -| [arg0] | string \| undefined | - - - -## Proof -Represents a cryptographic proof that can be used to validate verifiable credentials and -presentations. - -This representation does not inherently implement any standard; instead, it -can be utilized to implement standards or user-defined proofs. The presence of the -`type` field is necessary to accommodate different types of cryptographic proofs. - -Note that this proof is not related to JWT and can be used in combination or as an alternative -to it. - -**Kind**: global class - -* [Proof](#Proof) - * [new Proof(type_, properties)](#new_Proof_new) - * _instance_ - * [.type()](#Proof+type) ⇒ string - * [.properties()](#Proof+properties) ⇒ any - * [.toJSON()](#Proof+toJSON) ⇒ any - * [.clone()](#Proof+clone) ⇒ [Proof](#Proof) - * _static_ - * [.fromJSON(json)](#Proof.fromJSON) ⇒ [Proof](#Proof) - - - -### new Proof(type_, properties) - -| Param | Type | -| --- | --- | -| type_ | string | -| properties | any | - - - -### proof.type() ⇒ string -Returns the type of proof. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.properties() ⇒ any -Returns the properties of the proof. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.clone() ⇒ [Proof](#Proof) -Deep clones the object. - -**Kind**: instance method of [Proof](#Proof) - - -### Proof.fromJSON(json) ⇒ [Proof](#Proof) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Proof](#Proof) - -| Param | Type | -| --- | --- | -| json | any | - - - -## ProofUpdateCtx -**Kind**: global class - -* [ProofUpdateCtx](#ProofUpdateCtx) - * [.old_start_validity_timeframe](#ProofUpdateCtx+old_start_validity_timeframe) ⇒ Uint8Array - * [.old_start_validity_timeframe](#ProofUpdateCtx+old_start_validity_timeframe) - * [.new_start_validity_timeframe](#ProofUpdateCtx+new_start_validity_timeframe) ⇒ Uint8Array - * [.new_start_validity_timeframe](#ProofUpdateCtx+new_start_validity_timeframe) - * [.old_end_validity_timeframe](#ProofUpdateCtx+old_end_validity_timeframe) ⇒ Uint8Array - * [.old_end_validity_timeframe](#ProofUpdateCtx+old_end_validity_timeframe) - * [.new_end_validity_timeframe](#ProofUpdateCtx+new_end_validity_timeframe) ⇒ Uint8Array - * [.new_end_validity_timeframe](#ProofUpdateCtx+new_end_validity_timeframe) - * [.index_start_validity_timeframe](#ProofUpdateCtx+index_start_validity_timeframe) ⇒ number - * [.index_start_validity_timeframe](#ProofUpdateCtx+index_start_validity_timeframe) - * [.index_end_validity_timeframe](#ProofUpdateCtx+index_end_validity_timeframe) ⇒ number - * [.index_end_validity_timeframe](#ProofUpdateCtx+index_end_validity_timeframe) - * [.number_of_signed_messages](#ProofUpdateCtx+number_of_signed_messages) ⇒ number - * [.number_of_signed_messages](#ProofUpdateCtx+number_of_signed_messages) - - - -### proofUpdateCtx.old\_start\_validity\_timeframe ⇒ Uint8Array -Old `startValidityTimeframe` value - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.old\_start\_validity\_timeframe -Old `startValidityTimeframe` value - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | Uint8Array | - - - -### proofUpdateCtx.new\_start\_validity\_timeframe ⇒ Uint8Array -New `startValidityTimeframe` value to be signed - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.new\_start\_validity\_timeframe -New `startValidityTimeframe` value to be signed - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | Uint8Array | - - - -### proofUpdateCtx.old\_end\_validity\_timeframe ⇒ Uint8Array -Old `endValidityTimeframe` value - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.old\_end\_validity\_timeframe -Old `endValidityTimeframe` value - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | Uint8Array | - - - -### proofUpdateCtx.new\_end\_validity\_timeframe ⇒ Uint8Array -New `endValidityTimeframe` value to be signed - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.new\_end\_validity\_timeframe -New `endValidityTimeframe` value to be signed - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | Uint8Array | - - - -### proofUpdateCtx.index\_start\_validity\_timeframe ⇒ number -Index of `startValidityTimeframe` claim inside the array of Claims - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.index\_start\_validity\_timeframe -Index of `startValidityTimeframe` claim inside the array of Claims - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | number | - - - -### proofUpdateCtx.index\_end\_validity\_timeframe ⇒ number -Index of `endValidityTimeframe` claim inside the array of Claims - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.index\_end\_validity\_timeframe -Index of `endValidityTimeframe` claim inside the array of Claims - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | number | - - - -### proofUpdateCtx.number\_of\_signed\_messages ⇒ number -Number of signed messages, number of payloads in a JWP - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - - -### proofUpdateCtx.number\_of\_signed\_messages -Number of signed messages, number of payloads in a JWP - -**Kind**: instance property of [ProofUpdateCtx](#ProofUpdateCtx) - -| Param | Type | -| --- | --- | -| arg0 | number | - - - -## Resolver -Convenience type for resolving DID documents from different DID methods. - -Also provides methods for resolving DID Documents associated with -verifiable [Credential](#Credential)s and [Presentation](#Presentation)s. - -# Configuration - -The resolver will only be able to resolve DID documents for methods it has been configured for in the constructor. - -**Kind**: global class - -* [Resolver](#Resolver) - * [new Resolver(config)](#new_Resolver_new) - * [.resolve(did)](#Resolver+resolve) ⇒ Promise.<(CoreDocument\|IToCoreDocument)> - * [.resolveMultiple(dids)](#Resolver+resolveMultiple) ⇒ Promise.<Array.<(CoreDocument\|IToCoreDocument)>> - - - -### new Resolver(config) -Constructs a new [Resolver](#Resolver). - -# Errors -If both a `client` is given and the `handlers` map contains the "iota" key the construction process -will throw an error because the handler for the "iota" method then becomes ambiguous. - - -| Param | Type | -| --- | --- | -| config | ResolverConfig | - - - -### resolver.resolve(did) ⇒ Promise.<(CoreDocument\|IToCoreDocument)> -Fetches the DID Document of the given DID. - -### Errors - -Errors if the resolver has not been configured to handle the method -corresponding to the given DID or the resolution process itself fails. - -**Kind**: instance method of [Resolver](#Resolver) - -| Param | Type | -| --- | --- | -| did | string | - - - -### resolver.resolveMultiple(dids) ⇒ Promise.<Array.<(CoreDocument\|IToCoreDocument)>> -Concurrently fetches the DID Documents of the multiple given DIDs. - -# Errors -* If the resolver has not been configured to handle the method of any of the given DIDs. -* If the resolution process of any DID fails. - -## Note -* The order of the documents in the returned array matches that in `dids`. -* If `dids` contains duplicates, these will be resolved only once and the resolved document -is copied into the returned array to match the order of `dids`. - -**Kind**: instance method of [Resolver](#Resolver) - -| Param | Type | -| --- | --- | -| dids | Array.<string> | - - - -## RevocationBitmap -A compressed bitmap for managing credential revocation. - -**Kind**: global class - -* [RevocationBitmap](#RevocationBitmap) - * [new RevocationBitmap()](#new_RevocationBitmap_new) - * _instance_ - * [.isRevoked(index)](#RevocationBitmap+isRevoked) ⇒ boolean - * [.revoke(index)](#RevocationBitmap+revoke) ⇒ boolean - * [.unrevoke(index)](#RevocationBitmap+unrevoke) ⇒ boolean - * [.len()](#RevocationBitmap+len) ⇒ number - * [.toService(serviceId)](#RevocationBitmap+toService) ⇒ [Service](#Service) - * _static_ - * [.type()](#RevocationBitmap.type) ⇒ string - * [.fromEndpoint(service)](#RevocationBitmap.fromEndpoint) ⇒ [RevocationBitmap](#RevocationBitmap) - - - -### new RevocationBitmap() -Creates a new [RevocationBitmap](#RevocationBitmap) instance. - - - -### revocationBitmap.isRevoked(index) ⇒ boolean -Returns `true` if the credential at the given `index` is revoked. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.revoke(index) ⇒ boolean -Mark the given index as revoked. - -Returns true if the index was absent from the set. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.unrevoke(index) ⇒ boolean -Mark the index as not revoked. - -Returns true if the index was present in the set. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.len() ⇒ number -Returns the number of revoked credentials. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - - -### revocationBitmap.toService(serviceId) ⇒ [Service](#Service) -Return a `Service` with: -- the service's id set to `serviceId`, -- of type `RevocationBitmap2022`, -- and with the bitmap embedded in a data url in the service's endpoint. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| serviceId | [DIDUrl](#DIDUrl) | - - - -### RevocationBitmap.type() ⇒ string -The name of the service type. - -**Kind**: static method of [RevocationBitmap](#RevocationBitmap) - - -### RevocationBitmap.fromEndpoint(service) ⇒ [RevocationBitmap](#RevocationBitmap) -Try to construct a [RevocationBitmap](#RevocationBitmap) from a service -if it is a valid Revocation Bitmap Service. - -**Kind**: static method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -## RevocationTimeframeStatus -Information used to determine the current status of a [Credential](#Credential). - -**Kind**: global class - -* [RevocationTimeframeStatus](#RevocationTimeframeStatus) - * [new RevocationTimeframeStatus(id, index, duration, [start_validity])](#new_RevocationTimeframeStatus_new) - * _instance_ - * [.clone()](#RevocationTimeframeStatus+clone) ⇒ [RevocationTimeframeStatus](#RevocationTimeframeStatus) - * [.toJSON()](#RevocationTimeframeStatus+toJSON) ⇒ any - * [.startValidityTimeframe()](#RevocationTimeframeStatus+startValidityTimeframe) ⇒ [Timestamp](#Timestamp) - * [.endValidityTimeframe()](#RevocationTimeframeStatus+endValidityTimeframe) ⇒ [Timestamp](#Timestamp) - * [.id()](#RevocationTimeframeStatus+id) ⇒ string - * [.index()](#RevocationTimeframeStatus+index) ⇒ number \| undefined - * _static_ - * [.fromJSON(json)](#RevocationTimeframeStatus.fromJSON) ⇒ [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - - -### new RevocationTimeframeStatus(id, index, duration, [start_validity]) -Creates a new `RevocationTimeframeStatus`. - - -| Param | Type | -| --- | --- | -| id | string | -| index | number | -| duration | [Duration](#Duration) | -| [start_validity] | [Timestamp](#Timestamp) \| undefined | - - - -### revocationTimeframeStatus.clone() ⇒ [RevocationTimeframeStatus](#RevocationTimeframeStatus) -Deep clones the object. - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### revocationTimeframeStatus.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### revocationTimeframeStatus.startValidityTimeframe() ⇒ [Timestamp](#Timestamp) -Get startValidityTimeframe value. - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### revocationTimeframeStatus.endValidityTimeframe() ⇒ [Timestamp](#Timestamp) -Get endValidityTimeframe value. - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### revocationTimeframeStatus.id() ⇒ string -Return the URL fo the `RevocationBitmapStatus`. - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### revocationTimeframeStatus.index() ⇒ number \| undefined -Return the index of the credential in the issuer's revocation bitmap - -**Kind**: instance method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - - -### RevocationTimeframeStatus.fromJSON(json) ⇒ [RevocationTimeframeStatus](#RevocationTimeframeStatus) -Deserializes an instance from a JSON object. - -**Kind**: static method of [RevocationTimeframeStatus](#RevocationTimeframeStatus) - -| Param | Type | -| --- | --- | -| json | any | - - - -## SdJwt -Representation of an SD-JWT of the format -`~~~...~~`. - -**Kind**: global class - -* [SdJwt](#SdJwt) - * [new SdJwt(jwt, disclosures, [key_binding_jwt])](#new_SdJwt_new) - * _instance_ - * [.presentation()](#SdJwt+presentation) ⇒ string - * [.toString()](#SdJwt+toString) ⇒ string - * [.jwt()](#SdJwt+jwt) ⇒ string - * [.disclosures()](#SdJwt+disclosures) ⇒ Array.<string> - * [.keyBindingJwt()](#SdJwt+keyBindingJwt) ⇒ string \| undefined - * [.clone()](#SdJwt+clone) ⇒ [SdJwt](#SdJwt) - * _static_ - * [.parse(sd_jwt)](#SdJwt.parse) ⇒ [SdJwt](#SdJwt) - - - -### new SdJwt(jwt, disclosures, [key_binding_jwt]) -Creates a new `SdJwt` from its components. - - -| Param | Type | -| --- | --- | -| jwt | string | -| disclosures | Array.<string> | -| [key_binding_jwt] | string \| undefined | - - - -### sdJwt.presentation() ⇒ string -Serializes the components into the final SD-JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.toString() ⇒ string -Serializes the components into the final SD-JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.jwt() ⇒ string -The JWT part. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.disclosures() ⇒ Array.<string> -The disclosures part. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.keyBindingJwt() ⇒ string \| undefined -The optional key binding JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.clone() ⇒ [SdJwt](#SdJwt) -Deep clones the object. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### SdJwt.parse(sd_jwt) ⇒ [SdJwt](#SdJwt) -Parses an SD-JWT into its components as [`SdJwt`]. - -## Error -Returns `DeserializationError` if parsing fails. - -**Kind**: static method of [SdJwt](#SdJwt) - -| Param | Type | -| --- | --- | -| sd_jwt | string | - - - -## SdJwtCredentialValidator -A type for decoding and validating [Credential](#Credential). - -**Kind**: global class - -* [SdJwtCredentialValidator](#SdJwtCredentialValidator) - * [new SdJwtCredentialValidator(signatureVerifier)](#new_SdJwtCredentialValidator_new) - * [.validateCredential(sd_jwt, issuer, options, fail_fast)](#SdJwtCredentialValidator+validateCredential) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.verifySignature(credential, trustedIssuers, options)](#SdJwtCredentialValidator+verifySignature) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.validateKeyBindingJwt(sdJwt, holder, options)](#SdJwtCredentialValidator+validateKeyBindingJwt) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - - -### new SdJwtCredentialValidator(signatureVerifier) -Creates a new `SdJwtCredentialValidator`. If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### sdJwtCredentialValidator.validateCredential(sd_jwt, issuer, options, fail_fast) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decodes and validates a `Credential` issued as an SD-JWT. A `DecodedJwtCredential` is returned upon success. -The credential is constructed by replacing disclosures following the -[`Selective Disclosure for JWTs (SD-JWT)`](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html) standard. - -The following properties are validated according to `options`: -- the issuer's signature on the JWS, -- the expiration date, -- the issuance date, -- the semantic structure. - -# Warning -* The key binding JWT is not validated. If needed, it must be validated separately using -`SdJwtValidator::validate_key_binding_jwt`. -* The lack of an error returned from this method is in of itself not enough to conclude that the credential can be -trusted. This section contains more information on additional checks that should be carried out before and after -calling this method. - -## The state of the issuer's DID Document -The caller must ensure that `issuer` represents an up-to-date DID Document. - -## Properties that are not validated - There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: -`proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. -These should be manually checked after validation, according to your requirements. - -# Errors -An error is returned whenever a validated condition is not satisfied. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| sd_jwt | [SdJwt](#SdJwt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -### sdJwtCredentialValidator.verifySignature(credential, trustedIssuers, options) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decode and verify the JWS signature of a `Credential` issued as an SD-JWT using the DID Document of a trusted -issuer and replaces the disclosures. - -A `DecodedJwtCredential` is returned upon success. - -# Warning -The caller must ensure that the DID Documents of the trusted issuers are up-to-date. - -## Proofs - Only the JWS signature is verified. If the `Credential` contains a `proof` property this will not be verified -by this method. - -# Errors -* If the issuer' URL cannot be parsed. -* If Signature verification fails. -* If SD decoding fails. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [SdJwt](#SdJwt) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | - - - -### sdJwtCredentialValidator.validateKeyBindingJwt(sdJwt, holder, options) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Validates a Key Binding JWT (KB-JWT) according to `https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-key-binding-jwt`. -The Validation process includes: - * Signature validation using public key materials defined in the `holder` document. - * `typ` value in KB-JWT header. - * `sd_hash` claim value in the KB-JWT claim. - * Optional `nonce`, `aud` and issuance date validation. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| sdJwt | [SdJwt](#SdJwt) | -| holder | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) | - - - -## SdObjectDecoder -Substitutes digests in an SD-JWT object by their corresponding plaintext values provided by disclosures. - -**Kind**: global class - -* [SdObjectDecoder](#SdObjectDecoder) - * [new SdObjectDecoder()](#new_SdObjectDecoder_new) - * [.decode(object, disclosures)](#SdObjectDecoder+decode) ⇒ Record.<string, any> - - - -### new SdObjectDecoder() -Creates a new `SdObjectDecoder` with `sha-256` hasher. - - - -### sdObjectDecoder.decode(object, disclosures) ⇒ Record.<string, any> -Decodes an SD-JWT `object` containing by Substituting the digests with their corresponding -plaintext values provided by `disclosures`. - -## Notes -* Claims like `exp` or `iat` are not validated in the process of decoding. -* `_sd_alg` property will be removed if present. - -**Kind**: instance method of [SdObjectDecoder](#SdObjectDecoder) - -| Param | Type | -| --- | --- | -| object | Record.<string, any> | -| disclosures | Array.<string> | - - - -## SdObjectEncoder -Transforms a JSON object into an SD-JWT object by substituting selected values -with their corresponding disclosure digests. - -Note: digests are created using the sha-256 algorithm. - -**Kind**: global class - -* [SdObjectEncoder](#SdObjectEncoder) - * [new SdObjectEncoder(object)](#new_SdObjectEncoder_new) - * [.conceal(path, [salt])](#SdObjectEncoder+conceal) ⇒ [Disclosure](#Disclosure) - * [.addSdAlgProperty()](#SdObjectEncoder+addSdAlgProperty) - * [.encodeToString()](#SdObjectEncoder+encodeToString) ⇒ string - * [.toString()](#SdObjectEncoder+toString) ⇒ string - * [.encodeToObject()](#SdObjectEncoder+encodeToObject) ⇒ Record.<string, any> - * [.toJSON()](#SdObjectEncoder+toJSON) ⇒ any - * [.addDecoys(path, number_of_decoys)](#SdObjectEncoder+addDecoys) - - - -### new SdObjectEncoder(object) -Creates a new `SdObjectEncoder` with `sha-256` hash function. - - -| Param | Type | -| --- | --- | -| object | any | - - - -### sdObjectEncoder.conceal(path, [salt]) ⇒ [Disclosure](#Disclosure) -Substitutes a value with the digest of its disclosure. -If no salt is provided, the disclosure will be created with a random salt value. - -`path` indicates the pointer to the value that will be concealed using the syntax of -[JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - -For the following object: - - ``` -{ - "id": "did:value", - "claim1": { - "abc": true - }, - "claim2": ["val_1", "val_2"] -} -``` - -Path "/id" conceals `"id": "did:value"` -Path "/claim1/abc" conceals `"abc": true` -Path "/claim2/0" conceals `val_1` -``` - -## Errors -* `InvalidPath` if pointer is invalid. -* `DataTypeMismatch` if existing SD format is invalid. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - -| Param | Type | -| --- | --- | -| path | string | -| [salt] | string \| undefined | - - - -### sdObjectEncoder.addSdAlgProperty() -Adds the `_sd_alg` property to the top level of the object, with -its value set to "sha-256". - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.encodeToString() ⇒ string -Returns the modified object as a string. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.toString() ⇒ string -Returns the modified object as a string. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.encodeToObject() ⇒ Record.<string, any> -Returns the modified object. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.toJSON() ⇒ any -Returns the modified object. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.addDecoys(path, number_of_decoys) -Adds a decoy digest to the specified path. -If path is an empty slice, decoys will be added to the top level. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - -| Param | Type | -| --- | --- | -| path | string | -| number_of_decoys | number | - - - -## SelectiveDisclosurePresentation -Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes -- @context MUST NOT be blinded -- id MUST be blinded -- type MUST NOT be blinded -- issuer MUST NOT be blinded -- issuanceDate MUST be blinded (if Timeframe Revocation mechanism is used) -- expirationDate MUST be blinded (if Timeframe Revocation mechanism is used) -- credentialSubject (User have to choose which attribute must be blinded) -- credentialSchema MUST NOT be blinded -- credentialStatus MUST NOT be blinded -- refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism) -- termsOfUse NO reason to use it in ZK VC (will be in any case blinded) -- evidence (User have to choose which attribute must be blinded) - -**Kind**: global class - -* [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) - * [new SelectiveDisclosurePresentation(issued_jwp)](#new_SelectiveDisclosurePresentation_new) - * [.concealInSubject(path)](#SelectiveDisclosurePresentation+concealInSubject) - * [.concealInEvidence(path)](#SelectiveDisclosurePresentation+concealInEvidence) - * [.setPresentationHeader(header)](#SelectiveDisclosurePresentation+setPresentationHeader) - - - -### new SelectiveDisclosurePresentation(issued_jwp) -Initialize a presentation starting from an Issued JWP. -The properties `jti`, `nbf`, `issuanceDate`, `expirationDate` and `termsOfUse` are concealed by default. - - -| Param | Type | -| --- | --- | -| issued_jwp | [JwpIssued](#JwpIssued) | - - - -### selectiveDisclosurePresentation.concealInSubject(path) -Selectively disclose "credentialSubject" attributes. -# Example -``` -{ - "id": 1234, - "name": "Alice", - "mainCourses": ["Object-oriented Programming", "Mathematics"], - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", -} -``` -If you want to undisclose for example the Mathematics course and the name of the degree: -``` -undisclose_subject("mainCourses[1]"); -undisclose_subject("degree.name"); -``` - -**Kind**: instance method of [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) - -| Param | Type | -| --- | --- | -| path | string | - - - -### selectiveDisclosurePresentation.concealInEvidence(path) -Undiscloses "evidence" attributes. - -**Kind**: instance method of [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) - -| Param | Type | -| --- | --- | -| path | string | - - - -### selectiveDisclosurePresentation.setPresentationHeader(header) -Sets presentation protected header. - -**Kind**: instance method of [SelectiveDisclosurePresentation](#SelectiveDisclosurePresentation) - -| Param | Type | -| --- | --- | -| header | [PresentationProtectedHeader](#PresentationProtectedHeader) | - - - -## Service -A DID Document Service used to enable trusted interactions associated with a DID subject. - -**Kind**: global class - -* [Service](#Service) - * [new Service(service)](#new_Service_new) - * _instance_ - * [.id()](#Service+id) ⇒ [DIDUrl](#DIDUrl) - * [.type()](#Service+type) ⇒ Array.<string> - * [.serviceEndpoint()](#Service+serviceEndpoint) ⇒ string \| Array.<string> \| Map.<string, Array.<string>> - * [.properties()](#Service+properties) ⇒ Map.<string, any> - * [.toJSON()](#Service+toJSON) ⇒ any - * [.clone()](#Service+clone) ⇒ [Service](#Service) - * _static_ - * [.fromJSON(json)](#Service.fromJSON) ⇒ [Service](#Service) - - - -### new Service(service) - -| Param | Type | -| --- | --- | -| service | IService | - - - -### service.id() ⇒ [DIDUrl](#DIDUrl) -Returns a copy of the [Service](#Service) id. - -**Kind**: instance method of [Service](#Service) - - -### service.type() ⇒ Array.<string> -Returns a copy of the [Service](#Service) type. - -**Kind**: instance method of [Service](#Service) - - -### service.serviceEndpoint() ⇒ string \| Array.<string> \| Map.<string, Array.<string>> -Returns a copy of the [Service](#Service) endpoint. - -**Kind**: instance method of [Service](#Service) - - -### service.properties() ⇒ Map.<string, any> -Returns a copy of the custom properties on the [Service](#Service). - -**Kind**: instance method of [Service](#Service) - - -### service.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Service](#Service) - - -### service.clone() ⇒ [Service](#Service) -Deep clones the object. - -**Kind**: instance method of [Service](#Service) - - -### Service.fromJSON(json) ⇒ [Service](#Service) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Service](#Service) - -| Param | Type | -| --- | --- | -| json | any | - - - -## StatusList2021 -StatusList2021 data structure as described in [W3C's VC status list 2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/). - -**Kind**: global class - -* [StatusList2021](#StatusList2021) - * [new StatusList2021([size])](#new_StatusList2021_new) - * _instance_ - * [.clone()](#StatusList2021+clone) ⇒ [StatusList2021](#StatusList2021) - * [.len()](#StatusList2021+len) ⇒ number - * [.get(index)](#StatusList2021+get) ⇒ boolean - * [.set(index, value)](#StatusList2021+set) - * [.intoEncodedStr()](#StatusList2021+intoEncodedStr) ⇒ string - * _static_ - * [.fromEncodedStr(s)](#StatusList2021.fromEncodedStr) ⇒ [StatusList2021](#StatusList2021) - - - -### new StatusList2021([size]) -Creates a new [StatusList2021](#StatusList2021) of `size` entries. - - -| Param | Type | -| --- | --- | -| [size] | number \| undefined | - - - -### statusList2021.clone() ⇒ [StatusList2021](#StatusList2021) -Deep clones the object. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### statusList2021.len() ⇒ number -Returns the number of entries in this [StatusList2021](#StatusList2021). - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### statusList2021.get(index) ⇒ boolean -Returns whether the entry at `index` is set. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| index | number | - - - -### statusList2021.set(index, value) -Sets the value of the `index`-th entry. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| index | number | -| value | boolean | - - - -### statusList2021.intoEncodedStr() ⇒ string -Encodes this [StatusList2021](#StatusList2021) into its compressed -base64 string representation. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### StatusList2021.fromEncodedStr(s) ⇒ [StatusList2021](#StatusList2021) -Attempts to decode a [StatusList2021](#StatusList2021) from a string. - -**Kind**: static method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| s | string | - - - -## StatusList2021Credential -A parsed [StatusList2021Credential](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021credential). - -**Kind**: global class - -* [StatusList2021Credential](#StatusList2021Credential) - * [new StatusList2021Credential(credential)](#new_StatusList2021Credential_new) - * _instance_ - * [.id()](#StatusList2021Credential+id) ⇒ string - * [.setCredentialStatus(credential, index, revoked_or_suspended)](#StatusList2021Credential+setCredentialStatus) ⇒ [StatusList2021Entry](#StatusList2021Entry) - * [.purpose()](#StatusList2021Credential+purpose) ⇒ [StatusPurpose](#StatusPurpose) - * [.entry(index)](#StatusList2021Credential+entry) ⇒ [CredentialStatus](#CredentialStatus) - * [.clone()](#StatusList2021Credential+clone) ⇒ [StatusList2021Credential](#StatusList2021Credential) - * [.toJSON()](#StatusList2021Credential+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#StatusList2021Credential.fromJSON) ⇒ [StatusList2021Credential](#StatusList2021Credential) - - - -### new StatusList2021Credential(credential) -Creates a new [StatusList2021Credential](#StatusList2021Credential). - - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | - - - -### statusList2021Credential.id() ⇒ string -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.setCredentialStatus(credential, index, revoked_or_suspended) ⇒ [StatusList2021Entry](#StatusList2021Entry) -Sets the given credential's status using the `index`-th entry of this status list. -Returns the created `credentialStatus`. - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| index | number | -| revoked_or_suspended | boolean | - - - -### statusList2021Credential.purpose() ⇒ [StatusPurpose](#StatusPurpose) -Returns the [StatusPurpose](#StatusPurpose) of this [StatusList2021Credential](#StatusList2021Credential). - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.entry(index) ⇒ [CredentialStatus](#CredentialStatus) -Returns the state of the `index`-th entry, if any. - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| index | number | - - - -### statusList2021Credential.clone() ⇒ [StatusList2021Credential](#StatusList2021Credential) -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.toJSON() ⇒ any -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### StatusList2021Credential.fromJSON(json) ⇒ [StatusList2021Credential](#StatusList2021Credential) -**Kind**: static method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## StatusList2021CredentialBuilder -Builder type to construct valid [StatusList2021Credential](#StatusList2021Credential) istances. - -**Kind**: global class - -* [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [new StatusList2021CredentialBuilder([status_list])](#new_StatusList2021CredentialBuilder_new) - * [.purpose(purpose)](#StatusList2021CredentialBuilder+purpose) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.subjectId(id)](#StatusList2021CredentialBuilder+subjectId) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.expirationDate(time)](#StatusList2021CredentialBuilder+expirationDate) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.issuer(issuer)](#StatusList2021CredentialBuilder+issuer) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.context(context)](#StatusList2021CredentialBuilder+context) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.type(t)](#StatusList2021CredentialBuilder+type) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.proof(proof)](#StatusList2021CredentialBuilder+proof) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.build()](#StatusList2021CredentialBuilder+build) ⇒ [StatusList2021Credential](#StatusList2021Credential) - - - -### new StatusList2021CredentialBuilder([status_list]) -Creates a new [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder). - - -| Param | Type | -| --- | --- | -| [status_list] | [StatusList2021](#StatusList2021) \| undefined | - - - -### statusList2021CredentialBuilder.purpose(purpose) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the purpose of the [StatusList2021Credential](#StatusList2021Credential) that is being created. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| purpose | [StatusPurpose](#StatusPurpose) | - - - -### statusList2021CredentialBuilder.subjectId(id) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets `credentialSubject.id`. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| id | string | - - - -### statusList2021CredentialBuilder.expirationDate(time) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the expiration date of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| time | [Timestamp](#Timestamp) | - - - -### statusList2021CredentialBuilder.issuer(issuer) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the issuer of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| issuer | string | - - - -### statusList2021CredentialBuilder.context(context) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the context of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| context | string | - - - -### statusList2021CredentialBuilder.type(t) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Adds a credential type. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| t | string | - - - -### statusList2021CredentialBuilder.proof(proof) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Adds a credential's proof. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| proof | [Proof](#Proof) | - - - -### statusList2021CredentialBuilder.build() ⇒ [StatusList2021Credential](#StatusList2021Credential) -Attempts to build a valid [StatusList2021Credential](#StatusList2021Credential) with the previously provided data. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - - -## StatusList2021Entry -[StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. - -**Kind**: global class - -* [StatusList2021Entry](#StatusList2021Entry) - * [new StatusList2021Entry(status_list, purpose, index, [id])](#new_StatusList2021Entry_new) - * _instance_ - * [.id()](#StatusList2021Entry+id) ⇒ string - * [.purpose()](#StatusList2021Entry+purpose) ⇒ [StatusPurpose](#StatusPurpose) - * [.index()](#StatusList2021Entry+index) ⇒ number - * [.statusListCredential()](#StatusList2021Entry+statusListCredential) ⇒ string - * [.toStatus()](#StatusList2021Entry+toStatus) ⇒ Status - * [.clone()](#StatusList2021Entry+clone) ⇒ [StatusList2021Entry](#StatusList2021Entry) - * [.toJSON()](#StatusList2021Entry+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#StatusList2021Entry.fromJSON) ⇒ [StatusList2021Entry](#StatusList2021Entry) - - - -### new StatusList2021Entry(status_list, purpose, index, [id]) -Creates a new [StatusList2021Entry](#StatusList2021Entry). - - -| Param | Type | -| --- | --- | -| status_list | string | -| purpose | [StatusPurpose](#StatusPurpose) | -| index | number | -| [id] | string \| undefined | - - - -### statusList2021Entry.id() ⇒ string -Returns this `credentialStatus`'s `id`. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.purpose() ⇒ [StatusPurpose](#StatusPurpose) -Returns the purpose of this entry. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.index() ⇒ number -Returns the index of this entry. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.statusListCredential() ⇒ string -Returns the referenced [StatusList2021Credential](#StatusList2021Credential)'s url. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.toStatus() ⇒ Status -Downcasts [this](this) to [Status](Status) - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.clone() ⇒ [StatusList2021Entry](#StatusList2021Entry) -Deep clones the object. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### StatusList2021Entry.fromJSON(json) ⇒ [StatusList2021Entry](#StatusList2021Entry) -Deserializes an instance from a JSON object. - -**Kind**: static method of [StatusList2021Entry](#StatusList2021Entry) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Storage -A type wrapping a `JwkStorage` and `KeyIdStorage` that should always be used together when -working with storage backed DID documents. - -**Kind**: global class - -* [Storage](#Storage) - * [new Storage(jwkStorage, keyIdStorage)](#new_Storage_new) - * [.keyIdStorage()](#Storage+keyIdStorage) ⇒ KeyIdStorage - * [.keyStorage()](#Storage+keyStorage) ⇒ JwkStorage - - - -### new Storage(jwkStorage, keyIdStorage) -Constructs a new `Storage`. - - -| Param | Type | -| --- | --- | -| jwkStorage | JwkStorage | -| keyIdStorage | KeyIdStorage | - - - -### storage.keyIdStorage() ⇒ KeyIdStorage -Obtain the wrapped `KeyIdStorage`. - -**Kind**: instance method of [Storage](#Storage) - - -### storage.keyStorage() ⇒ JwkStorage -Obtain the wrapped `JwkStorage`. - -**Kind**: instance method of [Storage](#Storage) - - -## Timestamp -**Kind**: global class - -* [Timestamp](#Timestamp) - * [new Timestamp()](#new_Timestamp_new) - * _instance_ - * [.toRFC3339()](#Timestamp+toRFC3339) ⇒ string - * [.checkedAdd(duration)](#Timestamp+checkedAdd) ⇒ [Timestamp](#Timestamp) \| undefined - * [.checkedSub(duration)](#Timestamp+checkedSub) ⇒ [Timestamp](#Timestamp) \| undefined - * [.toJSON()](#Timestamp+toJSON) ⇒ any - * _static_ - * [.parse(input)](#Timestamp.parse) ⇒ [Timestamp](#Timestamp) - * [.nowUTC()](#Timestamp.nowUTC) ⇒ [Timestamp](#Timestamp) - * [.fromJSON(json)](#Timestamp.fromJSON) ⇒ [Timestamp](#Timestamp) - - - -### new Timestamp() -Creates a new [Timestamp](#Timestamp) with the current date and time. - - - -### timestamp.toRFC3339() ⇒ string -Returns the [Timestamp](#Timestamp) as an RFC 3339 `String`. - -**Kind**: instance method of [Timestamp](#Timestamp) - - -### timestamp.checkedAdd(duration) ⇒ [Timestamp](#Timestamp) \| undefined -Computes `self + duration` - -Returns `null` if the operation leads to a timestamp not in the valid range for [RFC 3339](https://tools.ietf.org/html/rfc3339). - -**Kind**: instance method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| duration | [Duration](#Duration) | - - - -### timestamp.checkedSub(duration) ⇒ [Timestamp](#Timestamp) \| undefined -Computes `self - duration` - -Returns `null` if the operation leads to a timestamp not in the valid range for [RFC 3339](https://tools.ietf.org/html/rfc3339). - -**Kind**: instance method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| duration | [Duration](#Duration) | - - - -### timestamp.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Timestamp](#Timestamp) - - -### Timestamp.parse(input) ⇒ [Timestamp](#Timestamp) -Parses a [Timestamp](#Timestamp) from the provided input string. - -**Kind**: static method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| input | string | - - - -### Timestamp.nowUTC() ⇒ [Timestamp](#Timestamp) -Creates a new [Timestamp](#Timestamp) with the current date and time. - -**Kind**: static method of [Timestamp](#Timestamp) - - -### Timestamp.fromJSON(json) ⇒ [Timestamp](#Timestamp) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| json | any | - - - -## UnknownCredential -**Kind**: global class - -* [UnknownCredential](#UnknownCredential) - * _instance_ - * [.tryIntoJwt()](#UnknownCredential+tryIntoJwt) ⇒ [Jwt](#Jwt) \| undefined - * [.tryIntoCredential()](#UnknownCredential+tryIntoCredential) ⇒ [Credential](#Credential) \| undefined - * [.tryIntoRaw()](#UnknownCredential+tryIntoRaw) ⇒ Record.<string, any> \| undefined - * [.toJSON()](#UnknownCredential+toJSON) ⇒ any - * [.clone()](#UnknownCredential+clone) ⇒ [UnknownCredential](#UnknownCredential) - * _static_ - * [.fromJSON(json)](#UnknownCredential.fromJSON) ⇒ [UnknownCredential](#UnknownCredential) - - - -### unknownCredential.tryIntoJwt() ⇒ [Jwt](#Jwt) \| undefined -Returns a [Jwt](#Jwt) if the credential is of type string, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.tryIntoCredential() ⇒ [Credential](#Credential) \| undefined -Returns a [Credential](#Credential) if the credential is of said type, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.tryIntoRaw() ⇒ Record.<string, any> \| undefined -Returns the contained value as an Object, if it can be converted, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.clone() ⇒ [UnknownCredential](#UnknownCredential) -Deep clones the object. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### UnknownCredential.fromJSON(json) ⇒ [UnknownCredential](#UnknownCredential) -Deserializes an instance from a JSON object. - -**Kind**: static method of [UnknownCredential](#UnknownCredential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## VerificationMethod -A DID Document Verification Method. - -**Kind**: global class - -* [VerificationMethod](#VerificationMethod) - * [new VerificationMethod(id, controller, type_, data)](#new_VerificationMethod_new) - * _instance_ - * [.id()](#VerificationMethod+id) ⇒ [DIDUrl](#DIDUrl) - * [.setId(id)](#VerificationMethod+setId) - * [.controller()](#VerificationMethod+controller) ⇒ [CoreDID](#CoreDID) - * [.setController(did)](#VerificationMethod+setController) - * [.type()](#VerificationMethod+type) ⇒ [MethodType](#MethodType) - * [.setType(type_)](#VerificationMethod+setType) - * [.data()](#VerificationMethod+data) ⇒ [MethodData](#MethodData) - * [.setData(data)](#VerificationMethod+setData) - * [.properties()](#VerificationMethod+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#VerificationMethod+setPropertyUnchecked) - * [.toJSON()](#VerificationMethod+toJSON) ⇒ any - * [.clone()](#VerificationMethod+clone) ⇒ [VerificationMethod](#VerificationMethod) - * _static_ - * [.newFromJwk(did, key, [fragment])](#VerificationMethod.newFromJwk) ⇒ [VerificationMethod](#VerificationMethod) - * [.fromJSON(json)](#VerificationMethod.fromJSON) ⇒ [VerificationMethod](#VerificationMethod) - - - -### new VerificationMethod(id, controller, type_, data) -Create a custom [VerificationMethod](#VerificationMethod). - - -| Param | Type | -| --- | --- | -| id | [DIDUrl](#DIDUrl) | -| controller | [CoreDID](#CoreDID) | -| type_ | [MethodType](#MethodType) | -| data | [MethodData](#MethodData) | - - - -### verificationMethod.id() ⇒ [DIDUrl](#DIDUrl) -Returns a copy of the [DIDUrl](#DIDUrl) of the [VerificationMethod](#VerificationMethod)'s `id`. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setId(id) -Sets the id of the [VerificationMethod](#VerificationMethod). - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| id | [DIDUrl](#DIDUrl) | - - - -### verificationMethod.controller() ⇒ [CoreDID](#CoreDID) -Returns a copy of the `controller` `DID` of the [VerificationMethod](#VerificationMethod). - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setController(did) -Sets the `controller` `DID` of the [VerificationMethod](#VerificationMethod) object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| did | [CoreDID](#CoreDID) | - - - -### verificationMethod.type() ⇒ [MethodType](#MethodType) -Returns a copy of the [VerificationMethod](#VerificationMethod) type. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setType(type_) -Sets the [VerificationMethod](#VerificationMethod) type. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| type_ | [MethodType](#MethodType) | - - - -### verificationMethod.data() ⇒ [MethodData](#MethodData) -Returns a copy of the [VerificationMethod](#VerificationMethod) public key data. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setData(data) -Sets [VerificationMethod](#VerificationMethod) public key data. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| data | [MethodData](#MethodData) | - - - -### verificationMethod.properties() ⇒ Map.<string, any> -Get custom properties of the Verification Method. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setPropertyUnchecked(key, value) -Adds a custom property to the Verification Method. -If the value is set to `null`, the custom property will be removed. - -### WARNING -This method can overwrite existing properties like `id` and result -in an invalid Verification Method. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### verificationMethod.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.clone() ⇒ [VerificationMethod](#VerificationMethod) -Deep clones the object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### VerificationMethod.newFromJwk(did, key, [fragment]) ⇒ [VerificationMethod](#VerificationMethod) -Creates a new [VerificationMethod](#VerificationMethod) from the given `did` and [Jwk](#Jwk). If `fragment` is not given -the `kid` value of the given `key` will be used, if present, otherwise an error is returned. - -### Recommendations -The following recommendations are essentially taken from the `publicKeyJwk` description from the [DID specification](https://www.w3.org/TR/did-core/#dfn-publickeyjwk): -- It is recommended that verification methods that use `Jwks` to represent their public keys use the value of - `kid` as their fragment identifier. This is -done automatically if `None` is passed in as the fragment. -- It is recommended that [Jwk](#Jwk) kid values are set to the public key fingerprint. - -**Kind**: static method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| did | [CoreDID](#CoreDID) \| IToCoreDID | -| key | [Jwk](#Jwk) | -| [fragment] | string \| undefined | - - - -### VerificationMethod.fromJSON(json) ⇒ [VerificationMethod](#VerificationMethod) -Deserializes an instance from a JSON object. - -**Kind**: static method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| json | any | - - - -## FailFast -Declares when validation should return if an error occurs. - -**Kind**: global variable - - -## AllErrors -Return all errors that occur during validation. - -**Kind**: global variable - - -## FirstError -Return after the first error occurs. - -**Kind**: global variable - - -## PayloadType -**Kind**: global variable - - -## SubjectHolderRelationship -Declares how credential subjects must relate to the presentation holder. - -See also the [Subject-Holder Relationship](https://www.w3.org/TR/vc-data-model/#subject-holder-relationships) section of the specification. - -**Kind**: global variable - - -## AlwaysSubject -The holder must always match the subject on all credentials, regardless of their [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property. -This variant is the default. - -**Kind**: global variable - - -## SubjectOnNonTransferable -The holder must match the subject only for credentials where the [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property is `true`. - -**Kind**: global variable - - -## Any -The holder is not required to have any kind of relationship to any credential subject. - -**Kind**: global variable - - -## StatusPurpose -Purpose of a [StatusList2021](#StatusList2021). - -**Kind**: global variable - - -## MethodRelationship -**Kind**: global variable - - -## PresentationProofAlgorithm -**Kind**: global variable - - -## CredentialStatus -**Kind**: global variable - - -## StatusCheck -Controls validation behaviour when checking whether or not a credential has been revoked by its -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). - -**Kind**: global variable - - -## Strict -Validate the status if supported, reject any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -Only `RevocationBitmap2022` is currently supported. - -This is the default. - -**Kind**: global variable - - -## SkipUnsupported -Validate the status if supported, skip any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -**Kind**: global variable - - -## SkipAll -Skip all status checks. - -**Kind**: global variable - - -## SerializationType -**Kind**: global variable - - -## ProofAlgorithm -**Kind**: global variable - - -## StateMetadataEncoding -**Kind**: global variable - - -## encodeB64(data) ⇒ string -Encode the given bytes in url-safe base64. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -## decodeB64(data) ⇒ Uint8Array -Decode the given url-safe base64-encoded slice into its raw bytes. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -## start() -Initializes the console error panic hook for better error messages - -**Kind**: global function - - -## verifyEd25519(alg, signingInput, decodedSignature, publicKey) -Verify a JWS signature secured with the `EdDSA` algorithm and curve `Ed25519`. - -This function is useful when one is composing a `IJwsVerifier` that delegates -`EdDSA` verification with curve `Ed25519` to this function. - -# Warning - -This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this -prior to calling the function. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| alg | JwsAlgorithm | -| signingInput | Uint8Array | -| decodedSignature | Uint8Array | -| publicKey | [Jwk](#Jwk) | - diff --git a/bindings/wasm/src/sd_jwt_vc/builder.rs b/bindings/wasm/src/sd_jwt_vc/builder.rs new file mode 100644 index 0000000000..b8606bccba --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/builder.rs @@ -0,0 +1,134 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::StringOrUrl; +use identity_iota::core::Url; +use identity_iota::credential::sd_jwt_vc::SdJwtVcBuilder; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::common::WasmTimestamp; +use crate::credential::WasmCredential; +use crate::error::Result; +use crate::error::WasmResult; +use crate::sd_jwt_vc::WasmSdJwtVc; + +use super::sd_jwt_v2::WasmHasher; +use super::sd_jwt_v2::WasmJwsSigner; +use super::sd_jwt_v2::WasmRequiredKeyBinding; +use super::WasmStatus; + +#[wasm_bindgen(js_name = SdJwtVcBuilder)] +pub struct WasmSdJwtVcBuilder(pub(crate) SdJwtVcBuilder); + +#[wasm_bindgen(js_class = SdJwtVcBuilder)] +impl WasmSdJwtVcBuilder { + /// Creates a new {@link SdJwtVcBuilder} using `object` JSON representation and a given + /// hasher `hasher`. + #[wasm_bindgen(constructor)] + pub fn new(object: js_sys::Object, hasher: WasmHasher) -> Result { + let object = serde_wasm_bindgen::from_value::(object.into()).wasm_result()?; + SdJwtVcBuilder::new_with_hasher(object, hasher).map(Self).wasm_result() + } + + /// Creates a new [`SdJwtVcBuilder`] starting from a {@link Credential} that is converted to a JWT claim set. + #[wasm_bindgen(js_name = fromCredential)] + pub fn new_from_credential(credential: WasmCredential, hasher: WasmHasher) -> Result { + SdJwtVcBuilder::new_from_credential(credential.0, hasher) + .map(Self) + .wasm_result() + } + + /// Substitutes a value with the digest of its disclosure. + /// + /// ## Notes + /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + #[wasm_bindgen(js_name = makeConcealable)] + pub fn make_concealable(self, path: &str) -> Result { + self.0.make_concealable(path).map(Self).wasm_result() + } + + /// Sets the JWT header. + /// ## Notes + /// - if {@link SdJwtVcBuilder.header} is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": + /// "" } ``` + /// - `alg` is always replaced with the value passed to {@link SdJwtVcBuilder.finish}. + #[wasm_bindgen] + pub fn header(self, header: js_sys::Object) -> Self { + let header = serde_wasm_bindgen::from_value(header.into()).expect("JS object is a valid JSON object"); + Self(self.0.header(header)) + } + + /// Adds a decoy digest to the specified path. + /// + /// `path` indicates the pointer to the value that will be concealed using the syntax of + /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + /// + /// Use `path` = "" to add decoys to the top level. + #[wasm_bindgen(js_name = addDecoys)] + pub fn add_decoys(self, path: &str, number_of_decoys: usize) -> Result { + self.0.add_decoys(path, number_of_decoys).map(Self).wasm_result() + } + + /// Require a proof of possession of a given key from the holder. + /// + /// This operation adds a JWT confirmation (`cnf`) claim as specified in + /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). + #[wasm_bindgen(js_name = requireKeyBinding)] + pub fn require_key_binding(self, key_bind: WasmRequiredKeyBinding) -> Result { + let key_bind = serde_wasm_bindgen::from_value(key_bind.into()).wasm_result()?; + Ok(Self(self.0.require_key_binding(key_bind))) + } + + /// Inserts an `iss` claim. See {@link SdJwtVcClaim.iss}. + #[wasm_bindgen] + pub fn iss(self, issuer: &str) -> Result { + let url = Url::parse(issuer).wasm_result()?; + Ok(Self(self.0.iss(url))) + } + + /// Inserts a `nbf` claim. See {@link SdJwtVcClaims.nbf}. + #[wasm_bindgen] + pub fn nbf(self, nbf: WasmTimestamp) -> Self { + Self(self.0.nbf(nbf.0)) + } + + /// Inserts a `exp` claim. See {@link SdJwtVcClaims.exp}. + #[wasm_bindgen] + pub fn exp(self, exp: WasmTimestamp) -> Self { + Self(self.0.exp(exp.0)) + } + + /// Inserts a `iat` claim. See {@link SdJwtVcClaims.iat}. + #[wasm_bindgen] + pub fn iat(self, iat: WasmTimestamp) -> Self { + Self(self.0.iat(iat.0)) + } + + /// Inserts a `vct` claim. See {@link SdJwtVcClaims.vct}. + #[wasm_bindgen] + pub fn vct(self, vct: &str) -> Self { + let vct = StringOrUrl::parse(vct).unwrap(); + Self(self.0.vct(vct)) + } + + /// Inserts a `sub` claim. See {@link SdJwtVcClaims.sub}. + #[allow(clippy::should_implement_trait)] + #[wasm_bindgen] + pub fn sub(self, sub: &str) -> Self { + let sub = StringOrUrl::parse(sub).unwrap(); + Self(self.0.sub(sub)) + } + + /// Inserts a `status` claim. See {@link SdJwtVcClaims.status}. + #[wasm_bindgen] + pub fn status(self, status: WasmStatus) -> Result { + let status = serde_wasm_bindgen::from_value(status.into()).wasm_result()?; + Ok(Self(self.0.status(status))) + } + + /// Creates an {@link SdJwtVc} with the provided data. + #[wasm_bindgen] + pub async fn finish(self, signer: &WasmJwsSigner, alg: &str) -> Result { + self.0.finish(signer, alg).await.map(WasmSdJwtVc).wasm_result() + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/claims.rs b/bindings/wasm/src/sd_jwt_vc/claims.rs new file mode 100644 index 0000000000..77c2dc3a89 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/claims.rs @@ -0,0 +1,25 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const I_SD_JWT_VC_CLAIMS: &str = r#" +interface ISdJwtVcClaims { + iss: string; + vct: string; + status: SdJwtVcStatus; + nbf?: string; + exp?: string; + iat?: string; + sub?: string; +} + +type SdJwtVcClaims = ISdJwtVcClaims & SdJwtClaims; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "SdJwtVcClaims")] + pub type WasmSdJwtVcClaims; +} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs b/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs new file mode 100644 index 0000000000..b99c2b0993 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/metadata/claim.rs @@ -0,0 +1,75 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::sd_jwt_vc::metadata::ClaimDisplay; +use identity_iota::credential::sd_jwt_vc::metadata::ClaimMetadata; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const T_CLAIM_PATH: &str = r#" +type ClaimPathSegment = string | number | null; +type ClaimPath = ClaimPathSegment[]; +"#; + +#[wasm_bindgen(typescript_custom_section)] +const T_DISCLOSABILITY: &str = r#" +type ClaimDisclosability = "always" | "allowed" | "never"; +"#; + +#[wasm_bindgen] +extern "C" { + #[derive(Clone)] + #[wasm_bindgen(typescript_type = ClaimPathSegment)] + pub type WasmClaimPathSegment; + + #[derive(Clone)] + #[wasm_bindgen(typescript_type = ClaimPath)] + pub type WasmClaimPath; + + #[derive(Clone)] + #[wasm_bindgen(typescript_type = ClaimDisclosability)] + pub type WasmClaimDisclosability; +} + +#[wasm_bindgen(js_name = ClaimMetadata, inspectable, getter_with_clone)] +pub struct WasmClaimMetadata { + pub path: WasmClaimPath, + pub display: Vec, + pub sd: Option, + pub svg_id: Option, +} + +impl From for ClaimMetadata { + fn from(value: WasmClaimMetadata) -> Self { + let path = serde_wasm_bindgen::from_value(value.path.into()).unwrap(); + let display = value.display.into_iter().map(ClaimDisplay::from).collect(); + let sd = value.sd.map(|sd| serde_wasm_bindgen::from_value(sd.into()).unwrap()); + Self { + path, + display, + sd, + svg_id: value.svg_id, + } + } +} + +#[derive(Clone)] +#[wasm_bindgen(js_name = ClaimDisplay, inspectable, getter_with_clone)] +pub struct WasmClaimDisplay { + /// A language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). + pub lang: String, + /// A human-readable label for the claim. + pub label: String, + /// A human-readable description for the claim. + pub description: Option, +} + +impl From for ClaimDisplay { + fn from(value: WasmClaimDisplay) -> Self { + Self { + lang: value.lang, + label: value.label, + description: value.description, + } + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs b/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs new file mode 100644 index 0000000000..03c3e37e65 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/metadata/issuer.rs @@ -0,0 +1,63 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Url; +use identity_iota::credential::sd_jwt_vc::metadata::IssuerMetadata; +use serde::Serialize; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +use crate::error::Result; +use crate::error::WasmResult; +use crate::sd_jwt_vc::WasmSdJwtVc; + +#[wasm_bindgen(typescript_custom_section)] +pub const I_JWKS: &str = r#" +type Jwks = { jwks_uri: string } | { jwks: { keys: IJwk[] }}; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = Jwks)] + pub type WasmJwks; +} + +#[wasm_bindgen(js_name = IssuerMetadata)] +pub struct WasmIssuerMetadata(pub(crate) IssuerMetadata); + +#[wasm_bindgen(js_class = IssuerMetadata)] +impl WasmIssuerMetadata { + #[wasm_bindgen(constructor)] + pub fn new(issuer: String, jwks: WasmJwks) -> Result { + let issuer = Url::parse(&issuer).wasm_result()?; + let jwks = serde_wasm_bindgen::from_value(jwks.into()).wasm_result()?; + + Ok(Self(IssuerMetadata { issuer, jwks })) + } + + #[wasm_bindgen] + pub fn issuer(&self) -> String { + self.0.issuer.to_string() + } + + #[wasm_bindgen] + pub fn jwks(&self) -> Result { + serde_wasm_bindgen::to_value(&self.0.jwks) + .wasm_result() + .map(JsCast::unchecked_into) + } + + /// Checks the validity of this {@link IssuerMetadata}. + /// {@link IssuerMetadata.issuer} must match `sd_jwt_vc`'s `iss` claim. + #[wasm_bindgen] + pub fn validate(&self, sd_jwt_vc: &WasmSdJwtVc) -> Result<()> { + self.0.validate(&sd_jwt_vc.0).wasm_result() + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + let js_serializer = serde_wasm_bindgen::Serializer::default().serialize_maps_as_objects(true); + self.0.serialize(&js_serializer).wasm_result() + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs b/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs new file mode 100644 index 0000000000..eff8dbcf41 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/metadata/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod claim; +mod issuer; +mod vc_type; + +pub use claim::*; +pub use issuer::*; +pub use vc_type::*; diff --git a/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs b/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs new file mode 100644 index 0000000000..409cfdff07 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/metadata/vc_type.rs @@ -0,0 +1,84 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::sd_jwt_vc::metadata::TypeMetadata; +use serde::Serialize; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +use crate::error::Result; +use crate::error::WasmResult; +use crate::sd_jwt_vc::resolver::ResolverUrlToValue; + +#[wasm_bindgen(typescript_custom_section)] +const I_TYPE_METADATA: &str = r#" +type SchemaByUri = { schema_uri: string, "schema_uri#integrity"?: string }; +type SchemaByObject = { schema: unknown, "schema#integrity"?: string }; +type NoSchema = {}; +type TypeSchema = SchemaByUri | SchemaByObject | NoSchema; + +type TypeMetadataHelper = { + name?: string; + description?: string; + extends?: string; + "extends#integrity"?: string; + display?: unknown[]; + claims?: ClaimMetadata[]; +} & TypeSchema; +"#; + +#[wasm_bindgen] +extern "C" { + pub type TypeMetadataHelper; +} + +#[derive(Serialize, Clone)] +#[serde(transparent)] +#[wasm_bindgen(js_name = TypeMetadata)] +pub struct WasmTypeMetadata(pub(crate) TypeMetadata); + +impl_wasm_json!(WasmTypeMetadata, TypeMetadata); + +#[wasm_bindgen(js_class = TypeMetadata)] +impl WasmTypeMetadata { + #[wasm_bindgen(constructor)] + pub fn new(helper: TypeMetadataHelper) -> Result { + serde_wasm_bindgen::from_value(helper.into()).map(Self).wasm_result() + } + + #[wasm_bindgen(js_name = intoInner)] + pub fn into_inner(&self) -> Result { + serde_wasm_bindgen::to_value(&self.0) + .wasm_result() + .and_then(JsCast::dyn_into) + } + + /// Uses this {@link TypeMetadata} to validate JSON object `credential`. This method fails + /// if the schema is referenced instead of embedded. + /// Use {@link TypeMetadata.validate_credential_with_resolver} for such cases. + /// ## Notes + /// This method ignores type extensions. + #[wasm_bindgen(js_name = validateCredential)] + pub fn validate_credential(&self, credential: JsValue) -> Result<()> { + let credential = serde_wasm_bindgen::from_value(credential).wasm_result()?; + self.0.validate_credential(&credential).wasm_result() + } + + /// Similar to {@link TypeMetadata.validate_credential}, but accepts a {@link Resolver} + /// {@link Url} -> {@link any} that is used to resolve any reference to either + /// another type or JSON schema. + #[wasm_bindgen(js_name = validateCredentialWithResolver)] + pub async fn validate_credential_with_resolver( + &self, + credential: JsValue, + resolver: &ResolverUrlToValue, + ) -> Result<()> { + let credential = serde_wasm_bindgen::from_value(credential).wasm_result()?; + self + .0 + .validate_credential_with_resolver(&credential, resolver) + .await + .wasm_result() + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/mod.rs b/bindings/wasm/src/sd_jwt_vc/mod.rs new file mode 100644 index 0000000000..7396c14386 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod builder; +mod claims; +pub mod metadata; +mod presentation; +mod resolver; +pub mod sd_jwt_v2; +mod status; +mod token; + +pub use builder::*; +pub use claims::*; +pub use presentation::*; +pub use status::*; +pub use token::*; diff --git a/bindings/wasm/src/sd_jwt_vc/presentation.rs b/bindings/wasm/src/sd_jwt_vc/presentation.rs new file mode 100644 index 0000000000..7b4c4da12e --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/presentation.rs @@ -0,0 +1,53 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::sd_jwt_vc::SdJwtVcPresentationBuilder; +use wasm_bindgen::prelude::wasm_bindgen; + +use super::sd_jwt_v2::WasmDisclosure; +use super::sd_jwt_v2::WasmHasher; +use super::sd_jwt_v2::WasmKeyBindingJwt; +use super::WasmSdJwtVc; +use crate::error::Result; +use crate::error::WasmResult; + +#[wasm_bindgen(js_name = SdJwtVcPresentationBuilder)] +pub struct WasmSdJwtVcPresentationBuilder(pub(crate) SdJwtVcPresentationBuilder); + +#[wasm_bindgen(js_class = SdJwtVcPresentationBuilder)] +impl WasmSdJwtVcPresentationBuilder { + /// Prepares a new presentation from a given {@link SdJwtVc}. + #[wasm_bindgen(constructor)] + pub fn new(token: WasmSdJwtVc, hasher: &WasmHasher) -> Result { + SdJwtVcPresentationBuilder::new(token.0, hasher).map(Self).wasm_result() + } + + #[wasm_bindgen] + pub fn conceal(self, path: &str) -> Result { + self.0.conceal(path).map(Self).wasm_result() + } + + #[wasm_bindgen(js_name = attachKeyBindingJwt)] + pub fn attach_key_binding_jwt(self, kb_jwt: WasmKeyBindingJwt) -> Self { + Self(self.0.attach_key_binding_jwt(kb_jwt.0)) + } + + #[wasm_bindgen] + pub fn finish(self) -> Result { + self + .0 + .finish() + .map(|(token, disclosures)| PresentationResult { + sd_jwt_vc: WasmSdJwtVc(token), + disclosures: disclosures.into_iter().map(WasmDisclosure::from).collect(), + }) + .wasm_result() + } +} + +#[wasm_bindgen(js_name = SdJwtVcPresentationResult, getter_with_clone)] +pub struct PresentationResult { + #[wasm_bindgen(js_name = sdJwtVc)] + pub sd_jwt_vc: WasmSdJwtVc, + pub disclosures: Vec, +} diff --git a/bindings/wasm/src/sd_jwt_vc/resolver.rs b/bindings/wasm/src/sd_jwt_vc/resolver.rs new file mode 100644 index 0000000000..b90a67b9d1 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/resolver.rs @@ -0,0 +1,74 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use async_trait::async_trait; +use identity_iota::core::Url; +use identity_iota::credential::sd_jwt_vc::resolver::Error as ErrorR; +use identity_iota::credential::sd_jwt_vc::Resolver; +use js_sys::Uint8Array; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const I_RESOLVER: &str = r#" +interface IResolver { + resolve: (input: I) => Promise; +} +"#; + +#[wasm_bindgen] +extern "C" { + // Resolver> + #[wasm_bindgen(typescript_type = "IResolver")] + pub type ResolverStringToUint8Array; + + #[wasm_bindgen(structural, method, catch)] + pub async fn resolve(this: &ResolverStringToUint8Array, input: &str) -> Result; + + // Resolver + #[wasm_bindgen(typescript_type = "IResolver")] + pub type ResolverUrlToValue; + + #[wasm_bindgen(structural, method, catch)] + pub async fn resolve(this: &ResolverUrlToValue, input: &str) -> Result; +} + +#[async_trait(?Send)] +impl Resolver> for ResolverStringToUint8Array +where + I: AsRef + Sync, +{ + async fn resolve(&self, input: &I) -> Result, ErrorR> { + self + .resolve(input.as_ref()) + .await + .map(|arr| arr.to_vec()) + .map_err(|e| ErrorR::Generic(anyhow::anyhow!("{}", e.to_string()))) + } +} + +#[async_trait(?Send)] +impl Resolver for ResolverStringToUint8Array +where + I: AsRef + Sync, +{ + async fn resolve(&self, input: &I) -> Result { + self + .resolve(input.as_ref()) + .await + .map(|arr| arr.to_vec()) + .map_err(|e| ErrorR::Generic(anyhow::anyhow!("{}", e.to_string()))) + .and_then(|bytes| serde_json::from_slice(&bytes).map_err(|e| ErrorR::ParsingFailure(e.into()))) + } +} + +#[async_trait(?Send)] +impl Resolver for ResolverUrlToValue { + async fn resolve(&self, input: &Url) -> Result { + self + .resolve(input.as_str()) + .await + .map(|js_value| serde_wasm_bindgen::from_value(js_value).expect("JS value is a JSON value")) + .map_err(|e| ErrorR::Generic(anyhow!("{}", e.to_string()))) + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs new file mode 100644 index 0000000000..e030bbbb94 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/builder.rs @@ -0,0 +1,85 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::sd_jwt_rework::SdJwtBuilder; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +use crate::error::Result; +use crate::error::WasmResult; +use crate::sd_jwt_vc::sd_jwt_v2::WasmSdJwt; + +use super::WasmHasher; +use super::WasmJwsSigner; +use super::WasmRequiredKeyBinding; + +#[wasm_bindgen(js_name = SdJwtBuilder)] +pub struct WasmSdJwtBuilder(pub(crate) SdJwtBuilder); + +#[wasm_bindgen(js_class = SdJwtBuilder)] +impl WasmSdJwtBuilder { + /// Creates a new {@link SdJwtVcBuilder} using `object` JSON representation and a given + /// hasher `hasher`. + #[wasm_bindgen(constructor)] + pub fn new(object: js_sys::Object, hasher: WasmHasher, salt_size: Option) -> Result { + let object = serde_wasm_bindgen::from_value::(object.into()).wasm_result()?; + let salt_size = salt_size.unwrap_or(30); + SdJwtBuilder::new_with_hasher_and_salt_size(object, hasher, salt_size) + .map(Self) + .wasm_result() + } + + /// Substitutes a value with the digest of its disclosure. + /// + /// ## Notes + /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + #[wasm_bindgen(js_name = makeConcealable)] + pub fn make_concealable(self, path: &str) -> Result { + self.0.make_concealable(path).map(Self).wasm_result() + } + + /// Sets the JWT header. + /// ## Notes + /// - if {@link SdJwtVcBuilder.header} is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": + /// "" } ``` + /// - `alg` is always replaced with the value passed to {@link SdJwtVcBuilder.finish}. + #[wasm_bindgen] + pub fn header(self, header: js_sys::Object) -> Self { + let header = serde_wasm_bindgen::from_value(header.into()).expect("JS object is a valid JSON object"); + Self(self.0.header(header)) + } + + /// Adds a new claim to the underlying object. + #[wasm_bindgen(js_name = insertClaim)] + pub fn insert_claim(self, key: String, value: JsValue) -> Result { + let value = serde_wasm_bindgen::from_value::(value).wasm_result()?; + self.0.insert_claim(key, value).map(Self).wasm_result() + } + + /// Adds a decoy digest to the specified path. + /// + /// `path` indicates the pointer to the value that will be concealed using the syntax of + /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + /// + /// Use `path` = "" to add decoys to the top level. + #[wasm_bindgen(js_name = addDecoys)] + pub fn add_decoys(self, path: &str, number_of_decoys: usize) -> Result { + self.0.add_decoys(path, number_of_decoys).map(Self).wasm_result() + } + + /// Require a proof of possession of a given key from the holder. + /// + /// This operation adds a JWT confirmation (`cnf`) claim as specified in + /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). + #[wasm_bindgen(js_name = requireKeyBinding)] + pub fn require_key_binding(self, key_bind: WasmRequiredKeyBinding) -> Result { + let key_bind = serde_wasm_bindgen::from_value(key_bind.into()).wasm_result()?; + Ok(Self(self.0.require_key_binding(key_bind))) + } + + /// Creates an {@link SdJwtVc} with the provided data. + #[wasm_bindgen] + pub async fn finish(self, signer: &WasmJwsSigner, alg: &str) -> Result { + self.0.finish(signer, alg).await.map(WasmSdJwt).wasm_result() + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs new file mode 100644 index 0000000000..b207fc0999 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/disclosure.rs @@ -0,0 +1,64 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::sd_jwt_rework::Disclosure; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +use crate::error::Result; +use crate::error::WasmResult; + +/// A disclosable value. +/// Both object properties and array elements disclosures are supported. +/// +/// See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures +#[derive(Clone)] +#[wasm_bindgen(js_name = DisclosureV2, inspectable, getter_with_clone)] +pub struct WasmDisclosure { + pub salt: String, + #[wasm_bindgen(js_name = claimName)] + pub claim_name: Option, + #[wasm_bindgen(js_name = claimValue)] + pub claim_value: JsValue, + unparsed: String, +} + +#[wasm_bindgen(js_class = DisclosureV2)] +impl WasmDisclosure { + #[wasm_bindgen] + pub fn parse(s: &str) -> Result { + Disclosure::parse(s).map(Self::from).wasm_result() + } + + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + self.unparsed.clone() + } +} + +impl From for Disclosure { + fn from(value: WasmDisclosure) -> Self { + Disclosure::parse(&value.unparsed).expect("valid WasmDisclosure is a valid disclosure") + } +} + +impl From for WasmDisclosure { + fn from(value: Disclosure) -> Self { + let unparsed = value.to_string(); + let Disclosure { + salt, + claim_name, + claim_value, + .. + } = value; + let claim_value = serde_wasm_bindgen::to_value(&claim_value).expect("serde JSON Value is a valid JS Value"); + + Self { + salt, + claim_name, + claim_value, + unparsed, + } + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs new file mode 100644 index 0000000000..18016713f3 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/hasher.rs @@ -0,0 +1,74 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::OnceLock; + +use identity_iota::sd_jwt_rework::Hasher; +use identity_iota::sd_jwt_rework::Sha256Hasher; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const I_HASHER: &str = r#" +interface Hasher { + digest: (input: Uint8Array) => Uint8Array; + algName: () => string; + encodedDigest: (data: string) => string; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Hasher")] + pub type WasmHasher; + + #[wasm_bindgen(structural, method)] + pub fn digest(this: &WasmHasher, input: &[u8]) -> Vec; + + #[wasm_bindgen(structural, method, js_name = "algName")] + pub fn alg_name(this: &WasmHasher) -> String; + + #[wasm_bindgen(structural, method, js_name = "encodedDigest")] + pub fn encoded_digest(this: &WasmHasher, data: &str) -> String; +} + +impl Hasher for WasmHasher { + fn alg_name(&self) -> &str { + static ALG: OnceLock = OnceLock::new(); + ALG.get_or_init(|| self.alg_name()) + } + + fn digest(&self, input: &[u8]) -> Vec { + self.digest(input) + } + + fn encoded_digest(&self, disclosure: &str) -> String { + self.encoded_digest(disclosure) + } +} + +#[wasm_bindgen(js_name = Sha256Hasher)] +pub struct WasmSha256Hasher(pub(crate) Sha256Hasher); + +#[wasm_bindgen(js_class = Sha256Hasher)] +impl WasmSha256Hasher { + #[allow(clippy::new_without_default)] + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self(Sha256Hasher) + } + + #[wasm_bindgen(js_name = algName)] + pub fn alg_name(&self) -> String { + self.0.alg_name().to_owned() + } + + #[wasm_bindgen] + pub fn digest(&self, input: &[u8]) -> Vec { + self.0.digest(input) + } + + #[wasm_bindgen(js_name = encodedDigest)] + pub fn encoded_digest(&self, data: &str) -> String { + self.0.encoded_digest(data) + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs new file mode 100644 index 0000000000..d483737237 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/kb_jwt.rs @@ -0,0 +1,139 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential; +use identity_iota::credential::sd_jwt_vc; +use identity_iota::sd_jwt_rework::KeyBindingJwt; +use identity_iota::sd_jwt_rework::KeyBindingJwtBuilder; +use identity_iota::sd_jwt_rework::Sha256Hasher; +use js_sys::Object; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +use crate::common::WasmTimestamp; +use crate::error::Result; +use crate::error::WasmResult; + +use super::WasmJwsSigner; +use super::WasmSdJwt; + +#[wasm_bindgen(typescript_custom_section)] +const T_REQUIRED_KB: &str = r#" +type RequiredKeyBinding = { jwk: Jwk } + | { jwe: string } + | { kid: string } + | { jwu: { jwu: string, kid: string }} + | unknown; +"#; + +#[wasm_bindgen(typescript_custom_section)] +const I_KB_JWT_CLAIMS: &str = r#" +interface KeyBindingJwtClaimsV2 { + iat: number; + aud: string; + nonce: string; + sd_hash: string; + [properties: string]: unknown; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "RequiredKeyBinding")] + pub type WasmRequiredKeyBinding; + + #[wasm_bindgen(typescript_type = "KeyBindingJwtClaimsV2")] + pub type WasmKeyBindingJwtClaims; +} + +#[wasm_bindgen(js_name = KeyBindingJwt)] +pub struct WasmKeyBindingJwt(pub(crate) KeyBindingJwt); + +#[wasm_bindgen(js_class = KeyBindingJwt)] +impl WasmKeyBindingJwt { + #[wasm_bindgen] + pub fn parse(s: &str) -> Result { + s.parse::() + .map_err(sd_jwt_vc::Error::from) + .map(WasmKeyBindingJwt) + .wasm_result() + } + + #[wasm_bindgen] + pub fn claims(&self) -> WasmKeyBindingJwtClaims { + serde_wasm_bindgen::to_value(self.0.claims()).unwrap().unchecked_into() + } + + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> JsValue { + JsValue::from_str(&self.to_string()) + } +} + +#[wasm_bindgen(js_name = KeyBindingJwtBuilder)] +pub struct WasmKeyBindingJwtBuilder(pub(crate) KeyBindingJwtBuilder); + +#[wasm_bindgen(js_class = KeyBindingJwtBuilder)] +impl WasmKeyBindingJwtBuilder { + #[allow(clippy::new_without_default)] + #[wasm_bindgen(constructor)] + pub fn new() -> WasmKeyBindingJwtBuilder { + Self(KeyBindingJwtBuilder::default()) + } + + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(obj: Object) -> Result { + serde_wasm_bindgen::from_value(obj.into()) + .map(KeyBindingJwtBuilder::from_object) + .map(Self) + .wasm_result() + } + + #[wasm_bindgen] + pub fn header(self, header: Object) -> Result { + serde_wasm_bindgen::from_value(header.into()) + .map(|obj| self.0.header(obj)) + .map(Self) + .wasm_result() + } + + #[wasm_bindgen] + pub fn iat(self, iat: WasmTimestamp) -> Self { + let iat = iat.0.to_unix(); + Self(self.0.iat(iat)) + } + + #[wasm_bindgen] + pub fn aud(self, aud: String) -> Self { + Self(self.0.aud(aud)) + } + + #[wasm_bindgen] + pub fn nonce(self, nonce: String) -> Self { + Self(self.0.nonce(nonce)) + } + + #[wasm_bindgen(js_name = "insertProperty")] + pub fn insert_property(self, name: String, value: JsValue) -> Result { + let value = serde_wasm_bindgen::from_value(value).wasm_result()?; + Ok(Self(self.0.insert_property(&name, value))) + } + + #[wasm_bindgen] + pub async fn finish(self, sd_jwt: &WasmSdJwt, alg: &str, signer: &WasmJwsSigner) -> Result { + self + .0 + .finish(&sd_jwt.0, &Sha256Hasher, alg, signer) + .await + .map(WasmKeyBindingJwt) + .map_err(|e| credential::Error::from(sd_jwt_vc::Error::SdJwt(e))) + .wasm_result() + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs new file mode 100644 index 0000000000..5a526b5d62 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod builder; +mod disclosure; +mod hasher; +mod kb_jwt; +mod sd_jwt; +mod signer; + +pub use builder::*; +pub use disclosure::*; +pub use hasher::*; +pub use kb_jwt::*; +pub use sd_jwt::*; +pub use signer::*; diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs new file mode 100644 index 0000000000..f71f1b927a --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/sd_jwt.rs @@ -0,0 +1,150 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::sd_jwt_vc::Error; +use identity_iota::sd_jwt_rework::SdJwt; +use identity_iota::sd_jwt_rework::SdJwtPresentationBuilder; +use identity_iota::sd_jwt_rework::Sha256Hasher; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +use crate::error::Result; +use crate::error::WasmResult; + +use super::WasmDisclosure; +use super::WasmHasher; +use super::WasmKeyBindingJwt; +use super::WasmRequiredKeyBinding; + +#[wasm_bindgen(typescript_custom_section)] +const I_SD_JWT_CLAIMS: &str = r#" +interface SdJwtClaims { + _sd: string[]; + _sd_alg?: string; + cnf?: RequiredKeyBinding; + [properties: string]: unknown; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "SdJwtClaims")] + pub type WasmSdJwtClaims; +} + +#[derive(Clone)] +#[wasm_bindgen(js_name = SdJwtV2)] +pub struct WasmSdJwt(pub(crate) SdJwt); + +#[wasm_bindgen(js_class = SdJwtV2)] +impl WasmSdJwt { + #[wasm_bindgen] + pub fn parse(s: &str) -> Result { + SdJwt::parse(s).map(Self).map_err(Error::from).wasm_result() + } + + #[wasm_bindgen] + pub fn header(&self) -> JsValue { + serde_wasm_bindgen::to_value(self.0.header()).unwrap() + } + + #[wasm_bindgen] + pub fn claims(&self) -> Result { + serde_wasm_bindgen::to_value(self.0.claims()) + .wasm_result() + .map(JsCast::unchecked_into) + } + + #[wasm_bindgen] + pub fn disclosures(&self) -> Vec { + self.0.disclosures().iter().map(ToString::to_string).collect() + } + + #[wasm_bindgen(js_name = "requiredKeyBind")] + pub fn required_key_bind(&self) -> Option { + self.0.required_key_bind().map(|required_kb| { + serde_wasm_bindgen::to_value(required_kb) + .expect("RequiredKeyBinding can be turned into a JS value") + .unchecked_into() + }) + } + + /// Returns the JSON object obtained by replacing all disclosures into their + /// corresponding JWT concealable claims. + #[wasm_bindgen(js_name = "intoDisclosedObject")] + pub fn into_disclosed_object(self) -> Result { + self + .0 + .into_disclosed_object(&Sha256Hasher) + .map_err(Error::from) + .map(|obj| serde_wasm_bindgen::to_value(&obj).expect("obj can be turned into a JS value")) + .wasm_result() + } + + /// Serializes the components into the final SD-JWT. + #[wasm_bindgen] + pub fn presentation(&self) -> String { + self.0.presentation() + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> JsValue { + JsValue::from_str(&self.presentation()) + } + + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> JsValue { + JsValue::from_str(&self.presentation()) + } +} + +#[wasm_bindgen(js_name = SdJwtPresentationBuilder)] +pub struct WasmSdJwtPresentationBuilder(pub(crate) SdJwtPresentationBuilder); + +#[wasm_bindgen(js_class = SdJwtPresentationBuilder)] +impl WasmSdJwtPresentationBuilder { + #[wasm_bindgen(constructor)] + pub fn new(sd_jwt: WasmSdJwt, hasher: &WasmHasher) -> Result { + SdJwtPresentationBuilder::new(sd_jwt.0, hasher).map(Self).wasm_result() + } + + /// Removes the disclosure for the property at `path`, concealing it. + /// + /// ## Notes + /// - When concealing a claim more than one disclosure may be removed: the disclosure for the claim itself and the + /// disclosures for any concealable sub-claim. + #[wasm_bindgen] + pub fn conceal(self, path: &str) -> Result { + self.0.conceal(path).map(Self).wasm_result() + } + + /// Adds a {@link KeyBindingJwt} to this {@link SdJwt}'s presentation. + #[wasm_bindgen(js_name = attachKeyBindingJwt)] + pub fn attach_key_binding_jwt(self, kb_jwt: WasmKeyBindingJwt) -> Self { + Self(self.0.attach_key_binding_jwt(kb_jwt.0)) + } + + /// Returns the resulting {@link SdJwt} together with all removed disclosures. + /// ## Errors + /// - Fails with `Error::MissingKeyBindingJwt` if this {@link SdJwt} requires a key binding but none was provided. + #[wasm_bindgen] + pub fn finish(self) -> Result { + self + .0 + .finish() + .map(|(sd_jwt, disclosures)| SdJwtPresentationResult { + sd_jwt: WasmSdJwt(sd_jwt), + disclosures: disclosures.into_iter().map(WasmDisclosure::from).collect(), + }) + .wasm_result() + } +} + +#[wasm_bindgen(inspectable, getter_with_clone)] +pub struct SdJwtPresentationResult { + #[wasm_bindgen(js_name = sdJwt)] + pub sd_jwt: WasmSdJwt, + pub disclosures: Vec, +} diff --git a/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs new file mode 100644 index 0000000000..b22bd7f2fa --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/sd_jwt_v2/signer.rs @@ -0,0 +1,53 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use identity_iota::sd_jwt_rework::JsonObject; +use identity_iota::sd_jwt_rework::JwsSigner; +use js_sys::Error as JsError; +use js_sys::Object; +use js_sys::Uint8Array; +use serde::Serialize as _; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; + +use crate::error::Result; + +#[wasm_bindgen(typescript_custom_section)] +const I_JWS_SIGNER: &str = r#" +interface JwsSigner { + sign: (header: object, payload: object) => Promise; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "JwsSigner")] + pub type WasmJwsSigner; + + #[wasm_bindgen(structural, method, catch)] + pub async fn sign(this: &WasmJwsSigner, header: Object, payload: Object) -> Result; +} + +#[async_trait(?Send)] +impl JwsSigner for WasmJwsSigner { + type Error = String; + async fn sign(&self, header: &JsonObject, payload: &JsonObject) -> std::result::Result, Self::Error> { + assert!(!payload.is_empty()); + let js_serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true); + let header = header + .serialize(&js_serializer) + .map_err(|e| format!("{e:?}"))? + .unchecked_into(); + let payload = payload + .serialize(&js_serializer) + .map_err(|e| format!("{e:?}"))? + .unchecked_into(); + + self + .sign(header, payload) + .await + .map_err(|e| e.unchecked_into::().to_string().into()) + .map(|arr| arr.to_vec()) + } +} diff --git a/bindings/wasm/src/sd_jwt_vc/status.rs b/bindings/wasm/src/sd_jwt_vc/status.rs new file mode 100644 index 0000000000..6d4e4ba9a8 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/status.rs @@ -0,0 +1,20 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const I_SD_JWT_VC_STATUS: &str = r#" +interface SdJwtVcStatusListRef { + uri: string; + idx: number; +} + +type SdJwtVcStatus = { status_list: SdJwtVcStatusListRef } | unknown; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_name = "SdJwtVcStatus")] + pub type WasmStatus; +} diff --git a/bindings/wasm/src/sd_jwt_vc/token.rs b/bindings/wasm/src/sd_jwt_vc/token.rs new file mode 100644 index 0000000000..c5219773f6 --- /dev/null +++ b/bindings/wasm/src/sd_jwt_vc/token.rs @@ -0,0 +1,172 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; + +use identity_iota::core::Url; +use identity_iota::credential::sd_jwt_vc::metadata::ClaimMetadata; +use identity_iota::credential::sd_jwt_vc::vct_to_url as vct_to_url_impl; +use identity_iota::credential::sd_jwt_vc::SdJwtVc; +use wasm_bindgen::prelude::*; + +use crate::credential::WasmKeyBindingJWTValidationOptions; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jose::WasmJwk; +use crate::sd_jwt_vc::metadata::WasmTypeMetadata; +use crate::verification::IJwsVerifier; +use crate::verification::WasmJwsVerifier; + +use super::metadata::WasmClaimMetadata; +use super::metadata::WasmIssuerMetadata; +use super::resolver::ResolverStringToUint8Array; +use super::sd_jwt_v2::WasmHasher; +use super::sd_jwt_v2::WasmSdJwt; +use super::WasmSdJwtVcClaims; +use super::WasmSdJwtVcPresentationBuilder; + +#[derive(Clone)] +#[wasm_bindgen(js_name = SdJwtVc)] +pub struct WasmSdJwtVc(pub(crate) SdJwtVc); + +impl_wasm_clone!(WasmSdJwtVc, SdJwtVc); + +#[wasm_bindgen(js_class = "SdJwtVc")] +impl WasmSdJwtVc { + /// Parses a `string` into an {@link SdJwtVc}. + #[wasm_bindgen] + pub fn parse(s: &str) -> Result { + SdJwtVc::parse(s).map(WasmSdJwtVc).wasm_result() + } + + #[wasm_bindgen] + pub fn claims(&self) -> Result { + serde_wasm_bindgen::to_value(self.0.claims()) + .map(JsCast::unchecked_into) + .wasm_result() + } + + #[wasm_bindgen(js_name = "asSdJwt")] + pub fn as_sd_jwt(&self) -> WasmSdJwt { + WasmSdJwt(self.0.deref().clone()) + } + + #[wasm_bindgen(js_name = "issuerJwk")] + pub async fn issuer_jwk(&self, resolver: &ResolverStringToUint8Array) -> Result { + self.0.issuer_jwk(resolver).await.map(WasmJwk).wasm_result() + } + + #[wasm_bindgen(js_name = "issuerMetadata")] + pub async fn issuer_metadata(&self, resolver: &ResolverStringToUint8Array) -> Result> { + self + .0 + .issuer_metadata(resolver) + .await + .map(|maybe_metadata| maybe_metadata.map(WasmIssuerMetadata)) + .wasm_result() + } + + #[wasm_bindgen(js_name = "typeMetadata")] + pub async fn type_metadata(&self, resolver: &ResolverStringToUint8Array) -> Result { + self + .0 + .type_metadata(resolver) + .await + .map(|(metadata, _)| WasmTypeMetadata(metadata)) + .wasm_result() + } + + /// Verifies this {@link SdJwtVc} JWT's signature. + #[wasm_bindgen(js_name = "verifySignature")] + pub fn verify_signature(&self, jwk: &WasmJwk, jws_verifier: Option) -> Result<()> { + let verifier = WasmJwsVerifier::new(jws_verifier); + self.0.verify_signature(&verifier, &jwk.0).wasm_result() + } + + /// Checks the disclosability of this {@link SdJwtVc}'s claims against a list of {@link ClaimMetadata}. + /// ## Notes + /// This check should be performed by the token's holder in order to assert the issuer's compliance with + /// the credential's type. + #[wasm_bindgen(js_name = "validateClaimDisclosability")] + pub fn validate_claims_disclosability(&self, claims_metadata: Vec) -> Result<()> { + let claims_metadata = claims_metadata.into_iter().map(ClaimMetadata::from).collect::>(); + self.0.validate_claims_disclosability(&claims_metadata).wasm_result() + } + + /// Check whether this {@link SdJwtVc} is valid. + /// + /// This method checks: + /// - JWS signature + /// - credential's type + /// - claims' disclosability + #[wasm_bindgen] + pub async fn validate( + &self, + resolver: &ResolverStringToUint8Array, + hasher: &WasmHasher, + jws_verifier: Option, + ) -> Result<()> { + let jws_verifier = WasmJwsVerifier::new(jws_verifier); + self.0.validate(resolver, &jws_verifier, hasher).await.wasm_result() + } + + /// Verify the signature of this {@link SdJwtVc}'s {@link KeyBindingJwt}. + #[wasm_bindgen(js_name = "verifyKeyBinding")] + pub fn verify_key_binding(&self, jwk: &WasmJwk, jws_verifier: Option) -> Result<()> { + let verifier = WasmJwsVerifier::new(jws_verifier); + self.0.verify_key_binding(&verifier, &jwk.0).wasm_result() + } + + #[wasm_bindgen(js_name = "validateKeyBinding")] + pub fn validate_key_binding( + &self, + jwk: &WasmJwk, + hasher: &WasmHasher, + options: &WasmKeyBindingJWTValidationOptions, + jws_verifier: Option, + ) -> Result<()> { + let jws_verifier = WasmJwsVerifier::new(jws_verifier); + self + .0 + .validate_key_binding(&jws_verifier, &jwk.0, hasher, &options.0) + .wasm_result() + } + + #[wasm_bindgen(js_name = "intoSdJwt")] + pub fn into_sd_jwt(self) -> WasmSdJwt { + WasmSdJwt(self.0.into()) + } + + #[wasm_bindgen(js_name = "intoDisclosedObject")] + pub fn into_disclosed_object(&self, hasher: &WasmHasher) -> Result { + self + .0 + .clone() + .into_disclosed_object(hasher) + .map(|obj| serde_wasm_bindgen::to_value(&obj).expect("JSON object is a valid JS object")) + .map(JsCast::unchecked_into) + .wasm_result() + } + + #[wasm_bindgen(js_name = "intoPresentation")] + pub fn into_presentation(self, hasher: &WasmHasher) -> Result { + WasmSdJwtVcPresentationBuilder::new(self, hasher) + } + + #[wasm_bindgen(js_name = "toJSON")] + pub fn to_json(&self) -> JsValue { + JsValue::from_str(&self.0.to_string()) + } + + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> JsValue { + JsValue::from_str(&self.0.to_string()) + } +} + +#[wasm_bindgen(js_name = "vctToUrl")] +pub fn vct_to_url(resource: &str) -> Option { + let url = resource.parse::().ok()?; + vct_to_url_impl(&url).map(|url| url.to_string()) +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 2cbeb96ac5..4a173d1315 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "examples" -version = "1.3.1" +version = "1.5.0" authors = ["IOTA Stiftung"] edition = "2021" publish = false diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index 39442e2183..4b64ca4e75 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_core" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 9d63144d71..475d33f500 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_credential" -version = "1.3.1" +version = "1.5.0" authors = ["IOTA Stiftung"] edition = "2021" homepage.workspace = true @@ -12,21 +12,24 @@ rust-version.workspace = true description = "An implementation of the Verifiable Credentials standard." [dependencies] +anyhow = { version = "1" } async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true } -futures = { version = "0.3", default-features = false, optional = true } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true } json-proof-token = { workspace = true, optional = true } +jsonschema = { version = "0.19", optional = true, default-features = false } once_cell = { version = "1.18", default-features = false, features = ["std"] } reqwest = { version = "0.11", default-features = false, features = ["default-tls", "json", "stream"], optional = true } roaring = { version = "0.10.2", default-features = false, features = ["serde"], optional = true } sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"], optional = true } +sd-jwt-payload-rework = { package = "sd-jwt-payload", version = "0.3", features = ["sha"], optional = true } serde.workspace = true serde-aux = { version = "4.3.1", default-features = false } serde_json.workspace = true @@ -50,7 +53,15 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["revocation-bitmap", "validator", "credential", "presentation", "domain-linkage-fetch", "sd-jwt"] +default = [ + "revocation-bitmap", + "validator", + "credential", + "presentation", + "domain-linkage-fetch", + "sd-jwt", + "sd-jwt-vc", +] credential = [] presentation = ["credential"] revocation-bitmap = ["dep:flate2", "dep:roaring"] @@ -59,6 +70,7 @@ validator = ["dep:itertools", "dep:serde_repr", "credential", "presentation"] domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] +sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema", "dep:futures"] jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] hybrid = ["credential", "validator"] diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index 1c814c3899..18b31d69c3 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -79,4 +79,9 @@ pub enum Error { /// Cause by an invalid attribute path #[error("Attribute Not found")] SelectiveDisclosureError, + + /// Failure of an SD-JWT VC operation. + #[cfg(feature = "sd-jwt-vc")] + #[error(transparent)] + SdJwtVc(#[from] crate::sd_jwt_vc::Error), } diff --git a/identity_credential/src/lib.rs b/identity_credential/src/lib.rs index 3111b72e0a..b7b02daf25 100644 --- a/identity_credential/src/lib.rs +++ b/identity_credential/src/lib.rs @@ -27,8 +27,15 @@ mod utils; #[cfg(feature = "validator")] pub mod validator; +/// Implementation of the SD-JWT VC token specification. +#[cfg(feature = "sd-jwt-vc")] +pub mod sd_jwt_vc; + pub use error::Error; pub use error::Result; #[cfg(feature = "sd-jwt")] pub use sd_jwt_payload; + +#[cfg(feature = "sd-jwt-vc")] +pub use sd_jwt_payload_rework as sd_jwt_v2; \ No newline at end of file diff --git a/identity_credential/src/sd_jwt_vc/builder.rs b/identity_credential/src/sd_jwt_vc/builder.rs new file mode 100644 index 0000000000..234f815bbd --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/builder.rs @@ -0,0 +1,386 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::vec_init_then_push)] +use std::sync::LazyLock; + +use identity_core::common::StringOrUrl; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::convert::ToJson; +use sd_jwt_payload_rework::Hasher; +use sd_jwt_payload_rework::JsonObject; +use sd_jwt_payload_rework::JwsSigner; +use sd_jwt_payload_rework::RequiredKeyBinding; +use sd_jwt_payload_rework::SdJwtBuilder; +use sd_jwt_payload_rework::Sha256Hasher; +use serde::Serialize; +use serde_json::json; +use serde_json::Value; + +use crate::credential::Credential; +use crate::credential::CredentialJwtClaims; + +use super::Error; +use super::Result; +use super::SdJwtVc; +use super::Status; +use super::SD_JWT_VC_TYP; + +static DEFAULT_HEADER: LazyLock = LazyLock::new(|| { + let mut object = JsonObject::default(); + object.insert("typ".to_string(), SD_JWT_VC_TYP.into()); + object +}); + +macro_rules! claim_to_key_value_pair { + ( $( $claim:ident ),+ ) => { + { + let mut claim_list = Vec::<(&'static str, serde_json::Value)>::new(); + $( + claim_list.push((stringify!($claim), serde_json::to_value($claim).unwrap())); + )* + claim_list + } + }; +} + +/// A structure to ease the creation of an [`SdJwtVc`]. +#[derive(Debug)] +pub struct SdJwtVcBuilder { + inner_builder: SdJwtBuilder, + header: JsonObject, + iss: Option, + nbf: Option, + exp: Option, + iat: Option, + vct: Option, + sub: Option, + status: Option, +} + +impl Default for SdJwtVcBuilder { + fn default() -> Self { + Self { + inner_builder: SdJwtBuilder::::new(json!({})).unwrap(), + header: DEFAULT_HEADER.clone(), + iss: None, + nbf: None, + exp: None, + iat: None, + vct: None, + sub: None, + status: None, + } + } +} + +impl SdJwtVcBuilder { + /// Creates a new [`SdJwtVcBuilder`] using `object` JSON representation and default + /// `sha-256` hasher. + pub fn new(object: T) -> Result { + let inner_builder = SdJwtBuilder::::new(object)?; + Ok(Self { + header: DEFAULT_HEADER.clone(), + inner_builder, + ..Default::default() + }) + } +} + +impl SdJwtVcBuilder { + /// Creates a new [`SdJwtVcBuilder`] using `object` JSON representation and a given + /// hasher `hasher`. + pub fn new_with_hasher(object: T, hasher: H) -> Result { + let inner_builder = SdJwtBuilder::new_with_hasher(object, hasher)?; + Ok(Self { + inner_builder, + header: DEFAULT_HEADER.clone(), + iss: None, + nbf: None, + exp: None, + iat: None, + vct: None, + sub: None, + status: None, + }) + } + + /// Creates a new [`SdJwtVcBuilder`] starting from a [`Credential`] that is converted to a JWT claim set. + pub fn new_from_credential(credential: Credential, hasher: H) -> std::result::Result { + let mut vc_jwt_claims = CredentialJwtClaims::new(&credential, None)? + .to_json_value() + .map_err(|e| crate::Error::JwtClaimsSetSerializationError(Box::new(e)))?; + // When converting a VC to its JWT claims representation, some VC specific claims are putted into a `vc` object + // property. Flatten out `vc`, keeping the other JWT claims intact. + { + let claims = vc_jwt_claims.as_object_mut().expect("serialized VC is a JSON object"); + let Value::Object(vc_properties) = claims.remove("vc").expect("serialized VC has `vc` property") else { + unreachable!("`vc` property's value is a JSON object"); + }; + for (key, value) in vc_properties { + claims.insert(key, value); + } + } + Ok(Self::new_with_hasher(vc_jwt_claims, hasher)?) + } + + /// Substitutes a value with the digest of its disclosure. + /// + /// ## Notes + /// - `path` indicates the pointer to the value that will be concealed using the syntax of [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + /// + /// ## Example + /// ```rust + /// use serde_json::json; + /// use identity_credential::sd_jwt_vc::SdJwtVcBuilder; + /// + /// let obj = json!({ + /// "id": "did:value", + /// "claim1": { + /// "abc": true + /// }, + /// "claim2": ["val_1", "val_2"] + /// }); + /// let builder = SdJwtVcBuilder::new(obj) + /// .unwrap() + /// .make_concealable("/id").unwrap() //conceals "id": "did:value" + /// .make_concealable("/claim1/abc").unwrap() //"abc": true + /// .make_concealable("/claim2/0").unwrap(); //conceals "val_1" + /// ``` + pub fn make_concealable(mut self, path: &str) -> Result { + self.inner_builder = self.inner_builder.make_concealable(path)?; + Ok(self) + } + + /// Sets the JWT header. + /// ## Notes + /// - if [`SdJwtVcBuilder::header`] is not called, the default header is used: ```json { "typ": "sd-jwt", "alg": + /// "" } ``` + /// - `alg` is always replaced with the value passed to [`SdJwtVcBuilder::finish`]. + pub fn header(mut self, header: JsonObject) -> Self { + self.header = header; + self + } + + /// Adds a decoy digest to the specified path. + /// + /// `path` indicates the pointer to the value that will be concealed using the syntax of + /// [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). + /// + /// Use `path` = "" to add decoys to the top level. + pub fn add_decoys(mut self, path: &str, number_of_decoys: usize) -> Result { + self.inner_builder = self.inner_builder.add_decoys(path, number_of_decoys)?; + + Ok(self) + } + + /// Require a proof of possession of a given key from the holder. + /// + /// This operation adds a JWT confirmation (`cnf`) claim as specified in + /// [RFC8300](https://www.rfc-editor.org/rfc/rfc7800.html#section-3). + pub fn require_key_binding(mut self, key_bind: RequiredKeyBinding) -> Self { + self.inner_builder = self.inner_builder.require_key_binding(key_bind); + self + } + + /// Inserts an `iss` claim. See [`super::SdJwtVcClaims::iss`]. + pub fn iss(mut self, issuer: Url) -> Self { + self.iss = Some(issuer); + self + } + + /// Inserts a `nbf` claim. See [`super::SdJwtVcClaims::nbf`]. + pub fn nbf(mut self, nbf: Timestamp) -> Self { + self.nbf = Some(nbf.to_unix()); + self + } + + /// Inserts a `exp` claim. See [`super::SdJwtVcClaims::exp`]. + pub fn exp(mut self, exp: Timestamp) -> Self { + self.exp = Some(exp.to_unix()); + self + } + + /// Inserts a `iat` claim. See [`super::SdJwtVcClaims::iat`]. + pub fn iat(mut self, iat: Timestamp) -> Self { + self.iat = Some(iat.to_unix()); + self + } + + /// Inserts a `vct` claim. See [`super::SdJwtVcClaims::vct`]. + pub fn vct(mut self, vct: impl Into) -> Self { + self.vct = Some(vct.into()); + self + } + + /// Inserts a `sub` claim. See [`super::SdJwtVcClaims::sub`]. + #[allow(clippy::should_implement_trait)] + pub fn sub(mut self, sub: impl Into) -> Self { + self.sub = Some(sub.into()); + self + } + + /// Inserts a `status` claim. See [`super::SdJwtVcClaims::status`]. + pub fn status(mut self, status: Status) -> Self { + self.status = Some(status); + self + } + + /// Creates an [`SdJwtVc`] with the provided data. + pub async fn finish(self, signer: &S, alg: &str) -> Result + where + S: JwsSigner, + { + let Self { + inner_builder, + mut header, + iss, + nbf, + exp, + iat, + vct, + sub, + status, + } = self; + // Check header. + header + .entry("typ") + .or_insert_with(|| SD_JWT_VC_TYP.to_owned().into()) + .as_str() + .filter(|typ| typ.contains(SD_JWT_VC_TYP)) + .ok_or_else(|| Error::InvalidJoseType(String::default()))?; + + let builder = inner_builder.header(header); + + // Insert SD-JWT VC claims into object. + let builder = claim_to_key_value_pair![iss, nbf, exp, iat, vct, sub, status] + .into_iter() + .filter(|(_, value)| !value.is_null()) + .fold(builder, |builder, (key, value)| { + builder.insert_claim(key, value).expect("value is a JSON Value") + }); + + let sd_jwt = builder.finish(signer, alg).await?; + SdJwtVc::try_from(sd_jwt) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::credential::CredentialBuilder; + use crate::credential::Subject; + use crate::sd_jwt_vc::tests::TestSigner; + + #[tokio::test] + async fn building_valid_vc_works() -> anyhow::Result<()> { + let credential = json!({ + "name": "John Doe", + "birthdate": "1970-01-01" + }); + + SdJwtVcBuilder::new(credential)? + .vct("https://bmi.bund.example/credential/pid/1.0".parse::()?) + .iat(Timestamp::now_utc()) + .iss("https://example.com/".parse()?) + .make_concealable("/birthdate")? + .finish(&TestSigner, "HS256") + .await?; + + Ok(()) + } + + #[tokio::test] + async fn building_vc_with_missing_mandatory_claims_fails() -> anyhow::Result<()> { + let credential = json!({ + "name": "John Doe", + "birthdate": "1970-01-01" + }); + + let err = SdJwtVcBuilder::new(credential)? + .vct("https://bmi.bund.example/credential/pid/1.0".parse::()?) + .iat(Timestamp::now_utc()) + // issuer is missing. + .make_concealable("/birthdate")? + .finish(&TestSigner, "HS256") + .await + .unwrap_err(); + assert!(matches!(err, Error::MissingClaim("iss"))); + + Ok(()) + } + + #[tokio::test] + async fn building_vc_with_invalid_mandatory_claims_fails() -> anyhow::Result<()> { + let credential = json!({ + "name": "John Doe", + "birthdate": "1970-01-01", + "vct": { "id": 1234567890 } + }); + + let err = SdJwtVcBuilder::new(credential)? + .iat(Timestamp::now_utc()) + .iss("https://example.com".parse()?) + .make_concealable("/birthdate")? + .finish(&TestSigner, "HS256") + .await + .unwrap_err(); + + assert!(matches!(err, Error::InvalidClaimValue { name: "vct", .. })); + + Ok(()) + } + + #[tokio::test] + async fn building_vc_with_disclosed_mandatory_claim_fails() -> anyhow::Result<()> { + let credential = json!({ + "name": "John Doe", + "birthdate": "1970-01-01", + "vct": { "id": 1234567890 } + }); + + let err = SdJwtVcBuilder::new(credential)? + .iat(Timestamp::now_utc()) + .iss("https://example.com".parse()?) + .make_concealable("/birthdate")? + .make_concealable("/vct")? + .finish(&TestSigner, "HS256") + .await + .unwrap_err(); + + assert!(matches!(err, Error::DisclosedClaim("vct"))); + + Ok(()) + } + + #[tokio::test] + async fn building_sd_jwt_vc_from_credential_works() -> anyhow::Result<()> { + let credential = CredentialBuilder::default() + .id(Url::parse("https://example.com/credentials/42")?) + .issuance_date(Timestamp::now_utc()) + .issuer(Url::parse("https://example.com/issuers/42")?) + .subject(Subject::with_id(Url::parse("https://example.com/subjects/42")?)) + .build()?; + + let sd_jwt_vc = SdJwtVcBuilder::new_from_credential(credential.clone(), Sha256Hasher)? + .vct(Url::parse("https://example.com/types/0")?) + .finish(&TestSigner, "HS256") + .await?; + + assert_eq!(sd_jwt_vc.claims().nbf.as_ref().unwrap(), &credential.issuance_date); + assert_eq!(&sd_jwt_vc.claims().iss, credential.issuer.url()); + assert_eq!( + sd_jwt_vc.claims().sub.as_ref().unwrap().as_url(), + credential.credential_subject.first().unwrap().id.as_ref() + ); + assert_eq!( + sd_jwt_vc.claims().get("jti"), + Some(&json!(credential.id.as_ref().unwrap())) + ); + assert_eq!(sd_jwt_vc.claims().get("type"), Some(&json!("VerifiableCredential"))); + + Ok(()) + } +} diff --git a/identity_credential/src/sd_jwt_vc/claims.rs b/identity_credential/src/sd_jwt_vc/claims.rs new file mode 100644 index 0000000000..9fcec11ce3 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/claims.rs @@ -0,0 +1,217 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; +use std::ops::DerefMut; + +use identity_core::common::StringOrUrl; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use sd_jwt_payload_rework::Disclosure; +use sd_jwt_payload_rework::SdJwtClaims; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; + +use super::Error; +use super::Result; +use super::Status; + +/// JOSE payload claims for SD-JWT VC. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct SdJwtVcClaims { + /// Issuer. + pub iss: Url, + /// Not before. + /// See [RFC7519 section 4.1.5](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5) for more information. + pub nbf: Option, + /// Expiration. + /// See [RFC7519 section 4.1.4](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4) for more information. + pub exp: Option, + /// Verifiable credential type. + /// See [SD-JWT VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-04.html#type-claim) + /// for more information. + pub vct: StringOrUrl, + /// Token's status. + /// See [OAuth status list specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-02) + /// for more information. + pub status: Option, + /// Issued at. + /// See [RFC7519 section 4.1.6](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6) for more information. + pub iat: Option, + /// Subject. + /// See [RFC7519 section 4.1.2](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.2) for more information. + pub sub: Option, + #[serde(flatten)] + pub(crate) sd_jwt_claims: SdJwtClaims, +} + +impl Deref for SdJwtVcClaims { + type Target = SdJwtClaims; + fn deref(&self) -> &Self::Target { + &self.sd_jwt_claims + } +} + +impl DerefMut for SdJwtVcClaims { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sd_jwt_claims + } +} + +impl SdJwtVcClaims { + pub(crate) fn try_from_sd_jwt_claims(mut claims: SdJwtClaims, disclosures: &[Disclosure]) -> Result { + let check_disclosed = |claim_name: &'static str| { + disclosures + .iter() + .any(|disclosure| disclosure.claim_name.as_deref() == Some(claim_name)) + .then_some(Error::DisclosedClaim(claim_name)) + }; + let iss = claims + .remove("iss") + .ok_or(Error::MissingClaim("iss")) + .map_err(|e| check_disclosed("iss").unwrap_or(e)) + .and_then(|value| { + value + .as_str() + .and_then(|s| Url::parse(s).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "iss", + expected: "URL", + found: value, + }) + })?; + let nbf = { + if let Some(value) = claims.remove("nbf") { + value + .as_number() + .and_then(|t| t.as_i64()) + .and_then(|t| Timestamp::from_unix(t).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "nbf", + expected: "unix timestamp", + found: value, + }) + .map(Some)? + } else { + if let Some(err) = check_disclosed("nbf") { + return Err(err); + } + None + } + }; + let exp = { + if let Some(value) = claims.remove("exp") { + value + .as_number() + .and_then(|t| t.as_i64()) + .and_then(|t| Timestamp::from_unix(t).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "exp", + expected: "unix timestamp", + found: value, + }) + .map(Some)? + } else { + if let Some(err) = check_disclosed("exp") { + return Err(err); + } + None + } + }; + let vct = claims + .remove("vct") + .ok_or(Error::MissingClaim("vct")) + .map_err(|e| check_disclosed("vct").unwrap_or(e)) + .and_then(|value| { + value + .as_str() + .and_then(|s| StringOrUrl::parse(s).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "vct", + expected: "String or URL", + found: value, + }) + })?; + let status = { + if let Some(value) = claims.remove("status") { + serde_json::from_value::(value.clone()) + .map_err(|_| Error::InvalidClaimValue { + name: "status", + expected: "credential's status object", + found: value, + }) + .map(Some)? + } else { + if let Some(err) = check_disclosed("status") { + return Err(err); + } + None + } + }; + let sub = claims + .remove("sub") + .map(|value| { + value + .as_str() + .and_then(|s| StringOrUrl::parse(s).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "sub", + expected: "String or URL", + found: value, + }) + }) + .transpose()?; + let iat = claims + .remove("iat") + .map(|value| { + value + .as_number() + .and_then(|t| t.as_i64()) + .and_then(|t| Timestamp::from_unix(t).ok()) + .ok_or_else(|| Error::InvalidClaimValue { + name: "iat", + expected: "unix timestamp", + found: value, + }) + }) + .transpose()?; + + Ok(Self { + iss, + nbf, + exp, + vct, + status, + iat, + sub, + sd_jwt_claims: claims, + }) + } +} + +impl From for SdJwtClaims { + fn from(claims: SdJwtVcClaims) -> Self { + let SdJwtVcClaims { + iss, + nbf, + exp, + vct, + status, + iat, + sub, + mut sd_jwt_claims, + } = claims; + + sd_jwt_claims.insert("iss".to_string(), Value::String(iss.into_string())); + nbf.and_then(|t| sd_jwt_claims.insert("nbf".to_string(), Value::Number(t.to_unix().into()))); + exp.and_then(|t| sd_jwt_claims.insert("exp".to_string(), Value::Number(t.to_unix().into()))); + sd_jwt_claims.insert("vct".to_string(), Value::String(vct.into())); + status.and_then(|status| sd_jwt_claims.insert("status".to_string(), serde_json::to_value(status).unwrap())); + iat.and_then(|t| sd_jwt_claims.insert("iat".to_string(), Value::Number(t.to_unix().into()))); + sub.and_then(|sub| sd_jwt_claims.insert("sub".to_string(), Value::String(sub.into()))); + + sd_jwt_claims + } +} diff --git a/identity_credential/src/sd_jwt_vc/error.rs b/identity_credential/src/sd_jwt_vc/error.rs new file mode 100644 index 0000000000..13af8911a3 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/error.rs @@ -0,0 +1,57 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde_json::Value; +use thiserror::Error; + +/// Error type that represents failures that might arise when dealing +/// with `SdJwtVc`s. +#[derive(Error, Debug)] +pub enum Error { + /// A JWT claim required for an operation is missing. + #[error("missing required claim \"{0}\"")] + MissingClaim(&'static str), + /// A JWT claim that must not be disclosed was found among the disclosed values. + #[error("claim \"{0}\" cannot be disclosed")] + DisclosedClaim(&'static str), + /// Invalid value for a given JWT claim. + #[error("invalid value for claim \"{name}\"; expected value of type {expected}, but {found} was found")] + InvalidClaimValue { + /// Name of the invalid claim. + name: &'static str, + /// Type expected for the claim's value. + expected: &'static str, + /// The claim's value. + found: Value, + }, + /// A low level SD-JWT error. + #[error(transparent)] + SdJwt(#[from] sd_jwt_payload_rework::Error), + /// Value of header parameter `typ` is not valid. + #[error("invalid \"typ\" value; expected \"vc+sd-jwt\" (or a superset) but found \"{0}\"")] + InvalidJoseType(String), + /// Resolution error. + #[error("failed to resolve \"{input}\"")] + Resolution { + /// The resource's identifier. + input: String, + /// Low level error. + #[source] + source: super::resolver::Error, + }, + /// Invalid issuer Metadata object. + #[error("invalid Issuer Metadata: {0}")] + InvalidIssuerMetadata(#[source] anyhow::Error), + /// Invalid credential type metadata object. + #[error("invalid Type Metadata: {0}")] + InvalidTypeMetadata(#[source] anyhow::Error), + /// Credential validation failed. + #[error("credential validation failed: {0}")] + Validation(#[source] anyhow::Error), + /// SD-JWT VC signature verification failed. + #[error("verification failed: {0}")] + Verification(#[source] anyhow::Error), +} + +/// Either a value of type `T` or an [`Error`]. +pub type Result = std::result::Result; diff --git a/identity_credential/src/sd_jwt_vc/metadata/claim.rs b/identity_credential/src/sd_jwt_vc/metadata/claim.rs new file mode 100644 index 0000000000..bee5f86e65 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/claim.rs @@ -0,0 +1,286 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; +use std::ops::Deref; + +use anyhow::anyhow; +use anyhow::Context; +use itertools::Itertools; +use serde::Deserialize; +use serde::Serialize; +use serde::Serializer; +use serde_json::Value; + +use crate::sd_jwt_vc::Error; + +/// Information about a particular claim for displaying and validation purposes. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ClaimMetadata { + /// [`ClaimPath`] of the claim or claims that are being addressed. + pub path: ClaimPath, + /// Object containing display information for the claim. + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub display: Vec, + /// A string indicating whether the claim is selectively disclosable. + pub sd: Option, + /// A string defining the ID of the claim for reference in the SVG template. + pub svg_id: Option, +} + +impl ClaimMetadata { + /// Checks whether `value` is compliant with the disclosability policy imposed by this [`ClaimMetadata`]. + pub fn check_value_disclosability(&self, value: &Value) -> Result<(), Error> { + if self.sd.unwrap_or_default() == ClaimDisclosability::Allowed { + return Ok(()); + } + + let interested_claims = self.path.reverse_index(value); + if self.sd.unwrap_or_default() == ClaimDisclosability::Always && interested_claims.is_ok() { + return Err(Error::Validation(anyhow!( + "claim or claims with path {} must always be disclosable", + &self.path + ))); + } + + if self.sd.unwrap_or_default() == ClaimDisclosability::Never && interested_claims.is_err() { + return Err(Error::Validation(anyhow!( + "claim or claims with path {} must never be disclosable", + &self.path + ))); + } + + Ok(()) + } +} + +/// A non-empty list of string, `null` values, or non-negative integers. +/// It is used to select a particular claim in the credential or a +/// set of claims. See [Claim Path](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-05.html#name-claim-path) for more information. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(try_from = "Vec")] +pub struct ClaimPath(Vec); + +impl ClaimPath { + fn reverse_index<'v>(&self, value: &'v Value) -> anyhow::Result> { + let mut segments = self.iter(); + let first_segment = segments.next().context("empty claim path")?; + segments.try_fold(index_value(value, first_segment)?, |values, segment| { + values.get(segment) + }) + } +} + +impl TryFrom> for ClaimPath { + type Error = anyhow::Error; + fn try_from(value: Vec) -> Result { + if value.is_empty() { + Err(anyhow::anyhow!("`ClaimPath` cannot be empty")) + } else { + Ok(Self(value)) + } + } +} + +impl Display for ClaimPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let segments = self.iter().join(", "); + write!(f, "[{segments}]") + } +} + +impl Deref for ClaimPath { + type Target = [ClaimPathSegment]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A single segment of a [`ClaimPath`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged, try_from = "Value")] +pub enum ClaimPathSegment { + /// JSON object property. + Name(String), + /// JSON array entry. + Position(usize), + /// All properties or entries. + #[serde(serialize_with = "serialize_all_variant")] + All, +} + +impl Display for ClaimPathSegment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::All => write!(f, "null"), + Self::Name(name) => write!(f, "\"{name}\""), + Self::Position(i) => write!(f, "{i}"), + } + } +} + +impl TryFrom for ClaimPathSegment { + type Error = anyhow::Error; + fn try_from(value: Value) -> Result { + match value { + Value::Null => Ok(ClaimPathSegment::All), + Value::String(s) => Ok(ClaimPathSegment::Name(s)), + Value::Number(n) => n + .as_u64() + .ok_or_else(|| anyhow::anyhow!("expected number greater or equal to 0")) + .map(|n| ClaimPathSegment::Position(n as usize)), + _ => Err(anyhow::anyhow!("expected either a string, number, or null")), + } + } +} + +fn serialize_all_variant(serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_none() +} + +/// Information about whether a given claim is selectively disclosable. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ClaimDisclosability { + /// The issuer **must** make the claim selectively disclosable. + Always, + /// The issuer **may** make the claim selectively disclosable. + #[default] + Allowed, + /// The issuer **must not** make the claim selectively disclosable. + Never, +} + +/// Display information for a given claim. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ClaimDisplay { + /// A language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). + pub lang: String, + /// A human-readable label for the claim. + pub label: String, + /// A human-readable description for the claim. + pub description: Option, +} + +enum OneOrManyValue<'v> { + One(&'v Value), + Many(Box + 'v>), +} + +impl<'v> OneOrManyValue<'v> { + fn get(self, segment: &ClaimPathSegment) -> anyhow::Result> { + match self { + Self::One(value) => index_value(value, segment), + Self::Many(values) => { + let new_values = values + .map(|value| index_value(value, segment)) + .collect::>>()? + .into_iter() + .flatten(); + + Ok(OneOrManyValue::Many(Box::new(new_values))) + } + } + } +} + +struct OneOrManyValueIter<'v>(Option>); + +impl<'v> OneOrManyValueIter<'v> { + fn new(value: OneOrManyValue<'v>) -> Self { + Self(Some(value)) + } +} + +impl<'v> IntoIterator for OneOrManyValue<'v> { + type IntoIter = OneOrManyValueIter<'v>; + type Item = &'v Value; + fn into_iter(self) -> Self::IntoIter { + OneOrManyValueIter::new(self) + } +} + +impl<'v> Iterator for OneOrManyValueIter<'v> { + type Item = &'v Value; + fn next(&mut self) -> Option { + match self.0.take()? { + OneOrManyValue::One(v) => Some(v), + OneOrManyValue::Many(mut values) => { + let value = values.next(); + self.0 = Some(OneOrManyValue::Many(values)); + + value + } + } + } +} + +fn index_value<'v>(value: &'v Value, segment: &ClaimPathSegment) -> anyhow::Result> { + match segment { + ClaimPathSegment::Name(name) => value.get(name).map(OneOrManyValue::One), + ClaimPathSegment::Position(i) => value.get(i).map(OneOrManyValue::One), + ClaimPathSegment::All => value + .as_array() + .map(|values| OneOrManyValue::Many(Box::new(values.iter()))), + } + .ok_or_else(|| anyhow::anyhow!("value {value:#} has no element {segment}")) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + fn sample_obj() -> Value { + json!({ + "vct": "https://betelgeuse.example.com/education_credential", + "name": "Arthur Dent", + "address": { + "street_address": "42 Market Street", + "city": "Milliways", + "postal_code": "12345" + }, + "degrees": [ + { + "type": "Bachelor of Science", + "university": "University of Betelgeuse" + }, + { + "type": "Master of Science", + "university": "University of Betelgeuse" + } + ], + "nationalities": ["British", "Betelgeusian"] + }) + } + + #[test] + fn claim_path_works() { + let name_path = serde_json::from_value::(json!(["name"])).unwrap(); + let city_path = serde_json::from_value::(json!(["address", "city"])).unwrap(); + let first_degree_path = serde_json::from_value::(json!(["degrees", 0])).unwrap(); + let degrees_types_path = serde_json::from_value::(json!(["degrees", null, "type"])).unwrap(); + + assert!(matches!( + name_path.reverse_index(&sample_obj()).unwrap(), + OneOrManyValue::One(&Value::String(_)) + )); + assert!(matches!( + city_path.reverse_index(&sample_obj()).unwrap(), + OneOrManyValue::One(&Value::String(_)) + )); + assert!(matches!( + first_degree_path.reverse_index(&sample_obj()).unwrap(), + OneOrManyValue::One(&Value::Object(_)) + )); + let obj = &sample_obj(); + let mut degree_types = degrees_types_path.reverse_index(obj).unwrap().into_iter(); + assert_eq!(degree_types.next().unwrap().as_str(), Some("Bachelor of Science")); + assert_eq!(degree_types.next().unwrap().as_str(), Some("Master of Science")); + assert_eq!(degree_types.next(), None); + } +} diff --git a/identity_credential/src/sd_jwt_vc/metadata/display.rs b/identity_credential/src/sd_jwt_vc/metadata/display.rs new file mode 100644 index 0000000000..f10212d9fc --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/display.rs @@ -0,0 +1,23 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; + +/// Credential type's display information of a given language. +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct DisplayMetadata { + /// Language tag as defined in [RFC5646](https://www.rfc-editor.org/rfc/rfc5646.txt). + pub lang: String, + /// VC type's human-readable name. + pub name: String, + /// VC type's human-readable description. + pub description: Option, + /// Optional rendering information. + pub rendering: Option, +} + +/// Information on how to render a given credential type. +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct RenderingMetadata(serde_json::Map); diff --git a/identity_credential/src/sd_jwt_vc/metadata/integrity.rs b/identity_credential/src/sd_jwt_vc/metadata/integrity.rs new file mode 100644 index 0000000000..d41ca1f097 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/integrity.rs @@ -0,0 +1,121 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; +use std::str::FromStr; + +use anyhow::anyhow; +use identity_core::convert::Base; +use identity_core::convert::BaseEncoding; +use serde::Deserialize; +use serde::Serialize; + +/// An integrity metadata string as defined in [W3C SRI](https://www.w3.org/TR/SRI/#integrity-metadata). +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(try_from = "String")] +pub struct IntegrityMetadata(String); + +impl IntegrityMetadata { + /// Parses an [`IntegrityMetadata`] from a string. + /// ## Example + /// ```rust + /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; + /// + /// let integrity_data = IntegrityMetadata::parse( + /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd", + /// ) + /// .unwrap(); + /// ``` + pub fn parse(s: &str) -> Result { + s.parse() + } + + /// Returns the digest algorithm's identifier string. + /// ## Example + /// ```rust + /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; + /// + /// let integrity_data: IntegrityMetadata = + /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" + /// .parse() + /// .unwrap(); + /// assert_eq!(integrity_data.alg(), "sha384"); + /// ``` + pub fn alg(&self) -> &str { + self.0.split_once('-').unwrap().0 + } + + /// Returns the base64 encoded digest part. + /// ## Example + /// ```rust + /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; + /// + /// let integrity_data: IntegrityMetadata = + /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" + /// .parse() + /// .unwrap(); + /// assert_eq!( + /// integrity_data.digest(), + /// "dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" + /// ); + /// ``` + pub fn digest(&self) -> &str { + self.0.split('-').nth(1).unwrap() + } + + /// Returns the digest's bytes. + pub fn digest_bytes(&self) -> Vec { + BaseEncoding::decode(self.digest(), Base::Base64).unwrap() + } + + /// Returns the option part. + /// ## Example + /// ```rust + /// use identity_credential::sd_jwt_vc::metadata::IntegrityMetadata; + /// + /// let integrity_data: IntegrityMetadata = + /// "sha384-dOTZf16X8p34q2/kYyEFm0jh89uTjikhnzjeLeF0FHsEaYKb1A1cv+Lyv4Hk8vHd" + /// .parse() + /// .unwrap(); + /// assert!(integrity_data.options().is_none()); + /// ``` + pub fn options(&self) -> Option<&str> { + self.0.splitn(3, '-').nth(2) + } +} + +impl AsRef for IntegrityMetadata { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +impl Display for IntegrityMetadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl FromStr for IntegrityMetadata { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + Self::try_from(s.to_owned()) + } +} + +impl TryFrom for IntegrityMetadata { + type Error = anyhow::Error; + fn try_from(value: String) -> Result { + let mut metadata_parts = value.splitn(3, '-'); + let _alg = metadata_parts + .next() + .ok_or_else(|| anyhow!("invalid integrity metadata"))?; + let _digest = metadata_parts + .next() + .and_then(|digest| BaseEncoding::decode(digest, Base::Base64).ok()) + .ok_or_else(|| anyhow!("invalid integrity metadata"))?; + let _options = metadata_parts.next(); + + Ok(Self(value)) + } +} diff --git a/identity_credential/src/sd_jwt_vc/metadata/issuer.rs b/identity_credential/src/sd_jwt_vc/metadata/issuer.rs new file mode 100644 index 0000000000..1b9838815c --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/issuer.rs @@ -0,0 +1,94 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; +use identity_verification::jwk::JwkSet; +use serde::Deserialize; +use serde::Serialize; + +use crate::sd_jwt_vc::Error; +use crate::sd_jwt_vc::SdJwtVc; +#[allow(unused_imports)] +use crate::sd_jwt_vc::SdJwtVcClaims; + +/// Path used to query [`IssuerMetadata`] for a given JWT VC issuer. +pub const WELL_KNOWN_VC_ISSUER: &str = "/.well-known/jwt-vc-issuer"; + +/// SD-JWT VC issuer's metadata. Contains information about one issuer's +/// public keys, either as an embedded JWK Set or a reference to one. +/// ## Notes +/// - [`IssuerMetadata::issuer`] must exactly match [`SdJwtVcClaims::iss`] in order to be considered valid. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct IssuerMetadata { + /// Issuer URI. + pub issuer: Url, + /// JWK Set containing the issuer's public keys. + #[serde(flatten)] + pub jwks: Jwks, +} + +impl IssuerMetadata { + /// Checks the validity of this [`IssuerMetadata`]. + /// [`IssuerMetadata::issuer`] must match `sd_jwt_vc`'s iss claim's value. + pub fn validate(&self, sd_jwt_vc: &SdJwtVc) -> Result<(), Error> { + let expected_issuer = &sd_jwt_vc.claims().iss; + let actual_issuer = &self.issuer; + if actual_issuer != expected_issuer { + Err(Error::InvalidIssuerMetadata(anyhow::anyhow!( + "expected issuer \"{expected_issuer}\", but found \"{actual_issuer}\"" + ))) + } else { + Ok(()) + } + } +} + +/// JWK Set containing the issuer's public keys or a URL string referencing them. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub enum Jwks { + /// Reference to a JWK set. + #[serde(rename = "jwks_uri")] + Uri(Url), + /// An embedded JWK set. + #[serde(rename = "jwks")] + Object(JwkSet), +} + +#[cfg(test)] +mod tests { + use super::*; + + const EXAMPLE_URI_ISSUER_METADATA: &str = r#" +{ + "issuer":"https://example.com", + "jwks_uri":"https://jwt-vc-issuer.example.org/my_public_keys.jwks" +} + "#; + const EXAMPLE_JWKS_ISSUER_METADATA: &str = r#" +{ + "issuer":"https://example.com", + "jwks":{ + "keys":[ + { + "kid":"doc-signer-05-25-2022", + "e":"AQAB", + "n":"nj3YJwsLUFl9BmpAbkOswCNVx17Eh9wMO-_AReZwBqfaWFcfGHrZXsIV2VMCNVNU8Tpb4obUaSXcRcQ-VMsfQPJm9IzgtRdAY8NN8Xb7PEcYyklBjvTtuPbpzIaqyiUepzUXNDFuAOOkrIol3WmflPUUgMKULBN0EUd1fpOD70pRM0rlp_gg_WNUKoW1V-3keYUJoXH9NztEDm_D2MQXj9eGOJJ8yPgGL8PAZMLe2R7jb9TxOCPDED7tY_TU4nFPlxptw59A42mldEmViXsKQt60s1SLboazxFKveqXC_jpLUt22OC6GUG63p-REw-ZOr3r845z50wMuzifQrMI9bQ", + "kty":"RSA" + } + ] + } +} + "#; + + #[test] + fn deserializing_uri_metadata_works() { + let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_URI_ISSUER_METADATA).unwrap(); + assert!(matches!(issuer_metadata.jwks, Jwks::Uri(_))); + } + + #[test] + fn deserializing_jwks_metadata_works() { + let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_JWKS_ISSUER_METADATA).unwrap(); + assert!(matches!(issuer_metadata.jwks, Jwks::Object { .. })); + } +} diff --git a/identity_credential/src/sd_jwt_vc/metadata/mod.rs b/identity_credential/src/sd_jwt_vc/metadata/mod.rs new file mode 100644 index 0000000000..662c42032f --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod claim; +mod display; +mod integrity; +mod issuer; +mod vc_type; + +pub use claim::*; +pub use display::*; +pub use integrity::*; +pub use issuer::*; +pub use vc_type::*; diff --git a/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs b/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs new file mode 100644 index 0000000000..04f3a87a11 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/metadata/vc_type.rs @@ -0,0 +1,268 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use futures::future::FutureExt; +use futures::future::LocalBoxFuture; +use identity_core::common::Url; +use itertools::Itertools as _; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; + +use crate::sd_jwt_vc::Error; +use crate::sd_jwt_vc::Resolver; +use crate::sd_jwt_vc::Result; + +use super::ClaimMetadata; +use super::DisplayMetadata; +use super::IntegrityMetadata; + +/// Path used to retrieve VC Type Metadata. +pub const WELL_KNOWN_VCT: &str = "/.well-known/vct"; + +/// SD-JWT VC's credential type. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TypeMetadata { + /// A human-readable name for the type, intended for developers reading the JSON document. + pub name: Option, + /// A human-readable description for the type, intended for developers reading the JSON document. + pub description: Option, + /// A URI of another type that this type extends. + pub extends: Option, + /// Integrity metadata for the extended type. + #[serde(rename = "extends#integrity")] + pub extends_integrity: Option, + /// Either an embedded schema or a reference to one. + #[serde(flatten)] + pub schema: Option, + /// A list containing display information for the type. + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub display: Vec, + /// A list of [`ClaimMetadata`] containing information about particular claims. + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub claims: Vec, +} + +impl TypeMetadata { + /// Returns the name of this VC type, if any. + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + /// Returns the description of this VC type, if any. + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + /// Returns the URI or string of the type this VC type extends, if any. + pub fn extends(&self) -> Option<&Url> { + self.extends.as_ref() + } + /// Returns the integrity string of the extended type object, if any. + pub fn extends_integrity(&self) -> Option<&str> { + self.extends_integrity.as_ref().map(|meta| meta.as_ref()) + } + /// Returns the [`ClaimMetadata`]s associated with this credential type. + pub fn claim_metadata(&self) -> &[ClaimMetadata] { + &self.claims + } + /// Returns the [`DisplayMetadata`]s associated with this credential type. + pub fn display_metadata(&self) -> &[DisplayMetadata] { + &self.display + } + /// Uses this [`TypeMetadata`] to validate JSON object `credential`. This method fails + /// if the schema is referenced instead of embedded. + /// Use [`TypeMetadata::validate_credential_with_resolver`] for such cases. + /// ## Notes + /// This method ignores type extensions. + pub fn validate_credential(&self, credential: &Value) -> Result<()> { + match &self.schema { + Some(TypeSchema::Object { schema, .. }) => validate_credential_with_schema(schema, credential), + Some(_) => Err(Error::Validation(anyhow::anyhow!( + "this credential type references a schema; resolution is required" + ))), + None => Ok(()), + } + } + + /// Similar to [`TypeMetadata::validate_credential`], but accepts a [`Resolver`] + /// [`Url`] -> [`Value`] that is used to resolve any reference to either + /// another type or JSON schema. + pub async fn validate_credential_with_resolver(&self, credential: &Value, resolver: &R) -> Result<()> + where + R: Resolver, + { + validate_credential_impl(self.clone(), credential, resolver, vec![]).await + } +} + +// Recursively validate a credential. +fn validate_credential_impl<'c, 'r, R>( + current_type: TypeMetadata, + credential: &'c Value, + resolver: &'r R, + mut passed_types: Vec, +) -> LocalBoxFuture<'c, Result<()>> +where + R: Resolver, + 'r: 'c, +{ + async move { + // Check if current type has already been checked. + let is_type_already_checked = passed_types.contains(¤t_type); + if is_type_already_checked { + // This is a dependency cycle! + return Err(Error::Validation(anyhow::anyhow!("dependency cycle detected"))); + } + + // Check if `validate_credential` should have been called instead. + let has_extend = current_type.extends.is_none(); + let is_immediate = current_type + .schema + .as_ref() + .map(|schema| matches!(schema, &TypeSchema::Object { .. })) + .unwrap_or(true); + + if is_immediate && !has_extend { + return current_type.validate_credential(credential); + } + + if !is_immediate { + // Fetch schema and validate `current_type`. + let TypeSchema::Uri { schema_uri, .. } = current_type.schema.as_ref().unwrap() else { + unreachable!("schema is provided through `schema_uri` as checked by `validate_credential`"); + }; + let schema = resolver.resolve(schema_uri).await.map_err(|e| Error::Resolution { + input: schema_uri.to_string(), + source: e, + })?; + validate_credential_with_schema(&schema, credential)?; + } + + // Check for extends. + if let Some(extends_uri) = current_type.extends() { + // Fetch the extended type metadata and parse it. + let raw_type_metadata = resolver.resolve(extends_uri).await.map_err(|e| Error::Resolution { + input: extends_uri.to_string(), + source: e, + })?; + let type_metadata = + serde_json::from_value(raw_type_metadata).map_err(|e| Error::InvalidTypeMetadata(e.into()))?; + // Forward validation of new type. + passed_types.push(current_type); + validate_credential_impl(type_metadata, credential, resolver, passed_types).await + } else { + Ok(()) + } + } + .boxed_local() +} + +fn validate_credential_with_schema(schema: &Value, credential: &Value) -> Result<()> { + let schema = jsonschema::compile(schema).map_err(|e| Error::Validation(anyhow::anyhow!(e.to_string())))?; + schema.validate(credential).map_err(|errors| { + let error_msg = errors.map(|e| e.to_string()).join("; "); + Error::Validation(anyhow::anyhow!(error_msg)) + }) +} + +/// Either a reference to or an embedded JSON Schema. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(untagged)] +pub enum TypeSchema { + /// URI reference to a JSON schema. + Uri { + /// URI of the referenced JSON schema. + schema_uri: Url, + /// Integrity string for the referenced schema. + #[serde(rename = "schema_uri#integrity")] + schema_uri_integrity: Option, + }, + /// An embedded JSON schema. + Object { + /// The JSON schema. + schema: Value, + /// Integrity of the JSON schema. + #[serde(rename = "schema#integrity")] + schema_integrity: Option, + }, +} + +#[cfg(test)] +mod tests { + use std::sync::LazyLock; + + use async_trait::async_trait; + use serde_json::json; + + use crate::sd_jwt_vc::resolver; + + use super::*; + + static IMMEDIATE_TYPE_METADATA: LazyLock = LazyLock::new(|| TypeMetadata { + name: Some("immediate credential".to_string()), + description: None, + extends: None, + extends_integrity: None, + display: vec![], + claims: vec![], + schema: Some(TypeSchema::Object { + schema: json!({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "required": ["name", "age"] + }), + schema_integrity: None, + }), + }); + static REFERENCED_TYPE_METADATA: LazyLock = LazyLock::new(|| TypeMetadata { + name: Some("immediate credential".to_string()), + description: None, + extends: None, + extends_integrity: None, + display: vec![], + claims: vec![], + schema: Some(TypeSchema::Uri { + schema_uri: Url::parse("https://example.com/vc_types/1").unwrap(), + schema_uri_integrity: None, + }), + }); + + struct SchemaResolver; + #[async_trait] + impl Resolver for SchemaResolver { + async fn resolve(&self, _input: &Url) -> resolver::Result { + Ok(serde_json::to_value(IMMEDIATE_TYPE_METADATA.clone().schema).unwrap()) + } + } + + #[test] + fn validation_of_immediate_type_metadata_works() { + IMMEDIATE_TYPE_METADATA + .validate_credential(&json!({ + "name": "John Doe", + "age": 42 + })) + .unwrap(); + } + + #[tokio::test] + async fn validation_of_referenced_type_metadata_works() { + REFERENCED_TYPE_METADATA + .validate_credential_with_resolver( + &json!({ + "name": "Aristide Zantedeschi", + "age": 90, + }), + &SchemaResolver, + ) + .await + .unwrap(); + } +} diff --git a/identity_credential/src/sd_jwt_vc/mod.rs b/identity_credential/src/sd_jwt_vc/mod.rs new file mode 100644 index 0000000000..66f4b530a1 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod builder; +mod claims; +mod error; +/// Additional metadata defined by the SD-JWT VC specification +/// such as issuer's metadata and credential type metadata. +pub mod metadata; +mod presentation; +/// Resolver trait. +pub mod resolver; +mod status; +#[cfg(test)] +pub(crate) mod tests; +mod token; + +pub use builder::*; +pub use claims::*; +pub use error::Error; +pub use error::Result; +pub use presentation::*; +pub use resolver::Resolver; +pub use status::*; +pub use token::*; diff --git a/identity_credential/src/sd_jwt_vc/presentation.rs b/identity_credential/src/sd_jwt_vc/presentation.rs new file mode 100644 index 0000000000..06a2d2feac --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/presentation.rs @@ -0,0 +1,54 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use super::Error; +use super::Result; +use super::SdJwtVc; +use super::SdJwtVcClaims; + +use sd_jwt_payload_rework::Disclosure; +use sd_jwt_payload_rework::Hasher; +use sd_jwt_payload_rework::KeyBindingJwt; +use sd_jwt_payload_rework::SdJwtPresentationBuilder; + +/// Builder structure to create an SD-JWT VC presentation. +/// It allows users to conceal claims and attach a key binding JWT. +#[derive(Debug, Clone)] +pub struct SdJwtVcPresentationBuilder { + vc_claims: SdJwtVcClaims, + builder: SdJwtPresentationBuilder, +} + +impl SdJwtVcPresentationBuilder { + /// Prepare a presentation for a given [`SdJwtVc`]. + pub fn new(token: SdJwtVc, hasher: &dyn Hasher) -> Result { + let SdJwtVc { + sd_jwt, + parsed_claims: vc_claims, + } = token; + let builder = sd_jwt.into_presentation(hasher).map_err(Error::SdJwt)?; + + Ok(Self { vc_claims, builder }) + } + /// Removes the disclosure for the property at `path`, conceiling it. + /// + /// ## Notes + /// - When concealing a claim more than one disclosure may be removed: the disclosure for the claim itself and the + /// disclosures for any concealable sub-claim. + pub fn conceal(mut self, path: &str) -> Result { + self.builder = self.builder.conceal(path).map_err(Error::SdJwt)?; + Ok(self) + } + + /// Adds a [`KeyBindingJwt`] to this [`SdJwtVc`]'s presentation. + pub fn attach_key_binding_jwt(mut self, kb_jwt: KeyBindingJwt) -> Self { + self.builder = self.builder.attach_key_binding_jwt(kb_jwt); + self + } + + /// Returns the resulting [`SdJwtVc`] together with all removed disclosures. + pub fn finish(self) -> Result<(SdJwtVc, Vec)> { + let (sd_jwt, disclosures) = self.builder.finish()?; + Ok((SdJwtVc::new(sd_jwt, self.vc_claims), disclosures)) + } +} diff --git a/identity_credential/src/sd_jwt_vc/resolver.rs b/identity_credential/src/sd_jwt_vc/resolver.rs new file mode 100644 index 0000000000..69bd74fc7a --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/resolver.rs @@ -0,0 +1,29 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use thiserror::Error; + +pub(crate) type Result = std::result::Result; + +/// [`Resolver`]'s errors. +#[derive(Debug, Error)] +pub enum Error { + /// The queried item doesn't exist. + #[error("The requested item \"{0}\" was not found.")] + NotFound(String), + /// Failed to parse input. + #[error("Failed to parse the provided input into a resolvable type: {0}")] + ParsingFailure(#[source] anyhow::Error), + /// Generic error. + #[error(transparent)] + Generic(#[from] anyhow::Error), +} + +/// A type capable of asynchronously producing values of type `T` from inputs of type `I`. +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait Resolver { + /// Fetch the resource of type [`Resolver::Target`] using `input`. + async fn resolve(&self, input: &I) -> Result; +} diff --git a/identity_credential/src/sd_jwt_vc/status.rs b/identity_credential/src/sd_jwt_vc/status.rs new file mode 100644 index 0000000000..1c68db6d4c --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/status.rs @@ -0,0 +1,52 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// SD-JWT VC's `status` claim value. Used to retrieve the status of the token. +pub struct Status(StatusMechanism); + +/// Mechanism used for representing the status of an SD-JWT VC token. +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum StatusMechanism { + /// Reference to a status list containing this token's status. + #[serde(rename = "status_list")] + StatusList(StatusListRef), + /// A non-standard status mechanism. + #[serde(untagged)] + Custom(serde_json::Value), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// A reference to an OAuth status list. +/// See [OAuth StatusList specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-02) +/// for more information. +pub struct StatusListRef { + /// URI of the status list. + pub uri: Url, + /// Index of the entry containing this token's status. + pub idx: usize, +} + +#[cfg(test)] +mod tests { + use super::*; + + use serde_json::json; + + #[test] + fn round_trip() { + let status_value = json!({ + "status_list": { + "idx": 420, + "uri": "https://example.com/statuslists/1" + } + }); + let status: Status = serde_json::from_value(status_value.clone()).unwrap(); + assert_eq!(serde_json::to_value(status).unwrap(), status_value); + } +} diff --git a/identity_credential/src/sd_jwt_vc/tests/mod.rs b/identity_credential/src/sd_jwt_vc/tests/mod.rs new file mode 100644 index 0000000000..f93fe20784 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/tests/mod.rs @@ -0,0 +1,113 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use async_trait::async_trait; +use identity_core::convert::Base; +use identity_core::convert::BaseEncoding; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkParamsOct; +use identity_verification::jws::JwsVerifier; +use josekit::jws::JwsHeader; +use josekit::jws::HS256; +use josekit::jwt::JwtPayload; +use josekit::jwt::{self}; +use sd_jwt_payload_rework::JsonObject; +use sd_jwt_payload_rework::JwsSigner; +use serde::Serialize; +use serde_json::Value; + +use super::resolver; +use super::Resolver; + +mod validation; + +pub(crate) const ISSUER_SECRET: &[u8] = b"0123456789ABCDEF0123456789ABCDEF"; + +/// A JWS signer that uses HS256 with a static secret string. +pub(crate) struct TestSigner; + +pub(crate) fn signer_secret_jwk() -> Jwk { + let mut params = JwkParamsOct::new(); + params.k = BaseEncoding::encode(ISSUER_SECRET, Base::Base64Url); + let mut jwk = Jwk::from_params(params); + jwk.set_kid("key1"); + + jwk +} + +#[async_trait] +impl JwsSigner for TestSigner { + type Error = josekit::JoseError; + async fn sign(&self, header: &JsonObject, payload: &JsonObject) -> std::result::Result, Self::Error> { + let signer = HS256.signer_from_bytes(ISSUER_SECRET)?; + let header = JwsHeader::from_map(header.clone())?; + let payload = JwtPayload::from_map(payload.clone())?; + let jws = jwt::encode_with_signer(&payload, &header, &signer)?; + + Ok(jws.into_bytes()) + } +} + +#[derive(Default, Debug, Clone)] +pub(crate) struct TestResolver(HashMap>); + +impl TestResolver { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn insert_resource(&mut self, id: K, value: V) + where + K: ToString, + V: Serialize, + { + let value = serde_json::to_vec(&value).unwrap(); + self.0.insert(id.to_string(), value); + } +} + +#[async_trait] +impl Resolver> for TestResolver +where + I: ToString + Sync, +{ + async fn resolve(&self, id: &I) -> Result, resolver::Error> { + let id = id.to_string(); + self.0.get(&id).cloned().ok_or_else(|| resolver::Error::NotFound(id)) + } +} + +#[async_trait] +impl Resolver for TestResolver +where + I: ToString + Sync, +{ + async fn resolve(&self, id: &I) -> Result { + let id = id.to_string(); + self + .0 + .get(&id) + .ok_or_else(|| resolver::Error::NotFound(id)) + .and_then(|bytes| serde_json::from_slice(bytes).map_err(|e| resolver::Error::ParsingFailure(e.into()))) + } +} + +pub(crate) struct TestJwsVerifier; + +impl JwsVerifier for TestJwsVerifier { + fn verify( + &self, + input: identity_verification::jws::VerificationInput, + public_key: &Jwk, + ) -> Result<(), identity_verification::jws::SignatureVerificationError> { + let key = serde_json::to_value(public_key.clone()) + .and_then(serde_json::from_value) + .unwrap(); + let verifier = HS256.verifier_from_jwk(&key).unwrap(); + verifier.verify(&input.signing_input, &input.decoded_signature).unwrap(); + + Ok(()) + } +} diff --git a/identity_credential/src/sd_jwt_vc/tests/validation.rs b/identity_credential/src/sd_jwt_vc/tests/validation.rs new file mode 100644 index 0000000000..bc17b33952 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/tests/validation.rs @@ -0,0 +1,172 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_verification::jwk::JwkSet; +use sd_jwt_payload_rework::Sha256Hasher; +use serde_json::json; + +use crate::sd_jwt_vc::metadata::IssuerMetadata; +use crate::sd_jwt_vc::metadata::Jwks; +use crate::sd_jwt_vc::metadata::TypeMetadata; +use crate::sd_jwt_vc::tests::TestJwsVerifier; +use crate::sd_jwt_vc::Error; +use crate::sd_jwt_vc::SdJwtVcBuilder; + +use super::TestResolver; +use super::TestSigner; + +fn issuer_metadata() -> IssuerMetadata { + let mut jwk_set = JwkSet::new(); + jwk_set.add(super::signer_secret_jwk()); + + IssuerMetadata { + issuer: "https://example.com".parse().unwrap(), + jwks: Jwks::Object(jwk_set), + } +} + +fn test_resolver() -> TestResolver { + let mut test_resolver = TestResolver::new(); + test_resolver.insert_resource("https://example.com/.well-known/jwt-vc-issuer/", issuer_metadata()); + test_resolver.insert_resource( + "https://example.com/.well-known/vct/education_credential", + vc_metadata(), + ); + + test_resolver +} + +#[tokio::test] +async fn validation_of_valid_token_works() -> anyhow::Result<()> { + let sd_jwt_credential = SdJwtVcBuilder::new(json!({ + "name": "John Doe", + "address": { + "street_address": "A random street", + "number": "3a" + }, + "degree": [] + }))? + .header(std::iter::once(("kid".to_string(), serde_json::Value::String("key1".to_string()))).collect()) + .vct("https://example.com/education_credential".parse::()?) + .iat(Timestamp::now_utc()) + .iss("https://example.com".parse()?) + .make_concealable("/address/street_address")? + .make_concealable("/address")? + .finish(&TestSigner, "HS256") + .await?; + + let resolver = test_resolver(); + sd_jwt_credential + .validate(&resolver, &TestJwsVerifier, &Sha256Hasher::new()) + .await?; + Ok(()) +} + +#[tokio::test] +async fn validation_of_invalid_token_fails() -> anyhow::Result<()> { + let sd_jwt_credential = SdJwtVcBuilder::new(json!({ + "name": "John Doe", + "address": { + "street_address": "A random street", + "number": "3a" + }, + "degree": [] + }))? + .header(std::iter::once(("kid".to_string(), serde_json::Value::String("invalid_key".to_string()))).collect()) + .vct("https://example.com/education_credential".parse::()?) + .iat(Timestamp::now_utc()) + .iss("https://example.com".parse()?) + .make_concealable("/address/street_address")? + .make_concealable("/address")? + .finish(&TestSigner, "HS256") + .await?; + + let resolver = test_resolver(); + let error = sd_jwt_credential + .validate(&resolver, &TestJwsVerifier, &Sha256Hasher::new()) + .await + .unwrap_err(); + assert!(matches!(error, Error::Verification(_))); + + Ok(()) +} + +fn vc_metadata() -> TypeMetadata { + serde_json::from_str( + r#"{ + "vct": "https://example.com/education_credential", + "name": "Betelgeuse Education Credential - Preliminary Version", + "description": "This is our development version of the education credential. Don't panic.", + "claims": [ + { + "path": ["name"], + "display": [ + { + "lang": "de-DE", + "label": "Vor- und Nachname", + "description": "Der Name des Studenten" + }, + { + "lang": "en-US", + "label": "Name", + "description": "The name of the student" + } + ], + "sd": "allowed" + }, + { + "path": ["address"], + "display": [ + { + "lang": "de-DE", + "label": "Adresse", + "description": "Adresse zum Zeitpunkt des Abschlusses" + }, + { + "lang": "en-US", + "label": "Address", + "description": "Address at the time of graduation" + } + ], + "sd": "always" + }, + { + "path": ["address", "street_address"], + "display": [ + { + "lang": "de-DE", + "label": "Straße" + }, + { + "lang": "en-US", + "label": "Street Address" + } + ], + "sd": "always", + "svg_id": "address_street_address" + }, + { + "path": ["degrees", null], + "display": [ + { + "lang": "de-DE", + "label": "Abschluss", + "description": "Der Abschluss des Studenten" + }, + { + "lang": "en-US", + "label": "Degree", + "description": "Degree earned by the student" + } + ], + "sd": "allowed" + } + ], + "schema_url": "https://example.com/credential-schema", + "schema_url#integrity": "sha256-o984vn819a48ui1llkwPmKjZ5t0WRL5ca_xGgX3c1VLmXfh" +}"#, + ) + .unwrap() +} diff --git a/identity_credential/src/sd_jwt_vc/token.rs b/identity_credential/src/sd_jwt_vc/token.rs new file mode 100644 index 0000000000..0cc05514a1 --- /dev/null +++ b/identity_credential/src/sd_jwt_vc/token.rs @@ -0,0 +1,476 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; +use std::ops::Deref; +use std::str::FromStr; + +use super::claims::SdJwtVcClaims; +use super::metadata::ClaimMetadata; +use super::metadata::IssuerMetadata; +use super::metadata::Jwks; +use super::metadata::TypeMetadata; +use super::metadata::WELL_KNOWN_VCT; +use super::metadata::WELL_KNOWN_VC_ISSUER; +use super::resolver::Error as ResolverErr; +use super::Error; +use super::Resolver; +use super::Result; +use super::SdJwtVcPresentationBuilder; +use crate::validator::JwtCredentialValidator as JwsUtils; +use crate::validator::KeyBindingJWTValidationOptions; +use anyhow::anyhow; +use identity_core::common::StringOrUrl; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::convert::ToJson as _; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkSet; +use identity_verification::jws::JwsVerifier; +use sd_jwt_payload_rework::Hasher; +use sd_jwt_payload_rework::JsonObject; +use sd_jwt_payload_rework::RequiredKeyBinding; +use sd_jwt_payload_rework::SdJwt; +use sd_jwt_payload_rework::SHA_ALG_NAME; +use serde_json::Value; + +/// SD-JWT VC's JOSE header `typ`'s value. +pub const SD_JWT_VC_TYP: &str = "vc+sd-jwt"; + +#[derive(Debug, Clone, PartialEq, Eq)] +/// An SD-JWT carrying a verifiable credential as described in +/// [SD-JWT VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-04.html). +pub struct SdJwtVc { + pub(crate) sd_jwt: SdJwt, + pub(crate) parsed_claims: SdJwtVcClaims, +} + +impl Deref for SdJwtVc { + type Target = SdJwt; + fn deref(&self) -> &Self::Target { + &self.sd_jwt + } +} + +impl SdJwtVc { + pub(crate) fn new(sd_jwt: SdJwt, claims: SdJwtVcClaims) -> Self { + Self { + sd_jwt, + parsed_claims: claims, + } + } + + /// Parses a string into an [`SdJwtVc`]. + pub fn parse(s: &str) -> Result { + s.parse() + } + + /// Returns a reference to this [`SdJwtVc`]'s JWT claims. + pub fn claims(&self) -> &SdJwtVcClaims { + &self.parsed_claims + } + + /// Prepares this [`SdJwtVc`] for a presentation, returning an [`SdJwtVcPresentationBuilder`]. + /// ## Errors + /// - [`Error::SdJwt`] is returned if the provided `hasher`'s algorithm doesn't match the algorithm specified by + /// SD-JWT's `_sd_alg` claim. "sha-256" is used if the claim is missing. + pub fn into_presentation(self, hasher: &dyn Hasher) -> Result { + SdJwtVcPresentationBuilder::new(self, hasher) + } + + /// Returns the JSON object obtained by replacing all disclosures into their + /// corresponding JWT concealable claims. + pub fn into_disclosed_object(self, hasher: &dyn Hasher) -> Result { + SdJwt::from(self).into_disclosed_object(hasher).map_err(Error::SdJwt) + } + + /// Retrieves this SD-JWT VC's issuer's metadata by querying its default location. + /// ## Notes + /// This method doesn't perform any validation of the retrieved [`IssuerMetadata`] + /// besides its syntactical validity. + /// To check if the retrieved [`IssuerMetadata`] is valid use [`IssuerMetadata::validate`]. + pub async fn issuer_metadata(&self, resolver: &R) -> Result> + where + R: Resolver>, + { + let metadata_url = { + let origin = self.claims().iss.origin().ascii_serialization(); + let path = self.claims().iss.path(); + format!("{origin}{WELL_KNOWN_VC_ISSUER}{path}").parse().unwrap() + }; + match resolver.resolve(&metadata_url).await { + Err(ResolverErr::NotFound(_)) => Ok(None), + Err(e) => Err(Error::Resolution { + input: metadata_url.to_string(), + source: e, + }), + Ok(json_res) => serde_json::from_slice(&json_res) + .map_err(|e| Error::InvalidIssuerMetadata(e.into())) + .map(Some), + } + } + + /// Retrieve this SD-JWT VC credential's type metadata [`TypeMetadata`]. + /// ## Notes + /// `resolver` is fed with whatever value [`SdJwtVc`]'s `vct` might have. + /// If `vct` is a URI with scheme `https`, `resolver` must fetch the [`TypeMetadata`] + /// resource by combining `vct`'s value with [`WELL_KNOWN_VCT`]. To simplify this process + /// the utility function [`vct_to_url`] is provided. + /// + /// Returns the parsed [`TypeMetadata`] along with the raw [`Resolver`]'s response. + /// The latter can be used to validate the `vct#integrity` claim if present. + pub async fn type_metadata(&self, resolver: &R) -> Result<(TypeMetadata, Vec)> + where + R: Resolver>, + { + let vct = match self.claims().vct.clone() { + StringOrUrl::Url(url) => StringOrUrl::Url(vct_to_url(&url).unwrap_or(url)), + s => s, + }; + let raw = resolver.resolve(&vct).await.map_err(|e| Error::Resolution { + input: vct.to_string(), + source: e, + })?; + let metadata = serde_json::from_slice(&raw).map_err(|e| Error::InvalidTypeMetadata(e.into()))?; + + Ok((metadata, raw)) + } + + /// Resolves the issuer's public key in JWK format. + /// The issuer's JWK is first fetched through the issuer's metadata, + /// if this attempt fails `resolver` is used to query the key directly + /// through `kid`'s value. + pub async fn issuer_jwk(&self, resolver: &R) -> Result + where + R: Resolver>, + { + let kid = self + .header() + .get("kid") + .and_then(|value| value.as_str()) + .ok_or_else(|| Error::Verification(anyhow!("missing header claim `kid`")))?; + + // Try to find the key among issuer metadata jwk set. + if let jwk @ Ok(_) = self.issuer_jwk_from_iss_metadata(resolver, kid).await { + jwk + } else { + // Issuer has no metadata that can lead to its JWK. Let's see if it can be resolved directly. + let jwk_uri = kid.parse::().map_err(|_| { + Error::Verification(anyhow!( + "JWK's kid \"{kid}\" could not be found in JKW set and cannot be resolved" + )) + })?; + resolver + .resolve(&jwk_uri) + .await + .map_err(|e| Error::Resolution { + input: jwk_uri.to_string(), + source: e, + }) + .and_then(|bytes| { + serde_json::from_slice(&bytes).map_err(|e| Error::Verification(anyhow!("invalid JWK: {}", e))) + }) + } + } + + async fn issuer_jwk_from_iss_metadata(&self, resolver: &R, kid: &str) -> Result + where + R: Resolver>, + { + let metadata = self + .issuer_metadata(resolver) + .await? + .ok_or_else(|| Error::Verification(anyhow!("missing issuer metadata")))?; + metadata.validate(self)?; + + let jwks = match metadata.jwks { + Jwks::Object(jwks) => jwks, + Jwks::Uri(jwks_uri) => resolver + .resolve(&jwks_uri) + .await + .map_err(|e| Error::Resolution { + input: jwks_uri.into_string(), + source: e, + }) + .and_then(|bytes| serde_json::from_slice::(&bytes).map_err(|e| Error::Verification(e.into())))?, + }; + jwks + .iter() + .find(|jwk| jwk.kid() == Some(kid)) + .cloned() + .ok_or_else(|| Error::Verification(anyhow!("missing key \"{kid}\" in issuer JWK set"))) + } + + /// Verifies this [`SdJwtVc`] JWT's signature. + pub fn verify_signature(&self, jws_verifier: &V, jwk: &Jwk) -> Result<()> + where + V: JwsVerifier, + { + let sd_jwt_str = self.sd_jwt.to_string(); + let jws_input = { + let jwt_str = sd_jwt_str.split_once('~').unwrap().0; + JwsUtils::::decode(jwt_str).map_err(|e| Error::Verification(e.into()))? + }; + + JwsUtils::::verify_signature_raw(jws_input, jwk, jws_verifier) + .map_err(|e| Error::Verification(e.into())) + .and(Ok(())) + } + + /// Checks the disclosability of this [`SdJwtVc`]'s claims against a list of [`ClaimMetadata`]. + /// ## Notes + /// This check should be performed by the token's holder in order to assert the issuer's compliance with + /// the credential's type. + pub fn validate_claims_disclosability(&self, claims_metadata: &[ClaimMetadata]) -> Result<()> { + let claims = Value::Object(self.parsed_claims.sd_jwt_claims.deref().clone()); + claims_metadata + .iter() + .try_fold((), |_, meta| meta.check_value_disclosability(&claims)) + } + + /// Check whether this [`SdJwtVc`] is valid. + /// + /// This method checks: + /// - JWS signature + /// - credential's type + /// - claims' disclosability + pub async fn validate(&self, resolver: &R, jws_verifier: &V, hasher: &dyn Hasher) -> Result<()> + where + R: Resolver>, + R: Resolver>, + R: Resolver, + V: JwsVerifier, + { + // Signature verification. + // Fetch issuer's JWK. + let jwk = self.issuer_jwk(resolver).await?; + self.verify_signature(jws_verifier, &jwk)?; + + // Credential type. + // Fetch type metadata. Skip integrity check. + let fully_disclosed_token = self.clone().into_disclosed_object(hasher).map(Value::Object)?; + let (type_metadata, _) = self.type_metadata(resolver).await?; + type_metadata + .validate_credential_with_resolver(&fully_disclosed_token, resolver) + .await?; + + // Claims' disclosability. + self.validate_claims_disclosability(type_metadata.claim_metadata())?; + + Ok(()) + } + + /// Verify the signature of this [`SdJwtVc`]'s [sd_jwt_payload_rework::KeyBindingJwt]. + pub fn verify_key_binding(&self, jws_verifier: &V, jwk: &Jwk) -> Result<()> { + let Some(kb_jwt) = self.key_binding_jwt() else { + return Ok(()); + }; + let kb_jwt_str = kb_jwt.to_string(); + let jws_input = JwsUtils::::decode(&kb_jwt_str).map_err(|e| Error::Verification(e.into()))?; + + JwsUtils::::verify_signature_raw(jws_input, jwk, jws_verifier) + .map_err(|e| Error::Verification(e.into())) + .and(Ok(())) + } + + /// Check the validity of this [`SdJwtVc`]'s [sd_jwt_payload_rework::KeyBindingJwt]. + /// # Notes + /// Validation of the required key binding (specified through the `cnf` JWT's claim) + /// is only partially validated - custom and "jwe" requirement are not checked. + pub fn validate_key_binding( + &self, + jws_verifier: &V, + jwk: &Jwk, + hasher: &dyn Hasher, + options: &KeyBindingJWTValidationOptions, + ) -> Result<()> { + self.verify_key_binding(jws_verifier, jwk)?; + + if let Some(requirement) = self.required_key_bind() { + if self.key_binding_jwt().is_none() { + return Err(Error::Validation(anyhow!( + "a key binding was required but none was provided" + ))); + } + match requirement { + RequiredKeyBinding::Jwk(json_jwk) => { + if jwk.to_json_value().unwrap().as_object().unwrap() != json_jwk { + return Err(Error::Validation(anyhow!( + "key used for signing KB-JWT does not match the key required in this SD-JWT" + ))); + } + } + RequiredKeyBinding::Kid(kid) | RequiredKeyBinding::Jwu { kid, .. } => jwk + .kid() + .filter(|id| id == kid) + .ok_or_else(|| { + Error::Validation(anyhow::anyhow!( + "the provided JWK doesn't have required `kid` \"{kid}\"" + )) + }) + .map(|_| ())?, + _ => (), + } + } + + let Some(kb_jwt) = self.key_binding_jwt() else { + return Ok(()); + }; + let KeyBindingJWTValidationOptions { + nonce, + aud, + earliest_issuance_date, + latest_issuance_date, + .. + } = options; + + let issuance_date = + Timestamp::from_unix(kb_jwt.claims().iat).map_err(|_| Error::Validation(anyhow!("invalid `iat` value")))?; + + if let Some(earliest_issuance_date) = earliest_issuance_date { + if issuance_date < *earliest_issuance_date { + return Err(Error::Validation(anyhow!( + "this KB-JWT has been created earlier than `earliest_issuance_date`" + ))); + } + } + + if let Some(latest_issuance_date) = latest_issuance_date { + if issuance_date > *latest_issuance_date { + return Err(Error::Validation(anyhow!( + "this KB-JWT has been created later than `latest_issuance_date`" + ))); + } + } else if issuance_date > Timestamp::now_utc() { + return Err(Error::Validation(anyhow!("this KB-JWT has been created in the future"))); + } + + if let Some(nonce) = nonce { + if nonce != &kb_jwt.claims().nonce { + return Err(Error::Validation(anyhow!("invalid KB-JWT's nonce: expected {nonce}"))); + } + } + + if let Some(aud) = aud { + if aud != &kb_jwt.claims().aud { + return Err(Error::Validation(anyhow!("invalid KB-JWT's `aud`: expected \"{aud}\""))); + } + } + + // Validate SD-JWT digest. + if self.claims()._sd_alg.as_deref().unwrap_or(SHA_ALG_NAME) != hasher.alg_name() { + return Err(Error::Validation(anyhow!("invalid hasher"))); + } + let encoded_sd_jwt = self.to_string(); + let digest = { + let last_tilde_idx = encoded_sd_jwt.rfind('~').expect("SD-JWT has a '~'"); + let sd_jwt_no_kb = &encoded_sd_jwt[..=last_tilde_idx]; + + hasher.encoded_digest(sd_jwt_no_kb) + }; + if kb_jwt.claims().sd_hash != digest { + return Err(Error::Validation(anyhow!("invalid KB-JWT's `sd_hash`"))); + } + + Ok(()) + } +} + +/// Converts `vct` claim's URI value into the appropriate well-known URL. +/// ## Warnings +/// Returns an [`Option::None`] if the URI's scheme is not `https`. +pub fn vct_to_url(resource: &Url) -> Option { + if resource.scheme() != "https" { + None + } else { + let origin = resource.origin().ascii_serialization(); + let path = resource.path(); + Some(format!("{origin}{WELL_KNOWN_VCT}{path}").parse().unwrap()) + } +} + +impl TryFrom for SdJwtVc { + type Error = Error; + fn try_from(mut sd_jwt: SdJwt) -> std::result::Result { + // Validate claims. + let claims = { + let claims = std::mem::take(sd_jwt.claims_mut()); + SdJwtVcClaims::try_from_sd_jwt_claims(claims, sd_jwt.disclosures())? + }; + + // Validate Header's typ. + let typ = sd_jwt + .header() + .get("typ") + .and_then(Value::as_str) + .ok_or_else(|| Error::InvalidJoseType("null".to_string()))?; + if !typ.contains(SD_JWT_VC_TYP) { + return Err(Error::InvalidJoseType(typ.to_string())); + } + + Ok(Self { + sd_jwt, + parsed_claims: claims, + }) + } +} + +impl FromStr for SdJwtVc { + type Err = Error; + fn from_str(s: &str) -> std::result::Result { + s.parse::().map_err(Error::SdJwt).and_then(TryInto::try_into) + } +} + +impl Display for SdJwtVc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.sd_jwt) + } +} + +impl From for SdJwt { + fn from(value: SdJwtVc) -> Self { + let SdJwtVc { + mut sd_jwt, + parsed_claims, + } = value; + // Put back `parsed_claims`. + *sd_jwt.claims_mut() = parsed_claims.into(); + + sd_jwt + } +} + +#[cfg(test)] +mod tests { + use std::sync::LazyLock; + + use identity_core::common::StringOrUrl; + use identity_core::common::Url; + + use super::*; + + const EXAMPLE_SD_JWT_VC: &str = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogInZjK3NkLWp3dCJ9.eyJfc2QiOiBbIjBIWm1uU0lQejMzN2tTV2U3QzM0bC0tODhnekppLWVCSjJWel9ISndBVGciLCAiOVpicGxDN1RkRVc3cWFsNkJCWmxNdHFKZG1lRU9pWGV2ZEpsb1hWSmRSUSIsICJJMDBmY0ZVb0RYQ3VjcDV5eTJ1anFQc3NEVkdhV05pVWxpTnpfYXdEMGdjIiwgIklFQllTSkdOaFhJbHJRbzU4eWtYbTJaeDN5bGw5WmxUdFRvUG8xN1FRaVkiLCAiTGFpNklVNmQ3R1FhZ1hSN0F2R1RyblhnU2xkM3o4RUlnX2Z2M2ZPWjFXZyIsICJodkRYaHdtR2NKUXNCQ0EyT3RqdUxBY3dBTXBEc2FVMG5rb3ZjS09xV05FIiwgImlrdXVyOFE0azhxM1ZjeUE3ZEMtbU5qWkJrUmVEVFUtQ0c0bmlURTdPVFUiLCAicXZ6TkxqMnZoOW80U0VYT2ZNaVlEdXZUeWtkc1dDTmcwd1RkbHIwQUVJTSIsICJ3elcxNWJoQ2t2a3N4VnZ1SjhSRjN4aThpNjRsbjFqb183NkJDMm9hMXVnIiwgInpPZUJYaHh2SVM0WnptUWNMbHhLdUVBT0dHQnlqT3FhMXoySW9WeF9ZRFEiXSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgInZjdCI6ICJodHRwczovL2JtaS5idW5kLmV4YW1wbGUvY3JlZGVudGlhbC9waWQvMS4wIiwgImFnZV9lcXVhbF9vcl9vdmVyIjogeyJfc2QiOiBbIkZjOElfMDdMT2NnUHdyREpLUXlJR085N3dWc09wbE1Makh2UkM0UjQtV2ciLCAiWEx0TGphZFVXYzl6Tl85aE1KUm9xeTQ2VXNDS2IxSXNoWnV1cVVGS1NDQSIsICJhb0NDenNDN3A0cWhaSUFoX2lkUkNTQ2E2NDF1eWNuYzh6UGZOV3o4bngwIiwgImYxLVAwQTJkS1dhdnYxdUZuTVgyQTctRVh4dmhveHY1YUhodUVJTi1XNjQiLCAiazVoeTJyMDE4dnJzSmpvLVZqZDZnNnl0N0Fhb25Lb25uaXVKOXplbDNqbyIsICJxcDdaX0t5MVlpcDBzWWdETzN6VnVnMk1GdVBOakh4a3NCRG5KWjRhSS1jIl19LCAiX3NkX2FsZyI6ICJzaGEtMjU2IiwgImNuZiI6IHsiandrIjogeyJrdHkiOiAiRUMiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiVENBRVIxOVp2dTNPSEY0ajRXNHZmU1ZvSElQMUlMaWxEbHM3dkNlR2VtYyIsICJ5IjogIlp4amlXV2JaTVFHSFZXS1ZRNGhiU0lpcnNWZnVlY0NFNnQ0alQ5RjJIWlEifX19.CaXec2NNooWAy4eTxYbGWI--UeUL0jpC7Zb84PP_09Z655BYcXUTvfj6GPk4mrNqZUU5GT6QntYR8J9rvcBjvA~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFIl1d~WyJNMEpiNTd0NDF1YnJrU3V5ckRUM3hBIiwgIjE4IiwgdHJ1ZV0~eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL2V4YW1wbGUuY29tL3ZlcmlmaWVyIiwgImlhdCI6IDE3MjA0NTQyOTUsICJzZF9oYXNoIjogIlZFejN0bEtqOVY0UzU3TTZoRWhvVjRIc19SdmpXZWgzVHN1OTFDbmxuZUkifQ.GqtiTKNe3O95GLpdxFK_2FZULFk6KUscFe7RPk8OeVLiJiHsGvtPyq89e_grBplvGmnDGHoy8JAt1wQqiwktSg"; + static EXAMPLE_ISSUER: LazyLock = LazyLock::new(|| "https://example.com/issuer".parse().unwrap()); + static EXAMPLE_VCT: LazyLock = LazyLock::new(|| { + "https://bmi.bund.example/credential/pid/1.0" + .parse::() + .unwrap() + .into() + }); + + #[test] + fn simple_sd_jwt_is_not_a_valid_sd_jwt_vc() { + let sd_jwt: SdJwt = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBbIkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3VldDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZGekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQTjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRNcFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFsU2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZdW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJUckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVzcyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBLbmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9IemciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIsICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQWUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hyU1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpDTkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0yNTYifQ.gR6rSL7urX79CNEvTQnP1MH5xthG11ucIV44SqKFZ4Pvlu_u16RfvXQd4k4CAIBZNKn2aTI18TfvFwV97gJFoA~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ~" + .parse().unwrap(); + let err = SdJwtVc::try_from(sd_jwt).unwrap_err(); + assert!(matches!(err, Error::MissingClaim("vct"))) + } + + #[test] + fn parsing_a_valid_sd_jwt_vc_works() { + let sd_jwt_vc: SdJwtVc = EXAMPLE_SD_JWT_VC.parse().unwrap(); + assert_eq!(sd_jwt_vc.claims().iss, *EXAMPLE_ISSUER); + assert_eq!(sd_jwt_vc.claims().vct, *EXAMPLE_VCT); + } +} diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index e1025a9fcd..049fca554d 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_did" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition = "2021" homepage.workspace = true @@ -13,8 +13,8 @@ description = "Agnostic implementation of the Decentralized Identifiers (DID) st [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } form_urlencoded = { version = "1.2.0", default-features = false, features = ["alloc"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_jose = { version = "=1.3.1", path = "../identity_jose" } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_jose = { version = "=1.5.0", path = "../identity_jose" } serde.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index f87fc86c33..7e8e8ee5f9 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_document" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -13,9 +13,9 @@ description = "Method-agnostic implementation of the Decentralized Identifiers ( [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did" } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.5.0", path = "../identity_did" } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } serde.workspace = true strum.workspace = true diff --git a/identity_ecdsa_verifier/Cargo.toml b/identity_ecdsa_verifier/Cargo.toml index 654b8aebe3..ea7436f072 100644 --- a/identity_ecdsa_verifier/Cargo.toml +++ b/identity_ecdsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_ecdsa_verifier" -version = "1.3.1" +version = "1.5.0" authors = ["IOTA Stiftung", "Filancore GmbH"] edition.workspace = true homepage.workspace = true @@ -15,7 +15,7 @@ description = "JWS ECDSA signature verification for IOTA Identity" workspace = true [dependencies] -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } k256 = { version = "0.13.3", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } signature = { version = "2", default-features = false } diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index 97308beebf..65d03354c0 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_eddsa_verifier" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,7 +12,7 @@ rust-version.workspace = true description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.5.0", path = "../identity_jose", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["std"] } [features] diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 413cbf1547..07bcd119cd 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,14 +12,14 @@ rust-version.workspace = true description = "Framework for Self-Sovereign Identity with IOTA DID." [dependencies] -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["validator"], default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false } -identity_resolver = { version = "=1.3.1", path = "../identity_resolver", default-features = false, optional = true } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["iota-document"] } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.5.0", path = "../identity_credential", features = ["validator"], default-features = false } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.5.0", path = "../identity_iota_core", default-features = false } +identity_resolver = { version = "=1.5.0", path = "../identity_resolver", default-features = false, optional = true } +identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } [dev-dependencies] anyhow = "1.0.64" diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 9ab2e53805..16fb29558f 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -40,6 +40,8 @@ pub mod credential { pub use identity_credential::presentation::*; #[cfg(feature = "revocation-bitmap")] pub use identity_credential::revocation::*; + #[cfg(feature = "sd-jwt-vc")] + pub use identity_credential::sd_jwt_vc; pub use identity_credential::validator::*; } @@ -128,3 +130,8 @@ pub mod sd_jwt_payload { //! Expose the selective disclosure crate. pub use identity_credential::sd_jwt_payload::*; } + +// Exposes the reworked version of the selective disclosure crate +// which is needed for selectively disclosable credentials. +#[cfg(feature = "sd-jwt-vc")] +pub use identity_credential::sd_jwt_v2 as sd_jwt_rework; diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index f44a3ca27c..35b9a8bdad 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota_core" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -14,11 +14,11 @@ description = "An IOTA Ledger integration for the IOTA DID Method." [dependencies] async-trait = { version = "0.1.56", default-features = false, optional = true } futures = { version = "0.3", default-features = false } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } iota-sdk = { version = "1.1.5", default-features = false, features = ["serde", "std"], optional = true } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["std"] } diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 14061f3059..80be00fc91 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_jose" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -13,7 +13,7 @@ description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true -identity_core = { version = "=1.3.1", path = "../identity_core" } +identity_core = { version = "1.5.0", path = "../identity_core" } iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha", "ed25519" ] } json-proof-token.workspace = true serde.workspace = true diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index d96e1be84d..460bb72fa4 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -68,7 +68,7 @@ impl FromStr for CompositeAlgId { "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), #[cfg(not(feature = "custom_alg"))] - _ => Err(crate::error::Error::JwsAlgorithmParsingError), + &_ => Err(crate::error::Error::JwsAlgorithmParsingError), } } } diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index 599e3bbf48..9fceaa64b6 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -226,6 +226,25 @@ impl JwsAlgorithm { Self::ES256K => "ES256K".to_string(), Self::NONE => "none".to_string(), Self::EdDSA => "EdDSA".to_string(), + Self::ML_DSA_44 => "ML-DSA-44".to_string(), + Self::ML_DSA_65 => "ML-DSA-65".to_string(), + Self::ML_DSA_87 => "ML-DSA-87".to_string(), + Self::SLH_DSA_SHA2_128s => "SLH-DSA-SHA2-128s".to_string(), + Self::SLH_DSA_SHAKE_128s => "SLH-DSA-SHAKE-128s".to_string(), + Self::SLH_DSA_SHA2_128f => "SLH-DSA-SHA2-128f".to_string(), + Self::SLH_DSA_SHAKE_128f => "SLH-DSA-SHAKE-128f".to_string(), + Self::SLH_DSA_SHA2_192s => "SLH-DSA-SHA2-192s".to_string(), + Self::SLH_DSA_SHAKE_192s => "SLH-DSA-SHAKE-192s".to_string(), + Self::SLH_DSA_SHA2_192f => "SLH-DSA-SHA2-192f".to_string(), + Self::SLH_DSA_SHAKE_192f => "SLH-DSA-SHAKE-192f".to_string(), + Self::SLH_DSA_SHA2_256s => "SLH-DSA-SHA2-256s".to_string(), + Self::SLH_DSA_SHAKE_256s => "SLH-DSA-SHAKE-256s".to_string(), + Self::SLH_DSA_SHA2_256f => "SLH-DSA-SHA2-256f".to_string(), + Self::SLH_DSA_SHAKE_256f => "SLH-DSA-SHAKE-256f".to_string(), + Self::FALCON512 => "FALCON512".to_string(), + Self::FALCON1024 => "FALCON1024".to_string(), + Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512".to_string(), + Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512".to_string(), Self::Custom(name) => name.clone(), } } diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 019394cfae..45709c8d84 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_pqc_verifier" -version = "0.1.0" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,7 +12,7 @@ rust-version.workspace = true description = "JWS PQC signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = true } +identity_jose = { version = "=1.5.0", path = "../identity_jose", default-features = true } oqs.workspace = true [features] diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index cf9cc8425f..d0bac33d47 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_resolver" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -15,10 +15,10 @@ description = "DID Resolution utilities for the identity.rs library." # This is currently necessary for the ResolutionHandler trait. This can be made an optional dependency if alternative ways of attaching handlers are introduced. async-trait = { version = "0.1", default-features = false } futures = { version = "0.3" } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } @@ -26,7 +26,7 @@ reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tl [dependencies.identity_iota_core] -version = "=1.3.1" +version = "=1.5.0" path = "../identity_iota_core" default-features = false features = ["send-sync-client-ext", "iota-client"] diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 02e3c1746f..347934d93b 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_storage" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -16,12 +16,12 @@ anyhow = "1.0.82" async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } futures = { version = "0.3.27", default-features = false, features = ["async-await"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false, optional = true } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default_features = false } +identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.5.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.5.0", path = "../identity_iota_core", default-features = false, optional = true } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default_features = false } iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha", "random"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } @@ -33,8 +33,8 @@ tokio = { version = "1.29.0", default-features = false, features = ["macros", "s zkryptium = { workspace = true, optional = true } oqs = { workspace = true, optional = true } [dev-dependencies] -identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } -identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } +identity_credential = { version = "=1.5.0", path = "../identity_credential", features = ["revocation-bitmap"] } +identity_eddsa_verifier = { version = "=1.5.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index d6b0825cba..dcf0139f3d 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_stronghold" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -14,8 +14,8 @@ description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519"] } iota-sdk = { version = "1.1.5", default-features = false, features = ["client", "stronghold"] } iota_stronghold = { version = "2.1.0", default-features = false } @@ -28,8 +28,8 @@ zkryptium = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.82" bls12_381_plus = { workspace = true } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } +identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } +identity_storage = { version = "=1.5.0", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } json-proof-token = { workspace = true } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } zkryptium = { workspace = true } diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 8990dd96e0..70e3c8483b 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_verification" -version = "1.3.1" +version = "1.5.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -10,9 +10,9 @@ rust-version.workspace = true description = "Verification data types and functionality for identity.rs" [dependencies] -identity_core = { version = "=1.3.1", path = "./../identity_core" } -identity_did = { version = "=1.3.1", path = "./../identity_did", default-features = false } -identity_jose = { version = "=1.3.1", path = "./../identity_jose", default-features = false } +identity_core = { version = "=1.5.0", path = "./../identity_core" } +identity_did = { version = "=1.5.0", path = "./../identity_did", default-features = false } +identity_jose = { version = "=1.5.0", path = "./../identity_jose", default-features = false } serde.workspace = true serde_json.workspace = true strum.workspace = true From bf3d64ac057f766d46c045afbd9024e40f3b6af6 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Tue, 1 Apr 2025 15:21:26 +0200 Subject: [PATCH 130/163] Remove unwanted changes --- .github/workflows/shared-release.yml | 3 + .github/workflows/upload-docs.yml | 3 + bindings/wasm/src/error.rs | 30 +++- identity_core/src/common/mod.rs | 2 + identity_core/src/common/string_or_url.rs | 151 ++++++++++++++++++ identity_core/src/common/url.rs | 8 +- identity_credential/Cargo.toml | 1 + .../src/credential/jwt_serialization.rs | 2 +- identity_credential/src/credential/mod.rs | 9 +- .../domain_linkage_validator.rs | 1 - .../src/presentation/jwt_serialization.rs | 2 +- .../src/revocation/status_list_2021/entry.rs | 24 ++- .../revocation_timeframe_status.rs | 2 +- identity_did/Cargo.toml | 2 +- identity_document/Cargo.toml | 1 - .../benches/deserialize_document.rs | 4 +- .../src/document/core_document.rs | 4 +- identity_document/src/utils/did_url_query.rs | 4 +- identity_ecdsa_verifier/Cargo.toml | 1 - identity_eddsa_verifier/Cargo.toml | 1 - identity_iota/Cargo.toml | 7 +- 21 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 identity_core/src/common/string_or_url.rs diff --git a/.github/workflows/shared-release.yml b/.github/workflows/shared-release.yml index 86a4d1ab94..a4b4403bf0 100644 --- a/.github/workflows/shared-release.yml +++ b/.github/workflows/shared-release.yml @@ -155,6 +155,9 @@ jobs: if: ${{env.IS_RELEASE && inputs.create-github-release}} uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 with: + # Token expires Jan 16, 2026 + # This is needed because of https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#triggering-a-workflow-from-a-workflow + token: ${{ secrets.GH_RELEASE_PUBLISH_PAT }} body_path: RELEASE_CHANGELOG.md prerelease: ${{env.IS_PRE_RELEASE}} tag_name: ${{env.CURRENT_VERSION}} diff --git a/.github/workflows/upload-docs.yml b/.github/workflows/upload-docs.yml index 5ce1429767..7c98b7cb7e 100644 --- a/.github/workflows/upload-docs.yml +++ b/.github/workflows/upload-docs.yml @@ -9,6 +9,9 @@ on: description: 'Version to publish docs under (e.g. `v1.2.3-dev.1`)' required: true +env: + GH_TOKEN: ${{ github.token }} + permissions: actions: 'write' diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs index 035e7838bf..db2bb49d38 100644 --- a/bindings/wasm/src/error.rs +++ b/bindings/wasm/src/error.rs @@ -108,7 +108,8 @@ impl_wasm_error_from!( identity_iota::sd_jwt_payload::Error, identity_iota::credential::KeyBindingJwtError, identity_iota::credential::status_list_2021::StatusListError, - identity_iota::credential::status_list_2021::StatusList2021CredentialError + identity_iota::credential::status_list_2021::StatusList2021CredentialError, + identity_iota::sd_jwt_rework::Error ); // Similar to `impl_wasm_error_from`, but uses the types name instead of requiring/calling Into &'static str @@ -175,6 +176,15 @@ impl From for WasmError<'_> { } } +impl From for WasmError<'_> { + fn from(value: anyhow::Error) -> Self { + Self { + name: Cow::Borrowed("Generic Error"), + message: Cow::Owned(value.to_string()), + } + } +} + impl From for WasmError<'_> { fn from(error: identity_iota::iota::block::Error) -> Self { Self { @@ -184,6 +194,15 @@ impl From for WasmError<'_> { } } +impl From for WasmError<'_> { + fn from(value: serde_wasm_bindgen::Error) -> Self { + Self { + name: Cow::Borrowed("JSConversionError"), + message: Cow::Owned(value.to_string()), + } + } +} + impl From for WasmError<'_> { fn from(error: identity_iota::credential::CompoundCredentialValidationError) -> Self { Self { @@ -265,6 +284,15 @@ impl From for WasmError<'_> { } } +impl From for WasmError<'_> { + fn from(error: identity_iota::credential::sd_jwt_vc::Error) -> Self { + Self { + name: Cow::Borrowed("SdJwtVcError"), + message: Cow::Owned(ErrorMessage(&error).to_string()), + } + } +} + /// Convenience struct to convert Result to errors in the Rust library. pub struct JsValueResult(pub(crate) Result); diff --git a/identity_core/src/common/mod.rs b/identity_core/src/common/mod.rs index 8d6be52251..04568e05b5 100644 --- a/identity_core/src/common/mod.rs +++ b/identity_core/src/common/mod.rs @@ -14,6 +14,7 @@ pub use self::single_struct_error::*; pub use self::timestamp::Duration; pub use self::timestamp::Timestamp; pub use self::url::Url; +pub use string_or_url::StringOrUrl; mod context; mod key_comparable; @@ -22,5 +23,6 @@ mod one_or_many; mod one_or_set; mod ordered_set; mod single_struct_error; +mod string_or_url; mod timestamp; mod url; diff --git a/identity_core/src/common/string_or_url.rs b/identity_core/src/common/string_or_url.rs new file mode 100644 index 0000000000..11e4e5dff2 --- /dev/null +++ b/identity_core/src/common/string_or_url.rs @@ -0,0 +1,151 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::convert::Infallible; +use std::fmt::Display; +use std::str::FromStr; + +use serde::Deserialize; +use serde::Serialize; + +use super::Url; + +/// A type that represents either an arbitrary string or a URL. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(untagged)] +pub enum StringOrUrl { + /// A well-formed URL. + Url(Url), + /// An arbitrary UTF-8 string. + String(String), +} + +impl StringOrUrl { + /// Parses a [`StringOrUrl`] from a string. + pub fn parse(s: &str) -> Result { + s.parse() + } + /// Returns a [`Url`] reference if `self` is [`StringOrUrl::Url`]. + pub fn as_url(&self) -> Option<&Url> { + match self { + Self::Url(url) => Some(url), + _ => None, + } + } + + /// Returns a [`str`] reference if `self` is [`StringOrUrl::String`]. + pub fn as_string(&self) -> Option<&str> { + match self { + Self::String(s) => Some(s), + _ => None, + } + } + + /// Returns whether `self` is a [`StringOrUrl::Url`]. + pub fn is_url(&self) -> bool { + matches!(self, Self::Url(_)) + } + + /// Returns whether `self` is a [`StringOrUrl::String`]. + pub fn is_string(&self) -> bool { + matches!(self, Self::String(_)) + } +} + +impl Default for StringOrUrl { + fn default() -> Self { + StringOrUrl::String(String::default()) + } +} + +impl Display for StringOrUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(url) => write!(f, "{url}"), + Self::String(s) => write!(f, "{s}"), + } + } +} + +impl FromStr for StringOrUrl { + // Cannot fail. + type Err = Infallible; + fn from_str(s: &str) -> Result { + Ok( + s.parse::() + .map(Self::Url) + .unwrap_or_else(|_| Self::String(s.to_string())), + ) + } +} + +impl AsRef for StringOrUrl { + fn as_ref(&self) -> &str { + match self { + Self::String(s) => s, + Self::Url(url) => url.as_str(), + } + } +} + +impl From for StringOrUrl { + fn from(value: Url) -> Self { + Self::Url(value) + } +} + +impl From for StringOrUrl { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From for String { + fn from(value: StringOrUrl) -> Self { + match value { + StringOrUrl::String(s) => s, + StringOrUrl::Url(url) => url.into_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, Serialize, Deserialize)] + struct TestData { + string_or_url: StringOrUrl, + } + + impl Default for TestData { + fn default() -> Self { + Self { + string_or_url: StringOrUrl::Url(TEST_URL.parse().unwrap()), + } + } + } + + const TEST_URL: &str = "file:///tmp/file.txt"; + + #[test] + fn deserialization_works() { + let test_data: TestData = serde_json::from_value(serde_json::json!({ "string_or_url": TEST_URL })).unwrap(); + let target_url: Url = TEST_URL.parse().unwrap(); + assert_eq!(test_data.string_or_url.as_url(), Some(&target_url)); + } + + #[test] + fn serialization_works() { + assert_eq!( + serde_json::to_value(TestData::default()).unwrap(), + serde_json::json!({ "string_or_url": TEST_URL }) + ) + } + + #[test] + fn parsing_works() { + assert!(TEST_URL.parse::().unwrap().is_url()); + assert!("I'm a random string :)".parse::().unwrap().is_string()); + } +} diff --git a/identity_core/src/common/url.rs b/identity_core/src/common/url.rs index 6736a4cac9..57937c80e7 100644 --- a/identity_core/src/common/url.rs +++ b/identity_core/src/common/url.rs @@ -17,7 +17,7 @@ use crate::error::Error; use crate::error::Result; /// A parsed URL. -#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[derive(Clone, Hash, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] pub struct Url(::url::Url); @@ -96,3 +96,9 @@ impl KeyComparable for Url { self } } + +impl AsRef for Url { + fn as_ref(&self) -> &str { + self.as_str() + } +} diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 475d33f500..9e7b27deee 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -43,6 +43,7 @@ zkryptium = { workspace = true, optional = true } anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "random"] } +josekit = "0.8" proptest = { version = "1.4.0", default-features = false, features = ["std"] } tokio = { version = "1.35.0", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 3f2a33f0a7..e7307ff5c7 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -67,7 +67,7 @@ impl<'credential, T> CredentialJwtClaims<'credential, T> where T: ToOwned + Serialize + DeserializeOwned, { - pub(super) fn new(credential: &'credential Credential, custom: Option) -> Result { + pub(crate) fn new(credential: &'credential Credential, custom: Option) -> Result { let Credential { context, id, diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 27dabfced6..07b15f4eba 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -37,10 +37,6 @@ pub use self::jpt::Jpt; pub use self::jwp_credential_options::JwpCredentialOptions; pub use self::jws::Jws; pub use self::jwt::Jwt; -#[cfg(feature = "validator")] -pub(crate) use self::jwt_serialization::CredentialJwtClaims; -#[cfg(feature = "presentation")] -pub(crate) use self::jwt_serialization::IssuanceDateClaims; pub use self::linked_domain_service::LinkedDomainService; pub use self::linked_verifiable_presentation_service::LinkedVerifiablePresentationService; pub use self::policy::Policy; @@ -53,3 +49,8 @@ pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; pub use self::status::Status; pub use self::subject::Subject; + +#[cfg(feature = "validator")] +pub(crate) use self::jwt_serialization::CredentialJwtClaims; +#[cfg(feature = "presentation")] +pub(crate) use self::jwt_serialization::IssuanceDateClaims; diff --git a/identity_credential/src/domain_linkage/domain_linkage_validator.rs b/identity_credential/src/domain_linkage/domain_linkage_validator.rs index be67c96832..746a9b2f5a 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_validator.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_validator.rs @@ -21,7 +21,6 @@ use super::DomainLinkageValidationResult; use crate::utils::url_only_includes_origin; /// A validator for a Domain Linkage Configuration and Credentials. - pub struct JwtDomainLinkageValidator { validator: JwtCredentialValidator, } diff --git a/identity_credential/src/presentation/jwt_serialization.rs b/identity_credential/src/presentation/jwt_serialization.rs index d8bb18c238..50aab3d428 100644 --- a/identity_credential/src/presentation/jwt_serialization.rs +++ b/identity_credential/src/presentation/jwt_serialization.rs @@ -136,7 +136,7 @@ where } #[cfg(feature = "validator")] -impl<'presentation, CRED, T> PresentationJwtClaims<'presentation, CRED, T> +impl PresentationJwtClaims<'_, CRED, T> where CRED: ToOwned + Serialize + DeserializeOwned + Clone, T: ToOwned + Serialize + DeserializeOwned, diff --git a/identity_credential/src/revocation/status_list_2021/entry.rs b/identity_credential/src/revocation/status_list_2021/entry.rs index 7eecf2f28e..1108b5e7c1 100644 --- a/identity_credential/src/revocation/status_list_2021/entry.rs +++ b/identity_credential/src/revocation/status_list_2021/entry.rs @@ -18,7 +18,7 @@ where D: serde::Deserializer<'de>, { struct ExactStrVisitor(&'static str); - impl<'a> Visitor<'a> for ExactStrVisitor { + impl Visitor<'_> for ExactStrVisitor { type Value = &'static str; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "the exact string \"{}\"", self.0) @@ -37,6 +37,14 @@ where .map(ToOwned::to_owned) } +/// Serialize usize as string. +fn serialize_number_as_string(value: &usize, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&value.to_string()) +} + /// [StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] #[serde(rename_all = "camelCase")] @@ -45,7 +53,10 @@ pub struct StatusList2021Entry { #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")] type_: String, status_purpose: StatusPurpose, - #[serde(deserialize_with = "serde_aux::prelude::deserialize_number_from_string")] + #[serde( + deserialize_with = "serde_aux::prelude::deserialize_number_from_string", + serialize_with = "serialize_number_as_string" + )] status_list_index: usize, status_list_credential: Url, } @@ -142,4 +153,13 @@ mod tests { }); serde_json::from_value::(status).expect("wrong type"); } + + #[test] + fn test_status_list_index_serialization() { + let base_url = Url::parse("https://example.com/credentials/status/3").unwrap(); + + let entry1 = StatusList2021Entry::new(base_url.clone(), StatusPurpose::Revocation, 94567, None); + let json1 = serde_json::to_value(&entry1).unwrap(); + assert_eq!(json1["statusListIndex"], "94567"); + } } diff --git a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs index 0a70589112..6ae6ea74f8 100644 --- a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs +++ b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs @@ -18,7 +18,7 @@ where D: serde::Deserializer<'de>, { struct ExactStrVisitor(&'static str); - impl<'a> Visitor<'a> for ExactStrVisitor { + impl Visitor<'_> for ExactStrVisitor { type Value = &'static str; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "the exact string \"{}\"", self.0) diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index 049fca554d..ee3b0f13ea 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true description = "Agnostic implementation of the Decentralized Identifiers (DID) standard." [dependencies] -did_url_parser = { version = "0.2.0", features = ["std", "serde"] } +did_url_parser = { version = "0.3.0", features = ["std", "serde"] } form_urlencoded = { version = "1.2.0", default-features = false, features = ["alloc"] } identity_core = { version = "=1.5.0", path = "../identity_core", default-features = false } identity_jose = { version = "=1.5.0", path = "../identity_jose" } diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index 7e8e8ee5f9..44bef94c6d 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "identity", "did"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "Method-agnostic implementation of the Decentralized Identifiers (DID) standard." [dependencies] diff --git a/identity_document/benches/deserialize_document.rs b/identity_document/benches/deserialize_document.rs index 1bd26ce870..0b5203b83c 100644 --- a/identity_document/benches/deserialize_document.rs +++ b/identity_document/benches/deserialize_document.rs @@ -220,9 +220,9 @@ fn deserialize_json_document(c: &mut Criterion) { (JSON_DOC_DID_KEY, "did:key document"), (JSON_DOCUMENT_LARGE, "large document"), ] { - group.throughput(Throughput::Bytes(json.as_bytes().len() as u64)); + group.throughput(Throughput::Bytes(json.len() as u64)); group.bench_with_input( - BenchmarkId::from_parameter(format!("{name}, document size: {} bytes", json.as_bytes().len())), + BenchmarkId::from_parameter(format!("{name}, document size: {} bytes", json.len())), json, |b, json| { b.iter(|| { diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 34c53b9044..0c0a41a6c7 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -696,7 +696,7 @@ impl CoreDocument { &'me self, method_query: Q, scope: Option, - ) -> Option<&VerificationMethod> + ) -> Option<&'me VerificationMethod> where Q: Into>, { @@ -779,7 +779,7 @@ impl CoreDocument { /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. // NOTE: This method demonstrates unexpected behavior in the edge cases where the document contains // services whose ids are of the form #. - pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> + pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&'me Service> where Q: Into>, { diff --git a/identity_document/src/utils/did_url_query.rs b/identity_document/src/utils/did_url_query.rs index 1af2b80b4c..d9399457e3 100644 --- a/identity_document/src/utils/did_url_query.rs +++ b/identity_document/src/utils/did_url_query.rs @@ -13,7 +13,7 @@ use identity_did::DID; #[repr(transparent)] pub struct DIDUrlQuery<'query>(Cow<'query, str>); -impl<'query> DIDUrlQuery<'query> { +impl DIDUrlQuery<'_> { /// Returns whether this query matches the given DIDUrl. pub(crate) fn matches(&self, did_url: &DIDUrl) -> bool { // Ensure the DID matches if included in the query. @@ -81,7 +81,7 @@ impl<'query> From<&'query DIDUrl> for DIDUrlQuery<'query> { } } -impl<'query> From for DIDUrlQuery<'query> { +impl From for DIDUrlQuery<'_> { fn from(other: DIDUrl) -> Self { Self(Cow::Owned(other.to_string())) } diff --git a/identity_ecdsa_verifier/Cargo.toml b/identity_ecdsa_verifier/Cargo.toml index ea7436f072..d9d7fca558 100644 --- a/identity_ecdsa_verifier/Cargo.toml +++ b/identity_ecdsa_verifier/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "JWS ECDSA signature verification for IOTA Identity" [lints] diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index 65d03354c0..8e4e7ee317 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 07bcd119cd..96841feb01 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "identity", "did", "ssi"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "Framework for Self-Sovereign Identity with IOTA DID." [dependencies] @@ -34,13 +33,12 @@ default = ["revocation-bitmap", "client", "iota-client", "resolver"] client = ["identity_iota_core/client"] # Enables the iota-client integration, the client trait implementations for it, and the `IotaClientExt` trait. -iota-client = ["identity_iota_core/iota-client", "identity_resolver?/iota"] +iota-client = ["identity_iota_core/iota-client", "identity_resolver/iota"] # Enables revocation with `RevocationBitmap2022`. revocation-bitmap = [ "identity_credential/revocation-bitmap", "identity_iota_core/revocation-bitmap", - "identity_resolver?/revocation-bitmap", ] # Enables revocation with `StatusList2021`. @@ -64,6 +62,9 @@ memstore = ["identity_storage/memstore"] # Enables selective disclosure features. sd-jwt = ["identity_credential/sd-jwt"] +# Enables selectively disclosable credentials. +sd-jwt-vc = ["identity_credential/sd-jwt-vc"] + # Enables zero knowledge selective disclosurable VCs jpt-bbs-plus = ["identity_storage/jpt-bbs-plus", "identity_credential/jpt-bbs-plus"] From 15d5527c7b156001aa449cc4c53f180b2d525f76 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 2 Apr 2025 11:28:19 +0200 Subject: [PATCH 131/163] Remove unwanted changes --- Cargo.toml | 1 - examples/1_advanced/14_did_web.rs | 220 ---------- examples/2_pqc/0_pq.rs | 177 -------- examples/2_pqc/1_hybrid.rs | 178 -------- examples/2_pqc/revocation_zk.rs | 409 ------------------ examples/2_pqc/server/.gitignore | 9 - examples/2_pqc/server/.well-known/.gitkeep | 0 examples/2_pqc/server/Cargo.toml | 12 - examples/2_pqc/server/src/main.rs | 113 ----- examples/2_pqc/traditional.rs | 180 -------- examples/2_pqc/traditional_zk.rs | 160 ------- examples/Cargo.toml | 2 +- identity_core/Cargo.toml | 1 - identity_credential/Cargo.toml | 1 - identity_did/src/did_web.rs | 227 ---------- identity_did/src/lib.rs | 2 - .../src/document/core_document.rs | 14 - identity_iota_core/Cargo.toml | 1 - identity_jose/Cargo.toml | 5 +- identity_pqc_verifier/Cargo.toml | 2 +- identity_resolver/Cargo.toml | 5 +- identity_resolver/src/error.rs | 3 - identity_resolver/src/resolution/resolver.rs | 49 --- identity_storage/Cargo.toml | 5 +- identity_stronghold/Cargo.toml | 1 - identity_verification/Cargo.toml | 1 - 26 files changed, 7 insertions(+), 1771 deletions(-) delete mode 100644 examples/1_advanced/14_did_web.rs delete mode 100644 examples/2_pqc/0_pq.rs delete mode 100644 examples/2_pqc/1_hybrid.rs delete mode 100644 examples/2_pqc/revocation_zk.rs delete mode 100644 examples/2_pqc/server/.gitignore delete mode 100644 examples/2_pqc/server/.well-known/.gitkeep delete mode 100644 examples/2_pqc/server/Cargo.toml delete mode 100644 examples/2_pqc/server/src/main.rs delete mode 100644 examples/2_pqc/traditional.rs delete mode 100644 examples/2_pqc/traditional_zk.rs delete mode 100644 identity_did/src/did_web.rs diff --git a/Cargo.toml b/Cargo.toml index 1c12798acf..efb1fe687b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ edition = "2021" homepage = "https://www.iota.org" license = "Apache-2.0" repository = "https://github.com/iotaledger/identity.rs" -rust-version = "1.65" [workspace.lints.clippy] result_large_err = "allow" diff --git a/examples/1_advanced/14_did_web.rs b/examples/1_advanced/14_did_web.rs deleted file mode 100644 index 25eed51cdc..0000000000 --- a/examples/1_advanced/14_did_web.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashMap, fs::File, path::Path}; - -use examples::{create_did, random_stronghold_path, MemStorage, API_ENDPOINT}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, iota::IotaDocument, resolver::Resolver, storage::{JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; -use identity_iota::storage::JwkDocumentExt; -use iota_sdk::{client::{secret::{stronghold::StrongholdSecretManager, SecretManager}, Client, Password}, types::block::address::Address}; -use reqwest::ClientBuilder; -use serde_json::json; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - - let did_url: &str = "https://localhost:4443/.well-known/did.json"; - let path_did_file: &str = "C:/Projects/did-web-server/.well-known/did.json"; - - // Create a new client to make HTTPS requests. - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - // Create a new Web DID document. - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - // Insert a new Ed25519 verification method in the DID document. - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method( - &storage_issuer, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ) - .await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - println!("Web DID Document: {:#}", issuer_document); - - // Create a new client to interact with the IOTA ledger. - let iota_client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - // Create an identity for the holder, in this case also the subject. - let mut secret_manager_alice: SecretManager = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let (_, alice_document, fragment_alice): (Address, IotaDocument, String) = - create_did(&iota_client, &mut secret_manager_alice, &storage_alice).await?; - - - // =========================================================================== - // Step 2: Issuer creates and signs a Verifiable Credential. - // =========================================================================== - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, - // that the issuance date is not in the future and that the expiration date is not in the past: - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("VC successfully validated"); - - // =========================================================================== - // Step 3: Issuer sends the Verifiable Credential to the holder. - // =========================================================================== - println!("Sending credential (as JWT) to the holder: {credential:#}"); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== - - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - // The verifier and holder also agree that the signature should have an expiry date - // 10 minutes from now. - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - // =========================================================================== - // Step 5: Holder creates and signs a verifiable presentation from the issued credential. - // =========================================================================== - - // Create an unsigned Presentation from the previously issued Verifiable Credential. - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - // Create a JWT verifiable presentation using the holder's verification method - // and include the requested challenge and expiry timestamp. - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. - // =========================================================================== - println!("Sending presentation (as JWT) to the verifier: {presentation:#}"); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(iota_client); - - - // Resolve the holder's document. - let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; - let holder: IotaDocument = resolver.resolve(&holder_did).await?; - - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - EdDSAJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - - Ok(()) -} \ No newline at end of file diff --git a/examples/2_pqc/0_pq.rs b/examples/2_pqc/0_pq.rs deleted file mode 100644 index b2ccb4f60e..0000000000 --- a/examples/2_pqc/0_pq.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashMap, fs::File, path::Path}; -use examples::{MemStorage, DID_URL, PATH_DID_FILE}; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwsDocumentExtPQC, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; -use identity_pqc_verifier::PQCJwsVerifier; -use reqwest::ClientBuilder; -use serde_json::json; -use colored::Colorize; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let binding = DID_URL.to_owned() + "did_pqc.json"; - let did_url: &str = binding.as_str(); - let binding = PATH_DID_FILE.to_owned() + "did_pqc.json"; - let path_did_file: &str = binding.as_str(); - - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method_pqc( - &storage_issuer, - JwkMemStore::ML_DSA_KEY_TYPE, - JwsAlgorithm::ML_DSA_44, - None, - MethodScope::VerificationMethod, - ).await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk_pqc( - &storage_alice, - JwkMemStore::ML_DSA_KEY_TYPE, - JwsAlgorithm::ML_DSA_44 - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - - println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document.create_credential_jwt_pqc( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ).await?; - - println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); - - println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); - - println!("{} {}", "[Holder]".blue(), ": Verify VC"); - - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - let presentation: Presentation =PresentationBuilder::new( - alice_document.id().to_url().into(), - Default::default() - ).credential(credential_jwt).build()?; - - let presentation_jwt: Jwt = alice_document.create_presentation_jwt_pqc( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - - println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_jwk_handler(); - - let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - PQCJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - println!("{} : VP successfully verified, access granted", "[Verifier]".green()); - - Ok(()) -} diff --git a/examples/2_pqc/1_hybrid.rs b/examples/2_pqc/1_hybrid.rs deleted file mode 100644 index 6cd6975777..0000000000 --- a/examples/2_pqc/1_hybrid.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashMap, fs::File, path::Path}; -use examples::{MemStorage, DID_URL, PATH_DID_FILE}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidatorHybrid, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidatorHybrid, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDCompositeJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExtHybrid, JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jwk::CompositeAlgId, MethodScope}}; -use identity_pqc_verifier::PQCJwsVerifier; -use reqwest::ClientBuilder; -use serde_json::json; -use colored::Colorize; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - - let binding = DID_URL.to_owned() + "did_hybrid.json"; - let did_url: &str = binding.as_str(); - let binding = PATH_DID_FILE.to_owned() + "did_hybrid.json"; - let path_did_file: &str = binding.as_str(); - - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document - .generate_method_hybrid( - &storage_issuer, - CompositeAlgId::IdMldsa44Ed25519Sha512, - None, - MethodScope::VerificationMethod, - ).await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (alice_document, fragment_alice) = CoreDocument::new_did_compositejwk( - &storage_alice, - CompositeAlgId::IdMldsa44Ed25519Sha512 - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Create DID compositeJwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - - println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {}", "[Holder]".blue(), "<-> [Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt_hybrid( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ).await?; - - println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); - - println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); - - println!("{} {}", "[Holder]".blue(), ": Verify VC"); - - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - let presentation: Presentation =PresentationBuilder::new( - alice_document.id().to_url().into(), - Default::default() - ).credential(credential_jwt).build()?; - - let presentation_jwt: Jwt = alice_document.create_presentation_jwt_hybrid( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - - println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_compositejwk_handler(); - - let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidatorHybrid::with_signature_verifiers( - EdDSAJwsVerifier::default(), - PQCJwsVerifier::default(), - ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - let credential_validator: JwtCredentialValidatorHybrid = - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - println!("{} : VP successfully verified, access granted", "[Verifier]".green()); - - Ok(()) -} diff --git a/examples/2_pqc/revocation_zk.rs b/examples/2_pqc/revocation_zk.rs deleted file mode 100644 index 7efc6a6292..0000000000 --- a/examples/2_pqc/revocation_zk.rs +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{fs::File, path::Path}; -use examples::{MemStorage, DID_URL, PATH_DID_FILE}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtPresentation, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptCredentialValidatorUtils, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, Jwt, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, RevocationBitmap, RevocationDocumentExt, RevocationTimeframeStatus, SelectiveDisclosurePresentation, Status, StatusCheck, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDUrl, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument, Service}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkDocumentExt, JwkMemStore, JwpDocumentExt, JwsSignatureOptions, KeyIdMemstore, TimeframeRevocationExtension}, verification::{jws::JwsAlgorithm, MethodScope}}; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use reqwest::ClientBuilder; -use serde_json::json; -use colored::Colorize; -use std::time::Duration as SleepDuration; -use std::thread; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let binding = DID_URL.to_owned() + "did_zk.json"; - let did_url: &str = binding.as_str(); - let binding = PATH_DID_FILE.to_owned() + "did_zk.json"; - let path_did_file: &str = binding.as_str(); - - println!("{} {} {}", "[Issuer]".red(), ": Create DID with the Revocationbitmap (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document.generate_method_jwp( - &storage_issuer, - JwkMemStore::BLS12381G2_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, - None, - MethodScope::VerificationMethod, - ).await?; - - let revocation_bitmap_issuer: RevocationBitmap = RevocationBitmap::new(); - - let service_id: DIDUrl = issuer_document.id().to_url().join("#my-revocation-service")?; - let service: Service = revocation_bitmap_issuer.to_service(service_id)?; - - issuer_document.insert_service(service).unwrap(); - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (mut alice_document, fragment_alice) = CoreDocument::new_did_jwk( - &storage_alice, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA - ).await?; - - let revocation_bitmap_holder: RevocationBitmap = RevocationBitmap::new(); - - let service_id: DIDUrl = alice_document.id().to_url().join("#my-revocation-service")?; - let service: Service = revocation_bitmap_holder.to_service(service_id)?; - - alice_document.insert_service(service).unwrap(); - - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - - println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - println!("{} {} ","[Issuer]".red(), ": Create a new timeframe of 30 seconds"); - - let duration = Duration::seconds(30); - - let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; - let credential_index: u32 = 5; - - let start_validity_timeframe = Timestamp::now_utc(); - let status: Status = RevocationTimeframeStatus::new( - Some(start_validity_timeframe), - duration, - service_url.into(), - credential_index, - )? - .into(); - - println!("{} {} ","[Issuer]".red(), ": Generate VC with timeframe"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .status(status) - .build()?; - - let credential_jpt: Jpt = issuer_document.create_credential_jpt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwpCredentialOptions::default(), - None, - ).await?; - - println!("{} {} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - let decoded_jpt = JptCredentialValidator::validate::<_, Object>( - &credential_jpt, - &issuer_document, - &JptCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Validate Timeframe and revocation"); - - let _revocation_result = JptCredentialValidatorUtils::check_timeframes_and_revocation_with_validity_timeframe_2024( - &decoded_jpt.credential, - &issuer_document, - None, - StatusCheck::Strict, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access with Selective Disclosure of VC attributes"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); - - println!("{} : Resolve Issuer's Public Key to compute the Signature Proof of Knowledge", "[Holder]".blue()); - - let method_id = decoded_jpt - .decoded_jwp - .get_issuer_protected_header() - .kid() - .unwrap(); - - println!("{} : Engages in the Selective Disclosure of credential's attributes GPA and name", "[Holder]".blue()); - - let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); - selective_disclosure_presentation - .conceal_in_subject("GPA") - .unwrap(); - - selective_disclosure_presentation.conceal_in_subject("name").unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded)"); - - let presentation_jpt: Jpt = issuer_document - .create_presentation_jpt( - &mut selective_disclosure_presentation, - method_id, - &JwpPresentationOptions::default().nonce(challenge), - ) - .await?; - - println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); - - println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); - let resolved_issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; - - let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); - - let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( - &presentation_jpt, - &resolved_issuer_document, - &presentation_validation_options, - FailFast::FirstError, - ).unwrap(); - - println!("{} : Verify the validity of timeframe","[Verifier]".green()); - - let _timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( - &decoded_presented_credential.credential, - None, - StatusCheck::Strict, - ).unwrap(); - - println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); - - println!(""); - - println!("Waiting for the next validityTimeframe"); - - println!(""); - - thread::sleep(SleepDuration::from_secs(31)); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access after Timeframe expiration"); - - let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( - &decoded_presented_credential.credential, - None, - StatusCheck::Strict, - ); - - println!("{} {} {}", "[Verifier]".green(), ": Verify the validity of timeframe :", timeframe_result.unwrap_err()); - - println!("{} : Access denied", "[Verifier]".green()); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request update Timeframe"); - - println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending a challenge"); - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jpt) - .build()?; - - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ).await?; - - println!("{} {} ","[Holder]".blue(), ": Generate a Verifiable Presentation (VP) from the expired VC including the challenge and a new expiry timestamp"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Sending VP (as JWT)"); - - println!("{} {} ","[Issuer]".red(), ": Resolve Holder's DID and verify the VP"); - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let mut resolver_jwk: Resolver = Resolver::new(); - let _ = resolver_jwk.attach_did_jwk_handler(); - - let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; - let holder:CoreDocument = resolver_jwk.resolve(&holder_did).await?; - - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - EdDSAJwsVerifier::default(), - ).validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - println!("{} {} ","[Issuer]".red(), ": Verify the VC inside the VP"); - - let validation_options: JptCredentialValidationOptions = JptCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - let jpt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let jpt_vc = jpt_credentials.first().unwrap(); - - let mut verified_credential_result = - JptCredentialValidator::validate::<_, Object>(jpt_vc, &issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - - let _revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( - &verified_credential_result.credential, - &issuer_document, - StatusCheck::Strict, - ).unwrap(); - - println!("{} {} ","[Issuer]".red(), ": VP successfully validated"); - - println!("{} {} ","[Issuer]".red(), ": Update credential with new Timeframe"); - - let new_credential_jpt = issuer_document - .update( - &storage_issuer, - &fragment_issuer, - None, - duration, - &mut verified_credential_result.decoded_jwp, - ).await?; - - println!("{} {} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending updated credential (as JPT)", new_credential_jpt.as_str()); - - println!("{} {}", "[Holder]".blue(), ": Validate updated VC"); - - let decoded_jpt = JptCredentialValidator::validate::<_, Object>( - &new_credential_jpt, - &issuer_document, - &JptCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - let _ = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( - &decoded_jpt.credential, - None, - StatusCheck::Strict, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); - - let challenge: &str = "7788554-2598-ff55-ef52-822888d464dd"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); - - println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded) from the updated VC"); - - let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); - selective_disclosure_presentation - .conceal_in_subject("GPA") - .unwrap(); - - selective_disclosure_presentation.conceal_in_subject("name").unwrap(); - - let method_id = decoded_jpt - .decoded_jwp - .get_issuer_protected_header() - .kid() - .unwrap(); - - let updated_presentation_jpt: Jpt = issuer_document - .create_presentation_jpt( - &mut selective_disclosure_presentation, - method_id, - &JwpPresentationOptions::default().nonce(challenge), - ).await?; - - println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", updated_presentation_jpt.as_str()); - - println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); - - let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); - - let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( - &updated_presentation_jpt, - &resolved_issuer_document, - &presentation_validation_options, - FailFast::FirstError, - ).unwrap(); - - println!("{} : Verify the validity of timeframe","[Verifier]".green()); - - let _timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( - &decoded_presented_credential.credential, - None, - StatusCheck::Strict, - ).unwrap(); - - println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); - - println!("{} {} ","[Issuer]".red(), ": Decide to revoke the Holder's Credential"); - - println!("{} {} {}", "[Issuer]".red(), ": Update the Bitmap and publish the updated DID Document (with did:web method) at", did_url); - - issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - println!("{} {}", "[Holder]".blue(), ": Check the revocations status of his credentias"); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&updated_presentation_jpt).unwrap(); - let resolved_issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; - - let decoded_jpt = JptCredentialValidator::validate::<_, Object>( - &new_credential_jpt, - &resolved_issuer_document, - &JptCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( - &decoded_jpt.credential, - &resolved_issuer_document, - StatusCheck::Strict, - ); - - println!("{} {} {}", "[Holder]".blue(), ": Revocation status:", revocation_result.unwrap_err()); - - Ok(()) -} diff --git a/examples/2_pqc/server/.gitignore b/examples/2_pqc/server/.gitignore deleted file mode 100644 index 061045c066..0000000000 --- a/examples/2_pqc/server/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/target/ -target/ - -Cargo.lock - -.well-known/* -/.well-known/* - -!.well-known/.gitkeep \ No newline at end of file diff --git a/examples/2_pqc/server/.well-known/.gitkeep b/examples/2_pqc/server/.well-known/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/2_pqc/server/Cargo.toml b/examples/2_pqc/server/Cargo.toml deleted file mode 100644 index 740859d90f..0000000000 --- a/examples/2_pqc/server/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[workspace] - -[package] -name = "server" -version = "0.1.0" -edition = "2021" - -[dependencies] -tokio = { version = "1", features = ["full"] } -warp = {version = "0.3.7", features = ["tls"]} - - diff --git a/examples/2_pqc/server/src/main.rs b/examples/2_pqc/server/src/main.rs deleted file mode 100644 index 8c333538f8..0000000000 --- a/examples/2_pqc/server/src/main.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use warp::Filter; - -const CERT_PEM: &str = r#" ------BEGIN CERTIFICATE----- -MIIFfTCCA2WgAwIBAgIUKRmy+fJlEoA6QuOV9cDgjapPtHIwDQYJKoZIhvcNAQEL -BQAwTjELMAkGA1UEBhMCSVQxEzARBgNVBAgMClNvbWUtU3RhdGUxFjAUBgNVBAoM -DUlzc3VlciBTZXJ2ZXIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNDA1MzAxNzEy -MjhaFw0yNTA1MzAxNzEyMjhaME4xCzAJBgNVBAYTAklUMRMwEQYDVQQIDApTb21l -LVN0YXRlMRYwFAYDVQQKDA1Jc3N1ZXIgU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhv -c3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0NgJZ4NAx0/Aho4/A -y+eq8XC8n8DUvQQ7kPpbHBzo0m3EFJS2I9cZg3m4UjZsDeX5hy+6hyA2cMTwY2rG -X4tz1FK+ObiM4MFgj0oI6UC9SrLrUdBP3dazdLEKbAjgLLgJwKMQKiPn8DNx+/Jw -pH9OhmqEiK3mOBjifKr9ib7rvRBfgar7xzyv+oLgjs9jR9KU2QO+jw15bp/8+aI8 -q3yIGkxP5lp/tmo1JzBEwfEgGz5X3LY+h5Z9EjQZPH1ZWp2WV6epk4WZVKMeSTEe -Rmv0ztG2Ib42GkIX4Ne1TNKWt/mHGwrfXCDkBk+PDqsMCvVk2ZoRPpriOU/1+dWv -Nbt69wqIxDMG+EvdlCEoOdFPJt+Wk0kO+k0MDHmXtEkyUNnRNds1yQ/+6/WuuZ+1 -+UPOs51QA/uSnhh9t2ZrpyVQYfAb6dlKYtB82aXwF0AF2yNVearmZuXqqm15EwBe -Ra3lFfbrQ1EfC1biaLrXaa6AR+xhNyTlJbjiN3hVUV9Z/0vLWMKfuKBWEB0rgEPp -3opTxyZj1MQQu5EkCABHu68yDPwRazd4pGsjcnwDTLC7JMp33vZCvOTkIL09yW4v -Dh6Um5vRtlB9UnoyT8h6+rvPzH6rnX+CURDgOFS4GP2jj+9KxtDdfwopZXYsEK9e -P0kYIszhaHhn5KE8bQOeafpWXwIDAQABo1MwUTAdBgNVHQ4EFgQUevGkLEz/xQ3q -cN/N9+5qLbLZfrMwHwYDVR0jBBgwFoAUevGkLEz/xQ3qcN/N9+5qLbLZfrMwDwYD -VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAhoa+2JoI0RX5uB4w2Lxz -q1XrIMShU5lj9PwyrwSAHR1A6UkPefnMk9aLybJhCgakfttgTETGChZoOQZdyn53 -QzZf66z2e6nzrQgMRiv3DqEPOtXb9CnbXWMeHGV2B7M0OOIvwQLabzzryT1RawJz -NIu8q8bEyo95/fdVtV/OPFP8/4LTiqGJCfQ4avH62NUFmxjVO9RpU2ULCSeAHWHG -5g8On/hTeI9eWugqkjH7UrSFm/aDQH0Qf+glnUZBFPHYd38oQQd0PvBYnlKccw6r -uzU1ekgM/bQl8C3BcXt/zFlT1Dtp6EY1pz5sKjEBMDjfWxnLGNhDbCYG7+w3ki/w -zTZjLyOh9R4rzXSTPACki8CcCz6rZqPCm5AIyPnPspeSgYLAIgHjrWKsUiX9Gl6m -D03Wk8YJLabRiz16l7FK/RN+vNlX8URuWZYOcdxHVz8ZU6tD+VAWh9vS+LS5a/1E -Hw3JobssPUA7ZsF9PYRpNv9RckMPbAnct9tmA9s6NUOuxzFfcNf13DGwkUBImqF3 -e38wWAchOydQ0vghbiCpMOwD6IrssQBTQMRYOlrol6osrRxuXJvb15dpUkq64Q6/ -gfi1O7mVEkNhDjpv8ebBLqy8qnQZmOc+h7JMPI7fHsFLTenJqKjawx6C3WVp/B0J -K/HioaQNxyYc+7hW1bWvwIU= ------END CERTIFICATE----- -"#; - -const KEY_PEM: &str = r#" ------BEGIN PRIVATE KEY----- -MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC0NgJZ4NAx0/Ah -o4/Ay+eq8XC8n8DUvQQ7kPpbHBzo0m3EFJS2I9cZg3m4UjZsDeX5hy+6hyA2cMTw -Y2rGX4tz1FK+ObiM4MFgj0oI6UC9SrLrUdBP3dazdLEKbAjgLLgJwKMQKiPn8DNx -+/JwpH9OhmqEiK3mOBjifKr9ib7rvRBfgar7xzyv+oLgjs9jR9KU2QO+jw15bp/8 -+aI8q3yIGkxP5lp/tmo1JzBEwfEgGz5X3LY+h5Z9EjQZPH1ZWp2WV6epk4WZVKMe -STEeRmv0ztG2Ib42GkIX4Ne1TNKWt/mHGwrfXCDkBk+PDqsMCvVk2ZoRPpriOU/1 -+dWvNbt69wqIxDMG+EvdlCEoOdFPJt+Wk0kO+k0MDHmXtEkyUNnRNds1yQ/+6/Wu -uZ+1+UPOs51QA/uSnhh9t2ZrpyVQYfAb6dlKYtB82aXwF0AF2yNVearmZuXqqm15 -EwBeRa3lFfbrQ1EfC1biaLrXaa6AR+xhNyTlJbjiN3hVUV9Z/0vLWMKfuKBWEB0r -gEPp3opTxyZj1MQQu5EkCABHu68yDPwRazd4pGsjcnwDTLC7JMp33vZCvOTkIL09 -yW4vDh6Um5vRtlB9UnoyT8h6+rvPzH6rnX+CURDgOFS4GP2jj+9KxtDdfwopZXYs -EK9eP0kYIszhaHhn5KE8bQOeafpWXwIDAQABAoICACk76GrPTWPxVhMkXSqknVQY -XY7TUOh8fWDxyc8aDiVZrML+YI6Sgc+1LR83NHuh7Hqda5bW/1BlmjJU1SQhzYNJ -1ErxYyBc9vJUpwCF+om1c13zwOHdZRh3vWCgmqFMqimVGdqDgea6p9LPbshGY1jA -2dOMgJnaQRtloixUHcrL3li0Dz7/9gtgGx0toBdb4jursjcifVdJMDNOnUgbhumP -duGbWB67yu5xkpJQ/A64HRo2hr6lAJKKnUAe/qF44fD6CoN7HUkZvB8caTA/tcj0 -n7h4XRD9Dh1vlXvG4bKZ0u8OiXzVFDOInCCkHxAo28BhJ+cFN6RGr9yc9z9ZYJWe -QzzAG7kLW179ZMOt24CSnA+rjNOq8J7yyeA2KnrKHE1aVDXKALANc8SK69gRqhG0 -8Kf37Pa74IMn4hwJY40MnGZCn8Bc72WEOu8Q/j3JKlXuvQ+2Juy3j0qeemb0sPhI -GDjEh0Osm+VvFK3MnQLjJ9fNis3FBnOkqemv7yXrbsDnLmbtKz1pVGXlt9WStWuf -1jxcP7vGRuxhI2ydJiDqny19eSYc+ze9ZdMGsinc6oiVtv96Kabrj83CzPxtMn4W -KyWVjq4H0Nvg3CbbIkQyrmIIxr7/9dcFxRZJj7Qp+7W4/hnEQJDFbijWsm8anu4j -bJqARy3qF7rhyhdVmIRlAoIBAQDkKXiDYkh80BCBB0CzQweHeQbC9KbGQ34spoAh -9C3iFeEukB4QbCYRzrC4BsE4FAkA5ZjkRFFuwtUQUfXFmCN4vvMKLEJ7knUZ4GtP -kKe8kUWiRLNX8NgO5Kp6U8eNwH9mCGfAG81FzYps0KXBKLdHyN8D3/XHBL1P77Sx -3f8U3N/8PeSkL5J7tAAPH1AT4dMW5Yyd35/nst0LgbhsBMoSZO9yoy40DCXc/PZa -XA8UyhzJ9brBDWLqTFhnH9FY0nDcBErn+k3Iy1yt+AtjzHkozc+8UgMebymETISg -hHuFkI+lCOOSalhSa+xaQ5mCxnug0RvamH8s7gJS7hmqgRI7AoIBAQDKMs/ABIu6 -AH6Bd0kslZiiiiBF+uD9esAm+5hKzEx0/rmVleqSPvgMZ/wOjGIDkWHlcuRYfT3x -qJfN+TTsRWO+I0qBN637pa61gyN0evyU0yNLHtTpk4kGul809033J2qI1lV5ZDZQ -VmRndxS5o+q258Q+k9gUEhH8pootIIskZce+Ap/1yQYU6dX8tZyxb0xIQRxHUoTr -wcGWF7TeLUKjet5STDQkFrJAsxqPnlGdZEbmIUwVq1JQFDmQLq1LMphsPCMRsorl -x1IXGMytW0t1Uo8oAPgLokkvlCVkcr36lK7PqztCs/7CMHFB4mDOQ6ebXXdeD+Cj -UozDXk1QCkYtAoIBAGQucWP++6USFq0Q6i/L9rpdQcLIeZIEgJpKYWiHNr5WrREs -5oZ/dhfLkXy8OorNAUcLiR5xgPAJTFRmKGwiaTDTZxPIa0im/hPmnjfJZF0/zDt7 -vuOWNMgVT2V28+Ah7c53Ulbf3joQDf/JfX5ayl8SrBmfdON2CUnYDpcqyp32fcLb -ylDtMnxuhLgfH6IbPfyHvj5zulqaBsTOqJOFZtS8zQag7+CalOV7jRAP6+9M72ce -Jmot3ojSDN768Yj2ned9WwaQuSa5ZHBewWGInop0FAq0V/o9BRW6I2H3yeTTnL7y -MDpRx4YpLRZVKEprrEzohDpvSygHOa4ALfTXx5ECggEAHdDgUbBZ6lOoU+8DG0/z -8gPsPmWAR+CR9S2kh2MY4yestiIlNOuUKEAtKvgIe/40MWF/yoytVbVGrVjSNQUG -cEND+Un8nd8QpDNyRvrbxBZeXfVt8AORcSyKXYUaOhfV3de9QulEGRSkOZ2VsMo7 -Ej2a8p9afZssi9UDCySBYByiF6LLc7Lw8qSIqXseYAvJc94mDV5du2YW5csZIKoL -6Y90uS4Dgk7WejPu9r6hylO/ILBIR+m7eZOB6YD4jN4pG8zCXxm04a1CraU6iVWt -Ct/hvERoT07e++UcnnNNEGZcYMMfOfbQ2lHKYazAGapgTMi7kWNF/M9vcl+1htf9 -AQKCAQBBHqBp7Td7W5az8cXN2OmMu3rf//cKod/XLutFDOqIcWF3hez72VJwdgWt -A5CWBdJs6Z6Te+R9bvvh6ejx18r+W+g28MmhqkhWpIL+wsS8k61HAB8yU3049e72 -0kAfr1KLOOGu7AkAZwosCUDM5QXSOI5DyPtV0zt/zZaJW1dgmnQWZOLVLFIw4+SN -nXlzM+holKXA/mFOMcC6vln7F6XcIp1eYWsoq8KpQcmYIFzXjQh9O6zpoecBZgD3 -jvEj1PHuYUJjxCxwtr27GymxWtFuuOTDA91H8cZSnUmW7MWnkFa/VlLHEkSP5fTY -DcdbW3ZurxzQIr/OYo8xwUOR9USF ------END PRIVATE KEY----- -"#; - - -#[tokio::main] -async fn main() { - const PORT: u16 = 4443; - println!("Server is listening on port {} ...", PORT); - - // Match any request and return hello world! - let route = warp::path(".well-known") - .and(warp::fs::dir("./.well-known")); - - warp::serve(route) - .tls() - .cert(CERT_PEM.as_bytes()) - .key(KEY_PEM.as_bytes()) - .run(([127, 0, 0, 1], PORT)) - .await; - -} \ No newline at end of file diff --git a/examples/2_pqc/traditional.rs b/examples/2_pqc/traditional.rs deleted file mode 100644 index ebd0e743a7..0000000000 --- a/examples/2_pqc/traditional.rs +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashMap, fs::File, path::Path}; -use examples::{MemStorage, DID_URL, PATH_DID_FILE}; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{core::{Duration, FromJson, Object, Timestamp, Url}, credential::{Credential, CredentialBuilder, DecodedJwtCredential, DecodedJwtPresentation, FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidator, JwtCredentialValidatorUtils, JwtPresentationOptions, JwtPresentationValidationOptions, JwtPresentationValidator, JwtPresentationValidatorUtils, Presentation, PresentationBuilder, Subject, SubjectHolderRelationship}, did::{CoreDID, DIDJwk, DID}, document::{verifiable::JwsVerificationOptions, CoreDocument}, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwsSignatureOptions, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; -use identity_iota::storage::JwkDocumentExt; -use reqwest::ClientBuilder; -use serde_json::json; -use colored::Colorize; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let binding = DID_URL.to_owned() + "did.json"; - let did_url: &str = binding.as_str(); - let binding = PATH_DID_FILE.to_owned() + "did.json"; - let path_did_file: &str = binding.as_str(); - - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document.generate_method( - &storage_issuer, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA, - None, - MethodScope::VerificationMethod, - ).await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (alice_document, fragment_alice) = CoreDocument::new_did_jwk( - &storage_alice, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - - println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document.create_credential_jwt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ).await?; - - println!("{} {} {}","[Issuer]".red(), ": Generate VC (JWT encoded): ", credential_jwt.as_str()); - - println!("{} {} {} {}", "[Issuer]".red(), "->", "[Holder]".blue(), ": Sending VC"); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); - - println!("{} {}", "[Holder]".blue(), ": Verify VC"); - - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge: ", challenge); - - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - let presentation: Presentation = - PresentationBuilder::new(alice_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - let presentation_jwt: Jwt = alice_document - .create_presentation_jwt( - &presentation, - &storage_alice, - &fragment_alice, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - println!("{} {} {}", "[Holder]".blue(), ": Generate Verifiable Presentation (VP) (JWT encoded) :", presentation_jwt.as_str()); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending VP"); - - println!("{} : Resolve Issuer's DID and Holder's DID to verify the VP", "[Verifier]".green()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_did_jwk_handler(); - - let holder_did: DIDJwk = JwtPresentationValidatorUtils::extract_holder::(&presentation_jwt)?; - let holder: CoreDocument = resolver.resolve(&holder_did).await?; - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - EdDSAJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver_web.resolve_multiple(&issuers).await?; - - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - let issuer_document: &CoreDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - println!("{} : VP successfully verified, access granted", "[Verifier]".green()); - - Ok(()) -} diff --git a/examples/2_pqc/traditional_zk.rs b/examples/2_pqc/traditional_zk.rs deleted file mode 100644 index 31515be137..0000000000 --- a/examples/2_pqc/traditional_zk.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{fs::File, path::Path}; -use examples::{MemStorage, DID_URL, PATH_DID_FILE}; -use identity_iota::{core::{FromJson, Object, Url}, credential::{Credential, CredentialBuilder, FailFast, Jpt, JptCredentialValidationOptions, JptCredentialValidator, JptPresentationValidationOptions, JptPresentationValidator, JptPresentationValidatorUtils, JwpCredentialOptions, JwpPresentationOptions, SelectiveDisclosurePresentation, Subject}, did::{CoreDID, DID}, document::CoreDocument, resolver::Resolver, storage::{DidJwkDocumentExt, JwkMemStore, JwpDocumentExt, KeyIdMemstore}, verification::{jws::JwsAlgorithm, MethodScope}}; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use reqwest::ClientBuilder; -use serde_json::json; -use colored::Colorize; - -pub fn write_to_file(doc: &CoreDocument, path: Option<&str>) -> anyhow::Result<()> { - let path = Path::new(path.unwrap_or_else(|| "did.json")); - let file = File::create(path)?; - serde_json::to_writer_pretty(file, doc)?; - Ok(()) -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let binding = DID_URL.to_owned() + "did_zk.json"; - let did_url: &str = binding.as_str(); - let binding = PATH_DID_FILE.to_owned() + "did_zk.json"; - let path_did_file: &str = binding.as_str(); - - println!("{} {} {}", "[Issuer]".red(), ": Create DID (with did:web method) and publish the DID Document at", did_url); - - let client= ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - - let mut issuer_document: CoreDocument = CoreDocument::new_from_url(did_url)?; - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - let fragment_issuer = issuer_document.generate_method_jwp( - &storage_issuer, - JwkMemStore::BLS12381G2_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, - None, - MethodScope::VerificationMethod, - ).await?; - - write_to_file(&issuer_document, Some(path_did_file))?; - - let storage_alice: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (alice_document, _fragment_alice) = CoreDocument::new_did_jwk( - &storage_alice, - JwkMemStore::ED25519_KEY_TYPE, - JwsAlgorithm::EdDSA - ).await?; - - println!("{} {} {}", "[Holder]".blue(), ": Create DID Jwk:", alice_document.id().as_str()); - - let subject: Subject = Subject::from_json_value(json!({ - "id": alice_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Issuer]".red(), ": Request Verifiable Credential (VC)"); - - println!("{} {} {}", "[Holder]".blue(), ": Credential information: ", serde_json::to_string_pretty(&subject)?); - - println!("{} {} {} {}", "[Holder]".blue(), "<->", "[Issuer]".red(), ": Challenge-response protocol to authenticate Holder's DID"); - - println!("{} {} ","[Issuer]".red(), ": Generate VC"); - - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jpt: Jpt = issuer_document.create_credential_jpt( - &credential, - &storage_issuer, - &fragment_issuer, - &JwpCredentialOptions::default(), - None, - ).await?; - - println!("{} {} {} {} {}", "[Issuer]".red(), " -> ", "[Holder]".blue(), ": Sending VC (as JPT):", credential_jpt.as_str()); - - println!("{} {} {}", "[Holder]".blue(), ": Resolve Issuer's DID:", issuer_document.id().as_str()); - - println!("{} {} {issuer_document:#}", "[Holder]".blue(), ": Issuer's DID Document:"); - - println!("{} {}", "[Holder]".blue(), ": Validate VC"); - - let decoded_jpt = JptCredentialValidator::validate::<_, Object>( - &credential_jpt, - &issuer_document, - &JptCredentialValidationOptions::default(), - FailFast::FirstError, - ).unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Successfull verification"); - - println!("{} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Request access with Selective Disclosure of VC attributes"); - - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - println!("{} {} {} {} {}", "[Verifier]".green(), "->", "[Holder]".blue(), ": Send challenge:", challenge); - - println!("{} : Resolve Issuer's Public Key to compute the Signature Proof of Knowledge", "[Holder]".blue()); - - let method_id = decoded_jpt - .decoded_jwp - .get_issuer_protected_header() - .kid() - .unwrap(); - - println!("{} : Engages in the Selective Disclosure of credential's attributes (Name and GPA)", "[Holder]".blue()); - - let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_jpt.decoded_jwp); - selective_disclosure_presentation - .conceal_in_subject("GPA") - .unwrap(); - - selective_disclosure_presentation.conceal_in_subject("name").unwrap(); - - println!("{} {}", "[Holder]".blue(), ": Compute the Signature Proof of Knowledge and generate the Presentation/zk_proof (JPT encoded)"); - - let presentation_jpt: Jpt = issuer_document - .create_presentation_jpt( - &mut selective_disclosure_presentation, - method_id, - &JwpPresentationOptions::default().nonce(challenge), - ) - .await?; - - println!("{} {} {} {} {}", "[Holder]".blue(), "->", "[Verifier]".green(), ": Sending Presentation (as JPT):", presentation_jpt.as_str()); - - println!("{} : Resolve Issuer's DID and verifies the Presentation/zk_proof (JPT encoded)","[Verifier]".green()); - - let mut resolver_web: Resolver = Resolver::new(); - let _ = resolver_web.attach_web_handler(client)?; - - let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); - let issuer_document: CoreDocument = resolver_web.resolve(&issuer).await?; - - let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); - - let _decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( - &presentation_jpt, - &issuer_document, - &presentation_validation_options, - FailFast::FirstError, - ).unwrap(); - - println!("{} : JPT successfully verified, access granted", "[Verifier]".green()); - - Ok(()) -} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4a173d1315..462e5cc05b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -23,7 +23,7 @@ identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = serde.workspace = true colored = "2.1.0" -identity_resolver = { path = "../identity_resolver", default-features = false, features = ["did-web"] } +identity_resolver = { path = "../identity_resolver", default-features = false } [lib] path = "utils/utils.rs" diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index 4b64ca4e75..92a728610d 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "The core traits and types for the identity-rs library." [dependencies] diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 9e7b27deee..cf7336f5c5 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "An implementation of the Verifiable Credentials standard." [dependencies] diff --git a/identity_did/src/did_web.rs b/identity_did/src/did_web.rs deleted file mode 100644 index 072bd6f93a..0000000000 --- a/identity_did/src/did_web.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::{fmt::{Display, Formatter}, str::FromStr}; - -use identity_core::common::Url; -use crate::{CoreDID, Error, DID}; -// use ref_cast::{ref_cast_custom, RefCastCustom}; -use ::serde::{Deserialize, Serialize}; -use crate::Error as DIDError; - -/// Alias for a `Result` with the error type [`DIDError`]. -type Result = std::result::Result; - -/// A DID conforming to the Web DID method specification. -/// -/// This is a thin wrapper around the [`DID`][`CoreDID`] type from the -/// [`identity_did`][`identity_did`] crate. -#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize/* , RefCastCustom*/)] -#[repr(transparent)] -#[serde(into = "CoreDID", try_from = "CoreDID")] -pub struct WebDID(CoreDID); - -impl WebDID { - /// The URL scheme for Decentralized Identifiers. - pub const SCHEME: &'static str = CoreDID::SCHEME; - - /// The IOTA DID method name (`"iota"`). - pub const METHOD: &'static str = "web"; - - /// Create a new valid Web DID. - pub fn new(url: &str) -> Result { - let parsed_url: Url = Url::parse(url).map_err(|_| Error::Other("Not a valid Url"))?; - - // Extract the domain and path - if let Some(domain) = parsed_url.domain() { - - let port = parsed_url.port().map_or(String::new(), |p| format!("%3a{}", p)); - - let path = parsed_url.path_segments().map_or(String::new(), |p| { - format!("{}{}", ":", p.into_iter().collect::>().join(":")) - }); - - let did_web = format!("did:{}:{}{}{}", Self::METHOD, domain, port, path); - let core_did = CoreDID::parse(did_web).map_err(|_| Error::Other("Cannot convert to CoreDID"))?; - Ok(Self(core_did)) - - } else { - return Err(Error::InvalidMethodId); - } - } - - /// Parses an [`WebDID`] from the given `input`. - /// - /// # Errors - /// - /// Returns `Err` if the input does not conform to the [`WebDID`] specification. - pub fn parse(input: impl AsRef) -> Result { - CoreDID::parse(input.as_ref().to_lowercase()).and_then(Self::try_from_core) - } - - /// Converts a [`WebDID`] to a [`Url`] - pub fn to_url(&self) -> Url { - //This is safe because if i have constructed a WebDID I already know it is valid - WebDID::check_validity(self).unwrap() - } - - - /// Converts a [`CoreDID`] to a [`WebDID`]. - /// - /// # Errors - /// - /// Returns `Err` if the input does not conform to the [`IotaDID`] specification. - pub fn try_from_core(did: CoreDID) -> Result { - Self::check_validity(&did)?; - Ok(Self(did)) - } - - // =========================================================================== - // Validation - // =========================================================================== - - /// Checks if the given `DID` is syntactically valid according to the [`WebDID`] method specification. - /// - /// # Errors - /// - /// Returns the corresponding [`Url`] or `Err` if the input is not a syntactically valid [`WebDID`]. - pub fn check_validity(did: &D) -> Result { - Self::check_method(did) - .and_then(|_| Self::check_method_id(did)) - } - - /// Returns a `bool` indicating if the given `DID` is valid according to the - /// [`WebDID`] method specification. - /// - /// Equivalent to `WebDID::check_validity(did).is_ok()`. - pub fn is_valid(did: &CoreDID) -> bool { - Self::check_validity(did).is_ok() - } - - - // =========================================================================== - // Helpers - // =========================================================================== - - /// Checks if the given `DID` has a valid [`WebDID`] `method` (i.e. `"web"`). - /// - /// # Errors - /// - /// Returns `Err` if the input represents another method. - fn check_method(did: &D) -> Result<()> { - (did.method() == Self::METHOD) - .then_some(()) - .ok_or(DIDError::InvalidMethodName) - } - - /// Checks if the given `DID` has a valid [`IotaDID`] `method_id`. - /// - /// # Errors - /// - /// Returns `Err` if the input does not have a [`IotaDID`] compliant method id. - fn check_method_id(did: &D) -> Result { - let (domain, port, path) = Self::denormalized_components(did.method_id()); - - let port = port.map(|p| u16::from_str(&p)) - .map_or(Ok(None), |r| r.map(Some) - .map_err(|_| Error::InvalidMethodId))?; - - let mut url = Url::parse(&format!("https://{}", domain)) - .map_err(|_| Error::InvalidMethodId)?; - - url.set_port(port).map_err(|_| Error::InvalidMethodId)?; - - path.and_then(|p| Some(url.set_path(&p))); - - url.domain().ok_or(Error::InvalidMethodId)?; - - Ok(url) - } - - /// example.github.io%3A3000:did-web-server:.well-known:did.json -> https:://example.github.io:3000/did-web-server/.well-known/did.json - #[inline(always)] - fn denormalized_components(input: &str) -> (String, Option, Option) { - - match input.find("%3a") { - Some(i) => { - let (domain, tail) = input.split_at(i); - match tail.find(":") { - Some(i) => { - let (port, path) = tail.split_at(i); - (domain.to_owned(), Some(port[3..].to_owned()), Some(path.replace(":", "/"))) - }, - None => (domain.to_owned(), Some(tail[3..].to_owned()), None), - } - - }, - None => { - match input.find(":") { - Some(i) => { - let (domain, path) = input.split_at(i); - (domain.to_owned(), None, Some(path.replace(":", "/"))) - }, - None => (input.to_owned(), None, None), - } - } - } - - } - -} - -impl From for CoreDID { - fn from(id: WebDID) -> Self { - id.0 - } -} - -impl TryFrom for WebDID { - type Error = DIDError; - - fn try_from(value: CoreDID) -> std::result::Result { - Self::try_from_core(value) - } -} - -impl FromStr for WebDID { - type Err = DIDError; - - fn from_str(s: &str) -> std::result::Result { - Self::parse(s) - } -} - -impl From for String { - fn from(did: WebDID) -> Self { - did.into_string() - } -} - - -impl TryFrom<&str> for WebDID { - type Error = DIDError; - - fn try_from(other: &str) -> std::result::Result { - Self::parse(other) - } -} - -impl TryFrom for WebDID { - type Error = DIDError; - - fn try_from(other: String) -> std::result::Result { - Self::parse(other) - } -} - -impl Display for WebDID { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl AsRef for WebDID { - fn as_ref(&self) -> &CoreDID { - &self.0 - } -} \ No newline at end of file diff --git a/identity_did/src/lib.rs b/identity_did/src/lib.rs index 1dc5285bcf..e9bb2c5873 100644 --- a/identity_did/src/lib.rs +++ b/identity_did/src/lib.rs @@ -25,7 +25,6 @@ mod did; mod did_jwk; mod did_url; mod error; -mod did_web; mod did_compositejwk; pub use crate::did_url::DIDUrl; @@ -35,5 +34,4 @@ pub use did::CoreDID; pub use did::DID; pub use did_jwk::*; pub use error::Error; -pub use did_web::*; pub use did_compositejwk::*; \ No newline at end of file diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 0c0a41a6c7..303e589eb3 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -13,7 +13,6 @@ use std::convert::Infallible; use identity_did::DIDCompositeJwk; use identity_did::DIDJwk; -use identity_did::WebDID; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::DecodedJws; use identity_verification::jose::jws::Decoder; @@ -1072,19 +1071,6 @@ impl CoreDocument { } } -impl CoreDocument { - /// Creates a [`CoreDocument`] from a url following the did:web method. - pub fn new_from_url(url: &str) -> Result{ - let id = WebDID::new(url).map_err(|_| Error::InvalidDocument("Invalid DID Web", None))?; - let document: CoreDocument = CoreDocument::builder(Object::default()) - .id(id.into()) - .build() - .map_err(|_| Error::InvalidDocument("empty Document construction failed", None))?; - - Ok(document) - } -} - #[cfg(test)] mod tests { use identity_core::convert::FromJson; diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index 35b9a8bdad..34b637c1e4 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "tangle", "utxo", "shimmer", "identity"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "An IOTA Ledger integration for the IOTA DID Method." [dependencies] diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 80be00fc91..71e941434d 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -8,13 +8,12 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true -identity_core = { version = "1.5.0", path = "../identity_core" } -iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha", "ed25519" ] } +identity_core = { version = "=1.5.0", path = "../identity_core" } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index 45709c8d84..cd549d0ff8 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["iota", "identity", "jose", "jwk", "jws"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true + description = "JWS PQC signature verification for IOTA Identity" [dependencies] diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index d0bac33d47..ec7ff818fa 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "did", "identity", "resolver", "resolution"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "DID Resolution utilities for the identity.rs library." [dependencies] @@ -22,8 +21,6 @@ identity_document = { version = "=1.5.0", path = "../identity_document", default serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } -reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} - [dependencies.identity_iota_core] version = "=1.5.0" @@ -42,7 +39,7 @@ default = ["revocation-bitmap", "iota"] revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_core?/revocation-bitmap"] # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] -did-web = [] + [lints] workspace = true diff --git a/identity_resolver/src/error.rs b/identity_resolver/src/error.rs index b37e6c9ecb..d72a78fd4a 100644 --- a/identity_resolver/src/error.rs +++ b/identity_resolver/src/error.rs @@ -71,7 +71,4 @@ pub enum ErrorCause { /// No client attached to the specific network. #[error("none of the attached clients support the network {0}")] UnsupportedNetwork(String), - /// Resolved DID is different from the DID value inside the DID Document - #[error("resolved DID different from the DID Document id")] - DidNotMatching } diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 504dace680..5e4bdf2fbb 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -372,55 +372,6 @@ mod iota_handler { } } -#[cfg(feature = "did-web")] -mod web_handler { - use crate::ErrorCause; - use super::Resolver; - use identity_did::DID; - use identity_document::document::CoreDocument; - use identity_did::WebDID; - use crate::Error; - use crate::Result; - - - impl Resolver - where - DOC: From + AsRef + 'static, - { - /// Convenience method for attaching a new handler responsible for resolving Web DIDs. - /// - /// See also [`attach_handler`](Self::attach_handler). - pub fn attach_web_handler(&mut self, client: reqwest::Client) -> Result<(), Error> - { - let handler = move |did: WebDID| { - let future_client = client.clone(); - async move { - future_client.get(did.to_url().as_ref()) - .send() - .await - .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) }))? - .json::() - .await - .map_err(|e| Error::new(ErrorCause::HandlerError { source: Box::new(e) })) - .and_then(|d| - if d.id().as_str() == did.as_str() { - Ok(d) - } else { - Err(Error::new(ErrorCause::DidNotMatching)) - } - ) - - } - }; - - self.attach_handler(WebDID::METHOD.to_owned(), handler); - Ok(()) - } - - } -} - - impl Default for Resolver where CMD: for<'r> Command<'r, Result>, diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 347934d93b..b663274b79 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "storage", "identity", "kms"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "Abstractions over storage for cryptographic keys used in DID Documents" [dependencies] @@ -21,8 +20,8 @@ identity_credential = { version = "=1.5.0", path = "../identity_credential", def identity_did = { version = "=1.5.0", path = "../identity_did", default-features = false } identity_document = { version = "=1.5.0", path = "../identity_document", default-features = false } identity_iota_core = { version = "=1.5.0", path = "../identity_iota_core", default-features = false, optional = true } -identity_verification = { version = "=1.5.0", path = "../identity_verification", default_features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "sha", "random"], optional = true } +identity_verification = { version = "=1.5.0", path = "../identity_verification", default-features = false } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "random"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } seahash = { version = "4.1.0", default-features = false } diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index dcf0139f3d..098cc8bf20 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -8,7 +8,6 @@ keywords = ["iota", "storage", "identity", "kms", "stronghold"] license.workspace = true readme = "./README.md" repository.workspace = true -rust-version.workspace = true description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 70e3c8483b..c88aa86501 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Verification data types and functionality for identity.rs" [dependencies] From 3cad376d8f839629f196dee93988b81abb41b6c8 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 2 Apr 2025 12:15:33 +0200 Subject: [PATCH 132/163] Remove unwanted changes --- Cargo.toml | 2 +- bindings/wasm/CHANGELOG.md | 24 ++++++++++++++- bindings/wasm/Cargo.toml | 2 ++ .../wasm/src/common/imported_document_lock.rs | 2 +- bindings/wasm/src/error.rs | 2 +- bindings/wasm/src/lib.rs | 2 +- bindings/wasm/src/macros.rs | 6 ++-- examples/Cargo.toml | 30 ------------------- identity_iota/README.md | 7 +++-- identity_iota/src/lib.rs | 1 - .../src/document/iota_document.rs | 6 ++-- .../src/state_metadata/document.rs | 5 +--- identity_jose/src/jwk/key_set.rs | 1 - identity_jose/src/jws/decoder.rs | 2 +- identity_jose/src/jws/encoding/utils.rs | 4 +-- identity_jose/src/jws/recipient.rs | 2 +- .../src/storage/jwk_document_ext.rs | 2 +- .../src/storage/jwp_document_ext.rs | 1 + 18 files changed, 46 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index efb1fe687b..6c5aae8902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "examples", ] -exclude = ["bindings/wasm", "bindings/grpc", "examples/demo/server"] +exclude = ["bindings/wasm", "bindings/grpc"] [workspace.dependencies] bls12_381_plus = { version = "0.8.17" } diff --git a/bindings/wasm/CHANGELOG.md b/bindings/wasm/CHANGELOG.md index cd49c3874f..01b3f5c312 100644 --- a/bindings/wasm/CHANGELOG.md +++ b/bindings/wasm/CHANGELOG.md @@ -1,6 +1,28 @@ # Changelog -## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-27) +## [wasm-v1.5.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.5.0) (2025-01-20) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.4.0...wasm-v1.5.0) + +### Added + +- SD-JWT VC implementation [\#1413](https://github.com/iotaledger/identity.rs/pull/1413) + +### Patch + +- Support %-encoded characters in DID URL [\#1496](https://github.com/iotaledger/identity.rs/pull/1496) +- fix: serialization of status list [\#1423](https://github.com/iotaledger/identity.rs/pull/1423) + +## [wasm-v1.4.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.1...wasm-v1.4.0) + +### Added + +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) + +## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-28) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.0...wasm-v1.3.1) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 91153ccbeb..308fbf93fc 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -16,6 +16,7 @@ description = "Web Assembly bindings for the identity-rs crate." crate-type = ["cdylib", "rlib"] [dependencies] +anyhow = { version = "1.0.94", features = ["std"] } async-trait = { version = "0.1", default-features = false } bls12_381_plus = "0.8.17" console_error_panic_hook = { version = "0.1" } @@ -26,6 +27,7 @@ js-sys = { version = "0.3.61" } json-proof-token = "0.3.4" proc_typescript = { version = "0.1.0", path = "./proc_typescript" } serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.6.5" serde_json = { version = "1.0", default-features = false } serde_repr = { version = "0.1", default-features = false } # Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads. diff --git a/bindings/wasm/src/common/imported_document_lock.rs b/bindings/wasm/src/common/imported_document_lock.rs index 4852ab216e..4452ce6dd2 100644 --- a/bindings/wasm/src/common/imported_document_lock.rs +++ b/bindings/wasm/src/common/imported_document_lock.rs @@ -79,7 +79,7 @@ impl From<&ArrayIToCoreDocument> for Vec { pub(crate) struct ImportedDocumentReadGuard<'a>(tokio::sync::RwLockReadGuard<'a, CoreDocument>); -impl<'a> AsRef for ImportedDocumentReadGuard<'a> { +impl AsRef for ImportedDocumentReadGuard<'_> { fn as_ref(&self) -> &CoreDocument { self.0.as_ref() } diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs index db2bb49d38..8c0effc4c7 100644 --- a/bindings/wasm/src/error.rs +++ b/bindings/wasm/src/error.rs @@ -152,7 +152,7 @@ fn error_chain_fmt(e: &impl std::error::Error, f: &mut std::fmt::Formatter<'_>) struct ErrorMessage<'a, E: std::error::Error>(&'a E); -impl<'a, E: std::error::Error> Display for ErrorMessage<'a, E> { +impl Display for ErrorMessage<'_, E> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { error_chain_fmt(self.0, f) } diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 6f65fa54be..549516005d 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -4,7 +4,6 @@ * Modifications Copyright 2024 Fondazione LINKS. */ -#![forbid(unsafe_code)] #![allow(deprecated)] #![allow(clippy::upper_case_acronyms)] // wasm_bindgen calls drop on non-Drop types. When/If this is fixed, this can be removed (no issue to link here yet). @@ -31,6 +30,7 @@ pub mod jpt; pub mod resolver; pub mod revocation; pub mod sd_jwt; +pub mod sd_jwt_vc; pub mod storage; pub mod verification; diff --git a/bindings/wasm/src/macros.rs b/bindings/wasm/src/macros.rs index ecc99a8082..26cb197993 100644 --- a/bindings/wasm/src/macros.rs +++ b/bindings/wasm/src/macros.rs @@ -29,14 +29,14 @@ macro_rules! impl_wasm_json { impl $wasm_class { /// Serializes this to a JSON object. #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> $crate::error::Result { + pub fn to_json(&self) -> $crate::error::Result { use $crate::error::WasmResult; - JsValue::from_serde(&self.0).wasm_result() + wasm_bindgen::JsValue::from_serde(&self.0).wasm_result() } /// Deserializes an instance from a JSON object. #[wasm_bindgen(js_name = fromJSON)] - pub fn from_json(json: &JsValue) -> $crate::error::Result<$wasm_class> { + pub fn from_json(json: &wasm_bindgen::JsValue) -> $crate::error::Result<$wasm_class> { use $crate::error::WasmResult; json.into_serde().map(Self).wasm_result() } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 462e5cc05b..a3605cae63 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,7 +12,6 @@ identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-feature identity_iota = { path = "../identity_iota", default-features = false, features = ["iota-client", "client", "memstore", "domain-linkage", "revocation-bitmap", "status-list-2021", "jpt-bbs-plus", "hybrid-liboqs", "resolver"] } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["bbs-plus"] } iota-sdk = { version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } -reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls", "json", "hickory-dns"]} json-proof-token.workspace = true primitive-types = "0.12.1" rand = "0.8.5" @@ -21,9 +20,6 @@ serde_json = { version = "1.0", default-features = false } tokio = { version = "1.29", default-features = false, features = ["rt"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } serde.workspace = true -colored = "2.1.0" - -identity_resolver = { path = "../identity_resolver", default-features = false } [lib] path = "utils/utils.rs" @@ -119,29 +115,3 @@ name = "12_pq" [[example]] path = "1_advanced/13_hybrid.rs" name = "13_hybrid" - -[[example]] -path = "1_advanced/14_did_web.rs" -name = "14_did_web" - -[[example]] -path = "2_pqc/0_pq.rs" -name = "0_pq" - -[[example]] -path = "2_pqc/1_hybrid.rs" -name = "1_hybrid" - -[[example]] -path = "2_pqc/traditional.rs" -name = "traditional" - -[[example]] -path = "2_pqc/traditional_zk.rs" -name = "traditional_zk" - -[[example]] -path = "2_pqc/revocation_zk.rs" -name = "revocation_zk" - - diff --git a/identity_iota/README.md b/identity_iota/README.md index e8001e6788..407314d782 100644 --- a/identity_iota/README.md +++ b/identity_iota/README.md @@ -22,6 +22,9 @@ --- +> [!NOTE] +> This version of the library is compatible with IOTA Stardust networks, for a version of the library compatible with IOTA Rebased networks check [here](https://github.com/iotaledger/identity.rs/tree/feat/identity-rebased-alpha/) + ## Introduction IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. @@ -54,7 +57,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.3.1" } +identity_iota = { version = "1.5.0" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -88,7 +91,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.3.1", features = ["memstore"] } +identity_iota = { version = "1.5.0", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 16fb29558f..2117a0867a 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -96,7 +96,6 @@ pub mod prelude { #[cfg_attr(docsrs, doc(cfg(feature = "resolver")))] pub mod resolver { //! DID resolution utilities - pub use identity_resolver::*; } diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs index 45ba645fe8..5c0813f28c 100644 --- a/identity_iota_core/src/document/iota_document.rs +++ b/identity_iota_core/src/document/iota_document.rs @@ -332,7 +332,7 @@ impl IotaDocument { /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains // services whose ids are of the form #. - pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> + pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&'me Service> where Q: Into>, { @@ -347,7 +347,7 @@ impl IotaDocument { &'me self, method_query: Q, scope: Option, - ) -> Option<&VerificationMethod> + ) -> Option<&'me VerificationMethod> where Q: Into>, { @@ -512,8 +512,6 @@ impl AsRef for IotaDocument { } } - -//TODO: Web DID - how to handle revocation? I think this code should be repeated for WebDocument. This can be avoided? #[cfg(feature = "revocation-bitmap")] mod iota_document_revocation { use identity_credential::revocation::RevocationDocumentExt; diff --git a/identity_iota_core/src/state_metadata/document.rs b/identity_iota_core/src/state_metadata/document.rs index e14e381f5b..197f9befb8 100644 --- a/identity_iota_core/src/state_metadata/document.rs +++ b/identity_iota_core/src/state_metadata/document.rs @@ -424,10 +424,7 @@ mod tests { // Encoding. assert_eq!(packed[4], StateMetadataEncoding::Json as u8); // JSON length. - assert_eq!( - &packed[5..=6], - (expected_payload.as_bytes().len() as u16).to_le_bytes().as_ref() - ); + assert_eq!(&packed[5..=6], (expected_payload.len() as u16).to_le_bytes().as_ref()); // JSON payload. assert_eq!(&packed[7..], expected_payload.as_bytes()); } diff --git a/identity_jose/src/jwk/key_set.rs b/identity_jose/src/jwk/key_set.rs index e1c9754a8a..22a629eaab 100644 --- a/identity_jose/src/jwk/key_set.rs +++ b/identity_jose/src/jwk/key_set.rs @@ -14,7 +14,6 @@ use crate::jwk::Jwk; /// /// [More Info](https://tools.ietf.org/html/rfc7517#section-5) #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -#[repr(transparent)] pub struct JwkSet { /// An array of JWK values. /// diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 7ef55d02a9..5605122cc7 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -422,7 +422,7 @@ pub struct JwsValidationIter<'decoder, 'payload, 'signatures> { payload: &'payload [u8], } -impl<'decoder, 'payload, 'signatures> Iterator for JwsValidationIter<'decoder, 'payload, 'signatures> { +impl<'payload> Iterator for JwsValidationIter<'_, 'payload, '_> { type Item = Result>; fn next(&mut self) -> Option { diff --git a/identity_jose/src/jws/encoding/utils.rs b/identity_jose/src/jws/encoding/utils.rs index b1d903e612..2be2703488 100644 --- a/identity_jose/src/jws/encoding/utils.rs +++ b/identity_jose/src/jws/encoding/utils.rs @@ -86,7 +86,7 @@ pub(super) struct Flatten<'payload, 'unprotected> { pub(super) signature: JwsSignature<'unprotected>, } -impl<'payload, 'unprotected> Flatten<'payload, 'unprotected> { +impl Flatten<'_, '_> { pub(super) fn to_json(&self) -> Result { serde_json::to_string(&self).map_err(Error::InvalidJson) } @@ -99,7 +99,7 @@ pub(super) struct General<'payload, 'unprotected> { pub(super) signatures: Vec>, } -impl<'payload, 'unprotected> General<'payload, 'unprotected> { +impl General<'_, '_> { pub(super) fn to_json(&self) -> Result { serde_json::to_string(&self).map_err(Error::InvalidJson) } diff --git a/identity_jose/src/jws/recipient.rs b/identity_jose/src/jws/recipient.rs index 602f1e6f3f..96dd410fa0 100644 --- a/identity_jose/src/jws/recipient.rs +++ b/identity_jose/src/jws/recipient.rs @@ -15,7 +15,7 @@ pub struct Recipient<'a> { pub unprotected: Option<&'a JwsHeader>, } -impl<'a> Default for Recipient<'a> { +impl Default for Recipient<'_> { fn default() -> Self { Self::new() } diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index d42b3b2d0f..f9ee100986 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -627,4 +627,4 @@ mod iota_document { .await } } -} \ No newline at end of file +} diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs index 0dadb3e97a..21ef7fafaa 100644 --- a/identity_storage/src/storage/jwp_document_ext.rs +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -315,6 +315,7 @@ mod iota_document { .create_issued_jwp(storage, fragment, jpt_claims, options) .await } + async fn create_presented_jwp( &self, presentation: &mut SelectiveDisclosurePresentation, From 966019c8c02ed01a748d8db2c6fd461ccfd0113b Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 2 Apr 2025 12:39:18 +0200 Subject: [PATCH 133/163] Remove unwanted changes --- .../examples/src/1_advanced/10_sd_jwt_vc.ts | 167 ++++++++++++++++++ bindings/wasm/examples/src/main.ts | 3 + .../src/credential/jwt_serialization.rs | 2 +- .../jpt_credential_validator_utils.rs | 13 +- .../jwt_credential_validator.rs | 2 +- identity_did/src/did_url.rs | 48 ++++- .../src/key_storage/jwk_storage.rs | 1 - 7 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts diff --git a/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts b/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts new file mode 100644 index 0000000000..8eef36c198 --- /dev/null +++ b/bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts @@ -0,0 +1,167 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { + IJwk, + IJwkParams, + IResolver, + IssuerMetadata, + Jwk, + JwkType, + JwsVerificationOptions, + KeyBindingJwtBuilder, + KeyBindingJWTValidationOptions, + SdJwtVcBuilder, + Sha256Hasher, + Timestamp, + TypeMetadataHelper, +} from "@iota/identity-wasm/node"; +import { exportJWK, generateKeyPair, JWK, JWTHeaderParameters, JWTPayload, SignJWT } from "jose"; + +const vc_metadata: TypeMetadataHelper = JSON.parse(`{ + "vct": "https://example.com/education_credential", + "name": "Betelgeuse Education Credential - Preliminary Version", + "description": "This is our development version of the education credential. Don't panic.", + "claims": [ + { + "path": ["name"], + "display": [ + { + "lang": "de-DE", + "label": "Vor- und Nachname", + "description": "Der Name des Studenten" + }, + { + "lang": "en-US", + "label": "Name", + "description": "The name of the student" + } + ], + "sd": "allowed" + }, + { + "path": ["address"], + "display": [ + { + "lang": "de-DE", + "label": "Adresse", + "description": "Adresse zum Zeitpunkt des Abschlusses" + }, + { + "lang": "en-US", + "label": "Address", + "description": "Address at the time of graduation" + } + ], + "sd": "always" + }, + { + "path": ["address", "street_address"], + "display": [ + { + "lang": "de-DE", + "label": "Straße" + }, + { + "lang": "en-US", + "label": "Street Address" + } + ], + "sd": "always", + "svg_id": "address_street_address" + }, + { + "path": ["degrees", null], + "display": [ + { + "lang": "de-DE", + "label": "Abschluss", + "description": "Der Abschluss des Studenten" + }, + { + "lang": "en-US", + "label": "Degree", + "description": "Degree earned by the student" + } + ], + "sd": "allowed" + } + ] +}`); + +const keypair_jwk = async (): Promise<[JWK, JWK]> => { + const [sk, pk] = await generateKeyPair("ES256").then(res => [res.privateKey, res.publicKey]); + const sk_jwk = await exportJWK(sk); + const pk_jwk = await exportJWK(pk); + + return [sk_jwk, pk_jwk]; +}; + +const signer = async (header: object, payload: object, sk_jwk: JWK) => { + return new SignJWT(payload as JWTPayload) + .setProtectedHeader(header as JWTHeaderParameters) + .sign(sk_jwk) + .then(jws => new TextEncoder().encode(jws)); +}; + +export async function sdJwtVc() { + const hasher = new Sha256Hasher(); + const issuer = "https://example.com/"; + const [sk_jwk, pk_jwk] = await keypair_jwk(); + const issuer_public_jwk = { ...pk_jwk, kty: JwkType.Ec, kid: "key1" } as IJwk; + const issuer_signer = (header: object, payload: object) => signer(header, payload, sk_jwk); + const issuer_metadata = new IssuerMetadata(issuer, { jwks: { keys: [issuer_public_jwk] } }); + const dummy_resolver = { + resolve: async (input: string) => { + if (input == "https://example.com/.well-known/jwt-vc-issuer/") { + return new TextEncoder().encode(JSON.stringify(issuer_metadata.toJSON())); + } + if (input == "https://example.com/.well-known/vct/education_credential") { + return new TextEncoder().encode(JSON.stringify(vc_metadata)); + } + }, + } as IResolver; + const [holder_sk, holder_pk] = await keypair_jwk(); + const holder_public_jwk = { ...holder_pk, kty: JwkType.Ec, kid: "key2" } as IJwk; + const holder_signer = (header: object, payload: object) => signer(header, payload, holder_sk); + + /// Issuer creates an SD-JWT VC. + let sd_jwt_vc = await new SdJwtVcBuilder({ + name: "John Doe", + address: { + street_address: "A random street", + number: "3a", + }, + degree: [], + }, hasher) + .header({ kid: "key1" }) + .vct("https://example.com/education_credential") + .iat(Timestamp.nowUTC()) + .iss(issuer) + .requireKeyBinding({ kid: holder_public_jwk.kid }) + .makeConcealable("/address/street_address") + .makeConcealable("/address") + .finish({ sign: issuer_signer }, "ES256"); + + console.log(`issued SD-JWT VC: ${sd_jwt_vc.toString()}`); + + // Holder receives its SD-JWT VC and attaches its keybinding JWT. + const kb_jwt = await new KeyBindingJwtBuilder() + .iat(Timestamp.nowUTC()) + .header({ kid: holder_public_jwk.kid }) + .nonce("abcdefghi") + .aud("https://example.com/verify") + .finish(sd_jwt_vc.asSdJwt(), "ES256", { sign: holder_signer }); + const { disclosures, sdJwtVc } = sd_jwt_vc.intoPresentation(hasher).attachKeyBindingJwt(kb_jwt).finish(); + console.log(`presented SD-JWT VC: ${sdJwtVc}`); + + // Verifier checks the presented sdJwtVc. + await sdJwtVc.validate(dummy_resolver, hasher); + sdJwtVc.validateKeyBinding( + new Jwk(holder_public_jwk as IJwkParams), + hasher, + new KeyBindingJWTValidationOptions({ nonce: "abcdefghi", jwsOptions: new JwsVerificationOptions() }), + ); + + console.log("The presented SdJwtVc is valid!"); +} diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 604197994b..35f347b53f 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -12,6 +12,7 @@ import { createVC } from "./0_basic/5_create_vc"; import { createVP } from "./0_basic/6_create_vp"; import { revokeVC } from "./0_basic/7_revoke_vc"; import { didControlsDid } from "./1_advanced/0_did_controls_did"; +import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc"; import { didIssuesNft } from "./1_advanced/1_did_issues_nft"; import { nftOwnsDid } from "./1_advanced/2_nft_owns_did"; import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens"; @@ -70,6 +71,8 @@ async function main() { return await zkp(); case "9_zkp_revocation": return await zkp_revocation(); + case "10_sd_jwt_vc": + return await sdJwtVc(); case "traditional": return createDidJwk(); case "zk": diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index e7307ff5c7..feb3de531d 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -118,7 +118,7 @@ where } #[cfg(feature = "validator")] -impl<'credential, T> CredentialJwtClaims<'credential, T> +impl CredentialJwtClaims<'_, T> where T: ToOwned + Serialize + DeserializeOwned, { diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs index 258df619d4..e7de3fa8e4 100644 --- a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -168,13 +168,12 @@ impl JptCredentialValidatorUtils { issuer: &DOC, status: RevocationTimeframeStatus, ) -> ValidationUnitResult { - let issuer_service_url: identity_did::DIDUrl = - identity_did::DIDUrl::parse(status.id().to_string()).map_err(|err| { - JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( - "could not convert status id to DIDUrl; {}", - err, - ))) - })?; + let issuer_service_url: identity_did::DIDUrl = identity_did::DIDUrl::parse(status.id()).map_err(|err| { + JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "could not convert status id to DIDUrl; {}", + err, + ))) + })?; // Check whether index is revoked. let revocation_bitmap: crate::revocation::RevocationBitmap = issuer diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs index c099d763ab..acaa991e45 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator.rs @@ -297,7 +297,7 @@ impl JwtCredentialValidator { } /// Verify the signature using the given `public_key` and `signature_verifier`. - fn verify_decoded_signature( + pub(crate) fn verify_decoded_signature( decoded: JwsValidationItem<'_>, public_key: &Jwk, signature_verifier: &S, diff --git a/identity_did/src/did_url.rs b/identity_did/src/did_url.rs index 0e8eebcace..686d3b5b49 100644 --- a/identity_did/src/did_url.rs +++ b/identity_did/src/did_url.rs @@ -96,7 +96,7 @@ impl RelativeDIDUrl { self.path = value .filter(|s| !s.is_empty()) .map(|s| { - if s.starts_with('/') && s.chars().all(is_char_path) { + if s.starts_with('/') && is_valid_url_segment(s, is_char_path) { Ok(s.to_owned()) } else { Err(Error::InvalidPath) @@ -138,7 +138,7 @@ impl RelativeDIDUrl { .map(|mut s| { // Ignore leading '?' during validation. s = s.strip_prefix('?').unwrap_or(s); - if s.is_empty() || !s.chars().all(is_char_query) { + if s.is_empty() || !is_valid_url_segment(s, is_char_query) { return Err(Error::InvalidQuery); } Ok(format!("?{s}")) @@ -188,7 +188,7 @@ impl RelativeDIDUrl { .map(|mut s| { // Ignore leading '#' during validation. s = s.strip_prefix('#').unwrap_or(s); - if s.is_empty() || !s.chars().all(is_char_fragment) { + if s.is_empty() || !is_valid_url_segment(s, is_char_fragment) { return Err(Error::InvalidFragment); } Ok(format!("#{s}")) @@ -519,8 +519,7 @@ impl KeyComparable for DIDUrl { #[inline(always)] #[rustfmt::skip] pub(crate) const fn is_char_path(ch: char) -> bool { - // Allow percent encoding or not? - is_char_method_id(ch) || matches!(ch, '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '@' | '/' /* | '%' */) + is_char_method_id(ch) || matches!(ch, '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '@' | '/') } /// Checks whether a character satisfies DID Url query constraints. @@ -535,6 +534,33 @@ pub(crate) const fn is_char_fragment(ch: char) -> bool { is_char_path(ch) || ch == '?' } +pub(crate) fn is_valid_percent_encoded_char(s: &str) -> bool { + let mut chars = s.chars(); + let Some('%') = chars.next() else { return false }; + s.len() >= 3 && chars.take(2).all(|c| c.is_ascii_hexdigit()) +} + +pub(crate) fn is_valid_url_segment(segment: &str, char_predicate: F) -> bool +where + F: Fn(char) -> bool, +{ + let mut chars = segment.char_indices(); + while let Some((i, c)) = chars.next() { + if c == '%' { + if !is_valid_percent_encoded_char(&segment[i..]) { + return false; + } + // skip the two HEX digits + chars.next(); + chars.next(); + } else if !char_predicate(c) { + return false; + } + } + + true +} + #[cfg(test)] mod tests { use super::*; @@ -639,6 +665,10 @@ mod tests { assert!(relative_url.path().is_none()); assert!(relative_url.set_path(None).is_ok()); assert!(relative_url.path().is_none()); + + // Percent encoded path. + assert!(relative_url.set_path(Some("/p%AAth")).is_ok()); + assert_eq!(relative_url.path().unwrap(), "/p%AAth"); } #[rustfmt::skip] @@ -697,6 +727,10 @@ mod tests { assert_eq!(relative_url.query().unwrap(), "query"); assert!(relative_url.set_query(Some("name=value&name2=value2&3=true")).is_ok()); assert_eq!(relative_url.query().unwrap(), "name=value&name2=value2&3=true"); + + // With percent encoded char. + assert!(relative_url.set_query(Some("qu%EEry")).is_ok()); + assert_eq!(relative_url.query().unwrap(), "qu%EEry"); } #[rustfmt::skip] @@ -745,6 +779,10 @@ mod tests { assert!(relative_url.fragment().is_none()); assert!(relative_url.set_fragment(None).is_ok()); assert!(relative_url.fragment().is_none()); + + // Percent encoded fragment. + assert!(relative_url.set_fragment(Some("fr%AAgm%EEnt")).is_ok()); + assert_eq!(relative_url.fragment().unwrap(), "fr%AAgm%EEnt"); } #[rustfmt::skip] diff --git a/identity_storage/src/key_storage/jwk_storage.rs b/identity_storage/src/key_storage/jwk_storage.rs index 7141e51aba..4f1918934c 100644 --- a/identity_storage/src/key_storage/jwk_storage.rs +++ b/identity_storage/src/key_storage/jwk_storage.rs @@ -62,4 +62,3 @@ pub trait JwkStorage: storage_sub_trait::StorageSendSyncMaybe { /// Returns `true` if the key with the given `key_id` exists in storage, `false` otherwise. async fn exists(&self, key_id: &KeyId) -> KeyStorageResult; } - From 85d00836e427c956b43e0c351ec9e8dd8d7ecef5 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 3 Apr 2025 16:43:27 +0200 Subject: [PATCH 134/163] revert changes --- bindings/wasm/Cargo.toml | 2 +- bindings/wasm/examples/src/main.ts | 15 ------------ .../examples/src/wallet/did_jwk_hybrid.ts | 21 ----------------- .../wasm/examples/src/wallet/did_jwk_pq.ts | 22 ------------------ .../src/wallet/did_jwk_traditional.ts | 23 ------------------- .../wasm/examples/src/wallet/did_jwk_zk.ts | 22 ------------------ bindings/wasm/lib/jose/jws_algorithm.ts | 6 +++-- bindings/wasm/rust-toolchain.toml | 3 ++- bindings/wasm/src/iota/iota_document.rs | 1 - bindings/wasm/src/storage/jwk_storage.rs | 3 +-- bindings/wasm/src/storage/mod.rs | 3 --- examples/utils/utils.rs | 3 --- 12 files changed, 8 insertions(+), 116 deletions(-) delete mode 100644 bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts delete mode 100644 bindings/wasm/examples/src/wallet/did_jwk_pq.ts delete mode 100644 bindings/wasm/examples/src/wallet/did_jwk_traditional.ts delete mode 100644 bindings/wasm/examples/src/wallet/did_jwk_zk.ts diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 308fbf93fc..ae6897f752 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -34,7 +34,7 @@ serde_repr = { version = "0.1", default-features = false } tokio = { version = "1.29", default-features = false, features = ["sync"] } wasm-bindgen = { version = "=0.2.92", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "=0.4", default-features = false } -web-sys = { version = "0.3", features = ["console"]} + zkryptium = "0.2.2" [dependencies.identity_iota] diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 35f347b53f..c60082cc46 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -1,8 +1,5 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/* - * Modifications Copyright 2024 Fondazione LINKS. - */ import { createIdentity } from "./0_basic/0_create_did"; import { updateIdentity } from "./0_basic/1_update_did"; import { resolveIdentity } from "./0_basic/2_resolve_did"; @@ -22,10 +19,6 @@ import { sdJwt } from "./1_advanced/6_sd_jwt"; import { statusList2021 } from "./1_advanced/7_status_list_2021"; import { zkp } from "./1_advanced/8_zkp"; import { zkp_revocation } from "./1_advanced/9_zkp_revocation"; -import { createDidJwk } from "./wallet/did_jwk_traditional" -import { createDidJwkZk } from "./wallet/did_jwk_zk" -import { createDidJwkPq } from "./wallet/did_jwk_pq" -import { createDidJwkHybrid } from "./wallet/did_jwk_hybrid" async function main() { // Extract example name. @@ -73,14 +66,6 @@ async function main() { return await zkp_revocation(); case "10_sd_jwt_vc": return await sdJwtVc(); - case "traditional": - return createDidJwk(); - case "zk": - return createDidJwkZk(); - case "pq": - return createDidJwkPq(); - case "hybrid": - return createDidJwkHybrid(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts b/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts deleted file mode 100644 index 6d096a1418..0000000000 --- a/bindings/wasm/examples/src/wallet/did_jwk_hybrid.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -import { - CompositeAlgId, - JwkMemStore, - KeyIdMemStore, - Storage, - CoreDocument -} from "@iota/identity-wasm/node"; - -/** Demonstrate how to create a DID CompositeJWK Document */ -export async function createDidJwkHybrid(){ - - const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidCompositeJwk( - storage, - CompositeAlgId.IdMldsa44Ed25519Sha512) - - console.log(JSON.stringify(document, null, 2)); -} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts b/bindings/wasm/examples/src/wallet/did_jwk_pq.ts deleted file mode 100644 index 8725c6d7fb..0000000000 --- a/bindings/wasm/examples/src/wallet/did_jwk_pq.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -import { - JwkMemStore, - JwsAlgorithm, - KeyIdMemStore, - Storage, - CoreDocument -} from "@iota/identity-wasm/node"; - -/** Demonstrate how to create a DID JWK PQ Document */ -export async function createDidJwkPq(){ - - const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwkPq( - storage, - JwkMemStore.mldsaKeyType(), - JwsAlgorithm.MLDSA44) - - console.log(JSON.stringify(document, null, 2)); -} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts b/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts deleted file mode 100644 index 4fd4606c26..0000000000 --- a/bindings/wasm/examples/src/wallet/did_jwk_traditional.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -import { - JwkMemStore, - JwsAlgorithm, - KeyIdMemStore, - Storage, - CoreDocument -} from "@iota/identity-wasm/node"; - -/** Demonstrate how to create a traditional DID JWK Document */ -export async function createDidJwk(){ - - const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document: CoreDocument = await CoreDocument.newDidJwk( - storage, - JwkMemStore.ed25519KeyType(), - JwsAlgorithm.EdDSA,) - - console.log(JSON.stringify(document, null, 2)); - console.log("fragment" + document.fragmentJwk()); -} diff --git a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts b/bindings/wasm/examples/src/wallet/did_jwk_zk.ts deleted file mode 100644 index eae269ddbd..0000000000 --- a/bindings/wasm/examples/src/wallet/did_jwk_zk.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -import { - ProofAlgorithm, - JwkMemStore, - KeyIdMemStore, - Storage, - CoreDocument -} from "@iota/identity-wasm/node"; - -/** Demonstrate how to create a ZK DID JWK Document */ -export async function createDidJwkZk(){ - - const storage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore()); - const document = await CoreDocument.newDidJwkZk( - storage, - ProofAlgorithm.BLS12381_SHA256, - ) - - console.log(JSON.stringify(document, null, 2)); -} diff --git a/bindings/wasm/lib/jose/jws_algorithm.ts b/bindings/wasm/lib/jose/jws_algorithm.ts index 4d832b174f..809979449a 100644 --- a/bindings/wasm/lib/jose/jws_algorithm.ts +++ b/bindings/wasm/lib/jose/jws_algorithm.ts @@ -32,9 +32,11 @@ export const enum JwsAlgorithm { NONE = "none", /** EdDSA signature algorithms */ EdDSA = "EdDSA", - + /** ML-DSA-44 */ MLDSA44 = "ML-DSA-44", - MLDSA65 = "ML-DSA-65", + /** ML-DSA-65 */ + MLDSA65 = "ML-DSA-65", + /** ML-DSA-87 */ MLDSA87 = "ML-DSA-87", } diff --git a/bindings/wasm/rust-toolchain.toml b/bindings/wasm/rust-toolchain.toml index 825d39b571..eb46cc977d 100644 --- a/bindings/wasm/rust-toolchain.toml +++ b/bindings/wasm/rust-toolchain.toml @@ -1,5 +1,6 @@ [toolchain] -channel = "stable" +# @itsyaasir - Update to latest stable version when wasm-bindgen is updated +channel = "1.81" components = ["rustfmt"] targets = ["wasm32-unknown-unknown"] profile = "minimal" diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index 9ff047db38..c899724c4e 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -1021,7 +1021,6 @@ impl WasmIotaDocument { .generate_method_pqc(&storage_clone, KeyType::from(keyType), alg, fragment.as_deref(), scope) .await .wasm_result()?; - web_sys::console::log_1(&method_fragment.clone().into()); Ok(JsValue::from(method_fragment)) }); Ok(promise.unchecked_into()) diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 9f1fa4685b..6adf78845b 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -38,7 +38,7 @@ extern "C" { #[wasm_bindgen(method)] pub fn generate(this: &WasmJwkStorage, key_type: String, algorithm: String) -> PromiseJwkGenOutput; - + #[wasm_bindgen(method)] pub fn insert(this: &WasmJwkStorage, jwk: WasmJwk) -> PromiseString; @@ -57,7 +57,6 @@ extern "C" { #[async_trait::async_trait(?Send)] impl JwkStorage for WasmJwkStorage { - async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { let promise: Promise = Promise::resolve(&WasmJwkStorage::generate(self, key_type.into(), alg.name().to_owned())); let result: JsValueResult = JsFuture::from(promise).await.into(); diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 53e954d044..7a6dd1fad5 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -23,6 +23,3 @@ pub use key_id_storage::*; pub use method_digest::*; pub use signature_options::*; pub use wasm_storage::*; - - - diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index fa9934ed6c..a9d2c4ad9b 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -34,9 +34,6 @@ use serde_json::Value; pub static API_ENDPOINT: &str = "http://localhost"; pub static FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; -pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -pub static PATH_DID_FILE: &str = "./examples/2_pqc/server/.well-known/"; - pub type MemStorage = Storage; /// Creates a DID Document and publishes it in a new Alias Output. From 2307e1dbb95011084ca1f8205e347ecbd9d8973c Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 3 Apr 2025 16:47:34 +0200 Subject: [PATCH 135/163] update examples readme --- examples/README.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/examples/README.md b/examples/README.md index dfdcc518da..6dd319d1ca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,5 @@ +![banner](./../documentation/static/img/Banner/banner_identity.svg) + # IOTA Identity Examples This folder provides code examples to learn how IOTA Identity can be used. @@ -14,6 +16,8 @@ For instance, to run the example `0_create_did`, use: cargo run --release --example 0_create_did ``` +### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. + ## Basic Examples The following basic CRUD (Create, Read, Update, Delete) examples are available: @@ -47,19 +51,8 @@ The following advanced examples are available: | [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | | [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | | [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | +| [12_pq](./1_advanced/12_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. | +| [13_hybrid](./1_advanced/13_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature | #### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. -## PQC Examples - -The following Post-Quantum (PQ) and Post-Quantum/Traditional (PQ/T) hybrid examples are available: -| Name | Information | -| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| [0_pq](./2_pqc/0_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. This example uses did:jwk | -| [1_hybrid](./2_pqc/1_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature. This example uses [did:compositejwk](https://github.com/Cybersecurity-LINKS/did-compositejwk/blob/main/spec.md) | - -#### **Note**: The PQC examples are configured with the Issuer using the [did:web](https://w3c-ccg.github.io/did-method-web/) method. To run these examples, you must have a server instance that hosts the Issuer's DID Document. You can use the default server provided in the `example/2_pqc/server` folder, or configure one yourself. However, ensure that the following variables in `utils.rs` are correctly set to point to your server instance: -> ```rust -> pub static DID_URL: &str = "https://localhost:4443/.well-known/"; -> pub static PATH_DID_FILE: &str = "C:/Projects/did-web-server/.well-known/"; -> ``` From e0968813b32b26a1cf4dece8518edc77c16930ba Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 10:53:08 +0200 Subject: [PATCH 136/163] revert more changes --- bindings/wasm/Cargo.toml | 18 +- bindings/wasm/examples/src/main.ts | 1 + bindings/wasm/package-lock.json | 1929 ++++++++--------- bindings/wasm/package.json | 8 +- .../jwt_credential_validator_hybrid.rs | 2 +- bindings/wasm/src/jose/jwk.rs | 3 +- examples/utils/utils.rs | 3 - identity_credential/src/lib.rs | 2 +- identity_resolver/Cargo.toml | 1 - identity_storage/src/key_storage/memstore.rs | 2 - package-lock.json | 6 + 11 files changed, 958 insertions(+), 1017 deletions(-) create mode 100644 package-lock.json diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index ae6897f752..1d8f9219a1 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -32,15 +32,25 @@ serde_json = { version = "1.0", default-features = false } serde_repr = { version = "0.1", default-features = false } # Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads. tokio = { version = "1.29", default-features = false, features = ["sync"] } -wasm-bindgen = { version = "=0.2.92", features = ["serde-serialize"] } -wasm-bindgen-futures = { version = "=0.4", default-features = false } - +wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] } +wasm-bindgen-futures = { version = "0.4", default-features = false } zkryptium = "0.2.2" [dependencies.identity_iota] path = "../../identity_iota" default-features = false -features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus", "pqc", "hybrid"] +features = [ + "client", + "revocation-bitmap", + "resolver", + "domain-linkage", + "sd-jwt", + "status-list-2021", + "jpt-bbs-plus", + "sd-jwt-vc", + "pqc", + "hybrid" +] [dev-dependencies] rand = "0.8.5" diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index c60082cc46..c88ee419c0 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -1,5 +1,6 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 + import { createIdentity } from "./0_basic/0_create_did"; import { updateIdentity } from "./0_basic/1_update_did"; import { resolveIdentity } from "./0_basic/2_resolve_did"; diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index 6ad97fcc84..69abb45ab4 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@iota/identity-wasm", - "version": "1.3.1", - "lockfileVersion": 3, + "version": "1.5.0", + "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@iota/identity-wasm", - "version": "1.3.1", + "version": "1.5.0", "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", @@ -44,32 +44,11 @@ "@iota/sdk-wasm": "^1.0.4" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz", + "integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg==", "dev": true, - "dependencies": { - "@babel/types": "^7.26.0" - }, "bin": { "parser": "bin/babel-parser.js" }, @@ -77,19 +56,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -112,9 +78,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", - "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -123,16 +89,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~4.0.0", - "http-signature": "~1.4.0", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -140,6 +106,20 @@ "node": ">= 6" } }, + "node_modules/@cypress/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -250,9 +230,9 @@ "license": "Unlicense" }, "node_modules/@iota/sdk-wasm": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@iota/sdk-wasm/-/sdk-wasm-1.1.3.tgz", - "integrity": "sha512-piyl0B6gcoo7mbmX3QUCyEYtqk6UoCS2cqBYiV7FFz3fmT2DPcQJmcaDvW0nmNh5BbRR9MhPkp3MEerPm6mezA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@iota/sdk-wasm/-/sdk-wasm-1.0.4.tgz", + "integrity": "sha512-sS+9avq4GFgFbDYNX2+sn8H3+rFYRhJiB7aa22T+xIixt4/VuMaHkWCzZSTnTUXeY92M7D0ABYKCF+OlHSLzxg==", "peer": true, "dependencies": { "class-transformer": "^0.5.1", @@ -269,45 +249,49 @@ "fsevents": "^2.3.2" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, + "node_modules/@iota/sdk-wasm/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "side-channel": "^1.0.4" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, "peer": true, "engines": { @@ -315,31 +299,20 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -352,18 +325,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@jsdoc/salty": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", - "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, "node_modules/@noble/ed25519": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", @@ -492,9 +453,9 @@ "dev": true }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", "dev": true }, "node_modules/@tsconfig/node12": { @@ -510,24 +471,24 @@ "dev": true }, "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, "dependencies": { "@types/ms": "*" } }, "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", + "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", "dev": true, "peer": true, "dependencies": { @@ -536,9 +497,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, "peer": true, "dependencies": { @@ -547,16 +508,16 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true, "peer": true }, "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, "node_modules/@types/json5": { @@ -566,35 +527,45 @@ "dev": true, "optional": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", "dev": true }, "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", "dev": true, "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" + "@types/linkify-it": "*", + "@types/mdurl": "*" } }, "node_modules/@types/mdast": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", - "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", "dev": true, "dependencies": { - "@types/unist": "^2" + "@types/unist": "*" } }, "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, "node_modules/@types/mocha": { @@ -604,26 +575,23 @@ "dev": true }, "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, "node_modules/@types/node": { - "version": "22.8.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz", - "integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==", - "dependencies": { - "undici-types": "~6.19.8" - } + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" }, "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", "dependencies": { "@types/node": "*", - "form-data": "^4.0.0" + "form-data": "^3.0.0" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -633,15 +601,15 @@ "dev": true }, "node_modules/@types/sizzle": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, "node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, "node_modules/@types/yauzl": { @@ -661,73 +629,73 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, "peer": true, "dependencies": { @@ -735,9 +703,9 @@ } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, "peer": true, "dependencies": { @@ -745,79 +713,79 @@ } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", "dev": true, "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, @@ -836,9 +804,9 @@ "peer": true }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -847,14 +815,21 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, - "dependencies": { - "acorn": "^8.11.0" - }, "engines": { "node": ">=0.4.0" } @@ -955,7 +930,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ansi-styles": { "version": "4.3.0", @@ -973,9 +949,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -1072,9 +1048,9 @@ } }, "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, "node_modules/asynckit": { @@ -1101,9 +1077,9 @@ } }, "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "node_modules/bail": { @@ -1183,9 +1159,9 @@ } }, "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", "dev": true, "engines": { "node": ">=0.6" @@ -1201,15 +1177,12 @@ } }, "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/blob-util": { @@ -1235,12 +1208,12 @@ } }, "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "dependencies": { - "fill-range": "^7.1.1" + "fill-range": "^7.0.1" }, "engines": { "node": ">=8" @@ -1253,9 +1226,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "dev": true, "funding": [ { @@ -1265,18 +1238,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ], "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" }, "bin": { "browserslist": "cli.js" @@ -1357,18 +1326,12 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1387,9 +1350,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", "dev": true, "funding": [ { @@ -1399,10 +1362,6 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ], "peer": true @@ -1509,9 +1468,9 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, "peer": true, "engines": { @@ -1561,9 +1520,9 @@ } }, "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", "dev": true, "dependencies": { "string-width": "^4.2.0" @@ -1592,28 +1551,14 @@ } }, "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" + "wrap-ansi": "^7.0.0" } }, "node_modules/collect-all": { @@ -1866,13 +1811,13 @@ } }, "node_modules/cypress": { - "version": "13.15.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.1.tgz", - "integrity": "sha512-DwUFiKXo4lef9kA0M4iEhixFqoqp2hw8igr0lTqafRb9qtU3X0XGxKbkSYsUFdkrAkphc7MPDxoNPhk5pj9PVg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^3.0.4", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -1911,8 +1856,7 @@ "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", + "tmp": "~0.2.1", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -1963,6 +1907,142 @@ "cypress-multi-reporters": "^1.5.0" } }, + "node_modules/cypress-parallel/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cypress-parallel/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cypress-parallel/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cypress-parallel/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress-parallel/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress-parallel/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress-parallel/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress-parallel/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress-parallel/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/cypress-parallel/node_modules/yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress-parallel/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cypress/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1991,18 +2071,18 @@ } }, "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", "dev": true }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "^2.1.3" + "ms": "2.1.2" }, "engines": { "node": ">=6.0" @@ -2014,12 +2094,15 @@ } }, "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decode-named-character-reference": { @@ -2044,22 +2127,6 @@ "node": ">=4.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2105,17 +2172,17 @@ } }, "node_modules/dmd": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.2.3.tgz", - "integrity": "sha512-SIEkjrG7cZ9GWZQYk/mH+mWtcRPly/3ibVuXO/tP/MFoWz6KiRK77tSMq6YQBPl7RljPtXPQ/JhxbNuCdi1bNw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.1.0.tgz", + "integrity": "sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==", "dev": true, "dependencies": { "array-back": "^6.2.2", "cache-point": "^2.0.0", "common-sequence": "^2.0.2", "file-set": "^4.0.2", - "handlebars": "^4.7.8", - "marked": "^4.3.0", + "handlebars": "^4.7.7", + "marked": "^4.0.12", "object-get": "^2.1.1", "reduce-flatten": "^3.0.1", "reduce-unique": "^2.0.1", @@ -2168,9 +2235,9 @@ "license": "Unlicense" }, "node_modules/electron-to-chromium": { - "version": "1.5.50", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz", - "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==", + "version": "1.4.304", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.304.tgz", + "integrity": "sha512-6c8M+ojPgDIXN2NyfGn8oHASXYnayj+gSEnGeLMKb9zjsySeVB/j7KkNAAG9yDcv8gNlhvFg5REa1N/kQU6pgA==", "dev": true, "peer": true }, @@ -2199,9 +2266,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dev": true, "peer": true, "dependencies": { @@ -2226,47 +2293,25 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true, - "engines": { - "node": ">=0.12" - }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true, "peer": true }, "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, "engines": { "node": ">=6" @@ -2421,9 +2466,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2443,9 +2488,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2498,9 +2543,9 @@ } }, "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2565,9 +2610,9 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2603,18 +2648,6 @@ "node": ">= 8" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fs-then-native": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", @@ -2631,9 +2664,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "optional": true, "os": [ @@ -2644,12 +2677,9 @@ } }, "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -2661,18 +2691,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", + "function-bind": "^1.1.1", + "has": "^1.0.3", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" + "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2715,7 +2741,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -2795,21 +2820,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "node_modules/growl": { @@ -2822,13 +2836,13 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.2", + "neo-async": "^2.6.0", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -2842,6 +2856,17 @@ "uglify-js": "^3.1.4" } }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2851,21 +2876,10 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "engines": { "node": ">= 0.4" }, @@ -2884,17 +2898,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2905,14 +2908,14 @@ } }, "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.18.0" + "sshpk": "^1.14.1" }, "engines": { "node": ">=0.10" @@ -2948,9 +2951,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" @@ -2969,7 +2972,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3183,6 +3185,15 @@ "node": ">= 10.13.0" } }, + "node_modules/jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3211,25 +3222,25 @@ "dev": true }, "node_modules/jsdoc": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", - "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", "dev": true, "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -3240,9 +3251,9 @@ } }, "node_modules/jsdoc-api": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.2.0.tgz", - "integrity": "sha512-93YDnlm/OYTlLOFeNs4qAv0RBCJ0kGj67xQaWy8wrbk97Rw1EySitoOTHsTHXPEs3uyx2IStPKGrbE7LTnZXbA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-7.1.1.tgz", + "integrity": "sha512-0pkuPCzVXiqsDAsVrNFXCkHzlyNepBIDVtwwehry4RJAnZmXtlAz7rh8F9FRz53u3NeynGbex+bpYWwi8lE66A==", "dev": true, "dependencies": { "array-back": "^6.2.2", @@ -3250,7 +3261,7 @@ "collect-all": "^1.0.4", "file-set": "^4.0.2", "fs-then-native": "^2.0.0", - "jsdoc": "^4.0.0", + "jsdoc": "^3.6.10", "object-to-spawn-args": "^2.0.1", "temp-path": "^1.0.0", "walk-back": "^5.1.0" @@ -3260,37 +3271,22 @@ } }, "node_modules/jsdoc-parse": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.4.tgz", - "integrity": "sha512-MQA+lCe3ioZd0uGbyB3nDCDZcKgKC7m/Ivt0LgKZdUoOlMJxUWJQ3WI6GeyHp9ouznKaCjlp7CU9sw5k46yZTw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.1.0.tgz", + "integrity": "sha512-n/hDGQJa69IBun1yZAjqzV4gVR41+flZ3bIlm9fKvNe2Xjsd1/+zCo2+R9ls8LxtePgIWbpA1jU7xkB2lRdLLg==", "dev": true, "dependencies": { "array-back": "^6.2.2", - "find-replace": "^5.0.1", "lodash.omit": "^4.5.0", - "sort-array": "^5.0.0" + "lodash.pick": "^4.4.0", + "reduce-extract": "^1.0.0", + "sort-array": "^4.1.4", + "test-value": "^3.0.0" }, "engines": { "node": ">=12" } }, - "node_modules/jsdoc-parse/node_modules/find-replace": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", - "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", - "dev": true, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@75lb/nature": "latest" - }, - "peerDependenciesMeta": { - "@75lb/nature": { - "optional": true - } - } - }, "node_modules/jsdoc-to-markdown": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-7.1.1.tgz", @@ -3362,7 +3358,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", @@ -3419,12 +3416,12 @@ } }, "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "dependencies": { - "uc.micro": "^2.0.0" + "uc.micro": "^1.0.1" } }, "node_modules/listr2": { @@ -3523,6 +3520,12 @@ "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", "dev": true }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3592,7 +3595,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3604,7 +3606,8 @@ "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/make-error": { "version": "1.3.6", @@ -3613,26 +3616,25 @@ "dev": true }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "dependencies": { "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" }, "bin": { - "markdown-it": "bin/markdown-it.mjs" + "markdown-it": "bin/markdown-it.js" } }, "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", + "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", "dev": true, "peerDependencies": { "@types/markdown-it": "*", @@ -3644,6 +3646,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -3652,9 +3655,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", + "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -3676,22 +3679,19 @@ } }, "node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, "node_modules/merge-stream": { @@ -3710,9 +3710,9 @@ } }, "node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", + "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", "dev": true, "funding": [ { @@ -3745,9 +3745,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", "dev": true, "funding": [ { @@ -3779,9 +3779,9 @@ } }, "node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", "dev": true, "funding": [ { @@ -3800,9 +3800,9 @@ } }, "node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", "dev": true, "funding": [ { @@ -3822,9 +3822,9 @@ } }, "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", "dev": true, "funding": [ { @@ -3842,9 +3842,9 @@ } }, "node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", "dev": true, "funding": [ { @@ -3860,13 +3860,14 @@ "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, "node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", "dev": true, "funding": [ { @@ -3886,9 +3887,9 @@ } }, "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", "dev": true, "funding": [ { @@ -3906,9 +3907,9 @@ } }, "node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", "dev": true, "funding": [ { @@ -3925,9 +3926,9 @@ } }, "node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", "dev": true, "funding": [ { @@ -3946,9 +3947,9 @@ } }, "node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", "dev": true, "funding": [ { @@ -3966,9 +3967,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", "dev": true, "funding": [ { @@ -3985,9 +3986,9 @@ } }, "node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", "dev": true, "funding": [ { @@ -4007,9 +4008,9 @@ } }, "node_modules/micromark-util-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", "dev": true, "funding": [ { @@ -4023,9 +4024,9 @@ ] }, "node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", "dev": true, "funding": [ { @@ -4039,9 +4040,9 @@ ] }, "node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", "dev": true, "funding": [ { @@ -4058,9 +4059,9 @@ } }, "node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", "dev": true, "funding": [ { @@ -4077,9 +4078,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", + "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", "dev": true, "funding": [ { @@ -4098,9 +4099,9 @@ } }, "node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", "dev": true, "funding": [ { @@ -4120,9 +4121,9 @@ } }, "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", "dev": true, "funding": [ { @@ -4136,9 +4137,9 @@ ] }, "node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", "dev": true, "funding": [ { @@ -4152,12 +4153,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.3", + "braces": "^3.0.2", "picomatch": "^2.3.1" }, "engines": { @@ -4214,10 +4215,13 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" } @@ -4235,18 +4239,6 @@ "node": ">= 8" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -4317,17 +4309,6 @@ "node": ">=6" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/mocha/node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -4367,7 +4348,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -4408,6 +4388,12 @@ "node": ">=10" } }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/mocha/node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -4417,15 +4403,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4478,9 +4455,9 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -4497,9 +4474,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true, "peer": true }, @@ -4531,12 +4508,9 @@ "dev": true }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "engines": { - "node": ">= 0.4" - }, + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4683,9 +4657,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true, "peer": true }, @@ -4737,10 +4711,16 @@ "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "dev": true }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "dependencies": { "end-of-stream": "^1.1.0", @@ -4748,29 +4728,21 @@ } }, "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.0.4" }, "engines": { "node": ">=0.6" @@ -4779,6 +4751,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4820,6 +4798,43 @@ "node": ">=8.10.0" } }, + "node_modules/reduce-extract": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", + "integrity": "sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==", + "dev": true, + "dependencies": { + "test-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reduce-extract/node_modules/array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", + "dev": true, + "dependencies": { + "typical": "^2.6.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/reduce-extract/node_modules/test-value": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", + "integrity": "sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==", + "dev": true, + "dependencies": { + "array-back": "^1.0.2", + "typical": "^2.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reduce-flatten": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", @@ -4876,15 +4891,15 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", "peer": true }, "node_modules/remark-parse": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -4920,13 +4935,19 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/requizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", "dev": true, "dependencies": { - "lodash": "^4.17.21" + "lodash": "^4.17.14" } }, "node_modules/restore-cursor": { @@ -4982,9 +5003,9 @@ } }, "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -5029,9 +5050,9 @@ "dev": true }, "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -5047,9 +5068,12 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -5098,6 +5122,7 @@ "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", @@ -5106,17 +5131,13 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5152,33 +5173,34 @@ } }, "node_modules/sort-array": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-5.0.0.tgz", - "integrity": "sha512-Sg9MzajSGprcSrMIxsXyNT0e0JB47RJRfJspC+7co4Z5BdNsNl8FmWI+lXEpyKq+vkMG6pHgAhqyCO+bkDTfFQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", + "integrity": "sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==", "dev": true, "dependencies": { - "array-back": "^6.2.2", - "typical": "^7.1.1" + "array-back": "^5.0.0", + "typical": "^6.0.1" }, "engines": { - "node": ">=12.17" - }, - "peerDependencies": { - "@75lb/nature": "^0.1.1" - }, - "peerDependenciesMeta": { - "@75lb/nature": { - "optional": true - } + "node": ">=10" + } + }, + "node_modules/sort-array/node_modules/array-back": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", + "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", + "dev": true, + "engines": { + "node": ">=10" } }, "node_modules/sort-array/node_modules/typical": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-7.2.0.tgz", - "integrity": "sha512-W1+HdVRUl8fS3MZ9ogD51GOb46xMmhAZzR0WPw5jcgIZQJVvkddYzAl4YTU6g5w33Y1iRQLdIi2/1jhi2RNL0g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", + "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", "dev": true, "engines": { - "node": ">=12.17" + "node": ">=10" } }, "node_modules/source-map": { @@ -5229,7 +5251,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "dependencies": { "array-back": "^1.0.2" @@ -5358,6 +5379,12 @@ "node": ">=4" } }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -5369,20 +5396,20 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", + "minipass": "^3.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">= 10" } }, "node_modules/temp-path": { @@ -5392,14 +5419,14 @@ "dev": true }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", + "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -5411,17 +5438,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.14", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" }, "engines": { "node": ">= 10.13.0" @@ -5446,20 +5473,20 @@ } }, "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", "dev": true, "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "peer": true, "dependencies": { @@ -5520,24 +5547,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/tldts": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.58.tgz", - "integrity": "sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==", - "dev": true, - "dependencies": { - "tldts-core": "^6.1.58" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.58", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.58.tgz", - "integrity": "sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==", - "dev": true - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -5560,15 +5569,27 @@ } }, "node_modules/tough-cookie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "dependencies": { - "tldts": "^6.1.32" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=16" + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" } }, "node_modules/tr46": { @@ -5576,19 +5597,10 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", "dev": true, "funding": { "type": "github", @@ -5673,14 +5685,14 @@ } }, "node_modules/ts-mocha/node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "optional": true, "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.2", + "json5": "^1.0.1", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } @@ -5695,9 +5707,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -5747,12 +5759,12 @@ } }, "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", + "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", "dev": true, "dependencies": { - "json5": "^2.2.2", + "json5": "^2.2.1", "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, @@ -5761,9 +5773,9 @@ } }, "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, "node_modules/tunnel-agent": { @@ -5785,9 +5797,9 @@ "dev": true }, "node_modules/txm": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/txm/-/txm-8.2.0.tgz", - "integrity": "sha512-ALNu1KIbUMXlsl/UacjfhbtG4CWK07JYtf0KNf8aXhoixmtgMj2MdOit5er6V8qh5eTIvtB6+AexIQMLZhSz6Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/txm/-/txm-8.1.0.tgz", + "integrity": "sha512-mVDmoN13jYX3igNcnS+TEJJmMIRLjn0wch/wOI23z5IkCKiw9xinv1WkugB55j57W8MfuEk/psVVO4BWMfZxfA==", "dev": true, "dependencies": { "async": "^3.2.1", @@ -5805,9 +5817,9 @@ } }, "node_modules/txm/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", + "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", "dev": true, "engines": { "node": ">=12" @@ -5833,6 +5845,7 @@ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", @@ -5854,6 +5867,7 @@ "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", "dev": true, + "license": "MIT", "dependencies": { "handlebars": "^4.7.7" }, @@ -5866,6 +5880,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5875,6 +5890,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5886,9 +5902,9 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5905,15 +5921,15 @@ "dev": true }, "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", + "integrity": "sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==", "dev": true, "optional": true, "bin": { @@ -5924,16 +5940,11 @@ } }, "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", + "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", "dev": true }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -5954,9 +5965,9 @@ } }, "node_modules/unist-util-stringify-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0" @@ -5967,9 +5978,9 @@ } }, "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -5985,9 +5996,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "dev": true, "funding": [ { @@ -5997,19 +6008,15 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" } ], "peer": true, "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "escalade": "^3.1.1", + "picocolors": "^1.0.0" }, "bin": { - "update-browserslist-db": "cli.js" + "browserslist-lint": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -6024,6 +6031,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -6072,9 +6089,9 @@ } }, "node_modules/vfile": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", - "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", + "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -6088,9 +6105,9 @@ } }, "node_modules/vfile-message": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", - "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", + "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -6105,41 +6122,43 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/vscode-textmate": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/walk-back": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.1.tgz", - "integrity": "sha512-e/FRLDVdZQWFrAzU6Hdvpm7D7m2ina833gIKLptQykRK49mmCYHLHq7UqjPDbxbKLZkTkW1rFqbengdE3sLfdw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", + "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", "dev": true, "engines": { "node": ">=12.17" } }, "node_modules/wasm-opt": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.4.0.tgz", - "integrity": "sha512-wIsxxp0/FOSphokH4VOONy1zPkVREQfALN+/JTvJPK8gFSKbsmrcfECu2hT7OowqPfb4WEMSMceHgNL0ipFRyw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wasm-opt/-/wasm-opt-1.3.0.tgz", + "integrity": "sha512-24+IOboX4Sav0bI8Krwf0Y6dnpN4KxYtqpl0qWt86qVLsmayUqx1KMBrJTlQWNC+/dsqzQJjK6QvxNJsZYOgJg==", "dev": true, "hasInstallScript": true, "dependencies": { - "node-fetch": "^2.6.9", - "tar": "^6.1.13" + "node-fetch": "^2.6.7", + "tar": "^6.1.11" }, "bin": { "wasm-opt": "bin/wasm-opt.js" } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, "peer": true, "dependencies": { @@ -6156,34 +6175,35 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.96.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", - "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", "dev": true, "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", + "graceful-fs": "^4.2.9", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^3.1.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -6306,38 +6326,18 @@ "dev": true }, "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs-parser": { "version": "20.2.4", @@ -6363,18 +6363,6 @@ "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs-unparser/node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6384,80 +6372,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -6722,6 +6636,19 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, + "@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + }, + "@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "requires": { + "@noble/hashes": "1.6.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 004f2f2292..652f802fa0 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@iota/identity-wasm", - "version": "1.3.1", + "version": "1.5.0", "description": "WASM bindings for IOTA Identity - A Self Sovereign Identity Framework implementing the DID and VC standards from W3C. To be used in Javascript/Typescript", "repository": { "type": "git", @@ -19,7 +19,7 @@ "build:examples:web": "tsc --project ./examples/tsconfig.web.json && node ./build/replace_paths ./examples/tsconfig.web.json ./examples/dist resolve", "build": "npm run build:web && npm run build:nodejs && npm run build:docs", "example:node": "ts-node --project tsconfig.node.json -r tsconfig-paths/register ./examples/src/main.ts", - "test": "npm run test:unit:node && npm run test:readme && npm run test:node && test:browser:parallel", + "test": "npm run test:unit:node && npm run test:readme && npm run test:node && npm run test:browser:parallel", "test:node": "ts-mocha -r tsconfig-paths/register -p tsconfig.node.json ./examples/src/tests/*.ts --parallel --jobs 4 --retries 3 --timeout 180000 --exit", "test:browser:parallel": "cypress-parallel -s test:browser -t 4 -d cypress/e2e -a '\"--quiet\"'", "test:browser:parallel:firefox": "cypress-parallel -s test:browser:firefox -t 4 -d cypress/e2e -a '\"--quiet\"'", @@ -57,7 +57,8 @@ "node/*" ], "devDependencies": { - "@transmute/did-key-ed25519": "0.3.0-unstable.9", + "@digitalcredentials/did-method-key": "^2.0.3", + "@types/jsonwebtoken": "^9.0.7", "@types/mocha": "^9.1.0", "big-integer": "^1.6.51", "copy-webpack-plugin": "^7.0.0", @@ -81,6 +82,7 @@ "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", + "jose": "^5.9.6", "node-fetch": "^2.6.7" }, "peerDependencies": { diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs index 68683fc1b3..818c4e9ece 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -1,4 +1,4 @@ -// Copyright 2020-2024 IOTA Stiftung +// Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 use identity_iota::core::Object; diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 0c50c67c8e..25489c367c 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -24,7 +24,8 @@ use crate::jose::WasmJwsAlgorithm; use core::ops::Deref; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[wasm_bindgen(js_name = Jwk, inspectable)] +#[serde(transparent)] +#[wasm_bindgen(js_name = Jwk)] pub struct WasmJwk(pub(crate) Jwk); #[wasm_bindgen(js_class = Jwk)] diff --git a/examples/utils/utils.rs b/examples/utils/utils.rs index a9d2c4ad9b..a79a74312e 100644 --- a/examples/utils/utils.rs +++ b/examples/utils/utils.rs @@ -1,8 +1,5 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/* - * Modifications Copyright 2024 Fondazione LINKS. - */ use std::path::PathBuf; diff --git a/identity_credential/src/lib.rs b/identity_credential/src/lib.rs index b7b02daf25..236329ab4c 100644 --- a/identity_credential/src/lib.rs +++ b/identity_credential/src/lib.rs @@ -38,4 +38,4 @@ pub use error::Result; pub use sd_jwt_payload; #[cfg(feature = "sd-jwt-vc")] -pub use sd_jwt_payload_rework as sd_jwt_v2; \ No newline at end of file +pub use sd_jwt_payload_rework as sd_jwt_v2; diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index ec7ff818fa..fe763ddb6c 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -40,6 +40,5 @@ revocation-bitmap = ["identity_credential/revocation-bitmap", "identity_iota_cor # Enables the IOTA integration for the resolver. iota = ["dep:identity_iota_core"] - [lints] workspace = true diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 4e81a9e6d7..f23667e4c7 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -30,10 +30,8 @@ use super::KeyStorageError; use super::KeyStorageErrorKind; use super::KeyStorageResult; use super::KeyType; - use crate::key_storage::JwkStorage; - /// The map from key ids to JWKs. type JwkKeyStore = HashMap; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..8af0c9dcc2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "pq-identity.rs", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 333c059a821b84dd806128eae0129e50fa5bf93e Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 11:54:43 +0200 Subject: [PATCH 137/163] Update CompositeAlgId to latest draft version --- bindings/wasm/lib/jose/composite_jwk.ts | 8 ++-- examples/1_advanced/13_hybrid.rs | 4 +- identity_jose/src/jwk/composite_jwk.rs | 23 +++++------ identity_jose/src/jws/algorithm.rs | 38 +++++++------------ identity_jose/src/jws/decoder.rs | 10 ++--- .../src/storage/did_jwk_document_ext.rs | 4 +- .../src/storage/hybrid_jws_document_ext.rs | 8 ++-- 7 files changed, 42 insertions(+), 53 deletions(-) diff --git a/bindings/wasm/lib/jose/composite_jwk.ts b/bindings/wasm/lib/jose/composite_jwk.ts index 7e1ff57904..54ba4b13b8 100644 --- a/bindings/wasm/lib/jose/composite_jwk.ts +++ b/bindings/wasm/lib/jose/composite_jwk.ts @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 export const enum CompositeAlgId { - /** DER encoded value in hex = 060B6086480186FA6B50080103 */ - IdMldsa44Ed25519Sha512 = "id-MLDSA44-Ed25519-SHA512", + /** DER encoded value in hex = 060B6086480186FA6B5008013E */ + IdMldsa44Ed25519 = "id-MLDSA44-Ed25519", - /** /// DER encoded value in hex = 060B6086480186FA6B5008010A */ - IdMldsa65Ed25519Sha512 = "id-MLDSA65-Ed25519-SHA512" + /** /// DER encoded value in hex = 060B6086480186FA6B50080147 */ + IdMldsa65Ed25519 = "id-MLDSA65-Ed25519" } \ No newline at end of file diff --git a/examples/1_advanced/13_hybrid.rs b/examples/1_advanced/13_hybrid.rs index 2057482727..d357ae45f8 100644 --- a/examples/1_advanced/13_hybrid.rs +++ b/examples/1_advanced/13_hybrid.rs @@ -118,7 +118,7 @@ async fn main() -> anyhow::Result<()> { &client, &mut secret_manager_issuer, &storage_issuer, - CompositeAlgId::IdMldsa65Ed25519Sha512, + CompositeAlgId::IdMldsa65Ed25519, ) .await?; @@ -134,7 +134,7 @@ async fn main() -> anyhow::Result<()> { &client, &mut secret_manager_holder, &storage_holder, - CompositeAlgId::IdMldsa65Ed25519Sha512, + CompositeAlgId::IdMldsa65Ed25519, ) .await?; diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index 460bb72fa4..5dd19ff360 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -5,23 +5,24 @@ use std::str::FromStr; use crate::jwk::Jwk; -/// Mame of algorithms used to generate the hybrid signature. Values taken from [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-02#name-domain-separators). +/// Mame of algorithms used to generate the hybrid signature. Values taken from +/// [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators). #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub enum CompositeAlgId { - /// DER encoded value in hex = 060B6086480186FA6B50080103 - #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] - IdMldsa44Ed25519Sha512, - /// DER encoded value in hex = 060B6086480186FA6B5008010A - #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] - IdMldsa65Ed25519Sha512, + /// DER encoded value in hex = 060B6086480186FA6B5008013E + #[serde(rename = "id-MLDSA44-Ed25519")] + IdMldsa44Ed25519, + /// DER encoded value in hex = 060B6086480186FA6B50080147 + #[serde(rename = "id-MLDSA65-Ed25519")] + IdMldsa65Ed25519, } impl CompositeAlgId { /// Returns the JWS algorithm as a `str` slice. pub const fn name(self) -> &'static str { match self { - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", - Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", + Self::IdMldsa44Ed25519 => "id-MLDSA44-Ed25519", + Self::IdMldsa65Ed25519 => "id-MLDSA65-Ed25519", } } } @@ -65,8 +66,8 @@ impl FromStr for CompositeAlgId { fn from_str(string: &str) -> std::result::Result { match string { - "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), - "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), + "id-MLDSA44-Ed25519" => Ok(Self::IdMldsa44Ed25519), + "id-MLDSA65-Ed25519" => Ok(Self::IdMldsa65Ed25519), #[cfg(not(feature = "custom_alg"))] &_ => Err(crate::error::Error::JwsAlgorithmParsingError), } diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index 9fceaa64b6..83b8c1e714 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -48,8 +48,6 @@ pub enum JwsAlgorithm { NONE, /// EdDSA signature algorithms EdDSA, - - //TODO: PQC - new PQ JwsAlgorithms /// JSON Web Signature Algorithm for ML-DSA-44 /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-algorithm-family) #[serde(rename = "ML-DSA-44")] @@ -74,7 +72,6 @@ pub enum JwsAlgorithm { /// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-algorithm-famil) #[serde(rename = "SLH-DSA-SHA2-128f")] SLH_DSA_SHA2_128f, - ///SLH_DSA_SHAKE_128f #[serde(rename = "SLH-DSA-SHAKE-128f")] SLH_DSA_SHAKE_128f, @@ -102,17 +99,16 @@ pub enum JwsAlgorithm { ///SLH-DSA-SHAKE-256f #[serde(rename = "SLH-DSA-SHAKE-256f")] SLH_DSA_SHAKE_256f, - ///FALCON512 FALCON512, ///FALCON1024 FALCON1024, - ///id-MLDSA44-Ed25519-SHA512 - #[serde(rename = "id-MLDSA44-Ed25519-SHA512")] - IdMldsa44Ed25519Sha512, - ///id-MLDSA65-Ed25519-SHA512 - #[serde(rename = "id-MLDSA65-Ed25519-SHA512")] - IdMldsa65Ed25519Sha512, + ///id-MLDSA44-Ed25519 + #[serde(rename = "id-MLDSA44-Ed25519")] + IdMldsa44Ed25519, + ///id-MLDSA65-Ed25519 + #[serde(rename = "id-MLDSA65-Ed25519")] + IdMldsa65Ed25519, /// Custom algorithm #[cfg(feature = "custom_alg")] #[serde(untagged)] @@ -159,8 +155,8 @@ impl JwsAlgorithm { Self::SLH_DSA_SHAKE_256f, Self::FALCON512, Self::FALCON1024, - Self::IdMldsa44Ed25519Sha512, - Self::IdMldsa65Ed25519Sha512, + Self::IdMldsa44Ed25519, + Self::IdMldsa65Ed25519, ]; /// Returns the JWS algorithm as a `str` slice. @@ -188,7 +184,6 @@ impl JwsAlgorithm { Self::SLH_DSA_SHA2_128s => "SLH-DSA-SHA2-128s", Self::SLH_DSA_SHAKE_128s => "SLH-DSA-SHAKE-128s", Self::SLH_DSA_SHA2_128f => "SLH-DSA-SHA2-128f", - Self::SLH_DSA_SHAKE_128f => "SLH-DSA-SHAKE-128f", Self::SLH_DSA_SHA2_192s => "SLH-DSA-SHA2-192s", Self::SLH_DSA_SHAKE_192s => "SLH-DSA-SHAKE-192s", @@ -198,12 +193,10 @@ impl JwsAlgorithm { Self::SLH_DSA_SHAKE_256s => "SLH-DSA-SHAKE-256s", Self::SLH_DSA_SHA2_256f => "SLH-DSA-SHA2-256f", Self::SLH_DSA_SHAKE_256f => "SLH-DSA-SHAKE-256f", - Self::FALCON512 => "FALCON512", Self::FALCON1024 => "FALCON1024", - - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512", - Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512", + Self::IdMldsa44Ed25519 => "id-MLDSA44-Ed25519", + Self::IdMldsa65Ed25519 => "id-MLDSA65-Ed25519", } } @@ -243,8 +236,8 @@ impl JwsAlgorithm { Self::SLH_DSA_SHAKE_256f => "SLH-DSA-SHAKE-256f".to_string(), Self::FALCON512 => "FALCON512".to_string(), Self::FALCON1024 => "FALCON1024".to_string(), - Self::IdMldsa44Ed25519Sha512 => "id-MLDSA44-Ed25519-SHA512".to_string(), - Self::IdMldsa65Ed25519Sha512 => "id-MLDSA65-Ed25519-SHA512".to_string(), + Self::IdMldsa44Ed25519 => "id-MLDSA44-Ed25519".to_string(), + Self::IdMldsa65Ed25519 => "id-MLDSA65-Ed25519".to_string(), Self::Custom(name) => name.clone(), } } @@ -276,7 +269,6 @@ impl FromStr for JwsAlgorithm { "SLH-DSA-SHA2-128s" => Ok(Self::SLH_DSA_SHA2_128s), "SLH-DSA-SHAKE-128s" => Ok(Self::SLH_DSA_SHAKE_128s), "SLH-DSA-SHA2-128f" => Ok(Self::SLH_DSA_SHA2_128f), - "SLH-DSA-SHAKE-128f" => Ok(Self::SLH_DSA_SHAKE_128f), "SLH-DSA-SHA2-192s" => Ok(Self::SLH_DSA_SHA2_192s), "SLH-DSA-SHAKE-192s" => Ok(Self::SLH_DSA_SHAKE_192s), @@ -286,12 +278,10 @@ impl FromStr for JwsAlgorithm { "SLH-DSA-SHAKE-256s" => Ok(Self::SLH_DSA_SHAKE_256s), "SLH-DSA-SHA2-256f" => Ok(Self::SLH_DSA_SHA2_256f), "SLH-DSA-SHAKE-256f" => Ok(Self::SLH_DSA_SHAKE_256f), - "FALCON512" => Ok(Self::FALCON512), "FALCON1024" => Ok(Self::FALCON1024), - - "id-MLDSA44-Ed25519-SHA512" => Ok(Self::IdMldsa44Ed25519Sha512), - "id-MLDSA65-Ed25519-SHA512" => Ok(Self::IdMldsa65Ed25519Sha512), + "id-MLDSA44-Ed25519" => Ok(Self::IdMldsa44Ed25519), + "id-MLDSA65-Ed25519" => Ok(Self::IdMldsa65Ed25519), #[cfg(feature = "custom_alg")] value => Ok(Self::Custom(value.to_string())), #[cfg(not(feature = "custom_alg"))] diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 5605122cc7..5752fb47af 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -212,10 +212,9 @@ impl<'a> JwsValidationItem<'a> { // Extract and validate alg from the protected header. let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?; let (t_alg, pq_alg, signing_input, traditional_signature_len) = match alg { - JwsAlgorithm::IdMldsa44Ed25519Sha512 => { - //TODO: hybrid - DER OID + JwsAlgorithm::IdMldsa44Ed25519 => { let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E ]; input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); ( @@ -225,9 +224,9 @@ impl<'a> JwsValidationItem<'a> { crypto::signatures::ed25519::Signature::LENGTH, ) } - JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + JwsAlgorithm::IdMldsa65Ed25519 => { let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47, ]; input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); ( @@ -246,7 +245,6 @@ impl<'a> JwsValidationItem<'a> { let extracted_signature_t = &decoded_signature[..traditional_signature_len]; let extracted_signature_pq = &decoded_signature[traditional_signature_len..]; - // println!("SIGN 2 = {:#?}", signing_input); // Construct verification input let input1 = VerificationInput { alg: t_alg, diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 74e8adcf8f..5617a89aa8 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -209,13 +209,13 @@ impl DidJwkDocumentExt for CoreDocument { use crate::KeyId; let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { - CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + CompositeAlgId::IdMldsa44Ed25519 => ( KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_44, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), - CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + CompositeAlgId::IdMldsa65Ed25519 => ( KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_65, KeyType::from_static_str("Ed25519"), diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 568d1594d1..331f23effc 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -53,13 +53,13 @@ macro_rules! generate_method_hybrid_for_document_type { I: KeyIdStorage, { let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { - CompositeAlgId::IdMldsa44Ed25519Sha512 => ( + CompositeAlgId::IdMldsa44Ed25519 => ( KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_44, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), - CompositeAlgId::IdMldsa65Ed25519Sha512 => ( + CompositeAlgId::IdMldsa65Ed25519 => ( KeyType::from_static_str("ML-DSA"), JwsAlgorithm::ML_DSA_65, KeyType::from_static_str("Ed25519"), @@ -310,7 +310,7 @@ impl JwkDocumentExtHybrid for CoreDocument { .map_err(|err| Error::EncodingError(err.into()))?; let signing_input = match alg { - JwsAlgorithm::IdMldsa44Ed25519Sha512 => { + JwsAlgorithm::IdMldsa44Ed25519 => { //TODO: hybrid - DER OID let mut input = vec![ 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, @@ -322,7 +322,7 @@ impl JwkDocumentExtHybrid for CoreDocument { ); input } - JwsAlgorithm::IdMldsa65Ed25519Sha512 => { + JwsAlgorithm::IdMldsa65Ed25519 => { let mut input = vec![ 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, ]; From 22c5f7ccd90e8759266aab821b604e421bb80597 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 11:55:11 +0200 Subject: [PATCH 138/163] add missing modification license header --- bindings/wasm/src/credential/jwt_credential_validation/mod.rs | 3 +++ .../src/validator/jwt_credential_validation/mod.rs | 3 +++ .../src/validator/jwt_presentation_validation/mod.rs | 3 +++ identity_storage/src/storage/error.rs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/bindings/wasm/src/credential/jwt_credential_validation/mod.rs b/bindings/wasm/src/credential/jwt_credential_validation/mod.rs index 5021f82f1a..c0c3adc88b 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/mod.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/mod.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ mod decoded_jwt_credential; mod jwt_credential_validator; diff --git a/identity_credential/src/validator/jwt_credential_validation/mod.rs b/identity_credential/src/validator/jwt_credential_validation/mod.rs index 6a31084313..2bf943ad31 100644 --- a/identity_credential/src/validator/jwt_credential_validation/mod.rs +++ b/identity_credential/src/validator/jwt_credential_validation/mod.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ //! Contains functionality for validating credentials issued as JWTs. mod decoded_jwt_credential; diff --git a/identity_credential/src/validator/jwt_presentation_validation/mod.rs b/identity_credential/src/validator/jwt_presentation_validation/mod.rs index 76000bd223..adf42dc3a9 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/mod.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/mod.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ mod decoded_jwt_presentation; mod error; diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index 17c0506cdb..3560ab2755 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -1,5 +1,8 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ use crate::key_id_storage::KeyIdStorageError; use crate::key_id_storage::MethodDigestConstructionError; From ccba2f9d1340f71b2d2cd42baf777b38c96bff54 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 12:27:30 +0200 Subject: [PATCH 139/163] Update README and dependencies for oqs verifier --- identity_credential/Cargo.toml | 2 +- identity_jose/Cargo.toml | 2 +- identity_pqc_verifier/README.md | 2 +- identity_pqc_verifier/src/oqs_verifier.rs | 43 +++++++---------------- 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index cf7336f5c5..4e365e60a7 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -71,7 +71,7 @@ domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema", "dep:futures"] -jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] +jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token", "dep:futures"] hybrid = ["credential", "validator"] [lints] diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 71e941434d..cf2ad90ec6 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -13,7 +13,7 @@ description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true identity_core = { version = "=1.5.0", path = "../identity_core" } -iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "sha"] } json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } diff --git a/identity_pqc_verifier/README.md b/identity_pqc_verifier/README.md index 994cdee1b6..676bfc724c 100644 --- a/identity_pqc_verifier/README.md +++ b/identity_pqc_verifier/README.md @@ -1,4 +1,4 @@ IOTA Identity - PQC Verifier === -This crate implements a `JwsVerifier` capable of verifying ML-DSA signatures. \ No newline at end of file +This crate implements a `JwsVerifier` capable of verifying Post-Quantum (PQ) signatures, based on liboqs PQ signatures implementation. \ No newline at end of file diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index cd81a160af..fb4ae32990 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -28,7 +28,7 @@ impl OQSVerifier { .with_custom_message("could not decode 'pub' parameter from jwk") })?; - oqs::init(); //TODO: check what this function does + oqs::init(); let scheme = Sig::new(alg).map_err(|_| { SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified) @@ -57,44 +57,25 @@ impl OQSVerifier { mod tests { use oqs::sig::{Algorithm, Sig}; - #[test] - fn sig_and_verify(){ - // generate random keypair - + fn test_sig_and_verify(){ oqs::init(); - let scheme = Sig::new(Algorithm::Dilithium2).unwrap(); - + let scheme = Sig::new(Algorithm::MlDsa44).unwrap(); let (pk, sk) = scheme.keypair().unwrap(); - - println!("Public key: {:?}", pk); - - let message = b"prova"; - + let message = b"test_message"; let signature = scheme.sign(message, &sk).unwrap(); - println!("Signature: {:?}", signature); - - scheme.verify(message, &signature, &pk).unwrap(); - - assert!(true) + assert!(scheme.verify(message, &signature, &pk).is_ok()); } - const NOBLE_PUB_KEY: &[u8] = &[209,131,252,5,180,196,210,99,214,113,221,246,97,94,45,90,147,37,94,209,79,92,237,114,51,54,130,154,79,33,98,96,191,71,153,75,190,253,62,58,150,79,104,91,158,229,105,8,225,119,116,50,82,210,92,222,12,144,125,161,148,117,26,164,102,25,66,244,178,254,2,102,55,30,117,234,109,151,178,151,210,207,39,49,117,70,253,178,235,237,183,189,145,197,116,235,132,236,147,66,175,236,234,202,65,218,212,200,75,101,214,178,48,207,197,174,75,224,183,47,3,2,14,192,88,205,166,60,19,219,67,158,147,148,229,16,85,244,8,123,51,44,42,0,18,179,136,193,62,255,100,129,228,14,125,53,191,127,8,32,109,223,155,223,98,60,46,159,40,134,144,253,121,89,107,74,32,58,88,181,237,6,76,223,215,193,87,212,210,190,236,243,72,107,209,66,245,239,133,99,145,174,59,151,2,93,88,145,240,105,46,158,213,210,204,71,209,221,62,105,113,75,189,63,120,125,151,124,58,79,133,104,200,87,63,33,60,142,21,37,3,102,254,135,210,13,208,155,32,222,162,63,127,17,10,60,46,228,234,59,75,57,97,117,229,245,89,9,55,52,69,73,223,50,70,70,16,64,118,51,105,196,18,96,247,43,24,139,146,137,67,31,101,82,42,214,177,41,125,216,202,5,212,40,97,244,145,180,236,206,170,124,192,126,227,254,86,45,6,221,200,235,143,228,162,88,248,155,36,169,45,147,111,2,249,186,179,123,150,221,223,176,131,170,216,129,241,222,5,195,255,241,1,39,129,119,100,217,175,249,18,3,131,89,231,46,70,21,147,72,89,162,194,25,113,78,213,130,66,218,42,149,228,205,36,59,108,57,189,171,200,57,246,37,6,143,159,84,129,205,132,132,146,41,237,76,93,103,1,3,130,61,116,171,16,82,255,107,113,223,89,37,69,184,161,83,11,92,160,201,154,5,10,200,17,197,39,25,91,32,129,143,6,226,146,172,97,191,197,204,29,128,226,60,17,9,171,178,78,148,17,30,96,223,77,180,156,31,236,59,236,168,238,113,252,59,209,154,227,149,204,167,63,210,45,75,173,247,126,247,216,232,74,216,15,158,109,89,88,17,54,117,165,4,52,2,10,148,27,185,24,124,182,175,145,38,45,224,236,106,94,156,195,120,200,94,114,17,62,64,188,50,11,161,82,115,12,147,222,94,90,123,227,88,129,58,140,138,120,241,168,35,42,78,236,9,196,213,180,60,229,99,127,252,154,61,45,174,1,148,92,215,9,130,50,239,143,181,3,34,13,79,51,63,49,157,64,86,121,88,213,225,116,159,155,48,155,84,199,254,148,247,93,48,119,249,71,241,109,144,82,8,14,116,4,41,146,112,20,251,30,211,8,123,164,59,217,231,4,66,70,103,247,224,232,37,131,212,51,212,82,131,166,252,184,190,115,238,165,212,37,53,156,173,97,254,90,43,179,232,165,38,99,72,174,70,247,163,73,162,225,233,73,98,89,225,128,148,172,206,211,249,0,160,242,242,121,5,148,202,251,62,17,220,89,101,80,183,204,186,241,28,17,231,43,2,71,167,92,111,240,166,110,0,47,188,75,251,35,72,148,115,23,157,35,165,132,252,232,57,12,60,41,104,192,57,143,227,45,199,13,146,33,176,234,160,246,21,65,195,231,98,255,146,118,50,49,11,119,175,129,12,63,52,162,90,143,152,32,58,137,61,169,23,247,85,228,85,157,238,57,61,40,220,183,240,171,169,241,246,112,59,255,221,9,19,204,39,80,105,96,85,199,183,91,137,180,52,29,194,5,32,45,51,30,197,26,28,81,130,141,28,138,35,123,236,36,202,72,0,40,109,12,61,175,252,60,216,42,178,95,12,73,188,129,191,235,69,0,153,88,10,103,248,210,20,238,197,94,213,186,32,55,80,194,128,199,45,83,199,51,237,18,88,217,38,102,231,4,106,208,209,170,54,103,189,97,148,141,218,114,196,178,40,66,238,75,43,130,86,183,10,39,228,225,69,91,39,11,93,181,61,157,149,2,32,23,67,78,212,117,45,26,226,21,220,169,201,41,114,44,130,240,47,152,115,54,243,240,98,186,195,120,110,151,182,10,19,24,71,168,162,216,225,5,68,39,12,189,26,37,196,25,151,61,242,247,121,107,32,6,72,44,147,186,133,50,93,255,157,186,147,161,163,219,99,35,236,180,226,11,40,154,56,132,19,106,252,209,231,85,234,80,244,211,234,128,123,99,130,210,16,51,13,65,212,230,196,113,152,47,206,7,166,37,129,105,80,40,85,12,246,204,117,162,178,65,17,185,7,135,216,102,108,125,233,133,227,96,100,142,100,16,189,148,47,17,87,47,154,170,55,9,178,118,231,61,253,138,204,161,195,95,99,211,120,67,255,26,134,227,191,126,112,16,72,148,240,198,215,40,29,229,32,167,153,78,105,56,166,56,23,28,175,132,68,198,172,123,192,192,245,201,203,223,108,207,192,180,105,148,123,149,88,229,80,134,130,190,195,124,107,184,28,106,76,234,125,104,250,159,134,226,23,74,66,110,132,164,32,130,121,223,89,5,8,55,196,210,91,44,196,17,209,86,53,65,193,179,108,118,87,232,92,2,4,214,191,235,127,189,53,209,145,40,234,27,70,105,50,129,147,39,153,42,211,146,76,243,203,146,86,7,116,195,77,27,24,106,243,190,3,23,174,109,219,52,45,190,245,47,164,11,201,63,170,3,117,59,59,254,232,197,248,79,40,157,131,3,30,30,103,179,167,25,11,207,122,215,31,190,0,109,81,151,4,200,209,178,48,29,115,103,159,211,54,220,124,70,205,216,178,215,114,37,249,169,40,252,137,7,21,222,205,211,57,117,210,17,253,1,100,80,33,184,110,205,29,7,225,235,122,53,214,95,169,215,185,53,24,199,155]; - const NOBLE_MSG: &[u8] = b"pq-idenity.rs"; - const NOBLE_SIGNATURE: &[u8] = &[141,47,4,215,33,246,2,43,59,126,229,138,192,102,222,241,120,4,117,30,149,66,16,134,102,28,160,233,112,241,187,171,114,202,3,252,199,61,39,69,107,64,81,26,220,52,23,34,173,156,62,184,110,116,105,44,158,190,112,146,115,116,50,76,102,245,132,34,127,218,40,133,194,120,5,91,11,36,36,120,162,172,25,158,9,232,162,158,120,29,19,134,220,139,62,2,173,225,163,108,85,166,142,72,18,134,116,4,45,143,232,32,134,107,10,249,252,149,156,136,24,10,21,184,109,132,239,193,100,37,206,215,19,23,253,112,193,97,147,117,236,253,49,230,50,225,2,219,40,91,189,131,206,55,70,18,170,160,32,178,95,244,163,228,34,232,66,240,106,56,44,212,220,221,152,56,169,247,40,215,231,200,142,81,137,68,84,248,216,238,237,201,253,36,150,97,152,189,183,238,51,194,123,102,129,115,10,176,184,25,151,152,107,182,91,3,57,4,234,102,85,67,219,76,197,42,156,210,70,189,245,154,25,55,146,25,254,40,124,31,222,237,165,203,136,133,188,201,62,61,76,137,238,209,16,242,45,130,239,29,161,57,73,48,73,43,91,246,131,56,34,48,101,55,182,160,8,158,226,64,32,144,104,82,166,71,153,246,230,216,112,50,186,83,156,98,4,2,32,28,85,5,235,190,5,9,225,31,117,90,235,49,222,8,223,218,229,173,106,196,67,202,198,6,17,239,146,30,31,197,232,198,215,206,219,246,250,54,57,217,195,179,142,165,177,2,48,217,42,42,213,37,134,252,2,65,234,182,26,53,191,39,93,137,75,25,54,223,68,1,53,74,40,214,83,9,255,89,140,94,111,197,83,225,244,10,64,185,193,44,10,121,231,231,131,185,84,83,181,181,222,118,224,30,116,121,124,56,170,86,108,94,131,56,53,126,5,150,75,48,229,142,91,36,21,137,17,81,186,52,162,192,230,9,113,129,85,224,140,72,14,252,154,153,52,62,49,38,131,81,98,224,24,54,42,59,139,234,215,170,107,69,74,196,142,66,75,5,253,124,34,239,152,234,10,79,90,203,139,3,49,159,6,10,93,92,46,160,7,197,155,107,190,183,251,135,54,159,153,124,100,94,173,36,62,147,155,88,197,21,66,61,94,87,238,14,240,65,86,192,20,217,93,33,28,131,103,74,226,30,60,158,50,132,169,79,140,8,120,1,20,165,30,23,211,46,103,122,212,212,63,213,5,23,79,37,42,100,239,7,246,110,119,253,152,141,228,164,138,165,114,163,149,198,169,130,195,210,229,130,45,107,108,244,11,168,129,110,179,130,203,240,53,221,217,213,183,97,215,58,8,139,48,26,184,8,98,103,173,185,186,242,203,107,145,115,128,154,251,38,226,96,201,233,39,237,234,80,118,100,94,52,202,114,150,56,10,207,66,61,103,15,7,231,62,153,214,196,207,1,183,199,120,88,141,60,104,147,208,123,242,245,141,106,39,227,25,41,24,9,154,195,252,104,118,243,194,230,114,234,119,32,18,104,186,54,210,218,167,100,86,13,83,135,114,163,166,218,200,162,78,241,167,215,172,113,199,197,68,21,92,28,87,36,89,121,19,255,104,14,147,14,76,132,50,116,24,139,199,154,31,220,32,76,185,130,217,219,178,0,248,132,186,133,192,208,119,26,235,164,90,240,128,207,15,18,160,46,86,236,79,108,90,232,64,27,15,186,189,139,81,193,105,220,9,248,239,50,219,237,186,218,66,229,25,194,28,4,78,229,179,36,252,142,20,200,67,118,33,68,51,187,177,165,227,18,155,133,140,145,144,33,44,36,174,5,74,201,171,70,213,129,123,53,212,240,173,91,238,170,62,195,110,208,14,185,195,201,213,113,26,107,26,164,168,71,209,21,104,241,41,52,190,8,223,222,66,8,183,154,121,207,172,35,98,204,133,146,221,225,126,170,80,133,192,28,106,194,149,228,217,220,99,140,43,135,45,154,243,118,196,33,241,0,48,128,90,82,169,218,157,170,31,228,134,249,218,12,22,150,191,171,4,27,213,97,85,255,215,71,25,253,236,70,43,16,178,155,44,121,161,177,102,46,213,217,136,53,230,97,225,89,201,87,107,147,231,133,12,34,76,122,175,189,233,216,78,183,1,95,207,53,131,111,177,63,150,18,78,8,248,19,35,224,52,196,165,37,107,80,131,62,55,251,34,182,112,233,136,87,116,95,235,50,200,226,14,24,165,143,114,53,37,186,4,237,159,9,88,49,29,79,12,84,143,224,165,254,72,53,100,64,157,74,10,13,122,222,208,170,42,123,211,16,200,73,145,202,85,145,82,241,110,90,200,221,187,109,125,251,29,50,157,141,169,46,178,102,58,235,48,68,28,59,102,119,237,177,95,232,180,34,241,65,88,69,188,52,176,75,99,145,65,189,47,63,57,140,82,142,210,52,44,252,228,201,163,6,220,91,247,170,90,205,248,198,138,105,190,214,168,191,225,253,32,254,54,125,15,9,27,75,26,5,115,132,23,252,81,160,78,112,185,245,127,229,83,164,21,105,207,29,3,58,164,220,85,102,27,87,99,92,44,170,189,139,129,155,1,22,50,54,176,119,122,64,231,251,82,26,113,14,32,150,72,122,159,223,115,232,52,236,41,5,224,188,248,195,8,139,76,200,214,188,39,169,73,90,150,206,218,97,192,228,215,115,202,42,253,121,10,47,191,145,35,145,66,120,36,105,11,84,132,31,98,154,48,17,102,58,162,43,90,139,62,146,90,92,138,69,231,20,96,236,233,6,227,58,21,160,253,27,138,167,39,37,181,167,131,1,146,237,31,134,66,119,144,206,27,109,183,117,86,208,241,180,168,234,143,100,26,166,67,168,75,52,221,128,51,111,118,25,168,88,240,61,97,180,112,2,162,34,198,136,147,126,159,116,174,109,60,25,141,11,51,217,35,121,115,241,179,56,90,135,138,5,126,223,252,105,155,126,173,61,198,121,112,168,111,147,85,80,87,43,210,242,102,14,106,156,3,55,31,29,219,149,154,161,67,193,37,70,126,167,64,21,53,144,202,53,177,246,223,6,205,107,5,180,93,44,63,96,189,183,22,55,217,121,64,41,103,94,185,247,100,41,130,99,200,248,68,62,178,8,174,55,34,241,121,239,137,53,229,187,152,30,53,12,182,128,167,243,72,114,250,19,161,212,183,151,3,140,78,222,251,86,112,66,188,16,111,188,106,215,126,228,24,100,141,65,58,52,188,170,82,41,155,80,182,95,73,237,133,137,246,15,60,88,43,250,112,110,25,38,173,58,88,218,101,234,45,52,159,73,58,40,94,20,107,75,181,142,151,182,117,90,3,229,96,195,59,57,153,141,135,235,213,177,167,151,153,164,216,46,121,236,221,229,170,207,3,118,17,138,214,151,132,91,209,154,200,122,50,6,47,121,84,186,62,51,136,124,189,174,24,248,35,137,158,11,79,170,183,160,61,47,179,52,170,15,8,5,156,63,53,140,139,77,76,165,97,143,253,255,192,219,128,6,57,15,134,3,19,214,107,206,145,74,104,58,248,176,197,100,73,173,189,16,240,74,115,198,51,43,100,185,63,125,83,200,187,123,118,81,23,168,55,163,92,13,193,56,12,176,147,84,151,243,105,230,61,78,166,225,34,132,221,201,225,57,203,162,98,181,62,206,137,205,100,162,181,123,41,16,40,242,212,212,18,162,166,170,26,222,19,122,125,22,62,206,182,62,234,12,59,96,41,61,215,254,84,144,20,53,229,75,25,196,28,118,3,19,161,252,186,238,147,53,204,10,153,144,48,45,148,33,7,216,98,209,236,194,147,76,111,80,100,225,184,75,163,98,167,25,169,58,151,183,60,71,200,104,208,161,192,70,184,99,176,216,128,28,25,205,252,106,176,107,13,157,77,32,234,60,114,119,111,237,250,179,138,159,160,90,143,82,29,183,117,190,122,4,245,164,125,145,24,128,31,215,226,74,234,59,122,184,247,222,64,96,113,169,250,190,176,68,158,215,231,173,221,41,59,229,120,184,218,139,127,202,145,154,157,85,219,48,102,193,141,148,245,212,94,231,47,171,203,78,147,95,207,102,51,180,27,244,59,251,128,171,222,227,141,85,226,244,7,44,206,123,108,69,85,193,158,98,228,5,18,192,70,35,86,255,5,136,214,117,192,200,97,251,171,183,233,163,30,172,68,231,41,175,84,94,166,126,91,145,157,196,190,145,197,144,176,247,97,53,139,185,131,211,106,14,248,167,119,112,243,234,64,19,202,135,115,245,86,89,3,118,44,123,70,64,69,202,158,218,132,84,183,41,87,106,241,152,76,238,208,106,179,154,231,163,236,43,139,162,113,189,216,247,177,139,54,145,19,193,207,225,180,67,108,16,110,60,88,238,35,109,246,53,60,134,200,21,151,117,131,31,174,223,32,227,244,105,53,207,195,25,249,181,177,80,122,101,200,48,1,237,79,242,172,134,164,222,66,28,173,130,86,153,193,154,60,86,107,30,157,222,109,203,120,102,229,27,184,1,27,201,225,18,15,59,117,11,62,36,243,214,17,38,51,229,156,189,66,138,80,220,27,164,170,115,147,181,254,178,160,249,197,228,18,2,165,143,67,226,214,217,69,210,116,79,15,3,71,232,237,100,228,106,141,94,181,118,85,17,125,238,45,182,125,69,155,255,31,207,210,193,115,98,92,66,131,241,173,147,246,125,107,105,21,150,8,32,175,72,168,21,21,222,141,236,117,183,98,44,2,124,166,150,232,132,226,41,144,96,27,234,3,61,113,69,52,18,171,1,29,41,33,29,189,146,155,15,37,252,33,174,143,32,95,157,39,142,98,194,86,76,224,124,173,237,23,217,9,111,115,244,22,100,230,229,100,183,96,76,146,105,90,194,61,165,89,206,57,187,212,60,154,224,223,102,19,243,161,141,195,208,200,43,111,226,107,162,13,116,170,181,197,128,230,9,65,34,31,240,167,151,107,90,130,50,94,140,251,176,20,174,247,23,129,78,175,85,233,124,216,45,147,76,125,123,180,216,238,217,188,153,109,88,223,150,124,9,39,180,83,59,99,230,108,235,182,213,8,48,235,110,236,152,190,226,214,158,190,17,140,175,219,139,133,15,134,244,229,254,89,115,104,6,234,210,158,235,201,125,50,95,39,183,102,215,101,171,146,84,167,120,150,150,43,42,114,41,178,214,22,61,239,214,28,84,46,220,17,26,45,51,73,106,162,197,205,209,212,218,223,9,35,39,82,83,93,95,103,110,111,115,129,155,169,180,188,190,196,209,216,243,34,41,66,90,99,100,116,133,144,159,181,185,190,219,239,250,7,13,18,29,69,71,72,76,91,93,157,163,174,177,226,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,34,50,66]; - #[test] - fn verify_noble_pq_signature(){ - + fn test_sig_and_invalid_verify(){ oqs::init(); - let scheme = Sig::new(Algorithm::MlDsa44).unwrap(); - - let pk = scheme.public_key_from_bytes(NOBLE_PUB_KEY).unwrap(); - let signature = scheme.signature_from_bytes(NOBLE_SIGNATURE).unwrap(); - - scheme.verify(NOBLE_MSG, &signature, &pk).unwrap(); - - assert!(true) + let scheme = Sig::new(Algorithm::MlDsa87).unwrap(); + let (pk, sk) = scheme.keypair().unwrap(); + let message = b"test_message"; + let wrong_message = b"wrong_message"; + let signature = scheme.sign(message, &sk).unwrap(); + assert!(scheme.verify(wrong_message, &signature, &pk).is_err()); } } From 5c866851d0388f0074b5bd9519a1dcb8f721566e Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 12:31:15 +0200 Subject: [PATCH 140/163] fix --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8af0c9dcc2..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "pq-identity.rs", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 8ce32d047b120d2ec8c3a23ec7437c9173233d85 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 15:16:53 +0200 Subject: [PATCH 141/163] update to Jwktype Algorithm Key Pair (AKP) --- bindings/wasm/lib/jose/jwk_type.ts | 4 +- bindings/wasm/lib/jwk_storage.ts | 4 +- bindings/wasm/src/jose/jwk.rs | 25 +++++---- bindings/wasm/src/jose/types.rs | 4 +- .../src/jwk/{jwk_pq.rs => jwk_akp.rs} | 18 +++--- identity_jose/src/jwk/key.rs | 36 ++++-------- identity_jose/src/jwk/key_params.rs | 27 +++------ identity_jose/src/jwk/key_type.rs | 21 ++----- identity_jose/src/jwk/mod.rs | 4 +- identity_pqc_verifier/src/oqs_verifier.rs | 6 +- identity_storage/src/key_storage/memstore.rs | 55 ++++++++----------- 11 files changed, 84 insertions(+), 120 deletions(-) rename identity_jose/src/jwk/{jwk_pq.rs => jwk_akp.rs} (83%) diff --git a/bindings/wasm/lib/jose/jwk_type.ts b/bindings/wasm/lib/jose/jwk_type.ts index 50c21a3de2..820308bd3b 100644 --- a/bindings/wasm/lib/jose/jwk_type.ts +++ b/bindings/wasm/lib/jose/jwk_type.ts @@ -10,6 +10,6 @@ export const enum JwkType { Oct = "oct", /** Octet string key pairs. */ Okp = "OKP", - - MLDSA = "ML-DSA", + /** Algorithm key pair. */ + Akp = "AKP", } diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 94dfbcb1ae..b3ef404a6d 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -159,7 +159,7 @@ async function encodeJwk( }); } else if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.MLDSA87) { return new Jwk({ - "kty": JwkType.MLDSA, + "kty": JwkType.Akp, pub: x, priv: d, alg, @@ -191,7 +191,7 @@ function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { throw new Error("expected Okp params"); } } else if (jwk.alg()! === JwsAlgorithm.MLDSA44 || jwk.alg()! === JwsAlgorithm.MLDSA65 || jwk.alg()! === JwsAlgorithm.MLDSA87) { - const paramsPQ = jwk.paramsMldsa(); + const paramsPQ = jwk.paramsAkp(); if (paramsPQ) { const priv = paramsPQ.priv; diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 25489c367c..6b0b6a7b37 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -17,7 +17,7 @@ use crate::jose::WasmJwkParamsEc; use crate::jose::WasmJwkParamsOct; use crate::jose::WasmJwkParamsOkp; use crate::jose::WasmJwkParamsRsa; -use crate::jose::WasmJwkParamsMLDSA; +use crate::jose::WasmJwkParamsAkp; use crate::jose::WasmJwkType; use crate::jose::WasmJwkUse; use crate::jose::WasmJwsAlgorithm; @@ -168,11 +168,12 @@ impl WasmJwk { } } - #[wasm_bindgen(js_name = paramsMldsa)] - pub fn params_mldsa(&self) -> crate::error::Result> { - if let JwkParams::MLDSA(params_mldsa) = self.0.params() { + /// If this JWK is of kty AKP, returns those parameters. + #[wasm_bindgen(js_name = paramsAkp)] + pub fn params_akp(&self) -> crate::error::Result> { + if let JwkParams::Akp(params_akp) = self.0.params() { // WARNING: this does not validate the return type. Check carefully. - Ok(Some(JsValue::from_serde(params_mldsa).wasm_result()?.unchecked_into())) + Ok(Some(JsValue::from_serde(params_akp).wasm_result()?.unchecked_into())) } else { Ok(None) } @@ -215,7 +216,7 @@ impl_wasm_clone!(WasmJwk, Jwk); #[wasm_bindgen(typescript_custom_section)] const I_JWK: &'static str = r#" -type IJwkParams = IJwkEc | IJwkRsa | IJwkOkp | IJwkOct | IJwkMLDSA +type IJwkParams = IJwkEc | IJwkRsa | IJwkOkp | IJwkOct | IJwkAkp /** A JSON Web Key with EC params. */ export interface IJwkEc extends IJwk, JwkParamsEc { kty: JwkType.Ec @@ -232,8 +233,9 @@ export interface IJwkOkp extends IJwk, JwkParamsOkp { export interface IJwkOct extends IJwk, JwkParamsOct { kty: JwkType.Oct } -export interface IJwkMLDSA extends IJwk, JwkParamsPQ { - kty: JwkType.MLDSA +/** A JSON Web Key with AKP params. */ +export interface IJwkAkp extends IJwk, JwkParamsAkp { + kty: JwkType.Akp } "#; @@ -421,8 +423,11 @@ interface JwkParamsOct { #[wasm_bindgen(typescript_custom_section)] -const IJWK_PARAMS_PQ: &str = r#" -interface JwkParamsPQ { +const IJWK_PARAMS_AKP: &str = r#" +/** Parameters for Algorithm Key Pair (AKP). + * + * [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-06#name-algorithm-key-pair-type) */ +interface JwkParamsAkp { pub: string, priv?: string }"#; \ No newline at end of file diff --git a/bindings/wasm/src/jose/types.rs b/bindings/wasm/src/jose/types.rs index b9679da794..c48436a310 100644 --- a/bindings/wasm/src/jose/types.rs +++ b/bindings/wasm/src/jose/types.rs @@ -29,8 +29,8 @@ extern "C" { pub type WasmJwkParamsRsa; #[wasm_bindgen(typescript_type = "JwkParamsOct")] pub type WasmJwkParamsOct; - #[wasm_bindgen(typescript_type = "JwkParamsPQ")] - pub type WasmJwkParamsMLDSA; + #[wasm_bindgen(typescript_type = "JwkParamsAkp")] + pub type WasmJwkParamsAkp; #[wasm_bindgen(typescript_type = "CompositeAlgId")] pub type WasmCompositeAlgId; } diff --git a/identity_jose/src/jwk/jwk_pq.rs b/identity_jose/src/jwk/jwk_akp.rs similarity index 83% rename from identity_jose/src/jwk/jwk_pq.rs rename to identity_jose/src/jwk/jwk_akp.rs index b97f00594a..a5becf0399 100644 --- a/identity_jose/src/jwk/jwk_pq.rs +++ b/identity_jose/src/jwk/jwk_akp.rs @@ -3,20 +3,19 @@ // ============================================================================= -// Post-quantum algorithm key parameters +// Algorithm Key Pair (AKP) key parameters for Post-quantum algorithm // ============================================================================= use zeroize::Zeroize; - -// TODO: PQ - parameter for PQ keys +use super::JwkType; /// Parameters for Post-Quantum algorithm keys /// -/// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium) +/// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-06) #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize, Zeroize)] #[zeroize(drop)] -pub struct JwkParamsPQ { +pub struct JwkParamsAKP { /// The public key as a base64url-encoded value. #[serde(rename = "pub")] pub public: String, // Public Key @@ -25,8 +24,8 @@ pub struct JwkParamsPQ { pub private: Option, // Private Key } -impl JwkParamsPQ { - /// Creates new JWK OKP Params. +impl JwkParamsAKP { + /// Creates new JWK AKP Params. pub const fn new() -> Self { Self { public: String::new(), @@ -34,6 +33,11 @@ impl JwkParamsPQ { } } + /// Returns the key type `kty`. + pub const fn kty(&self) -> JwkType { + JwkType::Akp + } + /// Returns a clone with _all_ private key components unset. pub fn to_public(&self) -> Self { Self { diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index fddde6b412..a1ef5d947a 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -21,12 +21,11 @@ use crate::jwk::JwkParamsEc; use crate::jwk::JwkParamsOct; use crate::jwk::JwkParamsOkp; use crate::jwk::JwkParamsRsa; +use crate::jwk::JwkParamsAKP; use crate::jwk::JwkType; use crate::jwk::JwkUse; use crate::jwu::encode_b64; -use super::JwkParamsPQ; - /// A SHA256 JSON Web Key Thumbprint. pub type JwkThumbprintSha256 = [u8; SHA256_LEN]; @@ -345,23 +344,19 @@ impl Jwk { } } - /// Returns the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA` or `SLH-DSA`. - pub fn try_pq_params(&self) -> Result<&JwkParamsPQ> { + /// Returns the [`JwkParamsAkp`] in this JWK if it is of type `Akp`. + pub fn try_akp_params(&self) -> Result<&JwkParamsAKP> { match self.params() { - JwkParams::MLDSA(params) => Ok(params), - JwkParams::SLHDSA(params) => Ok(params), - JwkParams::FALCON(params) => Ok(params), - _ => Err(Error::KeyError("PQ")), + JwkParams::Akp(params) => Ok(params), + _ => Err(Error::KeyError("Akp")), } } - /// Returns a mutable reference to the [`JwkParamsPQ`] in this JWK if it is of type `ML-DSA` or `SLH-DSA`. - pub fn try_pq_params_mut(&mut self) -> Result<&mut JwkParamsPQ> { + /// Returns a mutable reference to the [`JwkParamsAkp`] in this JWK if it is of type `Akp`. + pub fn try_akp_params_mut(&mut self) -> Result<&mut JwkParamsAKP> { match self.params_mut() { - JwkParams::MLDSA(params) => Ok(params), - JwkParams::SLHDSA(params) => Ok(params), - JwkParams::FALCON(params) => Ok(params), - _ => Err(Error::KeyError("PQ")), + JwkParams::Akp(params) => Ok(params), + _ => Err(Error::KeyError("Akp")), } } @@ -413,14 +408,7 @@ impl Jwk { JwkParams::Okp(JwkParamsOkp { crv, x, .. }) => { format!(r#"{{"crv":"{crv}","kty":"{kty}","x":"{x}"}}"#) } - //TODO: PQ - thumbprint for PQ keys - JwkParams::MLDSA(JwkParamsPQ { public, .. }) => { - format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) - } - JwkParams::SLHDSA(JwkParamsPQ { public, .. }) => { - format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) - } - JwkParams::FALCON(JwkParamsPQ { public, .. }) => { + JwkParams::Akp(JwkParamsAKP { public, .. }) => { format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#) } } @@ -475,9 +463,7 @@ impl Jwk { JwkParams::Rsa(params) => params.is_private(), JwkParams::Oct(_) => true, JwkParams::Okp(params) => params.is_private(), - JwkParams::MLDSA(params) => params.is_private(), //TODO: PQ - is_private Jwk method - JwkParams::SLHDSA(params) => params.is_private(), - JwkParams::FALCON(params) => params.is_private(), + JwkParams::Akp(params) => params.is_private(), } } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index 36d4ccfd24..613f6a84ba 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -8,7 +8,7 @@ use zeroize::Zeroize; use super::BlsCurve; -use super::JwkParamsPQ; +use super::JwkParamsAKP; use crate::error::Error; use crate::error::Result; use crate::jwk::EcCurve; @@ -32,13 +32,8 @@ pub enum JwkParams { Oct(JwkParamsOct), /// Octet Key Pairs parameters. Okp(JwkParamsOkp), - - /// ML-DSA parameters - MLDSA(JwkParamsPQ), - /// SLH-DSA parameters - SLHDSA(JwkParamsPQ), - /// FALCON parameters - FALCON(JwkParamsPQ), + /// Algorithm Key Pair Type parameters + Akp(JwkParamsAKP), } impl JwkParams { @@ -49,9 +44,7 @@ impl JwkParams { JwkType::Rsa => Self::Rsa(JwkParamsRsa::new()), JwkType::Oct => Self::Oct(JwkParamsOct::new()), JwkType::Okp => Self::Okp(JwkParamsOkp::new()), - JwkType::MLDSA => Self::MLDSA(JwkParamsPQ::new()), - JwkType::SLHDSA => Self::SLHDSA(JwkParamsPQ::new()), - JwkType::FALCON => Self::FALCON(JwkParamsPQ::new()), + JwkType::Akp => Self::Akp(JwkParamsAKP::new()) } } @@ -62,9 +55,7 @@ impl JwkParams { Self::Rsa(inner) => inner.kty(), Self::Oct(inner) => inner.kty(), Self::Okp(inner) => inner.kty(), - Self::MLDSA(_) => JwkType::MLDSA, - Self::SLHDSA(_) => JwkType::SLHDSA, - Self::FALCON(_) => JwkType::FALCON, + Self::Akp(_) => JwkType::Akp } } @@ -77,9 +68,7 @@ impl JwkParams { Self::Ec(inner) => Some(Self::Ec(inner.to_public())), Self::Rsa(inner) => Some(Self::Rsa(inner.to_public())), Self::Oct(_) => None, - Self::MLDSA(inner) => Some(Self::MLDSA(inner.to_public())), - Self::SLHDSA(inner) => Some(Self::SLHDSA(inner.to_public())), - Self::FALCON(inner) => Some(Self::FALCON(inner.to_public())), + Self::Akp(inner) => Some(Self::Akp(inner.to_public())) } } @@ -90,9 +79,7 @@ impl JwkParams { Self::Ec(value) => value.is_public(), Self::Rsa(value) => value.is_public(), Self::Oct(value) => value.is_public(), - Self::MLDSA(value) => value.is_public(), - Self::SLHDSA(value) => value.is_public(), - Self::FALCON(value) => value.is_public(), + Self::Akp(value) => value.is_public() } } } diff --git a/identity_jose/src/jwk/key_type.rs b/identity_jose/src/jwk/key_type.rs index e753a0c994..bbb4f9ed55 100644 --- a/identity_jose/src/jwk/key_type.rs +++ b/identity_jose/src/jwk/key_type.rs @@ -26,19 +26,10 @@ pub enum JwkType { /// Octet string key pairs. #[serde(rename = "OKP")] Okp, - - //TODO: PQ - new JwkType - /// JSON Web Key Type for the ML-DSA Algorithm Family. - /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium#name-the-ml-dsa-key-type) - #[serde(rename = "ML-DSA")] - MLDSA, - /// JSON Web Key Type for the SLH-DSA Algorithm Family. - /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-sphincs-plus#name-the-slh-dsa-key-type) - #[serde(rename = "SLH-DSA")] - SLHDSA, - - ///Falcon - FALCON, + /// Algorithm Key Pair, JSON Web Key Type for the ML-DSA and SLH-DSA Algorithm Family. + /// [More Info] (https://datatracker.ietf.org/doc/html/draft-ietf-cose-dilithium-06#name-algorithm-key-pair-type) + #[serde(rename = "AKP")] + Akp, } impl JwkType { @@ -49,9 +40,7 @@ impl JwkType { Self::Rsa => "RSA", Self::Oct => "oct", Self::Okp => "OKP", - Self::MLDSA => "ML-DSA", - Self::SLHDSA => "SLH-DSA", - Self::FALCON => "FALCON", + Self::Akp => "AKP", } } } diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index 4c96589276..609004c5fb 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -9,7 +9,7 @@ mod curve; mod jwk_ext; -mod jwk_pq; +mod jwk_akp; mod key; mod key_operation; mod key_params; @@ -19,7 +19,7 @@ mod key_use; mod composite_jwk; pub use self::curve::*; -pub use self::jwk_pq::*; +pub use self::jwk_akp::*; pub use self::key::*; pub use self::key_operation::*; pub use self::key_params::*; diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index fb4ae32990..1f37f079ad 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use identity_jose::jwk::Jwk; -use identity_jose::jwk::JwkParamsPQ; +use identity_jose::jwk::JwkParamsAKP; use identity_jose::jws::SignatureVerificationError; use identity_jose::jws::SignatureVerificationErrorKind; use identity_jose::jws::VerificationInput; @@ -19,8 +19,8 @@ impl OQSVerifier { /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { // Obtain an ML-DSA-44 public key. - let params: &JwkParamsPQ = public_key - .try_pq_params() + let params: &JwkParamsAKP = public_key + .try_akp_params() .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; let pk = identity_jose::jwu::decode_b64(params.public.as_str()).map_err(|_| { diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index f23667e4c7..4a435e9465 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -317,13 +317,10 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: &JwsAlgorithm) -> } } -//TODO: PQ - #[cfg(feature = "pqc-liboqs")] mod pqc_liboqs { use std::str::FromStr; use async_trait::async_trait; - //use crypto::signatures::ed25519::SecretKey; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; @@ -391,7 +388,7 @@ mod pqc_liboqs { } let oqs_alg = check_pq_alg_compatibility(alg)?; - oqs::init(); //TODO: check what this function does + oqs::init(); let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) @@ -410,23 +407,23 @@ mod pqc_liboqs { let private = jwu::encode_b64(sk.into_vec()); let mut jwk_params = match alg { - JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::MLDSA), - JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_128f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_192s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_192s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_192f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_192f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_256s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_256s => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHA2_256f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::SLH_DSA_SHAKE_256f => JwkParams::new(JwkType::SLHDSA), - JwsAlgorithm::FALCON512 => JwkParams::new(JwkType::FALCON), - JwsAlgorithm::FALCON1024 => JwkParams::new(JwkType::FALCON), + JwsAlgorithm::ML_DSA_44 => JwkParams::new(JwkType::Akp), + JwsAlgorithm::ML_DSA_65 => JwkParams::new(JwkType::Akp), + JwsAlgorithm::ML_DSA_87 => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_128s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_128s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_128f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_128f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_192s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_192s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_192f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_192f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_256s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_256s => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHA2_256f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::SLH_DSA_SHAKE_256f => JwkParams::new(JwkType::Akp), + JwsAlgorithm::FALCON512 => JwkParams::new(JwkType::Akp), + JwsAlgorithm::FALCON1024 => JwkParams::new(JwkType::Akp), other => { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) @@ -436,11 +433,7 @@ mod pqc_liboqs { }; match jwk_params { - JwkParams::MLDSA(ref mut params) => { - params.public = public; - params.private = Some(private); - } - JwkParams::SLHDSA(ref mut params) => { + JwkParams::Akp(ref mut params) => { params.public = public; params.private = Some(private); } @@ -476,7 +469,7 @@ mod pqc_liboqs { let oqs_alg = check_pq_alg_compatibility(alg)?; - // Check that `kty` is `ML-DSA`or `SLH-DSA` or `FALCON`. + // Check that `kty` is `AKP`. match alg { JwsAlgorithm::ML_DSA_44 | JwsAlgorithm::ML_DSA_65 @@ -494,9 +487,9 @@ mod pqc_liboqs { | JwsAlgorithm::SLH_DSA_SHA2_256f | JwsAlgorithm::SLH_DSA_SHAKE_256f | JwsAlgorithm::FALCON512 - | JwsAlgorithm::FALCON1024 => public_key.try_pq_params().map_err(|err| { + | JwsAlgorithm::FALCON1024 => public_key.try_akp_params().map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected a Jwk with ML-DSA params in order to sign with {alg}")) + .with_custom_message(format!("expected a Jwk with AKP params in order to sign with {alg}")) .with_source(err) })?, other => { @@ -512,7 +505,7 @@ mod pqc_liboqs { .get(key_id) .ok_or_else(|| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound))?; - let params = jwk.try_pq_params().unwrap(); + let params = jwk.try_akp_params().unwrap(); let sk_bytes = params .private @@ -527,7 +520,7 @@ mod pqc_liboqs { .with_custom_message("unable to decode `d` param") .with_source(err) })?; - oqs::init(); //TODO: check what this function does + oqs::init(); let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) From fb41fd5ea499e4b80cebd833e0df3afc97779e84 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 7 Apr 2025 15:54:03 +0200 Subject: [PATCH 142/163] fix --- identity_storage/src/storage/hybrid_jws_document_ext.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 331f23effc..acc81c78ae 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -313,7 +313,7 @@ impl JwkDocumentExtHybrid for CoreDocument { JwsAlgorithm::IdMldsa44Ed25519 => { //TODO: hybrid - DER OID let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x03, + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E, ]; input.extend( crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) @@ -324,7 +324,7 @@ impl JwkDocumentExtHybrid for CoreDocument { } JwsAlgorithm::IdMldsa65Ed25519 => { let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x0A, + 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47, ]; input.extend( crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) From a24fad0e921aa0319ed865322695ab1271141449 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 09:07:45 +0200 Subject: [PATCH 143/163] remove old todo --- identity_storage/src/storage/error.rs | 2 +- identity_storage/src/storage/hybrid_jws_document_ext.rs | 1 - identity_verification/src/verification_method/material.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index 3560ab2755..aad90b2d12 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -29,7 +29,7 @@ pub enum JwkStorageDocumentError { NotPublicKeyJwk, /// Caused by the usage of a non-Composite method where a Composite method is expected. #[error("invalid method data format: expected compositePublicKey")] - NotCompositePublicKey, //TODO: hybrid - new error + NotCompositePublicKey, /// Caused by an invalid JWS algorithm. #[error("invalid JWS algorithm")] InvalidJwsAlgorithm, diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index acc81c78ae..bc0485d30c 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -311,7 +311,6 @@ impl JwkDocumentExtHybrid for CoreDocument { let signing_input = match alg { JwsAlgorithm::IdMldsa44Ed25519 => { - //TODO: hybrid - DER OID let mut input = vec![ 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E, ]; diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 478695db72..6dbc08eba2 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -32,7 +32,7 @@ pub enum MethodData { PublicKeyBase58(String), /// Verification Material in the JSON Web Key format. PublicKeyJwk(Jwk), - /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ //TODO: Hybrid - new MethodData + /// Verification Material containing two keys in JSON Web Key format, one traditional and one PQ CompositeJwk(CompositeJwk), /// Arbitrary verification material. #[serde(untagged)] From 4f8497793b038fe735ce1acf977912bf9bf873ca Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 09:23:45 +0200 Subject: [PATCH 144/163] revert changes --- bindings/wasm/lib/jwk_storage.ts | 142 +++++++------------------------ 1 file changed, 30 insertions(+), 112 deletions(-) diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index b3ef404a6d..235abcc8ce 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -1,15 +1,11 @@ -/* - * Modifications Copyright 2024 Fondazione LINKS. - */ import * as ed from "@noble/ed25519"; -import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx, JwkStoragePQ } from "~identity_wasm"; +import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx } from "~identity_wasm"; import { EdCurve, JwkType, JwsAlgorithm } from "./jose"; -import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; type Ed25519PrivateKey = Uint8Array; type Ed25519PublicKey = Uint8Array; -export class JwkMemStore implements JwkStorage, JwkStoragePQ{ +export class JwkMemStore implements JwkStorage { /** The map from key identifiers to Jwks. */ private _keys: Map; @@ -18,47 +14,10 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ this._keys = new Map(); } - public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { - if (keyType !== JwkMemStore.mldsaKeyType()) { - throw new Error(`unsupported key type ${keyType}`); - } - - const seed = new TextEncoder().encode(randomKeyId()) - let keys; - if (algorithm === JwsAlgorithm.MLDSA44) { - keys = ml_dsa44.keygen(seed); - } else if(algorithm === JwsAlgorithm.MLDSA65) { - keys = ml_dsa65.keygen(seed); - } else if(algorithm === JwsAlgorithm.MLDSA87) { - keys = ml_dsa87.keygen(seed); - } else { - throw new Error(`unsupported algorithm`); - } - - const keyId = randomKeyId(); - const jwk = await encodeJwk(keys.secretKey, keys.publicKey, algorithm); - - if(jwk == undefined) - throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); - - this._keys.set(keyId, jwk); - - const publicJWK = jwk?.toPublic(); - if (!publicJWK) { - throw new Error(`JWK is not a public key`); - } - return new JwkGenOutput(keyId, publicJWK); - - } - public static ed25519KeyType(): string { return "Ed25519"; } - public static mldsaKeyType(): string { - return "ML-DSA"; - } - private _get_key(keyId: string): Jwk | undefined { return this._keys.get(keyId); } @@ -74,11 +33,7 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ const keyId = randomKeyId(); const privKey: Ed25519PrivateKey = ed.utils.randomPrivateKey(); - const publicKey = await ed.getPublicKey(privKey); - - const jwk = await encodeJwk(privKey, publicKey, algorithm); - if(jwk == undefined) - throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); + const jwk = await encodeJwk(privKey, algorithm); this._keys.set(keyId, jwk); @@ -91,7 +46,7 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ } public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { - if (publicKey.alg()! !== JwsAlgorithm.EdDSA) { + if (publicKey.alg() !== JwsAlgorithm.EdDSA) { throw new Error("unsupported JWS algorithm"); } else { if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) { @@ -136,82 +91,45 @@ export class JwkMemStore implements JwkStorage, JwkStoragePQ{ public count(): number { return this._keys.size; } - - } // Encodes a Ed25519 keypair into a Jwk. -async function encodeJwk( - privateKey: Uint8Array, - publicKey: Uint8Array, - alg: JwsAlgorithm -): Promise { - const x = encodeB64(publicKey); - const d = encodeB64(privateKey); - - if (alg === JwsAlgorithm.EdDSA) { - return new Jwk({ - kty: JwkType.Okp, - crv: "Ed25519", - d, - x, - alg, - }); - } else if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.MLDSA87) { - return new Jwk({ - "kty": JwkType.Akp, - pub: x, - priv: d, - alg, - }); - } - - return undefined; +async function encodeJwk(privateKey: Ed25519PrivateKey, alg: JwsAlgorithm): Promise { + const publicKey = await ed.getPublicKey(privateKey); + let x = encodeB64(publicKey); + let d = encodeB64(privateKey); + + return new Jwk({ + "kty": JwkType.Okp, + "crv": "Ed25519", + d, + x, + alg, + }); } -function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { - if (jwk.alg()! !== JwsAlgorithm.EdDSA) { +function decodeJwk(jwk: Jwk): [Ed25519PrivateKey, Ed25519PublicKey] { + if (jwk.alg() !== JwsAlgorithm.EdDSA) { throw new Error("unsupported `alg`"); } - if (jwk.alg()! === JwsAlgorithm.EdDSA) { - const paramsOkp = jwk.paramsOkp(); - if (paramsOkp) { - const d = paramsOkp.d; - - if (d) { - const textEncoder = new TextEncoder(); - const privateKey = decodeB64(textEncoder.encode(d)); - const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); - return [privateKey, publicKey]; - } else { - throw new Error("missing private key component"); - } - } else { - throw new Error("expected Okp params"); - } - } else if (jwk.alg()! === JwsAlgorithm.MLDSA44 || jwk.alg()! === JwsAlgorithm.MLDSA65 || jwk.alg()! === JwsAlgorithm.MLDSA87) { - const paramsPQ = jwk.paramsAkp(); - if (paramsPQ) { - const priv = paramsPQ.priv; - - if (priv) { - const textEncoder = new TextEncoder(); - const privateKey = decodeB64(textEncoder.encode(priv)); - const publicKey = decodeB64(textEncoder.encode(paramsPQ.pub)); - return [privateKey, publicKey]; - } else { - throw new Error("missing private key component"); - } + const paramsOkp = jwk.paramsOkp(); + if (paramsOkp) { + const d = paramsOkp.d; + + if (d) { + let textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(d)); + const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); + return [privateKey, publicKey]; } else { - throw new Error("expected Okp params"); + throw new Error("missing private key component"); } } else { - throw new Error("unsupported `alg`"); + throw new Error("expected Okp params"); } } -//TODO: non sembra servire a nulla export interface JwkStorageBBSPlusExt { // Generate a new BLS12381 key represented as a JSON Web Key. generateBBS: (algorithm: ProofAlgorithm) => Promise; @@ -244,4 +162,4 @@ function randomKeyId(): string { } return encodeB64(randomness); -} \ No newline at end of file +} From 0ef1e93cc896d876fb45eef66c56e6a47fb255c1 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 11:43:02 +0200 Subject: [PATCH 145/163] revert changes --- bindings/wasm/package-lock.json | 14 -------------- bindings/wasm/package.json | 1 - identity_credential/Cargo.toml | 10 ++++++++-- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index 69abb45ab4..33f9d564f9 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -10,7 +10,6 @@ "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", - "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", @@ -6636,19 +6635,6 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, - "@noble/hashes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", - "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" - }, - "@noble/post-quantum": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", - "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", - "requires": { - "@noble/hashes": "1.6.0" - } - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 652f802fa0..a63db388a7 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -79,7 +79,6 @@ }, "dependencies": { "@noble/ed25519": "^1.7.3", - "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 4e365e60a7..ec18e2d2eb 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -71,8 +71,14 @@ domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] sd-jwt-vc = ["sd-jwt", "dep:sd-jwt-payload-rework", "dep:jsonschema", "dep:futures"] -jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token", "dep:futures"] +jpt-bbs-plus = [ + "credential", + "validator", + "dep:zkryptium", + "dep:bls12_381_plus", + "dep:json-proof-token", + "dep:futures", +] hybrid = ["credential", "validator"] - [lints] workspace = true From 82b51956da1e7e5111d9f777fb8b5f5eafc25d27 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 11:44:19 +0200 Subject: [PATCH 146/163] Enhance documentation --- .../jwt_credential_validator_hybrid.rs | 15 ++++++------ .../jwt_presentation_validator_hybrid.rs | 8 +++---- .../src/document/core_document.rs | 12 +++++++++- identity_storage/src/key_storage/memstore.rs | 8 +++---- .../src/storage/did_jwk_document_ext.rs | 23 +++++++------------ .../src/storage/pqc_jws_document_ext.rs | 8 +++---- .../src/verification_method/material.rs | 2 +- .../src/verification_method/method.rs | 1 - 8 files changed, 39 insertions(+), 38 deletions(-) diff --git a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs index a3c85f2e5d..090db2bd97 100644 --- a/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -24,13 +24,13 @@ use crate::credential::CredentialJwtClaims; use crate::credential::Jwt; use crate::validator::FailFast; -/// A type for decoding and validating [`Credential`]s. +/// A type for decoding and validating [`Credential`]s signed with a PQ/T signature. #[non_exhaustive] pub struct JwtCredentialValidatorHybrid(TRV, PQV); impl JwtCredentialValidatorHybrid { - /// Create a new [`JwtCredentialValidator`] that delegates cryptographic signature verification to the given - /// `signature_verifier`. + /// Create a new [`JwtCredentialValidatorHybrid`] that delegates cryptographic signature verification to the given + /// traditional [`JwsVerifier`] and PQ [`JwsVerifier`]. pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self { Self(traditional_signature_verifier, pq_signature_verifier) } @@ -38,7 +38,7 @@ impl JwtCredentialValidatorHybrid /// Decodes and validates a [`Credential`] issued as a JWT. A [`DecodedJwtCredential`] is returned upon success. /// /// The following properties are validated according to `options`: - /// - the issuer's signature on the JWS, + /// - the issuer's PQ/T signature on the JWS, /// - the expiration date, /// - the issuance date, /// - the semantic structure. @@ -87,7 +87,7 @@ impl JwtCredentialValidatorHybrid ) } - /// Decode and verify the JWS signature of a [`Credential`] issued as a JWT using the DID Document of a trusted + /// Decode and verify the PQ/T JWS signature of a [`Credential`] issued as a JWT using the DID Document of a trusted /// issuer. /// /// A [`DecodedJwtCredential`] is returned upon success. @@ -96,7 +96,7 @@ impl JwtCredentialValidatorHybrid /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date. /// /// ## Proofs - /// Only the JWS signature is verified. If the [`Credential`] contains a `proof` property this will not be verified + /// Only the PQ/T JWS signature is verified. If the [`Credential`] contains a `proof` property this will not be verified /// by this method. /// /// # Errors @@ -252,8 +252,7 @@ impl JwtCredentialValidatorHybrid ) -> Result, JwtValidationError> where T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, - DOC: AsRef, - // S: JwsVerifier, + DOC: AsRef { // Note the below steps are necessary because `CoreDocument::verify_jws` decodes the JWS and then searches for a // method with a fragment (or full DID Url) matching `kid` in the given document. We do not want to carry out diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs index d50031a870..067eb34efe 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -21,7 +21,7 @@ use super::CompoundJwtPresentationValidationError; use super::DecodedJwtPresentation; use super::JwtPresentationValidationOptions; -/// Struct for validating [`Presentation`]. +/// Struct for validating [`Presentation`] signed with a PQ/T signature. #[derive(Debug, Clone)] #[non_exhaustive] pub struct JwtPresentationValidatorHybrid(TRV, PQV); @@ -31,17 +31,17 @@ where TRV: JwsVerifier, PQV: JwsVerifier, { - /// Creates a new [`JwtPresentationValidator`] using a specific [`JwsVerifier`]. + /// Creates a new [`JwtPresentationValidatorHybrid`] using a specific traditional [`JwsVerifier`] and a specific PQ [`JwsVerifier`]. pub fn with_signature_verifiers(traditional_signature_verifier: TRV, pq_signature_verifier: PQV) -> Self { Self(traditional_signature_verifier, pq_signature_verifier) } - /// Validates a [`Presentation`]. + /// Validates a [`Presentation`] signed with a PQ/T signature. /// /// The following properties are validated according to `options`: /// - the JWT can be decoded into a semantically valid presentation. /// - the expiration and issuance date contained in the JWT claims. - /// - the holder's signature. + /// - the holder's PQ/T signature. /// /// Validation is done with respect to the properties set in `options`. /// diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 303e589eb3..d1a5059630 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -989,6 +989,16 @@ impl CoreDocument { .map_err(Error::JwsVerificationError) } + /// Decodes and verifies the provided PQ/T JWS according to the passed [`JwsVerificationOptions`] with a + /// traditional [`JwsVerifier`] and PQ [`JwsVerifier`]. + /// + /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to + /// take place. + /// - The JWS must be encoded according to the JWS compact serialization. + /// - The `kid` value in the protected header must be an identifier of a verification method in this DID document, or + /// set explicitly in the `options`. + // + // NOTE: This is tested in `identity_storage` and `identity_credential`. pub fn verify_jws_hybrid<'jws, TRV: JwsVerifier, PQV: JwsVerifier>( &self, jws: &'jws str, @@ -1055,7 +1065,7 @@ impl CoreDocument { } impl CoreDocument { - /// Creates a [`CoreDocument`] from a did:jwk DID. + /// Creates a [`CoreDocument`] from a did:compositejwk DID. pub fn expand_did_compositejwk(did_compositejwk: DIDCompositeJwk) -> Result { let verification_method = VerificationMethod::try_from(did_compositejwk.clone()).map_err(Error::InvalidKeyMaterial)?; let verification_method_id = verification_method.id().clone(); diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 4a435e9465..18fc2a7b70 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -372,7 +372,7 @@ mod pqc_liboqs { } } - /// JwkStoragePQ implementation for JwkMemStore + /// JwkStoragePQ implementation for JwkMemStore based on liboqs #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkStoragePQ for JwkMemStore { @@ -513,11 +513,11 @@ mod pqc_liboqs { .map(jwu::decode_b64) .ok_or_else(|| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("expected Jwk `pub` param to be present") + .with_custom_message("expected Jwk `private` param to be present") })? .map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("unable to decode `d` param") + .with_custom_message("unable to decode `private` param") .with_source(err) })?; oqs::init(); @@ -530,7 +530,7 @@ mod pqc_liboqs { let secret_key = scheme.secret_key_from_bytes(&sk_bytes).ok_or( KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("wrong key length")), + .with_custom_message(format!("invalid private key")), )?; let signature = scheme.sign(&data, secret_key).map_err(|err| { diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 5617a89aa8..b7c66099d2 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -5,19 +5,19 @@ use identity_did::DIDJwk; use identity_document::document::CoreDocument; use identity_verification::{jws::JwsAlgorithm, jwu::encode_b64_json}; use async_trait::async_trait; -#[cfg(feature = "jpt-bbs-plus")] -use jsonprooftoken::jpa::algs::ProofAlgorithm; - - use crate::{JwkGenOutput, JwkStorage, JwkStorageDocumentError as Error, KeyIdStorage, KeyType, MethodDigest}; #[cfg(feature = "pqc")] use crate::JwkStoragePQ; #[cfg(feature = "jpt-bbs-plus")] use crate::JwkStorageBbsPlusExt; +#[cfg(feature = "jpt-bbs-plus")] +use jsonprooftoken::jpa::algs::ProofAlgorithm; #[cfg(feature = "hybrid")] use identity_verification::jwk::{CompositeAlgId, CompositeJwk}; #[cfg(feature = "hybrid")] use identity_did::DIDCompositeJwk; +#[cfg(feature = "hybrid")] +use crate::KeyId; use super::{Storage, StorageResult}; @@ -25,8 +25,7 @@ use super::{Storage, StorageResult}; #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait DidJwkDocumentExt{ - -/// Create a JWK-based DID documents with traditional keys. Returns the DID document and the fragment + /// Create a JWK-based DID documents with traditional keys. Returns the DID document and the fragment async fn new_did_jwk( storage: &Storage, key_type: KeyType, @@ -35,7 +34,7 @@ pub trait DidJwkDocumentExt{ where K: JwkStorage, I: KeyIdStorage; -/// Create a JWK-based DID documents with PQ keys. Returns the DID document and the fragment + /// Create a JWK-based DID documents with PQ keys. Returns the DID document and the fragment #[cfg(feature = "pqc")] async fn new_did_jwk_pqc( storage: &Storage, @@ -45,7 +44,7 @@ pub trait DidJwkDocumentExt{ where K: JwkStoragePQ, I: KeyIdStorage; -/// Create a JWK-based DID documents with zk keys. Returns the DID document and the fragment + /// Create a JWK-based DID documents with zk keys. Returns the DID document and the fragment #[cfg(feature = "jpt-bbs-plus")] async fn new_did_jwk_zk( storage: &Storage, @@ -56,7 +55,7 @@ pub trait DidJwkDocumentExt{ K: JwkStorageBbsPlusExt, I: KeyIdStorage; -/// Create a JWK-based DID documents with hybrid keys. Returns the DID document and the fragment + /// Create a JWK-based DID documents with hybrid keys. Returns the DID document and the fragment #[cfg(feature = "hybrid")] async fn new_did_compositejwk( storage: &Storage, @@ -70,7 +69,6 @@ pub trait DidJwkDocumentExt{ #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] impl DidJwkDocumentExt for CoreDocument { - async fn new_did_jwk( storage: &Storage, key_type: KeyType, @@ -113,7 +111,6 @@ impl DidJwkDocumentExt for CoreDocument { #[cfg(feature = "pqc")] async fn new_did_jwk_pqc( - storage: &Storage, key_type: KeyType, alg: JwsAlgorithm, @@ -121,7 +118,6 @@ impl DidJwkDocumentExt for CoreDocument { where K: JwkStoragePQ, I: KeyIdStorage - { let JwkGenOutput { key_id, jwk } = K::generate_pq_key(storage.key_storage(), @@ -205,9 +201,6 @@ impl DidJwkDocumentExt for CoreDocument { K: JwkStorage + JwkStoragePQ, I: KeyIdStorage { - - use crate::KeyId; - let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { CompositeAlgId::IdMldsa44Ed25519 => ( KeyType::from_static_str("ML-DSA"), diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index ddeb64866b..9f9ee63574 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -31,12 +31,12 @@ use identity_verification::VerificationMethod; use serde::de::DeserializeOwned; use serde::Serialize; -///New trait to handle JWP-based operations on DID Documents +///New trait to handle PQ-based operations on DID Documents #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] #[cfg_attr(feature = "send-sync-storage", async_trait)] pub trait JwsDocumentExtPQC { - /// Generate new key material in the given `storage` and insert a new verification method with the corresponding - /// public key material into the DID document. This support BBS+ keys. + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding PQ + /// public key material into the DID document. async fn generate_method_pqc( &mut self, storage: &Storage, @@ -49,7 +49,7 @@ pub trait JwsDocumentExtPQC { K: JwkStoragePQ, I: KeyIdStorage; - /// Create a JWS using a PQC + /// Create a PQ JWS async fn create_jws_pqc( &self, storage: &Storage, diff --git a/identity_verification/src/verification_method/material.rs b/identity_verification/src/verification_method/material.rs index 6dbc08eba2..578f7f8839 100644 --- a/identity_verification/src/verification_method/material.rs +++ b/identity_verification/src/verification_method/material.rs @@ -6,7 +6,7 @@ */ use crate::jose::jwk::Jwk; -use identity_jose::jwk::CompositeJwk; +use crate::jose::jwk::CompositeJwk; use core::fmt::Debug; use core::fmt::Formatter; use identity_core::convert::BaseEncoding; diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 59cc34e6de..565574cade 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -242,7 +242,6 @@ impl VerificationMethod { /// Creates a new [`VerificationMethod`] from the given `did` and [`CompositeJwk`]. If `fragment` is not given /// the `kid` value of the given `key` will be used, if present, otherwise an error is returned. pub fn new_from_compositejwk(did: D, key: CompositeJwk, fragment: Option<&str>) -> Result { - // Can i use ~ in a URI safely since is an unreserved character (https://www.rfc-editor.org/rfc/rfc3986#section-2.3) let composite_fragment = key.traditional_public_key() .kid() .map(|s| s.to_string()) From bc063d56df6bfa8a3024fd320f2c5c1ae606eda5 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 12:20:45 +0200 Subject: [PATCH 147/163] Bump zkryptium and json proof token to match to latest BBS and JPT draft --- Cargo.toml | 4 ++-- examples/1_advanced/10_zkp_revocation.rs | 4 ++-- examples/1_advanced/9_zkp.rs | 2 +- identity_storage/src/key_storage/bls.rs | 12 ++++++------ identity_stronghold/src/storage/mod.rs | 2 +- .../storage/stronghold_jwk_storage_bbs_plus_ext.rs | 2 +- identity_stronghold/src/tests/test_bbs_ext.rs | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c5aae8902..25fe6950be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -json-proof-token = { version = "0.3.5" } -zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } +json-proof-token = { version = "0.4.1" } +zkryptium = { version = "0.4.0", default-features = false, features = ["bbsplus"] } oqs = {version = "0.10.0", default-features = false, features = ["sigs", "std", "vendored"] } diff --git a/examples/1_advanced/10_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs index a78dea0e76..3fbffd6eb8 100644 --- a/examples/1_advanced/10_zkp_revocation.rs +++ b/examples/1_advanced/10_zkp_revocation.rs @@ -156,7 +156,7 @@ async fn main() -> anyhow::Result<()> { &storage_issuer, JwkMemStore::BLS12381G2_KEY_TYPE, None, - Some(ProofAlgorithm::BLS12381_SHA256), + Some(ProofAlgorithm::BBS), ) .await?; @@ -344,7 +344,7 @@ async fn main() -> anyhow::Result<()> { // Step 2b: Waiting for the next validityTimeframe, will result in the Credential timeframe interval NOT valid // =========================================================================== - thread::sleep(SleepDuration::from_secs(61)); + thread::sleep(SleepDuration::from_secs(63)); let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( &decoded_presented_credential.credential, diff --git a/examples/1_advanced/9_zkp.rs b/examples/1_advanced/9_zkp.rs index eeb4246280..1b056e334f 100644 --- a/examples/1_advanced/9_zkp.rs +++ b/examples/1_advanced/9_zkp.rs @@ -103,7 +103,7 @@ async fn main() -> anyhow::Result<()> { &secret_manager_issuer, &storage_issuer, JwkMemStore::BLS12381G2_KEY_TYPE, - ProofAlgorithm::BLS12381_SHA256, + ProofAlgorithm::BBS, ) .await?; diff --git a/identity_storage/src/key_storage/bls.rs b/identity_storage/src/key_storage/bls.rs index 2a3b38a0a7..ed0281d3bf 100644 --- a/identity_storage/src/key_storage/bls.rs +++ b/identity_storage/src/key_storage/bls.rs @@ -31,8 +31,8 @@ where /// Generates a new BBS+ keypair using either `BLS12381-SHA256` or `BLS12381-SHAKE256`. pub fn generate_bbs_keypair(alg: ProofAlgorithm) -> KeyStorageResult<(BBSplusSecretKey, BBSplusPublicKey)> { match alg { - ProofAlgorithm::BLS12381_SHA256 => random_bbs_keypair::(), - ProofAlgorithm::BLS12381_SHAKE256 => random_bbs_keypair::(), + ProofAlgorithm::BBS => random_bbs_keypair::(), + ProofAlgorithm::BBS_SHAKE256 => random_bbs_keypair::(), _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), } .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)) @@ -130,8 +130,8 @@ pub fn sign_bbs( header: &[u8], ) -> KeyStorageResult> { match alg { - ProofAlgorithm::BLS12381_SHA256 => _sign_bbs::(data, sk, pk, header), - ProofAlgorithm::BLS12381_SHAKE256 => _sign_bbs::(data, sk, pk, header), + ProofAlgorithm::BBS => _sign_bbs::(data, sk, pk, header), + ProofAlgorithm::BBS_SHAKE256 => _sign_bbs::(data, sk, pk, header), _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), } .map_err(|e| { @@ -188,8 +188,8 @@ pub fn update_bbs_signature( KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("invalid signature size".to_owned()) })?; match alg { - ProofAlgorithm::BLS12381_SHA256 => _update_bbs_signature::(exact_size_signature, sk, update_ctx), - ProofAlgorithm::BLS12381_SHAKE256 => { + ProofAlgorithm::BBS => _update_bbs_signature::(exact_size_signature, sk, update_ctx), + ProofAlgorithm::BBS_SHAKE256 => { _update_bbs_signature::(exact_size_signature, sk, update_ctx) } _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), diff --git a/identity_stronghold/src/storage/mod.rs b/identity_stronghold/src/storage/mod.rs index cb02b9274b..96503655a4 100644 --- a/identity_stronghold/src/storage/mod.rs +++ b/identity_stronghold/src/storage/mod.rs @@ -109,7 +109,7 @@ impl StrongholdStorage { .get_guards([location], |[sk]| { let sk = BBSplusSecretKey::from_bytes(&sk.borrow()).map_err(|e| FatalProcedureError::from(e.to_string()))?; let pk = sk.public_key(); - let public_jwk = encode_bls_jwk(&sk, &pk, ProofAlgorithm::BLS12381_SHA256).1; + let public_jwk = encode_bls_jwk(&sk, &pk, ProofAlgorithm::BBS).1; drop(Zeroizing::new(sk.to_bytes())); Ok(public_jwk) diff --git a/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs b/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs index 10fbe7faa0..8950d6de67 100644 --- a/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs +++ b/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs @@ -39,7 +39,7 @@ impl JwkStorageBbsPlusExt for StrongholdStorage { ); } - if !matches!(alg, ProofAlgorithm::BLS12381_SHA256 | ProofAlgorithm::BLS12381_SHAKE256) { + if !matches!(alg, ProofAlgorithm::BBS | ProofAlgorithm::BBS_SHAKE256) { return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()); } diff --git a/identity_stronghold/src/tests/test_bbs_ext.rs b/identity_stronghold/src/tests/test_bbs_ext.rs index efa71f3cc2..05f12c3524 100644 --- a/identity_stronghold/src/tests/test_bbs_ext.rs +++ b/identity_stronghold/src/tests/test_bbs_ext.rs @@ -23,7 +23,7 @@ use crate::StrongholdStorage; async fn stronghold_bbs_keypair_gen_works() -> anyhow::Result<()> { let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); let JwkGenOutput { key_id, jwk, .. } = stronghold_storage - .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BLS12381_SHA256) + .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BBS) .await?; assert!(jwk.is_public()); @@ -36,7 +36,7 @@ async fn stronghold_bbs_keypair_gen_works() -> anyhow::Result<()> { async fn stronghold_bbs_keypair_gen_fails_with_wrong_key_type() -> anyhow::Result<()> { let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); let error = stronghold_storage - .generate_bbs(StrongholdKeyType::Ed25519.into(), ProofAlgorithm::BLS12381_SHA256) + .generate_bbs(StrongholdKeyType::Ed25519.into(), ProofAlgorithm::BBS) .await .unwrap_err(); assert!(matches!(error.kind(), KeyStorageErrorKind::UnsupportedKeyType)); @@ -61,7 +61,7 @@ async fn stronghold_bbs_keypair_gen_fails_with_wrong_alg() -> anyhow::Result<()> async fn stronghold_sign_bbs_works() -> anyhow::Result<()> { let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); let JwkGenOutput { key_id, jwk, .. } = stronghold_storage - .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BLS12381_SHA256) + .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BBS) .await?; let pk = expand_bls_jwk(&jwk)?.1; let sk = { @@ -79,7 +79,7 @@ async fn stronghold_sign_bbs_works() -> anyhow::Result<()> { let mut data = vec![0; 1024]; rand::thread_rng().fill_bytes(&mut data); let expected_signature = sign_bbs( - ProofAlgorithm::BLS12381_SHA256, + ProofAlgorithm::BBS, std::slice::from_ref(&data), &sk, &pk, From 00bd8985a2e849985ec2545f84b47b2a75f9ccf3 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 9 Apr 2025 14:24:39 +0200 Subject: [PATCH 148/163] Wasm: Bump zkryptium and json proof token version to match the latest BBS and JPT drafts --- bindings/wasm/Cargo.toml | 4 ++-- bindings/wasm/examples/src/1_advanced/8_zkp.ts | 2 +- .../src/1_advanced/9_zkp_revocation.ts | 2 +- bindings/wasm/src/jpt/encoding.rs | 3 +++ .../src/jpt/presentation_protected_header.rs | 18 ++++++++++++------ bindings/wasm/src/jpt/proof_algorithm.rs | 18 ++++++++++++------ 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 1d8f9219a1..e8f07da09c 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -24,7 +24,7 @@ futures = { version = "0.3" } identity_ecdsa_verifier = { path = "../../identity_ecdsa_verifier", default-features = false, features = ["es256", "es256k"] } identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } js-sys = { version = "0.3.61" } -json-proof-token = "0.3.4" +json-proof-token = "0.4.1" proc_typescript = { version = "0.1.0", path = "./proc_typescript" } serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.6.5" @@ -34,7 +34,7 @@ serde_repr = { version = "0.1", default-features = false } tokio = { version = "1.29", default-features = false, features = ["sync"] } wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } -zkryptium = "0.2.2" +zkryptium = "0.4.0" [dependencies.identity_iota] path = "../../identity_iota" diff --git a/bindings/wasm/examples/src/1_advanced/8_zkp.ts b/bindings/wasm/examples/src/1_advanced/8_zkp.ts index 55d0c82fca..127b3fcbb0 100644 --- a/bindings/wasm/examples/src/1_advanced/8_zkp.ts +++ b/bindings/wasm/examples/src/1_advanced/8_zkp.ts @@ -64,7 +64,7 @@ export async function createDid(client: Client, secretManager: SecretManagerType const fragment = await document.generateMethodJwp( storage, - ProofAlgorithm.BLS12381_SHA256, + ProofAlgorithm.BBS, undefined, MethodScope.VerificationMethod(), ); diff --git a/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts b/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts index e8c3d586a1..7d34a19cb7 100644 --- a/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts +++ b/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts @@ -70,7 +70,7 @@ export async function createDid(client: Client, secretManager: SecretManagerType const fragment = await document.generateMethodJwp( storage, - ProofAlgorithm.BLS12381_SHA256, + ProofAlgorithm.BBS, undefined, MethodScope.VerificationMethod(), ); diff --git a/bindings/wasm/src/jpt/encoding.rs b/bindings/wasm/src/jpt/encoding.rs index e36a5307a5..fbedd4f5df 100644 --- a/bindings/wasm/src/jpt/encoding.rs +++ b/bindings/wasm/src/jpt/encoding.rs @@ -8,6 +8,7 @@ use wasm_bindgen::prelude::*; pub enum WasmSerializationType { COMPACT = 0, JSON = 1, + CBOR = 2, } impl From for SerializationType { @@ -15,6 +16,7 @@ impl From for SerializationType { match value { WasmSerializationType::COMPACT => SerializationType::COMPACT, WasmSerializationType::JSON => SerializationType::JSON, + WasmSerializationType::CBOR => SerializationType::CBOR, } } } @@ -24,6 +26,7 @@ impl From for WasmSerializationType { match value { SerializationType::COMPACT => WasmSerializationType::COMPACT, SerializationType::JSON => WasmSerializationType::JSON, + SerializationType::CBOR => WasmSerializationType::CBOR, } } } diff --git a/bindings/wasm/src/jpt/presentation_protected_header.rs b/bindings/wasm/src/jpt/presentation_protected_header.rs index 398870da4c..08b0132f3f 100644 --- a/bindings/wasm/src/jpt/presentation_protected_header.rs +++ b/bindings/wasm/src/jpt/presentation_protected_header.rs @@ -9,9 +9,11 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = PresentationProofAlgorithm)] #[allow(non_camel_case_types)] pub enum WasmPresentationProofAlgorithm { - BLS12381_SHA256_PROOF, - BLS12381_SHAKE256_PROOF, + BBS, + BBS_SHAKE256, SU_ES256, + SU_ES384, + SU_ES512, MAC_H256, MAC_H384, MAC_H512, @@ -23,9 +25,11 @@ pub enum WasmPresentationProofAlgorithm { impl From for PresentationProofAlgorithm { fn from(value: WasmPresentationProofAlgorithm) -> Self { match value { - WasmPresentationProofAlgorithm::BLS12381_SHA256_PROOF => PresentationProofAlgorithm::BLS12381_SHA256_PROOF, - WasmPresentationProofAlgorithm::BLS12381_SHAKE256_PROOF => PresentationProofAlgorithm::BLS12381_SHAKE256_PROOF, + WasmPresentationProofAlgorithm::BBS => PresentationProofAlgorithm::BBS, + WasmPresentationProofAlgorithm::BBS_SHAKE256 => PresentationProofAlgorithm::BBS_SHAKE256, WasmPresentationProofAlgorithm::SU_ES256 => PresentationProofAlgorithm::SU_ES256, + WasmPresentationProofAlgorithm::SU_ES384 => PresentationProofAlgorithm::SU_ES384, + WasmPresentationProofAlgorithm::SU_ES512 => PresentationProofAlgorithm::SU_ES512, WasmPresentationProofAlgorithm::MAC_H256 => PresentationProofAlgorithm::MAC_H256, WasmPresentationProofAlgorithm::MAC_H384 => PresentationProofAlgorithm::MAC_H384, WasmPresentationProofAlgorithm::MAC_H512 => PresentationProofAlgorithm::MAC_H512, @@ -39,9 +43,11 @@ impl From for PresentationProofAlgorithm { impl From for WasmPresentationProofAlgorithm { fn from(value: PresentationProofAlgorithm) -> Self { match value { - PresentationProofAlgorithm::BLS12381_SHA256_PROOF => WasmPresentationProofAlgorithm::BLS12381_SHA256_PROOF, - PresentationProofAlgorithm::BLS12381_SHAKE256_PROOF => WasmPresentationProofAlgorithm::BLS12381_SHAKE256_PROOF, + PresentationProofAlgorithm::BBS => WasmPresentationProofAlgorithm::BBS, + PresentationProofAlgorithm::BBS_SHAKE256 => WasmPresentationProofAlgorithm::BBS_SHAKE256, PresentationProofAlgorithm::SU_ES256 => WasmPresentationProofAlgorithm::SU_ES256, + PresentationProofAlgorithm::SU_ES384 => WasmPresentationProofAlgorithm::SU_ES384, + PresentationProofAlgorithm::SU_ES512 => WasmPresentationProofAlgorithm::SU_ES512, PresentationProofAlgorithm::MAC_H256 => WasmPresentationProofAlgorithm::MAC_H256, PresentationProofAlgorithm::MAC_H384 => WasmPresentationProofAlgorithm::MAC_H384, PresentationProofAlgorithm::MAC_H512 => WasmPresentationProofAlgorithm::MAC_H512, diff --git a/bindings/wasm/src/jpt/proof_algorithm.rs b/bindings/wasm/src/jpt/proof_algorithm.rs index 0f7f6986f1..09354e7358 100644 --- a/bindings/wasm/src/jpt/proof_algorithm.rs +++ b/bindings/wasm/src/jpt/proof_algorithm.rs @@ -8,9 +8,11 @@ use wasm_bindgen::prelude::*; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[wasm_bindgen(js_name = ProofAlgorithm)] pub enum WasmProofAlgorithm { - BLS12381_SHA256, - BLS12381_SHAKE256, + BBS, + BBS_SHAKE256, SU_ES256, + SU_ES384, + SU_ES512, MAC_H256, MAC_H384, MAC_H512, @@ -22,9 +24,11 @@ pub enum WasmProofAlgorithm { impl From for WasmProofAlgorithm { fn from(value: ProofAlgorithm) -> Self { match value { - ProofAlgorithm::BLS12381_SHA256 => WasmProofAlgorithm::BLS12381_SHA256, - ProofAlgorithm::BLS12381_SHAKE256 => WasmProofAlgorithm::BLS12381_SHAKE256, + ProofAlgorithm::BBS => WasmProofAlgorithm::BBS, + ProofAlgorithm::BBS_SHAKE256 => WasmProofAlgorithm::BBS_SHAKE256, ProofAlgorithm::SU_ES256 => WasmProofAlgorithm::SU_ES256, + ProofAlgorithm::SU_ES384 => WasmProofAlgorithm::SU_ES384, + ProofAlgorithm::SU_ES512 => WasmProofAlgorithm::SU_ES512, ProofAlgorithm::MAC_H256 => WasmProofAlgorithm::MAC_H256, ProofAlgorithm::MAC_H384 => WasmProofAlgorithm::MAC_H384, ProofAlgorithm::MAC_H512 => WasmProofAlgorithm::MAC_H512, @@ -38,9 +42,11 @@ impl From for WasmProofAlgorithm { impl From for ProofAlgorithm { fn from(value: WasmProofAlgorithm) -> Self { match value { - WasmProofAlgorithm::BLS12381_SHA256 => ProofAlgorithm::BLS12381_SHA256, - WasmProofAlgorithm::BLS12381_SHAKE256 => ProofAlgorithm::BLS12381_SHAKE256, + WasmProofAlgorithm::BBS => ProofAlgorithm::BBS, + WasmProofAlgorithm::BBS_SHAKE256 => ProofAlgorithm::BBS_SHAKE256, WasmProofAlgorithm::SU_ES256 => ProofAlgorithm::SU_ES256, + WasmProofAlgorithm::SU_ES384 => ProofAlgorithm::SU_ES384, + WasmProofAlgorithm::SU_ES512 => ProofAlgorithm::SU_ES512, WasmProofAlgorithm::MAC_H256 => ProofAlgorithm::MAC_H256, WasmProofAlgorithm::MAC_H384 => ProofAlgorithm::MAC_H384, WasmProofAlgorithm::MAC_H512 => ProofAlgorithm::MAC_H512, From 1c6c692b4ea7e72ff9686b840261a11c3bf8264d Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 10 Apr 2025 16:45:48 +0200 Subject: [PATCH 149/163] Update VerificationMethod to use CompositeJsonWebKey type --- identity_verification/src/verification_method/method.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 565574cade..6fd6d40b06 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -273,7 +273,7 @@ impl VerificationMethod { MethodBuilder::default() .id(id) - .type_(MethodType::custom("CompositeSignaturePublicKey")) + .type_(MethodType::custom("CompositeJsonWebKey")) .controller(did.into()) .data(MethodData::CompositeJwk(key)) .build() From ed4ecb1e25725442c44bcb9002e7b215d68db4c6 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 10 Apr 2025 16:47:06 +0200 Subject: [PATCH 150/163] Add missing wasm bindings --- .../wasm/src/did/wasm_did_jwk_document_ext.rs | 143 ++++++-- bindings/wasm/src/iota/iota_document.rs | 31 -- bindings/wasm/src/iota/iota_document_ext.rs | 318 ++++++++++++++++++ bindings/wasm/src/iota/mod.rs | 4 + 4 files changed, 441 insertions(+), 55 deletions(-) create mode 100644 bindings/wasm/src/iota/iota_document_ext.rs diff --git a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs index d446f431d7..bd1f096130 100644 --- a/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs +++ b/bindings/wasm/src/did/wasm_did_jwk_document_ext.rs @@ -4,44 +4,49 @@ use std::rc::Rc; use crate::error::Result; use crate::error::WasmResult; -use identity_iota::storage::DidJwkDocumentExt; -use identity_iota::document::CoreDocument; -use identity_iota::storage::key_storage::KeyType; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::jwk::CompositeAlgId; use crate::storage::WasmStorageInner; use crate::jose::WasmJwsAlgorithm; use crate::jose::WasmCompositeAlgId; use crate::storage::WasmStorage; use super::CoreDocumentLock; use super::WasmCoreDocument; -use wasm_bindgen::prelude::*; -use identity_iota::storage::storage::JwsDocumentExtPQC; -use identity_iota::storage::storage::JwkDocumentExtHybrid; +use crate::credential::WasmCredential; +use crate::common::RecordStringAny; +use crate::credential::WasmJpt; +use crate::credential::PromiseJpt; +use crate::credential::WasmJwpPresentationOptions; +use crate::jpt::WasmSelectiveDisclosurePresentation; +use crate::credential::WasmJwt; use crate::jpt::WasmProofAlgorithm; -use jsonprooftoken::jpa::algs::ProofAlgorithm; -use js_sys::Promise; -use identity_iota::storage::JwsSignatureOptions; +use crate::credential::UnknownCredential; use crate::did::PromiseJws; use crate::storage::WasmJwsSignatureOptions; use crate::credential::WasmJws; -use wasm_bindgen_futures::future_to_promise; use crate::credential::WasmPresentation; use crate::storage::WasmJwtPresentationOptions; use crate::did::PromiseJwt; use identity_iota::credential::Presentation; -use crate::credential::UnknownCredential; use identity_iota::credential::JwtPresentationOptions; -use crate::credential::WasmJwt; +use identity_iota::storage::JwsSignatureOptions; use identity_iota::storage::JwpDocumentExt; -use crate::credential::WasmJpt; -use crate::credential::PromiseJpt; -use crate::credential::WasmJwpPresentationOptions; -use crate::jpt::WasmSelectiveDisclosurePresentation; +use identity_iota::credential::Credential; +use identity_iota::core::Object; +use identity_iota::storage::storage::JwsDocumentExtPQC; +use identity_iota::storage::storage::JwkDocumentExtHybrid; +use identity_iota::storage::DidJwkDocumentExt; +use identity_iota::document::CoreDocument; +use identity_iota::storage::key_storage::KeyType; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::jwk::CompositeAlgId; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use js_sys::Promise; #[wasm_bindgen(js_class = CoreDocument)] impl WasmCoreDocument { + /// Creates a new DID Document with the given `key_type` and `alg` with the JWK did method. #[wasm_bindgen(js_name = newDidJwk)] pub async fn _new_did_jwk( storage: &WasmStorage, @@ -59,6 +64,7 @@ impl WasmCoreDocument { .wasm_result() } + /// Creates a new PQ DID Document with the given `key_type` and `alg` with the JWK did method. #[wasm_bindgen(js_name = newDidJwkPq)] pub async fn _new_did_jwk_pqc( storage: &WasmStorage, @@ -76,6 +82,7 @@ impl WasmCoreDocument { .wasm_result() } + /// Creates a new hybrid DID Document with the given `key_type` and `alg`with the compositeJWK did method. #[wasm_bindgen(js_name = newDidCompositeJwk)] pub async fn _new_did_compositejwk( storage: &WasmStorage, @@ -91,6 +98,7 @@ impl WasmCoreDocument { .wasm_result() } + /// Creates a new zk DID Document with the given `key_type` and `alg` with the JWK did method. #[wasm_bindgen(js_name = newDidJwkZk)] pub async fn _new_did_jwk_zk( storage: &WasmStorage, @@ -111,7 +119,32 @@ impl WasmCoreDocument { pub fn _fragment(self) -> String { "0".to_string() } + /// Produces a PQ JWS, from a document with a PQ method, where the payload is produced from the given `fragment` and `payload`. + #[wasm_bindgen(js_name = createPqJws)] + pub fn _create_pq_jws( + &self, + storage: &WasmStorage, + fragment: String, + payload: String, + options: &WasmJwsSignatureOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_jws_pqc(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .await + .wasm_result() + .map(WasmJws::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + /// Produces an hybrid JWS, from a document with an hybrid method, where the payload is produced from the given `fragment` and `payload`. #[wasm_bindgen(js_name = createHybridJws)] pub fn create_hybrid_jws( &self, @@ -136,30 +169,86 @@ impl WasmCoreDocument { Ok(promise.unchecked_into()) } - #[wasm_bindgen(js_name = createPqJws)] - pub fn _create_pq_jws( + /// Produces a PQ JWT, from a document with a PQ method, where the payload is produced from the given `credential` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + /// + /// The `custom_claims` can be used to set additional claims on the resulting JWT. + #[wasm_bindgen(js_name = createCredentialJwtPqc)] + pub fn _create_credential_jwt_pqc( &self, storage: &WasmStorage, fragment: String, - payload: String, + credential: &WasmCredential, options: &WasmJwsSignatureOptions, - ) -> Result { + custom_claims: Option, + ) -> Result { let storage_clone: Rc = storage.0.clone(); let options_clone: JwsSignatureOptions = options.0.clone(); let document_lock_clone: Rc = self.0.clone(); + let credential_clone: Credential = credential.0.clone(); + let custom: Option = custom_claims + .map(|claims| claims.into_serde().wasm_result()) + .transpose()?; let promise: Promise = future_to_promise(async move { document_lock_clone .read() .await - .create_jws_pqc(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .create_credential_jwt_pqc(&credential_clone, &storage_clone, &fragment, &options_clone, custom) .await .wasm_result() - .map(WasmJws::new) + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces an hybrid JWT, from a document with an hybrid method, where the payload is produced from the given `credential` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + /// + /// The `custom_claims` can be used to set additional claims on the resulting JWT. + #[wasm_bindgen(js_name = createCredentialJwtHybrid)] + pub fn _create_credential_jwt_hybrid( + &self, + storage: &WasmStorage, + fragment: String, + credential: &WasmCredential, + options: &WasmJwsSignatureOptions, + custom_claims: Option, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let credential_clone: Credential = credential.0.clone(); + let custom: Option = custom_claims + .map(|claims| claims.into_serde().wasm_result()) + .transpose()?; + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_credential_jwt_hybrid(&credential_clone, &storage_clone, &fragment, &options_clone, custom) + .await + .wasm_result() + .map(WasmJwt::new) .map(JsValue::from) }); Ok(promise.unchecked_into()) } + /// Produces a PQ JWT, from a document with a PQ method, where the payload is produced from the given presentation. + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. #[wasm_bindgen(js_name = createPresentationJwtPqc)] pub fn _create_presentation_jwt_pqc( &self, @@ -193,6 +282,12 @@ impl WasmCoreDocument { Ok(promise.unchecked_into()) } + /// Produces an hybrid JWT, from a document with an hybrid method, where the payload is produced from the given presentation. + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. #[wasm_bindgen(js_name = createPresentationJwtHybrid)] pub fn _create_presentation_jwt_hybrid( &self, diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index c899724c4e..1747f82e6e 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -1,8 +1,5 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/* - * Modifications Copyright 2024 Fondazione LINKS. - */ use std::rc::Rc; @@ -85,7 +82,6 @@ use crate::verification::WasmMethodRelationship; use crate::verification::WasmMethodScope; use crate::verification::WasmVerificationMethod; use identity_iota::storage::JwpDocumentExt; -use identity_iota::storage::JwsDocumentExtPQC; pub(crate) struct IotaDocumentLock(tokio::sync::RwLock); @@ -998,33 +994,6 @@ impl WasmIotaDocument { Ok(promise.unchecked_into()) } - - #[wasm_bindgen(js_name = generateMethodPQC)] - #[allow(non_snake_case)] - pub fn generate_method_pqc( - &self, - storage: &WasmStorage, - keyType: String, - alg: WasmJwsAlgorithm, - fragment: Option, - scope: WasmMethodScope, - ) -> Result { - let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; - let document_lock_clone: Rc = self.0.clone(); - let storage_clone: Rc = storage.0.clone(); - let scope: MethodScope = scope.0; - - let promise: Promise = future_to_promise(async move { - let method_fragment: String = document_lock_clone - .write() - .await - .generate_method_pqc(&storage_clone, KeyType::from(keyType), alg, fragment.as_deref(), scope) - .await - .wasm_result()?; - Ok(JsValue::from(method_fragment)) - }); - Ok(promise.unchecked_into()) - } } impl From for WasmIotaDocument { diff --git a/bindings/wasm/src/iota/iota_document_ext.rs b/bindings/wasm/src/iota/iota_document_ext.rs new file mode 100644 index 0000000000..8816830ff2 --- /dev/null +++ b/bindings/wasm/src/iota/iota_document_ext.rs @@ -0,0 +1,318 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::rc::Rc; + +use identity_iota::core::Object; + +use identity_iota::credential::Credential; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::Presentation; +use identity_iota::storage::key_storage::KeyType; +use identity_iota::storage::storage::JwsSignatureOptions; +use identity_iota::verification::jose::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use js_sys::Promise; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::future_to_promise; +use crate::common::PromiseString; +use crate::common::RecordStringAny; +use crate::credential::UnknownCredential; +use crate::credential::WasmCredential; +use crate::credential::WasmJws; +use crate::credential::WasmJwt; +use crate::credential::WasmPresentation; +use crate::iota::IotaDocumentLock; +use crate::did::PromiseJws; +use crate::did::PromiseJwt; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jose::WasmJwsAlgorithm; +use crate::storage::WasmJwsSignatureOptions; +use crate::storage::WasmJwtPresentationOptions; +use crate::storage::WasmStorage; +use crate::storage::WasmStorageInner; +use crate::verification::WasmMethodScope; +use crate::jose::WasmCompositeAlgId; +use crate::iota::WasmIotaDocument; +use identity_iota::storage::JwsDocumentExtPQC; +use identity_iota::storage::JwkDocumentExtHybrid; +use identity_iota::verification::jwk::CompositeAlgId; + +#[wasm_bindgen(js_class = IotaDocument)] +impl WasmIotaDocument { + + /// Generate new PQ key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. + /// + /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. + /// - The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants + /// for that use case. + /// + /// The fragment of the generated method is returned. + #[wasm_bindgen(js_name = generateMethodPQC)] + #[allow(non_snake_case)] + pub fn generate_method_pqc( + &self, + storage: &WasmStorage, + keyType: String, + alg: WasmJwsAlgorithm, + fragment: Option, + scope: WasmMethodScope, + ) -> Result { + let alg: JwsAlgorithm = alg.into_serde().wasm_result()?; + let document_lock_clone: Rc = self.0.clone(); + let storage_clone: Rc = storage.0.clone(); + let scope: MethodScope = scope.0; + + let promise: Promise = future_to_promise(async move { + let method_fragment: String = document_lock_clone + .write() + .await + .generate_method_pqc(&storage_clone, KeyType::from(keyType), alg, fragment.as_deref(), scope) + .await + .wasm_result()?; + Ok(JsValue::from(method_fragment)) + }); + Ok(promise.unchecked_into()) + } + + /// Generate new hybrid key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. + /// + /// - If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. + /// - The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants + /// for that use case. + /// + /// The fragment of the generated method is returned. + #[wasm_bindgen(js_name = generateMethodHybrid)] + #[allow(non_snake_case)] + pub fn generate_method_hybrid( + &self, + storage: &WasmStorage, + alg: WasmCompositeAlgId, + fragment: Option, + scope: WasmMethodScope, + ) -> Result { + let alg: CompositeAlgId = alg.into_serde().wasm_result()?; + let document_lock_clone: Rc = self.0.clone(); + let storage_clone: Rc = storage.0.clone(); + let scope: MethodScope = scope.0; + + let promise: Promise = future_to_promise(async move { + let method_fragment: String = document_lock_clone + .write() + .await + .generate_method_hybrid(&storage_clone, alg, fragment.as_deref(), scope) + .await + .wasm_result()?; + Ok(JsValue::from(method_fragment)) + }); + Ok(promise.unchecked_into()) + } + + /// Produces a PQ JWS, from a document with a PQ method, where the payload is produced from the given `fragment` and `payload`. + #[wasm_bindgen(js_name = createPqJws)] + pub fn _create_pq_jws( + &self, + storage: &WasmStorage, + fragment: String, + payload: String, + options: &WasmJwsSignatureOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_jws_pqc(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .await + .wasm_result() + .map(WasmJws::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces an hybrid JWS, from a document with an hybrid method, where the payload is produced from the given `fragment` and `payload`. + #[wasm_bindgen(js_name = createHybridJws)] + pub fn create_hybrid_jws( + &self, + storage: &WasmStorage, + fragment: String, + payload: String, + options: &WasmJwsSignatureOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_jws(&storage_clone, &fragment, payload.as_bytes(), &options_clone) + .await + .wasm_result() + .map(WasmJws::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces a PQ JWT, from a document with a PQ method, where the payload is produced from the given `credential` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + /// + /// The `custom_claims` can be used to set additional claims on the resulting JWT. + #[wasm_bindgen(js_name = createCredentialJwtPqc)] + pub fn _create_credential_jwt_pqc( + &self, + storage: &WasmStorage, + fragment: String, + credential: &WasmCredential, + options: &WasmJwsSignatureOptions, + custom_claims: Option, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let credential_clone: Credential = credential.0.clone(); + let custom: Option = custom_claims + .map(|claims| claims.into_serde().wasm_result()) + .transpose()?; + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_credential_jwt_pqc(&credential_clone, &storage_clone, &fragment, &options_clone, custom) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces an hybrid JWT, from a document with an hybrid method, where the payload is produced from the given `credential` + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + /// + /// The `custom_claims` can be used to set additional claims on the resulting JWT. + #[wasm_bindgen(js_name = createCredentialJwtHybrid)] + pub fn _create_credential_jwt_hybrid( + &self, + storage: &WasmStorage, + fragment: String, + credential: &WasmCredential, + options: &WasmJwsSignatureOptions, + custom_claims: Option, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let credential_clone: Credential = credential.0.clone(); + let custom: Option = custom_claims + .map(|claims| claims.into_serde().wasm_result()) + .transpose()?; + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_credential_jwt_hybrid(&credential_clone, &storage_clone, &fragment, &options_clone, custom) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces a PQ JWT, from a document with a PQ method, where the payload is produced from the given presentation. + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + #[wasm_bindgen(js_name = createPresentationJwtPqc)] + pub fn _create_presentation_jwt_pqc( + &self, + storage: &WasmStorage, + fragment: String, + presentation: &WasmPresentation, + signature_options: &WasmJwsSignatureOptions, + presentation_options: &WasmJwtPresentationOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = signature_options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let presentation_clone: Presentation = presentation.0.clone(); + let presentation_options_clone: JwtPresentationOptions = presentation_options.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_presentation_jwt_pqc( + &presentation_clone, + &storage_clone, + &fragment, + &options_clone, + &presentation_options_clone, + ) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + + /// Produces an hybrid JWT, from a document with an hybrid method, where the payload is produced from the given presentation. + /// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). + /// + /// Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` + /// of the method identified by `fragment` and the JWS signature will be produced by the corresponding + /// private key backed by the `storage` in accordance with the passed `options`. + #[wasm_bindgen(js_name = createPresentationJwtHybrid)] + pub fn _create_presentation_jwt_hybrid( + &self, + storage: &WasmStorage, + fragment: String, + presentation: &WasmPresentation, + signature_options: &WasmJwsSignatureOptions, + presentation_options: &WasmJwtPresentationOptions, + ) -> Result { + let storage_clone: Rc = storage.0.clone(); + let options_clone: JwsSignatureOptions = signature_options.0.clone(); + let document_lock_clone: Rc = self.0.clone(); + let presentation_clone: Presentation = presentation.0.clone(); + let presentation_options_clone: JwtPresentationOptions = presentation_options.0.clone(); + let promise: Promise = future_to_promise(async move { + document_lock_clone + .read() + .await + .create_presentation_jwt_hybrid( + &presentation_clone, + &storage_clone, + &fragment, + &options_clone, + &presentation_options_clone, + ) + .await + .wasm_result() + .map(WasmJwt::new) + .map(JsValue::from) + }); + Ok(promise.unchecked_into()) + } + +} + diff --git a/bindings/wasm/src/iota/mod.rs b/bindings/wasm/src/iota/mod.rs index fa68380d80..a6f50f0027 100644 --- a/bindings/wasm/src/iota/mod.rs +++ b/bindings/wasm/src/iota/mod.rs @@ -1,5 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ pub(crate) use identity_client::WasmIotaIdentityClient; pub use identity_client_ext::PromiseIotaDocument; @@ -13,5 +16,6 @@ mod identity_client; mod identity_client_ext; mod iota_did; mod iota_document; +mod iota_document_ext; mod iota_document_metadata; mod iota_metadata_encoding; From b4f8b6d96aacd78526cf6e3ae4a23dc4d5a6c25c Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 14 Apr 2025 11:25:34 +0200 Subject: [PATCH 151/163] wasm: JwtPresentationValidatorHybrid bindings --- .../jwt_credential_validator_hybrid.rs | 2 +- .../jwt_presentation_validator_hybrid.rs | 95 +++++++++++++++++++ .../jwt_presentation_validation/mod.rs | 5 + 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs index 818c4e9ece..db99373a99 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -30,7 +30,7 @@ use crate::verification::WasmJwsVerifier; use wasm_bindgen::prelude::*; -/// A type for decoding and validating {@link Credential}. +/// A type for decoding and validating PQ/T {@link Credential}. #[wasm_bindgen(js_name = JwtCredentialValidatorHybrid)] pub struct WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid); diff --git a/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs new file mode 100644 index 0000000000..219943b0cc --- /dev/null +++ b/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -0,0 +1,95 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use super::decoded_jwt_presentation::WasmDecodedJwtPresentation; +use super::options::WasmJwtPresentationValidationOptions; +use crate::common::ImportedDocumentLock; +use crate::credential::WasmJwt; +use crate::credential::WasmPresentation; +use crate::did::IToCoreDocument; +use crate::did::WasmCoreDID; +use crate::error::Result; +use crate::error::WasmResult; +use crate::verification::IJwsVerifier; +use crate::verification::WasmJwsVerifier; +use identity_iota::credential::JwtPresentationValidatorHybrid; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::did::CoreDID; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = JwtPresentationValidatorHybrid, inspectable)] +pub struct WasmJwtPresentationValidatorHybrid(JwtPresentationValidatorHybrid); + +#[wasm_bindgen(js_class = JwtPresentationValidatorHybrid)] +impl WasmJwtPresentationValidatorHybrid { + /// Creates a new {@link JwtPresentationValidatorHybrid}. If a `signatureVerifier` is provided it will be used when + /// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K` + /// algorithms will be used. + #[wasm_bindgen(constructor)] + #[allow(non_snake_case)] + pub fn new(traditionalSignatureVerifier: Option, pqSignatureVerifier: Option) -> WasmJwtPresentationValidatorHybrid { + let traditional_signature_verifier = WasmJwsVerifier::new(traditionalSignatureVerifier); + let pq_signature_verifier = WasmJwsVerifier::new(pqSignatureVerifier); + WasmJwtPresentationValidatorHybrid(JwtPresentationValidatorHybrid::with_signature_verifiers(traditional_signature_verifier, pq_signature_verifier)) + } + + /// Validates a {@link Presentation} encoded as a {@link Jwt}. + /// + /// The following properties are validated according to `options`: + /// - the JWT can be decoded into a semantically valid presentation. + /// - the expiration and issuance date contained in the JWT claims. + /// - the holder's signature. + /// + /// Validation is done with respect to the properties set in `options`. + /// + /// # Warning + /// + /// * This method does NOT validate the constituent credentials and therefore also not the relationship between the + /// credentials' subjects and the presentation holder. This can be done with {@link JwtCredentialValidationOptions}. + /// * The lack of an error returned from this method is in of itself not enough to conclude that the presentation can + /// be trusted. This section contains more information on additional checks that should be carried out before and + /// after calling this method. + /// + /// ## The state of the supplied DID Documents. + /// + /// The caller must ensure that the DID Documents in `holder` are up-to-date. + /// + /// # Errors + /// + /// An error is returned whenever a validated condition is not satisfied or when decoding fails. + #[wasm_bindgen] + #[allow(non_snake_case)] + pub fn validate( + &self, + presentationJwt: &WasmJwt, + holder: &IToCoreDocument, + validation_options: &WasmJwtPresentationValidationOptions, + ) -> Result { + let holder_lock = ImportedDocumentLock::from(holder); + let holder_guard = holder_lock.try_read()?; + + self + .0 + .validate(&presentationJwt.0, &holder_guard, &validation_options.0) + .map(WasmDecodedJwtPresentation::from) + .wasm_result() + } + + /// Validates the semantic structure of the {@link Presentation}. + #[wasm_bindgen(js_name = checkStructure)] + pub fn check_structure(presentation: &WasmPresentation) -> Result<()> { + JwtPresentationValidatorUtils::check_structure(&presentation.0).wasm_result()?; + Ok(()) + } + + /// Attempt to extract the holder of the presentation. + /// + /// # Errors: + /// * If deserialization/decoding of the presentation fails. + /// * If the holder can't be parsed as DIDs. + #[wasm_bindgen(js_name = extractHolder)] + pub fn extract_holder(presentation: &WasmJwt) -> Result { + let holder = JwtPresentationValidatorUtils::extract_holder::(&presentation.0).wasm_result()?; + Ok(WasmCoreDID(holder)) + } +} diff --git a/bindings/wasm/src/credential/jwt_presentation_validation/mod.rs b/bindings/wasm/src/credential/jwt_presentation_validation/mod.rs index 12c556852e..99d774244b 100644 --- a/bindings/wasm/src/credential/jwt_presentation_validation/mod.rs +++ b/bindings/wasm/src/credential/jwt_presentation_validation/mod.rs @@ -1,10 +1,15 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ mod decoded_jwt_presentation; mod jwt_presentation_validator; mod options; +mod jwt_presentation_validator_hybrid; pub use self::decoded_jwt_presentation::*; pub use self::jwt_presentation_validator::*; pub use self::options::*; +pub use self::jwt_presentation_validator_hybrid::*; \ No newline at end of file From 043e115a9a64d74e84a8014ada0e33a682208887 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 14 Apr 2025 11:44:50 +0200 Subject: [PATCH 152/163] wasm: PQ and PQ/T examples --- .../wasm/examples/src/1_advanced/11_pq.ts | 278 +++++++++++++++++ .../wasm/examples/src/1_advanced/12_hybrid.ts | 281 ++++++++++++++++++ bindings/wasm/examples/src/main.ts | 9 + bindings/wasm/lib/index.ts | 7 +- bindings/wasm/lib/jwk_storage_pq.ts | 250 ++++++++++++++++ bindings/wasm/lib/pq_verifier.ts | 48 +++ 6 files changed, 871 insertions(+), 2 deletions(-) create mode 100644 bindings/wasm/examples/src/1_advanced/11_pq.ts create mode 100644 bindings/wasm/examples/src/1_advanced/12_hybrid.ts create mode 100644 bindings/wasm/lib/jwk_storage_pq.ts create mode 100644 bindings/wasm/lib/pq_verifier.ts diff --git a/bindings/wasm/examples/src/1_advanced/11_pq.ts b/bindings/wasm/examples/src/1_advanced/11_pq.ts new file mode 100644 index 0000000000..c7229d44d8 --- /dev/null +++ b/bindings/wasm/examples/src/1_advanced/11_pq.ts @@ -0,0 +1,278 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +import { + CoreDID, + Credential, + Duration, + FailFast, + IotaDocument, + IotaIdentityClient, + JwkPqMemStore, + JwsAlgorithm, + JwsSignatureOptions, + JwsVerificationOptions, + Jwt, + PQJwsVerifier, + JwtCredentialValidationOptions, + JwtCredentialValidator, + JwtPresentationOptions, + JwtPresentationValidationOptions, + JwtPresentationValidator, + KeyIdMemStore, + MethodScope, + Presentation, + Resolver, + Storage, + SubjectHolderRelationship, + Timestamp, +} from "@iota/identity-wasm/node"; +import { Address, AliasOutput, Client, MnemonicSecretManager, SecretManager, SecretManagerType, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +async function createPQDid(client: Client, secretManager: SecretManagerType, storage: Storage): Promise<{ + address: Address; + document: IotaDocument; + fragment: string; +}> { + const didClient = new IotaIdentityClient(client); + const networkHrp: string = await didClient.getNetworkHrp(); + + const secretManagerInstance = new SecretManager(secretManager); + const walletAddressBech32 = (await secretManagerInstance.generateEd25519Addresses({ + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: networkHrp, + }))[0]; + + console.log("Wallet address Bech32:", walletAddressBech32); + + await ensureAddressHasFunds(client, walletAddressBech32); + + const address: Address = Utils.parseBech32Address(walletAddressBech32); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const document = new IotaDocument(networkHrp); + + // Create a new method with PQ algorithm. + const fragment = await document.generateMethodPQC( + storage, + JwkPqMemStore.mldsaKeyType(), + JwsAlgorithm.MLDSA44, + "#0", + MethodScope.VerificationMethod(), + ); + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + const aliasOutput: AliasOutput = await didClient.newDidOutput(address, document); + + // Publish the Alias Output and get the published DID document. + const published = await didClient.publishDidOutput(secretManager, aliasOutput); + + return { address, document: published, fragment }; +} + +/** + * This example shows how to create a PQ Verifiable Presentation and validate it + */ +export async function pq() { + // =========================================================================== + // Step 1: Create identities for the issuer and the holder. + // =========================================================================== + + const client = new Client({ + primaryNode: API_ENDPOINT, + localPow: true, + }); + const didClient = new IotaIdentityClient(client); + + // Creates a new wallet and identity (see "0_create_did" example). + const issuerSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const issuerStorage: Storage = new Storage( + new JwkPqMemStore(), + new KeyIdMemStore(), + ); + let { document: issuerDocument, fragment: issuerFragment } = await createPQDid( + client, + issuerSecretManager, + issuerStorage, + ); + + // Create an identity for the holder, in this case also the subject. + const aliceSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const aliceStorage: Storage = new Storage( + new JwkPqMemStore(), + new KeyIdMemStore(), + ); + let { document: aliceDocument, fragment: aliceFragment } = await createPQDid( + client, + aliceSecretManager, + aliceStorage, + ); + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential. + // =========================================================================== + + const subject = { + id: aliceDocument.id(), + name: "Alice", + degreeName: "Bachelor of Science and Arts", + degreeType: "BachelorDegree", + GPA: "4.0", + }; + + // Create an unsigned `UniversityDegree` credential for Alice + const unsignedVc = new Credential({ + id: "https://example.edu/credentials/3732", + type: "UniversityDegreeCredential", + issuer: issuerDocument.id(), + credentialSubject: subject, + }); + + // Create a Credential JWT with the issuer's PQ verification method. + const credentialJwt = await issuerDocument.createCredentialJwtPqc( + issuerStorage, + issuerFragment, + unsignedVc, + new JwsSignatureOptions(), + ); + + const res = new JwtCredentialValidator(new PQJwsVerifier()).validate( + credentialJwt, + issuerDocument, + new JwtCredentialValidationOptions(), + FailFast.FirstError, + ); + console.log("credentialjwt validation", res.intoCredential()); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + + // The credential is then serialized to JSON and transmitted to the holder in a secure manner. + // Note that the credential is NOT published to the IOTA Tangle. It is sent and stored off-chain. + console.log(`Sending credential (as JWT) to the holder`, unsignedVc.toJSON()); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + const nonce = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + const expires = Timestamp.nowUTC().checkedAdd(Duration.minutes(10)); + + // =========================================================================== + // Step 5: Holder creates a verifiable presentation from the issued credential for the verifier to validate. + // =========================================================================== + + // Create a Verifiable Presentation from the Credential + const unsignedVp = new Presentation({ + holder: aliceDocument.id(), + verifiableCredential: [credentialJwt], + }); + + // Create a PQ JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + const presentationJwt = await aliceDocument.createPresentationJwtPqc( + aliceStorage, + aliceFragment, + unsignedVp, + new JwsSignatureOptions({ nonce }), + new JwtPresentationOptions({ expirationDate: expires }), + ); + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + console.log( + `Sending presentation (as JWT) to the verifier`, + unsignedVp.toJSON(), + ); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + const jwtPresentationValidationOptions = new JwtPresentationValidationOptions( + { + presentationVerifierOptions: new JwsVerificationOptions({ nonce }), + }, + ); + + const resolver = new Resolver({ + client: didClient, + }); + // Resolve the presentation holder. + const presentationHolderDID: CoreDID = JwtPresentationValidator.extractHolder(presentationJwt); + const resolvedHolder = await resolver.resolve( + presentationHolderDID.toString(), + ); + + // Validate presentation. Note that this doesn't validate the included credentials. + let decodedPresentation = new JwtPresentationValidator(new PQJwsVerifier()).validate( + presentationJwt, + resolvedHolder, + jwtPresentationValidationOptions, + ); + + // Validate the credentials in the presentation. + let credentialValidator = new JwtCredentialValidator(new PQJwsVerifier()); + let validationOptions = new JwtCredentialValidationOptions({ + subjectHolderRelationship: [ + presentationHolderDID.toString(), + SubjectHolderRelationship.AlwaysSubject, + ], + }); + + let jwtCredentials: Jwt[] = decodedPresentation + .presentation() + .verifiableCredential() + .map((credential) => { + const jwt = credential.tryIntoJwt(); + if (!jwt) { + throw new Error("expected a JWT credential"); + } else { + return jwt; + } + }); + + // Concurrently resolve the issuers' documents. + let issuers: string[] = []; + for (let jwtCredential of jwtCredentials) { + let issuer = JwtCredentialValidator.extractIssuerFromJwt(jwtCredential); + issuers.push(issuer.toString()); + } + let resolvedIssuers = await resolver.resolveMultiple(issuers); + + // Validate the credentials in the presentation. + for (let i = 0; i < jwtCredentials.length; i++) { + credentialValidator.validate( + jwtCredentials[i], + resolvedIssuers[i], + validationOptions, + FailFast.FirstError, + ); + } + + // Since no errors were thrown we know that the validation was successful. + console.log(`VP successfully validated`); +} diff --git a/bindings/wasm/examples/src/1_advanced/12_hybrid.ts b/bindings/wasm/examples/src/1_advanced/12_hybrid.ts new file mode 100644 index 0000000000..0c4fcf7add --- /dev/null +++ b/bindings/wasm/examples/src/1_advanced/12_hybrid.ts @@ -0,0 +1,281 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +import { + CoreDID, + Credential, + Duration, + FailFast, + IotaDocument, + IotaIdentityClient, + JwkPqMemStore, + JwsSignatureOptions, + JwsVerificationOptions, + Jwt, + PQJwsVerifier, + EdDSAJwsVerifier, + JwtCredentialValidationOptions, + JwtCredentialValidator, + JwtPresentationOptions, + JwtPresentationValidationOptions, + JwtPresentationValidator, + KeyIdMemStore, + MethodScope, + Presentation, + Resolver, + Storage, + SubjectHolderRelationship, + Timestamp, + CompositeAlgId, + JwtCredentialValidatorHybrid, + JwtPresentationValidatorHybrid, +} from "@iota/identity-wasm/node"; +import { Address, AliasOutput, Client, MnemonicSecretManager, SecretManager, SecretManagerType, Utils } from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +async function createHybridDid(client: Client, secretManager: SecretManagerType, storage: Storage): Promise<{ + address: Address; + document: IotaDocument; + fragment: string; +}> { + const didClient = new IotaIdentityClient(client); + const networkHrp: string = await didClient.getNetworkHrp(); + + const secretManagerInstance = new SecretManager(secretManager); + const walletAddressBech32 = (await secretManagerInstance.generateEd25519Addresses({ + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: networkHrp, + }))[0]; + + console.log("Wallet address Bech32:", walletAddressBech32); + + await ensureAddressHasFunds(client, walletAddressBech32); + + const address: Address = Utils.parseBech32Address(walletAddressBech32); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const document = new IotaDocument(networkHrp); + + // Create a new method with PQ/T algorithm. + const fragment = await document.generateMethodHybrid( + storage, + CompositeAlgId.IdMldsa44Ed25519, + "#0", + MethodScope.VerificationMethod(), + ); + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + const aliasOutput: AliasOutput = await didClient.newDidOutput(address, document); + + // Publish the Alias Output and get the published DID document. + const published = await didClient.publishDidOutput(secretManager, aliasOutput); + + return { address, document: published, fragment }; +} + +/** + * This example shows how to create an hybrid Verifiable Presentation and validate it + */ +export async function hybrid() { + // =========================================================================== + // Step 1: Create identities for the issuer and the holder. + // =========================================================================== + + const client = new Client({ + primaryNode: API_ENDPOINT, + localPow: true, + }); + const didClient = new IotaIdentityClient(client); + + // Creates a new wallet and identity (see "0_create_did" example). + const issuerSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const issuerStorage: Storage = new Storage( + new JwkPqMemStore(), + new KeyIdMemStore(), + ); + let { document: issuerDocument, fragment: issuerFragment } = await createHybridDid( + client, + issuerSecretManager, + issuerStorage, + ); + + // Create an identity for the holder, in this case also the subject. + const aliceSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const aliceStorage: Storage = new Storage( + new JwkPqMemStore(), + new KeyIdMemStore(), + ); + let { document: aliceDocument, fragment: aliceFragment } = await createHybridDid( + client, + aliceSecretManager, + aliceStorage, + ); + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential. + // =========================================================================== + + const subject = { + id: aliceDocument.id(), + name: "Alice", + degreeName: "Bachelor of Science and Arts", + degreeType: "BachelorDegree", + GPA: "4.0", + }; + + // Create an unsigned `UniversityDegree` credential for Alice + const unsignedVc = new Credential({ + id: "https://example.edu/credentials/3732", + type: "UniversityDegreeCredential", + issuer: issuerDocument.id(), + credentialSubject: subject, + }); + + // Create a Credential JWT with the issuer's hybrid verification method. + const credentialJwt = await issuerDocument.createCredentialJwtHybrid( + issuerStorage, + issuerFragment, + unsignedVc, + new JwsSignatureOptions(), + ); + + + const res = new JwtCredentialValidatorHybrid(new EdDSAJwsVerifier, new PQJwsVerifier()).validate( + credentialJwt, + issuerDocument, + new JwtCredentialValidationOptions(), + FailFast.FirstError, + ); + console.log("credentialjwt validation", res.intoCredential()); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + + // The credential is then serialized to JSON and transmitted to the holder in a secure manner. + // Note that the credential is NOT published to the IOTA Tangle. It is sent and stored off-chain. + console.log(`Sending credential (as JWT) to the holder`, unsignedVc.toJSON()); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + const nonce = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + const expires = Timestamp.nowUTC().checkedAdd(Duration.minutes(10)); + + // =========================================================================== + // Step 5: Holder creates a verifiable presentation from the issued credential for the verifier to validate. + // =========================================================================== + + // Create a Verifiable Presentation from the Credential + const unsignedVp = new Presentation({ + holder: aliceDocument.id(), + verifiableCredential: [credentialJwt], + }); + + // Create a Hybrid JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + const presentationJwt = await aliceDocument.createPresentationJwtHybrid( + aliceStorage, + aliceFragment, + unsignedVp, + new JwsSignatureOptions({ nonce }), + new JwtPresentationOptions({ expirationDate: expires }), + ); + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + console.log( + `Sending presentation (as JWT) to the verifier`, + unsignedVp.toJSON(), + ); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + const jwtPresentationValidationOptions = new JwtPresentationValidationOptions( + { + presentationVerifierOptions: new JwsVerificationOptions({ nonce }), + }, + ); + + const resolver = new Resolver({ + client: didClient, + }); + // Resolve the presentation holder. + const presentationHolderDID: CoreDID = JwtPresentationValidator.extractHolder(presentationJwt); + const resolvedHolder = await resolver.resolve( + presentationHolderDID.toString(), + ); + + // Validate presentation. Note that this doesn't validate the included credentials. + let decodedPresentation = new JwtPresentationValidatorHybrid(new EdDSAJwsVerifier, new PQJwsVerifier()).validate( + presentationJwt, + resolvedHolder, + jwtPresentationValidationOptions, + ); + + // Validate the hybrid credentials in the presentation. + let credentialValidator = new JwtCredentialValidatorHybrid(new EdDSAJwsVerifier, new PQJwsVerifier()); + let validationOptions = new JwtCredentialValidationOptions({ + subjectHolderRelationship: [ + presentationHolderDID.toString(), + SubjectHolderRelationship.AlwaysSubject, + ], + }); + + let jwtCredentials: Jwt[] = decodedPresentation + .presentation() + .verifiableCredential() + .map((credential) => { + const jwt = credential.tryIntoJwt(); + if (!jwt) { + throw new Error("expected a JWT credential"); + } else { + return jwt; + } + }); + + // Concurrently resolve the issuers' documents. + let issuers: string[] = []; + for (let jwtCredential of jwtCredentials) { + let issuer = JwtCredentialValidator.extractIssuerFromJwt(jwtCredential); + issuers.push(issuer.toString()); + } + let resolvedIssuers = await resolver.resolveMultiple(issuers); + + // Validate the credentials in the presentation. + for (let i = 0; i < jwtCredentials.length; i++) { + credentialValidator.validate( + jwtCredentials[i], + resolvedIssuers[i], + validationOptions, + FailFast.FirstError, + ); + } + + // Since no errors were thrown we know that the validation was successful. + console.log(`VP successfully validated`); +} diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index c88ee419c0..b64e5d2031 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -1,5 +1,8 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ import { createIdentity } from "./0_basic/0_create_did"; import { updateIdentity } from "./0_basic/1_update_did"; @@ -11,6 +14,8 @@ import { createVP } from "./0_basic/6_create_vp"; import { revokeVC } from "./0_basic/7_revoke_vc"; import { didControlsDid } from "./1_advanced/0_did_controls_did"; import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc"; +import { pq } from "./1_advanced/11_pq"; +import { hybrid } from "./1_advanced/12_hybrid"; import { didIssuesNft } from "./1_advanced/1_did_issues_nft"; import { nftOwnsDid } from "./1_advanced/2_nft_owns_did"; import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens"; @@ -67,6 +72,10 @@ async function main() { return await zkp_revocation(); case "10_sd_jwt_vc": return await sdJwtVc(); + case "11_pq": + return await pq(); + case "12_hybrid": + return await hybrid(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/lib/index.ts b/bindings/wasm/lib/index.ts index dd46503528..e6b76d9b1c 100644 --- a/bindings/wasm/lib/index.ts +++ b/bindings/wasm/lib/index.ts @@ -1,10 +1,13 @@ // Copyright 2021-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 - +/* + * Modifications Copyright 2024 Fondazione LINKS. + */ import "./append_functions.js"; export * from "./iota_identity_client.js"; export * from "./jose"; export * from "./jwk_storage"; +export * from "./jwk_storage_pq"; export * from "./key_id_storage"; - +export * from "./pq_verifier"; export * from "~identity_wasm"; diff --git a/bindings/wasm/lib/jwk_storage_pq.ts b/bindings/wasm/lib/jwk_storage_pq.ts new file mode 100644 index 0000000000..437abf4577 --- /dev/null +++ b/bindings/wasm/lib/jwk_storage_pq.ts @@ -0,0 +1,250 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 +import * as ed from "@noble/ed25519"; +import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; +import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStoragePQ, JwkStorage } from "~identity_wasm"; +import { EdCurve, JwkType, JwsAlgorithm} from "./jose"; + +type Ed25519PrivateKey = Uint8Array; +type Ed25519PublicKey = Uint8Array; + + +//JkwStorage for PQ and PQ/T examples +export class JwkPqMemStore implements JwkStorage, JwkStoragePQ{ + /** The map from key identifiers to Jwks. */ + private _keys: Map; + + /** Creates a new, empty `MemStore` instance. */ + constructor() { + this._keys = new Map(); + } + + public static mldsaKeyType(): string { + return "AKP"; + } + + public static ed25519KeyType(): string { + return "Ed25519"; + } + + + private _get_key(keyId: string): Jwk | undefined { + return this._keys.get(keyId); + } + + public async generate(keyType: string, algorithm: JwsAlgorithm): Promise { + if (keyType !== JwkPqMemStore.ed25519KeyType()) { + throw new Error(`unsupported key type ${keyType}`); + } + + if (algorithm !== JwsAlgorithm.EdDSA) { + throw new Error(`unsupported algorithm`); + } + + const keyId = randomKeyId(); + const privKey: Ed25519PrivateKey = ed.utils.randomPrivateKey(); + + const publicKey: Ed25519PublicKey = await ed.getPublicKey(privKey); + const jwk = await encodeJwk(privKey, publicKey, algorithm); + + this._keys.set(keyId, jwk); + + const publicJWK = jwk?.toPublic(); + if (!publicJWK) { + throw new Error(`JWK is not a public key`); + } + + return new JwkGenOutput(keyId, publicJWK); + } + + public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { + + if (keyType !== JwkPqMemStore.mldsaKeyType()) { + throw new Error(`unsupported key type ${keyType}`); + } + + const seed = new TextEncoder().encode(randomKeyId()) + let keys; + if (algorithm === JwsAlgorithm.MLDSA44) { + keys = ml_dsa44.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA65) { + keys = ml_dsa65.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA87) { + keys = ml_dsa87.keygen(seed); + } else { + throw new Error(`unsupported algorithm`); + } + + const keyId = randomKeyId(); + + const jwk = await encodeJwk(keys.secretKey, keys.publicKey, algorithm); + + if(jwk == undefined) + throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); + + this._keys.set(keyId, jwk); + + const publicJWK = jwk?.toPublic(); + if (!publicJWK) { + throw new Error(`JWK is not a public key`); + } + + return new JwkGenOutput(keyId, publicJWK); + + } + + public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { + let alg = publicKey.alg(); + let signature = null; + if(alg === undefined) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + if (alg !== JwsAlgorithm.EdDSA && alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported JWS algorithm"); + } else { + if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string) && publicKey.paramsAkp()?.pub === undefined) + { + throw new Error("unsupported Okp parameter"); + } + } + + const jwk = this._keys.get(keyId); + + if (jwk) { + + const [privateKey, _] = decodeJwk(jwk); + + if (alg == JwsAlgorithm.EdDSA){ + signature = await ed.sign(data, privateKey); + } else { + if(alg == JwsAlgorithm.MLDSA44) + signature = ml_dsa44.sign(privateKey, data); + else if(alg == JwsAlgorithm.MLDSA65) + signature = ml_dsa65.sign(privateKey, data); + else if(alg == JwsAlgorithm.MLDSA87) + signature = ml_dsa87.sign(privateKey, data); + else + throw new Error("unsupported algorithm"); + + } + } else { + throw new Error(`key with id ${keyId} not found`); + } + return signature; + } + + public async insert(jwk: Jwk): Promise { + const keyId = randomKeyId(); + + if (!jwk.isPrivate) { + throw new Error("expected a JWK with all private key components set"); + } + + if (!jwk.alg()) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + this._keys.set(keyId, jwk); + + return keyId; + } + + public async delete(keyId: string): Promise { + this._keys.delete(keyId); + } + + public async exists(keyId: string): Promise { + return this._keys.has(keyId); + } + + public count(): number { + return this._keys.size; + } +} + +// Encodes a Ed25519 keypair into a Jwk. +async function encodeJwk(privateKey: Uint8Array, publicKey: Uint8Array, alg: JwsAlgorithm): Promise { + let pub = encodeB64(publicKey); + let priv = encodeB64(privateKey); + + if (alg === JwsAlgorithm.EdDSA) { + return new Jwk({ + "kty": JwkType.Okp, + crv: "Ed25519", + d: priv, + x: pub, + alg, + }); + } else { + return new Jwk({ + "kty": JwkType.Akp, + pub: pub, + priv: priv, + alg, + }); + } + +} + +function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { + if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && + jwk.alg()! !== JwsAlgorithm.MLDSA65 && + jwk.alg()! !== JwsAlgorithm.MLDSA87 && + jwk.alg()! !== JwsAlgorithm.EdDSA) { + throw new Error("unsupported `alg`"); + } + if (jwk.alg()! === JwsAlgorithm.EdDSA) { + const paramsOkp = jwk.paramsOkp(); + if (paramsOkp) { + const d = paramsOkp.d; + + if (d) { + const textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(d)); + const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } + } else { + throw new Error("expected Okp params"); + } + } else { + const paramsPQ = jwk.paramsAkp(); + + if (paramsPQ) { + const priv = paramsPQ.priv; + + if (priv) { + let textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(priv)); + const publicKey = decodeB64(textEncoder.encode(paramsPQ.pub)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } + } else { + throw new Error("expected Okp params"); + } + + } + +} + +// Returns a random number between `min` and `max` (inclusive). +// SAFETY NOTE: This is not cryptographically secure randomness and thus not suitable for production use. +// It suffices for our testing implementation however and avoids an external dependency. +function getRandomNumber(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// Returns a random key id. +function randomKeyId(): string { + const randomness = new Uint8Array(20); + for (let index = 0; index < randomness.length; index++) { + randomness[index] = getRandomNumber(0, 255); + } + + return encodeB64(randomness); +} diff --git a/bindings/wasm/lib/pq_verifier.ts b/bindings/wasm/lib/pq_verifier.ts new file mode 100644 index 0000000000..37989d9497 --- /dev/null +++ b/bindings/wasm/lib/pq_verifier.ts @@ -0,0 +1,48 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; +import { decodeB64, Jwk, IJwsVerifier} from "~identity_wasm"; +import { JwsAlgorithm } from "./jose"; + +export class PQJwsVerifier implements IJwsVerifier{ + + public verify (alg: JwsAlgorithm, signingInput: Uint8Array, decodedSignature: Uint8Array, publicKey: Jwk): void{ + let res = false; + if (alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported JWS algorithm"); + } + + const pubKey = decodeJwk(publicKey); + + if (alg === JwsAlgorithm.MLDSA44) { + res = ml_dsa44.verify(pubKey, signingInput, decodedSignature); + } else if (alg === JwsAlgorithm.MLDSA65) { + res = ml_dsa65.verify(pubKey, signingInput, decodedSignature); + } else if (alg === JwsAlgorithm.MLDSA87) { + res = ml_dsa87.verify(pubKey, signingInput, decodedSignature); + } + if (!res) { + throw new Error("signature verification failed"); + } + } + +} + +function decodeJwk(jwk: Jwk): Uint8Array { + if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && jwk.alg()! !== JwsAlgorithm.MLDSA65 && jwk.alg()! !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported `alg`"); + } + + const paramsPQ = jwk.paramsAkp(); + + if (paramsPQ) { + let textEncoder = new TextEncoder(); + return decodeB64(textEncoder.encode(paramsPQ.pub)); + } else { + throw new Error("expected Okp params"); + } +} + + + From b2288cfd5542f53ce97b6371c10ea49d04f12711 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 14 Apr 2025 11:45:06 +0200 Subject: [PATCH 153/163] add noble pq --- bindings/wasm/package-lock.json | 14 ++++++++++++++ bindings/wasm/package.json | 1 + 2 files changed, 15 insertions(+) diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index 33f9d564f9..69abb45ab4 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", + "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", @@ -6635,6 +6636,19 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, + "@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + }, + "@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "requires": { + "@noble/hashes": "1.6.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index a63db388a7..652f802fa0 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -79,6 +79,7 @@ }, "dependencies": { "@noble/ed25519": "^1.7.3", + "@noble/post-quantum": "^0.2.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", From f7dd7c3a4223d829d66319473598bdcfd2238ddb Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 14 Apr 2025 11:45:25 +0200 Subject: [PATCH 154/163] fix --- identity_storage/src/key_storage/memstore.rs | 15 +++------------ .../src/storage/did_jwk_document_ext.rs | 4 ++-- .../src/storage/hybrid_jws_document_ext.rs | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 18fc2a7b70..3c2cf1d89f 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -206,17 +206,10 @@ impl JwkMemStore { /// The BLS12381G2 key type pub const BLS12381G2_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381G2_KEY_TYPE_STR); - const ML_DSA: &'static str = "ML-DSA"; + const PQ_KEY_TYPE_STR: &'static str = "AKP"; /// ML-DSA algorithms key types; - pub const ML_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ML_DSA); + pub const PQ_KEY_TYPE: KeyType = KeyType::from_static_str(Self::PQ_KEY_TYPE_STR); - const SLH_DSA: &'static str = "SLH-DSA"; - /// SLH-DSA algorithms key types; - pub const SLH_DSA_KEY_TYPE: KeyType = KeyType::from_static_str(Self::SLH_DSA); - - const FALCON: &'static str = "FALCON"; - /// FALCON algorithms key types; - pub const FALCON_KEY_TYPE: KeyType = KeyType::from_static_str(Self::FALCON); } impl MemStoreKeyType { @@ -377,9 +370,7 @@ mod pqc_liboqs { #[cfg_attr(feature = "send-sync-storage", async_trait)] impl JwkStoragePQ for JwkMemStore { async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { - if key_type != JwkMemStore::ML_DSA_KEY_TYPE - && key_type != JwkMemStore::SLH_DSA_KEY_TYPE - && key_type != JwkMemStore::FALCON_KEY_TYPE + if key_type != JwkMemStore::PQ_KEY_TYPE { return Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index b7c66099d2..2fca498b0a 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -203,13 +203,13 @@ impl DidJwkDocumentExt for CoreDocument { { let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg { CompositeAlgId::IdMldsa44Ed25519 => ( - KeyType::from_static_str("ML-DSA"), + KeyType::from_static_str("AKP"), JwsAlgorithm::ML_DSA_44, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), CompositeAlgId::IdMldsa65Ed25519 => ( - KeyType::from_static_str("ML-DSA"), + KeyType::from_static_str("AKP"), JwsAlgorithm::ML_DSA_65, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index bc0485d30c..d30a87c29a 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -54,13 +54,13 @@ macro_rules! generate_method_hybrid_for_document_type { { let (pq_key_type, pq_alg, trad_key_type, trad_alg) = match alg_id { CompositeAlgId::IdMldsa44Ed25519 => ( - KeyType::from_static_str("ML-DSA"), + KeyType::from_static_str("AKP"), JwsAlgorithm::ML_DSA_44, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, ), CompositeAlgId::IdMldsa65Ed25519 => ( - KeyType::from_static_str("ML-DSA"), + KeyType::from_static_str("AKP"), JwsAlgorithm::ML_DSA_65, KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA, From df139063b46568303b8445ff2256565930e8bbd8 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Mon, 14 Apr 2025 12:18:25 +0200 Subject: [PATCH 155/163] fix --- examples/1_advanced/12_pq.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/1_advanced/12_pq.rs b/examples/1_advanced/12_pq.rs index 24de5c14b6..025e5f4908 100644 --- a/examples/1_advanced/12_pq.rs +++ b/examples/1_advanced/12_pq.rs @@ -114,7 +114,7 @@ async fn main() -> anyhow::Result<()> { &client, &mut secret_manager_issuer, &storage_issuer, - JwkMemStore::ML_DSA_KEY_TYPE, + JwkMemStore::PQ_KEY_TYPE, JwsAlgorithm::ML_DSA_87, ) .await?; @@ -131,7 +131,7 @@ async fn main() -> anyhow::Result<()> { &client, &mut secret_manager_holder, &storage_holder, - JwkMemStore::SLH_DSA_KEY_TYPE, + JwkMemStore::PQ_KEY_TYPE, JwsAlgorithm::SLH_DSA_SHA2_128s, ) .await?; From 03356745390261952fdb78ea68b760e035f83add Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 16 Apr 2025 12:22:42 +0200 Subject: [PATCH 156/163] hybrid signature implementation update to the latest draft --- identity_jose/src/jws/decoder.rs | 52 ++++++++++------- identity_pqc_verifier/src/oqs_verifier.rs | 45 ++++++++++++++- identity_pqc_verifier/src/pqc_verifier.rs | 4 ++ .../src/key_storage/jwk_storage_pqc.rs | 4 +- identity_storage/src/key_storage/memstore.rs | 32 ++++++++--- .../src/storage/hybrid_jws_document_ext.rs | 57 +++++++++++-------- .../src/storage/pqc_jws_document_ext.rs | 2 +- 7 files changed, 142 insertions(+), 54 deletions(-) diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 5752fb47af..7fd2626182 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -7,7 +7,6 @@ use core::str; use std::borrow::Cow; -use std::ops::Deref; use crate::error::Error; use crate::error::Result; @@ -20,8 +19,6 @@ use crate::jwu::decode_b64_json; use crate::jwu::filter_non_empty_bytes; use crate::jwu::parse_utf8; use crate::jwu::validate_jws_headers; -use crypto::hashes::Digest; - use super::JwsVerifier; use super::VerificationInput; @@ -183,7 +180,7 @@ impl<'a> JwsValidationItem<'a> { }) } - ///Hybrid verify + ///Hybrid signature verify, based on [Composite ML-DSA draft](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsaverify) pub fn verify_hybrid( self, traditional_verifier: &TRV, @@ -211,36 +208,52 @@ impl<'a> JwsValidationItem<'a> { // Extract and validate alg from the protected header. let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?; + + // M' = Prefix || Domain || len(ctx) || ctx || M let (t_alg, pq_alg, signing_input, traditional_signature_len) = match alg { JwsAlgorithm::IdMldsa44Ed25519 => { - let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E - ]; - input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + //Prefix: CompositeAlgorithmSignatures2025 + let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); + + //Domain: id-MLDSA44-Ed25519 (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators) + input.extend_from_slice(&[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E]); + + //len(ctx) = 0 + input.push(0x00); + + //M + input.extend(signing_input); ( JwsAlgorithm::EdDSA, - JwsAlgorithm::ML_DSA_44, + JwsAlgorithm::IdMldsa44Ed25519, input, crypto::signatures::ed25519::Signature::LENGTH, ) } JwsAlgorithm::IdMldsa65Ed25519 => { - let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47, - ]; - input.extend(crypto::hashes::sha::Sha512::digest(signing_input).deref().to_vec()); + //Prefix: CompositeAlgorithmSignatures2025 + let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); + + //Domain: id-MLDSA65-Ed25519 https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators + input.extend_from_slice(&[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47]); + + //len(ctx) = 0 + input.push(0x00); + + //M + input.extend(signing_input); + ( JwsAlgorithm::EdDSA, - JwsAlgorithm::ML_DSA_65, + JwsAlgorithm::IdMldsa65Ed25519, input, crypto::signatures::ed25519::Signature::LENGTH, ) } _ => return Err(Error::JwsAlgorithmParsingError), }; - + traditional_pk.check_alg(t_alg.name())?; - pq_pk.check_alg(pq_alg.name())?; let extracted_signature_t = &decoded_signature[..traditional_signature_len]; let extracted_signature_pq = &decoded_signature[traditional_signature_len..]; @@ -252,7 +265,7 @@ impl<'a> JwsValidationItem<'a> { decoded_signature: extracted_signature_t.into(), }; - // Call verifier + // Call the traditional verifier traditional_verifier .verify(input1, traditional_pk) .map_err(Error::SignatureVerificationError)?; @@ -262,11 +275,12 @@ impl<'a> JwsValidationItem<'a> { signing_input: signing_input.into(), decoded_signature: extracted_signature_pq.into(), }; - // Call verifier + + // Call the PQ verifier pq_verifier .verify(input2, pq_pk) .map_err(Error::SignatureVerificationError)?; - + Ok(DecodedJws { protected, unprotected, diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index 1f37f079ad..d980b28f6e 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -18,7 +18,7 @@ pub struct OQSVerifier; impl OQSVerifier { /// Verify a JWS signature secured with the on the [`Algorithm`] defined in liboqs. pub fn verify(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { - // Obtain an ML-DSA-44 public key. + let params: &JwkParamsAKP = public_key .try_akp_params() .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; @@ -51,6 +51,49 @@ impl OQSVerifier { .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?, ) } + + /// Verify a JWS signature signed with a ctx and secured with the on the [`Algorithm`] defined in liboqs, used in hybrid signature. + /// The ctx value is set as the Domain separator value for binding the signature to the Composite OID, as definied in [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsaverify) + pub fn verify_hybrid_signature(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { + + let params: &JwkParamsAKP = public_key + .try_akp_params() + .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; + + let pk = identity_jose::jwu::decode_b64(params.public.as_str()).map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure) + .with_custom_message("could not decode 'pub' parameter from jwk") + })?; + + oqs::init(); + + let scheme = Sig::new(alg).map_err(|_| { + SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified) + .with_custom_message("signature scheme init failed") + })?; + + let public_key = scheme + .public_key_from_bytes(&pk) + .ok_or(SignatureVerificationError::new( + SignatureVerificationErrorKind::KeyDecodingFailure, + ))?; + + let signature = scheme + .signature_from_bytes(input.decoded_signature.deref()) + .ok_or(SignatureVerificationErrorKind::InvalidSignature)?; + + let ctx = match alg { + Algorithm::MlDsa44 => &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E], + Algorithm::MlDsa65 => &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47], + _ => return Err(SignatureVerificationError::new(SignatureVerificationErrorKind::UnsupportedKeyType)), + }; + + Ok( + scheme + .verify_with_ctx_str(&input.signing_input, signature, ctx, public_key) + .map_err(|_| SignatureVerificationErrorKind::InvalidSignature)?, + ) + } } #[cfg(test)] diff --git a/identity_pqc_verifier/src/pqc_verifier.rs b/identity_pqc_verifier/src/pqc_verifier.rs index 4789d168d4..72a4a9d9bd 100644 --- a/identity_pqc_verifier/src/pqc_verifier.rs +++ b/identity_pqc_verifier/src/pqc_verifier.rs @@ -44,6 +44,10 @@ impl JwsVerifier for PQCJwsVerifier { JwsAlgorithm::ML_DSA_65 => OQSVerifier::verify(input, public_key, Algorithm::MlDsa65), #[cfg(feature = "ML_DSA_87")] JwsAlgorithm::ML_DSA_87 => OQSVerifier::verify(input, public_key, Algorithm::MlDsa87), + #[cfg(feature = "ML_DSA_44")] + JwsAlgorithm::IdMldsa44Ed25519 => OQSVerifier::verify_hybrid_signature(input, public_key, Algorithm::MlDsa44), + #[cfg(feature = "ML_DSA_65")] + JwsAlgorithm::IdMldsa65Ed25519 => OQSVerifier::verify_hybrid_signature(input, public_key, Algorithm::MlDsa65), #[cfg(feature = "SLH_DSA_SHA2_128s")] JwsAlgorithm::SLH_DSA_SHA2_128s => OQSVerifier::verify(input, public_key, Algorithm::SphincsSha2128sSimple), diff --git a/identity_storage/src/key_storage/jwk_storage_pqc.rs b/identity_storage/src/key_storage/jwk_storage_pqc.rs index 3d52304e44..a058e35c58 100644 --- a/identity_storage/src/key_storage/jwk_storage_pqc.rs +++ b/identity_storage/src/key_storage/jwk_storage_pqc.rs @@ -18,6 +18,6 @@ pub trait JwkStoragePQ: JwkStorage { /// Generates a JWK representing a PQ key async fn generate_pq_key(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult; - /// Sign the provided `data` using a PQ algorithm - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult>; + /// Sign the provided `data` using a PQ algorithm, ctx is optional for the ctx paramter of the algorithm ML-DSA + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk, ctx: Option<&[u8]>) -> KeyStorageResult>; } \ No newline at end of file diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 3c2cf1d89f..a3eb0fa3c8 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -447,7 +447,7 @@ mod pqc_liboqs { Ok(JwkGenOutput::new(kid, public_jwk)) } - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk, ctx: Option<&[u8]>) -> KeyStorageResult> { let jwk_store: RwLockReadGuard<'_, JwkKeyStore> = self.jwk_store.read().await; // Extract the required alg from the given public key @@ -523,13 +523,29 @@ mod pqc_liboqs { KeyStorageError::new(KeyStorageErrorKind::Unspecified) .with_custom_message(format!("invalid private key")), )?; - - let signature = scheme.sign(&data, secret_key).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature computation failed")) - .with_source(err) - })?; - + + let signature: oqs::sig::Signature; + + if let Some(ctx) = ctx { + if !scheme.has_ctx_str_support() { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature with ctx is not supported with this algorithm")) + ); + } + signature = scheme.sign_with_ctx_str(&data, ctx, secret_key).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature computation failed")) + .with_source(err) + })?; + } else { + signature = scheme.sign(&data, secret_key).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("signature computation failed")) + .with_source(err) + })?; + } + Ok(signature.into_vec()) } } diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index d30a87c29a..3a667cd649 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -1,8 +1,6 @@ // Copyright 2024 Fondazione Links // SPDX-License-Identifier: Apache-2.0 -use std::ops::Deref; - use super::JwkStorageDocumentError as Error; use crate::try_undo_key_generation; use crate::JwkGenOutput; @@ -17,7 +15,6 @@ use crate::MethodDigest; use crate::Storage; use crate::StorageResult; use async_trait::async_trait; -use crypto::hashes::Digest; use identity_core::common::Object; use identity_credential::credential::Credential; use identity_credential::credential::Jws; @@ -214,6 +211,7 @@ impl JwkDocumentExtHybrid for CoreDocument { generate_method_hybrid_core_document(self, storage, alg_id, fragment, scope).await } + /// Hybrid signature implementation based on [Composite ML-DSA draft](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsasign) async fn create_jws( &self, storage: &Storage, @@ -309,28 +307,41 @@ impl JwkDocumentExtHybrid for CoreDocument { let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) .map_err(|err| Error::EncodingError(err.into()))?; - let signing_input = match alg { + //M' = Prefix || Domain || len(ctx) || ctx || M + //let prefix = b"CompositeAlgorithmSignatures2025"; + + let (signing_input, ctx) = match alg { JwsAlgorithm::IdMldsa44Ed25519 => { - let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E, - ]; - input.extend( - crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) - .deref() - .to_vec(), - ); - input + //Prefix: CompositeAlgorithmSignatures2025 + let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); + + //Domain: id-MLDSA44-Ed25519 (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators) + let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E]; + input.extend_from_slice(domain); + + //len(ctx) = 0 + input.push(0x00); + + //M + input.extend(jws_encoder.signing_input()); + + (input, domain) } JwsAlgorithm::IdMldsa65Ed25519 => { - let mut input = vec![ - 0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47, - ]; - input.extend( - crypto::hashes::sha::Sha512::digest(jws_encoder.signing_input()) - .deref() - .to_vec(), - ); - input + //Prefix: CompositeAlgorithmSignatures2025 + let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); + + //Domain: id-MLDSA65-Ed25519 https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators + let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47]; + input.extend_from_slice(domain); + + //len(ctx) = 0 + input.push(0x00); + + //M + input.extend(jws_encoder.signing_input()); + + (input, domain) } _ => return Err(Error::InvalidJwsAlgorithm), }; @@ -339,7 +350,7 @@ impl JwkDocumentExtHybrid for CoreDocument { .await .map_err(Error::KeyStorageError)?; - let signature_pq = ::pq_sign(storage.key_storage(), &pq_key_id, &signing_input, pq_jwk) + let signature_pq = ::pq_sign(storage.key_storage(), &pq_key_id, &signing_input, pq_jwk, Some(ctx)) .await .map_err(Error::KeyStorageError)?; diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index 9f9ee63574..e534355760 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -225,7 +225,7 @@ impl JwsDocumentExtPQC for CoreDocument { let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) .map_err(|err| Error::EncodingError(err.into()))?; - let signature = ::pq_sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk) + let signature = ::pq_sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk, None) .await .map_err(Error::KeyStorageError)?; Ok(Jws::new(jws_encoder.into_jws(&signature))) From 18c4894349cec55d17313adc2ff43f3a11ac9adb Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 17 Apr 2025 10:12:08 +0200 Subject: [PATCH 157/163] WASM: hydrid signature update --- bindings/wasm/lib/jose/jws_algorithm.ts | 4 ++ bindings/wasm/lib/jwk_storage_pq.ts | 51 +++++++++++++------ bindings/wasm/lib/pq_verifier.ts | 23 +++++++-- bindings/wasm/src/storage/jwk_storage_pqc.rs | 17 +++++-- .../src/storage/hybrid_jws_document_ext.rs | 2 + .../src/storage/pqc_jws_document_ext.rs | 1 + 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/bindings/wasm/lib/jose/jws_algorithm.ts b/bindings/wasm/lib/jose/jws_algorithm.ts index 809979449a..edfa545a06 100644 --- a/bindings/wasm/lib/jose/jws_algorithm.ts +++ b/bindings/wasm/lib/jose/jws_algorithm.ts @@ -38,5 +38,9 @@ export const enum JwsAlgorithm { MLDSA65 = "ML-DSA-65", /** ML-DSA-87 */ MLDSA87 = "ML-DSA-87", + /** ML-DSA-44 in hybrid signature*/ + IdMldsa44Ed25519 = "id-MLDSA44-Ed25519", + /** ML-DSA-65 in hybrid signature*/ + IdMldsa65Ed25519 = "id-MLDSA65-Ed25519", } diff --git a/bindings/wasm/lib/jwk_storage_pq.ts b/bindings/wasm/lib/jwk_storage_pq.ts index 437abf4577..04058ec027 100644 --- a/bindings/wasm/lib/jwk_storage_pq.ts +++ b/bindings/wasm/lib/jwk_storage_pq.ts @@ -96,14 +96,15 @@ export class JwkPqMemStore implements JwkStorage, JwkStoragePQ{ public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { let alg = publicKey.alg(); let signature = null; + if(alg === undefined) { throw new Error("expected a Jwk with an `alg` parameter"); } - if (alg !== JwsAlgorithm.EdDSA && alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + if (alg !== JwsAlgorithm.EdDSA ) { throw new Error("unsupported JWS algorithm"); } else { - if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string) && publicKey.paramsAkp()?.pub === undefined) + if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) { throw new Error("unsupported Okp parameter"); } @@ -111,23 +112,43 @@ export class JwkPqMemStore implements JwkStorage, JwkStoragePQ{ const jwk = this._keys.get(keyId); + if (jwk) { + const [privateKey, _] = decodeJwk(jwk); + signature = await ed.sign(data, privateKey); + + } else { + throw new Error(`key with id ${keyId} not found`); + } + return signature; + } + + public async signPQ(keyId: string, data: Uint8Array, publicKey: Jwk, ctx: Uint8Array|undefined ): Promise { + let alg = publicKey.alg(); + let signature = null; + + if(alg === undefined) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + if (alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported JWS algorithm"); + } + + const jwk = this._keys.get(keyId); + if (jwk) { const [privateKey, _] = decodeJwk(jwk); - if (alg == JwsAlgorithm.EdDSA){ - signature = await ed.sign(data, privateKey); - } else { - if(alg == JwsAlgorithm.MLDSA44) - signature = ml_dsa44.sign(privateKey, data); - else if(alg == JwsAlgorithm.MLDSA65) - signature = ml_dsa65.sign(privateKey, data); - else if(alg == JwsAlgorithm.MLDSA87) - signature = ml_dsa87.sign(privateKey, data); - else - throw new Error("unsupported algorithm"); - - } + if(alg == JwsAlgorithm.MLDSA44) + signature = ml_dsa44.sign(privateKey, data, ctx); + else if(alg == JwsAlgorithm.MLDSA65) + signature = ml_dsa65.sign(privateKey, data, ctx); + else if(alg == JwsAlgorithm.MLDSA87) + signature = ml_dsa87.sign(privateKey, data, ctx); + else + throw new Error("unsupported algorithm"); + } else { throw new Error(`key with id ${keyId} not found`); } diff --git a/bindings/wasm/lib/pq_verifier.ts b/bindings/wasm/lib/pq_verifier.ts index 37989d9497..9fa5582d58 100644 --- a/bindings/wasm/lib/pq_verifier.ts +++ b/bindings/wasm/lib/pq_verifier.ts @@ -9,16 +9,29 @@ export class PQJwsVerifier implements IJwsVerifier{ public verify (alg: JwsAlgorithm, signingInput: Uint8Array, decodedSignature: Uint8Array, publicKey: Jwk): void{ let res = false; - if (alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + let ctx = undefined; + + if (alg !== JwsAlgorithm.MLDSA44 && + alg !== JwsAlgorithm.MLDSA65 && + alg !== JwsAlgorithm.MLDSA87 && + alg !== JwsAlgorithm.IdMldsa44Ed25519 && + alg !== JwsAlgorithm.IdMldsa65Ed25519) { throw new Error("unsupported JWS algorithm"); } const pubKey = decodeJwk(publicKey); - if (alg === JwsAlgorithm.MLDSA44) { - res = ml_dsa44.verify(pubKey, signingInput, decodedSignature); - } else if (alg === JwsAlgorithm.MLDSA65) { - res = ml_dsa65.verify(pubKey, signingInput, decodedSignature); + //Domain separator for hybrid signatures + if (alg === JwsAlgorithm.IdMldsa44Ed25519) { + ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 62]); + } else if (alg === JwsAlgorithm.IdMldsa65Ed25519) { + ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 71]); + } + + if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.IdMldsa44Ed25519) { + res = ml_dsa44.verify(pubKey, signingInput, decodedSignature, ctx); + } else if (alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.IdMldsa65Ed25519) { + res = ml_dsa65.verify(pubKey, signingInput, decodedSignature, ctx); } else if (alg === JwsAlgorithm.MLDSA87) { res = ml_dsa87.verify(pubKey, signingInput, decodedSignature); } diff --git a/bindings/wasm/src/storage/jwk_storage_pqc.rs b/bindings/wasm/src/storage/jwk_storage_pqc.rs index a97a284a83..aef570adb8 100644 --- a/bindings/wasm/src/storage/jwk_storage_pqc.rs +++ b/bindings/wasm/src/storage/jwk_storage_pqc.rs @@ -20,12 +20,18 @@ use js_sys::Array; use crate::jose::WasmJwk; use js_sys::Uint8Array; + +use crate::common::PromiseUint8Array; + #[wasm_bindgen] extern "C" { #[wasm_bindgen(method, js_name = generatePQKey)] pub fn _generate_pq_key(this: &WasmJwkStorage, key_type: String, alg: String) -> PromiseJwkGenOutput; + #[wasm_bindgen(method, js_name = signPQ)] + pub fn _pq_sign(this: &WasmJwkStorage, key_id: String, data: Vec, public_key: WasmJwk, ctx: Option<&[u8]>) -> PromiseUint8Array; + } @@ -37,12 +43,13 @@ impl JwkStoragePQ for WasmJwkStorage { result.into() } - async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { - let promise: Promise = Promise::resolve(&WasmJwkStorage::sign( + async fn pq_sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk, ctx: Option<&[u8]>) -> KeyStorageResult> { + let promise: Promise = Promise::resolve(&WasmJwkStorage::_pq_sign( self, key_id.clone().into(), data.to_owned(), - WasmJwk(public_key.clone()), + WasmJwk(public_key.clone(),), + ctx )); let result: JsValueResult = JsFuture::from(promise).await.into(); result.to_key_storage_error().map(uint8array_to_bytes)? @@ -54,10 +61,12 @@ impl JwkStoragePQ for WasmJwkStorage { const JWK_STORAGE_PQ: &'static str = r#" /** Secure storage for cryptographic keys represented as JWKs. */ interface JwkStoragePQ { - /** Generate a new key represented as a JSON Web Key. + /** Generate a new PQ key represented as a JSON Web Key. * * It's recommend that the implementer exposes constants for the supported key type string. */ generatePQKey: (keyType: string, algorithm: JwsAlgorithm) => Promise; + + signPQ: (keyId: string, data: Uint8Array, publicKey: Jwk, ctx: Uint8Array|undefined ) => Promise; }"#; fn uint8array_to_bytes(value: JsValue) -> KeyStorageResult> { diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 3a667cd649..0bea0005bc 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -317,6 +317,7 @@ impl JwkDocumentExtHybrid for CoreDocument { //Domain: id-MLDSA44-Ed25519 (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators) let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E]; + input.extend_from_slice(domain); //len(ctx) = 0 @@ -333,6 +334,7 @@ impl JwkDocumentExtHybrid for CoreDocument { //Domain: id-MLDSA65-Ed25519 https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47]; + input.extend_from_slice(domain); //len(ctx) = 0 diff --git a/identity_storage/src/storage/pqc_jws_document_ext.rs b/identity_storage/src/storage/pqc_jws_document_ext.rs index e534355760..01a0e8e85e 100644 --- a/identity_storage/src/storage/pqc_jws_document_ext.rs +++ b/identity_storage/src/storage/pqc_jws_document_ext.rs @@ -225,6 +225,7 @@ impl JwsDocumentExtPQC for CoreDocument { let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options) .map_err(|err| Error::EncodingError(err.into()))?; + let signature = ::pq_sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk, None) .await .map_err(Error::KeyStorageError)?; From 1f1a4e2e4ef58f5f4482e5945baf3734227f4c04 Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Thu, 17 Apr 2025 11:49:33 +0200 Subject: [PATCH 158/163] fix and cleanup --- identity_jose/src/jwk/composite_jwk.rs | 3 +-- identity_jose/src/jws/decoder.rs | 6 +++--- identity_pqc_verifier/Cargo.toml | 4 ++-- identity_pqc_verifier/src/oqs_verifier.rs | 2 +- identity_storage/src/storage/hybrid_jws_document_ext.rs | 6 +++--- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index 5dd19ff360..f6b99eec96 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -5,8 +5,7 @@ use std::str::FromStr; use crate::jwk::Jwk; -/// Mame of algorithms used to generate the hybrid signature. Values taken from -/// [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators). +/// Mame of algorithms used to generate the hybrid signature. #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub enum CompositeAlgId { /// DER encoded value in hex = 060B6086480186FA6B5008013E diff --git a/identity_jose/src/jws/decoder.rs b/identity_jose/src/jws/decoder.rs index 7fd2626182..6db2e9a59e 100644 --- a/identity_jose/src/jws/decoder.rs +++ b/identity_jose/src/jws/decoder.rs @@ -180,7 +180,7 @@ impl<'a> JwsValidationItem<'a> { }) } - ///Hybrid signature verify, based on [Composite ML-DSA draft](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsaverify) + ///Hybrid signature verify pub fn verify_hybrid( self, traditional_verifier: &TRV, @@ -215,7 +215,7 @@ impl<'a> JwsValidationItem<'a> { //Prefix: CompositeAlgorithmSignatures2025 let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); - //Domain: id-MLDSA44-Ed25519 (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators) + //Domain: id-MLDSA44-Ed25519 input.extend_from_slice(&[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E]); //len(ctx) = 0 @@ -234,7 +234,7 @@ impl<'a> JwsValidationItem<'a> { //Prefix: CompositeAlgorithmSignatures2025 let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); - //Domain: id-MLDSA65-Ed25519 https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators + //Domain: id-MLDSA65-Ed25519 input.extend_from_slice(&[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47]); //len(ctx) = 0 diff --git a/identity_pqc_verifier/Cargo.toml b/identity_pqc_verifier/Cargo.toml index cd549d0ff8..d21f520ece 100644 --- a/identity_pqc_verifier/Cargo.toml +++ b/identity_pqc_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_pqc_verifier" -version = "1.5.0" +version = "1.5.1" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,7 +12,7 @@ repository.workspace = true description = "JWS PQC signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.5.0", path = "../identity_jose", default-features = true } +identity_jose = { version = "=1.5.1", path = "../identity_jose", default-features = true } oqs.workspace = true [features] diff --git a/identity_pqc_verifier/src/oqs_verifier.rs b/identity_pqc_verifier/src/oqs_verifier.rs index d980b28f6e..337aec83d2 100644 --- a/identity_pqc_verifier/src/oqs_verifier.rs +++ b/identity_pqc_verifier/src/oqs_verifier.rs @@ -53,7 +53,7 @@ impl OQSVerifier { } /// Verify a JWS signature signed with a ctx and secured with the on the [`Algorithm`] defined in liboqs, used in hybrid signature. - /// The ctx value is set as the Domain separator value for binding the signature to the Composite OID, as definied in [here](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsaverify) + /// The ctx value is set as the Domain separator value for binding the signature to the Composite OID pub fn verify_hybrid_signature(input: VerificationInput, public_key: &Jwk, alg: Algorithm) -> Result<(), SignatureVerificationError> { let params: &JwkParamsAKP = public_key diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 0bea0005bc..38e28e82b0 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -211,7 +211,7 @@ impl JwkDocumentExtHybrid for CoreDocument { generate_method_hybrid_core_document(self, storage, alg_id, fragment, scope).await } - /// Hybrid signature implementation based on [Composite ML-DSA draft](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-composite-ml-dsasign) + /// Hybrid signature implementation async fn create_jws( &self, storage: &Storage, @@ -315,7 +315,7 @@ impl JwkDocumentExtHybrid for CoreDocument { //Prefix: CompositeAlgorithmSignatures2025 let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); - //Domain: id-MLDSA44-Ed25519 (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators) + //Domain: id-MLDSA44-Ed25519 let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x3E]; input.extend_from_slice(domain); @@ -332,7 +332,7 @@ impl JwkDocumentExtHybrid for CoreDocument { //Prefix: CompositeAlgorithmSignatures2025 let mut input = b"CompositeAlgorithmSignatures2025".to_vec(); - //Domain: id-MLDSA65-Ed25519 https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs-04#name-domain-separators + //Domain: id-MLDSA65-Ed25519 let domain = &[0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x08, 0x01, 0x47]; input.extend_from_slice(domain); From 4dab1772c165682c46b8e3c3ebba006234f611bc Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 23 Apr 2025 14:14:40 +0200 Subject: [PATCH 159/163] fix fmt --- .../jwt_presentation_validator_hybrid.rs | 8 +++--- identity_jose/src/jwk/composite_jwk.rs | 1 - identity_jose/src/jwk/jwk_akp.rs | 6 ++++ identity_storage/src/key_storage/memstore.rs | 28 +++++++++---------- .../src/storage/did_jwk_document_ext.rs | 16 +++++------ .../src/storage/hybrid_jws_document_ext.rs | 2 +- .../src/verification_method/method.rs | 2 +- 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs index 067eb34efe..ffc9e4ff9f 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator_hybrid.rs @@ -48,11 +48,11 @@ where /// # Warning /// /// * This method does NOT validate the constituent credentials and therefore also not the relationship between the - /// credentials' subjects and the presentation holder. This can be done with - /// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions). + /// credentials' subjects and the presentation holder. This can be done with + /// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions). /// * The lack of an error returned from this method is in of itself not enough to conclude that the presentation can - /// be trusted. This section contains more information on additional checks that should be carried out before and - /// after calling this method. + /// be trusted. This section contains more information on additional checks that should be carried out before and + /// after calling this method. /// /// ## The state of the supplied DID Documents. /// diff --git a/identity_jose/src/jwk/composite_jwk.rs b/identity_jose/src/jwk/composite_jwk.rs index f6b99eec96..61735b825a 100644 --- a/identity_jose/src/jwk/composite_jwk.rs +++ b/identity_jose/src/jwk/composite_jwk.rs @@ -67,7 +67,6 @@ impl FromStr for CompositeAlgId { match string { "id-MLDSA44-Ed25519" => Ok(Self::IdMldsa44Ed25519), "id-MLDSA65-Ed25519" => Ok(Self::IdMldsa65Ed25519), - #[cfg(not(feature = "custom_alg"))] &_ => Err(crate::error::Error::JwsAlgorithmParsingError), } } diff --git a/identity_jose/src/jwk/jwk_akp.rs b/identity_jose/src/jwk/jwk_akp.rs index a5becf0399..9d6e943a4a 100644 --- a/identity_jose/src/jwk/jwk_akp.rs +++ b/identity_jose/src/jwk/jwk_akp.rs @@ -24,6 +24,12 @@ pub struct JwkParamsAKP { pub private: Option, // Private Key } +impl Default for JwkParamsAKP { + fn default() -> Self { + Self::new() + } +} + impl JwkParamsAKP { /// Creates new JWK AKP Params. pub const fn new() -> Self { diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index a3eb0fa3c8..f7034a4486 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -335,7 +335,7 @@ mod pqc_liboqs { use crate::key_storage::jwk_storage_pqc::JwkStoragePQ; use crate::JwkGenOutput; - fn check_pq_alg_compatibility(alg: JwsAlgorithm) -> KeyStorageResult { + fn check_pq_alg_compatibility(alg: &JwsAlgorithm) -> KeyStorageResult { match alg { JwsAlgorithm::ML_DSA_44 => Ok(Algorithm::MlDsa44), JwsAlgorithm::ML_DSA_65 => Ok(Algorithm::MlDsa65), @@ -357,10 +357,10 @@ mod pqc_liboqs { JwsAlgorithm::FALCON512 => Ok(Algorithm::Falcon512), JwsAlgorithm::FALCON1024 => Ok(Algorithm::Falcon1024), other => { - return Err( + Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) .with_custom_message(format!("{other} is not supported")), - ); + ) } } } @@ -378,17 +378,17 @@ mod pqc_liboqs { ); } - let oqs_alg = check_pq_alg_compatibility(alg)?; + let oqs_alg = check_pq_alg_compatibility(&alg)?; oqs::init(); let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature scheme init failed")) + .with_custom_message("signature scheme init failed".to_string()) .with_source(err) })?; let (pk, sk) = scheme.keypair().map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("keypair generation failed!")) + .with_custom_message("keypair generation failed!".to_string()) .with_source(err) })?; @@ -458,7 +458,7 @@ mod pqc_liboqs { JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) })?; - let oqs_alg = check_pq_alg_compatibility(alg)?; + let oqs_alg = check_pq_alg_compatibility(&alg)?; // Check that `kty` is `AKP`. match alg { @@ -515,13 +515,13 @@ mod pqc_liboqs { let scheme = Sig::new(oqs_alg).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature scheme init failed")) + .with_custom_message("signature scheme init failed".to_string()) .with_source(err) })?; let secret_key = scheme.secret_key_from_bytes(&sk_bytes).ok_or( KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("invalid private key")), + .with_custom_message("invalid private key".to_string()), )?; let signature: oqs::sig::Signature; @@ -530,18 +530,18 @@ mod pqc_liboqs { if !scheme.has_ctx_str_support() { return Err( KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature with ctx is not supported with this algorithm")) + .with_custom_message("signature with ctx is not supported with this algorithm".to_string()) ); } - signature = scheme.sign_with_ctx_str(&data, ctx, secret_key).map_err(|err| { + signature = scheme.sign_with_ctx_str(data, ctx, secret_key).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature computation failed")) + .with_custom_message("signature computation failed".to_string()) .with_source(err) })?; } else { - signature = scheme.sign(&data, secret_key).map_err(|err| { + signature = scheme.sign(data, secret_key).map_err(|err| { KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("signature computation failed")) + .with_custom_message("signature computation failed".to_string()) .with_source(err) })?; } diff --git a/identity_storage/src/storage/did_jwk_document_ext.rs b/identity_storage/src/storage/did_jwk_document_ext.rs index 2fca498b0a..b5010baaec 100644 --- a/identity_storage/src/storage/did_jwk_document_ext.rs +++ b/identity_storage/src/storage/did_jwk_document_ext.rs @@ -100,9 +100,9 @@ impl DidJwkDocumentExt for CoreDocument { .map_err(Error::VerificationMethodConstructionError)?; let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; + .map_err(Error::MethodDigestConstructionError)?; - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone()) .await .map_err(Error::KeyIdStorageError)?; @@ -142,9 +142,9 @@ impl DidJwkDocumentExt for CoreDocument { .map_err(Error::VerificationMethodConstructionError)?; let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; + .map_err(Error::MethodDigestConstructionError)?; - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone()) .await .map_err(Error::KeyIdStorageError)?; @@ -183,9 +183,9 @@ impl DidJwkDocumentExt for CoreDocument { .map_err(Error::VerificationMethodConstructionError)?; let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; + .map_err(Error::MethodDigestConstructionError)?; - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone()) .await .map_err(Error::KeyIdStorageError)?; @@ -250,9 +250,9 @@ impl DidJwkDocumentExt for CoreDocument { .map_err(Error::VerificationMethodConstructionError)?; let method_digest = MethodDigest::new(verification_method) - .map_err(|err| Error::MethodDigestConstructionError(err))?; + .map_err(Error::MethodDigestConstructionError)?; - I::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone()) + I::insert_key_id(storage.key_id_storage(), method_digest, key_id.clone()) .await .map_err(Error::KeyIdStorageError)?; diff --git a/identity_storage/src/storage/hybrid_jws_document_ext.rs b/identity_storage/src/storage/hybrid_jws_document_ext.rs index 38e28e82b0..f516393180 100644 --- a/identity_storage/src/storage/hybrid_jws_document_ext.rs +++ b/identity_storage/src/storage/hybrid_jws_document_ext.rs @@ -240,7 +240,7 @@ impl JwkDocumentExtHybrid for CoreDocument { let header: JwsHeader = { let mut header = JwsHeader::new(); - header.set_alg(alg); + header.set_alg(alg.clone()); if let Some(custom) = &options.custom_header_parameters { header.set_custom(custom.clone()) } diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 6fd6d40b06..d5286395dc 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -257,7 +257,7 @@ impl VerificationMethod { let fragment: Cow<'_, str> = { let given_fragment: &str = fragment - .or_else(|| composite_fragment.as_deref()) + .or(composite_fragment.as_deref()) .ok_or(Error::InvalidMethod( "an explicit fragment or kid is required", ))?; From 12a0674fa6285caff6fd054995d8142c3d8999bf Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 23 Apr 2025 14:16:54 +0200 Subject: [PATCH 160/163] fix fmt --- examples/1_advanced/12_pq.rs | 14 +++++++------- examples/1_advanced/13_hybrid.rs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/1_advanced/12_pq.rs b/examples/1_advanced/12_pq.rs index 025e5f4908..3d4d1c49eb 100644 --- a/examples/1_advanced/12_pq.rs +++ b/examples/1_advanced/12_pq.rs @@ -64,7 +64,7 @@ async fn create_did( alg: JwsAlgorithm, ) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; + let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; @@ -75,7 +75,7 @@ async fn create_did( // New Verification Method containing a PQC key let fragment = document - .generate_method_pqc(&storage, key_type, alg, None, MethodScope::VerificationMethod) + .generate_method_pqc(storage, key_type, alg, None, MethodScope::VerificationMethod) .await?; // Construct an Alias Output containing the DID document, with the wallet address @@ -83,7 +83,7 @@ async fn create_did( let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; println!("Published DID document: {document:#}"); Ok((address, document, fragment)) @@ -102,7 +102,7 @@ async fn main() -> anyhow::Result<()> { .finish() .await?; - let mut secret_manager_issuer = SecretManager::Stronghold( + let secret_manager_issuer = SecretManager::Stronghold( StrongholdSecretManager::builder() .password(Password::from("secure_password_1".to_owned())) .build(random_stronghold_path())?, @@ -112,14 +112,14 @@ async fn main() -> anyhow::Result<()> { let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( &client, - &mut secret_manager_issuer, + &secret_manager_issuer, &storage_issuer, JwkMemStore::PQ_KEY_TYPE, JwsAlgorithm::ML_DSA_87, ) .await?; - let mut secret_manager_holder = SecretManager::Stronghold( + let secret_manager_holder = SecretManager::Stronghold( StrongholdSecretManager::builder() .password(Password::from("secure_password_2".to_owned())) .build(random_stronghold_path())?, @@ -129,7 +129,7 @@ async fn main() -> anyhow::Result<()> { let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( &client, - &mut secret_manager_holder, + &secret_manager_holder, &storage_holder, JwkMemStore::PQ_KEY_TYPE, JwsAlgorithm::SLH_DSA_SHA2_128s, diff --git a/examples/1_advanced/13_hybrid.rs b/examples/1_advanced/13_hybrid.rs index d357ae45f8..f01a5de0ef 100644 --- a/examples/1_advanced/13_hybrid.rs +++ b/examples/1_advanced/13_hybrid.rs @@ -66,7 +66,7 @@ async fn create_did( alg_id: CompositeAlgId, ) -> anyhow::Result<(Address, IotaDocument, String)> { // Get an address with funds for testing. - let address: Address = get_address_with_funds(&client, &secret_manager, FAUCET_ENDPOINT).await?; + let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; // Get the Bech32 human-readable part (HRP) of the network. let network_name: NetworkName = client.network_name().await?; @@ -77,7 +77,7 @@ async fn create_did( // New Verification Method containing a PQC key let fragment = document - .generate_method_hybrid(&storage, alg_id, None, MethodScope::VerificationMethod) + .generate_method_hybrid(storage, alg_id, None, MethodScope::VerificationMethod) .await?; // Construct an Alias Output containing the DID document, with the wallet address @@ -85,7 +85,7 @@ async fn create_did( let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(&secret_manager, alias_output).await?; + let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; println!("Published DID document: {document:#}"); Ok((address, document, fragment)) @@ -106,7 +106,7 @@ async fn main() -> anyhow::Result<()> { .finish() .await?; - let mut secret_manager_issuer = SecretManager::Stronghold( + let secret_manager_issuer = SecretManager::Stronghold( StrongholdSecretManager::builder() .password(Password::from("secure_password_1".to_owned())) .build(random_stronghold_path())?, @@ -116,13 +116,13 @@ async fn main() -> anyhow::Result<()> { let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( &client, - &mut secret_manager_issuer, + &secret_manager_issuer, &storage_issuer, CompositeAlgId::IdMldsa65Ed25519, ) .await?; - let mut secret_manager_holder = SecretManager::Stronghold( + let secret_manager_holder = SecretManager::Stronghold( StrongholdSecretManager::builder() .password(Password::from("secure_password_2".to_owned())) .build(random_stronghold_path())?, @@ -132,7 +132,7 @@ async fn main() -> anyhow::Result<()> { let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( &client, - &mut secret_manager_holder, + &secret_manager_holder, &storage_holder, CompositeAlgId::IdMldsa65Ed25519, ) From 40192b362099d667a79f0d1b833f76d480f7a2eb Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 23 Apr 2025 15:30:15 +0200 Subject: [PATCH 161/163] synch --- Cargo.toml | 1 + bindings/wasm/Cargo.toml | 16 - .../src/1_advanced/{11_pq.ts => 13_pq.ts} | 0 .../{12_hybrid.ts => 14_hybrid copy.ts} | 0 bindings/wasm/examples/src/main.ts | 14 +- bindings/wasm/lib/jwk_storage_pq.ts | 272 ++++++++++++++++ bindings/wasm/lib/pq_verifier.ts | 62 ++++ bindings/wasm/package-lock.json | 36 +-- .../jwt_credential_validator_hybrid.rs | 212 +++++++++++++ bindings/wasm/src/jose/jwk.rs | 11 + bindings/wasm/src/lib.rs | 13 + bindings/wasm/src/storage/mod.rs | 2 + examples/1_advanced/13_pq.rs | 292 ++++++++++++++++++ examples/1_advanced/14_hybrid.rs | 289 +++++++++++++++++ examples/Cargo.toml | 31 +- examples/README.md | 29 +- identity_iota/Cargo.toml | 8 + identity_resolver/src/resolution/resolver.rs | 2 +- 18 files changed, 1213 insertions(+), 77 deletions(-) rename bindings/wasm/examples/src/1_advanced/{11_pq.ts => 13_pq.ts} (100%) rename bindings/wasm/examples/src/1_advanced/{12_hybrid.ts => 14_hybrid copy.ts} (100%) create mode 100644 examples/1_advanced/13_pq.rs create mode 100644 examples/1_advanced/14_hybrid.rs diff --git a/Cargo.toml b/Cargo.toml index 25fe6950be..c843e1f3a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } +thiserror = { version = "1.0", default-features = false } json-proof-token = { version = "0.4.1" } zkryptium = { version = "0.4.0", default-features = false, features = ["bbsplus"] } oqs = {version = "0.10.0", default-features = false, features = ["sigs", "std", "vendored"] } diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 253f9b27c7..30a247261d 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -36,22 +36,6 @@ wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } zkryptium = "0.4.0" -[dependencies.identity_iota] -path = "../../identity_iota" -default-features = false -features = [ - "client", - "revocation-bitmap", - "resolver", - "domain-linkage", - "sd-jwt", - "status-list-2021", - "jpt-bbs-plus", - "sd-jwt-vc", - "pqc", - "hybrid" -] - [dev-dependencies] rand = "0.8.5" diff --git a/bindings/wasm/examples/src/1_advanced/11_pq.ts b/bindings/wasm/examples/src/1_advanced/13_pq.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/11_pq.ts rename to bindings/wasm/examples/src/1_advanced/13_pq.ts diff --git a/bindings/wasm/examples/src/1_advanced/12_hybrid.ts b/bindings/wasm/examples/src/1_advanced/14_hybrid copy.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/12_hybrid.ts rename to bindings/wasm/examples/src/1_advanced/14_hybrid copy.ts diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index b64e5d2031..70b95d6a62 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -25,6 +25,8 @@ import { sdJwt } from "./1_advanced/6_sd_jwt"; import { statusList2021 } from "./1_advanced/7_status_list_2021"; import { zkp } from "./1_advanced/8_zkp"; import { zkp_revocation } from "./1_advanced/9_zkp_revocation"; +import { pq } from "./1_advanced/13_pq"; +import { hybrid } from "./1_advanced/14_hybrid"; async function main() { // Extract example name. @@ -72,10 +74,14 @@ async function main() { return await zkp_revocation(); case "10_sd_jwt_vc": return await sdJwtVc(); - case "11_pq": - return await pq(); - case "12_hybrid": - return await hybrid(); + case "11_advanced_transactions": + return await advancedTransaction(); + case "12_iota_keytool_integration": + return await iotaKeytoolIntegration(); + case "13_pq": + return await pq(); + case "14_hybrid": + return await hybrid(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/lib/jwk_storage_pq.ts b/bindings/wasm/lib/jwk_storage_pq.ts index 04058ec027..4578f3f31f 100644 --- a/bindings/wasm/lib/jwk_storage_pq.ts +++ b/bindings/wasm/lib/jwk_storage_pq.ts @@ -269,3 +269,275 @@ function randomKeyId(): string { return encodeB64(randomness); } + +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 +import * as ed from "@noble/ed25519"; +import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; +import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStoragePQ, JwkStorage } from "~identity_wasm"; +import { EdCurve, JwkType, JwsAlgorithm} from "./jose"; + +type Ed25519PrivateKey = Uint8Array; +type Ed25519PublicKey = Uint8Array; + + +//JkwStorage for PQ and PQ/T examples +export class JwkPqMemStore implements JwkStorage, JwkStoragePQ{ + /** The map from key identifiers to Jwks. */ + private _keys: Map; + + /** Creates a new, empty `MemStore` instance. */ + constructor() { + this._keys = new Map(); + } + + public static mldsaKeyType(): string { + return "AKP"; + } + + public static ed25519KeyType(): string { + return "Ed25519"; + } + + + private _get_key(keyId: string): Jwk | undefined { + return this._keys.get(keyId); + } + + public async generate(keyType: string, algorithm: JwsAlgorithm): Promise { + if (keyType !== JwkPqMemStore.ed25519KeyType()) { + throw new Error(`unsupported key type ${keyType}`); + } + + if (algorithm !== JwsAlgorithm.EdDSA) { + throw new Error(`unsupported algorithm`); + } + + const keyId = randomKeyId(); + const privKey: Ed25519PrivateKey = ed.utils.randomPrivateKey(); + + const publicKey: Ed25519PublicKey = await ed.getPublicKey(privKey); + const jwk = await encodeJwk(privKey, publicKey, algorithm); + + this._keys.set(keyId, jwk); + + const publicJWK = jwk?.toPublic(); + if (!publicJWK) { + throw new Error(`JWK is not a public key`); + } + + return new JwkGenOutput(keyId, publicJWK); + } + + public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { + + if (keyType !== JwkPqMemStore.mldsaKeyType()) { + throw new Error(`unsupported key type ${keyType}`); + } + + const seed = new TextEncoder().encode(randomKeyId()) + let keys; + if (algorithm === JwsAlgorithm.MLDSA44) { + keys = ml_dsa44.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA65) { + keys = ml_dsa65.keygen(seed); + } else if(algorithm === JwsAlgorithm.MLDSA87) { + keys = ml_dsa87.keygen(seed); + } else { + throw new Error(`unsupported algorithm`); + } + + const keyId = randomKeyId(); + + const jwk = await encodeJwk(keys.secretKey, keys.publicKey, algorithm); + + if(jwk == undefined) + throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); + + this._keys.set(keyId, jwk); + + const publicJWK = jwk?.toPublic(); + if (!publicJWK) { + throw new Error(`JWK is not a public key`); + } + + return new JwkGenOutput(keyId, publicJWK); + + } + + public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { + let alg = publicKey.alg(); + let signature = null; + + if(alg === undefined) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + if (alg !== JwsAlgorithm.EdDSA ) { + throw new Error("unsupported JWS algorithm"); + } else { + if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) + { + throw new Error("unsupported Okp parameter"); + } + } + + const jwk = this._keys.get(keyId); + + if (jwk) { + const [privateKey, _] = decodeJwk(jwk); + signature = await ed.sign(data, privateKey); + + } else { + throw new Error(`key with id ${keyId} not found`); + } + return signature; + } + + public async signPQ(keyId: string, data: Uint8Array, publicKey: Jwk, ctx: Uint8Array|undefined ): Promise { + let alg = publicKey.alg(); + let signature = null; + + if(alg === undefined) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + if (alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported JWS algorithm"); + } + + const jwk = this._keys.get(keyId); + + if (jwk) { + + const [privateKey, _] = decodeJwk(jwk); + + if(alg == JwsAlgorithm.MLDSA44) + signature = ml_dsa44.sign(privateKey, data, ctx); + else if(alg == JwsAlgorithm.MLDSA65) + signature = ml_dsa65.sign(privateKey, data, ctx); + else if(alg == JwsAlgorithm.MLDSA87) + signature = ml_dsa87.sign(privateKey, data, ctx); + else + throw new Error("unsupported algorithm"); + + } else { + throw new Error(`key with id ${keyId} not found`); + } + return signature; + } + + public async insert(jwk: Jwk): Promise { + const keyId = randomKeyId(); + + if (!jwk.isPrivate) { + throw new Error("expected a JWK with all private key components set"); + } + + if (!jwk.alg()) { + throw new Error("expected a Jwk with an `alg` parameter"); + } + + this._keys.set(keyId, jwk); + + return keyId; + } + + public async delete(keyId: string): Promise { + this._keys.delete(keyId); + } + + public async exists(keyId: string): Promise { + return this._keys.has(keyId); + } + + public count(): number { + return this._keys.size; + } +} + +// Encodes a Ed25519 keypair into a Jwk. +async function encodeJwk(privateKey: Uint8Array, publicKey: Uint8Array, alg: JwsAlgorithm): Promise { + let pub = encodeB64(publicKey); + let priv = encodeB64(privateKey); + + if (alg === JwsAlgorithm.EdDSA) { + return new Jwk({ + "kty": JwkType.Okp, + crv: "Ed25519", + d: priv, + x: pub, + alg, + }); + } else { + return new Jwk({ + "kty": JwkType.Akp, + pub: pub, + priv: priv, + alg, + }); + } + +} + +function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { + if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && + jwk.alg()! !== JwsAlgorithm.MLDSA65 && + jwk.alg()! !== JwsAlgorithm.MLDSA87 && + jwk.alg()! !== JwsAlgorithm.EdDSA) { + throw new Error("unsupported `alg`"); + } + if (jwk.alg()! === JwsAlgorithm.EdDSA) { + const paramsOkp = jwk.paramsOkp(); + if (paramsOkp) { + const d = paramsOkp.d; + + if (d) { + const textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(d)); + const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } + } else { + throw new Error("expected Okp params"); + } + } else { + const paramsPQ = jwk.paramsAkp(); + + if (paramsPQ) { + const priv = paramsPQ.priv; + + if (priv) { + let textEncoder = new TextEncoder(); + const privateKey = decodeB64(textEncoder.encode(priv)); + const publicKey = decodeB64(textEncoder.encode(paramsPQ.pub)); + return [privateKey, publicKey]; + } else { + throw new Error("missing private key component"); + } + } else { + throw new Error("expected Okp params"); + } + + } + +} + +// Returns a random number between `min` and `max` (inclusive). +// SAFETY NOTE: This is not cryptographically secure randomness and thus not suitable for production use. +// It suffices for our testing implementation however and avoids an external dependency. +function getRandomNumber(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// Returns a random key id. +function randomKeyId(): string { + const randomness = new Uint8Array(20); + for (let index = 0; index < randomness.length; index++) { + randomness[index] = getRandomNumber(0, 255); + } + + return encodeB64(randomness); +} diff --git a/bindings/wasm/lib/pq_verifier.ts b/bindings/wasm/lib/pq_verifier.ts index 9fa5582d58..2fd14d76e1 100644 --- a/bindings/wasm/lib/pq_verifier.ts +++ b/bindings/wasm/lib/pq_verifier.ts @@ -59,3 +59,65 @@ function decodeJwk(jwk: Jwk): Uint8Array { + +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; +import { decodeB64, Jwk, IJwsVerifier} from "~identity_wasm"; +import { JwsAlgorithm } from "./jose"; + +export class PQJwsVerifier implements IJwsVerifier{ + + public verify (alg: JwsAlgorithm, signingInput: Uint8Array, decodedSignature: Uint8Array, publicKey: Jwk): void{ + let res = false; + let ctx = undefined; + + if (alg !== JwsAlgorithm.MLDSA44 && + alg !== JwsAlgorithm.MLDSA65 && + alg !== JwsAlgorithm.MLDSA87 && + alg !== JwsAlgorithm.IdMldsa44Ed25519 && + alg !== JwsAlgorithm.IdMldsa65Ed25519) { + throw new Error("unsupported JWS algorithm"); + } + + const pubKey = decodeJwk(publicKey); + + //Domain separator for hybrid signatures + if (alg === JwsAlgorithm.IdMldsa44Ed25519) { + ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 62]); + } else if (alg === JwsAlgorithm.IdMldsa65Ed25519) { + ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 71]); + } + + if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.IdMldsa44Ed25519) { + res = ml_dsa44.verify(pubKey, signingInput, decodedSignature, ctx); + } else if (alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.IdMldsa65Ed25519) { + res = ml_dsa65.verify(pubKey, signingInput, decodedSignature, ctx); + } else if (alg === JwsAlgorithm.MLDSA87) { + res = ml_dsa87.verify(pubKey, signingInput, decodedSignature); + } + if (!res) { + throw new Error("signature verification failed"); + } + } + +} + +function decodeJwk(jwk: Jwk): Uint8Array { + if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && jwk.alg()! !== JwsAlgorithm.MLDSA65 && jwk.alg()! !== JwsAlgorithm.MLDSA87) { + throw new Error("unsupported `alg`"); + } + + const paramsPQ = jwk.paramsAkp(); + + if (paramsPQ) { + let textEncoder = new TextEncoder(); + return decodeB64(textEncoder.encode(paramsPQ.pub)); + } else { + throw new Error("expected Okp params"); + } +} + + + diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index 0c6d032ecd..b8dd3cc682 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@noble/ed25519": "^1.7.3", "@noble/post-quantum": "^0.2.0", + "@noble/hashes": "^1.4.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", @@ -335,28 +336,6 @@ } ] }, - "node_modules/@noble/hashes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", - "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/post-quantum": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", - "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", - "dependencies": { - "@noble/hashes": "1.6.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6558,19 +6537,6 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, - "@noble/hashes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", - "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" - }, - "@noble/post-quantum": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", - "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", - "requires": { - "@noble/hashes": "1.6.0" - } - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs index db99373a99..0fbaa9808a 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -209,3 +209,215 @@ impl WasmJwtCredentialValidatorHybrid { .wasm_result() } } + +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::credential::JwtCredentialValidatorHybrid; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::StatusCheck; +use identity_iota::did::CoreDID; + +use super::options::WasmJwtCredentialValidationOptions; +use crate::common::ImportedDocumentLock; +use crate::common::ImportedDocumentReadGuard; +use crate::common::WasmTimestamp; +use crate::credential::options::WasmStatusCheck; +use crate::credential::revocation::status_list_2021::WasmStatusList2021Credential; +use crate::credential::WasmCredential; +use crate::credential::WasmDecodedJwtCredential; +use crate::credential::WasmFailFast; +use crate::credential::WasmJwt; +use crate::credential::WasmSubjectHolderRelationship; +use crate::did::ArrayIToCoreDocument; +use crate::did::IToCoreDocument; +use crate::did::WasmCoreDID; +use crate::did::WasmJwsVerificationOptions; +use crate::error::Result; +use crate::error::WasmResult; +use crate::verification::IJwsVerifier; +use crate::verification::WasmJwsVerifier; + +use wasm_bindgen::prelude::*; + +/// A type for decoding and validating PQ/T {@link Credential}. +#[wasm_bindgen(js_name = JwtCredentialValidatorHybrid)] +pub struct WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid); + +#[wasm_bindgen(js_class = JwtCredentialValidatorHybrid)] +impl WasmJwtCredentialValidatorHybrid { + /// Creates a new {@link JwtCredentialValidatorHybrid}. + #[wasm_bindgen(constructor)] + #[allow(non_snake_case)] + pub fn new(traditionalSignatureVerifier: Option, pqSignatureVerifier: Option) -> WasmJwtCredentialValidatorHybrid { + let traditional_signature_verifier = WasmJwsVerifier::new(traditionalSignatureVerifier); + let pq_signature_verifier = WasmJwsVerifier::new(pqSignatureVerifier); + WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid::with_signature_verifiers(traditional_signature_verifier, pq_signature_verifier)) + } + + /// Decodes and validates a {@link Credential} issued as a JWS. A {@link DecodedJwtCredential} is returned upon + /// success. + /// + /// The following properties are validated according to `options`: + /// - the issuer's signature on the JWS, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + /// + /// # Warning + /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be + /// trusted. This section contains more information on additional checks that should be carried out before and after + /// calling this method. + /// + /// ## The state of the issuer's DID Document + /// The caller must ensure that `issuer` represents an up-to-date DID Document. + /// + /// ## Properties that are not validated + /// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: + /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. + /// These should be manually checked after validation, according to your requirements. + /// + /// # Errors + /// An error is returned whenever a validated condition is not satisfied. + #[wasm_bindgen] + pub fn validate( + &self, + credential_jwt: &WasmJwt, + issuer: &IToCoreDocument, + options: &WasmJwtCredentialValidationOptions, + fail_fast: WasmFailFast, + ) -> Result { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + + self + .0 + .validate(&credential_jwt.0, &issuer_guard, &options.0, fail_fast.into()) + .wasm_result() + .map(WasmDecodedJwtCredential) + } + + /// Decode and verify the JWS signature of a {@link Credential} issued as a JWT using the DID Document of a trusted + /// issuer. + /// + /// A {@link DecodedJwtCredential} is returned upon success. + /// + /// # Warning + /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date. + /// + /// ## Proofs + /// Only the JWS signature is verified. If the {@link Credential} contains a `proof` property this will not be + /// verified by this method. + /// + /// # Errors + /// This method immediately returns an error if + /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt + /// to verify the credential's signature will be made and an error is returned upon failure. + #[wasm_bindgen(js_name = verifySignature)] + #[allow(non_snake_case)] + pub fn verify_signature( + &self, + credential: &WasmJwt, + trustedIssuers: &ArrayIToCoreDocument, + options: &WasmJwsVerificationOptions, + ) -> Result { + let issuer_locks: Vec = trustedIssuers.into(); + let trusted_issuers: Vec> = issuer_locks + .iter() + .map(ImportedDocumentLock::try_read) + .collect::>>>( + )?; + + self + .0 + .verify_signature(&credential.0, &trusted_issuers, &options.0) + .wasm_result() + .map(WasmDecodedJwtCredential) + } + + /// Validate that the credential expires on or after the specified timestamp. + #[wasm_bindgen(js_name = checkExpiresOnOrAfter)] + pub fn check_expires_on_or_after(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { + JwtCredentialValidatorUtils::check_expires_on_or_after(&credential.0, timestamp.0).wasm_result() + } + + /// Validate that the credential is issued on or before the specified timestamp. + #[wasm_bindgen(js_name = checkIssuedOnOrBefore)] + pub fn check_issued_on_or_before(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { + JwtCredentialValidatorUtils::check_issued_on_or_before(&credential.0, timestamp.0).wasm_result() + } + + /// Validate that the relationship between the `holder` and the credential subjects is in accordance with + /// `relationship`. The `holder` parameter is expected to be the URL of the holder. + #[wasm_bindgen(js_name = checkSubjectHolderRelationship)] + pub fn check_subject_holder_relationship( + credential: &WasmCredential, + holder: &str, + relationship: WasmSubjectHolderRelationship, + ) -> Result<()> { + let holder: Url = Url::parse(holder).wasm_result()?; + JwtCredentialValidatorUtils::check_subject_holder_relationship(&credential.0, &holder, relationship.into()) + .wasm_result() + } + + /// Checks whether the credential status has been revoked. + /// + /// Only supports `RevocationBitmap2022`. + #[wasm_bindgen(js_name = checkStatus)] + #[allow(non_snake_case)] + pub fn check_status( + credential: &WasmCredential, + trustedIssuers: &ArrayIToCoreDocument, + statusCheck: WasmStatusCheck, + ) -> Result<()> { + let issuer_locks: Vec = trustedIssuers.into(); + let trusted_issuers: Vec> = issuer_locks + .iter() + .map(ImportedDocumentLock::try_read) + .collect::>>>( + )?; + let status_check: StatusCheck = statusCheck.into(); + JwtCredentialValidatorUtils::check_status(&credential.0, &trusted_issuers, status_check).wasm_result() + } + + /// Checks wheter the credential status has been revoked using `StatusList2021`. + #[wasm_bindgen(js_name = checkStatusWithStatusList2021)] + pub fn check_status_with_status_list_2021( + credential: &WasmCredential, + status_list: &WasmStatusList2021Credential, + status_check: WasmStatusCheck, + ) -> Result<()> { + JwtCredentialValidatorUtils::check_status_with_status_list_2021( + &credential.0, + &status_list.inner, + status_check.into(), + ) + .wasm_result() + } + + /// Utility for extracting the issuer field of a {@link Credential} as a DID. + /// + /// ### Errors + /// + /// Fails if the issuer field is not a valid DID. + #[wasm_bindgen(js_name = extractIssuer)] + pub fn extract_issuer(credential: &WasmCredential) -> Result { + JwtCredentialValidatorUtils::extract_issuer::(&credential.0) + .map(WasmCoreDID::from) + .wasm_result() + } + + /// Utility for extracting the issuer field of a credential in JWT representation as DID. + /// + /// # Errors + /// + /// If the JWT decoding fails or the issuer field is not a valid DID. + #[wasm_bindgen(js_name = extractIssuerFromJwt)] + pub fn extract_issuer_from_jwt(credential: &WasmJwt) -> Result { + JwtCredentialValidatorUtils::extract_issuer_from_jwt::(&credential.0) + .map(WasmCoreDID::from) + .wasm_result() + } +} diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 6b0b6a7b37..6265b7a677 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -179,6 +179,17 @@ impl WasmJwk { } } + /// If this JWK is of kty AKP, returns those parameters. + #[wasm_bindgen(js_name = paramsAkp)] + pub fn params_akp(&self) -> crate::error::Result> { + if let JwkParams::Akp(params_akp) = self.0.params() { + // WARNING: this does not validate the return type. Check carefully. + Ok(Some(JsValue::from_serde(params_akp).wasm_result()?.unchecked_into())) + } else { + Ok(None) + } + } + /// Returns a clone of the {@link Jwk} with _all_ private key components unset. /// Nothing is returned when `kty = oct` as this key type is not considered public by this library. #[wasm_bindgen(js_name = toPublic)] diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 549516005d..d8faec2931 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -47,4 +47,17 @@ pub fn start() -> Result<(), JsValue> { #[wasm_bindgen(typescript_custom_section)] const CUSTOM_IMPORTS: &'static str = r#" import { JwsAlgorithm, JwkOperation, JwkUse, JwkType, CompositeAlgId} from '../lib/jose/index'; +import { + Proposal, + ProposalOutput, + Transaction, + TransactionOutput, + TransactionBuilder, + SponsorFn, + ApproveProposal, + CreateProposal, + ExecuteProposal, + UpdateDid, + ProposalResult, +} from '../lib/index'; "#; diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 7a6dd1fad5..5dccdbdccd 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -13,6 +13,8 @@ mod key_id_storage; mod method_digest; mod signature_options; mod wasm_storage; +mod wasm_storage_signer; +mod wasm_transaction_signer; mod jwk_storage_pqc; pub use jpt_timeframe_revocation_ext::*; diff --git a/examples/1_advanced/13_pq.rs b/examples/1_advanced/13_pq.rs new file mode 100644 index 0000000000..3d4d1c49eb --- /dev/null +++ b/examples/1_advanced/13_pq.rs @@ -0,0 +1,292 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_iota::core::Duration; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtCredential; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::JwtCredentialValidator; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwsDocumentExtPQC; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyType; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use identity_pqc_verifier::PQCJwsVerifier; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; +use serde_json::json; + +// The API endpoint of an IOTA node, e.g. Hornet. +const API_ENDPOINT: &str = "http://localhost"; +// The faucet endpoint allows requesting funds for testing purposes. +const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; + +async fn create_did( + client: &Client, + secret_manager: &SecretManager, + storage: &MemStorage, + key_type: KeyType, + alg: JwsAlgorithm, +) -> anyhow::Result<(Address, IotaDocument, String)> { + // Get an address with funds for testing. + let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // New Verification Method containing a PQC key + let fragment = document + .generate_method_pqc(storage, key_type, alg, None, MethodScope::VerificationMethod) + .await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) +} + +/// Demonstrates how to create a Post-Quantum Verifiable Credential. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identitiy for the issuer. + // =========================================================================== + + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + let secret_manager_issuer = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( + &client, + &secret_manager_issuer, + &storage_issuer, + JwkMemStore::PQ_KEY_TYPE, + JwsAlgorithm::ML_DSA_87, + ) + .await?; + + let secret_manager_holder = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( + &client, + &secret_manager_holder, + &storage_holder, + JwkMemStore::PQ_KEY_TYPE, + JwsAlgorithm::SLH_DSA_SHA2_128s, + ) + .await?; + + // ====================================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with a Post-Quantum algorithm. + // ====================================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_pqc( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!( + "Sending credential (as JWT) to the holder: {}\n", + credential_jwt.as_str() + ); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt_pqc( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!( + "Sending presentation (as JWT) to the verifier: {}\n", + presentation_jwt.as_str() + ); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + PQCJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator: JwtCredentialValidator = + JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + println!("--------------------------"); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + Ok(()) +} diff --git a/examples/1_advanced/14_hybrid.rs b/examples/1_advanced/14_hybrid.rs new file mode 100644 index 0000000000..f01a5de0ef --- /dev/null +++ b/examples/1_advanced/14_hybrid.rs @@ -0,0 +1,289 @@ +// Copyright 2024 Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; +use examples::get_address_with_funds; +use examples::random_stronghold_path; +use examples::MemStorage; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::Duration; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtCredential; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::JwtCredentialValidatorHybrid; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidatorHybrid; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExtHybrid; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::verification::jwk::CompositeAlgId; +use identity_iota::verification::MethodScope; +use identity_pqc_verifier::PQCJwsVerifier; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::output::AliasOutput; +use serde_json::json; + +// // The API endpoint of an IOTA node, e.g. Hornet. +// const API_ENDPOINT: &str = "http://localhost"; +// // The faucet endpoint allows requesting funds for testing purposes. +// const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; + +const API_ENDPOINT: &str = "https://api.testnet.shimmer.network"; +const FAUCET_ENDPOINT: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; + +async fn create_did( + client: &Client, + secret_manager: &SecretManager, + storage: &MemStorage, + alg_id: CompositeAlgId, +) -> anyhow::Result<(Address, IotaDocument, String)> { + // Get an address with funds for testing. + let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; + + // Get the Bech32 human-readable part (HRP) of the network. + let network_name: NetworkName = client.network_name().await?; + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut document: IotaDocument = IotaDocument::new(&network_name); + + // New Verification Method containing a PQC key + let fragment = document + .generate_method_hybrid(storage, alg_id, None, MethodScope::VerificationMethod) + .await?; + + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; + + // Publish the Alias Output and get the published DID document. + let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; + println!("Published DID document: {document:#}"); + + Ok((address, document, fragment)) +} + +/// Demonstrates how to create a DID Document and publish it in a new Alias Output. +/// +/// In this example we connect to a locally running private network, but it can be adapted +/// to run on any IOTA node by setting the network and faucet endpoints. +/// +/// See the following instructions on running your own private network +/// https://github.com/iotaledger/hornet/tree/develop/private_tangle +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Create a new client to interact with the IOTA ledger. + let client: Client = Client::builder() + .with_primary_node(API_ENDPOINT, None)? + .finish() + .await?; + + let secret_manager_issuer = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_1".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( + &client, + &secret_manager_issuer, + &storage_issuer, + CompositeAlgId::IdMldsa65Ed25519, + ) + .await?; + + let secret_manager_holder = SecretManager::Stronghold( + StrongholdSecretManager::builder() + .password(Password::from("secure_password_2".to_owned())) + .build(random_stronghold_path())?, + ); + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( + &client, + &secret_manager_holder, + &storage_holder, + CompositeAlgId::IdMldsa65Ed25519, + ) + .await?; + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jwt: Jwt = issuer_document + .create_credential_jwt_hybrid( + &credential, + &storage_issuer, + &fragment_issuer, + &JwsSignatureOptions::default(), + None, + ) + .await?; + + println!("Credential JWT: {}", credential_jwt.as_str()); + + // Before sending this credential to the holder the issuer wants to validate that some properties + // of the credential satisfy their expectations. + + // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_credential: DecodedJwtCredential = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate::<_, Object>( + &credential_jwt, + &issuer_document, + &JwtCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + println!("VC successfully validated"); + + println!("Credential JSON > {:#}", decoded_credential.credential); + + // =========================================================================== + // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The verifier and holder also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // Step 5: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jwt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt_hybrid( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // Step 6: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!( + "Sending presentation (as JWT) to the verifier: {}", + presentation_jwt.as_str() + ); + + // =========================================================================== + // Step 7: Verifier receives the Verifiable Presentation and verifies it. + // =========================================================================== + + // The verifier wants the following requirements to be satisfied: + // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) + // - JWT verification of the credentials. + // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property + // - The issuance date must not be in the future. + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_iota_handler(client); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = + JwtPresentationValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // Concurrently resolve the issuers' documents. + let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; + let issuers: Vec = jwt_credentials + .iter() + .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) + .collect::, _>>()?; + let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; + + // Validate the credentials in the presentation. + let credential_validator = + JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); + let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + for (index, jwt_vc) in jwt_credentials.iter().enumerate() { + // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. + let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; + + let _decoded_credential: DecodedJwtCredential = credential_validator + .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + } + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!("VP successfully validated: {:#?}", presentation.presentation); + + // Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check + // that the credentials do not have an issuance date in the future which is a default check. + + Ok(()) +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d4e4aa746a..3605b86904 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -17,10 +17,27 @@ primitive-types = "0.12.1" rand = "0.8.5" sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } serde_json = { version = "1.0", default-features = false } -tokio = { version = "1.29", default-features = false, features = ["rt"] } +tokio = { version = "1.43", default-features = false, features = ["rt", "macros"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } serde.workspace = true +[dependencies.identity_iota] +path = "../identity_iota" +default-features = false +features = [ + "domain-linkage", + "jpt-bbs-plus", + "iota-client", + "send-sync-storage", + "memstore", + "resolver", + "revocation-bitmap", + "sd-jwt", + "status-list-2021", + "keytool", + "hybrid-liboqs" +] + [lib] path = "utils/utils.rs" @@ -113,5 +130,13 @@ path = "1_advanced/12_pq.rs" name = "12_pq" [[example]] -path = "1_advanced/13_hybrid.rs" -name = "13_hybrid" +path = "1_advanced/13_iota_keytool_integration.rs" +name = "13_iota_keytool_integration" + +[[example]] +path = "1_advanced/13_pq.rs" +name = "13_pq" + +[[example]] +path = "1_advanced/14_hybrid.rs" +name = "14_hybrid" diff --git a/examples/README.md b/examples/README.md index 6dd319d1ca..1c251fd942 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,22 +37,15 @@ The following basic CRUD (Create, Read, Update, Delete) examples are available: The following advanced examples are available: -| Name | Information | -| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | -| [0_did_controls_did](./1_advanced/0_did_controls_did.rs) | Demonstrates how an identity can control another identity. | -| [1_did_issues_nft](./1_advanced/1_did_issues_nft.rs) | Demonstrates how an identity can issue and own NFTs, and how observers can verify the issuer of the NFT. | -| [2_nft_owns_did](./1_advanced/2_nft_owns_did.rs) | Demonstrates how an identity can be owned by NFTs, and how observers can verify that relationship. | -| [3_did_issues_tokens](./1_advanced/3_did_issues_tokens.rs) | Demonstrates how an identity can issue and control a Token Foundry and its tokens. | -| [4_alias_output_history](./1_advanced/4_alias_output_history.rs) | Demonstrates fetching the history of an Alias Output. | -| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | -| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | -| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | -| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | -| [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | -| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | -| [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | +| Name | Information | +| :------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------| +| [4_identity_history](./1_advanced/4_identity_history.rs) | Demonstrates fetching the history of an identity. | +| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | +| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | +| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | +| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to create an Anonymous Credential with BBS+. | +| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a credential. | +| [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | | [12_pq](./1_advanced/12_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. | -| [13_hybrid](./1_advanced/13_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature | - -#### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. - +| [13_hybrid](./1_advanced/13_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature | \ No newline at end of file diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 94b512a715..1178b1fc6c 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -76,6 +76,14 @@ pqc-liboqs = ["identity_storage/pqc-liboqs"] hybrid = ["identity_storage/hybrid", "identity_credential/hybrid"] hybrid-liboqs = ["identity_storage/hybrid-liboqs", "identity_credential/hybrid"] +# Enables PQC +pqc = ["identity_storage/pqc"] +pqc-liboqs = ["identity_storage/pqc-liboqs"] + +# Enables PQ/T Hybrid +hybrid = ["identity_storage/hybrid", "identity_credential/hybrid"] +hybrid-liboqs = ["identity_storage/hybrid-liboqs", "identity_credential/hybrid"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 5e4bdf2fbb..0ec1d00a5e 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -285,7 +285,7 @@ impl + 'static> Resolver> { } } -#[cfg(feature = "iota")] +#[cfg(all(feature = "iota", not(target_arch = "wasm32")))] mod iota_handler { use crate::ErrorCause; From bd9cfed1a8ddb1fc1f2057a079088dbe61d4b46d Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 23 Apr 2025 15:43:24 +0200 Subject: [PATCH 162/163] Revert "synch" This reverts commit 40192b362099d667a79f0d1b833f76d480f7a2eb. --- Cargo.toml | 1 - bindings/wasm/Cargo.toml | 16 + .../src/1_advanced/{13_pq.ts => 11_pq.ts} | 0 .../{14_hybrid copy.ts => 12_hybrid.ts} | 0 bindings/wasm/examples/src/main.ts | 14 +- bindings/wasm/lib/jwk_storage_pq.ts | 272 ---------------- bindings/wasm/lib/pq_verifier.ts | 62 ---- bindings/wasm/package-lock.json | 36 ++- .../jwt_credential_validator_hybrid.rs | 212 ------------- bindings/wasm/src/jose/jwk.rs | 11 - bindings/wasm/src/lib.rs | 13 - bindings/wasm/src/storage/mod.rs | 2 - examples/1_advanced/13_pq.rs | 292 ------------------ examples/1_advanced/14_hybrid.rs | 289 ----------------- examples/Cargo.toml | 31 +- examples/README.md | 29 +- identity_iota/Cargo.toml | 8 - identity_resolver/src/resolution/resolver.rs | 2 +- 18 files changed, 77 insertions(+), 1213 deletions(-) rename bindings/wasm/examples/src/1_advanced/{13_pq.ts => 11_pq.ts} (100%) rename bindings/wasm/examples/src/1_advanced/{14_hybrid copy.ts => 12_hybrid.ts} (100%) delete mode 100644 examples/1_advanced/13_pq.rs delete mode 100644 examples/1_advanced/14_hybrid.rs diff --git a/Cargo.toml b/Cargo.toml index c843e1f3a7..25fe6950be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0", default-features = false } -thiserror = { version = "1.0", default-features = false } json-proof-token = { version = "0.4.1" } zkryptium = { version = "0.4.0", default-features = false, features = ["bbsplus"] } oqs = {version = "0.10.0", default-features = false, features = ["sigs", "std", "vendored"] } diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 30a247261d..253f9b27c7 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -36,6 +36,22 @@ wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } zkryptium = "0.4.0" +[dependencies.identity_iota] +path = "../../identity_iota" +default-features = false +features = [ + "client", + "revocation-bitmap", + "resolver", + "domain-linkage", + "sd-jwt", + "status-list-2021", + "jpt-bbs-plus", + "sd-jwt-vc", + "pqc", + "hybrid" +] + [dev-dependencies] rand = "0.8.5" diff --git a/bindings/wasm/examples/src/1_advanced/13_pq.ts b/bindings/wasm/examples/src/1_advanced/11_pq.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/13_pq.ts rename to bindings/wasm/examples/src/1_advanced/11_pq.ts diff --git a/bindings/wasm/examples/src/1_advanced/14_hybrid copy.ts b/bindings/wasm/examples/src/1_advanced/12_hybrid.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/14_hybrid copy.ts rename to bindings/wasm/examples/src/1_advanced/12_hybrid.ts diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 70b95d6a62..b64e5d2031 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -25,8 +25,6 @@ import { sdJwt } from "./1_advanced/6_sd_jwt"; import { statusList2021 } from "./1_advanced/7_status_list_2021"; import { zkp } from "./1_advanced/8_zkp"; import { zkp_revocation } from "./1_advanced/9_zkp_revocation"; -import { pq } from "./1_advanced/13_pq"; -import { hybrid } from "./1_advanced/14_hybrid"; async function main() { // Extract example name. @@ -74,14 +72,10 @@ async function main() { return await zkp_revocation(); case "10_sd_jwt_vc": return await sdJwtVc(); - case "11_advanced_transactions": - return await advancedTransaction(); - case "12_iota_keytool_integration": - return await iotaKeytoolIntegration(); - case "13_pq": - return await pq(); - case "14_hybrid": - return await hybrid(); + case "11_pq": + return await pq(); + case "12_hybrid": + return await hybrid(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/lib/jwk_storage_pq.ts b/bindings/wasm/lib/jwk_storage_pq.ts index 4578f3f31f..04058ec027 100644 --- a/bindings/wasm/lib/jwk_storage_pq.ts +++ b/bindings/wasm/lib/jwk_storage_pq.ts @@ -269,275 +269,3 @@ function randomKeyId(): string { return encodeB64(randomness); } - -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 -import * as ed from "@noble/ed25519"; -import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; -import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStoragePQ, JwkStorage } from "~identity_wasm"; -import { EdCurve, JwkType, JwsAlgorithm} from "./jose"; - -type Ed25519PrivateKey = Uint8Array; -type Ed25519PublicKey = Uint8Array; - - -//JkwStorage for PQ and PQ/T examples -export class JwkPqMemStore implements JwkStorage, JwkStoragePQ{ - /** The map from key identifiers to Jwks. */ - private _keys: Map; - - /** Creates a new, empty `MemStore` instance. */ - constructor() { - this._keys = new Map(); - } - - public static mldsaKeyType(): string { - return "AKP"; - } - - public static ed25519KeyType(): string { - return "Ed25519"; - } - - - private _get_key(keyId: string): Jwk | undefined { - return this._keys.get(keyId); - } - - public async generate(keyType: string, algorithm: JwsAlgorithm): Promise { - if (keyType !== JwkPqMemStore.ed25519KeyType()) { - throw new Error(`unsupported key type ${keyType}`); - } - - if (algorithm !== JwsAlgorithm.EdDSA) { - throw new Error(`unsupported algorithm`); - } - - const keyId = randomKeyId(); - const privKey: Ed25519PrivateKey = ed.utils.randomPrivateKey(); - - const publicKey: Ed25519PublicKey = await ed.getPublicKey(privKey); - const jwk = await encodeJwk(privKey, publicKey, algorithm); - - this._keys.set(keyId, jwk); - - const publicJWK = jwk?.toPublic(); - if (!publicJWK) { - throw new Error(`JWK is not a public key`); - } - - return new JwkGenOutput(keyId, publicJWK); - } - - public async generatePQKey(keyType: String, algorithm: JwsAlgorithm): Promise { - - if (keyType !== JwkPqMemStore.mldsaKeyType()) { - throw new Error(`unsupported key type ${keyType}`); - } - - const seed = new TextEncoder().encode(randomKeyId()) - let keys; - if (algorithm === JwsAlgorithm.MLDSA44) { - keys = ml_dsa44.keygen(seed); - } else if(algorithm === JwsAlgorithm.MLDSA65) { - keys = ml_dsa65.keygen(seed); - } else if(algorithm === JwsAlgorithm.MLDSA87) { - keys = ml_dsa87.keygen(seed); - } else { - throw new Error(`unsupported algorithm`); - } - - const keyId = randomKeyId(); - - const jwk = await encodeJwk(keys.secretKey, keys.publicKey, algorithm); - - if(jwk == undefined) - throw new Error("Unexpected error: await encodeJwk(privKey, publicKey, algorithm)"); - - this._keys.set(keyId, jwk); - - const publicJWK = jwk?.toPublic(); - if (!publicJWK) { - throw new Error(`JWK is not a public key`); - } - - return new JwkGenOutput(keyId, publicJWK); - - } - - public async sign(keyId: string, data: Uint8Array, publicKey: Jwk): Promise { - let alg = publicKey.alg(); - let signature = null; - - if(alg === undefined) { - throw new Error("expected a Jwk with an `alg` parameter"); - } - - if (alg !== JwsAlgorithm.EdDSA ) { - throw new Error("unsupported JWS algorithm"); - } else { - if (publicKey.paramsOkp()?.crv !== (EdCurve.Ed25519 as string)) - { - throw new Error("unsupported Okp parameter"); - } - } - - const jwk = this._keys.get(keyId); - - if (jwk) { - const [privateKey, _] = decodeJwk(jwk); - signature = await ed.sign(data, privateKey); - - } else { - throw new Error(`key with id ${keyId} not found`); - } - return signature; - } - - public async signPQ(keyId: string, data: Uint8Array, publicKey: Jwk, ctx: Uint8Array|undefined ): Promise { - let alg = publicKey.alg(); - let signature = null; - - if(alg === undefined) { - throw new Error("expected a Jwk with an `alg` parameter"); - } - - if (alg !== JwsAlgorithm.MLDSA44 && alg !== JwsAlgorithm.MLDSA65 && alg !== JwsAlgorithm.MLDSA87) { - throw new Error("unsupported JWS algorithm"); - } - - const jwk = this._keys.get(keyId); - - if (jwk) { - - const [privateKey, _] = decodeJwk(jwk); - - if(alg == JwsAlgorithm.MLDSA44) - signature = ml_dsa44.sign(privateKey, data, ctx); - else if(alg == JwsAlgorithm.MLDSA65) - signature = ml_dsa65.sign(privateKey, data, ctx); - else if(alg == JwsAlgorithm.MLDSA87) - signature = ml_dsa87.sign(privateKey, data, ctx); - else - throw new Error("unsupported algorithm"); - - } else { - throw new Error(`key with id ${keyId} not found`); - } - return signature; - } - - public async insert(jwk: Jwk): Promise { - const keyId = randomKeyId(); - - if (!jwk.isPrivate) { - throw new Error("expected a JWK with all private key components set"); - } - - if (!jwk.alg()) { - throw new Error("expected a Jwk with an `alg` parameter"); - } - - this._keys.set(keyId, jwk); - - return keyId; - } - - public async delete(keyId: string): Promise { - this._keys.delete(keyId); - } - - public async exists(keyId: string): Promise { - return this._keys.has(keyId); - } - - public count(): number { - return this._keys.size; - } -} - -// Encodes a Ed25519 keypair into a Jwk. -async function encodeJwk(privateKey: Uint8Array, publicKey: Uint8Array, alg: JwsAlgorithm): Promise { - let pub = encodeB64(publicKey); - let priv = encodeB64(privateKey); - - if (alg === JwsAlgorithm.EdDSA) { - return new Jwk({ - "kty": JwkType.Okp, - crv: "Ed25519", - d: priv, - x: pub, - alg, - }); - } else { - return new Jwk({ - "kty": JwkType.Akp, - pub: pub, - priv: priv, - alg, - }); - } - -} - -function decodeJwk(jwk: Jwk): [Uint8Array, Uint8Array] { - if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && - jwk.alg()! !== JwsAlgorithm.MLDSA65 && - jwk.alg()! !== JwsAlgorithm.MLDSA87 && - jwk.alg()! !== JwsAlgorithm.EdDSA) { - throw new Error("unsupported `alg`"); - } - if (jwk.alg()! === JwsAlgorithm.EdDSA) { - const paramsOkp = jwk.paramsOkp(); - if (paramsOkp) { - const d = paramsOkp.d; - - if (d) { - const textEncoder = new TextEncoder(); - const privateKey = decodeB64(textEncoder.encode(d)); - const publicKey = decodeB64(textEncoder.encode(paramsOkp.x)); - return [privateKey, publicKey]; - } else { - throw new Error("missing private key component"); - } - } else { - throw new Error("expected Okp params"); - } - } else { - const paramsPQ = jwk.paramsAkp(); - - if (paramsPQ) { - const priv = paramsPQ.priv; - - if (priv) { - let textEncoder = new TextEncoder(); - const privateKey = decodeB64(textEncoder.encode(priv)); - const publicKey = decodeB64(textEncoder.encode(paramsPQ.pub)); - return [privateKey, publicKey]; - } else { - throw new Error("missing private key component"); - } - } else { - throw new Error("expected Okp params"); - } - - } - -} - -// Returns a random number between `min` and `max` (inclusive). -// SAFETY NOTE: This is not cryptographically secure randomness and thus not suitable for production use. -// It suffices for our testing implementation however and avoids an external dependency. -function getRandomNumber(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -// Returns a random key id. -function randomKeyId(): string { - const randomness = new Uint8Array(20); - for (let index = 0; index < randomness.length; index++) { - randomness[index] = getRandomNumber(0, 255); - } - - return encodeB64(randomness); -} diff --git a/bindings/wasm/lib/pq_verifier.ts b/bindings/wasm/lib/pq_verifier.ts index 2fd14d76e1..9fa5582d58 100644 --- a/bindings/wasm/lib/pq_verifier.ts +++ b/bindings/wasm/lib/pq_verifier.ts @@ -59,65 +59,3 @@ function decodeJwk(jwk: Jwk): Uint8Array { - -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -import { ml_dsa44, ml_dsa65, ml_dsa87 } from '@noble/post-quantum/ml-dsa'; -import { decodeB64, Jwk, IJwsVerifier} from "~identity_wasm"; -import { JwsAlgorithm } from "./jose"; - -export class PQJwsVerifier implements IJwsVerifier{ - - public verify (alg: JwsAlgorithm, signingInput: Uint8Array, decodedSignature: Uint8Array, publicKey: Jwk): void{ - let res = false; - let ctx = undefined; - - if (alg !== JwsAlgorithm.MLDSA44 && - alg !== JwsAlgorithm.MLDSA65 && - alg !== JwsAlgorithm.MLDSA87 && - alg !== JwsAlgorithm.IdMldsa44Ed25519 && - alg !== JwsAlgorithm.IdMldsa65Ed25519) { - throw new Error("unsupported JWS algorithm"); - } - - const pubKey = decodeJwk(publicKey); - - //Domain separator for hybrid signatures - if (alg === JwsAlgorithm.IdMldsa44Ed25519) { - ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 62]); - } else if (alg === JwsAlgorithm.IdMldsa65Ed25519) { - ctx = Uint8Array.from([6, 11, 96, 134, 72, 1, 134, 250, 107, 80, 8, 1, 71]); - } - - if (alg === JwsAlgorithm.MLDSA44 || alg === JwsAlgorithm.IdMldsa44Ed25519) { - res = ml_dsa44.verify(pubKey, signingInput, decodedSignature, ctx); - } else if (alg === JwsAlgorithm.MLDSA65 || alg === JwsAlgorithm.IdMldsa65Ed25519) { - res = ml_dsa65.verify(pubKey, signingInput, decodedSignature, ctx); - } else if (alg === JwsAlgorithm.MLDSA87) { - res = ml_dsa87.verify(pubKey, signingInput, decodedSignature); - } - if (!res) { - throw new Error("signature verification failed"); - } - } - -} - -function decodeJwk(jwk: Jwk): Uint8Array { - if (jwk.alg()! !== JwsAlgorithm.MLDSA44 && jwk.alg()! !== JwsAlgorithm.MLDSA65 && jwk.alg()! !== JwsAlgorithm.MLDSA87) { - throw new Error("unsupported `alg`"); - } - - const paramsPQ = jwk.paramsAkp(); - - if (paramsPQ) { - let textEncoder = new TextEncoder(); - return decodeB64(textEncoder.encode(paramsPQ.pub)); - } else { - throw new Error("expected Okp params"); - } -} - - - diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index b8dd3cc682..0c6d032ecd 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@noble/ed25519": "^1.7.3", "@noble/post-quantum": "^0.2.0", - "@noble/hashes": "^1.4.0", "@types/node-fetch": "^2.6.2", "base64-arraybuffer": "^1.0.2", "jose": "^5.9.6", @@ -336,6 +335,28 @@ } ] }, + "node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "dependencies": { + "@noble/hashes": "1.6.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6537,6 +6558,19 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, + "@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + }, + "@noble/post-quantum": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.2.1.tgz", + "integrity": "sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==", + "requires": { + "@noble/hashes": "1.6.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs index 0fbaa9808a..db99373a99 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator_hybrid.rs @@ -209,215 +209,3 @@ impl WasmJwtCredentialValidatorHybrid { .wasm_result() } } - -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use identity_iota::core::Object; -use identity_iota::core::Url; -use identity_iota::credential::JwtCredentialValidatorHybrid; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::StatusCheck; -use identity_iota::did::CoreDID; - -use super::options::WasmJwtCredentialValidationOptions; -use crate::common::ImportedDocumentLock; -use crate::common::ImportedDocumentReadGuard; -use crate::common::WasmTimestamp; -use crate::credential::options::WasmStatusCheck; -use crate::credential::revocation::status_list_2021::WasmStatusList2021Credential; -use crate::credential::WasmCredential; -use crate::credential::WasmDecodedJwtCredential; -use crate::credential::WasmFailFast; -use crate::credential::WasmJwt; -use crate::credential::WasmSubjectHolderRelationship; -use crate::did::ArrayIToCoreDocument; -use crate::did::IToCoreDocument; -use crate::did::WasmCoreDID; -use crate::did::WasmJwsVerificationOptions; -use crate::error::Result; -use crate::error::WasmResult; -use crate::verification::IJwsVerifier; -use crate::verification::WasmJwsVerifier; - -use wasm_bindgen::prelude::*; - -/// A type for decoding and validating PQ/T {@link Credential}. -#[wasm_bindgen(js_name = JwtCredentialValidatorHybrid)] -pub struct WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid); - -#[wasm_bindgen(js_class = JwtCredentialValidatorHybrid)] -impl WasmJwtCredentialValidatorHybrid { - /// Creates a new {@link JwtCredentialValidatorHybrid}. - #[wasm_bindgen(constructor)] - #[allow(non_snake_case)] - pub fn new(traditionalSignatureVerifier: Option, pqSignatureVerifier: Option) -> WasmJwtCredentialValidatorHybrid { - let traditional_signature_verifier = WasmJwsVerifier::new(traditionalSignatureVerifier); - let pq_signature_verifier = WasmJwsVerifier::new(pqSignatureVerifier); - WasmJwtCredentialValidatorHybrid(JwtCredentialValidatorHybrid::with_signature_verifiers(traditional_signature_verifier, pq_signature_verifier)) - } - - /// Decodes and validates a {@link Credential} issued as a JWS. A {@link DecodedJwtCredential} is returned upon - /// success. - /// - /// The following properties are validated according to `options`: - /// - the issuer's signature on the JWS, - /// - the expiration date, - /// - the issuance date, - /// - the semantic structure. - /// - /// # Warning - /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be - /// trusted. This section contains more information on additional checks that should be carried out before and after - /// calling this method. - /// - /// ## The state of the issuer's DID Document - /// The caller must ensure that `issuer` represents an up-to-date DID Document. - /// - /// ## Properties that are not validated - /// There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: - /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. - /// These should be manually checked after validation, according to your requirements. - /// - /// # Errors - /// An error is returned whenever a validated condition is not satisfied. - #[wasm_bindgen] - pub fn validate( - &self, - credential_jwt: &WasmJwt, - issuer: &IToCoreDocument, - options: &WasmJwtCredentialValidationOptions, - fail_fast: WasmFailFast, - ) -> Result { - let issuer_lock = ImportedDocumentLock::from(issuer); - let issuer_guard = issuer_lock.try_read()?; - - self - .0 - .validate(&credential_jwt.0, &issuer_guard, &options.0, fail_fast.into()) - .wasm_result() - .map(WasmDecodedJwtCredential) - } - - /// Decode and verify the JWS signature of a {@link Credential} issued as a JWT using the DID Document of a trusted - /// issuer. - /// - /// A {@link DecodedJwtCredential} is returned upon success. - /// - /// # Warning - /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date. - /// - /// ## Proofs - /// Only the JWS signature is verified. If the {@link Credential} contains a `proof` property this will not be - /// verified by this method. - /// - /// # Errors - /// This method immediately returns an error if - /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt - /// to verify the credential's signature will be made and an error is returned upon failure. - #[wasm_bindgen(js_name = verifySignature)] - #[allow(non_snake_case)] - pub fn verify_signature( - &self, - credential: &WasmJwt, - trustedIssuers: &ArrayIToCoreDocument, - options: &WasmJwsVerificationOptions, - ) -> Result { - let issuer_locks: Vec = trustedIssuers.into(); - let trusted_issuers: Vec> = issuer_locks - .iter() - .map(ImportedDocumentLock::try_read) - .collect::>>>( - )?; - - self - .0 - .verify_signature(&credential.0, &trusted_issuers, &options.0) - .wasm_result() - .map(WasmDecodedJwtCredential) - } - - /// Validate that the credential expires on or after the specified timestamp. - #[wasm_bindgen(js_name = checkExpiresOnOrAfter)] - pub fn check_expires_on_or_after(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { - JwtCredentialValidatorUtils::check_expires_on_or_after(&credential.0, timestamp.0).wasm_result() - } - - /// Validate that the credential is issued on or before the specified timestamp. - #[wasm_bindgen(js_name = checkIssuedOnOrBefore)] - pub fn check_issued_on_or_before(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> { - JwtCredentialValidatorUtils::check_issued_on_or_before(&credential.0, timestamp.0).wasm_result() - } - - /// Validate that the relationship between the `holder` and the credential subjects is in accordance with - /// `relationship`. The `holder` parameter is expected to be the URL of the holder. - #[wasm_bindgen(js_name = checkSubjectHolderRelationship)] - pub fn check_subject_holder_relationship( - credential: &WasmCredential, - holder: &str, - relationship: WasmSubjectHolderRelationship, - ) -> Result<()> { - let holder: Url = Url::parse(holder).wasm_result()?; - JwtCredentialValidatorUtils::check_subject_holder_relationship(&credential.0, &holder, relationship.into()) - .wasm_result() - } - - /// Checks whether the credential status has been revoked. - /// - /// Only supports `RevocationBitmap2022`. - #[wasm_bindgen(js_name = checkStatus)] - #[allow(non_snake_case)] - pub fn check_status( - credential: &WasmCredential, - trustedIssuers: &ArrayIToCoreDocument, - statusCheck: WasmStatusCheck, - ) -> Result<()> { - let issuer_locks: Vec = trustedIssuers.into(); - let trusted_issuers: Vec> = issuer_locks - .iter() - .map(ImportedDocumentLock::try_read) - .collect::>>>( - )?; - let status_check: StatusCheck = statusCheck.into(); - JwtCredentialValidatorUtils::check_status(&credential.0, &trusted_issuers, status_check).wasm_result() - } - - /// Checks wheter the credential status has been revoked using `StatusList2021`. - #[wasm_bindgen(js_name = checkStatusWithStatusList2021)] - pub fn check_status_with_status_list_2021( - credential: &WasmCredential, - status_list: &WasmStatusList2021Credential, - status_check: WasmStatusCheck, - ) -> Result<()> { - JwtCredentialValidatorUtils::check_status_with_status_list_2021( - &credential.0, - &status_list.inner, - status_check.into(), - ) - .wasm_result() - } - - /// Utility for extracting the issuer field of a {@link Credential} as a DID. - /// - /// ### Errors - /// - /// Fails if the issuer field is not a valid DID. - #[wasm_bindgen(js_name = extractIssuer)] - pub fn extract_issuer(credential: &WasmCredential) -> Result { - JwtCredentialValidatorUtils::extract_issuer::(&credential.0) - .map(WasmCoreDID::from) - .wasm_result() - } - - /// Utility for extracting the issuer field of a credential in JWT representation as DID. - /// - /// # Errors - /// - /// If the JWT decoding fails or the issuer field is not a valid DID. - #[wasm_bindgen(js_name = extractIssuerFromJwt)] - pub fn extract_issuer_from_jwt(credential: &WasmJwt) -> Result { - JwtCredentialValidatorUtils::extract_issuer_from_jwt::(&credential.0) - .map(WasmCoreDID::from) - .wasm_result() - } -} diff --git a/bindings/wasm/src/jose/jwk.rs b/bindings/wasm/src/jose/jwk.rs index 6265b7a677..6b0b6a7b37 100644 --- a/bindings/wasm/src/jose/jwk.rs +++ b/bindings/wasm/src/jose/jwk.rs @@ -179,17 +179,6 @@ impl WasmJwk { } } - /// If this JWK is of kty AKP, returns those parameters. - #[wasm_bindgen(js_name = paramsAkp)] - pub fn params_akp(&self) -> crate::error::Result> { - if let JwkParams::Akp(params_akp) = self.0.params() { - // WARNING: this does not validate the return type. Check carefully. - Ok(Some(JsValue::from_serde(params_akp).wasm_result()?.unchecked_into())) - } else { - Ok(None) - } - } - /// Returns a clone of the {@link Jwk} with _all_ private key components unset. /// Nothing is returned when `kty = oct` as this key type is not considered public by this library. #[wasm_bindgen(js_name = toPublic)] diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index d8faec2931..549516005d 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -47,17 +47,4 @@ pub fn start() -> Result<(), JsValue> { #[wasm_bindgen(typescript_custom_section)] const CUSTOM_IMPORTS: &'static str = r#" import { JwsAlgorithm, JwkOperation, JwkUse, JwkType, CompositeAlgId} from '../lib/jose/index'; -import { - Proposal, - ProposalOutput, - Transaction, - TransactionOutput, - TransactionBuilder, - SponsorFn, - ApproveProposal, - CreateProposal, - ExecuteProposal, - UpdateDid, - ProposalResult, -} from '../lib/index'; "#; diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 5dccdbdccd..7a6dd1fad5 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -13,8 +13,6 @@ mod key_id_storage; mod method_digest; mod signature_options; mod wasm_storage; -mod wasm_storage_signer; -mod wasm_transaction_signer; mod jwk_storage_pqc; pub use jpt_timeframe_revocation_ext::*; diff --git a/examples/1_advanced/13_pq.rs b/examples/1_advanced/13_pq.rs deleted file mode 100644 index 3d4d1c49eb..0000000000 --- a/examples/1_advanced/13_pq.rs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use examples::get_address_with_funds; -use examples::random_stronghold_path; -use examples::MemStorage; -use identity_iota::core::Duration; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::Timestamp; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::DecodedJwtCredential; -use identity_iota::credential::DecodedJwtPresentation; -use identity_iota::credential::FailFast; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidator; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::JwtPresentationOptions; -use identity_iota::credential::JwtPresentationValidationOptions; -use identity_iota::credential::JwtPresentationValidator; -use identity_iota::credential::JwtPresentationValidatorUtils; -use identity_iota::credential::Presentation; -use identity_iota::credential::PresentationBuilder; -use identity_iota::credential::Subject; -use identity_iota::credential::SubjectHolderRelationship; -use identity_iota::did::CoreDID; -use identity_iota::did::DID; -use identity_iota::document::verifiable::JwsVerificationOptions; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsDocumentExtPQC; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::storage::KeyType; -use identity_iota::verification::jws::JwsAlgorithm; -use identity_iota::verification::MethodScope; -use identity_pqc_verifier::PQCJwsVerifier; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; -use serde_json::json; - -// The API endpoint of an IOTA node, e.g. Hornet. -const API_ENDPOINT: &str = "http://localhost"; -// The faucet endpoint allows requesting funds for testing purposes. -const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; - -async fn create_did( - client: &Client, - secret_manager: &SecretManager, - storage: &MemStorage, - key_type: KeyType, - alg: JwsAlgorithm, -) -> anyhow::Result<(Address, IotaDocument, String)> { - // Get an address with funds for testing. - let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; - - // Get the Bech32 human-readable part (HRP) of the network. - let network_name: NetworkName = client.network_name().await?; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // New Verification Method containing a PQC key - let fragment = document - .generate_method_pqc(storage, key_type, alg, None, MethodScope::VerificationMethod) - .await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; - println!("Published DID document: {document:#}"); - - Ok((address, document, fragment)) -} - -/// Demonstrates how to create a Post-Quantum Verifiable Credential. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // =========================================================================== - // Step 1: Create identitiy for the issuer. - // =========================================================================== - - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - let secret_manager_issuer = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( - &client, - &secret_manager_issuer, - &storage_issuer, - JwkMemStore::PQ_KEY_TYPE, - JwsAlgorithm::ML_DSA_87, - ) - .await?; - - let secret_manager_holder = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - - let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( - &client, - &secret_manager_holder, - &storage_holder, - JwkMemStore::PQ_KEY_TYPE, - JwsAlgorithm::SLH_DSA_SHA2_128s, - ) - .await?; - - // ====================================================================================== - // Step 2: Issuer creates and signs a Verifiable Credential with a Post-Quantum algorithm. - // ====================================================================================== - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": holder_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt_pqc( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("VC successfully validated"); - - // =========================================================================== - // Step 3: Issuer sends the Verifiable Credential to the holder. - // =========================================================================== - println!( - "Sending credential (as JWT) to the holder: {}\n", - credential_jwt.as_str() - ); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== - - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - // The verifier and holder also agree that the signature should have an expiry date - // 10 minutes from now. - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - // =========================================================================== - // Step 5: Holder creates and signs a verifiable presentation from the issued credential. - // =========================================================================== - - // Create an unsigned Presentation from the previously issued Verifiable Credential. - let presentation: Presentation = - PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - // Create a JWT verifiable presentation using the holder's verification method - // and include the requested challenge and expiry timestamp. - let presentation_jwt: Jwt = holder_document - .create_presentation_jwt_pqc( - &presentation, - &storage_holder, - &fragment_holder, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. - // =========================================================================== - println!( - "Sending presentation (as JWT) to the verifier: {}\n", - presentation_jwt.as_str() - ); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - - // Resolve the holder's document. - let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; - let holder: IotaDocument = resolver.resolve(&holder_did).await?; - - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( - PQCJwsVerifier::default(), - ) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator: JwtCredentialValidator = - JwtCredentialValidator::with_signature_verifier(PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - println!("--------------------------"); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - Ok(()) -} diff --git a/examples/1_advanced/14_hybrid.rs b/examples/1_advanced/14_hybrid.rs deleted file mode 100644 index f01a5de0ef..0000000000 --- a/examples/1_advanced/14_hybrid.rs +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2024 Fondazione Links -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; -use examples::get_address_with_funds; -use examples::random_stronghold_path; -use examples::MemStorage; -use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::core::Duration; -use identity_iota::core::FromJson; -use identity_iota::core::Object; -use identity_iota::core::Timestamp; -use identity_iota::core::Url; -use identity_iota::credential::Credential; -use identity_iota::credential::CredentialBuilder; -use identity_iota::credential::DecodedJwtCredential; -use identity_iota::credential::DecodedJwtPresentation; -use identity_iota::credential::FailFast; -use identity_iota::credential::Jwt; -use identity_iota::credential::JwtCredentialValidationOptions; -use identity_iota::credential::JwtCredentialValidatorHybrid; -use identity_iota::credential::JwtCredentialValidatorUtils; -use identity_iota::credential::JwtPresentationOptions; -use identity_iota::credential::JwtPresentationValidationOptions; -use identity_iota::credential::JwtPresentationValidatorHybrid; -use identity_iota::credential::JwtPresentationValidatorUtils; -use identity_iota::credential::Presentation; -use identity_iota::credential::PresentationBuilder; -use identity_iota::credential::Subject; -use identity_iota::credential::SubjectHolderRelationship; -use identity_iota::did::CoreDID; -use identity_iota::did::DID; -use identity_iota::document::verifiable::JwsVerificationOptions; -use identity_iota::iota::IotaClientExt; -use identity_iota::iota::IotaDocument; -use identity_iota::iota::IotaIdentityClientExt; -use identity_iota::iota::NetworkName; -use identity_iota::resolver::Resolver; -use identity_iota::storage::JwkDocumentExtHybrid; -use identity_iota::storage::JwkMemStore; -use identity_iota::storage::JwsSignatureOptions; -use identity_iota::storage::KeyIdMemstore; -use identity_iota::verification::jwk::CompositeAlgId; -use identity_iota::verification::MethodScope; -use identity_pqc_verifier::PQCJwsVerifier; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_sdk::client::Client; -use iota_sdk::client::Password; -use iota_sdk::types::block::address::Address; -use iota_sdk::types::block::output::AliasOutput; -use serde_json::json; - -// // The API endpoint of an IOTA node, e.g. Hornet. -// const API_ENDPOINT: &str = "http://localhost"; -// // The faucet endpoint allows requesting funds for testing purposes. -// const FAUCET_ENDPOINT: &str = "http://localhost/faucet/api/enqueue"; - -const API_ENDPOINT: &str = "https://api.testnet.shimmer.network"; -const FAUCET_ENDPOINT: &str = "https://faucet.testnet.shimmer.network/api/enqueue"; - -async fn create_did( - client: &Client, - secret_manager: &SecretManager, - storage: &MemStorage, - alg_id: CompositeAlgId, -) -> anyhow::Result<(Address, IotaDocument, String)> { - // Get an address with funds for testing. - let address: Address = get_address_with_funds(client, secret_manager, FAUCET_ENDPOINT).await?; - - // Get the Bech32 human-readable part (HRP) of the network. - let network_name: NetworkName = client.network_name().await?; - - // Create a new DID document with a placeholder DID. - // The DID will be derived from the Alias Id of the Alias Output after publishing. - let mut document: IotaDocument = IotaDocument::new(&network_name); - - // New Verification Method containing a PQC key - let fragment = document - .generate_method_hybrid(storage, alg_id, None, MethodScope::VerificationMethod) - .await?; - - // Construct an Alias Output containing the DID document, with the wallet address - // set as both the state controller and governor. - let alias_output: AliasOutput = client.new_did_output(address, document, None).await?; - - // Publish the Alias Output and get the published DID document. - let document: IotaDocument = client.publish_did_output(secret_manager, alias_output).await?; - println!("Published DID document: {document:#}"); - - Ok((address, document, fragment)) -} - -/// Demonstrates how to create a DID Document and publish it in a new Alias Output. -/// -/// In this example we connect to a locally running private network, but it can be adapted -/// to run on any IOTA node by setting the network and faucet endpoints. -/// -/// See the following instructions on running your own private network -/// https://github.com/iotaledger/hornet/tree/develop/private_tangle -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Create a new client to interact with the IOTA ledger. - let client: Client = Client::builder() - .with_primary_node(API_ENDPOINT, None)? - .finish() - .await?; - - let secret_manager_issuer = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_1".to_owned())) - .build(random_stronghold_path())?, - ); - - let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, issuer_document, fragment_issuer): (Address, IotaDocument, String) = create_did( - &client, - &secret_manager_issuer, - &storage_issuer, - CompositeAlgId::IdMldsa65Ed25519, - ) - .await?; - - let secret_manager_holder = SecretManager::Stronghold( - StrongholdSecretManager::builder() - .password(Password::from("secure_password_2".to_owned())) - .build(random_stronghold_path())?, - ); - - let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); - - let (_, holder_document, fragment_holder): (Address, IotaDocument, String) = create_did( - &client, - &secret_manager_holder, - &storage_holder, - CompositeAlgId::IdMldsa65Ed25519, - ) - .await?; - - // Create a credential subject indicating the degree earned by Alice. - let subject: Subject = Subject::from_json_value(json!({ - "id": holder_document.id().as_str(), - "name": "Alice", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts", - }, - "GPA": "4.0", - }))?; - - // Build credential using subject above and issuer. - let credential: Credential = CredentialBuilder::default() - .id(Url::parse("https://example.edu/credentials/3732")?) - .issuer(Url::parse(issuer_document.id().as_str())?) - .type_("UniversityDegreeCredential") - .subject(subject) - .build()?; - - let credential_jwt: Jwt = issuer_document - .create_credential_jwt_hybrid( - &credential, - &storage_issuer, - &fragment_issuer, - &JwsSignatureOptions::default(), - None, - ) - .await?; - - println!("Credential JWT: {}", credential_jwt.as_str()); - - // Before sending this credential to the holder the issuer wants to validate that some properties - // of the credential satisfy their expectations. - - // Validate the credential's signature using the issuer's DID Document, the credential's semantic structure, - // that the issuance date is not in the future and that the expiration date is not in the past: - let decoded_credential: DecodedJwtCredential = - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) - .validate::<_, Object>( - &credential_jwt, - &issuer_document, - &JwtCredentialValidationOptions::default(), - FailFast::FirstError, - ) - .unwrap(); - - println!("VC successfully validated"); - - println!("Credential JSON > {:#}", decoded_credential.credential); - - // =========================================================================== - // Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation. - // =========================================================================== - - // A unique random challenge generated by the requester per presentation can mitigate replay attacks. - let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; - - // The verifier and holder also agree that the signature should have an expiry date - // 10 minutes from now. - let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); - - // =========================================================================== - // Step 5: Holder creates and signs a verifiable presentation from the issued credential. - // =========================================================================== - - // Create an unsigned Presentation from the previously issued Verifiable Credential. - let presentation: Presentation = - PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) - .credential(credential_jwt) - .build()?; - - // Create a JWT verifiable presentation using the holder's verification method - // and include the requested challenge and expiry timestamp. - let presentation_jwt: Jwt = holder_document - .create_presentation_jwt_hybrid( - &presentation, - &storage_holder, - &fragment_holder, - &JwsSignatureOptions::default().nonce(challenge.to_owned()), - &JwtPresentationOptions::default().expiration_date(expires), - ) - .await?; - - // =========================================================================== - // Step 6: Holder sends a verifiable presentation to the verifier. - // =========================================================================== - println!( - "Sending presentation (as JWT) to the verifier: {}", - presentation_jwt.as_str() - ); - - // =========================================================================== - // Step 7: Verifier receives the Verifiable Presentation and verifies it. - // =========================================================================== - - // The verifier wants the following requirements to be satisfied: - // - JWT verification of the presentation (including checking the requested challenge to mitigate replay attacks) - // - JWT verification of the credentials. - // - The presentation holder must always be the subject, regardless of the presence of the nonTransferable property - // - The issuance date must not be in the future. - - let presentation_verifier_options: JwsVerificationOptions = - JwsVerificationOptions::default().nonce(challenge.to_owned()); - - let mut resolver: Resolver = Resolver::new(); - resolver.attach_iota_handler(client); - - // Resolve the holder's document. - let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; - let holder: IotaDocument = resolver.resolve(&holder_did).await?; - - // Validate presentation. Note that this doesn't validate the included credentials. - let presentation_validation_options = - JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); - let presentation: DecodedJwtPresentation = - JwtPresentationValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()) - .validate(&presentation_jwt, &holder, &presentation_validation_options)?; - - // Concurrently resolve the issuers' documents. - let jwt_credentials: &Vec = &presentation.presentation.verifiable_credential; - let issuers: Vec = jwt_credentials - .iter() - .map(JwtCredentialValidatorUtils::extract_issuer_from_jwt) - .collect::, _>>()?; - let issuers_documents: HashMap = resolver.resolve_multiple(&issuers).await?; - - // Validate the credentials in the presentation. - let credential_validator = - JwtCredentialValidatorHybrid::with_signature_verifiers(EdDSAJwsVerifier::default(), PQCJwsVerifier::default()); - let validation_options: JwtCredentialValidationOptions = JwtCredentialValidationOptions::default() - .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); - - for (index, jwt_vc) in jwt_credentials.iter().enumerate() { - // SAFETY: Indexing should be fine since we extracted the DID from each credential and resolved it. - let issuer_document: &IotaDocument = &issuers_documents[&issuers[index]]; - - let _decoded_credential: DecodedJwtCredential = credential_validator - .validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError) - .unwrap(); - } - - // Since no errors were thrown by `verify_presentation` we know that the validation was successful. - println!("VP successfully validated: {:#?}", presentation.presentation); - - // Note that we did not declare a latest allowed issuance date for credentials. This is because we only want to check - // that the credentials do not have an issuance date in the future which is a default check. - - Ok(()) -} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3605b86904..d4e4aa746a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -17,27 +17,10 @@ primitive-types = "0.12.1" rand = "0.8.5" sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } serde_json = { version = "1.0", default-features = false } -tokio = { version = "1.43", default-features = false, features = ["rt", "macros"] } +tokio = { version = "1.29", default-features = false, features = ["rt"] } identity_pqc_verifier = { path = "../identity_pqc_verifier", default-features = true } serde.workspace = true -[dependencies.identity_iota] -path = "../identity_iota" -default-features = false -features = [ - "domain-linkage", - "jpt-bbs-plus", - "iota-client", - "send-sync-storage", - "memstore", - "resolver", - "revocation-bitmap", - "sd-jwt", - "status-list-2021", - "keytool", - "hybrid-liboqs" -] - [lib] path = "utils/utils.rs" @@ -130,13 +113,5 @@ path = "1_advanced/12_pq.rs" name = "12_pq" [[example]] -path = "1_advanced/13_iota_keytool_integration.rs" -name = "13_iota_keytool_integration" - -[[example]] -path = "1_advanced/13_pq.rs" -name = "13_pq" - -[[example]] -path = "1_advanced/14_hybrid.rs" -name = "14_hybrid" +path = "1_advanced/13_hybrid.rs" +name = "13_hybrid" diff --git a/examples/README.md b/examples/README.md index 1c251fd942..6dd319d1ca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,15 +37,22 @@ The following basic CRUD (Create, Read, Update, Delete) examples are available: The following advanced examples are available: -| Name | Information | -| :------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------| -| [4_identity_history](./1_advanced/4_identity_history.rs) | Demonstrates fetching the history of an identity. | -| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | -| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | -| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | -| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | -| [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to create an Anonymous Credential with BBS+. | -| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a credential. | -| [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | +| Name | Information | +| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | +| [0_did_controls_did](./1_advanced/0_did_controls_did.rs) | Demonstrates how an identity can control another identity. | +| [1_did_issues_nft](./1_advanced/1_did_issues_nft.rs) | Demonstrates how an identity can issue and own NFTs, and how observers can verify the issuer of the NFT. | +| [2_nft_owns_did](./1_advanced/2_nft_owns_did.rs) | Demonstrates how an identity can be owned by NFTs, and how observers can verify that relationship. | +| [3_did_issues_tokens](./1_advanced/3_did_issues_tokens.rs) | Demonstrates how an identity can issue and control a Token Foundry and its tokens. | +| [4_alias_output_history](./1_advanced/4_alias_output_history.rs) | Demonstrates fetching the history of an Alias Output. | +| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | +| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | +| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | +| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| [9_zkp](./1_advanced/9_zkp.rs) | Demonstrates how to generate, present and verify a ZK VC (BBS+) with Selective Disclosure. | +| [10_zkp_revocation](./1_advanced/10_zkp_revocation.rs) | Demonstrates how to revoke a ZK VC (BBS+). | +| [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | | [12_pq](./1_advanced/12_pq.rs) | Demonstrates how to generate, present and verify a VC with pure PQ signature. | -| [13_hybrid](./1_advanced/13_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature | \ No newline at end of file +| [13_hybrid](./1_advanced/13_hybrid.rs) | Demonstrates how to generate, present and verify a VC with PQ/T hybrid signature | + +#### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. + diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 1178b1fc6c..94b512a715 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -76,14 +76,6 @@ pqc-liboqs = ["identity_storage/pqc-liboqs"] hybrid = ["identity_storage/hybrid", "identity_credential/hybrid"] hybrid-liboqs = ["identity_storage/hybrid-liboqs", "identity_credential/hybrid"] -# Enables PQC -pqc = ["identity_storage/pqc"] -pqc-liboqs = ["identity_storage/pqc-liboqs"] - -# Enables PQ/T Hybrid -hybrid = ["identity_storage/hybrid", "identity_credential/hybrid"] -hybrid-liboqs = ["identity_storage/hybrid-liboqs", "identity_credential/hybrid"] - [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 0ec1d00a5e..5e4bdf2fbb 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -285,7 +285,7 @@ impl + 'static> Resolver> { } } -#[cfg(all(feature = "iota", not(target_arch = "wasm32")))] +#[cfg(feature = "iota")] mod iota_handler { use crate::ErrorCause; From f38f0eeaefb4113e930b2d03ba5a0e75f83bba3e Mon Sep 17 00:00:00 2001 From: AleCla97 Date: Wed, 23 Apr 2025 15:46:10 +0200 Subject: [PATCH 163/163] remove example number --- .../examples/src/1_advanced/{12_hybrid.ts => hybrid.ts} | 0 bindings/wasm/examples/src/1_advanced/{11_pq.ts => pq.ts} | 0 bindings/wasm/examples/src/main.ts | 8 ++++---- examples/1_advanced/{13_hybrid.rs => hybrid.rs} | 0 examples/1_advanced/{12_pq.rs => pq.rs} | 0 examples/Cargo.toml | 8 ++++---- 6 files changed, 8 insertions(+), 8 deletions(-) rename bindings/wasm/examples/src/1_advanced/{12_hybrid.ts => hybrid.ts} (100%) rename bindings/wasm/examples/src/1_advanced/{11_pq.ts => pq.ts} (100%) rename examples/1_advanced/{13_hybrid.rs => hybrid.rs} (100%) rename examples/1_advanced/{12_pq.rs => pq.rs} (100%) diff --git a/bindings/wasm/examples/src/1_advanced/12_hybrid.ts b/bindings/wasm/examples/src/1_advanced/hybrid.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/12_hybrid.ts rename to bindings/wasm/examples/src/1_advanced/hybrid.ts diff --git a/bindings/wasm/examples/src/1_advanced/11_pq.ts b/bindings/wasm/examples/src/1_advanced/pq.ts similarity index 100% rename from bindings/wasm/examples/src/1_advanced/11_pq.ts rename to bindings/wasm/examples/src/1_advanced/pq.ts diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index b64e5d2031..0a28208978 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -14,8 +14,8 @@ import { createVP } from "./0_basic/6_create_vp"; import { revokeVC } from "./0_basic/7_revoke_vc"; import { didControlsDid } from "./1_advanced/0_did_controls_did"; import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc"; -import { pq } from "./1_advanced/11_pq"; -import { hybrid } from "./1_advanced/12_hybrid"; +import { pq } from "./1_advanced/pq"; +import { hybrid } from "./1_advanced/hybrid"; import { didIssuesNft } from "./1_advanced/1_did_issues_nft"; import { nftOwnsDid } from "./1_advanced/2_nft_owns_did"; import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens"; @@ -72,9 +72,9 @@ async function main() { return await zkp_revocation(); case "10_sd_jwt_vc": return await sdJwtVc(); - case "11_pq": + case "pq": return await pq(); - case "12_hybrid": + case "hybrid": return await hybrid(); default: throw "Unknown example name: '" + argument + "'"; diff --git a/examples/1_advanced/13_hybrid.rs b/examples/1_advanced/hybrid.rs similarity index 100% rename from examples/1_advanced/13_hybrid.rs rename to examples/1_advanced/hybrid.rs diff --git a/examples/1_advanced/12_pq.rs b/examples/1_advanced/pq.rs similarity index 100% rename from examples/1_advanced/12_pq.rs rename to examples/1_advanced/pq.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d4e4aa746a..a66a0b3232 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -109,9 +109,9 @@ path = "1_advanced/11_linked_verifiable_presentation.rs" name = "11_linked_verifiable_presentation" [[example]] -path = "1_advanced/12_pq.rs" -name = "12_pq" +path = "1_advanced/pq.rs" +name = "pq" [[example]] -path = "1_advanced/13_hybrid.rs" -name = "13_hybrid" +path = "1_advanced/hybrid.rs" +name = "hybrid"