Ana içeriğe geç

Verification (Validation) 🎯

In this section, we will collectively undertake the roles of the Verifier individual.

alternative text

These processes include:

  • 📥 We will receive the Presentation object provided by the Claimer.
  • 🧐 We will verify the accuracy of the information within this data.
  • 🔗 You will confirm whether the attestation process is correct by checking the hash value on the chain. This attestation might also have been revoked. This situation needs to be considered as well.
  • 🤔 You will ensure that the Claimer sent the identity document.
🎉 Presentation: Now, am I supposed to prepare a PowerPoint presentation too?

A Presentation is a document produced using a credential. Unlike a credential, a presentation can hide some of its documents. These are pieces of information that the verifiers don't need to know. This presentation also proves that the credential belongs to the Claimer.

🤓 What was Verification again? Shall we recall?

🔍 When a Claimer receives an attested Credential, they can store it in their wallets. But how can we determine its validity if there's no copy of this credential?

🔒 KILT provides a Verifier with the opportunity to verify the accuracy and validity of the information presented in a credential by a Claimer.

💡 The Verifier trusts this third party directly based on their reputation or through a delegation structure involving the Attester.

✅ For the verification process:

🔑 The Claimer needs a private key associated with the credential and their identifiers.

🔒 The Verifier needs the identifier of the Attester they trust.

🔎 During the verification process, the Claimer aims to prove three things to the Verifier:

1️⃣ The credential is valid and has not been revoked by the Attester. 2️⃣ The attributes specified in the credential genuinely belong to the Claimer. 3️⃣ The credential contains the information relevant to the Verifier for a specific use case.

🎨 Creating the Presentation

We need to find a way to send the Claimer and wait for the Verifier to verify it. The Claimer also needs to prove that they own this credential. To prove this ownership, they need to create and sign a presentation.

📦 Package Inclusions:

claimer/createPresentation.ts
import * as Kilt from '@kiltprotocol/sdk-js'

This line incorporates the KILT SDK into the project. This SDK provides the necessary functions and classes to interact with the KILT protocol.

🛠 createPresentation Function:

claimer/createPresentation.ts
export async function createPresentation(
credential: Kilt.ICredential,
signCallback: Kilt.SignCallback,
challenge?: string
): Promise<Kilt.ICredentialPresentation> {

This function is an asynchronous function to create a credential presentation. It takes three parameters:

  • credential: The credential that will be presented to the Verifier.
  • signCallback: The callback function to sign the credential presentation.
  • challenge: An optional parameter. This is a random string requested by the verifier and is used to confirm that the verifier asked the credential presentation.

The function returns an ICredentialPresentation object, which is the credential presentation itself.

🎨 Creating the Presentation:

claimer/createPresentation.ts
return Kilt.Credential.createPresentation({
credential,
signCallback,
challenge
})

This section creates a credential presentation using the Kilt.Credential.createPresentation function. This function takes the three parameters mentioned above and returns a credential presentation.

🌍 Let's take a broader look

If we want to understand the function by looking at the entire code:

claimer/createPresentation.ts
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
})
}

In summary, this code enables the creation of a credential presentation. This presentation is used when presenting a credential to a verifier. Using this presentation, the verifier can verify the credential's validity and the identity of the credential's owner.

Verify (Validation)

Now, let's write our verification code. First, we'll create a function named getChallenge to prove that the Claimer has the credential by signing it.

Then, we'll perform the validation or verification process with the verifyPresentation function. We will use the verify.ts file for these operations.

Package and Module Imports

verify.ts
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'

In this section, the necessary modules and packages are imported. In particular, environment variables are loaded using dotenv, and the KILT SDK and other helper functions are included.

Generating a Unique Challenge

verify.ts
function getChallenge(): string {
return Kilt.Utils.UUID.generate()
}

This function generates a unique challenge for the verification process.

alternative text

Presentation Verification Function

