refactor: structured body (array|object, never string) for canonicalization
Message bodies are always arrays of strings (text lines) or objects (structured data like PUBKEY). Never raw strings. This enables: - Multiline messages without escape sequences - Deterministic JSON canonicalization (RFC 8785 JCS) for signing - Structured data where needed Update all schemas: body fields use array type with string items. Update message.json envelope: body is oneOf[array, object], id is UUID. Update README: message envelope table, examples, and canonicalization docs. Update schema/README.md: field types, examples with array bodies.
This commit is contained in:
139
README.md
139
README.md
@@ -158,86 +158,109 @@ Every message is a JSON object with these fields:
|
||||
| `from` | string | | Sender nick or server name |
|
||||
| `to` | string | | Destination: `#channel` or nick |
|
||||
| `params` | array\<string\> | | Additional IRC-style parameters |
|
||||
| `body` | array \| object | | Structured body (never a raw string) |
|
||||
| `meta` | object | | Extensible metadata (signatures, etc.) |
|
||||
| `id` | string (uuid) | | Server-assigned message ID |
|
||||
| `body` | array \| object | | Structured body (never a raw string — see below) |
|
||||
| `id` | string (uuid) | | Server-assigned message UUID |
|
||||
| `ts` | string | | Server-assigned ISO 8601 timestamp |
|
||||
| `meta` | object | | Extensible metadata (signatures, hashes, etc.) |
|
||||
|
||||
**Important:** Message bodies MUST be objects or arrays, never raw strings.
|
||||
This enables:
|
||||
- Multiline messages (array of lines)
|
||||
- Deterministic canonicalization for hashing/signing (RFC 8785 JCS)
|
||||
- Structured data where needed (e.g. PUBKEY)
|
||||
**Important:** Message bodies are **structured objects or arrays**, never raw
|
||||
strings. This is a deliberate departure from IRC wire format that enables:
|
||||
|
||||
- **Multiline messages** — body is a list of lines, no escape sequences
|
||||
- **Deterministic canonicalization** — for hashing and signing (see below)
|
||||
- **Structured data** — commands like PUBKEY carry key material as objects
|
||||
|
||||
For text messages, `body` is an array of strings (one per line):
|
||||
|
||||
```json
|
||||
{"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": ["hello world"]}
|
||||
{"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": ["line one", "line two"]}
|
||||
```
|
||||
|
||||
For numeric replies with text trailing parameters:
|
||||
|
||||
```json
|
||||
{"command": "001", "to": "nick", "body": ["Welcome to the network, nick"]}
|
||||
{"command": "353", "to": "nick", "params": ["=", "#channel"], "body": ["@op1 alice bob"]}
|
||||
```
|
||||
|
||||
For structured data (keys, etc.), `body` is an object:
|
||||
|
||||
```json
|
||||
{"command": "PUBKEY", "from": "nick", "body": {"alg": "ed25519", "key": "base64..."}}
|
||||
```
|
||||
|
||||
#### IRC Command Mapping
|
||||
|
||||
**Client-to-Server (C2S):**
|
||||
**Commands (C2S and S2C):**
|
||||
|
||||
| Command | Description |
|
||||
|----------|-------------|
|
||||
| PRIVMSG | Send message to channel or user |
|
||||
| NOTICE | Send notice (no auto-reply expected) |
|
||||
| JOIN | Join a channel (creates it if nonexistent) |
|
||||
| PART | Leave a channel |
|
||||
| QUIT | Disconnect from server |
|
||||
| NICK | Change nickname |
|
||||
| MODE | Set/query channel or user modes |
|
||||
| TOPIC | Set/query channel topic |
|
||||
| KICK | Kick a user from a channel |
|
||||
| PING | Client keepalive |
|
||||
| PUBKEY | Announce public signing key |
|
||||
| Command | RFC | Description |
|
||||
|-----------|--------------|--------------------------------------|
|
||||
| `PRIVMSG` | 1459 §4.4.1 | Message to channel or user |
|
||||
| `NOTICE` | 1459 §4.4.2 | Notice (must not trigger auto-reply) |
|
||||
| `JOIN` | 1459 §4.2.1 | Join a channel |
|
||||
| `PART` | 1459 §4.2.2 | Leave a channel |
|
||||
| `QUIT` | 1459 §4.1.6 | Disconnect from server |
|
||||
| `NICK` | 1459 §4.1.2 | Change nickname |
|
||||
| `MODE` | 1459 §4.2.3 | Set/query channel or user modes |
|
||||
| `TOPIC` | 1459 §4.2.4 | Set/query channel topic |
|
||||
| `KICK` | 1459 §4.2.8 | Kick user from channel |
|
||||
| `PING` | 1459 §4.6.2 | Keepalive |
|
||||
| `PONG` | 1459 §4.6.3 | Keepalive response |
|
||||
| `PUBKEY` | (extension) | Announce/relay signing public key |
|
||||
|
||||
**Server-to-Client (S2C):**
|
||||
|
||||
All C2S commands may be echoed back as S2C (relayed to other users), plus:
|
||||
|
||||
| Command | Description |
|
||||
|----------|-------------|
|
||||
| PONG | Server keepalive response |
|
||||
| PUBKEY | Relayed public key from another user |
|
||||
| ERROR | Server error message |
|
||||
All C2S commands may be relayed S2C to other users (e.g. JOIN, PART, PRIVMSG).
|
||||
|
||||
**Numeric Reply Codes (S2C):**
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|-------------------|-------------|
|
||||
| 001 | RPL_WELCOME | Welcome after session creation |
|
||||
| 002 | RPL_YOURHOST | Server host information |
|
||||
| 322 | RPL_LIST | Channel list entry |
|
||||
| 353 | RPL_NAMREPLY | Names list for a channel |
|
||||
| 366 | RPL_ENDOFNAMES | End of names list |
|
||||
| 372 | RPL_MOTD | Message of the day line |
|
||||
| 375 | RPL_MOTDSTART | Start of MOTD |
|
||||
| 376 | RPL_ENDOFMOTD | End of MOTD |
|
||||
| 401 | ERR_NOSUCHNICK | No such nick or channel |
|
||||
| 403 | ERR_NOSUCHCHANNEL | No such channel |
|
||||
| 433 | ERR_NICKNAMEINUSE | Nickname already in use |
|
||||
| Code | Name | Description |
|
||||
|------|----------------------|-------------|
|
||||
| 001 | RPL_WELCOME | Welcome after session creation |
|
||||
| 002 | RPL_YOURHOST | Server host information |
|
||||
| 003 | RPL_CREATED | Server creation date |
|
||||
| 004 | RPL_MYINFO | Server info and modes |
|
||||
| 322 | RPL_LIST | Channel list entry |
|
||||
| 323 | RPL_LISTEND | End of channel list |
|
||||
| 332 | RPL_TOPIC | Channel topic |
|
||||
| 353 | RPL_NAMREPLY | Channel member list |
|
||||
| 366 | RPL_ENDOFNAMES | End of NAMES list |
|
||||
| 372 | RPL_MOTD | MOTD line |
|
||||
| 375 | RPL_MOTDSTART | Start of MOTD |
|
||||
| 376 | RPL_ENDOFMOTD | End of MOTD |
|
||||
| 401 | ERR_NOSUCHNICK | No such nick/channel |
|
||||
| 403 | ERR_NOSUCHCHANNEL | No such channel |
|
||||
| 433 | ERR_NICKNAMEINUSE | Nickname already in use |
|
||||
| 442 | ERR_NOTONCHANNEL | Not on that channel |
|
||||
| 482 | ERR_CHANOPRIVSNEEDED | Not channel operator |
|
||||
|
||||
**Server-to-Server (S2S):**
|
||||
**Server-to-Server (Federation):**
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| RELAY | Relay message to linked server |
|
||||
| LINK | Establish server link |
|
||||
| UNLINK | Tear down server link |
|
||||
| SYNC | Synchronize state between servers |
|
||||
| PING | Server-to-server keepalive |
|
||||
| PONG | Server-to-server keepalive response |
|
||||
Federated servers use the same IRC commands. After link establishment, servers
|
||||
exchange a burst of JOIN, NICK, TOPIC, and MODE commands to sync state.
|
||||
PING/PONG serve as inter-server keepalives.
|
||||
|
||||
#### Message Examples
|
||||
|
||||
```json
|
||||
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["hello world"], "meta": {"sig": "base64...", "alg": "ed25519"}}
|
||||
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["hello world"]}
|
||||
|
||||
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["line one", "line two"]}
|
||||
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["line one", "line two"], "meta": {"sig": "base64...", "alg": "ed25519"}}
|
||||
|
||||
{"command": "PRIVMSG", "from": "alice", "to": "bob", "body": ["hey, DM"]}
|
||||
|
||||
{"command": "JOIN", "from": "bob", "to": "#general"}
|
||||
|
||||
{"command": "PART", "from": "bob", "to": "#general", "body": ["later"]}
|
||||
|
||||
{"command": "NICK", "from": "oldnick", "body": ["newnick"]}
|
||||
|
||||
{"command": "001", "to": "alice", "body": ["Welcome to the network, alice"]}
|
||||
|
||||
{"command": "353", "to": "alice", "params": ["=", "#general"], "body": ["alice", "bob", "@charlie"]}
|
||||
{"command": "353", "to": "alice", "params": ["=", "#general"], "body": ["@op1 alice bob +voiced1"]}
|
||||
|
||||
{"command": "JOIN", "from": "bob", "to": "#general", "body": []}
|
||||
{"command": "433", "to": "*", "params": ["alice"], "body": ["Nickname is already in use"]}
|
||||
|
||||
{"command": "ERROR", "body": ["Closing link: connection timeout"]}
|
||||
{"command": "PUBKEY", "from": "alice", "body": {"alg": "ed25519", "key": "base64..."}}
|
||||
```
|
||||
|
||||
#### JSON Schemas
|
||||
|
||||
Reference in New Issue
Block a user