KERI Tutorial: Sign and Verify with Signify & Keria

Kriskanin Hengniran
Finema
Published in
7 min readMay 6, 2024

Authors: Kriskanin Hengniran & Nuttawut Kongsuwan, Finema Co., Ltd.

Note: This tutorial is heavily inspired by “KERI Tutorial Series — KLI: Sign and Verify with Heartnet” by Kent Bull

https://kentbull.com/2023/01/27/keri-tutorial-series-kli-sign-and-verify-with-heartnet/

This blog presents an introductory guide for implementing the Key Event Receipt Infrastructure (KERI) protocol using the Signify and KERIA agents. The guide starts with installation and running the agents using Docker containers. The guide then provides a script to showcase the use of Signify and KERIA agents, including procedures for creating autonomic identifiers (AIDs), signing messages, and verifying signatures.

Signify & KERIA

Signify & KERIA are open-source projects developed for building client-side identity-wallet applications using the KERI protocol. Signify-KERIA identity wallet utilizes the hybrid edge-cloud wallet architecture where Signify provides a lightweight edge wallet component whereas KERIA provides a heavier cloud wallet component. Signify and KERIA were designed based on the principle of “key at the edge (KATE)”. That is, essential cryptographic operations are performed at edge devices.

Some resources for Signify & KERIA can be found here

Signify Edge Agent

Signify provides an edge agent for a KERI identity wallet and is used primarily for essential cryptographic operations including key pair generation and digital signature creation. Signify utilizes the hierarchical deterministic (HD) key algorithm. For example, a Signify application could safeguard a single master seed which is used to generate and manage any number of AIDs. Signify is designed to be lightweight so as to support devices with limited capabilities.

Signify is currently available in Typescript and Python

KERIA Cloud Agent

KERI Agent (KERIA) provides a cloud agent for a KERI identity wallet and is used for, e.g., data storage, agent-to-agent communications, and verification of KERI key event logs (KELs). KERIA is engineered to handle the heavy lifting for users, allowing their edge devices to stay lightweight while maintaining high security by performing all essential cryptographic operations at the edge.

A KERIA agent is cryptographically delegated by a Signify agent using the KERI delegation protocol. All instructions from a user are signed at the edge by a Signify agent and subsequently verified by a KERIA cloud agent. KERIA is currently available in Python: https://github.com/WebOfTrust/keria

Installation Guide for Node and NVM

In this guide, we will be using Node.js to run Signify-TS (Typescript) with a KERIA server running on a Docker container.

Install Node.js

Visit the Node.js website to download Node.js.

Install NVM on Linux or MacOS

To easily switch between different versions of Node.js, you could use Node Version Manager (NVM). NVM on Linux and macOS may be installed using curl

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash

Alternatively, we could use wget

wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash

Install NVM on Windows

On Windows, NVM may be installed from the GitHub releases page.

Note: After installing NVM, do not forget to restart your terminal. Alternatively, you can refresh the available commands in your system path by executing source ~/.nvm/nvm.sh

Using NVM

To ensure that NVM is correctly installed, you can check its version with:

nvm ls

To install Node.js version 18.18.2, run:

nvm install v18.18.2

Here, we use v18.18.2 in this guide. This is not a requirement and other versions of Node.js should work with Signify-TS.

Install Dependencies and Run a KERIA Server

To install dependencies for the tutorial, you could clone this repository https://github.com/enauthn/tutorial-signify-keria and run the following script

git clone https://github.com/enauthn/tutorial-signify-keria
cd tutorial-signify-keria
npm install
docker-compose up -d

where docker-compose up -d runs a KERIA server in a Docker container. Alternatively, you could get a Docker image for running a KERIA server from Docker Hub https://hub.docker.com/r/weboftrust/keria.

Running Signify-TS Scripts

Here, we demonstrate Signify-TS Scripts for signing and verifying an arbitrary message. Here, a signer, called Allie, signs a message “Hello Brett” and sends it to a verifier, called Brett. Brett subsequently obtains Allie’s key event log (KEL) and uses it to verify the signature on the message.

Connecting Signify clients to the KERIA server

First, Allie and Brett must boot and then connect to a KERIA server with their Signify (edge) clients. Each boot command creates a separate agent and a database in the KERIA server. see https://github.com/WebOfTrust/keria for more details.

Typically, Allie’s and Brett’s Signify scripts should run on separate devices, but here we put them in the same Typescript file for simplicity. Allie’s and Brett’s Signify clients may also connect to different KERI servers at different service endpoints. In this tutorial, both clients connect to a KERIA server running on the local host for simplicity.

await signify.ready();

const url = 'http://127.0.0.1:3901';
const bootUrl = 'http://127.0.0.1:3903';
const bran1 = signify.randomPasscode();
const bran2 = signify.randomPasscode();

const allieClient = new signify.SignifyClient(
url,
bran1,
signify.Tier.low,
bootUrl
);
await allieClient.boot();
await allieClient.connect();

const brettClient = new signify.SignifyClient(
url,
bran2,
signify.Tier.low,
bootUrl
);
await brettClient.boot();
await brettClient.connect();

