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

Add ZK BBS+-based selectively disclosable credentials (JPT) #1355

Merged
merged 35 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6003cf0
Support BBS+ and JWP (#1285)
AlbertoSvg Mar 4, 2024
bdf6b54
merge main
UMR1352 Mar 19, 2024
37d1bd5
Wasm bindings for Jpt credentials
UMR1352 Mar 20, 2024
a02f6fe
JPT presentation bindings
UMR1352 Mar 20, 2024
7b45d70
docs
UMR1352 Mar 20, 2024
592bd9c
jsonprooftoken payloads
UMR1352 Mar 21, 2024
c102864
Refactor `RevocationTimeframeStatus` to align with other setups (#1340)
wulfraem Mar 21, 2024
fd2070c
binding coverage for jsonprooftoken
UMR1352 Mar 22, 2024
711d4ae
Use latest releases of zkryptium/json-proof-token and add new BLS key…
AlbertoSvg Mar 25, 2024
be35e7b
Use zkryptium for cryptographic operations inside Memstore (#1351)
AlbertoSvg Apr 17, 2024
e835a71
Feat/jpt bbs+ sd stronghold impl (#1354)
UMR1352 Apr 24, 2024
ae8e022
rename JwkStorageExt to JwkStorageBbsPlusExt
UMR1352 Apr 24, 2024
4a152d0
JwkStorageBbsPlusExt impl refactor for Stronghold, MemStore, WasmStore
UMR1352 Apr 25, 2024
be022b0
Squashed commit of the following:
UMR1352 Apr 25, 2024
3a6f951
Merge branch 'main' into feat/jpt-bbs+-sd
UMR1352 Apr 25, 2024
94565c9
clippy
UMR1352 Apr 25, 2024
4738605
fmt
UMR1352 Apr 25, 2024
6a32429
add stronghold bbs+ tests
UMR1352 Apr 25, 2024
23a2f46
review comments
UMR1352 Apr 25, 2024
e014728
add license header
UMR1352 Apr 25, 2024
614719b
fix wasm bindings
UMR1352 Apr 26, 2024
096bb30
Persist Stronghold's changes only when its handle is dropped
UMR1352 Apr 26, 2024
b347a1e
Fix StrongholdStorage::get_public_key
UMR1352 Apr 29, 2024
453103c
rename stronghold_jwk_storage_ext
UMR1352 Apr 29, 2024
9632e8d
Add inx-faucet profile in CI
UMR1352 Apr 29, 2024
479f4bc
change stronghold crate's structure, revert persist changes on drop
UMR1352 Apr 30, 2024
87a744c
review comments
UMR1352 Apr 30, 2024
ef85f1e
Update identity_credential/src/presentation/jwp_presentation_builder.rs
UMR1352 Apr 30, 2024
041fbab
fix wasm bindings
UMR1352 Apr 30, 2024
5135f0b
expose stronghold's key types
UMR1352 Apr 30, 2024
63df8d4
revert last commit
UMR1352 May 8, 2024
b2f6277
Add "Fondazione Links" to license header
UMR1352 May 13, 2024
4cf455d
Squashed commit of the following:
UMR1352 May 21, 2024
5cc7f3b
update stronghold and sdk
UMR1352 May 22, 2024
dc0dc96
fix conflicts with main
UMR1352 May 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/iota-sandbox/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ runs:

# Start Tangle
sudo ./bootstrap.sh
docker compose up -d
docker compose --profile inx-faucet up -d
- name: Wait for tangle to start
shell: bash
run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/$WAIT_FOR_VERSION/wait-for | sh -s -- -t 60 http://localhost/health -- echo "Tangle is up"
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
thiserror = { version = "1.0", default-features = false }
strum = { version = "0.25", default-features = false, features = ["std", "derive"] }
serde_json = { version = "1.0", default-features = false }
json-proof-token = { version = "0.3.5" }
zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] }

[workspace.package]
authors = ["IOTA Stiftung"]
Expand All @@ -32,5 +34,8 @@ license = "Apache-2.0"
repository = "https://github.com/iotaledger/identity.rs"
rust-version = "1.65"

[patch.crates-io]
iota_stronghold = { git = "https://github.com/tensor-programming/stronghold.rs.git", branch = "feat/expose_runner" }

[workspace.lints.clippy]
result_large_err = "allow"
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.1"

[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());
}