1.0.18 • Published 2 years ago

whatsdapp v1.0.18

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

WhatsDapp

The core of WhatsDapp as a private messenger library should be an asynchronous, censorship- and ddos-resistant (availability) end-2-end-encrypted communication channel (integrity) with forward- and backward-secrecy (confidentiality).

This development corresponds to the Private Messenger Dapp project on Dash Incubator App.

Install

Just install via npm

npm i whatsdapp

or clone, compile via:

npm run dist

and add to your package.json-dependencies:

"whatsdapp": "<path-to>/whatsdapp-lib"

Usage

Storage

First you need a storage. The whatsdapp-lib does not come with one, since we want developers to use the storage that best fits their needs. Therefore we defined the interface KVStore in src/storage/StructuredStorage.ts as follows:

export type KVStore = {
  get(key: string): Promise<Uint8Array | null>,
  set(key: string, value: Uint8Array): void,
  del(key: string): void,
}

WhatsDapp comes with an encryption tidbit for storage. If you give a password it will AES-encrypt all local data so you can use an unencrypted local storage as well.

For a quick start you can use the LocalStorageClearDevPurposeOnly implementing the KVStore interface located in little_helper/local_storage_clear_dev.js.

Prepare Storage

On first use (and only on first use) you need to give your mnemonic. From then it stays in local storage. Therefore call static function:

static async prepareEmptyStorage(mnemonic: string, identityString: string | null, storageObj: KVStore, storagePassword?: string): Promise<void>;

e.g.

//identity will be generated later, use unencrypted storage
await WhatsDapp.prepareEmptyStorage(<MNEMONIC>, null, storageObj);

or:

//fixed identity, use password encrypted storage
await WhatsDapp.prepareEmptyStorage(<MNEMONIC>, <IDENTITY>, storageObj, '53cur3p455w0rd');

Create Messenger

To create a new WhatsDapp-object use the static factory function:

static async createWhatsDapp(storageObj: KVStore, storagePassword?: string): Promise<WhatsDapp>;

Set optional password according parameters set in prepareEmptyStorage:

const messenger = await WhatsDapp.createWhatsDapp(storageObj);

resp:

const messenger = await WhatsDapp.createWhatsDapp(storageObj, '53cur3p455w0rd');

If there is no identity deposited in storage a new one will be generated and automatically topped up.

After the initialization, generated Dash-Identity and WhatsDapp-Profile can be retrieved by getter-functions:

getCurrentIdentityString(): string
getCurrentProfile(): WhatsDappProfile | null

Connecting Events

There are currently three events emitted by WhatsDapp:

emit(ev: WhatsDappEvent.NewIncomingMessage, msg: WhatsDappMessage, interlocutor: string): boolean;
emit(ev: WhatsDappEvent.MessageSent, msg: WhatsDappMessage, interlocutor: string): boolean;
emit(ev: WhatsDappEvent.LowFunds, remainingDuffs: number): boolean;

The type WhatsDappMessage contains also drive info as $id, $createdAt and $updatedAt. So the MessageSent event tells the listenig application that the document was uploaded to the drive, at what point of time and with what id.

export type WhatsDappMessage = {
  //global 32B ID derived from document on drive
  id: string,
  //plaintext message content
  content: string,
  //timestamps
  createdAt: number,
  updatedAt: number,
  //Dash identity-IDs
  senderId: string, //same as ownerId
  recipientId: string,
  referenceToMessageId?: string,
  //marks new messages as unread
  read?: boolean,
}

While the LowFunds-event is fired if identity.getBalance() returns a value lower than MINIMUM_DUFFS_TO_SEND_MESSAGE= 100.

Polling

To receive new messages we use a polling. The default interval is currently 5 seconds.

There are three functions to control the polling:

startPolling(pollIntervalMilliseconds?: number): boolean;
setPollIntervall(pollIntervalMilliseconds: number): void;
stopPolling(): void;

