From 08ae148e2c12cee34204013c691c2e8036ebcd62 Mon Sep 17 00:00:00 2001 From: Alberto Solavagione Date: Thu, 31 Oct 2024 18:04:50 +0100 Subject: [PATCH 01/14] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index fe5200c40..5f1606fb7 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 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 02/14] 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 9665bcc3f..9fd4108fd 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 1b8382f25..e0881936e 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 2f4171ac3..c99104a26 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 29225efaa..e6b858f53 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 03/14] 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 099297193..24de5c14b 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 632b1534d..205748272 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 727be0cd4..25eed51cd 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 563102b66..8c333538f 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 d63b2580e..4b110162a 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 7b68591ef..a3c85f2e5 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 6da78fb11..d50031a87 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 6899df315..de9cf6118 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 ac09ddaee..468935128 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 f1217faa8..072bd6f93 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 9b0785731..1dc5285bc 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 302013e7c..34c53b904 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 90001516c..b92138d8e 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 ee29f4cf2..b97f00594 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 97112ef53..fddde6b41 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 07d743c76..36d4ccfd2 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 9af2d9cb1..e753a0c99 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 9392176bd..4c9658927 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 ffbff252f..599e3bbf4 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 e67f11ebe..7ef55d02a 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 8e69ba856..a4cbc5cbe 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 193d14c3d..e85db05c2 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 905ec373c..5d8bcb3e9 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 3d1a39ba2..707b5bd5f 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 f4ce3dde1..262843d5d 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 0f9192cc3..3d52304e4 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 3016b3cfa..58236434a 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 b5f86a57b..90cd700ec 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 75efb3341..abc27c704 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 654c1ea79..568d1594d 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 5c7f2f2a8..e8ec970a5 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 fd68bf1b4..ddeb64866 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 8f0e0b168..dedc53d1b 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 982cb3754..478695db7 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 2c43d1055..59cc34e6d 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 04/14] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5f1606fb7..15be3ad7d 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 05/14] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15be3ad7d..abf2dc712 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 06/14] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index abf2dc712..93f640361 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 07/14] Update README.md --- README.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 93f640361..8ab25af98 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 08/14] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8ab25af98..f2878c483 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 09/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2878c483..0b8532894 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 10/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b8532894..400a9ad4c 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 11/14] 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 9fd4108fd..1d90fb842 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 e6b858f53..358a08b2d 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 12/14] 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 358a08b2d..70a3fdc64 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 13/14] 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 1d90fb842..6cd697577 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 e0881936e..b2ccb4f60 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 c99104a26..ebd0e743a 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 70a3fdc64..e2de9dab6 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 14/14] 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 db85317c0..8fd1340bb 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 000000000..a592cf44d --- /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(()) +}