specs/status-whisper-usage-spec.md

9.2 KiB

Status Whisper Usage Specification

Version: 0.1 (Draft)

Authors: Adam Babik adam@status.im, Corey Petty corey@status.im, Oskar Thorén oskar@status.im (alphabetical order)

Abstract

Status uses Whisper to provide privacy-preserving routing and messaging on top of devP2P. Whisper uses topics to partition its messages, and these are leveraged for all chat capabilities. In the case of public chats, the channel name maps directly to its Whisper topic. This allows allows anyone to listen on a single channel.

Additionally, since anyone can receive Whisper envelopes, it relies on the ability to decrypt messages to decide who is the correct recipient. We do however not rely on this property, but instead implement another secure transport layer on top of Whisper.

Finally, we use an extension of Whisper to provide the ability to do offline messaging.

Reason

Provide routing, metadata protection, topic-based multicasting and basic encryption properties to support asynchronous chat.

Terminology

  • Whisper node: an Ethereum node with Whisper V6 enabled (in the case of geth, it's --shh option)
  • Whisper network: a group of Whisper nodes connected together through the internet connection and forming a graph
  • Message: decrypted Whisper message
  • Offline message: an archived envelope
  • Envelope: encrypted message with metadata like topic and Time-To-Live

Whisper node configuration

If you want to run a Whisper node and receive messages from Status clients, it must be properly configured.

Whisper's Proof Of Work algorithm is used to deter denial of service and various spam/flood attacks against the Whisper network. The sender of a message must perform some work which in this case means processing time. Because Status' main client is a mobile client, this easily leads to battery draining and poor performance of the app itself. Hence, all clients MUST use the following Whisper node settings:

  • proof-of-work requirement not larger than 0.002
  • time-to-live not lower than 10 (in seconds)

Keys management

The protocol requires a key (symmetric or asymmetric) for the following actions:

  • signing & verifying messages (asymmetric key)
  • encrypting & decrypting messages (asymmetric or symmetric key).

As asymmetric keys and symmetric keys are required to process incoming messages, they must be available all the time and are stored in memory.

Keys management for PFS is described in Perfect forward secrecy section.

The Status protocols uses a few particular Whisper topics to achieve its goals.

Contact code topic

Contact code topic is used to facilitate the discovery of X3DH bundles so that the first message can be PFS-encrypted.

Each user publishes periodically to this topic. If user A wants to contact user B, she SHOULD look for their bundle on this contact code topic.

Contact code topic MUST be created following the algorithm below:

contactCode := "0x" + hexEncode(activePublicKey) + "-contact-code"

var hash []byte = keccak256(name)
var topicLen int = 4

if len(hash) < topicLen {
    topicLen = len(hash)
}

var topic [4]byte
for i = 0; i < topicLen; i++ {
    topic[i] = hash[i]
}

Partitioned topic

Whisper is broadcast-based protocol. In theory, everyone could communicate using a single topic but that would be extremaly inefficient. Opposite would be using a unique topic for each conversation, however, this brings privacy concerns because it would be much easier to detect whether and when two parties have an active conversation.

Partitioned topics are used to broadcast private messages efficiently. By selecting a number of topic, it is possible to balance efficiency and privacy.

Currently, the number of partitioned topics is set to 5000. They MUST be generated following the algorithm below:

var partitionsNum *big.Int = big.NewInt(5000)
var partition *big.Int = big.NewInt(0).Mod(publicKey.X, partitionsNum)

partitionTopic := "contact-discovery-" + strconv.FormatInt(partition.Int64(), 10)

var hash []byte = keccak256(partitionTopic)
var topicLen int = 4

if len(hash) < topicLen {
    topicLen = len(hash)
}

var topic [4]byte
for i = 0; i < topicLen; i++ {
    topic[i] = hash[i]
}

If partitioned topic support is enabled by the Status client, it MUST listen to its paritioned topic. It MUST be generated using the algorithm above and active public key.

Public chats

A public chat MUST use a topic derived from a public chat name following the algorithm below:

var hash []byte
hash = keccak256(name)

topicLen = 4
if len(hash) < topicLen {
    topicLen = len(hash)
}

var topic [4]byte
for i = 0; i < topicLen; i++ {
    topic[i] = hash[i]
}

One-to-one topic

In order to receive one-to-one messages incoming from a public key P, the Status Client MUST listen to a Contact Code Topic created for that public key.

Group chat topic

Group chats does not have a dedicated topic. All group chat messages (including membership updates) are sent as one-to-one messages to multiple recipients.

Message encryption

Even though, the protocol specifies an encryption layer that encrypts messages before passing them to the transport layer, Whisper protocol requires each Whisper message to be encrypted anyway.

Public and group messages are encrypted using symmetric encryption and the key is created from a channel name string. The implementation is available in shh_generateSymKeyFromPassword JSON-RPC method of go-ethereum Whisper implementation.

One-to-one messages are encrypted using asymmetric encryption.

Whisper V6 extensions

Request historic messages

Sends a request for historic messages to a Mailserver. The Mailserver node MUST be a direct peer and MUST be marked as trusted (using shh_markTrustedPeer).

The request does not wait for the response. It merely sends a peer-to-peer message to the Mailserver and it's up to Mailserver to process it and start sending historic messages.

The drawback of this approach is that it is impossible to tell which historic messages are the result of which request.

It's recommended to return messages from newest to oldest. To move further back in time, use cursor and limit.

shhext_requestMessages

Parameters:

  1. Object - The message request object:
    • mailServerPeer - String: Mailserver's enode address.
    • from - Number (optional): Lower bound of time range as unix timestamp, default is 24 hours back from now.
    • to - Number (optional): Upper bound of time range as unix timestamp, default is now.
    • limit - Number (optional): Limit the number of messages sent back, default is no limit.
    • cursor - String (optional): Used for paginated requests.
    • topics - Array: hex-encoded message topics.
    • symKeyID - String: an ID of a symmetric key to authenticate to Mailserver, derived from Mailserver password.

Returns: Boolean - returns true if the request was sent.

The above topics is then converted into a bloom filter and then and sent to the Mailserver.