dialectlabs / protocol

Apache License 2.0
77 stars 12 forks source link

Protocol & web3

Summary

Dialect is a smart messaging protocol for dapp notifications and wallet-to-wallet messaging on the Solana Blockchain.

Dialect works by decorating on-chain resources, or sets of resources, with publish-subscribe (pub-sub) messaging capabilities. This is accomplished by creating a PDA whose seeds are the (lexically sorted) resources' public keys. Each pub-sub messaging PDA is called a dialect.

Dialect v0 currently supports one-to-one messaging between wallets, which powers both dapp notifications as well as user-to-user chat. Future versions of Dialect will also support one-to-many and many-to-many messaging.

This repository contains both the Dialect rust programs (protocol), in Anchor, as well as a typescript client, published to npm as @dialectlabs/web3.

Currently, the dialect account rent cost is ~0.059 SOL.

Table of Contents

  1. Installation
  2. Usage
  3. Local Development
  4. Docker
  5. Anchor Tests
  6. Examples
  7. Message Encryption

Installation

npm:

npm install @dialectlabs/protocol --save

yarn:

yarn add @dialectlabs/protocol

Usage

This section describes how to use Dialect protocol in your app by showing you examples in theexamples/ directory of this repository. Follow along in this section, & refer to the code in those examples.

Create your first dialect, send and receive message

The example in examples/hello-world.ts demonstrates how to create a new dialect, send and receive message.

import {
  createDialect,
  getDialectForMembers,
  sendMessage,
  Member,
} from '@dialectlabs/protocol';

const program = // ... initialize dialect program

const [user1, user2] = [Keypair.generate(), Keypair.generate()];
// ... fund keypairs
const dialectMembers: Member[] = [
  {
    publicKey: user1.publicKey,
    scopes: [true, true],
  },
  {
    publicKey: user2.publicKey,
    scopes: [false, true],
  },
];
const user1Dialect = await createDialect(
  program,
  user1,
  dialectMembers,
  false,
); // crate dialect on behalf of 1st user
await sendMessage(program, user1Dialect, user1, 'Hello dialect!'); // send message
const { dialect: user2Dialect } = await getDialectForMembers(
  program,
  dialectMembers,
  user2,
); // get dialect on behalf of 2nd user
console.log(JSON.stringify(user2Dialect.messages));
// Will print [{"text":"Hello dialect!", ...}]

Run the example above

ts-node examples/hello-world.ts

Local Development

Note: If you just need a local running instance of the Dialect program, it is easiest to simply run Dialect in a docker container. See the Docker section below.

Dialect is built with Solana and Anchor. Install both dependencies first following their respective documentation

We recommend installing anchor with avm and using the "@project-serum/anchor" version that's specified in our package.json file.

Be sure you are targeting a Solana localnet instance:

solana config set --url localhost

Next run the tests to verify everything is setup correctly:

First ensure you have ts-mocha installed globally:

npm install -g ts-mocha

Run the tests with:

anchor test

Run a local validator:

solana-test-validator --rpc-port 8899

Build the Dialect Solana program:

anchor build

If you haven't deployed this program to localnet before, anchor build produces a program-id. To get this program-id use the command:

solana address -k target/deploy/dialect-keypair.json

Add this program id in the following additional places before proceeding:

  1. In the dialect = "<program-id>" in Anchor.toml
  2. In the declare_id!("<program-id>") in programs/dialect/src/lib.rs
  3. In the localnet key in src/utils/program.json (redundant, to be retired)

Before deploying make sure you fund your Solana wallet:

You can fund your wallet with:

solana airdrop 2

You can verify your token balance with:

solana balance

Deploy the Dialect Solana program with:

anchor deploy

Finally, install the js/ts npm dependencies with

npm i

Docker

The Dialect docker image will get you a deployed Dialect program running on a Solana validator. This is ideal if you're building off of @dialectlabs/protocol, rather than actively developing on it.

# build
docker build -f docker/Dockerfile . -t dialect/protocol:latest

# run
docker run -i --rm -p 8899:8899 -p 8900:8900 -p 9900:9900 --name protocol dialect/protocol:latest

Tests

First ensure you have ts-mocha install globally:

npm install -g ts-mocha

Run the tests with:

anchor test

Examples

Run the example with:

DIALECT_PUBLIC_KEY=<dialect-public-key> ts-node examples/hello-world.ts

It is fine to omit the DIALECT_PUBLIC_KEY environment variable, the example will generate one on the fly. However, if you're using this example as an integration test with other services, such as the monitoring service, you'll need to set it to the public key corresponding to the private key in the monitoring service.

Message Encryption

A note about the encryption nonce.

https://pynacl.readthedocs.io/en/v0.2.1/secret/

Nonce

The 24 bytes nonce (Number used once) given to encrypt() and decrypt() must NEVER be reused for a particular key. Reusing the nonce means an attacker will have enough information to recover your secret key and encrypt or decrypt arbitrary messages. A nonce is not considered secret and may be freely transmitted or stored in plaintext alongside the ciphertext.

A nonce does not need to be random, nor does the method of generating them need to be secret. A nonce could simply be a counter incremented with each message encrypted.

Both the sender and the receiver should record every nonce both that they’ve used and they’ve received from the other. They should reject any message which reuses a nonce and they should make absolutely sure never to reuse a nonce. It is not enough to simply use a random value and hope that it’s not being reused (simply generating random values would open up the system to a Birthday Attack).

One good method of generating nonces is for each person to pick a unique prefix, for example b"p1" and b"p2". When each person generates a nonce they prefix it, so instead of nacl.utils.random(24) you’d do b"p1" + nacl.utils.random(22). This prefix serves as a guarantee that no two messages from different people will inadvertently overlap nonces while in transit. They should still record every nonce they’ve personally used and every nonce they’ve received to prevent reuse or replays.