This document describes how to create v1 verifiable presentations and how to verify them.
Table of Contents:
The SDK contains a helper to create as set of subject claims about identities, which can then be proven.
To do so, use the presentation request claims builder, to build a set of claims:
From ./../examples/nodejs/common/verifiable-credential-claims.ts#3~23
let builder = VerificationRequestV1.claimsBuilder();
// Add a set of identity credential claims. Alternatively, if the proof produced from the
// claims should be tied to an account, use `builder.addAccountClaims`.
// A third option `builder.addAccountOrIdentityClaims`exists where it's up to the
// application holding the credentials to decide which proof is produced.
builder = builder.addIdentityClaims(
[0, 1].map((idpIndex) => new IdentityProviderDID('Testnet', idpIndex)),
(b) => {
b.addMinimumAge(18);
b.addEUResidency();
b.revealAttribute('firstName');
}
);
// Get the complete set of claims to request a proof of.
const claims = builder.getClaims();
console.log('successfully constructed subject claims', claims);
To build a set of claims against an identity credential, the builder has two different entrypoints, which have an identical function signature, which consists of
// used for proofs tied to an account created from the identity credential.
builder.addAccountClaims([0,2].map(idp => new IdentityProviderDID('Testnet', idp)), (build) => ...)
// used for proofs which are not tied to a specific account
builder.addIdentityClaims([0,2].map(idp => new IdentityProviderDID('Testnet', idp)), (build) => ...)
// alternatively let the application producing the proof decide
builder.addAccountOrIdentityClaims([0,2].map(idp => new IdentityProviderDID('Testnet', idp)), (build) => ...)
Below are a set of functions accessible for the build object passed in the callback
There is a helper function for specifying the prover must have some minimum age.
Example: add the statement that the prover must be at least 18 years old:
build.addMinimumAge(18);
There is a helper function for specifying the prover must have some maximum age.
Example: add the statement that the prover must be at most 27 years old:
build.addMaximumAge(27);
There are helpers for specifying the country of residency or nationality to be one of the EU member states.
build.addEUNationality();
build.addEUResidency();
State that a given attribute should be revealed as part of the proof.
build.revealAttribute(AttributesKeys.nationality);
State that a given attribute should be between 2 given values.
Example: add the statement that the prover must be born between January 1, 1941 and Februar 2, 2005.
build.addRange(AttributesKeys.dob, 19410101, 20050202);
Note that this type of statement is only allowed for the following attributes:
Example: add the statement that the prover's country of residency is France or Spain, specified in ISO3166-1 alpha-2 format:
build.addMembership(AttributesKeys.CountryOfResidency, ['FR', 'ES']);
Note that this type of statement is only allowed for the following attributes:
Example: add the statement that the prover's country of residency not Germany nor Portugal:
build.addNonMembership(AttributesKeys.CountryOfResidency, ['DE', 'PT']);
Note that this type of statement is only allowed for the following attributes:
The VerificationRequestV1, VerifiablePresentationV1, and VerifiableAuditRecordV1 can be represented as
JSON by calling the associated .toJSON method (will also be called implicitly with JSON.stringify). Correspondingly,
parsing the JSON values can be done with the .fromJSON function exposed for each type.
bigints are used internally in the types described above and need to be handled with something like
json-bigint
Example: service serializes presentation request in response to frontend; frontend deserializes and parses the JSON.
const json = JSON.stringify(presentationRequest); // service sends back presentation request to frontend
...
const presentationRequest = VerificationRequestV1.fromJSON(JSON.parse(json)); // frontend parses the JSON.
To get a verifiable presentation of one or more verifiable credentials owned by a user, the entity requesting the information must first build a verification request. In the V1 protocol, this is done in the following sequence:
Once this is done, the request must be anchored on chain with a transaction. This can be achieved by calling
const nonce = Uint8Array.from(...) // randomly generated 32-byte value
const connectionID = ... // e.g. a wallet-connect ID
const contextString = 'My compliant web3 wine shop'
const context = VerificationRequestV1.createSimpleContext(nonce, connectionID, contextString)
const statement = new VerificationRequestV1.claimsBuilder()...
// a GRPC client connected a node on the network the anchor should be registered on
const grpcClient: ConcordiumGRPCClient = ...;
// the sender of the anchor transaction
const sender: AccountAddress.Type = ...;
// the keys for the account to sign the anchor transaction
const signer: Signer = ...;
// create the verification request with an on-chain anchor, which can be checked by the owner of the credentials.
const verificationRequest = await VerificationRequestV1.createAndAnchor(
grpcClient,
{ sender, signer }
context,
statement
);
Computing a verifiable presentation from a verifiable presentation request is a process of the following sequence for each credential statement in the request:
VerificationRequestV1.SubjectClaims.VerifiablePresentationV1.SubjectClaims corresponding to the credential. This is is not the same as the
VerificationRequestV1.Statement we built for the VerificationRequestV1 previously; here we're working with
a specific credential, e.g. from the users wallet.
a. for VerificationRequestV1.IdentityStatements, the source also needs to be taken into account. This
specifies the type of credential requested by the dapp. This can either be set to
["Identity"] | ["Account"] | ["Identity", "Account"], where the latter means that the application constructing
the proof can decide which proof to construct for the statement.When this is done for all credential statements in the request, we construct the proof context corresponding to the
request context of the request, specifying values for each requested context value in
VerificationRequestV1.Context.requested.
// specify the resource ID (e.g. website URL or fingerprint of TLS certificate that the wallet is connected to)
// from the connection to the requester of the proof. The block hash is _not_ specified here, as it is looked up
// later in `VerifiablePresentationV1.createFromAnchor` from the anchor transaction reference in the presentation
// request.
const contextValues: GivenContext[] = [{label: 'ResourceID', context: ...}];
// The application goes through each set of claims in the verification request, and constructs a corresponding set of
// claims used as input to the presentation. The difference between the two statement types boil down to the presence
// of an ID qualifier vs. an ID (selected by the application based on the id qualifier).
const statements: VerifiablePresentationV1.SubjectClaims[] = verificationRequest.subjectClaims.map((entry) => {
// prioritize creating identity based proofs, as these are more privacy-preserving
if (entry.source.includes('identity'))
return VerifiablePresentationV1.createIdentityClaims(..., entry.statement);
return VerifiablePresentation.createAccountClaims(..., entry.statement);
});
// the inputs for the credential owned by the user, i.e. credential attribute values. For each
// `VerifiablePresentationV1.SubjectClaims`, there should be a corresponding input
const inputs: CommitmentInput[] = [
createIdentityCommitmentInputWithHdWallet(...),
createAccountCommitmentInputWithHdWallet(...),
];
const presentation = await VerifiablePresentationV1.createFromAnchor(
grpcClient,
verificationRequest,
statements,
inputs,
contextValues
);
Services can opt in to create a verification audit record from the verification request and corresponding verifiable presentation. This exists as a record and a corresponding anchor. The record should be stored by the dapp backend (e.g. in a database), and the anchor should be registered on chain. The transacton hash of the anchor registration should be stored along the record.
const uuid: string = ...;
// Verify the presentation in the context of the verification request and create the audit record and register the audit
// anchor.
const record = VerificationAuditRecordV1.createAndAnchor(
uuid,
verificationRequest,
presentation,
grpcClient,
{ metadata: { sender, signer } },
{ network }
);
Generated using TypeDoc