Send Message

To send a message there is the following function.

sendMessage(recipientId: string, plaintext: string, referenceToMessageId?: string): Promise<boolean> 

The recipientId is a Dash-Identity string.

Minimal working Example

Bring above info together in one code snippet you can reuse.

const {WhatsDapp, WhatsDappEvent} = require('whatsdapp');
const MNEMONIC='<mnemonic goes here>';
const IDENTITY='<identity id goes here>'
// retrieve appdata path
const path = require('path');
const appDataPath = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")
//set storage path for dev purpose in a directory depending on identity
const storagePath = path.join(appDataPath, 'whatsDappSessions/'+IDENTITY)

//use one arbitraty KVStore (copy js from little_helper/)
const LocalStorageClearDevPurposeOnly = require('./local_storage_clear_dev');
const storage = new LocalStorageClearDevPurposeOnly(storagePath)

const demoRun = async () => {
  //prepare first time. If userdata exist, this will throw an error and move on
  try {
    await WhatsDapp.prepareEmptyStorage(MNEMONIC, IDENTITY, storage, "53cur3p455w0rd");
  } catch (e) {
    console.log("Storage seems to be initiated already. Try to move on...")
  }

  //optionally an new identity and a generic profile will be created
  const messenger = await WhatsDapp.createWhatsDapp(storage, "53cur3p455w0rd");

  //define listener function
  const newMsgArrived=async (msg, interlocutor)=>{
    //DO SOMETHING
    console.log("New message arrived:",msg);
  }

  //connect event
  messenger.on(WhatsDappEvent.NewIncomingMessage, newMsgArrived)

  //start polling for new messages
  messenger.startPolling();
}
demoRun();

Loss of private Keys

If you loose your storage, remote profile is useless since we don't have the private keys to do anything. In this case run:

await messenger.discardOldProfileAndCreateNew()

This will remove the old profile from Drive, so nobody can retrieve invalid keys. After that a new keybundle will be created and a new profile uploaded, so you can start over again.

Net Prefs

WhatsDapp currently running on testnet synchronizing from mid 2021 as stated in src/dapi/dash_client/WhatsDappDashClient.ts:

const clientOpts = {
    network: 'testnet',
    wallet: {
      mnemonic: mnemonic,
      unsafeOptions: {
        skipSynchronizationBeforeHeight: 500000, // only sync from mid of 2021
      },
    },
    apps
  };

Data Contracts

There are two data contracts we use:

  • profile: WhatsDapp profiles conatining the signal keybundle and
  • message: Container for the encrypted messages

You can find them in src/dapi/dash_client/Contracts.ts

Pitfalls

  • You cannot write yourself ;)
    That's not provided by libsignal

  • Messages you receive are not validated yet, just decrypted and displayed. We need to think about XSS protection.

Used packages

Why Signal?

Storing private messages in the blockchain forever is risky. For that we chose Signal protocol for its forward- and backward-secrecy. This way it’s not possible to find any previous or future keys from one compromised message-key. The basis is a double-key-ratchet that generates new keys for every message of each session. One ratchet performs extended triple Diffie-Hellman (X3DH) based on EC. There are 4 keypairs used for generating session key. This session key then runs through a hash-ratchet that generates new keys for each message. Each message will be encrypted AES-256 with kind of HMAC with an unique key. For further well-arranged protocol info see https://signal.org/docs/specifications/x3dh/ .

Links

License

Licensed under the MIT License.

Note that the signal-protocol lib we currently use is licensed under the GPLv3.

1.0.2

2 years ago

1.0.18

2 years ago

1.0.1

2 years ago

1.0.17

2 years ago

1.0.0

2 years ago

1.0.16

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.11

2 years ago

1.0.10

2 years ago

1.0.15

2 years ago

1.0.14

2 years ago

1.0.13

2 years ago

1.0.12

2 years ago

0.1.1

3 years ago

0.1.0

3 years ago