Release: [email protected]

It has been exactly one month since the latest js-waku release. A lot has happen, let’s look together at what js-waku 0.8.0 brings us. Full changelog can be found on GitHub.

Feel free to skip to the last section if you are just interested in the new encryption API!

Disclaimer: As I wrote this article as I realized some wording in the API was not consistent so I have released 0.8.1 with breaking changes :scream:.

Keep alive

New keep alive feature that pings peers regularly using /ipfs/ping/1.0.0, can be enabled when creating a waku node (time expressed in seconds):

const waku = new Waku.create({ keepAlive: 10});

New method WakuRelay.deleteObserver

There are scenarios where one would want to remove observers, for example when using ReactJS.
This new method allows the removal of observers, it fix duplicate message issues one could enter by adding an observer several time (e.g. when mount a ReactJS component).

useEffect(() => {
  if (!waku) return;

  const handleRelayMessage = (wakuMsg: WakuMessage) => {
    console.log('Message received: ', wakuMsg);
  };

  waku.relay.addObserver(handleRelayMessage, [ChatContentTopic]);

  return function cleanUp() {
    waku?.relay.deleteObserver(handleRelayMessage, [ChatContentTopic]);
  };
}, [waku]);

New utils module for easy conversion and comparison of byte arrays

A number of variable are byte arrays that are often represented in hex string with or without a 0x prefix. To save time and mistake, a utils module was introduced. Ideally, a dApp developer would not need to use this module as I aim for js-waku API to be consistent and robust. However, it is published and available just in case. Feel free to open issues if you end up using it!

import { hexToBuf, equalByteArrays, bufToHex } from 'js-waku/lib/utils';

// Get a `Buffer` from hex string whether or not it has a `0x` prefix.
const buf1 = hexToBuf("0x5a...97");
const buf2 = hexToBuf("5a...97");

// You can also pass a `Buffer` or `UInt8Array` in case your own API accepts several types
const buf3 = hexToBuf(buf1);

// Compare 2 addresses or signature, etc, without having to worry about `0x` prefix or type
const equals = equalByteArrays("0x5a...b97", "5a...b97");
const equals = equalByteArrays("0x5a...b97", buf1);

// Convert a byte array (`Buffer`, `Uint8Array`, `BufferArray`, etc) to hex string without `0x` prefix:
const hex = bufToHex(buf1); // hex = "5ac...b97"

New peers and randomPeer methods on WakuStore and WakuLightPush

WakuStore.peers and WakuLightPush.peers were introduced to give better feedback about connectivity, you can see a live usage on Eth-DM:

image

Peer auto selection for store and light push protocols

You do not need to select a peer id to use store or light push protocols anymore. js-waku will select an available peer for you or throw an error if no peer is available:

const res = await waku.lightPush.push(msg);
const messages = await waku.store.queryHistory({ contentTopics: [] });

Introduction of Options structure for WakuMessage creation

As new features are added, the number of optional parameters to create a Waku Message have increased, they are now grouped in a single optional object:

interface Options {
  /**
   * Content topic to set on the message, defaults to {@link DefaultContentTopic}
   * if not passed.
   */
  contentTopic?: string;
  /**
   * Timestamp to set on the message, defaults to now if not passed.
   */
  timestamp?: Date;
  /**
   * Public Key to use to encrypt the messages using ECIES (Asymmetric Encryption).
   *
   * @throws if both `encPublicKey` and `symKey` are passed
   */
  encPublicKey?: Uint8Array | string;
  /**
   * Key to use to encrypt the messages using AES (Symmetric Encryption).
   *
   * @throws if both `encPublicKey` and `symKey` are passed
   */
  symKey?: Uint8Array | string;
  /**
   * Private key to use to sign the message, either `encPublicKey` or `symKey` must be provided as only
   * encrypted messages are signed.
   */
  sigPrivKey?: Uint8Array;
}

Example:

const wakuMessage = await WakuMessage.fromBytes(payload, {
        contentTopic: '/cool-app/1/kitties/proto',
      });

WakuMessage constructor is now private

To ensure that messages are encrypted and decrypted properly (see last section), the WakuMessage constructor is now private and the following functions should be used instead:

  • fromUtf8String: To create a Waku Message with a given string as payload
  • fromBytes: To create a Waku Message with a given byte array as payload
  • decode: To decode a Waku Message received over the wire from a byte array
  • decodeProto: To decrypto/decode a Waku Message Object received over the wire.

Introduced Karma for Browser testing

