Skip to main content

Verify the authenticity

Did you notice anything wrong with our verification in the previous step?

Nothing? Let's see:

  • You've checked that the Credential data is valid and you've verified that the corresponding attestation is on chain and not revoked. All good.

  • But: are you sure that the entity/person that sent you the Credential owns it? What if a malicious actor stole this Credential and is now presenting it to you as theirs? We'll see how to mitigate this.

Understand credential theft mitigation

To mitigate credential theft, a verifier can initiate a cryptographic challenge with a claimer.

The underlying idea is simple: to prove their DID, the claimer signs on-the-fly - that's important - a piece of data under the same DID as the DID associated with their Credential. By checking this signature's validity, the verifier makes sure that the Credential is owned by the person who just sent it.

What piece of data should be signed? It doesn't really matter; it can be an arbitrary number picked by the verifier. What matters is that this number should be used only once. Otherwise, the cryptographic challenge is worthless.

Nonce

In a cryptographic communication, an arbitrary number that can be used just once is called a nonce.

Here's how it works:

  1. The verifier sends a nonce to the claimer as a challenge.
  2. The claimer sends back a presentation of the credential with the nonce signed with their encryption key.
  3. The verifier checks the following:
    • Does the signature on the nonce match the public key contained in the Credential? If so: the entity/person who just sent the Credential plus the signed nonce is also the owner of this Credential. If not: the Credential might be stolen.
    • Is the data valid? Is the attestation on-chain and not revoked? See the simple Verification for more information about the validation logic.

OK, let's see this in action.

As the verifier: create a nonce

To generate a random, unique piece of data, we'll use the ProofID SDK to generate secure nonces starting from a randomly generated UUID. The most important properties of nonces are randomness and uniqueness.

Create a new file nonce.js, and paste the following code into it:

const ProofID = require('@proofid/pid-ts-lib')

const nonce = ProofID.Utils.UUID.generate()
console.log('Nonce: ', nonce)

Run the code by running this command in your terminal, still within your pid-app directory:

node nonce.js

You should see in your logs the resulting HEX string that will be used as a nonce; it should look something like this: 0x942ab89b01671faeec84a76f4a8eae9b57ec12bf06157f8a87315cd29a5e0d25.

Copy it, you'll need it in the next step.

As the claimer: create the presentation, which will sign with the challenge and prepare the data

Let's put together the data you would send back to the verifier, as the claimer.

Create a new file create-presentation.js.

Copy the following code into the create-presentation.js


const ProofID = require('@proofid/pid-ts-lib')

async function createPresentation(
claimerLightDid,
credential,
nonce,
keystore
) {
// sign the nonce as the claimer with the claimer's DID
const presentation = await credential.createPresentation({
signer: keystore,
claimerDid: claimerLightDid,
challenge: nonce,
})

// this is the message to send to the verifier
const dataToVerify = {
presentation,
nonce,
}

console.log('ProofID Credential:\n', presentation.request.claim)

console.log(
'dataToVerifyJSONString:\n',
JSON.stringify(dataToVerify, undefined, 2)
)
return presentation
}

module.exports.createPresentation = createPresentation

As the verifier: verify the presentation and credential

Create a new file verification-of-presentation.js.

Copy the following code into the verification-of-presentation.js

const ProofID = require('@proofid/pid-ts-lib')

async function verifyPresentation(presentation, nonce) {
await ProofID.connect()
// verify the presentation from the nonce (<nonce> is the uuid you've generated as the verifier)
const isSenderOwner = await ProofID.Credential.verify(presentation, {
challenge: nonce,
})
console.log('isSenderOwner: ', isSenderOwner)

// proceed with verifying the credential itself
// --> see simple "Verification" step in this tutorial

// disconnect from the chain
await ProofID.disconnect()
return isSenderOwner
}

module.exports.verifyPresentation = verifyPresentation

Run the code by running this command in your terminal, still within your pid-app directory:

node index.js

You should see in your logs the dataToVerifyJSONString, which is a string representation of the data to verify.

You should see in your logs that isSenderOwner is true: this means that the claimer presenting the credential is the same that owns it, so it has not been stolen or compromised.

Looking good!

You can also see what would happen when a malicious actor presents a stolen credential to a verifier. Try this out:

  • Create another account and light DID, let's refer to it as Mallory (= malicious);
  • Sign the presentation above with Mallory's light DID, hence creating a new presentation;
  • Create a new invalidDataToVerify object with this new presentation and with Alice's credential we've been using so far;
  • As a verifier, verify the presentation in invalidDataToVerify via ProofID.Credential.verify;
  • You'll see that this verification will return false: the verifier will know that this credential is not owned by Mallory.