Skip to content

Commit 85ee868

Browse files
committed
feat(specification): add Content Format V3 and Payload Format V3 with bitmap field presence
1 parent 4be6639 commit 85ee868

1 file changed

Lines changed: 129 additions & 5 deletions

File tree

docs/specification.md

Lines changed: 129 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
- [Content Format V0](#content-format-v0)
77
- [Content Format V1](#content-format-v1)
88
- [Content Format V2](#content-format-v2)
9+
- [Content Format V3](#content-format-v3)
910
- [Payload Format](#supported-payload-versions)
1011
- [Payload Format V0](#payload-format-v0)
1112
- [Payload Format V1](#payload-format-v1)
1213
- [Payload Format V2](#payload-format-v2)
14+
- [Payload Format V3](#payload-format-v3)
1315

1416
## Content Format
1517

@@ -182,13 +184,87 @@
182184
- **2 byte**: Length of `refresh_token` field (set to `0`).
183185
- **Variable**: Value of `from` field (considered the test ID).
184186

187+
### Content Format V3
188+
189+
> [!NOTE]
190+
>
191+
> For detailed instructions on encrypting the content format using the Double Ratchet algorithm, refer to the [smswithoutborders_lib_sig documentation](https://github.com/smswithoutborders/lib_signal_double_ratchet_python?tab=readme-ov-file#double-ratchet-implementations).
192+
193+
> [!NOTE]
194+
>
195+
> This format uses a bitmap to indicate which fields are present, making it more efficient than previous versions.
196+
>
197+
> **All 2-byte length fields are encoded as unsigned little-endian.**
198+
199+
#### Field Bitmap Definition
200+
201+
The bitmap is a single byte where each bit represents the presence of a specific field:
202+
203+
- **Bit 0 (0x01)**: `from` field
204+
- **Bit 1 (0x02)**: `to` field
205+
- **Bit 2 (0x04)**: `cc` field
206+
- **Bit 3 (0x08)**: `bcc` field
207+
- **Bit 4 (0x10)**: `subject` field
208+
- **Bit 5 (0x20)**: `body` field
209+
- **Bit 6 (0x40)**: `access_token` field
210+
- **Bit 7 (0x80)**: `refresh_token` field
211+
212+
1. **Email format**: Binary-encoded fields with the following structure:
213+
214+
- **1 byte**: Field presence bitmap
215+
- **1 byte**: Length of `from` field (if bit 0 is set)
216+
- **2 bytes**: Length of `to` field (if bit 1 is set)
217+
- **2 bytes**: Length of `cc` field (if bit 2 is set)
218+
- **2 bytes**: Length of `bcc` field (if bit 3 is set)
219+
- **1 byte**: Length of `subject` field (if bit 4 is set)
220+
- **2 bytes**: Length of `body` field (if bit 5 is set)
221+
- **2 bytes**: Length of `access_token` field (if bit 6 is set)
222+
- **2 bytes**: Length of `refresh_token` field (if bit 7 is set)
223+
- **Variable**: Value of `from` field (if present)
224+
- **Variable**: Value of `to` field (if present)
225+
- **Variable**: Value of `cc` field (if present)
226+
- **Variable**: Value of `bcc` field (if present)
227+
- **Variable**: Value of `subject` field (if present)
228+
- **Variable**: Value of `body` field (if present)
229+
- **Variable**: Value of `access_token` field (if present)
230+
- **Variable**: Value of `refresh_token` field (if present)
231+
232+
2. **Text format**: Binary-encoded fields with the following structure:
233+
234+
- **1 byte**: Field presence bitmap
235+
- **1 byte**: Length of `from` field (if bit 0 is set)
236+
- **2 bytes**: Length of `body` field (if bit 5 is set)
237+
- **2 bytes**: Length of `access_token` field (if bit 6 is set)
238+
- **2 bytes**: Length of `refresh_token` field (if bit 7 is set)
239+
- **Variable**: Value of `from` field (if present)
240+
- **Variable**: Value of `body` field (if present)
241+
- **Variable**: Value of `access_token` field (if present)
242+
- **Variable**: Value of `refresh_token` field (if present)
243+
244+
3. **Message format**: Binary-encoded fields with the following structure:
245+
246+
- **1 byte**: Field presence bitmap
247+
- **1 byte**: Length of `from` field (if bit 0 is set)
248+
- **2 bytes**: Length of `to` field (if bit 1 is set)
249+
- **2 bytes**: Length of `body` field (if bit 5 is set)
250+
- **Variable**: Value of `from` field (if present)
251+
- **Variable**: Value of `to` field (if present)
252+
- **Variable**: Value of `body` field (if present)
253+
254+
4. **Test format**: Binary-encoded fields with the following structure:
255+
256+
- **1 byte**: Field presence bitmap
257+
- **1 byte**: Length of `from` field (if bit 0 is set)
258+
- **Variable**: Value of `from` field (considered the test ID, if present)
259+
185260
## Supported Payload Versions
186261

187-
| **Version** | **Hexadecimal Value** | **Decimal Value** | **Description** |
188-
| ------------------------ | --------------------- | ----------------- | ----------------------------------------------------------- |
189-
| [v0](#payload-format-v0) | `None` | `None` | No explicit version marker, backward-compatible formats. |
190-
| [v1](#payload-format-v1) | `0x01` | `1` | Includes a version marker as the first byte of the payload. |
191-
| [v2](#payload-format-v1) | `0x02` | `2` | Includes a version marker as the first byte of the payload. |
262+
| **Version** | **Hexadecimal Value** | **Decimal Value** | **Description** |
263+
| ------------------------ | --------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
264+
| [v0](#payload-format-v0) | `None` | `None` | Legacy format with 4-byte ciphertext length and no version marker. Device ID has variable length without explicit length field. |
265+
| [v1](#payload-format-v1) | `0x01` | `1` | Introduces version marker, 2-byte ciphertext length, explicit device ID length field, and language code support. Uses Content Format V1 with 1-byte token length fields. |
266+
| [v2](#payload-format-v2) | `0x02` | `2` | Same payload structure as V1 but uses Content Format V2 with 2-byte token length fields for better support of longer access/refresh tokens. |
267+
| [v3](#payload-format-v3) | `0x03` | `3` | Same payload structure as V1/V2 but uses Content Format V3 with bitmap-based field presence indication for more efficient encoding. |
192268

193269
## Payload Format V0
194270

@@ -320,3 +396,51 @@ payload = (
320396
encoded = base64.b64encode(payload).decode("utf-8")
321397
print(encoded)
322398
```
399+
400+
## Payload Format V3
401+
402+
> [See available versions](#supported-payload-versions)
403+
404+
### Message Payload
405+
406+
- **Format**:
407+
- **1 byte**: Version Marker. [See available versions](#supported-payload-versions).
408+
- **2 bytes**: Ciphertext Length.
409+
- **1 byte**: Device ID Length.
410+
- **1 byte**: Platform shortcode.
411+
- **Variable**: Ciphertext. (encrypted [Content Format V3](#content-format-v3)).
412+
- **Variable**: Device ID.
413+
- **2 bytes**: Language Code (ISO 639-1 format).
414+
415+
> [!NOTE]
416+
>
417+
> For detailed instructions on using the Double Ratchet algorithm to create ciphertext, refer to the [smswithoutborders_lib_sig documentation](https://github.com/smswithoutborders/lib_signal_double_ratchet_python?tab=readme-ov-file#double-ratchet-implementations).
418+
419+
#### Visual Representation:
420+
421+
```plaintext
422+
+----------------+-------------------+------------------+--------------------+-----------------+-----------------+---------------+
423+
| Version Marker | Ciphertext Length | Device ID Length | Platform shortcode | Ciphertext | Device ID | Language Code |
424+
| (1 byte) | (2 bytes) | (1 byte) | (1 byte) | (Variable size) | (Variable size) | (2 bytes) |
425+
+----------------+-------------------+------------------+--------------------+-----------------+-----------------+---------------+
426+
```
427+
428+
```python
429+
version_marker = b'\x03'
430+
platform_shortcode = b'g'
431+
language_code = b'en'
432+
device_id = b'...'
433+
encrypted_content = b'...'
434+
435+
payload = (
436+
version_marker +
437+
struct.pack("<H", len(encrypted_content)) +
438+
bytes([len(device_id)]) +
439+
platform_shortcode +
440+
encrypted_content +
441+
device_id +
442+
language_code
443+
)
444+
encoded = base64.b64encode(payload).decode("utf-8")
445+
print(encoded)
446+
```

0 commit comments

Comments
 (0)