Skip to content

Commit

Permalink
Remove reexported Resolver validation APIs (#1183)
Browse files Browse the repository at this point in the history
* Issuer/credentialSubject.id must be a DID

* Validate origin as a domain

* Use JSON Web Token Proof Format in Domain Linkage

* Rename fixture files

* Update Domain Linkage example

* Replace linked data proof format mentions

* Update Wasm Domain Linkage bindings

* Update Wasm Domain Linkage example

* Expose `memstore` feature via `identity_iota`

* Feature-gate domain linkage config & builder

* Use MemStores in Wasm domain linkage example

* Update api-reference

* Update create VP example with storage APIs

* Rm credential & presentation APIs from Resolver

* Remove unused fixtures

* Avoid unwrap in examples

* Update revoke VC example

* Remove resolver APIs in Wasm

* Fix doc errors

* Update resolver tests

* Apply suggestions from code review
  • Loading branch information
PhilippGackstatter authored Jun 13, 2023
1 parent b8374a7 commit 4f27434
Show file tree
Hide file tree
Showing 31 changed files with 373 additions and 1,261 deletions.
126 changes: 70 additions & 56 deletions bindings/wasm/examples/src/0_basic/6_create_vp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,27 @@
import { Client, MnemonicSecretManager } from "@iota/client-wasm/node";
import { Bip39 } from "@iota/crypto.js";
import {
CoreDocument,
Credential,
CredentialValidationOptions,
Duration,
FailFast,
IotaIdentityClient,
Presentation,
PresentationValidationOptions,
ProofOptions,
JwkMemStore,
JwsSignatureOptions,
JwsVerificationOptions,
JwtCredentialValidationOptions,
JwtCredentialValidator,
JwtPresentation,
JwtPresentationOptions,
JwtPresentationValidationOptions,
JwtPresentationValidator,
KeyIdMemStore,
Resolver,
Storage,
SubjectHolderRelationship,
Timestamp,
VerifierOptions,
} from "@iota/identity-wasm/node";
import { API_ENDPOINT, createDid } from "../util";
import { API_ENDPOINT, createDidStorage } from "../util";

/**
* This example shows how to create a Verifiable Presentation and validate it.
Expand All @@ -35,16 +42,27 @@ export async function createVP() {
});
const didClient = new IotaIdentityClient(client);

// Generate a random mnemonic for our wallet.
const secretManager: MnemonicSecretManager = {
// Creates a new wallet and identity (see "0_create_did" example).
const issuerSecretManager: MnemonicSecretManager = {
mnemonic: Bip39.randomMnemonic(),
};

// Create an identity for the issuer with one verification method `key-1`.
const { document: issuerDocument, keypair: keypairIssuer } = await createDid(client, secretManager);
const issuerStorage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore());
let { document: issuerDocument, fragment: issuerFragment } = await createDidStorage(
client,
issuerSecretManager,
issuerStorage,
);

// Create an identity for the holder, in this case also the subject.
const { document: aliceDocument, keypair: keypairAlice } = await createDid(client, secretManager);
const aliceSecretManager: MnemonicSecretManager = {
mnemonic: Bip39.randomMnemonic(),
};
const aliceStorage: Storage = new Storage(new JwkMemStore(), new KeyIdMemStore());
let { document: aliceDocument, fragment: aliceFragment } = await createDidStorage(
client,
aliceSecretManager,
aliceStorage,
);

// ===========================================================================
// Step 2: Issuer creates and signs a Verifiable Credential.
Expand All @@ -66,24 +84,35 @@ export async function createVP() {
credentialSubject: subject,
});

// Created a signed credential by the issuer.
let signedVc = issuerDocument.signCredential(unsignedVc, keypairIssuer.private(), "#key-1", ProofOptions.default());
const credentialJwt = await issuerDocument.createCredentialJwt(
issuerStorage,
issuerFragment,
unsignedVc,
new JwsSignatureOptions(),
);

const res = new JwtCredentialValidator().validate(
credentialJwt,
issuerDocument,
new JwtCredentialValidationOptions({}),
FailFast.FirstError,
);
console.log("credentialjwt validation", res.intoCredential());

// ===========================================================================
// Step 3: Issuer sends the Verifiable Credential to the holder.
// ===========================================================================

// The credential is then serialized to JSON and transmitted to the holder in a secure manner.
// Note that the credential is NOT published to the IOTA Tangle. It is sent and stored off-chain.
const signedVcJson = signedVc.toJSON();
console.log(`Credential JSON >`, JSON.stringify(signedVcJson, null, 2));
console.log(`Sending credential (as JWT) to the holder`, unsignedVc.toJSON());

// ===========================================================================
// Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation.
// ===========================================================================

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

// The verifier and holder also agree that the signature should have an expiry date
// 10 minutes from now.
Expand All @@ -93,66 +122,50 @@ export async function createVP() {
// Step 5: Holder creates a verifiable presentation from the issued credential for the verifier to validate.
// ===========================================================================

// Deserialize the credential.
const receivedVc = Credential.fromJSON(signedVcJson);

// Create a Verifiable Presentation from the Credential
const unsignedVp = new Presentation({
const unsignedVp = new JwtPresentation({
holder: aliceDocument.id(),
verifiableCredential: receivedVc,
verifiableCredential: credentialJwt,
});

// Sign the verifiable presentation using the holder's verification method
// and include the requested challenge and expiry timestamp.
const signedVp = await aliceDocument.signPresentation(
const presentationJwt = await aliceDocument.createPresentationJwt(
aliceStorage,
aliceFragment,
unsignedVp,
keypairAlice.private(),
"#key-1",
new ProofOptions({
challenge: challenge,
expires,
}),
new JwsSignatureOptions({ nonce }),
new JwtPresentationOptions({ expirationDate: expires }),
);

// ===========================================================================
// Step 6: Holder sends a verifiable presentation to the verifier.
// ===========================================================================

// Convert the Verifiable Presentation to JSON to send it to the verifier.
const signedVpJSON = signedVp.toJSON();
console.log(`Sending presentation (as JWT) to the verifier`, unsignedVp.toJSON());

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

// Deserialize the presentation from the holder.
const presentation = Presentation.fromJSON(signedVpJSON);

// The verifier wants the following requirements to be satisfied:
// - Signature verification (including checking the requested challenge to mitigate replay attacks)
// - Presentation validation must fail if credentials expiring within the next 10 hours are encountered
// - 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.

// Declare that the challenge must match our expectation:
const presentationVerifierOptions = new VerifierOptions({
challenge: "475a7984-1bb5-4c4c-a56f-822bccd46440",
allowExpired: false,
});

// Declare that any credential contained in the presentation are not allowed to expire within the next 10 hours:
const earliestExpiryDate = Timestamp.nowUTC().checkedAdd(Duration.hours(10));
const credentialValidationOptions = new CredentialValidationOptions({
earliestExpiryDate: earliestExpiryDate,
const sharedValidationOptions = new JwtCredentialValidationOptions({
earliestExpiryDate,
});

// Declare that the presentation holder's DID must match the subject ID on all credentials in the presentation.
const subjectHolderRelationship = SubjectHolderRelationship.AlwaysSubject;

const presentationValidationOptions = new PresentationValidationOptions({
sharedValidationOptions: credentialValidationOptions,
presentationVerifierOptions: presentationVerifierOptions,
subjectHolderRelationship: subjectHolderRelationship,
const jwtPresentationValidationOptions = new JwtPresentationValidationOptions({
presentationVerifierOptions: new JwsVerificationOptions({ nonce }),
sharedValidationOptions,
subjectHolderRelationship,
});

// In order to validate presentations and credentials one needs to resolve the DID Documents of
Expand All @@ -161,16 +174,17 @@ export async function createVP() {
client: didClient,
});

// Validate the presentation and all the credentials included in it according to the validation options
// Note that the `verifyPresentation` method we called automatically resolves all DID Documents that are necessary to validate the presentation.
// It is also possible to supply extra arguments to avoid some resolutions if one already has up-to-date resolved documents of
// either the holder or issuers (see the method's documentation).
await resolver.verifyPresentation(
presentation,
presentationValidationOptions,
const presentationDids = JwtPresentationValidator.extractDids(presentationJwt);
const resolvedHolder = await resolver.resolve(presentationDids.holder.toString());
const resolvedIssuer = await resolver.resolve(presentationDids.issuers[0].toString());

// Validate the presentation and all the credentials included in it according to the validation options.
new JwtPresentationValidator().validate(
presentationJwt,
resolvedHolder,
[resolvedIssuer],
jwtPresentationValidationOptions,
FailFast.FirstError,
undefined,
undefined,
);

// Since no errors were thrown by `verifyPresentation` we know that the validation was successful.
Expand Down
2 changes: 2 additions & 0 deletions bindings/wasm/src/common/imported_document_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ impl ImportedDocumentLock {
}
}

// Currently unused, but might be needed in the future.
#[allow(dead_code)]
pub(crate) async fn read(&self) -> ImportedDocumentReadGuard<'_> {
match self {
Self::Iota(lock) => ImportedDocumentReadGuard(tokio::sync::RwLockReadGuard::map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ struct IJwtPresentationHelper {
#[typescript(name = "type", type = "string | Array<string>")]
r#type: Option<OneOrMany<String>>,
/// JWT Credential(s) expressing the claims of the presentation.
#[typescript(optional = false, name = "verifiableCredential", type = "string | Array<string>")]
#[typescript(
optional = false,
name = "verifiableCredential",
type = "Jwt | Array<Jwt> | string | Array<string>"
)]
verifiable_credential: OneOrMany<String>,
/// The entity that generated the presentation.
#[typescript(optional = false, type = "string | CoreDID | IotaDID ")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::error::Result;
use crate::error::WasmResult;
use crate::verification::IJwsVerifier;
use crate::verification::WasmJwsVerifier;
use identity_iota::core::Object;
use identity_iota::core::OneOrMany;
use identity_iota::credential::JwtPresentationValidator;
use identity_iota::did::CoreDID;
Expand Down Expand Up @@ -108,7 +107,7 @@ impl WasmJwtPresentationValidator {
#[wasm_bindgen(js_name = extractDids)]
pub fn extract_dids(presentation: &WasmJwt) -> Result<JwtPresentationDids> {
let (holder, issuers) =
JwtPresentationValidator::extract_dids::<CoreDID, CoreDID, Object, Object>(&presentation.0).wasm_result()?;
JwtPresentationValidator::extract_dids::<CoreDID, CoreDID>(&presentation.0).wasm_result()?;
let mut map = BTreeMap::<&str, OneOrMany<CoreDID>>::new();
map.insert("holder", OneOrMany::One(holder));
map.insert("issuers", OneOrMany::Many(issuers));
Expand Down
Loading

0 comments on commit 4f27434

Please sign in to comment.