Menu Home

KERI Tutorial Series – KLI: Sign and Verify with Heartnet

writing a love letter

Ever needed to send a love letter and trust that your recipient knew it was really you that sent it? If so then this post is for you! KERI provides the end-verifiability and secure attribution you need to trust your communications are secure.
Join Brett and Allie as they show how to send a secure love letter and a reply using KERI.
Follow along with the companion video of their story: KERI KLI Series: Sign and Verify.

Even if you have not such a need and you only have an interest in learning about KERI (Key Event Receipt Infrastructure) this post is also for you.

KERI KLI Series: Sign and Verify

Sign and Verify

“Sign and Verify with Heartnet” is the first of the KERI Tutorial Series focused on accomplishing tasks the KERI command line. This tutorial uses KERIpy, the Python reference implementation of the KERI protocol.

Our Task

The task for today is simple:

  1. Establish trust between Allie and Brett’s communication lines
  2. Sign a love letter from Allie to Brett
  3. Verify the love letter signature
  4. Sign a love reply from Brett to Allie
  5. Verify the love reply signature

The diagram below shows what you will end up with, a local KERI network with two keystores, one each for Allie and Brett, and a pool of six witnesses, the demonstration witness pool provided by the Python implementation of KERI.

Outline

  1. Set up your machine for a KERI deployment.
    • Docker Setup with GLEIF image – Easiest
    • Manual Setup form Source – Harder
  2. Create configuration and keystore directories
  3. Start Witness network
  4. Initialize keystores
  5. Create KERI identifiers by making an inception event
  6. Connect the two KERI identifiers using OOBIs (direct service discovery)
  7. Increase the trust level with MFA challenge phrases
  8. Write and sign the love letter
  9. Verify the love letter signature
  10. Write and sign the love reply
  11. Verify the love reply

