Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

synch #5

Merged
merged 14 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,50 @@
# Zero-Knowledge (ZK)
# 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 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).

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:
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.

* **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.
```json
"compositeJwk": {
"algId": ".. composite key OID ..",
"pqPublicKey": {
".. PQ JWK encoded key .."
},
"traditionalPublicKey": {
".. Traditional JWK encoded key .."
}
}
```

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/).
**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.

# PQ/T Hybrid
# did:compositejwk

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 now supports Zero-Knowledge functionalities, thanks to the [integration](https://github.com/iotaledger/identity.rs/pull/1285) of two key components:

* **BBS+ Signature**: the scheme has been integrated through the [ZKryptium](https://github.com/Cybersecurity-LINKS/zkryptium) library for secure and privacy-preserving VC management with ZK selective disclosure.
* **JSON Web Proof Representation**: the [json-proof-token](https://github.com/Cybersecurity-LINKS/json-proof-token) library implements the JSON Web Proof (JWP) specification, enabling verifiable claims with selective disclosure.

**Note**: the BBS+ signature scheme uses traditional cryptography, hence it is not quantum-secure; for more details on the implementation and how to use these features, refer to the [full documentation](https://wiki.iota.org/identity.rs/how-tos/verifiable-credentials/zero-knowledge-selective-disclosure/).

# Examples

To test 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
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.

4 changes: 3 additions & 1 deletion examples/1_advanced/12_pq.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 3 additions & 1 deletion examples/1_advanced/13_hybrid.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions examples/1_advanced/14_did_web.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
5 changes: 5 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,8 @@ name = "hybrid"
path = "demo/traditional_zk.rs"
name = "traditional_zk"

[[example]]
path = "demo/revocation_zk.rs"
name = "revocation_zk"


48 changes: 22 additions & 26 deletions examples/demo/hybrid.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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")?)
Expand All @@ -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>(
Expand All @@ -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<Jwt> =PresentationBuilder::new(
alice_document.id().to_url().into(),
Expand All @@ -117,39 +126,28 @@ 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<CoreDocument> = Resolver::new();
resolver.attach_did_compositejwk_handler();

// Resolve the holder's document.
let holder_did: DIDCompositeJwk = JwtPresentationValidatorUtils::extract_holder::<DIDCompositeJwk>(&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<Jwt> = JwtPresentationValidatorHybrid::with_signature_verifiers(
EdDSAJwsVerifier::default(),
PQCJwsVerifier::default(),
).validate(&presentation_jwt, &holder, &presentation_validation_options)?;

// Concurrently resolve the issuers' documents.
let jwt_credentials: &Vec<Jwt> = &presentation.presentation.verifiable_credential;

let mut resolver_web: Resolver<CoreDocument> = Resolver::new();
Expand All @@ -161,22 +159,20 @@ async fn main() -> anyhow::Result<()> {
.collect::<Result<Vec<CoreDID>, _>>()?;
let issuers_documents: HashMap<CoreDID, CoreDocument> = resolver_web.resolve_multiple(&issuers).await?;

// Validate the credentials in the presentation.
let credential_validator: JwtCredentialValidatorHybrid<EdDSAJwsVerifier, PQCJwsVerifier> =
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<Object> = credential_validator
.validate::<_, Object>(jwt_vc, issuer_document, &validation_options, FailFast::FirstError)
.unwrap();
}

println!("{}: Verifiable Presentation successfully verified", "[Verifier]".green());
println!("{} : VP successfully verified, access granted", "[Verifier]".green());

Ok(())
}
Loading
Loading