This specification describes how the payload of each message in Status looks like. It is primarily centered around chat and chat-related use cases.
The payloads aims to be flexible enough to support messaging but also cases described in the Status Whitepaper as well as various clients created using different technologies.
The node wraps all payloads in a protobuf record record:
signature
is the bytes of the signed SHA3-256
of the payload, signed with the key of the author of the message.
The node needs the signature to validate authorship of the message, so that the message can be relayed to third parties.
If a signature is not present, but an author is provided by a layer below, the message is not to be relayed to third parties, and it is considered plausibly deniable.
The node encodes the payload using Protobuf
The type ChatMessage
represents a chat message exchanged between clients.
The protobuf description is:
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | The clock of the chat |
2 | timestamp | uint64 | The sender timestamp at message creation |
3 | text | string | The content of the message |
4 | response_to | string | The ID of the message replied to |
5 | ens_name | string | The ENS name of the user sending the message |
6 | chat_id | string | The local ID of the chat the message is sent to |
7 | message_type | MessageType | The type of message, different for one-to-one, public or group chats |
8 | content_type | ContentType | The type of the content of the message |
9 | payload | Sticker I Image I Audio I DiscordMessage I bytes I nil` | The payload of the message based on the content type |
13 | grant | bytes | Grant for community chat messages |
14 | display_name | string | The message author's display name |
15 | contact_request_propagated_state | ContactRequestPropagatedState | Contact request clock values |
16 | unfurled_links | UnfurledLink[] | Unfurled links and their metadata that have been attached to the message |
A node requires content types for a proper interpretation of incoming messages. Not each message is plain text but may carry different information.
The following content types MUST be supported:
TEXT_PLAIN
identifies a message which content is a plaintext.
There are other content types that MAY be implemented by the client:
STICKER
STATUS
EMOJI
TRANSACTION_COMMAND
IMAGE
AUDIO
COMMUNITY
CONTACT_REQUEST
DISCORD_MESSAGE
IDENTITY_VERIFICATION
A mention MUST be represented as a string with the @0xpk
format, where pk
is the public key of the user account to be mentioned, within the text
field of a message with content_type TEXT_PLAIN
.
A message MAY contain more than one mention.
This specification RECOMMENDs that the application does not require the user to enter the entire pk.
This specification RECOMMENDs that the application allows the user to create a mention by typing @ followed by the related ENS or 3-word pseudonym.
This specification RECOMMENDs that the application provides the user auto-completion functionality to create a mention.
For better user experience, the client SHOULD display a known ens name or the 3-word pseudonym corresponding to the key instead of the pk
.
A ChatMessage
with STICKER
Content/Type
MUST also specify the ID of the Pack
and
the Hash
of the pack, in the Sticker
field of ChatMessage
A ChatMessage
with IMAGE
Content/Type
MUST also specify the payload
of the image
and the type
.
Clients MUST sanitize the payload before accessing its content, in particular:
- Clients MUST choose a secure decoder
- Clients SHOULD strip metadata if present without parsing/decoding it
- Clients SHOULD NOT add metadata/exif when sending an image file for privacy and security reasons
- Clients MUST make sure that the transport layer constraints the size of the payload to limit they are able to handle securely
A ChatMessage
with AUDIO
Content/Type
MUST also specify the payload
of the audio,
the type
and the duration in milliseconds (duration_ms
).
Clients MUST sanitize the payload before accessing its content, in particular:
- Clients MUST choose a secure decoder
- Clients SHOULD strip metadata if present without parsing/decoding it
- Clients SHOULD NOT add metadata/exif when sending an audio file for privacy and security reasons
- Clients MUST make sure that the transport layer constraints the size of the payload to limit they are able to handle securely
A ChatMessage
with COMMUNITY
Content/Type
MUST also specify the payload
of the community as bytes from a CommunityDescription.
A ChatMessage
with DISCORD_MESSAGE
Content/Type
MUST also specify the payload
of the DiscordMessage
.
A node requires message types to decide how to encrypt a particular message and what metadata needs to be attached when passing a message to the transport layer. For more on this, see 10/WAKU2.
The following messages types MUST be supported:
ONE_TO_ONE
is a message to the public groupPUBLIC_GROUP
is a private messagePRIVATE_GROUP
is a message to the private group.COMMUNITY_CHAT
is a message to a community channe.
If a user sends a new message before the messages sent while the user was offline are received, the new message is supposed to be displayed last in a chat. This is where the basic algorithm of Lamport timestamp would fall short as it's only meant to order causally related events.
The status client therefore makes a "bid", speculating that it will beat the current chat-timestamp, s.t. the status client's Lamport timestamp format is: clock = max({timestamp}, chat_clock + 1)
This will satisfy the Lamport requirement, namely: a -> b then T(a) < T(b)
timestamp
MUST be Unix time calculated, when the node creates the message, in milliseconds.
This field SHOULD not be relied upon for message ordering.
clock
SHOULD be calculated using the algorithm of Lamport timestamps.
When there are messages available in a chat, the node calculates clock
's value based on the last received message in a particular chat: max(timeNowInMs, last-message-clock-value + 1)
.
If there are no messages, clock
is initialized with timestamp
's value.
Messages with a clock
greater than 120
seconds over the Whisper/Waku timestamp SHOULD be discarded, in order to avoid malicious users to increase the clock
of a chat arbitrarily.
Messages with a clock
less than 120
seconds under the Whisper/Waku timestamp might indicate an attempt to insert messages in the chat history which is not distinguishable from a datasync
layer re-transit event.
A client MAY mark this messages with a warning to the user, or discard them.
The node uses clock
value for the message ordering. The algorithm used, and the distributed nature of the system produces casual ordering, which might produce counter-intuitive results in some edge cases.
For example, when a user joins a public chat and sends a message before receiving the exist messages, their message clock
value might be lower and the message will end up in the past when the historical messages are fetched.
Chat is a structure that helps organize messages. It's usually desired to display messages only from a single recipient, or a group of recipients at a time and chats help to achieve that.
All incoming messages can be matched against a chat. The below table describes how to calculate a chat ID for each message type.
Message Type | Chat ID Calculation | Direction | Comment |
---|---|---|---|
PUBLIC_GROUP | chat ID is equal to a public channel name; it should equal chatId from the message | Incoming/Outgoing | |
ONE_TO_ONE | let P be a public key of the recipient; hex-encode(P) is a chat ID; use it as chatId value in the message | Outgoing | |
user-message | let P be a public key of message's signature; hex-encode(P) is a chat ID; discard chat-id from message | Incoming | if there is no matched chat, it might be the first message from public key P ; the node MAY discard the message or MAY create a new chat; Status official clients create a new chat |
PRIVATE_GROUP | use chatId from the message | Incoming/Outgoing | find an existing chat by chatId ; if none is found, the user is not a member of that chat or the user hasn't joined that chat, the message MUST be discarded |
COMMUNITY_CHAT | use chatId from the message | Incoming/Outgoing | find existing community chat within a community based on chatId ; the chatId includes a community id prefix |
ContactUpdate
is a message exchange to notify peers that either the user has been added as a contact, or that information about the sending user have changed.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | The clock of the chat with the user |
2 | ens_name | string | The ENS name if set |
3 | profile_image | string | The base64 encoded profile picture of the user |
4 | display_name | string | The display name of the user |
5 | contact_request_clock | uint64 | The clock at which a contact request was sent |
6 | contact_request_propagated_state | ContactRequestPropagatedState | Additional propagation clock values |
7 | public_key | string | The public key of the user |
A client SHOULD send a ContactUpdate
to all the contacts each time:
- The ens_name has changed
- A user edits the profile image
A client SHOULD also periodically send a ContactUpdate
to all the contacts, the interval is up to the client, the Status official client sends these updates every 48 hours.
EmojiReaction
s represents a user's "reaction" to a specific chat message.
For more information about the concept of emoji reactions see Facebook Reactions.
This specification RECOMMENDS that the UI/UX implementation of sending EmojiReactions
requires only a single click operation, as users have an expectation that emoji reactions are effortless and simple to perform.
Clients MUST specify clock
, chat_id
, message_id
, type
and message_type
.
This specification RECOMMENDS that the UI/UX implementation of retracting an EmojiReaction
s requires only a single click operation, as users have an expectation that emoji reaction removals are effortless and simple to perform.
MembershipUpdateEvent
is a message used to propagate information about group membership changes in a group chat.
The details are in the Group chats specs.
Field | Name | Type | Description |
---|---|---|---|
1 | chat_id | string | The chat id of the private group chat |
2 | events | bytes[] | The list of events for this group chat |
A MembershipUpdateMessage
includes either a ChatMessage
or EmojiReaction
.
The node uses SyncInstallationContact
messages to synchronize in a best-effort the contacts to other devices.
Field | Name | Type | Description |
---|---|---|---|
1 | last_updated_locally | uint64 | Timestamp of last local update |
2 | id | string | id of the contact synced |
3 | profile_image | string | base64 encoded profile picture of the user |
4 | ens_name | string | ENS name of the contact |
5 | array[string] | Array of system_tags for the user, this can currently be: ":contact/added", ":contact/blocked", ":contact/request-received" | |
7 | local_nickname | string | Local display name of the contact |
9 | added | bool | Wether the contact is added |
10 | blocked | bool | Wether the contact is blocked |
11 | muted | bool | Wether the contact is muted |
12 | removed | bool | Wether the contact is removed |
13 | has_added_us | bool | Wether the contact has added us |
14 | verification_status | int64 | The verification status of the contact |
15 | trust_status | int64 | The trust status of the contact |
16 | contact_request_local_state | int64 | The local contact request state |
17 | contact_request_local_clock | int64 | The local contact request clock |
18 | contact_request_remote_state | int64 | The remote contact request state |
19 | contact_request_remote_clock | int64 | The remote contact request clock |
The node uses SyncInstallationPublicChat
message to synchronize in a best-effort the public chats to other devices.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | clock value of the chat |
2 | id | string | id of the chat synced |
The node uses PairInstallation
messages to propagate information about a device to its paired devices.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | clock value of the chat |
2 | installation_id | string | A randomly generated id that identifies this device |
3 | device_type | string | The OS of the device ios ,android or desktop |
4 | name | string | The self-assigned name of the device |
ChatIdentity
represents the user defined identity associated with their public chat key.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | ens_name | string | A valid ENS associated with the chat key |
3 | images | map<string, IdentityImage> | Image data associated with the chat key |
4 | display_name | string | The self-assigned display_name of the chat key |
5 | description | string | The description of the chat |
6 | color | string | The color of the chat |
7 | emoji | string | The emoji of the chat |
8 | social_links | array<SocialLink> | A list of links to social platforms |
9 | first_message_timestamp | uint32 | First known message timestamp in seconds |
CommunityDescription
represents a community metadata that is used to discover communities and share community updates.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | members | map<string, CommunityMember> | The members of the community |
3 | permissions | CommunityPermissions | Image data associated with the chat key |
4 | display_name | string | The self-assigned display_name of the chat key |
5 | description | string | The description of the chat |
6 | color | string | The color of the chat |
7 | emoji | string | The emoji of the chat |
8 | social_links | array<SocialLink> | A list of links to social platforms |
9 | first_message_timestamp | uint32 | First known message timestamp in seconds |
A CommunityRequestToJoin
represents a request to join a community, sent by a user that is not yet a member of that community.
A request to join a community includes a list of RevealedAccount
.
These are wallet addresses that users are willing to reveal with the community's control node and admins.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | ens_name | string | The ENS of the user sending the request |
3 | chat_id | string | The id of the chat to request access to |
4 | community_id | bytes | The public key of the community |
5 | display_name | string | The display name of the usre sending the request |
6 | revealed_accounts | array<RevealedAccount> | A list of accounts to reveal to the control node |
A PinMessage
is a signal that tells a client that a specific message has to be marked as pinned.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | message_id | string | The id of the message to be pinned |
3 | chat_id | string | The id of the chat of the message to be pinned |
4 | pinned | bool | Whether the message should be pinned or unpinned |
5 | message_type | MessageType | The type of message (public/one-to-one/private-group-chat) |
A EditMessage
represents an update to an existing message.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | text | string | The updated message text |
3 | chat_id | string | The id of the chat of the message |
4 | message_id | string | The id of the message to be edited |
5 | grant | bytes | A grant for a community edit messages |
6 | message_type | MessageType | The type of message |
7 | content_type | ChatMessage.ContentType | The updated content type of the message |
8 | unfurled_links | array<UnfurledLink> | Updated link metadata |
A DeleteMessage
represents a signal to delete a message from the local database of a client.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | chat_id | string | The id of the chat of the message |
3 | message_id | string | The id of the message to delete |
4 | grant | bytes | A grant for a community edit messages |
5 | message_type | MessageType | The type of message |
6 | deleted_by | string | The public key of the user who deleted the message |
A CommunityMessageArchiveLink
contains a magnet uri for a community's message archive, created using 61/STATUS-Community-History-Archives.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | magnet_uri | string | The magnet uri of the community archive torrent |
A AcceptContractRequest
message signals to the requester that the request was accepted.
Field | Name | Type | Description |
---|---|---|---|
1 | id | string | The id of the contact request |
2 | clock | uint64 | Clock value of the message |
A RetractContractRequest
message signals to the reiver of a request that the request was retracted.
Field | Name | Type | Description |
---|---|---|---|
1 | id | string | The id of the contact request |
2 | clock | uint64 | Clock value of the message |
A CommunityRequestToJoinResponse
represents a response to a request to join a community.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | community | CommunityDescription | The community metadata |
3 | accepted | bool | Whether the request was accepted |
4 | grant | bytes | The grant |
5 | community_id | bytes | The id of the community |
6 | magnet_uri | string | The latest magnet uri of the community's archive torrent |
A CommunityRequestToLeave
represents a signal to a community that a user wants to be removed from the community's member list.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | community_id | bytes | The id of the community |
A RequestContactVerification
is a request to verify a contact using a "challenge", which can by any string message and typically involves questions that only the contact should know.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | challenge | string | The challenge message used for verification |
A AcceptContactVerification
signals that a verification request was accepted and includes a response to the challenge.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | id | string | The verification request id |
3 | response | string | The response for the challenge |
A DeclineContactVerification
declines a contact verification request.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | id | string | The verification request id |
A CancelContactVerification
cancels a contact verification request.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | id | string | The verification request id |
A CommunityCancelRequestToJoin
cancels a pending request to join.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | ens_name | string | The ENS name of the account cancelling the request |
3 | chat_id | string | The id of the chat |
4 | community_id | bytes | The id of the community |
5 | display_name | string | The display name of the account cancelling the request |
A CommunityEditSharedAddresses
message allows users to edit the shared accounts they've revealed when requesting to join a community.
Field | Name | Type | Description |
---|---|---|---|
1 | clock | uint64 | Clock value of the message |
2 | community_id | bytes | The id of the community |
3 | revealed_accounts | array<RevealedAccount> | A list of revealed accounts |
There are two ways to upgrade the protocol without breaking compatibility:
- A node always supports accretion
- A node does not support deletion of existing fields or messages, which might break compatibility
Released August 25, 2020
- Added support for emoji reactions
Released July 16, 2020
- Added support for images
- Added support for audio
Released May 22, 2020
- Added language to include Waku in all relevant places
Copyright and related rights waived via CC0.