verify.ts
async function verifyPresentation(
presentation: Kilt.ICredentialPresentation,
challenge: string,
trustedAttesterUris: Kilt.DidUri[]
): Promise<boolean> {

We can start the process by creating a function for verification. This function takes in 3 parameters and returns a Promise with a value of True or False. Looking at these parameters one by one:

  1. presentation: The credential presentation to be verified.
  2. challenge: The unique value sent by the Verifier (Verifier). This is used to confirm that the credential presentation was requested by the verifier.
  3. trustedAttesterUris: The list of DID URIs of Attesters (Attester) trusted by the verifier.

This function verifies a credential presentation. It checks if the presentation is valid, its ownership, and if the attestation is correct.

Access to the KILT API:

verify.ts
Kilt.ConfigService.get('api')

This line provides access to the KILT API.

Try Catch Structure

verify.ts
try {
.
.
.
} catch {
return false
}

Upon entering the structure, we first encounter a try catch structure. This structure checks if the attestation is correct. If it's not correct, the structure is caught by the catch block, and it's understood that the attestation is not on the chain with a value of false.

Verifying the Presentation:

verify.ts
const { revoked, attester } = await Kilt.Credential.verifyPresentation(
presentation,
{ challenge }
)

Inside the Try structure, we are greeted by the Kilt.Credential.verifyPresentation function, which performs the actual verification. This function takes in 2 variables. These are the presented presentation and the challenge values to prove that the Claimer is the correct individual.

This function returns 2 values. These values indicate whether the attestation has been revoked, i.e., lost its validity, and who the attester of the presentation is.

verify.ts
if (revoked) {
return false
}

If the revoke value is true, i.e., if the credential has lost its validity, the verification value is given as output as false.

verify.ts
return trustedAttesterUris.includes(attester)

This line checks if the presenter (attester) is on the list of trusted attestors (trustedAttesterUris). If the attester is on this list, the function returns true; otherwise, it returns false.

tehlike

One of the most important attributes a Verifier needs to check is whether or not they trust an attester. If they don't trust, they may not accept the document, whether the attestation is on the chain or not. This situation creates a trust race among the attesters.

Verification Flow

verify.ts
export async function verificationFlow(
credential: Kilt.ICredential,
signCallback: Kilt.SignCallback,
trustedAttesterUris: Kilt.DidUri[] = []
) {

This function manages the verification process. Firstly, it generates a challenge, then creates a presentation, and finally verifies the presentation. This function takes in 3 parameters. Looking at these parameters one by one:

  1. credential: Kilt.ICredential: The credential to be presented to the Verifier.
  2. signCallback: Kilt.SignCallback: The callback function to sign the credential presentation.
  3. trustedAttesterUris: Kilt.DidUri[]: The list of DID URIs of Attesters (Attester) trusted by the verifier. It's an optional parameter and starts with an empty list by default.
What's the Difference Between verificationFlow and verifyPresentation?

By creating this function, we manage the entire verification process in one place.

The verificationFlow function represents a broader verification process that includes the verifyPresentation function. The verifyPresentation function alone only checks the validity of a credential presentation. However, in the real world, a verification process requires more than just checking the validity of a presentation. That's why we need the verificationFlow function.

The objectives of the verificationFlow function are:

  1. Generating a Unique Challenge: The Verifier sends a unique challenge to the Claimer. This is used to ensure that the presentation is created in real-time and for a specific request.

  2. Creating the Presentation: Using this challenge, the Claimer creates and signs the credential presentation.

  3. Verifying the Presentation: The Verifier verifies the presentation received from the Claimer with the verifyPresentation function.

  4. Reporting the Results: If the presentation is correct, the verifier prints a successful verification message. Otherwise, it prints a failed verification message.

Each of these steps is a critical component of a decentralized identity verification process. The verifyPresentation function is just a part of this process. The verificationFlow function brings these steps together to provide a complete verification process. That's why we need not only the verifyPresentation function but also the verificationFlow function that combines these steps.

Sure, let's continue with the English translation:

Generating a Unique Challenge

verify.ts
const challenge = getChallenge()

This line generates a unique challenge to be sent to the Claimer. This ensures that the presentation is created in real-time and for a specific request. We had previously defined this function.

Creating the Credential Presentation

verify.ts
const presentation = await createPresentation(
credential,
signCallback,
challenge
)

This line creates a credential presentation using the provided credential, signature callback function, and challenge. We had previously defined this function in the claimer folder.

Verifying the Presentation:

verify.ts
const isValid = await verifyPresentation(
presentation,
challenge,
trustedAttesterUris
)

This line verifies the created credential presentation. If the presentation is valid, the isValid value will be true.

Reporting the Results:

verify.ts
if (isValid) {
console.log('Verification successful! You are allowed to enter the club 🎉')
} else {
console.log('Verification failed! 🚫')
}

This section prints a message based on the validity of the presentation. If the presentation is valid, a successful verification message is printed. Otherwise, a failed verification message is printed.

What Does the Function Do?

When we look inside the verificationFlow() function, we essentially execute the functions we previously wrote. Sequentially:

  • We generate a Challenge to authenticate the Claimer.
  • The Claimer creates a presentation.
  • As a Verifier, we verify the presented authentication.

Main Function

This section contains the main function that will execute when the file is run directly. It loads environment variables, connects to KILT, and initiates the verification flow.

verify.ts
if (require.main === module) {
;(async () => {

This if structure checks if the module is being run directly.

Loading Environment Variables:

verify.ts
envConfig()

This line loads the environment variables from the .env file. This is used to store various configuration values used in the project.

Connecting to KILT:

verify.ts
await Kilt.connect(process.env.WSS_ADDRESS as string)

This line establishes a connection to the KILT network. The connection address is retrieved from the .env file.

Generating Claimer's DID Information:

verify.ts
const claimerDidMnemonic = process.env.CLAIMER_DID_MNEMONIC as string
const { authentication } = generateKeypairs(claimerDidMnemonic)
const claimerDid = generateLightDid(claimerDidMnemonic)

These steps are used to generate the Claimer's DID (Decentralized Identifier) information. This is required to sign the credential presentation.

uyarı

As you might have noticed, when querying the Claimer's DID, we executed a new Light DID generation function. The reason for this is that light DIDs, which are not stored on-chain, are open to being regenerated each time.

The verificationFlow() function also runs the presentation creation function. Therefore, we need the Claimer's DID.

Loading Attester's DID Information:

verify.ts
const attesterDid = process.env.ATTESTER_DID_URI as Kilt.DidUri

This line retrieves the Attester's DID URI from the .env file.

Loading the Credential:

verify.ts
const credential = JSON.parse(process.env.CLAIMER_CREDENTIAL as string)

This line loads the Credential that we previously obtained by running our code in the attestation section and saved in the .env file.

Initiating the Verification Flow:

verify.ts
await verificationFlow(
credential,
async ({ data }) => ({
signature: authentication.sign(data),
keyType: authentication.type,
keyUri: `${claimerDid.uri}${claimerDid.authentication[0].id}`
}),
[attesterDid]
)

This line calls the previously defined verificationFlow function to start the verification process.

Error Handling:

verify.ts
} catch (e) {
console.log('Error in the verification flow')
throw e
}

This section catches any errors that might occur during the verification process and prints an error message.


Overview of the Code

When we look at the entirety of the code:

verify.ts
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(
presentation: Kilt.ICredentialPresentation,
challenge: string,
trustedAttesterUris: Kilt.DidUri[]
): Promise<boolean> {
Kilt.ConfigService.get('api')

try {
const { revoked, attester } = await Kilt.Credential.verifyPresentation(
presentation,
{ challenge }
)

if (revoked) {
return false
}
return trustedAttesterUris.includes(attester)
} catch {
return false
}
}

export async function verificationFlow(
credential: Kilt.ICredential,
signCallback: Kilt.SignCallback,
trustedAttesterUris: Kilt.DidUri[] = []
) {
const challenge = getChallenge()
const presentation = await createPresentation(
credential,
signCallback,
challenge
)
const isValid = await verifyPresentation(
presentation,
challenge,
trustedAttesterUris
)

if (isValid) {
console.log('Verification successful! You are allowed to enter the club 🎉')
} else {
console.log('Verification failed! 🚫')
}
}

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

The functions, in order:

  • Packages are imported.
  • A challenge is generated for the Claimer to prove they possess the credential.
  • A Presentation verification function is written, which outputs either True or False.
  • The verificationFlow() function is written to execute all processes from start to finish.
  • A function is written to specify what the function will do when run on its own.

Let's Run the Code!

To run the code, make sure you are in the kilt-rocks directory in the terminal. Then, you can execute the following command:

yarn ts-node verify.ts
YOU DID IT

You've successfully executed all the entities with all the codes! I'm proud of you! Let's create projects together with KILT-SDK!