Added karma as part of the CI, which means that some of the code is now tested in Chrome for each pull request.
This was important to do for the encryption part as libraries and API differs between NodeJS and Browser environment. As js-waku targets dApps (ie, web apps), some effort will be made to have more tests done in a browser environment (see #52).

Also, these functions are now async to allow encryption or decryption to take place (see last section).

Waku Message version 1 support: Encryption (symmetric & asymmetric) and signature of the payload

Last but not least, js-waku 0.8.0 introduces the full support of Waku Message version 1 as defined here: 26/WAKU-PAYLOAD | Vac RFC.

Now, js-waku gives you tools to easily:

  • Encrypt messages over the wire using public/private key pair (asymmetric encryption),
  • Encrypt messages over the wire using a unique key to both encrypt and decrypt (symmetric encryption),
  • Sign and verify your waku messages (must use encryption, compatible with both symmetric and asymmetric).

The Eth-DM example (e2e encrypted direct messages) has been updated accordingly to use asymmetric encryption (signatures does not use version 1 yet).

Cryptographic Libraries

A quick note on the cryptographic libraries used as it is a not a straightforward affair:

The API

Let’s review the API together. I made a number of design choices that I am happy to evolve as developers start using the API.

Creating new keys

Asymmetric private keys and symmetric keys are expected to be 32 bytes arrays. Hence, the developer has a choice between coming with their own way to generate it or just use the provided api:

import { generatePrivateKey, getPublicKey } from 'js-waku';

// Asymmetric
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);

// Symmetric
const symKey = generatePrivateKey();

Encrypt Waku Messages

To encrypt your waku messages, simply pass the encryption key when creating it:

import { WakuMessage } from 'js-waku';

// Asymmetric
const message = await WakuMessage.fromBytes(payload, {
    contentTopic: myAppContentTopic,
    encPublicKey: publicKey,
  });

// Symmetric
const message = await WakuMessage.fromBytes(payload, {
    contentTopic: myAppContentTopic,
    symKey: symKey,
  });

I believe this is the simplest API I could provide. An alternative API could be to pass the keys to WakuRelay or WakuLightPush for encryption purposes so that all message they send is encrypted. Let me know if this would make it easier.

Decrypt Waku Messages

There are currently two ways to receive messages with js-waku: using WakuRelay or WakuStorage.

Waku Relay

The recommended way to use WakuRelay is to add observers to process new messages:

waku.relay.addObserver(
  (wakuMessage) => {
    // Process wakuMessage
  },
  [myAppContentTopic]
);

If you expect to receive encrypted messages then simply add private decryption key(s) to WakuRelay.
Waku Relay will attempt to decrypt incoming messages with each keys, both for symmetric and asymmetric encryption.
Messages that are successfully decrypted (or received in clear) will be passed to the observers, other messages will be omitted.

// Asymmetric
waku.relay.addDecryptionKey(privateKey);

// Symmetric
waku.relay.addDecryptionKey(symKey);

// Then add the observer
waku.relay.addObserver(callback, [contentTopic]);

Keys can be removed using WakuMessage.deleteDecryptionKey.

Waku Store

When using WakuStore, simply pass one or several decryption keys (asymmetric or symmetric) to the call:

const messages = await waku.store.queryHistory({
  contentTopics: [],
  decryptionKeys: [privateKey, symKey],
});

Similarly to relay, only decrypted or clear messages will be returned.

Sign Waku Messages

As per version 1`s specs, signatures are only included in encrypted messages.
In the case where your app does not need encryption then you could use symmetric encryption with a trivial key, I intend to dig more on the subject and come back with recommendation and examples.

Signature keys can be generated the same way asymmetric keys for encryption are:

import { generatePrivateKey, getPublicKey, WakuMessage } from 'js-waku';

const signPrivateKey = generatePrivateKey();

// Asymmetric Encryption
const message = await WakuMessage.fromBytes(payload, {
    contentTopic: myAppContentTopic,
    encPublicKey: recipientPublicKey,
    sigPrivKey: signPrivateKey
  });

// Symmetric Encryption
const message = await WakuMessage.fromBytes(payload, {
    contentTopic: myAppContentTopic,
    encPublicKey: symKey,
    sigPrivKey: signPrivateKey
  });

Verify Waku Message signatures

Two new fields have been added to WakuMessage to be able to verify signatures:

  • signaturePublicKey: If the message is signed, it holds the public key of the signature,
  • signature: If the message is signed, it holds the actual signature.

Thus, if you expect messages to be signed by Alice, you can simply compare WakuMessage.signaturePublicKey with Alice’s public key. As comparing hex string can lead to issues (is the 0x prefix present?), simply use helper function equalByteArrays.

import { equalByteArrays } from 'js-waku/lib/utils';

const sigPubKey = wakuMessage.signaturePublicKey;

const isSignedByAlice = sigPubKey && equalByteArrays(sigPubKey, alicePublicKey);

Conclusion and What’s next?

Now that encryption, decryption and signature are tackled, I look forward to your feedback.

In the mean time, I will be focusing on

  • Recruitment: We hire both in the js-waku team and for Vac/nim-waku/waku.
  • Presentations: Next week I will be presenting DappConnect and js-waku at both EthCC and the Ethereum Engineering Group
  • Stability: There is a known issue with relay stream closing and not re-opening, the issues comes from upstream library and more investigation is needed.
  • Website: We are building a new website for DappConnect to gather all resources!
  • Developer Experience: Focus continues on the developer experience to refine the API and produce more documentation, examples and tutorials.

As usual you can check what we have in the piple on the js-waku board, feel free to reach out on Discord or in the Waku powered example web chat app.

Chat soon!

4 Likes

Thanks for the great tutorial!

No worries, glad it helped!

I am writing more tutorials at the moment, you can find them at js-waku/guides at main · status-im/js-waku · GitHub

I intend to write more about encryption and signatures next week too, especially when one wants to use web3.