๐ค Verification
In this section, you'll play the role of a Verifier:
- You'll take an
Presentation
object given to you by a Claimer; - You'll verify that its data is correct;
- You'll verify that the attestation is valid, i.e., its hash exists on-chain and the attestation has not been revoked.
- You'll verify that the
Credential
is owned by the Claimer sending it
A Credential is used to create the Presentation
object by the claimer.
Unlike the credential, a presentation
can hide some attributes that are not required by the verifiers and can contain a claimer-signed challenge.
A presentation
also contains a proof that the claimer owns the credential.
Create Presentationโ
It's not enough to just send our credential as a Claimer as we also need to prove ownership of it. This is done by creating a presentation and signing the Verifier's challenge.
- Typescript
- Javascript
import * as Kilt from '@kiltprotocol/sdk-js'
export async function createPresentation(
credential: Kilt.ICredential,
signCallback: Kilt.SignCallback,
challenge?: string
): Promise<Kilt.ICredentialPresentation> {
// Create the presentation from credential, DID and challenge.
return Kilt.Credential.createPresentation({
credential,
signCallback,
challenge
})
}
import * as Kilt from '@kiltprotocol/sdk-js'
export async function createPresentation(credential, signCallback, challenge) {
// Create the presentation from credential, DID and challenge.
return Kilt.Credential.createPresentation({
credential,
signCallback,
challenge,
})
}
Verifyโ
Let's write our verification script.
Here we'll expose getChallenge
which returns a random and unique
challenge for the Claimer to sign; this is used to prove ownership.
We'll also expose verifyPresentation
which will do the actual verification.
Copy the code below, this completes the Verifier code!
- Typescript
- Javascript
import type { ApiPromise } from '@polkadot/api'
import { config as envConfig } from 'dotenv'
import * as Kilt from '@kiltprotocol/sdk-js'
import { createPresentation } from './claimer/createPresentation'
import { generateKeypairs } from './claimer/generateKeypairs'
import { generateLightDid } from './claimer/generateLightDid'
function getChallenge(): string {
return Kilt.Utils.UUID.generate()
}
// Verifies validity, ownership & attestation.
async function verifyPresentation(
api: ApiPromise,
presentation: Kilt.ICredentialPresentation,
challenge: string,
trustedAttesterUris: Kilt.DidUri[]
): Promise<boolean> {
try {
await Kilt.Credential.verifyPresentation(presentation, { challenge })
const attestationInfo = Kilt.Attestation.fromChain(
await api.query.attestation.attestations(presentation.rootHash),
presentation.rootHash
)
if (attestationInfo.revoked) {
return false
}
// Returns true if no trusted attester URI is provided or, if it is, if it matches the one that issued the presented credential.
return trustedAttesterUris.includes(attestationInfo.owner)
} catch {
return false
}
}
export async function verificationFlow(
credential: Kilt.ICredential,
signCallback: Kilt.SignCallback,
trustedAttesterUris: Kilt.DidUri[] = []
) {
const api = Kilt.ConfigService.get('api')
// Verifier sends a unique challenge to the claimer ๐
const challenge = getChallenge()
// Create a presentation and send it to the verifier ๐
const presentation = await createPresentation(
credential,
signCallback,
challenge
)
// The verifier checks the presentation.
const isValid = await verifyPresentation(
api,
presentation,
challenge,
trustedAttesterUris
)
if (isValid) {
console.log('Verification successful! You are allowed to enter the club ๐')
} else {
console.log('Verification failed! ๐ซ')
}
}
// Don't execute if this is imported by another file.
if (require.main === module) {
;(async () => {
envConfig()
try {
await Kilt.connect(process.env.WSS_ADDRESS as string)
const claimerDidMnemonic = process.env.CLAIMER_DID_MNEMONIC as string
const { authentication } = generateKeypairs(claimerDidMnemonic)
const claimerDid = generateLightDid(claimerDidMnemonic)
const attesterDid = process.env.ATTESTER_DID_URI as Kilt.DidUri
// Load credential and claimer DID
const credential = JSON.parse(process.env.CLAIMER_CREDENTIAL as string)
await verificationFlow(
credential,
async ({ data }) => ({
signature: authentication.sign(data),
keyType: authentication.type,
keyUri: `${claimerDid.uri}${claimerDid.authentication[0].id}`
}),
[attesterDid]
)
} catch (e) {
console.log('Error in the verification flow')
throw e
}
})()
}
import { config as envConfig } from 'dotenv'
import * as Kilt from '@kiltprotocol/sdk-js'
import { createPresentation } from './claimer/createPresentation'
import { generateKeypairs } from './claimer/generateKeypairs'
import { generateLightDid } from './claimer/generateLightDid'
function getChallenge() {
return Kilt.Utils.UUID.generate()
}
// Verifies validity, ownership & attestation.
async function verifyPresentation(
api,
presentation,
challenge,
trustedAttesterUris
) {
try {
await Kilt.Credential.verifyPresentation(presentation, { challenge })
const attestationInfo = Kilt.Attestation.fromChain(
await api.query.attestation.attestations(presentation.rootHash),
presentation.rootHash
)
if (attestationInfo.revoked) {
return false
}
// Returns true if no trusted attester URI is provided or, if it is, if it matches the one that issued the presented credential.
return trustedAttesterUris.includes(attestationInfo.owner)
} catch {
return false
}
}
export async function verificationFlow(
credential,
signCallback,
trustedAttesterUris = []
) {
const api = Kilt.ConfigService.get('api')
// Verifier sends a unique challenge to the claimer ๐
const challenge = getChallenge()
// Create a presentation and send it to the verifier ๐
const presentation = await createPresentation(
credential,
signCallback,
challenge
)
// The verifier checks the presentation.
const isValid = await verifyPresentation(
api,
presentation,
challenge,
trustedAttesterUris
)
if (isValid) {
console.log('Verification successful! You are allowed to enter the club ๐')
} else {
console.log('Verification failed! ๐ซ')
}
}
// Don't execute if this is imported by another file.
if (require.main === module) {
;(async () => {
envConfig()
try {
await Kilt.connect(process.env.WSS_ADDRESS)
const claimerDidMnemonic = process.env.CLAIMER_DID_MNEMONIC
const { authentication } = generateKeypairs(claimerDidMnemonic)
const claimerDid = generateLightDid(claimerDidMnemonic)
const attesterDid = process.env.ATTESTER_DID_URI
// Load credential and claimer DID
const credential = JSON.parse(process.env.CLAIMER_CREDENTIAL)
await verificationFlow(
credential,
async ({ data }) => ({
signature: authentication.sign(data),
keyType: authentication.type,
keyUri: `${claimerDid.uri}${claimerDid.authentication[0].id}`,
}),
[attesterDid]
)
} catch (e) {
console.log('Error in the verification flow')
throw e
}
})()
}
Runโ
Run the verification flow on command line:
- Typescript
- Javascript
yarn ts-node verify.ts
node verify.js
That's it! All done :-)