Skip to content

Commit

Permalink
Add ZK BBS+-based selectively disclosable credentials (JPT) (#1355)
Browse files Browse the repository at this point in the history
* Support BBS+ and JWP (#1285)

* merge main

* Wasm bindings for Jpt credentials

* JPT presentation bindings

* docs

* jsonprooftoken payloads

* Refactor `RevocationTimeframeStatus` to align with other setups (#1340)

* refactor `RevocationTimeframeStatus` to other setups

* fix smaller typos

* binding coverage for jsonprooftoken

* Use latest releases of zkryptium/json-proof-token and add new BLS key representation (#1339)

* update zkryptium/json-proof-token deps and new BLS key representation

* minor fix

* Use zkryptium for cryptographic operations inside Memstore (#1351)

* update zkryptium/json-proof-token deps and new BLS key representation

* minor fix

* use zkryptium for crypto operations and JPT for serialization

* fix format

* Feat/jpt bbs+ sd stronghold impl (#1354)

* Implement JwkStorageExt for StrongholdStorage

* reorganize code

* persist changes to stronghold when creating bbs+ keypair, clippy, fmt

* feature gate

* zkp wasm example

* zkp_revocation wasm example

* wasm bindings

* fix docs

* rename JwkStorageExt to JwkStorageBbsPlusExt

* JwkStorageBbsPlusExt impl refactor for Stronghold, MemStore, WasmStore

* Squashed commit of the following:

commit 30c9bf2
Author: Foorack / Max Faxälv <[email protected]>
Date:   Tue Apr 2 10:32:48 2024 +0200

    inherit `repository` in identity_verification (#1348)

commit 1e9c9a3
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Wed Mar 27 15:35:29 2024 +0100

    Release wasm-v1.2.0 (#1345)

commit 84a630d
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Wed Mar 27 15:32:19 2024 +0100

    Release v1.2.0 (#1347)

commit 1aba4b5
Author: Eike Haß <[email protected]>
Date:   Wed Mar 27 13:13:27 2024 +0100

    removed dev_dep version

commit 0352b84
Author: Enrico Marconi <[email protected]>
Date:   Wed Mar 27 10:44:43 2024 +0100

    Support %-encoded characters in DID method id (#1303)

commit e68538f
Author: Enrico Marconi <[email protected]>
Date:   Tue Mar 26 11:58:35 2024 +0100

    gRPC bindings (#1264)

commit e53561e
Author: Enrico Marconi <[email protected]>
Date:   Tue Mar 26 11:18:14 2024 +0100

    allow large result err variants (#1342)

commit 4a144a3
Author: Eike Haß <[email protected]>
Date:   Tue Mar 19 09:51:52 2024 +0100

    fix readme links (#1336)

commit 0af29fc
Author: Enrico Marconi <[email protected]>
Date:   Mon Mar 18 17:16:57 2024 +0100

    Feat/custom verification method (#1334)

    * Add support for arbitrary (custom) verification method data

    * wasm bindings

    * custom method type + wasm

    * workaround serde's issue

    * Update bindings/wasm/src/verification/wasm_method_data.rs

    Co-authored-by: Abdulrahim Al Methiab <[email protected]>

    * review comments

    * fmt

    * review comment

    ---------

    Co-authored-by: Abdulrahim Al Methiab <[email protected]>

commit edb9150
Author: Enrico Marconi <[email protected]>
Date:   Tue Mar 12 14:45:04 2024 +0100

    use latest release of sd-jwt-payload (#1333)

    * use latest release of sd-jwt-payload

    * make clippy happy

commit 0794379
Author: Abdulrahim Al Methiab <[email protected]>
Date:   Wed Mar 6 14:16:00 2024 +0100

    Wasm bindings for `BlockChainAccountId` verification method. (#1326)

commit 59d38f7
Author: Abdulrahim Al Methiab <[email protected]>
Date:   Wed Mar 6 10:56:23 2024 +0100

    Add constructor for VerificationMethod in TS (#1321)

* clippy

* fmt

* add stronghold bbs+ tests

* review comments

* add license header

* fix wasm bindings

* Persist Stronghold's changes only when its handle is dropped

* Fix StrongholdStorage::get_public_key

* rename stronghold_jwk_storage_ext

* Add inx-faucet profile in CI

* change stronghold crate's structure, revert persist changes on drop

* review comments

* Update identity_credential/src/presentation/jwp_presentation_builder.rs

Co-authored-by: wulfraem <[email protected]>

* fix wasm bindings

* expose stronghold's key types

* revert last commit

* Add "Fondazione Links" to license header

* Squashed commit of the following:

commit 9abdb38
Author: Sven <[email protected]>
Date:   Tue May 14 09:16:09 2024 +0200

    Add EcDSA verifier (#1353)

    * add ecdsa verifier

    * add identity_ecdsa_verifier to workspace, add license headers

    * Update identity_ecdsa_verifier/Cargo.toml

    Co-authored-by: wulfraem <[email protected]>

    * Update identity_ecdsa_verifier/src/secp256k1.rs

    Co-authored-by: wulfraem <[email protected]>

    * Update identity_ecdsa_verifier/Cargo.toml

    Co-authored-by: wulfraem <[email protected]>

    * Update identity_ecdsa_verifier/src/secp256k1.rs

    Co-authored-by: wulfraem <[email protected]>

    * Update identity_ecdsa_verifier/src/secp256r1.rs

    Co-authored-by: wulfraem <[email protected]>

    * add feedback

    * add OpenSSL installation to windows runner in CI

    * update license headers and authors for ecdsa verifier

    * update license template to allow multiple contributors

    ---------

    Co-authored-by: Sebastian Wolfram <[email protected]>

commit 149bfac
Author: wulfraem <[email protected]>
Date:   Mon May 13 10:44:09 2024 +0200

    Fix findings after clippy update (#1365)

    * fix clippy findings

    * fix formatting

    * refactor .clone_into calls into .to_string

    * fix previous edit

    * disable empty_docs for wasm binding for now

    * fix missing newline

    * disable self update from rust setup in ci for now

    * update self update skip to skip only for windows build

commit 51aedd5
Author: Enrico Marconi <[email protected]>
Date:   Tue Apr 30 16:16:36 2024 +0200

    Use STRONGHOLD_PWD_FILE env variable to pass stronghold's password (#1363)

commit edec26c
Author: Enrico Marconi <[email protected]>
Date:   Tue Apr 30 15:40:55 2024 +0200

    Arbitrary data signing service (#1350)

commit f59e75a
Author: Eike Haß <[email protected]>
Date:   Tue Apr 30 15:34:40 2024 +0200

    Fix dockerhub workflow (#1343)

commit 993cfec
Author: Enrico Marconi <[email protected]>
Date:   Fri Apr 26 13:39:29 2024 +0200

    add inx-faucet profile (#1356)

* update stronghold and sdk

---------

Co-authored-by: Alberto Solavagione <[email protected]>
Co-authored-by: wulfraem <[email protected]>
  • Loading branch information
3 people committed May 24, 2024
1 parent b634ead commit f25c593
Show file tree
Hide file tree
Showing 115 changed files with 8,084 additions and 413 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
thiserror = { version = "1.0", default-features = false }
strum = { version = "0.25", default-features = false, features = ["std", "derive"] }
serde_json = { version = "1.0", default-features = false }
json-proof-token = { version = "0.3.5" }
zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] }

[workspace.package]
authors = ["IOTA Stiftung"]
Expand Down
2 changes: 1 addition & 1 deletion bindings/grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ futures = { version = "0.3" }
identity_eddsa_verifier = { path = "../../identity_eddsa_verifier" }
identity_iota = { path = "../../identity_iota", features = ["resolver", "sd-jwt", "domain-linkage", "domain-linkage-fetch", "status-list-2021"] }
identity_stronghold = { path = "../../identity_stronghold", features = ["send-sync-storage"] }
iota-sdk = { version = "1.1.2", features = ["stronghold"] }
iota-sdk = { version = "1.1.5", features = ["stronghold"] }
openssl = { version = "0.10", features = ["vendored"] }
prost = "0.12"
rand = "0.8.5"
Expand Down
4 changes: 3 additions & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ console_error_panic_hook = { version = "0.1" }
futures = { version = "0.3" }
identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
js-sys = { version = "0.3.61" }
json-proof-token = "0.3.4"
proc_typescript = { version = "0.1.0", path = "./proc_typescript" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
Expand All @@ -29,11 +30,12 @@ serde_repr = { version = "0.1", default-features = false }
tokio = { version = "1.29", default-features = false, features = ["sync"] }
wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "0.4", default-features = false }
zkryptium = "0.2.2"

[dependencies.identity_iota]
path = "../../identity_iota"
default-features = false
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021"]
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus"]

[dev-dependencies]
rand = "0.8.5"
Expand Down
1,664 changes: 1,501 additions & 163 deletions bindings/wasm/docs/api-reference.md

Large diffs are not rendered by default.

226 changes: 226 additions & 0 deletions bindings/wasm/examples/src/1_advanced/8_zkp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import {
Credential,
FailFast,
IotaDID,
IotaDocument,
IotaIdentityClient,
JptCredentialValidationOptions,
JptCredentialValidator,
JptCredentialValidatorUtils,
JptPresentationValidationOptions,
JptPresentationValidator,
JptPresentationValidatorUtils,
JwkMemStore,
JwpCredentialOptions,
JwpPresentationOptions,
KeyIdMemStore,
MethodScope,
ProofAlgorithm,
SelectiveDisclosurePresentation,
Storage,
} from "@iota/identity-wasm/node";
import {
type Address,
AliasOutput,
Client,
MnemonicSecretManager,
SecretManager,
SecretManagerType,
Utils,
} from "@iota/sdk-wasm/node";
import { API_ENDPOINT, ensureAddressHasFunds } from "../util";

/** Creates a DID Document and publishes it in a new Alias Output.
Its functionality is equivalent to the "create DID" example
and exists for convenient calling from the other examples. */
export async function createDid(client: Client, secretManager: SecretManagerType, storage: Storage): Promise<{
address: Address;
document: IotaDocument;
fragment: string;
}> {
const didClient = new IotaIdentityClient(client);
const networkHrp: string = await didClient.getNetworkHrp();

const secretManagerInstance = new SecretManager(secretManager);
const walletAddressBech32 = (await secretManagerInstance.generateEd25519Addresses({
accountIndex: 0,
range: {
start: 0,
end: 1,
},
bech32Hrp: networkHrp,
}))[0];

console.log("Wallet address Bech32:", walletAddressBech32);

await ensureAddressHasFunds(client, walletAddressBech32);

const address: Address = Utils.parseBech32Address(walletAddressBech32);

// Create a new DID document with a placeholder DID.
// The DID will be derived from the Alias Id of the Alias Output after publishing.
const document = new IotaDocument(networkHrp);

const fragment = await document.generateMethodJwp(
storage,
ProofAlgorithm.BLS12381_SHA256,
undefined,
MethodScope.VerificationMethod(),
);
// Construct an Alias Output containing the DID document, with the wallet address
// set as both the state controller and governor.
const aliasOutput: AliasOutput = await didClient.newDidOutput(address, document);

// Publish the Alias Output and get the published DID document.
const published = await didClient.publishDidOutput(secretManager, aliasOutput);

return { address, document: published, fragment };
}
export async function zkp() {
// ===========================================================================
// Step 1: Create identity for the issuer.
// ===========================================================================

// Create a new client to interact with the IOTA ledger.
const client = new Client({
primaryNode: API_ENDPOINT,
localPow: true,
});

// Creates a new wallet and identity (see "0_create_did" example).
const issuerSecretManager: MnemonicSecretManager = {
mnemonic: Utils.generateMnemonic(),
};
const issuerStorage: Storage = new Storage(
new JwkMemStore(),
new KeyIdMemStore(),
);
let { document: issuerDocument, fragment: issuerFragment } = await createDid(
client,
issuerSecretManager,
issuerStorage,
);

// ===========================================================================
// Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm.
// ===========================================================================

// Create a credential subject indicating the degree earned by Alice.
const subject = {
name: "Alice",
mainCourses: ["Object-oriented Programming", "Mathematics"],
degree: {
type: "BachelorDegree",
name: "Bachelor of Science and Arts",
},
GPA: 4.0,
};

// Build credential using the above subject and issuer.
const credential = new Credential({
id: "https:/example.edu/credentials/3732",
issuer: issuerDocument.id(),
type: "UniversityDegreeCredential",
credentialSubject: subject,
});
const credentialJpt = await issuerDocument
.createCredentialJpt(
credential,
issuerStorage,
issuerFragment,
new JwpCredentialOptions(),
);
// Validate the credential's proof using the issuer's DID Document, the credential's semantic structure,
// that the issuance date is not in the future and that the expiration date is not in the past:
const decodedJpt = JptCredentialValidator.validate(
credentialJpt,
issuerDocument,
new JptCredentialValidationOptions(),
FailFast.FirstError,
);

// ===========================================================================
// Step 3: Issuer sends the Verifiable Credential to the holder.
// ===========================================================================
console.log("Sending credential (as JPT) to the holder: " + credentialJpt.toString());

// ============================================================================================
// Step 4: Holder resolve Issuer's DID, retrieve Issuer's document and validate the Credential
// ============================================================================================
const identityClient = new IotaIdentityClient(client);

// Holder resolves issuer's DID.
let issuerDid = IotaDID.parse(JptCredentialValidatorUtils.extractIssuerFromIssuedJpt(credentialJpt).toString());
let issuerDoc = await identityClient.resolveDid(issuerDid);

// Holder validates the credential and retrieve the JwpIssued, needed to construct the JwpPresented
let decodedCredential = JptCredentialValidator.validate(
credentialJpt,
issuerDoc,
new JptCredentialValidationOptions(),
FailFast.FirstError,
);

// ===========================================================================
// Step 5: Verifier sends the holder a challenge and requests a Presentation.
//
// Please be aware that when we mention "Presentation," we are not alluding to the Verifiable Presentation standard as defined by W3C (https://www.w3.org/TR/vc-data-model/#presentations).
// Instead, our reference is to a JWP Presentation (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form), which differs from the W3C standard.
// ===========================================================================

// A unique random challenge generated by the requester per presentation can mitigate replay attacks.
const challenge = "475a7984-1bb5-4c4c-a56f-822bccd46440";

// =========================================================================================================
// Step 6: Holder engages in the Selective Disclosure of credential's attributes.
// =========================================================================================================
const methodId = decodedCredential
.decodedJwp()
.getIssuerProtectedHeader()
.kid!;
const selectiveDisclosurePresentation = new SelectiveDisclosurePresentation(decodedCredential.decodedJwp());
selectiveDisclosurePresentation.concealInSubject("mainCourses[1]");
selectiveDisclosurePresentation.concealInSubject("degree.name");

// =======================================================================================================================================
// Step 7: Holder needs Issuer's Public Key to compute the Signature Proof of Knowledge and construct the Presentation
// JPT.
// =======================================================================================================================================

// Construct a JPT(JWP in the Presentation form) representing the Selectively Disclosed Verifiable Credential
const presentationOptions = new JwpPresentationOptions();
presentationOptions.nonce = challenge;
const presentationJpt = await issuerDoc
.createPresentationJpt(
selectiveDisclosurePresentation,
methodId,
presentationOptions,
);

// ===========================================================================
// Step 8: Holder sends a Presentation JPT to the Verifier.
// ===========================================================================

console.log("Sending presentation (as JPT) to the verifier: " + presentationJpt.toString());

// ===========================================================================
// Step 9: Verifier receives the Presentation and verifies it.
// ===========================================================================

// Verifier resolve Issuer DID
const issuerDidV = IotaDID.parse(
JptPresentationValidatorUtils.extractIssuerFromPresentedJpt(presentationJpt).toString(),
);
const issuerDocV = await identityClient.resolveDid(issuerDidV);

const presentationValidationOptions = new JptPresentationValidationOptions({ nonce: challenge });
const decodedPresentedCredential = JptPresentationValidator.validate(
presentationJpt,
issuerDocV,
presentationValidationOptions,
FailFast.FirstError,
);

console.log("Presented credential successfully validated: " + decodedPresentedCredential.credential());
}
Loading

0 comments on commit f25c593

Please sign in to comment.