To explain the above script:

  • signify.randomPasscode() generates a random string with 126-bit entropy using libsodium
  • new signify.SignifyClient(…) creates a new Signify instance, initialized with the newly generated passcode
  • allieClient.boot() creates a KERIA agent and a corresponding database at the KERIA server via port 3903
  • allieClient.connect() connects a Signify agent to the corresponding KERIA agent that has been booted.

Signify uses a passcode to generate cryptographic keys using a key derivation function (KDF) where signify.Tier specifies how much the passcode is stretched.

Allie creates an AID

Before Allie can sign a message with the KERI protocol, she must first create an autonomic identifier (AID) with a key inception event in a Key Event Log (KEL).

const icpResult1 = await allieClient
.identifiers()
.create('aid1', {});
await waitOperation(allieClient, await icpResult1.op());

const rpyResult1 = await allieClient
.identifiers()
.addEndRole('aid1', 'agent', allieClient!.agent!.pre);
await waitOperation(allieClient, await rpyResult1.op());

To explain the above script:

  • allieClient.identifiers().create('aid1', {}) creates an AID where the Signify agent signs the inception event and sends the event along with its signature to the KERIA agent. Here, the AID is given an alias 'aid1'.
  • allieClient.identifiers().addEndRole(...) cryptographically authorizes the KERIA agent to operate on behalf of the AID’s controller.
  • waitOperation(...) waits for the KERIA agent to complete its operation.

addEndRole stands for “adding endpoint role authorization”. This is a mechanism in the KERI protocol where the controller of an AID cryptographically authorizes—by signing with a private key associated with the AID—a service endpoint of a KERIA agent to operate on the AID’s behalf. Another agent that needs to communicate with the authorized KERIA agent can then verify the authorization signature.

Brett resolves Allie’s AID

Allie and Brett may exchange their KELs using the Out-Of-Band Introduction (OOBI) protocol. Allie may generate her OOBI URL that points to her KERIA agent’s service endpoint (which has been authorized in the previous step) as follows:

const oobi1 = await allieClient.oobis().get('aid1', 'agent');

The generated OOBI URL could be sent to Brett via an out-of-band channel such as email, messaging apps, or scanning QR code. Subsequently, Brett may ask his KERIA agent to resolve Allie’s OOBI to obtain the AID’s KEL from Allie’s KERIA agent:

const oobiOp = await brettClient.oobis().resolve(oobi1.oobis[0], 'aid1');
await waitOperation(brettClient, oobiOp);

Allie signs the Message

To sign a message, Allie may use the Signify KeyManager class as follows:

const aid1 = await allieClient.identifiers().get('aid1');
const keeper1 = await allieClient.manager!.get(aid1);
const message = "Test message";
const signature = await keeper1.sign(signify.b(message))[0];
console.log('signature', signature);

which generates the following CESR-encoded signature:

signature AAAnBe-VPfBU9-3eb7aM5GNwr_NBuoJzA8vm9AFPmgj3I4LIv1mup2bwPDlbIQ6gAgtaEZg5rwE1_fTVVTmPo0oI

To explain the above script:

  • allieClient.identifiers().get('aid1') retrieves the information about the AID with alias 'aid1' from the KERIA agent
  • allieClient.manager!.get(aid1) creates an instance from the KeyManager class called a keeper that signs an arbitrary byte string with keeper.sign()
  • signify.b() turns a text string into a byte string.

When a Signify agent creates an AID, it uses a salt together with its passcode to generates cryptographic keys associated with the AID. The salt is then encrypted and sent to the KERIA agent. allieClient.identifiers().get('aid1') also retrieves and decrypts the salt where the KeyManager could use the salt to regenerate the key for signing the message.

Brett verifies Allie’s signature

After Allie sends the message to Brett, Brett wants to make sure the message is really from Allie by verifying the signature on the message. Brett may retrieve Allie’s KEL and the corresponding key state of her AID to verify the signature as follows:

const aid1StateBybrettClient = await brettClient.keyStates().get(aid1.prefix);
const siger = new signify.Siger({qb64: signature});
const verfer = new signify.Verfer({
qb64: aid1StateBybrettClient[0].k[0]
});
const verificationResult = verfer.verify(siger.raw, signify.b(message));
console.log('verificationResult', verificationResult);

which gives the following output:

verificationResult true

To explain the above script:

  • brettClient.keyStates().get(aid1.prefix) retrieves the key state of the Allie’s AID
  • signify.Siger({qb64: signature}) is a signature-wrapper instance of the Siger class, initialized with the Allies’ signature on the message.
  • signify.Verfer(...) is a verifier-wrapper instance of the Verfer class, initialized by the Allie’s public key
  • verfer.verify(...) then verifies the message and its signature using Allie’s public key

The verification of the signature against the message indicates that the signature is valid. This ensures the authenticity and integrity of the message that Brett received from Allie.

Conclusion

This tutorial gives a brief introduction for using the KERI protocol with the Signify and KERIA agents, which provide a footing for building client-side KERI-based applications. Signify provides libraries for building KERI edge agents whereas KERIA provides libraries for building companion cloud agents. These agents follow the principle of “key at the edge (KATE)” where essential cryptographic operations are performed at edge devices.

Unfortunately, this is still the early days for these two projects, and there are not many educational materials around as of May 2024. To dive deeper into these two projects, I recommend studying their integration scripts at https://github.com/WebOfTrust/signify-ts/tree/main/examples/integration-scripts.

--

--