diff --git a/packages/status-js/protos/protocol-message.proto b/packages/status-js/protos/protocol-message.proto new file mode 100644 index 00000000..3df17360 --- /dev/null +++ b/packages/status-js/protos/protocol-message.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +message SignedPreKey { + bytes signed_pre_key = 1; + uint32 version = 2; + uint32 protocol_version = 3; +} + +// X3DH prekey bundle +message Bundle { + // Identity key + bytes identity = 1; + // Installation id + map signed_pre_keys = 2; + // Prekey signature + bytes signature = 4; + + // When the bundle was created locally + int64 timestamp = 5; +} + +message BundleContainer { + reserved 3; + // X3DH prekey bundle + Bundle bundle = 1; + // Private signed prekey + bytes private_signed_pre_key = 2; +} + +message DRHeader { + // Current ratchet public key + bytes key = 1; + // Number of the message in the sending chain + uint32 n = 2; + // Length of the previous sending chain + uint32 pn = 3; + // Bundle ID + bytes id = 4; +} + +message DHHeader { + // Compressed ephemeral public key + bytes key = 1; +} + +message X3DHHeader { + reserved 3; + // Ephemeral key used + bytes key = 1; + // Used bundle's signed prekey + bytes id = 4; +} + +// Hash Ratchet Header +message HRHeader { + // community key ID + uint32 key_id = 1; + // Community message number for this key_id + uint32 seq_no = 2; + // Community ID + string group_id = 3; +} + +// Direct message value +message EncryptedMessageProtocol { + X3DHHeader X3DH_header = 1; + DRHeader DR_header = 2; + DHHeader DH_header = 101; + HRHeader HR_header = 102; + // Encrypted payload + bytes payload = 3; +} + +// Top-level protocol message +message ProtocolMessage { + // The device id of the sender + string installation_id = 2; + + // List of bundles + repeated Bundle bundles = 3; + + // One to one message, encrypted, indexed by installation_id + // TODO map here is redundant in case of community messages + map encrypted_message = 101; + + // Public chats, not encrypted + bytes public_message = 102; +} diff --git a/packages/status-js/protos/protocol-message.ts b/packages/status-js/protos/protocol-message.ts new file mode 100644 index 00000000..02b2562b --- /dev/null +++ b/packages/status-js/protos/protocol-message.ts @@ -0,0 +1,223 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, bytes, uint32, int64, string } from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface SignedPreKey { + signedPreKey: Uint8Array + version: number + protocolVersion: number +} + +export namespace SignedPreKey { + export const codec = (): Codec => { + return message({ + 1: { name: 'signedPreKey', codec: bytes }, + 2: { name: 'version', codec: uint32 }, + 3: { name: 'protocolVersion', codec: uint32 } + }) + } + + export const encode = (obj: SignedPreKey): Uint8Array => { + return encodeMessage(obj, SignedPreKey.codec()) + } + + export const decode = (buf: Uint8Array): SignedPreKey => { + return decodeMessage(buf, SignedPreKey.codec()) + } +} + +export interface Bundle { + identity: Uint8Array + signedPreKeys: SignedPreKey + signature: Uint8Array + timestamp: bigint +} + +export namespace Bundle { + export const codec = (): Codec => { + return message({ + 1: { name: 'identity', codec: bytes }, + 2: { name: 'signedPreKeys', codec: SignedPreKey.codec() }, + 4: { name: 'signature', codec: bytes }, + 5: { name: 'timestamp', codec: int64 } + }) + } + + export const encode = (obj: Bundle): Uint8Array => { + return encodeMessage(obj, Bundle.codec()) + } + + export const decode = (buf: Uint8Array): Bundle => { + return decodeMessage(buf, Bundle.codec()) + } +} + +export interface BundleContainer { + bundle: Bundle + privateSignedPreKey: Uint8Array +} + +export namespace BundleContainer { + export const codec = (): Codec => { + return message({ + 1: { name: 'bundle', codec: Bundle.codec() }, + 2: { name: 'privateSignedPreKey', codec: bytes } + }) + } + + export const encode = (obj: BundleContainer): Uint8Array => { + return encodeMessage(obj, BundleContainer.codec()) + } + + export const decode = (buf: Uint8Array): BundleContainer => { + return decodeMessage(buf, BundleContainer.codec()) + } +} + +export interface DRHeader { + key: Uint8Array + n: number + pn: number + id: Uint8Array +} + +export namespace DRHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes }, + 2: { name: 'n', codec: uint32 }, + 3: { name: 'pn', codec: uint32 }, + 4: { name: 'id', codec: bytes } + }) + } + + export const encode = (obj: DRHeader): Uint8Array => { + return encodeMessage(obj, DRHeader.codec()) + } + + export const decode = (buf: Uint8Array): DRHeader => { + return decodeMessage(buf, DRHeader.codec()) + } +} + +export interface DHHeader { + key: Uint8Array +} + +export namespace DHHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes } + }) + } + + export const encode = (obj: DHHeader): Uint8Array => { + return encodeMessage(obj, DHHeader.codec()) + } + + export const decode = (buf: Uint8Array): DHHeader => { + return decodeMessage(buf, DHHeader.codec()) + } +} + +export interface X3DHHeader { + key: Uint8Array + id: Uint8Array +} + +export namespace X3DHHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes }, + 4: { name: 'id', codec: bytes } + }) + } + + export const encode = (obj: X3DHHeader): Uint8Array => { + return encodeMessage(obj, X3DHHeader.codec()) + } + + export const decode = (buf: Uint8Array): X3DHHeader => { + return decodeMessage(buf, X3DHHeader.codec()) + } +} + +export interface HRHeader { + keyId: number + seqNo: number + groupId: string +} + +export namespace HRHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'keyId', codec: uint32 }, + 2: { name: 'seqNo', codec: uint32 }, + 3: { name: 'groupId', codec: string } + }) + } + + export const encode = (obj: HRHeader): Uint8Array => { + return encodeMessage(obj, HRHeader.codec()) + } + + export const decode = (buf: Uint8Array): HRHeader => { + return decodeMessage(buf, HRHeader.codec()) + } +} + +export interface EncryptedMessageProtocol { + X3DHHeader: X3DHHeader + DRHeader: DRHeader + DHHeader: DHHeader + HRHeader: HRHeader + payload: Uint8Array +} + +export namespace EncryptedMessageProtocol { + export const codec = (): Codec => { + return message({ + 1: { name: 'X3DHHeader', codec: X3DHHeader.codec() }, + 2: { name: 'DRHeader', codec: DRHeader.codec() }, + 101: { name: 'DHHeader', codec: DHHeader.codec() }, + 102: { name: 'HRHeader', codec: HRHeader.codec() }, + 3: { name: 'payload', codec: bytes } + }) + } + + export const encode = (obj: EncryptedMessageProtocol): Uint8Array => { + return encodeMessage(obj, EncryptedMessageProtocol.codec()) + } + + export const decode = (buf: Uint8Array): EncryptedMessageProtocol => { + return decodeMessage(buf, EncryptedMessageProtocol.codec()) + } +} + +export interface ProtocolMessage { + installationId: string + bundles: Bundle[] + encryptedMessage: EncryptedMessageProtocol + publicMessage: Uint8Array +} + +export namespace ProtocolMessage { + export const codec = (): Codec => { + return message({ + 2: { name: 'installationId', codec: string }, + 3: { name: 'bundles', codec: Bundle.codec(), repeats: true }, + 101: { name: 'encryptedMessage', codec: EncryptedMessageProtocol.codec() }, + 102: { name: 'publicMessage', codec: bytes } + }) + } + + export const encode = (obj: ProtocolMessage): Uint8Array => { + return encodeMessage(obj, ProtocolMessage.codec()) + } + + export const decode = (buf: Uint8Array): ProtocolMessage => { + return decodeMessage(buf, ProtocolMessage.codec()) + } +}