Skip to content

Commit 28ac373

Browse files
authored
Update COLDWIRE_PROTOCOL.md
1 parent 3c86464 commit 28ac373

File tree

1 file changed

+68
-4
lines changed

1 file changed

+68
-4
lines changed

COLDWIRE_PROTOCOL.md

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ If you rotate header sets between different requests, that creates a unique fing
6767
#### 2.2. Federated servers headers
6868
`Coldwire` server implementations *must* adhere to the following requirements for `HTTP` requests:
6969
- You **must not** include any headers that may indicate to a server you're intending to receive compressed (gzip, etc) response!
70+
71+
- Your `Coldwire` server implementation **must not** return any compressed responses. Return responses as raw uncompressed bytes.
7072

7173
- *All* `HTTP` field headers name **must** be lower-case, for compatiability with `HTTP/2` servers.
7274
The HTTP specification requires it, needed incase a Coldwire server implementation is behind another server.
@@ -231,12 +233,11 @@ The `Strandlock protocol` can be quite heavy (some ciphertext reaching MBs in si
231233
### 5. Federation
232234
Federation protocol between different `Coldwire` servers.
233235

234-
All `Coldwire` servers must have a long-term `ML-DSA-87` keypair.
235-
236-
All requests payloads and responses are returned in `JSON` format.
237-
236+
All `Coldwire` servers must have a long-term `ML-DSA-87` keypair saved securely, locally.
238237

239238
#### 5.1. Federation Info
239+
All requests payloads and responses are sent and returned in `JSON` format.
240+
240241
When a `Coldwire` server (`server A`) process a request from another `Coldwire` server (`server B`), `server A` checks if they have `server B` public-key saved, if not, they fetch it by sending a `GET` request to the following endpoint:
241242
```
242243
URL: example.com/federation/info
@@ -263,3 +264,66 @@ response = server_url_utf_8 + refetch_date_utf_8
263264
After `server A` receives the response from `server B`, they verify the signature. If valid, they save the `public_key` and `refetch_date` alongside `server B`'s` URL.
264265

265266

267+
#### 5.2. Federation send
268+
When `Alice` who is using a Coldwire server (`server A`) sends `Bob` a request who is using another Coldwire server (`server B`), `server A` constructs a `Form` payload with field `metadata` and a `File Upload` with file name of `blob`.
269+
270+
The `metadata` field payload data:
271+
```
272+
{
273+
"recipient": "recipient 16-digits User-ID, no URL",
274+
"sender": "sender 16-digits User-ID, no URL",
275+
"url": "server_A URL with no HTTP/S prefixes."
276+
}
277+
```
278+
279+
`server A` also creates a `ML-DSA-87- signature with following data:
280+
```
281+
signature = create_signature(ML_DSA_87_NAME, url.encode("utf-8") + recipient.encode("utf-8") + sender.encode("utf-8") + blob)
282+
```
283+
284+
The `blob` field payload data:
285+
```
286+
blob_payload = signature + blob
287+
```
288+
`blob` being the ciphertext `Alice` is sending to `Bob.
289+
290+
291+
`server B` receives the request, processes it by doing sanity checks against the provided User-IDs (i.e., are they correct format, etc), and sanity checks against provided `url` (is it valid domain and or IP, etc), and it checks if `recipient` exists in the database. If any of checks failed, `server B` must return a 40x error code to `server A`.
292+
293+
294+
`server B` then checks if they have `server A` public-key saved, if not, they fetch and save it (see `5.1. Federation Info`).
295+
`server B` then checks the saved `server A's` refetch_date, if the date is due (=< today), `server B` refetches `server A` public-key.
296+
297+
298+
If all the previous checks and operations succeed, `server B` separates the `signature` from the `blob`:
299+
```
300+
signature = blob[:ML_DSA_87_SIGN_LEN]
301+
```
302+
And sets blob:
303+
```
304+
blob = blob[ML_DSA_87_SIGN_LEN:]
305+
```
306+
`ML_DSA_87_SIGN_LEN` being the signature length that `ML-DSA-87` produces (`4627 bytes`)
307+
308+
Then, `server B` checks signature using `server A's` public-key.
309+
If not valid, `server B` returns a 40x error code.
310+
311+
If valid:
312+
`server B` adds `url` to `sender`, separated by "`@`", then UTF-8 encoding it:
313+
```
314+
sender_with_url_utf_8 = sender + "@" + url
315+
sender_with_url_utf_8 = sender_with_url.encode("utf-8")
316+
```
317+
318+
`server B` then checks if there's a NULL byte in `sender_with_url`, if there is, abort process, and return `40x` status code.
319+
320+
If all checks pass, `server B` stores the data in same way described in `4.2. Data processor (Server)`:
321+
The `Coldwire` server then process the data by constructing a payload which consists of:
322+
```
323+
payload = sender_with_url_utf_8 + \x0 + blob
324+
```
325+
Then the length of the payload is calculated, and a length prefix of size `3 bytes` in `big-endian` format is inserted at the start of the payload:
326+
```
327+
payload = length_prefix + payload
328+
```
329+
And the data is saved to the recipient inbox (any saving medium, can be Redis, SQL database, etc).

0 commit comments

Comments
 (0)