General notes

  • The “$” dollar sign character indicates the type of shell being used, specifically a user shell rather than a root shell (signified by a pound sign “#”). You do not need to type the “$” character when it appears at the beginning of a shell command line.
  • I make frequent use of the shell continuation character “\” to provide readability to commands. This could be hard on the eyes of seasoned professionals though it is helpful for those new to KERI and those who appreciate separate arguments on separate lines.
    You do not need to use this continuation character when you use the commands. You can omit it and type everything on or condense everything to one line.
  • Output of commands is typically included in code highlighting blocks directly after the command with a header of “Output:”.

Step 1: Machine Setup

Docker Setup with GLEIF image – Easiest

  1. Install Docker
  2. Run the following command: docker run --rm -it gleif/keri /bin/bash

Make sure to include the “/bin/bash” command as the default command in the GLEIF image is to run Python as it is based on the python:3.10.4-alpine3.16 image. You want a shell prompt, not a Python prompt.

Manual Setup from Source – Harder

Linux (recommended, especially as a Ubuntu container)

  1. Install Git, cURL, Python 3, Python 3 PIP, Python 3 Venv, Libsodium-dev, maturin, and the Rust toolchain
    • $ apt update
    • $ apt install git curl python3 python3-pip python3-venv libsodium-dev
    • $ curl https://sh.rustup.rs -sSf | bash -s -- -y
    • $ source "$HOME/.cargo/env"
    • $ pip3 install maturin
  2. Make the working directory /keri
    • $ mkdir /keri
    • $ cd /keri
  3. Install KERI
    • $ git clone https://github.com/WebOfTrust/keripy.git
    • $ python3 -m pip install -e ./
  4. Verify your installation
    • $ kli version
    • This should output something like “0.6.8” or “0.6.9”

OS X / Mac

If you are going to install with OS X then be sure to translate all of the “/keri” directories to whatever the root directory is you are using for this tutorial work.
I will use $HOME/allie-brett.
You will also need to translate the “python” commands to whatever you end up having on your system whether “python3”, “python3.10”, or anything like that. I use “python” because I set up my system to point to my Pyenv installation in $HOME/.pyenv/shims/python binary.

  1. With Homebrew: Install Python 3, Python 3 PIP, Python 3 Venv, Libsodium-dev, maturin, and the Rust toolchain
    • Installing Python 3 on a Mac is a rather involved process since the operating system default installation can interfere with things.
      Use the following freeCodeCamp guide to install “pyenv” and then install Python 3 on top of that:
      How to Install Python 3 on Mac – Brew Install Update Tutorial
    • $ brew install libsodium
    • $ pip install maturin
  2. Set up the working directory
    • $ mkdir $HOME/allie-brett
    • $ cd $HOME/allie-brett
  3. Install KERI
    • $ git clone git clone https://github.com/WebOfTrust/keripy.git
    • $ cd $HOME/allie-brett/keripy
    • $ python -m pip install -e ./
  4. Verify your installation
    • $ kli version
    • This should output something like “0.6.8” or “0.6.9”

Windows

I hardly use Windows anymore so do not rely too much on the following instructions. Using Git Bash is likely your best option. If you end up getting this working on Windows then please drop me a comment below with your instructions and I will add them to this post, or send me an email. I will give you credit here.
Consider using Chocolatey.

  1. Install Dependencies: Python 3, Libsodium, Maturin
  2. Set up the working directory (don’t know if these work)
    • $ mkdir %userprofile%\keri
    • $ cd %userprofile%/keri
  3. Install KERI
    • $ git clone git clone https://github.com/WebOfTrust/keripy.git
    • $ cd %userprofile%\allie-brett\keripy
    • $ python -m pip install -e ./
  4. Verify your installation
    • $ kli version
    • This should output something like “0.6.8” or “0.6.9”

The rest of the tutorial will be using Linux since it is the easiest to work with. If you aren’t yet familiar with Docker I highly recommend you take the plunge and learn how to use it. You will be well rewarded for your efforts.

Step 2: Create configuration and keystore directories

Pretty simple, just making a few directories and configuration files. If you haven’t seen the “{,}” syntax before in making directories it allows you to specify a list of directories to be made at a given level.
Keep in mind the KERI_CONFIG_DIR variable as it will be used later.

The two witness configuration files, “allie-witness-oobis.json” and “brett-witness-oobis.json”, are the witness pool configurations for both Allie and Brett. Similarly the “magic-pencil.json” and the “secret-speaker.json” configuration files are the identifier configurations used for Allie and Brett, respectively.

$ mkdir -p /keri/heartnet/keri/cf
$ mkdir /keri/{allie,brett}
$ export KERI_CONFIG_DIR=/keri/heartnet

$ echo '{"dt": "2022-01-20T12:57:59.823350+00:00","iurls": ["http://127.0.0.1:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller","http://127.0.0.1:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller","http://127.0.0.1:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller"]}' \
     > /keri/heartnet/keri/cf/allie-witness-oobis.json

$ echo '{"dt": "2022-01-20T12:57:59.823350+00:00","iurls": ["http://127.0.0.1:5645/oobi/BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE/controller","http://127.0.0.1:5646/oobi/BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP/controller","http://127.0.0.1:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller"]}' \
     > /keri/heartnet/keri/cf/brett-witness-oobis.json

$ echo '{"transferable": true,"wits": ["BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha","BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM","BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX"],"toad": 3,"icount": 1,"ncount": 1,"isith": "1","nsith": "1"}' \
     > /keri/allie/magic-pencil.json

$ echo '{"transferable": true,"wits": ["BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE","BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP","BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM"],"toad": 3,"icount": 1,"ncount": 1,"isith": "1","nsith": "1"}' \
     > /keri/brett/secret-speaker.json

At this point you have created the keystores and have the foundation set up for both Allie and Brett

Step 3: Start Witness Network

Very simple, one command, since the demo witness network is being used. Ensure you follow the command with the ampersand “&” sign so it runs in the background and you can use your terminal session for the rest of the commands. Alternatively you can run this in a separate terminal window or with something like the screen command.

$ kli witness demo &

Output will look like:
[1] 23
Witness wan : BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha
Witness wil : BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM
Witness wes : BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX
Witness wit : BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE
Witness wub : BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP
Witness wyz : BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM

The witness network is a set of six witnesses separated into two groups, three each for Allie and Brett.

Step 4: Initialize Keystores

You create a unique cryptographic salt for Allie that is used to derive her public key. This salt should be kept secure with the utmost protections. You can use other sources for the salt.

For Allie:

$ export ALLIE_SALT="$(kli salt)"
$ kli init \
      --name allie_ks \
      --base /keri/allie  \
      --nopasscode \
      --salt ${ALLIE_SALT} \
      --config-dir ${KERI_CONFIG_DIR} \
      --config-file allie-witness-oobis

Output:
KERI Keystore created at: /keri/allie/allie_ks
KERI Database created at: /keri/allie/allie_ks
KERI Credential Store created at: /keri/allie/allie_ks

Loading 3 OOBIs...
http://127.0.0.1:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller succeeded
http://127.0.0.1:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM/controller succeeded
http://127.0.0.1:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller succeeded

For Brett:

$ export BRETT_SALT="$(kli salt)"
$ kli init \
      --name brett_ks \
      --base /keri/brett  \
      --nopasscode \
      --salt ${BRETT_SALT} \
      --config-dir ${KERI_CONFIG_DIR} \
      --config-file brett-witness-oobis

Output:
KERI Keystore created at: /keri/brett/brett_ks
KERI Database created at: /keri/brett/brett_ks
KERI Credential Store created at: /keri/brett/brett_ks

Loading 3 OOBIs...
http://127.0.0.1:5645/oobi/BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE/controller succeeded
http://127.0.0.1:5646/oobi/BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP/controller succeeded
http://127.0.0.1:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller succeeded

You have now successfully initialized the keystores for Allie and Brett.

Step 5: Create KERI identifiers by making an inception event

The most important part to being able to use KERI is the creation of KERI identifiers, also known as Autonomic Identifiers, or AIDs. One identifier will be created for each Allie and Brett. The witnesses in the witness pools will each observe, or witness, the inception event and give a receipt of that observation back to the AID controller.

  • Allie’s AID will be aliased as “magic-pencil”
    • She writes her love letter with a magic pencil!
  • Brett’s AID will be aliased as “secret-speaker”
    • Brett has a special device known as a secret speaker that will only play verified messages from his sweetheart.

For Allie:

$ kli incept \
      --name allie_ks \
      --base /keri/allie \
      --alias magic-pencil \
      -f /keri/allie/magic-pencil.json

Output:
Waiting for witness receipts...
Prefix  EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP
	Public key 1:  DDENeQF4NUaO4AdJfNdyxnrEYRTCIZoEzJ9kh4t0woLN

Place the prefix in a variable for later
$ export ALLIE_PREFIX=EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP

For Brett:

$ kli incept \
      --name brett_ks \
      --base /keri/brett  \
      --alias secret-speaker \
      -f /keri/brett/secret-speaker.json

Output:
Waiting for witness receipts...
Prefix  EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA
	Public key 1:  DE2RDQFCUvEx8kcKEg15S8txBZ8X1lURdwLpWylLD2Jm

Do the same thing for Brett's prefix:
$ export BRETT_PREFIX=EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA

Now that both of the identifiers are set up then key event logs (KELs) have been created for Allie and Brett. You also see that receipts from each of the witnesses sent back to the controller of each AID are stored in the key event log and can be called a key event receipt log (KERL).

Step 6: Connect the two KERI identifiers using OOBIs (direct service discovery)

Currently Allie and Brett’s KERI controller nodes (their AID controllers) do not know about each other and must be introduced to begin the trust bond.

To do this you first generate an Out Of Band Introduction (OOBI) value for a given source identifier and then you pass that value to the destination who you want to learn about the location of, or discover, the source.

OOBI Generation

For Allie:

$ kli oobi generate \
      --name allie_ks \
      --base /keri/allie \
      --alias magic-pencil \
      --role witness

Output:
http://127.0.0.1:5642/oobi/EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP/witness
http://127.0.0.1:5643/oobi/EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP/witness
http://127.0.0.1:5644/oobi/EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP/witness

You see an OOBI value for each of the witnesses specified in the configuration file. Take any one of these values to place in the magic_pencil_oobi variable:

$ export magic_pencil_oobi=http://127.0.0.1:5642/oobi/EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP/witness

For Brett:

$ kli oobi generate \
      --name brett_ks \
      --base /keri/brett \
      --alias secret-speaker \
      --role witness

Output:
http://127.0.0.1:5645/oobi/EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA/witness
http://127.0.0.1:5646/oobi/EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA/witness
http://127.0.0.1:5647/oobi/EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA/witness

And again put one of the values in the secret_speaker_oobi variable:

$ export secret_speaker_oobi=http://127.0.0.1:5645/oobi/EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA/witness

OOBI Resolution (discovery)

We switch up the storyline order here and have Allie introduce her magic-pencil AID to Brett followed by Brett introducing his secret-speaker AID to Allie. This mutual resolution of OOBIs to completes the discovery process.

For Brett:

kli oobi resolve \
    --name brett_ks \
    --base /keri/brett \
    --oobi-alias magic-pencil \
    --oobi "${magic_pencil_oobi}"

Output: 
http://127.0.0.1:5642/oobi/EGdeGrvLiZ7K8KFuSrFhc4AghPwkwi-qEMGKokaTh2JP/witness resolved

This introduces Allie to Brett. Allie has shared her magic-pencil OOBI with Brett and Brett has resolved that OOBI (a URL) using typical internet infrastructure to find and connect with Allie’s AID. Brett’s secret-speaker AID then analyzes the key event log of Allie’s magic-pencil AID and verifies it so Brett’s secret-speaker AID knows it can trust it is really speaking with Allie’s magic-pencil AID.

For Allie:

$ kli oobi resolve \
      --name allie_ks \
      --base /keri/allie \
      --oobi-alias secret-speaker \
      --oobi "${secret_speaker_oobi}"

Output:
http://127.0.0.1:5645/oobi/EMFWPbZf4uG2XUV-LSJ4zWkLU-2tbuCMZh36gW1NeJiA/witness resolved

This introduces Brett to Allie. Brett gives his secret_speaker_oobi value to Allie which she uses to then resolve his location using typical internet infrastructure, establishes a connection with him, and then verifies his key event log for the secret-speaker AID to ensure she can trust the secret-speaker.

Step 7: Increase the trust level with MFA challenge phrases

As a multi-factor authentication (MFA) method you can issue challenge phrases to an identifier that it would prepare a response to which you could then verify using the challenge verify workflow. For Allie and Brett they both generate their own challenge phrase which the other party signs after which the original party who generated the challenge phrase verifies the signature of the signed phrase.

Generate Challenge Phrase

The kli challenge generate command works like so:

$ kli challenge generate --out string

Sample Output:
ranch space pipe later they jazz retreat tide expand inform barrel gorilla

For brevity I use command expansion and string interpolation to generate the challenge phrases. You can use any string you like for the challenge phrase and are not limited to phrases generated by kli challenge generate.

For Allie:

allie_words="$(kli challenge generate --out string)"

For Brett:

brett_words="$(kli challenge generate --out string)"

Prepare Challenge Response

Allie prepares a response to Brett’s challenge phrase and Brett verifies Allie’s challenge response.

Allie preparing a response to Brett's challenge phrase:
$ kli challenge respond \
      --name allie_ks \
      --base /keri/allie \
      --alias magic-pencil \
      --recipient secret-speaker \
      --words "${allie_words}"

Output: no output

Brett verifying Allie's challenge response:
$ kli challenge verify \
      --name brett_ks \
      --base /keri/brett \
      --alias secret-speaker \
      --signer magic-pencil \
      --words "${allie_words}"

Output:
Checking mailboxes for any challenge responses..

Signer magic-pencil successfully responded to challenge words: '['term', 'spatial', 'weasel', 'prison', 'sniff', 'worth', 'unfold', 'balance', 'motor', 'monkey', 'cloud', 'economy']'

Brett then similarly prepares a response to Allie’s challenge phrase and Allie verifies Brett’s challenge response.

Brett preparing a response to Allie's challenge phrase:
$ kli challenge respond \
      --name brett_ks \
      --base /keri/brett \
      --alias secret-speaker \
      --recipient magic-pencil \
      --words "${brett_words}"

Output: no output

Allie verifying Brett's challenge response:
$ kli challenge verify \
      --name allie_ks \
      --base /keri/allie \
      --alias magic-pencil \
      --signer secret-speaker \
      --words "${brett_words}"

Output:
Checking mailboxes for any challenge responses..

Signer secret-speaker successfully responded to challenge words: '['life', 'lift', 'sheriff', 'same', 'gentle', 'traffic', 'foot', 'trash', 'approve', 'dawn', 'audit', 'type']'

Step 8: Write and sign the love letter

Now we can finally do what we came here to do! It’s time to write the letter and sign it.

Allie’s love letter:

Allie writes the letter:
$ echo '{"love_letter": "well, hello there, honey. Happy Valentines :*"}' > /keri/heartnet/love-letter.json

And signs it:
$ kli sign \
      --name allie_ks \
      --base /keri/allie \
      --alias magic-pencil \
      --text @/keri/heartnet/love-letter.json 

Output (is a signature):
1. AAAZP-KPS1FtkeVNvvm8uSFJqlDNmJy8tpD1hB2-OYcmlP39Fog4wETcDEL_4QNXHeUVV_QiuUpqbxgIzAaiV9EK

Place output in a variable:
export ALLIE_SIGNATURE=AAAZP-KPS1FtkeVNvvm8uSFJqlDNmJy8tpD1hB2-OYcmlP39Fog4wETcDEL_4QNXHeUVV_QiuUpqbxgIzAaiV9EK

Step 9: Verify the love letter signature

Brett wants to know this letter really is from Allie so he verifies it with kli verify:

Brett verifies Allie's signature on the love letter:
$ kli verify \
      --name brett_ks \
      --base /keri/brett \
      --alias secret-speaker \
      --prefix $ALLIE_PREFIX \
      --text @/keri/heartnet/love-letter.json \
      --signature $ALLIE_SIGNATURE

Output:
Signature 1 is valid.

Step 10: Write and sign the love reply

In turn, for such a show of affection from Allie, Brett signs and returns a love reply.

Brett's love reply:
$ echo '{"love_letter": "Hey sweetie, I got your letter! <3 <3"}' > /keri/heartnet/love-reply.json

And his signature:
$ kli sign \
      --name brett_ks \
      --base /keri/brett \
      --alias secret-speaker \
      --text @/keri/heartnet/love-reply.json

Output (a signature):
1. AAB3VtKayW1e1zn-RvyWLo3SChc01RitBgeYlX_apLZ8geCd8iMLFUu8exS0txwp9znjtMoayFy9-edim5Vfd7EK

And place it in a variable for later use:
export BRETT_SIGNATURE=AAB3VtKayW1e1zn-RvyWLo3SChc01RitBgeYlX_apLZ8geCd8iMLFUu8exS0txwp9znjtMoayFy9-edim5Vfd7EK

Step 11: Verify the love reply

Allie wants to be sure this reply to her letter really came from Brett so she will verify the signature Brett sent her.

Allie verifies Brett's love reply by checking his signature of it:
$ kli verify \
    --name allie_ks \
    --base /keri/allie \
    --alias magic-pencil \
    --prefix $BRETT_PREFIX \
    --text @/keri/heartnet/love-reply.json \
    --signature $BRETT_SIGNATURE

Output:
Signature 1 is valid.

It verifies! Both Allie and Brett can enjoy their secure, authentic love letter communications.

Wrap up

Brett and Allie had a great laugh knowing they have a secret conversation that nobody else knows about where they can trust that they were really communicating with each other. They can now have a happy, peaceful valentines.

You have learned about the following KERI concepts:

  1. Installing KERI
  2. KERI Keystores
  3. KERI identifiers – autonomic identifiers
  4. Inception events
  5. Out of band introductions (OOBIs)
  6. KERI MFA challenge phrase response and verification process
  7. KERI Signing and Verification
  8. The KERI Command Line tool, the KLI

And as you found out, KERI is very approachable and easy to learn.

See you in the next tutorial!

References

Categories: KERI

Tagged as:

Kent Bull

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: