|
6 | 6 | - [Content Format V0](#content-format-v0) |
7 | 7 | - [Content Format V1](#content-format-v1) |
8 | 8 | - [Content Format V2](#content-format-v2) |
| 9 | + - [Content Format V3](#content-format-v3) |
9 | 10 | - [Payload Format](#supported-payload-versions) |
10 | 11 | - [Payload Format V0](#payload-format-v0) |
11 | 12 | - [Payload Format V1](#payload-format-v1) |
12 | 13 | - [Payload Format V2](#payload-format-v2) |
| 14 | + - [Payload Format V3](#payload-format-v3) |
13 | 15 |
|
14 | 16 | ## Content Format |
15 | 17 |
|
|
182 | 184 | - **2 byte**: Length of `refresh_token` field (set to `0`). |
183 | 185 | - **Variable**: Value of `from` field (considered the test ID). |
184 | 186 |
|
| 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 | + |
185 | 260 | ## Supported Payload Versions |
186 | 261 |
|
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. | |
192 | 268 |
|
193 | 269 | ## Payload Format V0 |
194 | 270 |
|
@@ -320,3 +396,51 @@ payload = ( |
320 | 396 | encoded = base64.b64encode(payload).decode("utf-8") |
321 | 397 | print(encoded) |
322 | 398 | ``` |
| 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