feat: add protocol test vectors generated from the reference implementation
Known-answer tests for every part of the protocol that produces bytes: key derivation, sealed envelopes with their intermediate values, accept tokens, session and registration frames, rotation certificates, frontmatter parsing, and the inputs a conformant implementation must refuse. vectors.py derives every value by calling smolmail.py, so the vectors cannot drift from the code, and verifies each one from the opposite side before emitting it. --check regenerates and diffs against the committed files. Verified independently: a checker written against SPEC.md and libsodium alone, without importing the reference, reproduces all 81 values. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43e4be2b8a
commit
9d5908104e
4 changed files with 2216 additions and 1 deletions
15
README.md
15
README.md
|
|
@ -84,6 +84,19 @@ A security review of the protocol and these reference implementations found no i
|
||||||
|
|
||||||
[SPEC.md](SPEC.md) defines the wire format, the operations and the trust model.
|
[SPEC.md](SPEC.md) defines the wire format, the operations and the trust model.
|
||||||
|
|
||||||
|
## Test vectors
|
||||||
|
|
||||||
|
[VECTORS.md](VECTORS.md) is a set of known-answer tests for every part of the protocol that produces bytes: key derivation, sealed envelopes with their intermediate values, accept tokens, the session and registration frames, rotation certificates, frontmatter parsing, and the inputs a conformant implementation must refuse. [vectors.json](vectors.json) carries the same values for a test harness to load.
|
||||||
|
|
||||||
|
[vectors.py](vectors.py) generates both from `smolmail.py`, so they cannot drift from the code, and verifies every vector from the opposite side before emitting it. `uv run vectors.py --check` regenerates and compares against the committed files:
|
||||||
|
|
||||||
|
```
|
||||||
|
uv run vectors.py # write vectors.json and VECTORS.md
|
||||||
|
uv run vectors.py --check # verify the committed files still reproduce
|
||||||
|
```
|
||||||
|
|
||||||
|
Start from the `unpadded` envelope. If its `seal_key` matches and the envelope does not, the fault is in the payload layout; if `x25519_shared` already differs, it is in the §2 key map.
|
||||||
|
|
||||||
## Inspiration
|
## Inspiration
|
||||||
|
|
||||||
The smolmail project takes inspiration from [Gemini Protocol](https://geminiprotocol.net), [Misfin](gemini://misfin.org/), and [LXMF](https://github.com/markqvist/lxmf).
|
The smolmail project takes inspiration from [Gemini Protocol](https://geminiprotocol.net), [Misfin](gemini://misfin.org/), and [LXMF](https://github.com/markqvist/lxmf).
|
||||||
|
|
@ -92,4 +105,4 @@ The smolmail project takes inspiration from [Gemini Protocol](https://geminiprot
|
||||||
|
|
||||||
The protocol definition, [SPEC.md](SPEC.md), is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license ([CC BY-SA-4.0](LICENSES/CC-BY-SA-4.0.txt)).
|
The protocol definition, [SPEC.md](SPEC.md), is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license ([CC BY-SA-4.0](LICENSES/CC-BY-SA-4.0.txt)).
|
||||||
|
|
||||||
The reference code, `smolmail.py`, `smolmaild.py`, `smolmail_rns.py` and `smolmaild_rns.py`, is licensed under the Apache License 2.0 ([Apache-2.0](LICENSES/Apache-2.0.txt)).
|
The reference code, `smolmail.py`, `smolmaild.py`, `smolmail_rns.py`, `smolmaild_rns.py` and `vectors.py`, is licensed under the Apache License 2.0 ([Apache-2.0](LICENSES/Apache-2.0.txt)). The vectors it generates, [VECTORS.md](VECTORS.md) and [vectors.json](vectors.json), are licensed with the specification.
|
||||||
|
|
|
||||||
817
VECTORS.md
Normal file
817
VECTORS.md
Normal file
|
|
@ -0,0 +1,817 @@
|
||||||
|
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
|
||||||
|
|
||||||
|
# smolmail Test Vectors, version 1.2
|
||||||
|
|
||||||
|
Known-answer tests for [SPEC.md](SPEC.md), generated from the reference implementation by [vectors.py](vectors.py). [vectors.json](vectors.json) carries the same values in a form a test harness can load; this file is for reading alongside the specification.
|
||||||
|
|
||||||
|
Work through them in order. Derivation first (§2), because every later vector depends on it, then an envelope you can seal and unseal (§5), then the wire frames (§4, §6.1, §7). Finish with the rejections: they are the ones that catch an implementation which decrypts without verifying.
|
||||||
|
|
||||||
|
Hex throughout, unless a field name says otherwise. The two masters are test data and the private keys here are public. Never use them for anything.
|
||||||
|
|
||||||
|
## Labels (§11)
|
||||||
|
|
||||||
|
Every hash, signature and MAC is domain-separated by one of these, prefixed to the input as raw bytes with no separator or length:
|
||||||
|
|
||||||
|
- `LABEL_IDENTITY` = `smolmail/1 identity`
|
||||||
|
- `LABEL_ACCEPT` = `smolmail/1 accept`
|
||||||
|
- `LABEL_SEAL` = `smolmail/1 seal`
|
||||||
|
- `LABEL_MSG` = `smolmail/1 msg`
|
||||||
|
- `LABEL_ID` = `smolmail/1 id`
|
||||||
|
- `LABEL_MAC` = `smolmail/1 mac`
|
||||||
|
- `LABEL_AUTH` = `smolmail/1 auth`
|
||||||
|
- `LABEL_ROTATE` = `smolmail/1 rotate`
|
||||||
|
- `LABEL_REGISTER` = `smolmail/1 register`
|
||||||
|
- `PROLOGUE` = `smolmail/1`
|
||||||
|
|
||||||
|
## §2 Identity
|
||||||
|
|
||||||
|
`seed_n = HKDF-SHA256(master, salt = "", info = "smolmail/1 identity" || n)`, with `n` a big-endian u32. The Ed25519 public key of that seed *is* the identity. The X25519 keypair comes from the same key via libsodium's conversions, and the accept key is derived without the index so it survives rotation.
|
||||||
|
|
||||||
|
### alice, rotation index 0
|
||||||
|
|
||||||
|
- `master`: `000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f`
|
||||||
|
- `rotation_index`: `0`
|
||||||
|
- `seed`: `f2fd61c1fba591a06eadf25ab3252caa1885b615f35fe9eab89dbf64ed233788`
|
||||||
|
- `identity_key`: `da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292`
|
||||||
|
- `x25519_private`: `205ef2abb8830bfaf41c910fe2fa2a67f3b7b5c7f87c74001bacbedea0be3a7a`
|
||||||
|
- `x25519_public`: `0c2dfdd8a679c9ca05c908651396b36dd4da1cf6ac3fc5b0037bde8d3feb506b`
|
||||||
|
- `base32`: `3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja`
|
||||||
|
- `fingerprint`: `3jxs xqtd vxwo bge7 uxef`
|
||||||
|
- `uri`: `smol://alice@example.org/3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja`
|
||||||
|
|
||||||
|
### alice, rotation index 1
|
||||||
|
|
||||||
|
- `master`: `000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f`
|
||||||
|
- `rotation_index`: `1`
|
||||||
|
- `seed`: `01bcae47fc0b45b9cfd8f6b6cc7ec094970e517715d9c037354646f37fd82dea`
|
||||||
|
- `identity_key`: `cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466`
|
||||||
|
- `x25519_private`: `884e39cd42c09e5817059634c94c3855b93ebda61f864cc951c4d6e89389ea6b`
|
||||||
|
- `x25519_public`: `d9c834e4238b16e5f92bcf3fcb78fd163dec6b1c6b6ba4867f5526bed5bd6e3c`
|
||||||
|
- `base32`: `zoieneun2k7n5b65yxfeen4pxvxzq7sclm2kfaa7os3wrwrz4rta`
|
||||||
|
- `fingerprint`: `zoie neun 2k7n 5b65 yxfe`
|
||||||
|
- `uri`: `smol://alice@example.org/zoieneun2k7n5b65yxfeen4pxvxzq7sclm2kfaa7os3wrwrz4rta`
|
||||||
|
|
||||||
|
### bob, rotation index 0
|
||||||
|
|
||||||
|
- `master`: `202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f`
|
||||||
|
- `rotation_index`: `0`
|
||||||
|
- `seed`: `469fdf277869a739339c3c8d0877ca7d8bbd27170238aa61681787e5feafba8c`
|
||||||
|
- `identity_key`: `37b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c`
|
||||||
|
- `x25519_private`: `e0d577fb7fe95cf8f8e3a7d500e060155e484f89ef46c4f208fce77ccd485e48`
|
||||||
|
- `x25519_public`: `0814610bf746f1e6f33eb9275c5a8e3b43ecf92fa442f144444779d62fe1144e`
|
||||||
|
- `base32`: `g6zze6hqrpvn53gfsyleb23wgs6wmtys5vamhvuuqicfmb6ji4oa`
|
||||||
|
- `fingerprint`: `g6zz e6hq rpvn 53gf syle`
|
||||||
|
- `uri`: `smol://bob@example.org/g6zze6hqrpvn53gfsyleb23wgs6wmtys5vamhvuuqicfmb6ji4oa`
|
||||||
|
|
||||||
|
### Accept keys and tokens (§5.8)
|
||||||
|
|
||||||
|
alice's accept key, and the token she issues to bob. The accept key is derived without the rotation index, so it and every token from it survive rotation.
|
||||||
|
|
||||||
|
- `alice_accept_key`: `2c18d0eeaa9b2333f3ecd41391131ea13f10aa35903389d860c25265545026ae`
|
||||||
|
- `bob_accept_key`: `eff6fd323304674053b55a8b6a804b6a9fdbd111faad802763c1feced48259ee`
|
||||||
|
- `alice_token_for_bob`: `084e74a6dec8ed8101672225450265e5fa3dcbe3a69455569ce1ffc254df1885`
|
||||||
|
- `alice_token_for_bob_base32`: `bbhhjjw6zdwycalheisukatf4x5d3s7du2kfkvu44h74evg7dccq`
|
||||||
|
- `bob_token_for_alice_0`: `411bccb54d51808fcf329ca609e8d8dac866e556b2a90bb4374979cb0d8689b7`
|
||||||
|
- `alice_token_for_bob_rotated`: `96ddeaeabc81dadf328898de5adec032eef7d98d799fed54b8b737282090831b`
|
||||||
|
|
||||||
|
`alice_token_for_bob_rotated` is the token alice would issue to bob's index-1 key. It differs from the one bob already holds, which is why §5.8 says a token is derived from the correspondent's identity *as it stood when they were accepted*: bob's rotation does not invalidate the token he was given.
|
||||||
|
|
||||||
|
## §3 Addressing
|
||||||
|
|
||||||
|
The short form is resolved by the server; the smol:// form carries the key and needs no server to be trusted. 1961 is the default port and is omitted from the short form.
|
||||||
|
|
||||||
|
### Usernames
|
||||||
|
|
||||||
|
| username | valid |
|
||||||
|
| --- | --- |
|
||||||
|
| `a` | yes |
|
||||||
|
| `a0` | yes |
|
||||||
|
| `alice` | yes |
|
||||||
|
| `al-ice` | yes |
|
||||||
|
| `a.b-c_d` | yes |
|
||||||
|
| `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | yes |
|
||||||
|
| `(empty)` | no |
|
||||||
|
| `-alice` | no |
|
||||||
|
| `alice-` | no |
|
||||||
|
| `.alice` | no |
|
||||||
|
| `al..ice` | no |
|
||||||
|
| `a._b` | no |
|
||||||
|
| `a-_b` | no |
|
||||||
|
| `Alice` | no |
|
||||||
|
| `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | no |
|
||||||
|
| `al ice` | no |
|
||||||
|
| `alice@` | no |
|
||||||
|
| `ali/ce` | no |
|
||||||
|
|
||||||
|
### Addresses
|
||||||
|
|
||||||
|
| input | user | host | port | identity |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `alice@example.org` | `alice` | `example.org` | 1961 | resolved |
|
||||||
|
| `alice@example.org:1961` | `alice` | `example.org` | 1961 | resolved |
|
||||||
|
| `alice@example.org:2000` | `alice` | `example.org` | 2000 | resolved |
|
||||||
|
| `smol://alice@example.org/3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja` | `alice` | `example.org` | 1961 | `da6f2bc263adece0…` |
|
||||||
|
|
||||||
|
Refused:
|
||||||
|
|
||||||
|
- `smol://alice@example.org` — SmolError: smol://alice@example.org: smol:// address carries no key
|
||||||
|
- `smol://alice@example.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` — SmolError: smol://alice@example.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: key is 31 bytes, expected 32
|
||||||
|
- `smol://alice@example.org/not-base32!` — SmolError: smol://alice@example.org/not-base32!: undecodable key: Non-base32 digit found
|
||||||
|
- `-alice@example.org` — SmolError: '-alice' must begin and end with a letter or digit and may not contain two separators in a row
|
||||||
|
|
||||||
|
### Fingerprint
|
||||||
|
|
||||||
|
- `identity`: `da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292`
|
||||||
|
- `encoded`: `3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja`
|
||||||
|
- `fingerprint`: `3jxs xqtd vxwo bge7 uxef`
|
||||||
|
- `fingerprint_note`: `the first 20 base32 characters in groups of four: 100 bits, so a collision costs ~2^100`
|
||||||
|
|
||||||
|
## §5 Messages
|
||||||
|
|
||||||
|
Each vector pins the ephemeral `esk` that §5.2 would draw at random and the `time` that §5.3 would take from the clock. Nothing else in the protocol is randomised, so with those two fixed an envelope reproduces byte for byte.
|
||||||
|
|
||||||
|
Build one in this order: agree on `x25519_shared`, derive `seal_key`, assemble `payload_header`, sign `signed_preimage`, concatenate the payload, then encrypt it under `aad` with an all-zero nonce. If your `seal_key` matches and your envelope does not, the fault is in the payload layout; if `x25519_shared` already differs, it is in §2's map.
|
||||||
|
|
||||||
|
`accept_mac` is what a sender holding the recipient's token attaches to `SEND` (§5.8); `message_id` is what the server returns. The full payload plaintext and the whole `SEND` request body are in vectors.json as `payload` and `send_body`.
|
||||||
|
|
||||||
|
### empty-body
|
||||||
|
|
||||||
|
body_len 0. The payload is 109 bytes and the envelope 194.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[0]`
|
||||||
|
- `body`: (empty)
|
||||||
|
- `body_len`: `0`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4040404040404040404040404040404040404040404040404040404040404040`
|
||||||
|
- `epk`: `d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10`
|
||||||
|
- `x25519_shared`: `383fc0e605a085e558481bcf53dfc0247a6f2c1ad46fe659953cf36874000469`
|
||||||
|
- `seal_key`: `d035954d97b00442b87ce8025cafe438c7791079d407dc11b75915ace169609c`
|
||||||
|
- `aad`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000000`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1
|
||||||
|
149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddb
|
||||||
|
ba100137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b90000000000
|
||||||
|
```
|
||||||
|
- `signature`: `e41a00f14ce5dd28ca4242be7f1d4e92c4ddc5256a9071982462da20644cb136629e0c6dd817db8b0d5dcd2d23239f0b807586193acea67a3abfb339804e3501`
|
||||||
|
- `payload_len`: `109`
|
||||||
|
- `envelope_len`: `194`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7
|
||||||
|
b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10786f5f9e9e91dd
|
||||||
|
4e29fc0a8e9516fcde856e9f6dcf5010ba87e7878bf99db9030cca7b73d61fef8a4e23e24628
|
||||||
|
f0e75b993c647361e41bd53d6ddd3a565b2ba334afaeb191f13dc801d9e7d9eeffad7eae12b0
|
||||||
|
e2e59243c7a523db3fcdb9c21e3b83508f4905624e4ae5234ba51cb5633813ffff93b60de3f1
|
||||||
|
ca8cd8a8
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `1a37555e369665f562b6a492de97a8c9cf024764b2fae32bf7bce33ebc9bc88f`
|
||||||
|
- `message_id`: `289dd78f0fb9ec8e27b1b648b1fc4cd36e388cad63e5320e8f331315998fc451`
|
||||||
|
- `accept_mac`: `1ce7f575843fa08ca8976213efbb951c8e13f119ff578da8c3f4091a15c284e5`
|
||||||
|
|
||||||
|
### unpadded
|
||||||
|
|
||||||
|
The minimal form: 69 + 45 + 11 + 64 + 16 bytes. Start here.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[0]`
|
||||||
|
- `body`: `68656c6c6f20616c696365`
|
||||||
|
- `body_len`: `11`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4141414141414141414141414141414141414141414141414141414141414141`
|
||||||
|
- `epk`: `7a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522`
|
||||||
|
- `x25519_shared`: `012b8e969fb857162e7d00cef9e68b60ebc30a858439bf0c0e256b8807f70769`
|
||||||
|
- `seal_key`: `c4c72488dc2b6582952cb297d5d54b05b43ec7540cfdc173e55010f38d6444ed`
|
||||||
|
- `aad`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1
|
||||||
|
149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e
|
||||||
|
85220137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b9000000000b68656c6c6f20616c696365
|
||||||
|
```
|
||||||
|
- `signature`: `f4b5f898318a0c7da2f779a24972f1b51041bce5723d54e340f9887199c2f299b40b76ca3097562928cff3c2d8e02e7e78492f7f80ed6b0f784e1015b3f3b20c`
|
||||||
|
- `payload_len`: `120`
|
||||||
|
- `envelope_len`: `205`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a
|
||||||
|
1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e85223f096c1be498f7
|
||||||
|
9a436bb6e96b7d425fc1173bd34c39f824e44c886a0291c1ce88d5956af3ee9d13b4c002eb50
|
||||||
|
63388392b4777356a96d32e8940477577337b48af677c773482b47c49e558e7f0dd5b93396e5
|
||||||
|
f38e3b7b01d4e2784bf4bd3df0aa84efabe12925c7522c8ca9ab867bd64aa38f1488bf2cac84
|
||||||
|
f2d6fd1f6657a67991393a5607e721
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `8594cdb21ec3373f1516253e7db829e18471c066d8a891f71e9c75d547a696c5`
|
||||||
|
- `message_id`: `75f74029f5e6a60dae846767622c09177ec75ea8de171b1b4314e44faac26d35`
|
||||||
|
- `accept_mac`: `5cb318c5db75b0f37f8b3b8bf31a5899e5f673416c0c442a3d01abc8019b01b4`
|
||||||
|
|
||||||
|
### padded
|
||||||
|
|
||||||
|
The same message with §5.3 padding to a 1024-byte multiple. A receiver must ignore the trailing zeros and trust body_len.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[0]`
|
||||||
|
- `body`: `68656c6c6f20616c696365`
|
||||||
|
- `body_len`: `11`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `true`
|
||||||
|
- `esk`: `4242424242424242424242424242424242424242424242424242424242424242`
|
||||||
|
- `epk`: `132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f472`
|
||||||
|
- `x25519_shared`: `2957671c03ec2f21c2d1a1c9e743564016a3107618f53077f02aff745c90ef02`
|
||||||
|
- `seal_key`: `db77b3dc494a2b299eadbbbdd3c3c0cd5e44a6b8c7ad7490729a63905668fbf4`
|
||||||
|
- `aad`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f472`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1
|
||||||
|
149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993
|
||||||
|
f4720137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b9000000000b68656c6c6f20616c696365
|
||||||
|
```
|
||||||
|
- `signature`: `000e182ed5e1e10fc9ab0d1e358d54fb6ffb8fe9ea097fb795b900dfe3b9914734a11cfb0ddff7a5ce47d9b1f66fc48022b7402e2fbae0f32762632db59e7507`
|
||||||
|
- `payload_len`: `1024`
|
||||||
|
- `envelope_len`: `1109`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf146529213
|
||||||
|
2c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f47269e94cda066826
|
||||||
|
23d97aed6f33fdd2ff38d48811450881350eb826904757f1e3e30ceeb0989c56e4638131f409
|
||||||
|
68350f507f2d61d2320abb5cc3bc433340094c37bcc350abf0e7910a672879527c99e7a9f77c
|
||||||
|
bff5e9d99138c56f75e9a98bf4128107a83176666c5db76298e338ce4be914cbecaca5ea11f1
|
||||||
|
4555cb7028ac1ccf41d0b330beada77c61eb42cab21097b005d1cf1ad60b5bfcd137a02ecd9a
|
||||||
|
af65a4692882d75e73e52d6ebcfe47bfead71ac9e54d7b1ac275aaf28a192666930324091049
|
||||||
|
3ffdd92e396387dbfff545f4d8d6b598fcb4a79f48d681f03c70e7ee378bb2cef91c9f744793
|
||||||
|
4d19aaaf36fd38434eef7dfcb2572b51ab49182278426cfb11f72614130a01907e1104ad5e27
|
||||||
|
10072041085179eac48281396bbcc5de0f2f60d89934a70b4583dda1b45188609b7d0f959559
|
||||||
|
38fb49aa2d61ad0c63d69a7f8da875b957bfff6f9b7d83a7ebb1e05f4099033e81cdaae5b0ab
|
||||||
|
83960871f3da11ceabcdd710d589948e7cd7a0e3eedee7941ad250b224d8869e657b0af53d22
|
||||||
|
ab8502919e40959acd69e303879d2ed2a4487e0d7d5a2203f886e2dd95c41655a3c4c09bcbb6
|
||||||
|
b5fbff92f1694d5e1ad5c470bbe33eaa3254e416f72d32be96d46ecaf6fc12bd891f8d3a0dc8
|
||||||
|
c36403b23f7875fa0afc518a307dcd3d7cb8846bbfdf40fedf49e3ea87a5f378050fa2f6ea8e
|
||||||
|
a86a422c58f8e7c9a077cd3fbc69c4837d748a0d32678cc7f8b482d09e3c2c9eb5037cdf2082
|
||||||
|
4be0f0c1fe26893d7e3b04a4a16ed8c7910fbf1a106a12d4c145dae793b46e36176f3c621c30
|
||||||
|
75adec0e445c033dc6d4fbe0f3c7f9419604361435e2af332cc428b038ab0e339c07248f8ac9
|
||||||
|
fc520f722558ff015b372a5d9f90a867f606b63329b21dcd05587f13e3abe40be2f5c9177048
|
||||||
|
c69168f087e071aecccff1eb3f04f95d9557dd6a40c37338765bff6de1861ca04e28a92d372e
|
||||||
|
fb92103a982d760aa89301b60c8d9a591c39492dad4467053f3c85ca226777a591434b814bdf
|
||||||
|
86d4d7557665bdf118da32e2396eba2ca3dc24731690eee9bae9398f6b9ca81f449a486ef14f
|
||||||
|
7db9c2c8e330c4b56569b24bdf1d53961d0338fd38e7ac345a1bccbd2ead3a2a133b798762bb
|
||||||
|
ddaf60f7e743b2ab0bc9f9b14b7755b02f9068ab9b4a8f154d6e9d6f61013281a6a7a69ebaa2
|
||||||
|
80f0fd7a7bc1734ddc37ef0af2d0628d5d6f7a62d345648e03b6aa84d2470a0883e79e962faa
|
||||||
|
65b3547d2b4eac386cdcdcc2afa8ebf525183854d8baafbd09691edaa73c193ad54f99bea34e
|
||||||
|
f8a2fd03ef6f869bd28bafb8377affdab5867cec7609eff5834d4cb7c9820a07fb17aebc6827
|
||||||
|
ee1c7505f820d9be04973c0b264bb3be34d54cb2e1ac4edf437a3a065a85c4158af88952721c
|
||||||
|
3d83195f898e514ba9e751060be46d2a6067e99c99a61d2959cb0d1b7ca6e76c207d26a50509
|
||||||
|
0fa3007a406471
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `41a93d239e7da918e1e6480b724b8e23eeb39ddd454b889f6a55934658c143b2`
|
||||||
|
- `message_id`: `ff849aa8b573dfc10dc5ac731b862ba1205f42c81a014744f2812d3bf1d62a2e`
|
||||||
|
- `accept_mac`: `2b0a0b0804a78a32f47e461b8ed06e6c1a21ed750b2b33290cc91f884fb86ca0`
|
||||||
|
|
||||||
|
### utf8
|
||||||
|
|
||||||
|
body_len counts bytes, not characters: 29 bytes, 22 characters.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[0]`
|
||||||
|
- `body`: `4772c3bcc39f65206175732064656d204d65736820e2809420f09f9bb00a`
|
||||||
|
- `body_len`: `30`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4343434343434343434343434343434343434343434343434343434343434343`
|
||||||
|
- `epk`: `cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70`
|
||||||
|
- `x25519_shared`: `40ff868360e1f61dd53a383f4e8d96205ac1eab3dea7f1c69f9a8d44f853da79`
|
||||||
|
- `seal_key`: `3bab319a2a1e80a93c2639891f9dce11c35e8cbc558fea02f3c3ef494276b54b`
|
||||||
|
- `aad`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001e`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1
|
||||||
|
149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c1
|
||||||
|
0c700137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b9000000001e4772c3bcc39f65206175732064656d204d65736820e2809420f09f9bb0
|
||||||
|
0a
|
||||||
|
```
|
||||||
|
- `signature`: `86372500958b7808f6fb1878d40ccc9a2b090fbb042f834b6e131ee1c7d55ef3be7ecb44db86c59bee9dc4f2268fce878e0d6e79e046969f3667bbb8b6fd110e`
|
||||||
|
- `payload_len`: `139`
|
||||||
|
- `envelope_len`: `224`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cd
|
||||||
|
efd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70315b93e0580e4c
|
||||||
|
e845678e9aa71eeb8bf2f8c3155a8c1fd58dc43c91095e0a19032a91231f3f7ef11167dac73e
|
||||||
|
296e98c921c15c8d02e24747df1ffa600818abfb6858aa82184277da1d1fd02d1d59ada01941
|
||||||
|
8a2c708a7521e6a0f1e8e55f24f46d0bfa7d9c1ad761580f320f5c3248c18aac5158c0296992
|
||||||
|
6f427d921fee8ba4ccf2bff233733e64d5e3b50757f8f5b880d1f869651c9a186608
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `fcf78b183a0c2fad26f36b15e6cd222d1e864f48c7eea616f66aa65a14872b7c`
|
||||||
|
- `message_id`: `82c6d8c341ea821e4558dc6bb35f533e399a89646d9023437802b1d2499f2b88`
|
||||||
|
- `accept_mac`: `b221784d76eb2d52517d142912139ab0f6e61900ff35eed45998cb459683cf68`
|
||||||
|
|
||||||
|
### frontmatter
|
||||||
|
|
||||||
|
§5.5 frontmatter carrying the reserved Reply-To (§5.7) and Accept (§5.8) fields. Both are only meaningful because the signature covers them.
|
||||||
|
|
||||||
|
- `sender`: `alice[0]`
|
||||||
|
- `recipient`: `bob[0]`
|
||||||
|
|
||||||
|
`body`
|
||||||
|
|
||||||
|
```
|
||||||
|
2d2d2d0a5375626a6563743a20746865207468696e670a5265706c792d546f3a20736d6f6c3a
|
||||||
|
2f2f616c696365406578616d706c652e6f72672f336a7873787174647678776f626765377578
|
||||||
|
656670626f72646b756c7636626336616f336834697573716c6f76346b676b6b6a610a416363
|
||||||
|
6570743a20626268686a6a77367a64777963616c68656973756b617466347835643373376475
|
||||||
|
326b666b7675343468373465766737646363710a2d2d2d0a48657265206973206120746f6b65
|
||||||
|
6e20736f20796f7572206d61696c20736b697073206d7920726571756573747320746965722e
|
||||||
|
0a
|
||||||
|
```
|
||||||
|
- `body_len`: `229`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4444444444444444444444444444444444444444444444444444444444444444`
|
||||||
|
- `epk`: `ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b`
|
||||||
|
- `x25519_shared`: `07c5ae79c7568952aa78668c613389e4ecdd2f61f207618532fb3c8c8a26f213`
|
||||||
|
- `seal_key`: `4dd79e54e611ac322ac0ace7e7a64b2c2139d422097f939c603ee6dd9843f7cc`
|
||||||
|
- `aad`: `534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b`
|
||||||
|
- `payload_header`: `01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292000000006955b900000000e5`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d736737b39278f08beadeecc5961640eb7634bd664f12ed40c3d6
|
||||||
|
9482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff
|
||||||
|
3e6b01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292000000
|
||||||
|
006955b900000000e52d2d2d0a5375626a6563743a20746865207468696e670a5265706c792d
|
||||||
|
546f3a20736d6f6c3a2f2f616c696365406578616d706c652e6f72672f336a78737871746476
|
||||||
|
78776f626765377578656670626f72646b756c7636626336616f336834697573716c6f76346b
|
||||||
|
676b6b6a610a4163636570743a20626268686a6a77367a64777963616c68656973756b617466
|
||||||
|
347835643373376475326b666b7675343468373465766737646363710a2d2d2d0a4865726520
|
||||||
|
6973206120746f6b656e20736f20796f7572206d61696c20736b697073206d79207265717565
|
||||||
|
73747320746965722e0a
|
||||||
|
```
|
||||||
|
- `signature`: `23e0b6bd5660ab109144e07b24affb7298041dafe908a469ab28c120f812b91dbcc7cee0763d058df4357d74ef4dee85f42da50dffdc66fb8604288a3c3d0000`
|
||||||
|
- `payload_len`: `338`
|
||||||
|
- `envelope_len`: `423`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff
|
||||||
|
2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b7d16509bc2c568
|
||||||
|
3501cacd195740772232cdb04f5375e55dac3d49e258524c0eca1819d6911503a76d1b80e2e2
|
||||||
|
048947659c9925faa4e2710420d0be9110cd216f651808f4132dc13d6a91c55d9ac653b161ad
|
||||||
|
63cc70b7c9eac3b89d4994b04e791b195ca148845649a3a473f7dc26bfd0326e1f65b7559d12
|
||||||
|
824920c136e502d1c29c3211a3ca7d0782df4655e31123de9d61ebb35fe9fcfd0ecdf0c7e406
|
||||||
|
854ab7bfddbc818dd839e02ea450fd94124228a8bae41bf020962919de514525304a8f2c512b
|
||||||
|
8335bbef48af719c340d63a7e38cd6ab40c004601edf67a572062182a15328825f017d10b841
|
||||||
|
9edf162610ece5884539e8408bd6893a47b5d99d02bd944e98194424333ee1a99aabf600ec83
|
||||||
|
ae4cabc38c12c0044bc16536d54a65d78df62428b4ba290296986b19d8e143cad25ee6c20351
|
||||||
|
5c3dcb3907f3adab87ec4dd4d5cdbfe24621beb2d4901939a02df296b67c996fef0b84fc591c
|
||||||
|
87b9dc35be
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `4cd667f09da015acc5c6c1f57e57181334b89a11c2d7d543e4dcb2bf012bd698`
|
||||||
|
- `message_id`: `e6a52f744a8c0eab0c0a72c56ef51af96b83d9721d38dc669be38ca2c272f498`
|
||||||
|
- `accept_mac`: `8589379776f7d005c016593b5e04f624d7530f405213639361ab2a127004cd40`
|
||||||
|
|
||||||
|
### superseded-key
|
||||||
|
|
||||||
|
Sealed to alice's index-0 key while she is at index 1 (§7). She opens it with the retained key, which is re-derivable from the master alone.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[0]`
|
||||||
|
- `body`: `73656e74206265666f726520796f7520726f74617465640a`
|
||||||
|
- `body_len`: `24`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4545454545454545454545454545454545454545454545454545454545454545`
|
||||||
|
- `epk`: `3286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b`
|
||||||
|
- `x25519_shared`: `bf14188f69b809b1c944f8d46c1d4a91f641b741537b88d98622d7f2ac310434`
|
||||||
|
- `seal_key`: `7367d84d99359d719fdb9fceb49b29c19b14636dd4fc2896774061d110973cc8`
|
||||||
|
- `aad`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000018`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1
|
||||||
|
149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36
|
||||||
|
736b0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b9000000001873656e74206265666f726520796f7520726f74617465640a
|
||||||
|
```
|
||||||
|
- `signature`: `4eb267c60e58d0f6acc2a9f0c39e384e16324ddb7bf8c23e14659682af1cfca526e5e72e39663c3d5207c929b0f65982f74ff7d3de4fce80d76bc8a39bbeac04`
|
||||||
|
- `payload_len`: `133`
|
||||||
|
- `envelope_len`: `218`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf146529232
|
||||||
|
86894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b4272b58c6b941f
|
||||||
|
f140f69144dc10f1d49a0f9603e7a5a97718edc2e0617631d2fed71776274b6d7d2653c1fa88
|
||||||
|
9af93c9d57fbfd0d0b0ced3f8efdf59adaa23e3101c671d55850adbbb7d776c5f1a1296b6b46
|
||||||
|
68041c0575d317b2e7bc796894091313cbf1c6be2382fe26229ba9bcfcee3281fd6b55731e06
|
||||||
|
bf72bdd73efef8b4c32c6a269a2036fc272885f0f6551f9322f727ab
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `57c33559dc31934cae4d41e99954911422178b4a54a7c7cf5a8b3174485dee37`
|
||||||
|
- `message_id`: `292854907f2bc92223edabf57ceead7276402ee3569b18d2d0d40b0e2c50886b`
|
||||||
|
- `accept_mac`: `f417c61474e4c15bbecf8586a8b1adf37a03106aea0ef818a4b970b4e8d429e9`
|
||||||
|
|
||||||
|
### rotated-key
|
||||||
|
|
||||||
|
The same correspondents after alice's rotation. Only `to` changes; her accept token does not.
|
||||||
|
|
||||||
|
- `sender`: `bob[0]`
|
||||||
|
- `recipient`: `alice[1]`
|
||||||
|
- `body`: `73656e7420616674657220796f7520726f74617465640a`
|
||||||
|
- `body_len`: `23`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
- `padded`: `false`
|
||||||
|
- `esk`: `4646464646464646464646464646464646464646464646464646464646464646`
|
||||||
|
- `epk`: `a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75`
|
||||||
|
- `x25519_shared`: `e8265154d3d8063d89c7b591427fab6ae3b3207bc8ba87d287c2c10bcbfb5b4a`
|
||||||
|
- `seal_key`: `b4e8a0876228c50744d1f3cde417d6299be612b485554300e44434e2f9446106`
|
||||||
|
- `aad`: `534d4f4c01cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75`
|
||||||
|
- `payload_header`: `0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000017`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31206d7367cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a280
|
||||||
|
1f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e
|
||||||
|
1a750137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000
|
||||||
|
006955b9000000001773656e7420616674657220796f7520726f74617465640a
|
||||||
|
```
|
||||||
|
- `signature`: `857ef01e8bd0d7aee6650a86d189c9cb384c1bf6d1cc47c8666e67d88270d0d1b10a2b53388c8a9aca1c6fc0645a40cd8cb4f8af58cdc2e06801918e4e475203`
|
||||||
|
- `payload_len`: `132`
|
||||||
|
- `envelope_len`: `217`
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a2
|
||||||
|
8a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75559e854a04d51f
|
||||||
|
d20336516fa2471e8b13784f6597813601f15c8f3d9aa95c049f9671ab843e1d40dc0eccf227
|
||||||
|
942b48240590da1e753e443129d8ffdaf032d7f5d2298eb7b426bd937faf52af2c0303c33e25
|
||||||
|
78574c37c0492a2b80a793d539c3ecd13a50dcd6d9ed86510749ae97e0f256d45ad8a4ea0bed
|
||||||
|
15e50a125933d17e11fc3615f288c5aeeaf706f7dbf279db165e53
|
||||||
|
```
|
||||||
|
- `envelope_sha256`: `b8fbe91f24f6f8969a7a6e2aefaf03a0f8e8e8947d8521baa9fa641c002344c4`
|
||||||
|
- `message_id`: `72c054747b8edd9c710df7ab295ed832ef791e6db4a585821968d8dff9ae3b02`
|
||||||
|
- `accept_mac`: `e7d594c129757cdbf628b354c592cf566f6b40d95fa7f6a7c6be6ca2696917cb`
|
||||||
|
|
||||||
|
## §4 and §6.1 Session frames
|
||||||
|
|
||||||
|
The AUTH signature covers the Noise handshake hash, so it cannot be replayed to another session or server. The REGISTER signature covers the server's static key for the same reason: without it, any party could bind any public key to any username.
|
||||||
|
|
||||||
|
- `handshake_hash`: `808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f`
|
||||||
|
- `server_static`: `606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f`
|
||||||
|
- `auth_signed_preimage`: `736d6f6c6d61696c2f312061757468808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f`
|
||||||
|
- `auth_signature`: `26936929ab0ead5f407d69a6516f32d2b871f41d73345b9b7d076d85eea9b6736ea0f522bcff8511c2aea1f8cf0e7a7d3f1f81f3d6d5654a27c5b81e462fa903`
|
||||||
|
|
||||||
|
`auth_frame`
|
||||||
|
|
||||||
|
```
|
||||||
|
05616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292
|
||||||
|
26936929ab0ead5f407d69a6516f32d2b871f41d73345b9b7d076d85eea9b6736ea0f522bcff
|
||||||
|
8511c2aea1f8cf0e7a7d3f1f81f3d6d5654a27c5b81e462fa903010001084e74a6dec8ed8101
|
||||||
|
672225450265e5fa3dcbe3a69455569ce1ffc254df1885
|
||||||
|
```
|
||||||
|
|
||||||
|
username_len=5 || 'alice' || identity || signature || sync=1 || count=1 || alice's token for bob
|
||||||
|
|
||||||
|
`register_signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f31207265676973746572606162636465666768696a6b6c6d6e6f707172
|
||||||
|
737475767778797a7b7c7d7e7f616c696365da6f2bc263adece0989fa5c85785d11aa8baf822
|
||||||
|
f01db3f1149416eaf1465292
|
||||||
|
```
|
||||||
|
- `register_signature`: `78364170da13b57566df066c66756c29ba4f080e8b75e3503ac7614534cff3486b888455fd5e6ad756edb4ed28567aab8ccc3820b0289d15c48720066818220f`
|
||||||
|
|
||||||
|
`register_frame`
|
||||||
|
|
||||||
|
```
|
||||||
|
05616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292
|
||||||
|
78364170da13b57566df066c66756c29ba4f080e8b75e3503ac7614534cff3486b888455fd5e
|
||||||
|
6ad756edb4ed28567aab8ccc3820b0289d15c48720066818220f0000
|
||||||
|
```
|
||||||
|
|
||||||
|
token_len=0 and cert_len=0: a first registration, no invite token and no rotation certificate
|
||||||
|
|
||||||
|
## §7 Key rotation
|
||||||
|
|
||||||
|
A certificate is 200 bytes and carries no username: the name is covered by both signatures but not transported, so a certificate cannot be replayed against another username bound to the same key.
|
||||||
|
|
||||||
|
- `username`: `alice`
|
||||||
|
- `old_key`: `da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292`
|
||||||
|
- `new_key`: `cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466`
|
||||||
|
- `time`: `1767225600`
|
||||||
|
|
||||||
|
`signed_preimage`
|
||||||
|
|
||||||
|
```
|
||||||
|
736d6f6c6d61696c2f3120726f74617465616c696365da6f2bc263adece0989fa5c85785d11a
|
||||||
|
a8baf822f01db3f1149416eaf1465292cb9046928dd2bede87ddc5ca42378fbd6f987e425b34
|
||||||
|
a2801f74b768da39e466000000006955b900
|
||||||
|
```
|
||||||
|
- `sig_old`: `ba3bd58ab28a0bb6758bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e61959a523429ea2d4d47622df470743caf298a19e43b9b03`
|
||||||
|
- `sig_new`: `58a928e912485548a74c2d8e9d14553a5f90c34411848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad774a614283c2268b958a0e`
|
||||||
|
|
||||||
|
`certificate`
|
||||||
|
|
||||||
|
```
|
||||||
|
da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cb9046928dd2
|
||||||
|
bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466000000006955b900ba3bd58a
|
||||||
|
b28a0bb6758bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e6195
|
||||||
|
9a523429ea2d4d47622df470743caf298a19e43b9b0358a928e912485548a74c2d8e9d14553a
|
||||||
|
5f90c34411848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad77
|
||||||
|
4a614283c2268b958a0e
|
||||||
|
```
|
||||||
|
|
||||||
|
`resolve_body`
|
||||||
|
|
||||||
|
```
|
||||||
|
cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e46601da6f2bc263
|
||||||
|
adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cb9046928dd2bede87ddc5
|
||||||
|
ca42378fbd6f987e425b34a2801f74b768da39e466000000006955b900ba3bd58ab28a0bb675
|
||||||
|
8bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e61959a523429ea
|
||||||
|
2d4d47622df470743caf298a19e43b9b0358a928e912485548a74c2d8e9d14553a5f90c34411
|
||||||
|
848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad774a614283c2
|
||||||
|
268b958a0e
|
||||||
|
```
|
||||||
|
|
||||||
|
Chain walking:
|
||||||
|
|
||||||
|
- pinned=old, offered=new, chain=[cert], username=alice → **accept**
|
||||||
|
- same certificate checked as username=mallory → **refuse** (both signatures cover the username)
|
||||||
|
- pinned=old, offered=new, empty chain → **refuse** (a key change needs a chain; absent means ask the user)
|
||||||
|
- sig_new zeroed → **refuse** (the new key must sign too, or the old key alone could hand the username to a key nobody controls)
|
||||||
|
|
||||||
|
## §5.5 Frontmatter
|
||||||
|
|
||||||
|
A flat `Key: value` block, not YAML. A malformed line invalidates the whole block, which is then shown as ordinary body text: frontmatter fails closed toward display, never toward silent discard.
|
||||||
|
|
||||||
|
| case | input | fields | text | note |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `no-block` | `'hello\n'` | `{}` | `'hello\n'` | no leading '---\n', so the whole body is text |
|
||||||
|
| `simple` | `'---\nSubject: hi\n---\nbody\n'` | `{'subject': 'hi'}` | `'body\n'` | one field, then the text |
|
||||||
|
| `empty-block` | `'---\n---\n---\nliteral dashes\n'` | `{}` | `'---\nliteral dashes\n'` | an empty block escapes a body that genuinely starts with '---' |
|
||||||
|
| `case-and-duplicates` | `'---\nSubject: first\nsubject: second\nX-Mood: calm\n'…` | `{'subject': 'first', 'x-mood': 'calm'}` | `'b'` | keys are lowercased for comparison and the first occurrence wins |
|
||||||
|
| `value-trimmed` | `'---\nSubject: spaced out \n---\n'` | `{'subject': 'spaced out'}` | `''` | the value is the rest of the line, trimmed |
|
||||||
|
| `colon-in-value` | `'---\nSubject: re: the thing\n---\n'` | `{'subject': 're: the thing'}` | `''` | only the first colon separates |
|
||||||
|
| `no-colon` | `'---\nSubject hi\n---\nbody\n'` | `{}` | `'---\nSubject hi\n---\nbody\n'` | a malformed line invalidates the whole block, which becomes text |
|
||||||
|
| `bad-key` | `'---\nSub ject: hi\n---\nbody\n'` | `{}` | `'---\nSub ject: hi\n---\nbody\n'` | key charset is [A-Za-z0-9-] |
|
||||||
|
| `unterminated` | `'---\nSubject: hi\nbody\n'` | `{}` | `'---\nSubject: hi\nbody\n'` | no closing '---' |
|
||||||
|
| `too-many-keys` | `'---\nK0: v\nK1: v\nK2: v\nK3: v\nK4: v\nK5: v\nK6: v\nK7'…` | `{}` | `'---\nK0: v\nK1: v\nK2: v\nK3: v\nK4: v\nK5: v\nK6: v\nK7'…` | over 64 keys |
|
||||||
|
| `too-large` | `'---\nK: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'…` | `{}` | `'---\nK: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'…` | over 4 KiB |
|
||||||
|
| `injected-field` | `'---\nSubject: hi\nAccept: aaaaaaaa\n---\nb'` | `{'subject': 'hi', 'accept': 'aaaaaaaa'}` | `'b'` | what a line break in a sender-supplied value produces: a second field, signed and indistinguishable from an intended one. A client building frontmatter MUST refuse a value containing a line break. |
|
||||||
|
|
||||||
|
Long inputs are clipped here; vectors.json has them whole.
|
||||||
|
|
||||||
|
## Rejections
|
||||||
|
|
||||||
|
Inputs a conformant implementation refuses. The envelope cases are mutations of one valid message, listed with the reference's own error, so a client can be tested against its own failure path.
|
||||||
|
|
||||||
|
Each envelope is a mutation of this valid one:
|
||||||
|
|
||||||
|
`valid_baseline`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
|
||||||
|
### bad-magic
|
||||||
|
|
||||||
|
magic is not 'SMOL' (§5.1)
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
584d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: not a smolmail envelope`
|
||||||
|
|
||||||
|
### bad-envelope-version
|
||||||
|
|
||||||
|
unknown envelope version; the sealed-box construction may differ
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c02da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: unsupported envelope version 2`
|
||||||
|
|
||||||
|
### truncated
|
||||||
|
|
||||||
|
shorter than the 69-byte header plus a 16-byte tag
|
||||||
|
|
||||||
|
- `envelope`: `534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d36558252`
|
||||||
|
- `rejected_with`: `SmolError: envelope too short`
|
||||||
|
|
||||||
|
### tag-flipped
|
||||||
|
|
||||||
|
the Poly1305 tag no longer authenticates
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2be
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: decryption failed: wrong key or corrupt envelope`
|
||||||
|
|
||||||
|
### aad-tampered
|
||||||
|
|
||||||
|
epk is covered by the AAD, so altering it breaks the tag rather than silently re-addressing the message
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ff2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: decryption failed: wrong key or corrupt envelope`
|
||||||
|
|
||||||
|
### not-for-us
|
||||||
|
|
||||||
|
`to` names a key we do not hold; a client MUST check it against its own
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cc3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: addressed to g6zze6hqrpvn53gf…, not one of our keys`
|
||||||
|
|
||||||
|
### bad-signature
|
||||||
|
|
||||||
|
decrypts cleanly and the signature is zeroed. An implementation that skips §5.3 verification will accept forged mail here.
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3daf229b9065119b10570cbeec2967e78dc7d26906bda10ec4702b50a7d0f93418
|
||||||
|
a97ab7aa5b6d10455d6ba65081e74824e96e1d637a4dc26845810958fe11269209adcd49b5e7
|
||||||
|
6471b2c4df881338ba42
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: signature does not verify`
|
||||||
|
|
||||||
|
### signature-bound-to-other-recipient
|
||||||
|
|
||||||
|
a payload signed for bob, resealed to alice. The signature covers `to`, so re-addressing a recorded message must not verify.
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3db7459221085c65f94569e41f2a7f00d84dc2045e491c300ebdfa3f8ea2af2560
|
||||||
|
1ad057d0013ef1fad9564dc1f24b034f4ebff12458d979f593fe9fa4d5a70595bf0215f69abc
|
||||||
|
1e1197d0b5979071727f
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: signature does not verify`
|
||||||
|
|
||||||
|
### body-len-overflows
|
||||||
|
|
||||||
|
body_len exceeds the payload (§5.3)
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2bbdfc2
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd977b2479cfcb4d
|
||||||
|
3883cf888582df8b22f0
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: payload body length exceeds the payload`
|
||||||
|
|
||||||
|
### payload-version
|
||||||
|
|
||||||
|
unknown payload version
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825290a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97372cf15fd0d6
|
||||||
|
c068a477ac4c35c10862
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: unsupported payload version`
|
||||||
|
|
||||||
|
### dated-in-the-future
|
||||||
|
|
||||||
|
time is 4102444800, more than 86400s ahead of any clock before 2100 (§5.3)
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c3
|
||||||
|
06fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd8b19bf25e2abdfc4
|
||||||
|
c605dfcb7d3da3c142dc7b0dbabf90b361744b6247bb8e9fc9407d51b383f018556d9fbfd999
|
||||||
|
beaeb49bec2d719ce3c62785f850ce261eb68784050fd68f1e7655fcae4a659fe7353abcadb4
|
||||||
|
da68e9b733c5704cbb51
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: payload is dated in the future`
|
||||||
|
|
||||||
|
### low-order-epk
|
||||||
|
|
||||||
|
a low-order ephemeral key, which §2 requires be refused
|
||||||
|
|
||||||
|
`envelope`
|
||||||
|
|
||||||
|
```
|
||||||
|
534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292e0
|
||||||
|
eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b80093a846f5413d3a
|
||||||
|
945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4
|
||||||
|
dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f5462671898
|
||||||
|
30879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96
|
||||||
|
ad8d633e2c6b0d95f2bf
|
||||||
|
```
|
||||||
|
- `rejected_with`: `SmolError: rejected key agreement: Unexpected library error`
|
||||||
|
|
||||||
|
### Low-order X25519 keys
|
||||||
|
|
||||||
|
§2 and §4: a received ephemeral public key that is a low-order point MUST be refused, and an all-zero X25519 output MUST be refused. libsodium reports the first as the second, since the agreement output is zero.
|
||||||
|
|
||||||
|
- `0000000000000000000000000000000000000000000000000000000000000000`
|
||||||
|
- `0100000000000000000000000000000000000000000000000000000000000000`
|
||||||
|
- `e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800`
|
||||||
|
- `5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157`
|
||||||
|
- `ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f`
|
||||||
|
- `edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f`
|
||||||
|
- `eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f`
|
||||||
|
|
||||||
|
### Invalid identity keys
|
||||||
|
|
||||||
|
Keys that do not decode as Ed25519 points, or are of small order, so §2's map to X25519 has no valid result.
|
||||||
|
|
||||||
|
- `0000000000000000000000000000000000000000000000000000000000000000` — SmolError: not a valid Ed25519 public key: Unexpected library error
|
||||||
|
- `ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` — SmolError: not a valid Ed25519 public key: Unexpected library error
|
||||||
|
- `0100000000000000000000000000000000000000000000000000000000000000` — SmolError: not a valid Ed25519 public key: Unexpected library error
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
These vectors are licensed under [CC BY-SA 4.0](LICENSES/CC-BY-SA-4.0.txt), with the specification.
|
||||||
674
vectors.json
Normal file
674
vectors.json
Normal file
|
|
@ -0,0 +1,674 @@
|
||||||
|
{
|
||||||
|
"spec": "smolmail/1.2",
|
||||||
|
"generated_by": "vectors.py, from smolmail.py",
|
||||||
|
"note": "All values are hex unless the field name says otherwise. `alice[n]` and `bob[n]` name a master and a rotation index from `identity`. Masters are test data: never use them.",
|
||||||
|
"labels": {
|
||||||
|
"LABEL_IDENTITY": "smolmail/1 identity",
|
||||||
|
"LABEL_ACCEPT": "smolmail/1 accept",
|
||||||
|
"LABEL_SEAL": "smolmail/1 seal",
|
||||||
|
"LABEL_MSG": "smolmail/1 msg",
|
||||||
|
"LABEL_ID": "smolmail/1 id",
|
||||||
|
"LABEL_MAC": "smolmail/1 mac",
|
||||||
|
"LABEL_AUTH": "smolmail/1 auth",
|
||||||
|
"LABEL_ROTATE": "smolmail/1 rotate",
|
||||||
|
"LABEL_REGISTER": "smolmail/1 register",
|
||||||
|
"PROLOGUE": "smolmail/1"
|
||||||
|
},
|
||||||
|
"constants": {
|
||||||
|
"default_port": 1961,
|
||||||
|
"magic": "SMOL",
|
||||||
|
"version": 1,
|
||||||
|
"envelope_header": 69,
|
||||||
|
"payload_header": 45,
|
||||||
|
"pad_to": 1024,
|
||||||
|
"max_chain": 16,
|
||||||
|
"max_skew": 86400,
|
||||||
|
"noise_protocol": "Noise_NX_25519_ChaChaPoly_SHA256"
|
||||||
|
},
|
||||||
|
"identity": [
|
||||||
|
{
|
||||||
|
"name": "alice",
|
||||||
|
"master": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"rotation_index": 0,
|
||||||
|
"seed": "f2fd61c1fba591a06eadf25ab3252caa1885b615f35fe9eab89dbf64ed233788",
|
||||||
|
"identity_key": "da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292",
|
||||||
|
"x25519_private": "205ef2abb8830bfaf41c910fe2fa2a67f3b7b5c7f87c74001bacbedea0be3a7a",
|
||||||
|
"x25519_public": "0c2dfdd8a679c9ca05c908651396b36dd4da1cf6ac3fc5b0037bde8d3feb506b",
|
||||||
|
"base32": "3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja",
|
||||||
|
"fingerprint": "3jxs xqtd vxwo bge7 uxef",
|
||||||
|
"uri": "smol://alice@example.org/3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alice",
|
||||||
|
"master": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"rotation_index": 1,
|
||||||
|
"seed": "01bcae47fc0b45b9cfd8f6b6cc7ec094970e517715d9c037354646f37fd82dea",
|
||||||
|
"identity_key": "cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466",
|
||||||
|
"x25519_private": "884e39cd42c09e5817059634c94c3855b93ebda61f864cc951c4d6e89389ea6b",
|
||||||
|
"x25519_public": "d9c834e4238b16e5f92bcf3fcb78fd163dec6b1c6b6ba4867f5526bed5bd6e3c",
|
||||||
|
"base32": "zoieneun2k7n5b65yxfeen4pxvxzq7sclm2kfaa7os3wrwrz4rta",
|
||||||
|
"fingerprint": "zoie neun 2k7n 5b65 yxfe",
|
||||||
|
"uri": "smol://alice@example.org/zoieneun2k7n5b65yxfeen4pxvxzq7sclm2kfaa7os3wrwrz4rta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bob",
|
||||||
|
"master": "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
|
"rotation_index": 0,
|
||||||
|
"seed": "469fdf277869a739339c3c8d0877ca7d8bbd27170238aa61681787e5feafba8c",
|
||||||
|
"identity_key": "37b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c",
|
||||||
|
"x25519_private": "e0d577fb7fe95cf8f8e3a7d500e060155e484f89ef46c4f208fce77ccd485e48",
|
||||||
|
"x25519_public": "0814610bf746f1e6f33eb9275c5a8e3b43ecf92fa442f144444779d62fe1144e",
|
||||||
|
"base32": "g6zze6hqrpvn53gfsyleb23wgs6wmtys5vamhvuuqicfmb6ji4oa",
|
||||||
|
"fingerprint": "g6zz e6hq rpvn 53gf syle",
|
||||||
|
"uri": "smol://bob@example.org/g6zze6hqrpvn53gfsyleb23wgs6wmtys5vamhvuuqicfmb6ji4oa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accept": {
|
||||||
|
"note": "alice's accept key, and the token she issues to bob. The accept key is derived without the rotation index, so it and every token from it survive rotation.",
|
||||||
|
"alice_accept_key": "2c18d0eeaa9b2333f3ecd41391131ea13f10aa35903389d860c25265545026ae",
|
||||||
|
"bob_accept_key": "eff6fd323304674053b55a8b6a804b6a9fdbd111faad802763c1feced48259ee",
|
||||||
|
"alice_token_for_bob": "084e74a6dec8ed8101672225450265e5fa3dcbe3a69455569ce1ffc254df1885",
|
||||||
|
"alice_token_for_bob_base32": "bbhhjjw6zdwycalheisukatf4x5d3s7du2kfkvu44h74evg7dccq",
|
||||||
|
"bob_token_for_alice_0": "411bccb54d51808fcf329ca609e8d8dac866e556b2a90bb4374979cb0d8689b7",
|
||||||
|
"alice_token_for_bob_rotated": "96ddeaeabc81dadf328898de5adec032eef7d98d799fed54b8b737282090831b"
|
||||||
|
},
|
||||||
|
"addressing": {
|
||||||
|
"note": "The short form is resolved by the server; the smol:// form carries the key and needs no server to be trusted. 1961 is the default port and is omitted from the short form.",
|
||||||
|
"usernames": [
|
||||||
|
{
|
||||||
|
"username": "a",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "a0",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "alice",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "al-ice",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "a.b-c_d",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "-alice",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "alice-",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": ".alice",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "al..ice",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "a._b",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "a-_b",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "Alice",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "al ice",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "alice@",
|
||||||
|
"valid": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "ali/ce",
|
||||||
|
"valid": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"input": "alice@example.org",
|
||||||
|
"user": "alice",
|
||||||
|
"host": "example.org",
|
||||||
|
"port": 1961,
|
||||||
|
"short": "alice@example.org",
|
||||||
|
"identity": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "alice@example.org:1961",
|
||||||
|
"user": "alice",
|
||||||
|
"host": "example.org",
|
||||||
|
"port": 1961,
|
||||||
|
"short": "alice@example.org",
|
||||||
|
"identity": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "alice@example.org:2000",
|
||||||
|
"user": "alice",
|
||||||
|
"host": "example.org",
|
||||||
|
"port": 2000,
|
||||||
|
"short": "alice@example.org:2000",
|
||||||
|
"identity": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "smol://alice@example.org/3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja",
|
||||||
|
"user": "alice",
|
||||||
|
"host": "example.org",
|
||||||
|
"port": 1961,
|
||||||
|
"short": "alice@example.org",
|
||||||
|
"identity": "da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rejected": {
|
||||||
|
"smol://alice@example.org": "SmolError: smol://alice@example.org: smol:// address carries no key",
|
||||||
|
"smol://alice@example.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": "SmolError: smol://alice@example.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: key is 31 bytes, expected 32",
|
||||||
|
"smol://alice@example.org/not-base32!": "SmolError: smol://alice@example.org/not-base32!: undecodable key: Non-base32 digit found",
|
||||||
|
"-alice@example.org": "SmolError: '-alice' must begin and end with a letter or digit and may not contain two separators in a row"
|
||||||
|
},
|
||||||
|
"base32": {
|
||||||
|
"identity": "da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292",
|
||||||
|
"encoded": "3jxsxqtdvxwobge7uxefpbordkulv6bc6ao3h4iusqlov4kgkkja",
|
||||||
|
"fingerprint": "3jxs xqtd vxwo bge7 uxef",
|
||||||
|
"fingerprint_note": "the first 20 base32 characters in groups of four: 100 bits, so a collision costs ~2^100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"envelopes": [
|
||||||
|
{
|
||||||
|
"name": "empty-body",
|
||||||
|
"note": "body_len 0. The payload is 109 bytes and the envelope 194.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[0]",
|
||||||
|
"body": "",
|
||||||
|
"body_len": 0,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4040404040404040404040404040404040404040404040404040404040404040",
|
||||||
|
"epk": "d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10",
|
||||||
|
"x25519_shared": "383fc0e605a085e558481bcf53dfc0247a6f2c1ad46fe659953cf36874000469",
|
||||||
|
"seal_key": "d035954d97b00442b87ce8025cafe438c7791079d407dc11b75915ace169609c",
|
||||||
|
"aad": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000000",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba100137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000000",
|
||||||
|
"signature": "e41a00f14ce5dd28ca4242be7f1d4e92c4ddc5256a9071982462da20644cb136629e0c6dd817db8b0d5dcd2d23239f0b807586193acea67a3abfb339804e3501",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000000e41a00f14ce5dd28ca4242be7f1d4e92c4ddc5256a9071982462da20644cb136629e0c6dd817db8b0d5dcd2d23239f0b807586193acea67a3abfb339804e3501",
|
||||||
|
"payload_len": 109,
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10786f5f9e9e91dd4e29fc0a8e9516fcde856e9f6dcf5010ba87e7878bf99db9030cca7b73d61fef8a4e23e24628f0e75b993c647361e41bd53d6ddd3a565b2ba334afaeb191f13dc801d9e7d9eeffad7eae12b0e2e59243c7a523db3fcdb9c21e3b83508f4905624e4ae5234ba51cb5633813ffff93b60de3f1ca8cd8a8",
|
||||||
|
"envelope_len": 194,
|
||||||
|
"envelope_sha256": "1a37555e369665f562b6a492de97a8c9cf024764b2fae32bf7bce33ebc9bc88f",
|
||||||
|
"message_id": "289dd78f0fb9ec8e27b1b648b1fc4cd36e388cad63e5320e8f331315998fc451",
|
||||||
|
"accept_mac": "1ce7f575843fa08ca8976213efbb951c8e13f119ff578da8c3f4091a15c284e5",
|
||||||
|
"send_body": "201ce7f575843fa08ca8976213efbb951c8e13f119ff578da8c3f4091a15c284e5534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292d7b5e81d336e578b13b8d706e82d061e3038c96bce66cdcf50d566b96ddbba10786f5f9e9e91dd4e29fc0a8e9516fcde856e9f6dcf5010ba87e7878bf99db9030cca7b73d61fef8a4e23e24628f0e75b993c647361e41bd53d6ddd3a565b2ba334afaeb191f13dc801d9e7d9eeffad7eae12b0e2e59243c7a523db3fcdb9c21e3b83508f4905624e4ae5234ba51cb5633813ffff93b60de3f1ca8cd8a8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unpadded",
|
||||||
|
"note": "The minimal form: 69 + 45 + 11 + 64 + 16 bytes. Start here.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[0]",
|
||||||
|
"body": "68656c6c6f20616c696365",
|
||||||
|
"body_len": 11,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4141414141414141414141414141414141414141414141414141414141414141",
|
||||||
|
"epk": "7a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522",
|
||||||
|
"x25519_shared": "012b8e969fb857162e7d00cef9e68b60ebc30a858439bf0c0e256b8807f70769",
|
||||||
|
"seal_key": "c4c72488dc2b6582952cb297d5d54b05b43ec7540cfdc173e55010f38d6444ed",
|
||||||
|
"aad": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e85220137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b68656c6c6f20616c696365",
|
||||||
|
"signature": "f4b5f898318a0c7da2f779a24972f1b51041bce5723d54e340f9887199c2f299b40b76ca3097562928cff3c2d8e02e7e78492f7f80ed6b0f784e1015b3f3b20c",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b68656c6c6f20616c696365f4b5f898318a0c7da2f779a24972f1b51041bce5723d54e340f9887199c2f299b40b76ca3097562928cff3c2d8e02e7e78492f7f80ed6b0f784e1015b3f3b20c",
|
||||||
|
"payload_len": 120,
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e85223f096c1be498f79a436bb6e96b7d425fc1173bd34c39f824e44c886a0291c1ce88d5956af3ee9d13b4c002eb5063388392b4777356a96d32e8940477577337b48af677c773482b47c49e558e7f0dd5b93396e5f38e3b7b01d4e2784bf4bd3df0aa84efabe12925c7522c8ca9ab867bd64aa38f1488bf2cac84f2d6fd1f6657a67991393a5607e721",
|
||||||
|
"envelope_len": 205,
|
||||||
|
"envelope_sha256": "8594cdb21ec3373f1516253e7db829e18471c066d8a891f71e9c75d547a696c5",
|
||||||
|
"message_id": "75f74029f5e6a60dae846767622c09177ec75ea8de171b1b4314e44faac26d35",
|
||||||
|
"accept_mac": "5cb318c5db75b0f37f8b3b8bf31a5899e5f673416c0c442a3d01abc8019b01b4",
|
||||||
|
"send_body": "205cb318c5db75b0f37f8b3b8bf31a5899e5f673416c0c442a3d01abc8019b01b4534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652927a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e85223f096c1be498f79a436bb6e96b7d425fc1173bd34c39f824e44c886a0291c1ce88d5956af3ee9d13b4c002eb5063388392b4777356a96d32e8940477577337b48af677c773482b47c49e558e7f0dd5b93396e5f38e3b7b01d4e2784bf4bd3df0aa84efabe12925c7522c8ca9ab867bd64aa38f1488bf2cac84f2d6fd1f6657a67991393a5607e721"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "padded",
|
||||||
|
"note": "The same message with \u00a75.3 padding to a 1024-byte multiple. A receiver must ignore the trailing zeros and trust body_len.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[0]",
|
||||||
|
"body": "68656c6c6f20616c696365",
|
||||||
|
"body_len": 11,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": true,
|
||||||
|
"esk": "4242424242424242424242424242424242424242424242424242424242424242",
|
||||||
|
"epk": "132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f472",
|
||||||
|
"x25519_shared": "2957671c03ec2f21c2d1a1c9e743564016a3107618f53077f02aff745c90ef02",
|
||||||
|
"seal_key": "db77b3dc494a2b299eadbbbdd3c3c0cd5e44a6b8c7ad7490729a63905668fbf4",
|
||||||
|
"aad": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f472",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f4720137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b68656c6c6f20616c696365",
|
||||||
|
"signature": "000e182ed5e1e10fc9ab0d1e358d54fb6ffb8fe9ea097fb795b900dfe3b9914734a11cfb0ddff7a5ce47d9b1f66fc48022b7402e2fbae0f32762632db59e7507",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000000b68656c6c6f20616c696365000e182ed5e1e10fc9ab0d1e358d54fb6ffb8fe9ea097fb795b900dfe3b9914734a11cfb0ddff7a5ce47d9b1f66fc48022b7402e2fbae0f32762632db59e750700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"payload_len": 1024,
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f47269e94cda06682623d97aed6f33fdd2ff38d48811450881350eb826904757f1e3e30ceeb0989c56e4638131f40968350f507f2d61d2320abb5cc3bc433340094c37bcc350abf0e7910a672879527c99e7a9f77cbff5e9d99138c56f75e9a98bf4128107a83176666c5db76298e338ce4be914cbecaca5ea11f14555cb7028ac1ccf41d0b330beada77c61eb42cab21097b005d1cf1ad60b5bfcd137a02ecd9aaf65a4692882d75e73e52d6ebcfe47bfead71ac9e54d7b1ac275aaf28a1926669303240910493ffdd92e396387dbfff545f4d8d6b598fcb4a79f48d681f03c70e7ee378bb2cef91c9f7447934d19aaaf36fd38434eef7dfcb2572b51ab49182278426cfb11f72614130a01907e1104ad5e2710072041085179eac48281396bbcc5de0f2f60d89934a70b4583dda1b45188609b7d0f95955938fb49aa2d61ad0c63d69a7f8da875b957bfff6f9b7d83a7ebb1e05f4099033e81cdaae5b0ab83960871f3da11ceabcdd710d589948e7cd7a0e3eedee7941ad250b224d8869e657b0af53d22ab8502919e40959acd69e303879d2ed2a4487e0d7d5a2203f886e2dd95c41655a3c4c09bcbb6b5fbff92f1694d5e1ad5c470bbe33eaa3254e416f72d32be96d46ecaf6fc12bd891f8d3a0dc8c36403b23f7875fa0afc518a307dcd3d7cb8846bbfdf40fedf49e3ea87a5f378050fa2f6ea8ea86a422c58f8e7c9a077cd3fbc69c4837d748a0d32678cc7f8b482d09e3c2c9eb5037cdf20824be0f0c1fe26893d7e3b04a4a16ed8c7910fbf1a106a12d4c145dae793b46e36176f3c621c3075adec0e445c033dc6d4fbe0f3c7f9419604361435e2af332cc428b038ab0e339c07248f8ac9fc520f722558ff015b372a5d9f90a867f606b63329b21dcd05587f13e3abe40be2f5c9177048c69168f087e071aecccff1eb3f04f95d9557dd6a40c37338765bff6de1861ca04e28a92d372efb92103a982d760aa89301b60c8d9a591c39492dad4467053f3c85ca226777a591434b814bdf86d4d7557665bdf118da32e2396eba2ca3dc24731690eee9bae9398f6b9ca81f449a486ef14f7db9c2c8e330c4b56569b24bdf1d53961d0338fd38e7ac345a1bccbd2ead3a2a133b798762bbddaf60f7e743b2ab0bc9f9b14b7755b02f9068ab9b4a8f154d6e9d6f61013281a6a7a69ebaa280f0fd7a7bc1734ddc37ef0af2d0628d5d6f7a62d345648e03b6aa84d2470a0883e79e962faa65b3547d2b4eac386cdcdcc2afa8ebf525183854d8baafbd09691edaa73c193ad54f99bea34ef8a2fd03ef6f869bd28bafb8377affdab5867cec7609eff5834d4cb7c9820a07fb17aebc6827ee1c7505f820d9be04973c0b264bb3be34d54cb2e1ac4edf437a3a065a85c4158af88952721c3d83195f898e514ba9e751060be46d2a6067e99c99a61d2959cb0d1b7ca6e76c207d26a505090fa3007a406471",
|
||||||
|
"envelope_len": 1109,
|
||||||
|
"envelope_sha256": "41a93d239e7da918e1e6480b724b8e23eeb39ddd454b889f6a55934658c143b2",
|
||||||
|
"message_id": "ff849aa8b573dfc10dc5ac731b862ba1205f42c81a014744f2812d3bf1d62a2e",
|
||||||
|
"accept_mac": "2b0a0b0804a78a32f47e461b8ed06e6c1a21ed750b2b33290cc91f884fb86ca0",
|
||||||
|
"send_body": "202b0a0b0804a78a32f47e461b8ed06e6c1a21ed750b2b33290cc91f884fb86ca0534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292132c442be010fbd57e72603328aa76e71fccc1503aae219327d14d9c9993f47269e94cda06682623d97aed6f33fdd2ff38d48811450881350eb826904757f1e3e30ceeb0989c56e4638131f40968350f507f2d61d2320abb5cc3bc433340094c37bcc350abf0e7910a672879527c99e7a9f77cbff5e9d99138c56f75e9a98bf4128107a83176666c5db76298e338ce4be914cbecaca5ea11f14555cb7028ac1ccf41d0b330beada77c61eb42cab21097b005d1cf1ad60b5bfcd137a02ecd9aaf65a4692882d75e73e52d6ebcfe47bfead71ac9e54d7b1ac275aaf28a1926669303240910493ffdd92e396387dbfff545f4d8d6b598fcb4a79f48d681f03c70e7ee378bb2cef91c9f7447934d19aaaf36fd38434eef7dfcb2572b51ab49182278426cfb11f72614130a01907e1104ad5e2710072041085179eac48281396bbcc5de0f2f60d89934a70b4583dda1b45188609b7d0f95955938fb49aa2d61ad0c63d69a7f8da875b957bfff6f9b7d83a7ebb1e05f4099033e81cdaae5b0ab83960871f3da11ceabcdd710d589948e7cd7a0e3eedee7941ad250b224d8869e657b0af53d22ab8502919e40959acd69e303879d2ed2a4487e0d7d5a2203f886e2dd95c41655a3c4c09bcbb6b5fbff92f1694d5e1ad5c470bbe33eaa3254e416f72d32be96d46ecaf6fc12bd891f8d3a0dc8c36403b23f7875fa0afc518a307dcd3d7cb8846bbfdf40fedf49e3ea87a5f378050fa2f6ea8ea86a422c58f8e7c9a077cd3fbc69c4837d748a0d32678cc7f8b482d09e3c2c9eb5037cdf20824be0f0c1fe26893d7e3b04a4a16ed8c7910fbf1a106a12d4c145dae793b46e36176f3c621c3075adec0e445c033dc6d4fbe0f3c7f9419604361435e2af332cc428b038ab0e339c07248f8ac9fc520f722558ff015b372a5d9f90a867f606b63329b21dcd05587f13e3abe40be2f5c9177048c69168f087e071aecccff1eb3f04f95d9557dd6a40c37338765bff6de1861ca04e28a92d372efb92103a982d760aa89301b60c8d9a591c39492dad4467053f3c85ca226777a591434b814bdf86d4d7557665bdf118da32e2396eba2ca3dc24731690eee9bae9398f6b9ca81f449a486ef14f7db9c2c8e330c4b56569b24bdf1d53961d0338fd38e7ac345a1bccbd2ead3a2a133b798762bbddaf60f7e743b2ab0bc9f9b14b7755b02f9068ab9b4a8f154d6e9d6f61013281a6a7a69ebaa280f0fd7a7bc1734ddc37ef0af2d0628d5d6f7a62d345648e03b6aa84d2470a0883e79e962faa65b3547d2b4eac386cdcdcc2afa8ebf525183854d8baafbd09691edaa73c193ad54f99bea34ef8a2fd03ef6f869bd28bafb8377affdab5867cec7609eff5834d4cb7c9820a07fb17aebc6827ee1c7505f820d9be04973c0b264bb3be34d54cb2e1ac4edf437a3a065a85c4158af88952721c3d83195f898e514ba9e751060be46d2a6067e99c99a61d2959cb0d1b7ca6e76c207d26a505090fa3007a406471"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "utf8",
|
||||||
|
"note": "body_len counts bytes, not characters: 29 bytes, 22 characters.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[0]",
|
||||||
|
"body": "4772c3bcc39f65206175732064656d204d65736820e2809420f09f9bb00a",
|
||||||
|
"body_len": 30,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4343434343434343434343434343434343434343434343434343434343434343",
|
||||||
|
"epk": "cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70",
|
||||||
|
"x25519_shared": "40ff868360e1f61dd53a383f4e8d96205ac1eab3dea7f1c69f9a8d44f853da79",
|
||||||
|
"seal_key": "3bab319a2a1e80a93c2639891f9dce11c35e8cbc558fea02f3c3ef494276b54b",
|
||||||
|
"aad": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001e",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c700137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001e4772c3bcc39f65206175732064656d204d65736820e2809420f09f9bb00a",
|
||||||
|
"signature": "86372500958b7808f6fb1878d40ccc9a2b090fbb042f834b6e131ee1c7d55ef3be7ecb44db86c59bee9dc4f2268fce878e0d6e79e046969f3667bbb8b6fd110e",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001e4772c3bcc39f65206175732064656d204d65736820e2809420f09f9bb00a86372500958b7808f6fb1878d40ccc9a2b090fbb042f834b6e131ee1c7d55ef3be7ecb44db86c59bee9dc4f2268fce878e0d6e79e046969f3667bbb8b6fd110e",
|
||||||
|
"payload_len": 139,
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70315b93e0580e4ce845678e9aa71eeb8bf2f8c3155a8c1fd58dc43c91095e0a19032a91231f3f7ef11167dac73e296e98c921c15c8d02e24747df1ffa600818abfb6858aa82184277da1d1fd02d1d59ada019418a2c708a7521e6a0f1e8e55f24f46d0bfa7d9c1ad761580f320f5c3248c18aac5158c02969926f427d921fee8ba4ccf2bff233733e64d5e3b50757f8f5b880d1f869651c9a186608",
|
||||||
|
"envelope_len": 224,
|
||||||
|
"envelope_sha256": "fcf78b183a0c2fad26f36b15e6cd222d1e864f48c7eea616f66aa65a14872b7c",
|
||||||
|
"message_id": "82c6d8c341ea821e4558dc6bb35f533e399a89646d9023437802b1d2499f2b88",
|
||||||
|
"accept_mac": "b221784d76eb2d52517d142912139ab0f6e61900ff35eed45998cb459683cf68",
|
||||||
|
"send_body": "20b221784d76eb2d52517d142912139ab0f6e61900ff35eed45998cb459683cf68534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cdefd8783a91b446640e2e1f95599db35e484a0071bd2182b3b60d0812c10c70315b93e0580e4ce845678e9aa71eeb8bf2f8c3155a8c1fd58dc43c91095e0a19032a91231f3f7ef11167dac73e296e98c921c15c8d02e24747df1ffa600818abfb6858aa82184277da1d1fd02d1d59ada019418a2c708a7521e6a0f1e8e55f24f46d0bfa7d9c1ad761580f320f5c3248c18aac5158c02969926f427d921fee8ba4ccf2bff233733e64d5e3b50757f8f5b880d1f869651c9a186608"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frontmatter",
|
||||||
|
"note": "\u00a75.5 frontmatter carrying the reserved Reply-To (\u00a75.7) and Accept (\u00a75.8) fields. Both are only meaningful because the signature covers them.",
|
||||||
|
"sender": "alice[0]",
|
||||||
|
"recipient": "bob[0]",
|
||||||
|
"body": "2d2d2d0a5375626a6563743a20746865207468696e670a5265706c792d546f3a20736d6f6c3a2f2f616c696365406578616d706c652e6f72672f336a7873787174647678776f626765377578656670626f72646b756c7636626336616f336834697573716c6f76346b676b6b6a610a4163636570743a20626268686a6a77367a64777963616c68656973756b617466347835643373376475326b666b7675343468373465766737646363710a2d2d2d0a48657265206973206120746f6b656e20736f20796f7572206d61696c20736b697073206d7920726571756573747320746965722e0a",
|
||||||
|
"body_len": 229,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||||
|
"epk": "ff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b",
|
||||||
|
"x25519_shared": "07c5ae79c7568952aa78668c613389e4ecdd2f61f207618532fb3c8c8a26f213",
|
||||||
|
"seal_key": "4dd79e54e611ac322ac0ace7e7a64b2c2139d422097f939c603ee6dd9843f7cc",
|
||||||
|
"aad": "534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b",
|
||||||
|
"payload_header": "01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292000000006955b900000000e5",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d736737b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292000000006955b900000000e52d2d2d0a5375626a6563743a20746865207468696e670a5265706c792d546f3a20736d6f6c3a2f2f616c696365406578616d706c652e6f72672f336a7873787174647678776f626765377578656670626f72646b756c7636626336616f336834697573716c6f76346b676b6b6a610a4163636570743a20626268686a6a77367a64777963616c68656973756b617466347835643373376475326b666b7675343468373465766737646363710a2d2d2d0a48657265206973206120746f6b656e20736f20796f7572206d61696c20736b697073206d7920726571756573747320746965722e0a",
|
||||||
|
"signature": "23e0b6bd5660ab109144e07b24affb7298041dafe908a469ab28c120f812b91dbcc7cee0763d058df4357d74ef4dee85f42da50dffdc66fb8604288a3c3d0000",
|
||||||
|
"payload": "01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292000000006955b900000000e52d2d2d0a5375626a6563743a20746865207468696e670a5265706c792d546f3a20736d6f6c3a2f2f616c696365406578616d706c652e6f72672f336a7873787174647678776f626765377578656670626f72646b756c7636626336616f336834697573716c6f76346b676b6b6a610a4163636570743a20626268686a6a77367a64777963616c68656973756b617466347835643373376475326b666b7675343468373465766737646363710a2d2d2d0a48657265206973206120746f6b656e20736f20796f7572206d61696c20736b697073206d7920726571756573747320746965722e0a23e0b6bd5660ab109144e07b24affb7298041dafe908a469ab28c120f812b91dbcc7cee0763d058df4357d74ef4dee85f42da50dffdc66fb8604288a3c3d0000",
|
||||||
|
"payload_len": 338,
|
||||||
|
"envelope": "534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b7d16509bc2c5683501cacd195740772232cdb04f5375e55dac3d49e258524c0eca1819d6911503a76d1b80e2e2048947659c9925faa4e2710420d0be9110cd216f651808f4132dc13d6a91c55d9ac653b161ad63cc70b7c9eac3b89d4994b04e791b195ca148845649a3a473f7dc26bfd0326e1f65b7559d12824920c136e502d1c29c3211a3ca7d0782df4655e31123de9d61ebb35fe9fcfd0ecdf0c7e406854ab7bfddbc818dd839e02ea450fd94124228a8bae41bf020962919de514525304a8f2c512b8335bbef48af719c340d63a7e38cd6ab40c004601edf67a572062182a15328825f017d10b8419edf162610ece5884539e8408bd6893a47b5d99d02bd944e98194424333ee1a99aabf600ec83ae4cabc38c12c0044bc16536d54a65d78df62428b4ba290296986b19d8e143cad25ee6c203515c3dcb3907f3adab87ec4dd4d5cdbfe24621beb2d4901939a02df296b67c996fef0b84fc591c87b9dc35be",
|
||||||
|
"envelope_len": 423,
|
||||||
|
"envelope_sha256": "4cd667f09da015acc5c6c1f57e57181334b89a11c2d7d543e4dcb2bf012bd698",
|
||||||
|
"message_id": "e6a52f744a8c0eab0c0a72c56ef51af96b83d9721d38dc669be38ca2c272f498",
|
||||||
|
"accept_mac": "8589379776f7d005c016593b5e04f624d7530f405213639361ab2a127004cd40",
|
||||||
|
"send_body": "208589379776f7d005c016593b5e04f624d7530f405213639361ab2a127004cd40534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cff2ee45601ec1b67310c7790404585ae697331eee1c1f8cf2419731c1fff3e6b7d16509bc2c5683501cacd195740772232cdb04f5375e55dac3d49e258524c0eca1819d6911503a76d1b80e2e2048947659c9925faa4e2710420d0be9110cd216f651808f4132dc13d6a91c55d9ac653b161ad63cc70b7c9eac3b89d4994b04e791b195ca148845649a3a473f7dc26bfd0326e1f65b7559d12824920c136e502d1c29c3211a3ca7d0782df4655e31123de9d61ebb35fe9fcfd0ecdf0c7e406854ab7bfddbc818dd839e02ea450fd94124228a8bae41bf020962919de514525304a8f2c512b8335bbef48af719c340d63a7e38cd6ab40c004601edf67a572062182a15328825f017d10b8419edf162610ece5884539e8408bd6893a47b5d99d02bd944e98194424333ee1a99aabf600ec83ae4cabc38c12c0044bc16536d54a65d78df62428b4ba290296986b19d8e143cad25ee6c203515c3dcb3907f3adab87ec4dd4d5cdbfe24621beb2d4901939a02df296b67c996fef0b84fc591c87b9dc35be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "superseded-key",
|
||||||
|
"note": "Sealed to alice's index-0 key while she is at index 1 (\u00a77). She opens it with the retained key, which is re-derivable from the master alone.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[0]",
|
||||||
|
"body": "73656e74206265666f726520796f7520726f74617465640a",
|
||||||
|
"body_len": 24,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4545454545454545454545454545454545454545454545454545454545454545",
|
||||||
|
"epk": "3286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b",
|
||||||
|
"x25519_shared": "bf14188f69b809b1c944f8d46c1d4a91f641b741537b88d98622d7f2ac310434",
|
||||||
|
"seal_key": "7367d84d99359d719fdb9fceb49b29c19b14636dd4fc2896774061d110973cc8",
|
||||||
|
"aad": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000018",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001873656e74206265666f726520796f7520726f74617465640a",
|
||||||
|
"signature": "4eb267c60e58d0f6acc2a9f0c39e384e16324ddb7bf8c23e14659682af1cfca526e5e72e39663c3d5207c929b0f65982f74ff7d3de4fce80d76bc8a39bbeac04",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001873656e74206265666f726520796f7520726f74617465640a4eb267c60e58d0f6acc2a9f0c39e384e16324ddb7bf8c23e14659682af1cfca526e5e72e39663c3d5207c929b0f65982f74ff7d3de4fce80d76bc8a39bbeac04",
|
||||||
|
"payload_len": 133,
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b4272b58c6b941ff140f69144dc10f1d49a0f9603e7a5a97718edc2e0617631d2fed71776274b6d7d2653c1fa889af93c9d57fbfd0d0b0ced3f8efdf59adaa23e3101c671d55850adbbb7d776c5f1a1296b6b4668041c0575d317b2e7bc796894091313cbf1c6be2382fe26229ba9bcfcee3281fd6b55731e06bf72bdd73efef8b4c32c6a269a2036fc272885f0f6551f9322f727ab",
|
||||||
|
"envelope_len": 218,
|
||||||
|
"envelope_sha256": "57c33559dc31934cae4d41e99954911422178b4a54a7c7cf5a8b3174485dee37",
|
||||||
|
"message_id": "292854907f2bc92223edabf57ceead7276402ee3569b18d2d0d40b0e2c50886b",
|
||||||
|
"accept_mac": "f417c61474e4c15bbecf8586a8b1adf37a03106aea0ef818a4b970b4e8d429e9",
|
||||||
|
"send_body": "20f417c61474e4c15bbecf8586a8b1adf37a03106aea0ef818a4b970b4e8d429e9534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf14652923286894cd2845a6db6a28fbf0677605f80e5a62385bf4e10a790ae5fde36736b4272b58c6b941ff140f69144dc10f1d49a0f9603e7a5a97718edc2e0617631d2fed71776274b6d7d2653c1fa889af93c9d57fbfd0d0b0ced3f8efdf59adaa23e3101c671d55850adbbb7d776c5f1a1296b6b4668041c0575d317b2e7bc796894091313cbf1c6be2382fe26229ba9bcfcee3281fd6b55731e06bf72bdd73efef8b4c32c6a269a2036fc272885f0f6551f9322f727ab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rotated-key",
|
||||||
|
"note": "The same correspondents after alice's rotation. Only `to` changes; her accept token does not.",
|
||||||
|
"sender": "bob[0]",
|
||||||
|
"recipient": "alice[1]",
|
||||||
|
"body": "73656e7420616674657220796f7520726f74617465640a",
|
||||||
|
"body_len": 23,
|
||||||
|
"time": 1767225600,
|
||||||
|
"padded": false,
|
||||||
|
"esk": "4646464646464646464646464646464646464646464646464646464646464646",
|
||||||
|
"epk": "a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75",
|
||||||
|
"x25519_shared": "e8265154d3d8063d89c7b591427fab6ae3b3207bc8ba87d287c2c10bcbfb5b4a",
|
||||||
|
"seal_key": "b4e8a0876228c50744d1f3cde417d6299be612b485554300e44434e2f9446106",
|
||||||
|
"aad": "534d4f4c01cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75",
|
||||||
|
"payload_header": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b90000000017",
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f31206d7367cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a750137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001773656e7420616674657220796f7520726f74617465640a",
|
||||||
|
"signature": "857ef01e8bd0d7aee6650a86d189c9cb384c1bf6d1cc47c8666e67d88270d0d1b10a2b53388c8a9aca1c6fc0645a40cd8cb4f8af58cdc2e06801918e4e475203",
|
||||||
|
"payload": "0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471c000000006955b9000000001773656e7420616674657220796f7520726f74617465640a857ef01e8bd0d7aee6650a86d189c9cb384c1bf6d1cc47c8666e67d88270d0d1b10a2b53388c8a9aca1c6fc0645a40cd8cb4f8af58cdc2e06801918e4e475203",
|
||||||
|
"payload_len": 132,
|
||||||
|
"envelope": "534d4f4c01cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75559e854a04d51fd20336516fa2471e8b13784f6597813601f15c8f3d9aa95c049f9671ab843e1d40dc0eccf227942b48240590da1e753e443129d8ffdaf032d7f5d2298eb7b426bd937faf52af2c0303c33e2578574c37c0492a2b80a793d539c3ecd13a50dcd6d9ed86510749ae97e0f256d45ad8a4ea0bed15e50a125933d17e11fc3615f288c5aeeaf706f7dbf279db165e53",
|
||||||
|
"envelope_len": 217,
|
||||||
|
"envelope_sha256": "b8fbe91f24f6f8969a7a6e2aefaf03a0f8e8e8947d8521baa9fa641c002344c4",
|
||||||
|
"message_id": "72c054747b8edd9c710df7ab295ed832ef791e6db4a585821968d8dff9ae3b02",
|
||||||
|
"accept_mac": "e7d594c129757cdbf628b354c592cf566f6b40d95fa7f6a7c6be6ca2696917cb",
|
||||||
|
"send_body": "20e7d594c129757cdbf628b354c592cf566f6b40d95fa7f6a7c6be6ca2696917cb534d4f4c01cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466a28a7c44ede257d664fbf156affa7da8abb3ae74b9fee8d7a2078543504e1a75559e854a04d51fd20336516fa2471e8b13784f6597813601f15c8f3d9aa95c049f9671ab843e1d40dc0eccf227942b48240590da1e753e443129d8ffdaf032d7f5d2298eb7b426bd937faf52af2c0303c33e2578574c37c0492a2b80a793d539c3ecd13a50dcd6d9ed86510749ae97e0f256d45ad8a4ea0bed15e50a125933d17e11fc3615f288c5aeeaf706f7dbf279db165e53"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"session": {
|
||||||
|
"note": "The AUTH signature covers the Noise handshake hash, so it cannot be replayed to another session or server. The REGISTER signature covers the server's static key for the same reason: without it, any party could bind any public key to any username.",
|
||||||
|
"handshake_hash": "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||||
|
"server_static": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f",
|
||||||
|
"auth_signed_preimage": "736d6f6c6d61696c2f312061757468808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||||
|
"auth_signature": "26936929ab0ead5f407d69a6516f32d2b871f41d73345b9b7d076d85eea9b6736ea0f522bcff8511c2aea1f8cf0e7a7d3f1f81f3d6d5654a27c5b81e462fa903",
|
||||||
|
"auth_frame": "05616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf146529226936929ab0ead5f407d69a6516f32d2b871f41d73345b9b7d076d85eea9b6736ea0f522bcff8511c2aea1f8cf0e7a7d3f1f81f3d6d5654a27c5b81e462fa903010001084e74a6dec8ed8101672225450265e5fa3dcbe3a69455569ce1ffc254df1885",
|
||||||
|
"auth_frame_note": "username_len=5 || 'alice' || identity || signature || sync=1 || count=1 || alice's token for bob",
|
||||||
|
"register_signed_preimage": "736d6f6c6d61696c2f31207265676973746572606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292",
|
||||||
|
"register_signature": "78364170da13b57566df066c66756c29ba4f080e8b75e3503ac7614534cff3486b888455fd5e6ad756edb4ed28567aab8ccc3820b0289d15c48720066818220f",
|
||||||
|
"register_frame": "05616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf146529278364170da13b57566df066c66756c29ba4f080e8b75e3503ac7614534cff3486b888455fd5e6ad756edb4ed28567aab8ccc3820b0289d15c48720066818220f0000",
|
||||||
|
"register_frame_note": "token_len=0 and cert_len=0: a first registration, no invite token and no rotation certificate"
|
||||||
|
},
|
||||||
|
"rotation": {
|
||||||
|
"note": "A certificate is 200 bytes and carries no username: the name is covered by both signatures but not transported, so a certificate cannot be replayed against another username bound to the same key.",
|
||||||
|
"username": "alice",
|
||||||
|
"old_key": "da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292",
|
||||||
|
"new_key": "cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466",
|
||||||
|
"time": 1767225600,
|
||||||
|
"signed_preimage": "736d6f6c6d61696c2f3120726f74617465616c696365da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466000000006955b900",
|
||||||
|
"sig_old": "ba3bd58ab28a0bb6758bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e61959a523429ea2d4d47622df470743caf298a19e43b9b03",
|
||||||
|
"sig_new": "58a928e912485548a74c2d8e9d14553a5f90c34411848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad774a614283c2268b958a0e",
|
||||||
|
"certificate": "da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466000000006955b900ba3bd58ab28a0bb6758bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e61959a523429ea2d4d47622df470743caf298a19e43b9b0358a928e912485548a74c2d8e9d14553a5f90c34411848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad774a614283c2268b958a0e",
|
||||||
|
"resolve_body": "cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e46601da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292cb9046928dd2bede87ddc5ca42378fbd6f987e425b34a2801f74b768da39e466000000006955b900ba3bd58ab28a0bb6758bfda39b95c2e391ef922a60a010e74543c255b6689f5e81bef8ee3f84e88e61959a523429ea2d4d47622df470743caf298a19e43b9b0358a928e912485548a74c2d8e9d14553a5f90c34411848a8d612fb55600539f762849102530f0ab411914e48879a62b33b1297a18ad774a614283c2268b958a0e",
|
||||||
|
"walk_chain": [
|
||||||
|
{
|
||||||
|
"case": "pinned=old, offered=new, chain=[cert], username=alice",
|
||||||
|
"accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "same certificate checked as username=mallory",
|
||||||
|
"accepted": false,
|
||||||
|
"why": "both signatures cover the username"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "pinned=old, offered=new, empty chain",
|
||||||
|
"accepted": false,
|
||||||
|
"why": "a key change needs a chain; absent means ask the user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"case": "sig_new zeroed",
|
||||||
|
"accepted": false,
|
||||||
|
"why": "the new key must sign too, or the old key alone could hand the username to a key nobody controls"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frontmatter": [
|
||||||
|
{
|
||||||
|
"name": "no-block",
|
||||||
|
"note": "no leading '---\\n', so the whole body is text",
|
||||||
|
"body": "hello\n",
|
||||||
|
"fields": {},
|
||||||
|
"text": "hello\n",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "simple",
|
||||||
|
"note": "one field, then the text",
|
||||||
|
"body": "---\nSubject: hi\n---\nbody\n",
|
||||||
|
"fields": {
|
||||||
|
"subject": "hi"
|
||||||
|
},
|
||||||
|
"text": "body\n",
|
||||||
|
"block_accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "empty-block",
|
||||||
|
"note": "an empty block escapes a body that genuinely starts with '---'",
|
||||||
|
"body": "---\n---\n---\nliteral dashes\n",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nliteral dashes\n",
|
||||||
|
"block_accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "case-and-duplicates",
|
||||||
|
"note": "keys are lowercased for comparison and the first occurrence wins",
|
||||||
|
"body": "---\nSubject: first\nsubject: second\nX-Mood: calm\n---\nb",
|
||||||
|
"fields": {
|
||||||
|
"subject": "first",
|
||||||
|
"x-mood": "calm"
|
||||||
|
},
|
||||||
|
"text": "b",
|
||||||
|
"block_accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value-trimmed",
|
||||||
|
"note": "the value is the rest of the line, trimmed",
|
||||||
|
"body": "---\nSubject: spaced out \n---\n",
|
||||||
|
"fields": {
|
||||||
|
"subject": "spaced out"
|
||||||
|
},
|
||||||
|
"text": "",
|
||||||
|
"block_accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "colon-in-value",
|
||||||
|
"note": "only the first colon separates",
|
||||||
|
"body": "---\nSubject: re: the thing\n---\n",
|
||||||
|
"fields": {
|
||||||
|
"subject": "re: the thing"
|
||||||
|
},
|
||||||
|
"text": "",
|
||||||
|
"block_accepted": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "no-colon",
|
||||||
|
"note": "a malformed line invalidates the whole block, which becomes text",
|
||||||
|
"body": "---\nSubject hi\n---\nbody\n",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nSubject hi\n---\nbody\n",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bad-key",
|
||||||
|
"note": "key charset is [A-Za-z0-9-]",
|
||||||
|
"body": "---\nSub ject: hi\n---\nbody\n",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nSub ject: hi\n---\nbody\n",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unterminated",
|
||||||
|
"note": "no closing '---'",
|
||||||
|
"body": "---\nSubject: hi\nbody\n",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nSubject: hi\nbody\n",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "too-many-keys",
|
||||||
|
"note": "over 64 keys",
|
||||||
|
"body": "---\nK0: v\nK1: v\nK2: v\nK3: v\nK4: v\nK5: v\nK6: v\nK7: v\nK8: v\nK9: v\nK10: v\nK11: v\nK12: v\nK13: v\nK14: v\nK15: v\nK16: v\nK17: v\nK18: v\nK19: v\nK20: v\nK21: v\nK22: v\nK23: v\nK24: v\nK25: v\nK26: v\nK27: v\nK28: v\nK29: v\nK30: v\nK31: v\nK32: v\nK33: v\nK34: v\nK35: v\nK36: v\nK37: v\nK38: v\nK39: v\nK40: v\nK41: v\nK42: v\nK43: v\nK44: v\nK45: v\nK46: v\nK47: v\nK48: v\nK49: v\nK50: v\nK51: v\nK52: v\nK53: v\nK54: v\nK55: v\nK56: v\nK57: v\nK58: v\nK59: v\nK60: v\nK61: v\nK62: v\nK63: v\nK64: v\n---\nb",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nK0: v\nK1: v\nK2: v\nK3: v\nK4: v\nK5: v\nK6: v\nK7: v\nK8: v\nK9: v\nK10: v\nK11: v\nK12: v\nK13: v\nK14: v\nK15: v\nK16: v\nK17: v\nK18: v\nK19: v\nK20: v\nK21: v\nK22: v\nK23: v\nK24: v\nK25: v\nK26: v\nK27: v\nK28: v\nK29: v\nK30: v\nK31: v\nK32: v\nK33: v\nK34: v\nK35: v\nK36: v\nK37: v\nK38: v\nK39: v\nK40: v\nK41: v\nK42: v\nK43: v\nK44: v\nK45: v\nK46: v\nK47: v\nK48: v\nK49: v\nK50: v\nK51: v\nK52: v\nK53: v\nK54: v\nK55: v\nK56: v\nK57: v\nK58: v\nK59: v\nK60: v\nK61: v\nK62: v\nK63: v\nK64: v\n---\nb",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "too-large",
|
||||||
|
"note": "over 4 KiB",
|
||||||
|
"body": "---\nK: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n---\nb",
|
||||||
|
"fields": {},
|
||||||
|
"text": "---\nK: vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n---\nb",
|
||||||
|
"block_accepted": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "injected-field",
|
||||||
|
"note": "what a line break in a sender-supplied value produces: a second field, signed and indistinguishable from an intended one. A client building frontmatter MUST refuse a value containing a line break.",
|
||||||
|
"body": "---\nSubject: hi\nAccept: aaaaaaaa\n---\nb",
|
||||||
|
"fields": {
|
||||||
|
"subject": "hi",
|
||||||
|
"accept": "aaaaaaaa"
|
||||||
|
},
|
||||||
|
"text": "b",
|
||||||
|
"block_accepted": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rejects": {
|
||||||
|
"note": "Inputs a conformant implementation refuses. The envelope cases are mutations of one valid message, listed with the reference's own error, so a client can be tested against its own failure path.",
|
||||||
|
"valid_baseline": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelopes": [
|
||||||
|
{
|
||||||
|
"name": "bad-magic",
|
||||||
|
"why": "magic is not 'SMOL' (\u00a75.1)",
|
||||||
|
"envelope": "584d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: not a smolmail envelope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bad-envelope-version",
|
||||||
|
"why": "unknown envelope version; the sealed-box construction may differ",
|
||||||
|
"envelope": "534d4f4c02da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: unsupported envelope version 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "truncated",
|
||||||
|
"why": "shorter than the 69-byte header plus a 16-byte tag",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d36558252",
|
||||||
|
"envelope_len": 69,
|
||||||
|
"rejected_with": "SmolError: envelope too short"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tag-flipped",
|
||||||
|
"why": "the Poly1305 tag no longer authenticates",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2be",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: decryption failed: wrong key or corrupt envelope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aad-tampered",
|
||||||
|
"why": "epk is covered by the AAD, so altering it breaks the tag rather than silently re-addressing the message",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ff2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: decryption failed: wrong key or corrupt envelope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "not-for-us",
|
||||||
|
"why": "`to` names a key we do not hold; a client MUST check it against its own",
|
||||||
|
"envelope": "534d4f4c0137b39278f08beadeecc5961640eb7634bd664f12ed40c3d69482045607c9471cc306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: addressed to g6zze6hqrpvn53gf\u2026, not one of our keys"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bad-signature",
|
||||||
|
"why": "decrypts cleanly and the signature is zeroed. An implementation that skips \u00a75.3 verification will accept forged mail here.",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3daf229b9065119b10570cbeec2967e78dc7d26906bda10ec4702b50a7d0f93418a97ab7aa5b6d10455d6ba65081e74824e96e1d637a4dc26845810958fe11269209adcd49b5e76471b2c4df881338ba42",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: signature does not verify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "signature-bound-to-other-recipient",
|
||||||
|
"why": "a payload signed for bob, resealed to alice. The signature covers `to`, so re-addressing a recorded message must not verify.",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3db7459221085c65f94569e41f2a7f00d84dc2045e491c300ebdfa3f8ea2af25601ad057d0013ef1fad9564dc1f24b034f4ebff12458d979f593fe9fa4d5a70595bf0215f69abc1e1197d0b5979071727f",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: signature does not verify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body-len-overflows",
|
||||||
|
"why": "body_len exceeds the payload (\u00a75.3)",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2bbdfc2dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd977b2479cfcb4d3883cf888582df8b22f0",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: payload body length exceeds the payload"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payload-version",
|
||||||
|
"why": "unknown payload version",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825290a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97372cf15fd0d6c068a477ac4c35c10862",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: unsupported payload version"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dated-in-the-future",
|
||||||
|
"why": "time is 4102444800, more than 86400s ahead of any clock before 2100 (\u00a75.3)",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292c306fb0ef2bf8b7f93bad98155fa37daec74db0c4cbeda6c6f1dba9d3655825293a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd8b19bf25e2abdfc4c605dfcb7d3da3c142dc7b0dbabf90b361744b6247bb8e9fc9407d51b383f018556d9fbfd999beaeb49bec2d719ce3c62785f850ce261eb68784050fd68f1e7655fcae4a659fe7353abcadb4da68e9b733c5704cbb51",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: payload is dated in the future"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "low-order-epk",
|
||||||
|
"why": "a low-order ephemeral key, which \u00a72 requires be refused",
|
||||||
|
"envelope": "534d4f4c01da6f2bc263adece0989fa5c85785d11aa8baf822f01db3f1149416eaf1465292e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b80093a846f5413d3a945f8aa55bdc4f0fffd51e4f59624b753b1691571500e2db82aa003ffedd16ca5125e2abdfc4dc05c7c76b3d13e7aaee7c7d9eb6702bb90b0cbce8e59b7542200107e1fcda8f2f546267189830879a2d6d9493eec48c918ec35e855eb7381ee060a09b38e429c221d489cd97c23c37031b96ad8d633e2c6b0d95f2bf",
|
||||||
|
"envelope_len": 200,
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"low_order_x25519": [
|
||||||
|
{
|
||||||
|
"x25519_public": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x25519_public": "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
"rejected_with": "SmolError: rejected key agreement: Unexpected library error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"low_order_note": "\u00a72 and \u00a74: a received ephemeral public key that is a low-order point MUST be refused, and an all-zero X25519 output MUST be refused. libsodium reports the first as the second, since the agreement output is zero.",
|
||||||
|
"invalid_identity_keys": [
|
||||||
|
{
|
||||||
|
"identity_key": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"rejected_with": "SmolError: not a valid Ed25519 public key: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity_key": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"rejected_with": "SmolError: not a valid Ed25519 public key: Unexpected library error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity_key": "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"rejected_with": "SmolError: not a valid Ed25519 public key: Unexpected library error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invalid_identity_note": "Keys that do not decode as Ed25519 points, or are of small order, so \u00a72's map to X25519 has no valid result."
|
||||||
|
}
|
||||||
|
}
|
||||||
711
vectors.py
Normal file
711
vectors.py
Normal file
|
|
@ -0,0 +1,711 @@
|
||||||
|
#!/usr/bin/env -S uv run --quiet --script
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = ["noiseprotocol>=0.3.1", "pynacl>=1.5"]
|
||||||
|
# ///
|
||||||
|
# Copyright 2026 randogoth
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
"""Test vectors for SPEC.md 1.2, generated from the reference implementation.
|
||||||
|
|
||||||
|
Every value is produced by calling into smolmail.py rather than restated here,
|
||||||
|
so the vectors cannot drift from the code they document. Each one is also
|
||||||
|
verified from the opposite side before it is emitted: signatures are checked,
|
||||||
|
envelopes are unsealed, and the rejection cases are confirmed to actually
|
||||||
|
raise. `--check` repeats the generation and compares it against the committed
|
||||||
|
files, which is what makes them a regression test rather than a snapshot.
|
||||||
|
|
||||||
|
uv run vectors.py # write vectors.json and VECTORS.md
|
||||||
|
uv run vectors.py --check # verify the committed files still reproduce
|
||||||
|
|
||||||
|
Determinism: seal() draws its ephemeral from os.urandom and its timestamp from
|
||||||
|
the clock (§5.2), so both are pinned here. Nothing else in the protocol is
|
||||||
|
randomised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
import contextlib
|
||||||
|
import difflib
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import nacl.bindings as sodium
|
||||||
|
|
||||||
|
import smolmail as sm
|
||||||
|
|
||||||
|
# Obvious test data: two masters that no one could mistake for a real secret.
|
||||||
|
ALICE_MASTER = bytes(range(0x00, 0x20))
|
||||||
|
BOB_MASTER = bytes(range(0x20, 0x40))
|
||||||
|
SERVER_STATIC = bytes(range(0x60, 0x80))
|
||||||
|
HANDSHAKE_HASH = bytes(range(0x80, 0xA0))
|
||||||
|
|
||||||
|
WHEN = calendar.timegm((2026, 1, 1, 0, 0, 0))
|
||||||
|
FUTURE = calendar.timegm((2100, 1, 1, 0, 0, 0)) # beyond §5.3's 86400s skew
|
||||||
|
|
||||||
|
HOST = "example.org"
|
||||||
|
|
||||||
|
# §4's low-order blacklist. Every one of these must be refused as an ephemeral
|
||||||
|
# public key; the generator asserts that the reference actually refuses them.
|
||||||
|
LOW_ORDER = [
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800",
|
||||||
|
"5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157",
|
||||||
|
"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def h(raw: bytes) -> str:
|
||||||
|
return raw.hex()
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def ephemeral(esk: bytes):
|
||||||
|
"""Pin seal()'s per-message ephemeral so an envelope reproduces byte for byte."""
|
||||||
|
real = os.urandom
|
||||||
|
sm.os.urandom = lambda n: esk if n == sm.KEY_LEN else real(n)
|
||||||
|
try:
|
||||||
|
yield sodium.crypto_scalarmult_base(esk)
|
||||||
|
finally:
|
||||||
|
sm.os.urandom = real
|
||||||
|
|
||||||
|
|
||||||
|
def plaintext_of(sender: sm.Identity, to: bytes, body: bytes, when: int,
|
||||||
|
epk: bytes, pad: bool) -> tuple[bytes, bytes, bytes]:
|
||||||
|
"""§5.3's payload, returned with the header and the signed preimage.
|
||||||
|
|
||||||
|
seal() builds this internally and keeps none of it, but an implementer
|
||||||
|
debugging a signature needs the exact bytes that went under the key. The
|
||||||
|
emitted envelope is asserted equal to seal()'s, which keeps this honest.
|
||||||
|
"""
|
||||||
|
header = bytes([sm.VERSION]) + sender.pk + struct.pack(">qI", when, len(body))
|
||||||
|
signed = sm.LABEL_MSG + to + epk + header + body
|
||||||
|
payload = header + body + sender.sign(signed)
|
||||||
|
if pad:
|
||||||
|
payload += bytes(-len(payload) % sm.PAD_TO)
|
||||||
|
return header, signed, payload
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(payload: bytes, to: bytes, esk: bytes) -> bytes:
|
||||||
|
"""§5.2's sealing step alone, so a vector can carry a deliberately broken
|
||||||
|
payload that still passes the AEAD."""
|
||||||
|
epk = sodium.crypto_scalarmult_base(esk)
|
||||||
|
key = sm.hkdf_sha256(sm.agree(esk, sm.ed_to_x25519_pub(to)), epk + to, sm.LABEL_SEAL)
|
||||||
|
aad = sm.MAGIC + bytes([sm.VERSION]) + to + epk
|
||||||
|
return aad + sodium.crypto_aead_chacha20poly1305_ietf_encrypt(payload, aad, bytes(12), key)
|
||||||
|
|
||||||
|
|
||||||
|
def raises(fn) -> str:
|
||||||
|
"""Run a case that must fail and return the reference's own message."""
|
||||||
|
try:
|
||||||
|
fn()
|
||||||
|
except (sm.SmolError, Exception) as exc: # noqa: BLE001 - any refusal counts
|
||||||
|
return f"{type(exc).__name__}: {exc}"
|
||||||
|
raise AssertionError("expected a rejection, got none")
|
||||||
|
|
||||||
|
|
||||||
|
# --- §2 identity ------------------------------------------------------------
|
||||||
|
|
||||||
|
def identity_vectors() -> list[dict]:
|
||||||
|
out = []
|
||||||
|
for name, master, index in [("alice", ALICE_MASTER, 0), ("alice", ALICE_MASTER, 1),
|
||||||
|
("bob", BOB_MASTER, 0)]:
|
||||||
|
seed = sm.identity_seed(master, index)
|
||||||
|
who = sm.Identity(seed)
|
||||||
|
out.append({
|
||||||
|
"name": name,
|
||||||
|
"master": h(master),
|
||||||
|
"rotation_index": index,
|
||||||
|
"seed": h(seed),
|
||||||
|
"identity_key": h(who.pk),
|
||||||
|
"x25519_private": h(who.x_priv),
|
||||||
|
"x25519_public": h(sm.ed_to_x25519_pub(who.pk)),
|
||||||
|
"base32": sm.b32(who.pk),
|
||||||
|
"fingerprint": sm.fingerprint(who.pk),
|
||||||
|
"uri": f"smol://{name}@{HOST}/{sm.b32(who.pk)}",
|
||||||
|
})
|
||||||
|
# The X25519 keypair must agree with itself, or the §2 map is wrong.
|
||||||
|
assert sodium.crypto_scalarmult_base(who.x_priv) == sm.ed_to_x25519_pub(who.pk)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def accept_vectors(alice: sm.Account, bob: sm.Account) -> dict:
|
||||||
|
token = alice.token_for(bob.me.pk)
|
||||||
|
assert token == hmac.new(sm.accept_key(ALICE_MASTER), bob.me.pk, hashlib.sha256).digest()
|
||||||
|
return {
|
||||||
|
"note": "alice's accept key, and the token she issues to bob. The accept "
|
||||||
|
"key is derived without the rotation index, so it and every token "
|
||||||
|
"from it survive rotation.",
|
||||||
|
"alice_accept_key": h(sm.accept_key(ALICE_MASTER)),
|
||||||
|
"bob_accept_key": h(sm.accept_key(BOB_MASTER)),
|
||||||
|
"alice_token_for_bob": h(token),
|
||||||
|
"alice_token_for_bob_base32": sm.b32(token),
|
||||||
|
"bob_token_for_alice_0": h(bob.token_for(alice.keys[0].pk)),
|
||||||
|
"alice_token_for_bob_rotated": h(alice.token_for(
|
||||||
|
sm.Identity(sm.identity_seed(BOB_MASTER, 1)).pk)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- §5 messages ------------------------------------------------------------
|
||||||
|
|
||||||
|
def envelope_vectors(alice: sm.Account, bob: sm.Account) -> list[dict]:
|
||||||
|
reply_to = f"smol://alice@{HOST}/{sm.b32(alice.keys[0].pk)}"
|
||||||
|
accept = sm.b32(alice.token_for(bob.me.pk))
|
||||||
|
cases = [
|
||||||
|
{"name": "empty-body", "from": ("bob", 0), "to": ("alice", 0), "pad": False,
|
||||||
|
"body": b"",
|
||||||
|
"note": "body_len 0. The payload is 109 bytes and the envelope 194."},
|
||||||
|
{"name": "unpadded", "from": ("bob", 0), "to": ("alice", 0), "pad": False,
|
||||||
|
"body": b"hello alice",
|
||||||
|
"note": "The minimal form: 69 + 45 + 11 + 64 + 16 bytes. Start here."},
|
||||||
|
{"name": "padded", "from": ("bob", 0), "to": ("alice", 0), "pad": True,
|
||||||
|
"body": b"hello alice",
|
||||||
|
"note": "The same message with §5.3 padding to a 1024-byte multiple. A "
|
||||||
|
"receiver must ignore the trailing zeros and trust body_len."},
|
||||||
|
{"name": "utf8", "from": ("bob", 0), "to": ("alice", 0), "pad": False,
|
||||||
|
"body": "Grüße aus dem Mesh — 🛰\n".encode(),
|
||||||
|
"note": "body_len counts bytes, not characters: 29 bytes, 22 characters."},
|
||||||
|
{"name": "frontmatter", "from": ("alice", 0), "to": ("bob", 0), "pad": False,
|
||||||
|
"body": sm.build_frontmatter(
|
||||||
|
[("Subject", "the thing"), ("Reply-To", reply_to), ("Accept", accept)],
|
||||||
|
"Here is a token so your mail skips my requests tier.\n").encode(),
|
||||||
|
"note": "§5.5 frontmatter carrying the reserved Reply-To (§5.7) and Accept "
|
||||||
|
"(§5.8) fields. Both are only meaningful because the signature "
|
||||||
|
"covers them."},
|
||||||
|
{"name": "superseded-key", "from": ("bob", 0), "to": ("alice", 0), "pad": False,
|
||||||
|
"body": b"sent before you rotated\n",
|
||||||
|
"note": "Sealed to alice's index-0 key while she is at index 1 (§7). She "
|
||||||
|
"opens it with the retained key, which is re-derivable from the "
|
||||||
|
"master alone."},
|
||||||
|
{"name": "rotated-key", "from": ("bob", 0), "to": ("alice", 1), "pad": False,
|
||||||
|
"body": b"sent after you rotated\n",
|
||||||
|
"note": "The same correspondents after alice's rotation. Only `to` changes; "
|
||||||
|
"her accept token does not."},
|
||||||
|
]
|
||||||
|
accounts = {"alice": alice, "bob": bob}
|
||||||
|
out = []
|
||||||
|
for n, case in enumerate(cases):
|
||||||
|
esk = bytes([0x40 + n]) * 32
|
||||||
|
sender = accounts[case["from"][0]].keys[case["from"][1]]
|
||||||
|
recipient = accounts[case["to"][0]].keys[case["to"][1]].pk
|
||||||
|
body = case["body"]
|
||||||
|
with ephemeral(esk) as epk:
|
||||||
|
env = sm.seal(sender, recipient, body, when=WHEN, pad=case["pad"])
|
||||||
|
header, signed, payload = plaintext_of(sender, recipient, body, WHEN, epk, case["pad"])
|
||||||
|
# plaintext_of/encrypt duplicate seal()'s layout; pin them to it.
|
||||||
|
assert encrypt(payload, recipient, esk) == env, case["name"]
|
||||||
|
|
||||||
|
opened = sm.unseal(accounts[case["to"][0]].keys, env)
|
||||||
|
assert opened["body"] == body and opened["sender"] == sender.pk
|
||||||
|
assert opened["time"] == WHEN and opened["id"] == sm.message_id(env)
|
||||||
|
|
||||||
|
shared = sm.agree(esk, sm.ed_to_x25519_pub(recipient))
|
||||||
|
token = accounts[case["to"][0]].token_for(sender.pk)
|
||||||
|
out.append({
|
||||||
|
"name": case["name"],
|
||||||
|
"note": case["note"],
|
||||||
|
"sender": f"{case['from'][0]}[{case['from'][1]}]",
|
||||||
|
"recipient": f"{case['to'][0]}[{case['to'][1]}]",
|
||||||
|
"body": h(body),
|
||||||
|
"body_len": len(body),
|
||||||
|
"time": WHEN,
|
||||||
|
"padded": case["pad"],
|
||||||
|
"esk": h(esk),
|
||||||
|
"epk": h(epk),
|
||||||
|
"x25519_shared": h(shared),
|
||||||
|
"seal_key": h(sm.hkdf_sha256(shared, epk + recipient, sm.LABEL_SEAL)),
|
||||||
|
"aad": h(sm.MAGIC + bytes([sm.VERSION]) + recipient + epk),
|
||||||
|
"payload_header": h(header),
|
||||||
|
"signed_preimage": h(signed),
|
||||||
|
"signature": h(payload[sm.PAYLOAD_HEADER + len(body):][:sm.SIG_LEN]),
|
||||||
|
"payload": h(payload),
|
||||||
|
"payload_len": len(payload),
|
||||||
|
"envelope": h(env),
|
||||||
|
"envelope_len": len(env),
|
||||||
|
"envelope_sha256": h(hashlib.sha256(env).digest()),
|
||||||
|
"message_id": h(sm.message_id(env)),
|
||||||
|
"accept_mac": h(sm.accept_mac(token, sm.message_id(env))),
|
||||||
|
"send_body": h(bytes([sm.TOKEN_LEN]) + sm.accept_mac(token, sm.message_id(env)) + env),
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# --- §4, §6.1 and §7 wire bytes ---------------------------------------------
|
||||||
|
|
||||||
|
def session_vectors(alice: sm.Account) -> dict:
|
||||||
|
me = alice.keys[0]
|
||||||
|
name = b"alice"
|
||||||
|
auth_sig = me.sign(sm.LABEL_AUTH + HANDSHAKE_HASH)
|
||||||
|
assert sm.verify_sig(me.pk, auth_sig, sm.LABEL_AUTH + HANDSHAKE_HASH)
|
||||||
|
token = alice.token_for(sm.Identity(sm.identity_seed(BOB_MASTER, 0)).pk)
|
||||||
|
auth_frame = (bytes([len(name)]) + name + me.pk + auth_sig
|
||||||
|
+ bytes([1]) + struct.pack(">H", 1) + token)
|
||||||
|
|
||||||
|
reg_preimage = sm.register_signed(SERVER_STATIC, "alice", me.pk)
|
||||||
|
reg_sig = me.sign(reg_preimage)
|
||||||
|
assert sm.verify_sig(me.pk, reg_sig, reg_preimage)
|
||||||
|
reg_frame = (bytes([len(name)]) + name + me.pk + reg_sig + b"\0" + b"\0")
|
||||||
|
return {
|
||||||
|
"note": "The AUTH signature covers the Noise handshake hash, so it cannot "
|
||||||
|
"be replayed to another session or server. The REGISTER signature "
|
||||||
|
"covers the server's static key for the same reason: without it, "
|
||||||
|
"any party could bind any public key to any username.",
|
||||||
|
"handshake_hash": h(HANDSHAKE_HASH),
|
||||||
|
"server_static": h(SERVER_STATIC),
|
||||||
|
"auth_signed_preimage": h(sm.LABEL_AUTH + HANDSHAKE_HASH),
|
||||||
|
"auth_signature": h(auth_sig),
|
||||||
|
"auth_frame": h(auth_frame),
|
||||||
|
"auth_frame_note": "username_len=5 || 'alice' || identity || signature || "
|
||||||
|
"sync=1 || count=1 || alice's token for bob",
|
||||||
|
"register_signed_preimage": h(reg_preimage),
|
||||||
|
"register_signature": h(reg_sig),
|
||||||
|
"register_frame": h(reg_frame),
|
||||||
|
"register_frame_note": "token_len=0 and cert_len=0: a first registration, "
|
||||||
|
"no invite token and no rotation certificate",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def rotation_vectors(alice: sm.Account) -> dict:
|
||||||
|
old, new = alice.keys[0], alice.keys[1]
|
||||||
|
when = struct.pack(">q", WHEN)
|
||||||
|
signed = sm.LABEL_ROTATE + b"alice" + old.pk + new.pk + when
|
||||||
|
cert = old.pk + new.pk + when + old.sign(signed) + new.sign(signed)
|
||||||
|
assert len(cert) == sm.CERT_LEN
|
||||||
|
|
||||||
|
# A chain that leads from the pinned key to the offered one is accepted; the
|
||||||
|
# same certificate checked under another username is not (§7).
|
||||||
|
assert sm.walk_chain("alice", old.pk, new.pk, [cert])
|
||||||
|
assert not sm.walk_chain("mallory", old.pk, new.pk, [cert])
|
||||||
|
assert not sm.walk_chain("alice", old.pk, new.pk, [])
|
||||||
|
forged = old.pk + new.pk + when + old.sign(signed) + bytes(sm.SIG_LEN)
|
||||||
|
assert not sm.walk_chain("alice", old.pk, new.pk, [forged])
|
||||||
|
return {
|
||||||
|
"note": "A certificate is 200 bytes and carries no username: the name is "
|
||||||
|
"covered by both signatures but not transported, so a certificate "
|
||||||
|
"cannot be replayed against another username bound to the same key.",
|
||||||
|
"username": "alice",
|
||||||
|
"old_key": h(old.pk),
|
||||||
|
"new_key": h(new.pk),
|
||||||
|
"time": WHEN,
|
||||||
|
"signed_preimage": h(signed),
|
||||||
|
"sig_old": h(old.sign(signed)),
|
||||||
|
"sig_new": h(new.sign(signed)),
|
||||||
|
"certificate": h(cert),
|
||||||
|
"resolve_body": h(new.pk + bytes([1]) + cert),
|
||||||
|
"walk_chain": [
|
||||||
|
{"case": "pinned=old, offered=new, chain=[cert], username=alice",
|
||||||
|
"accepted": True},
|
||||||
|
{"case": "same certificate checked as username=mallory",
|
||||||
|
"accepted": False,
|
||||||
|
"why": "both signatures cover the username"},
|
||||||
|
{"case": "pinned=old, offered=new, empty chain", "accepted": False,
|
||||||
|
"why": "a key change needs a chain; absent means ask the user"},
|
||||||
|
{"case": "sig_new zeroed", "accepted": False,
|
||||||
|
"why": "the new key must sign too, or the old key alone could hand "
|
||||||
|
"the username to a key nobody controls"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- §3 addressing ----------------------------------------------------------
|
||||||
|
|
||||||
|
def addressing_vectors(alice: sm.Account) -> dict:
|
||||||
|
def username_ok(name: str) -> bool:
|
||||||
|
"""§3 through the real parser: a username is valid when `name@host` parses
|
||||||
|
and gives that exact username back."""
|
||||||
|
try:
|
||||||
|
return sm.Address.parse(f"{name}@{HOST}").user == name
|
||||||
|
except sm.SmolError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
names = ["a", "a0", "alice", "al-ice", "a.b-c_d", "x" * 63,
|
||||||
|
"", "-alice", "alice-", ".alice", "al..ice", "a._b", "a-_b",
|
||||||
|
"Alice", "x" * 64, "al ice", "alice@", "ali/ce"]
|
||||||
|
usernames = [{"username": n, "valid": username_ok(n)} for n in names]
|
||||||
|
assert [u["valid"] for u in usernames] == [True] * 6 + [False] * 12
|
||||||
|
|
||||||
|
addresses = []
|
||||||
|
for text in [f"alice@{HOST}", f"alice@{HOST}:1961", f"alice@{HOST}:2000",
|
||||||
|
f"smol://alice@{HOST}/{sm.b32(alice.keys[0].pk)}"]:
|
||||||
|
a = sm.Address.parse(text)
|
||||||
|
addresses.append({"input": text, "user": a.user, "host": a.host,
|
||||||
|
"port": a.port, "short": a.short,
|
||||||
|
"identity": h(a.identity) if a.identity else None})
|
||||||
|
bad = {text: raises(lambda t=text: sm.Address.parse(t))
|
||||||
|
for text in [f"smol://alice@{HOST}", f"smol://alice@{HOST}/{sm.b32(bytes(31))}",
|
||||||
|
f"smol://alice@{HOST}/not-base32!", f"-alice@{HOST}"]}
|
||||||
|
return {
|
||||||
|
"note": "The short form is resolved by the server; the smol:// form carries "
|
||||||
|
"the key and needs no server to be trusted. 1961 is the default "
|
||||||
|
"port and is omitted from the short form.",
|
||||||
|
"usernames": usernames,
|
||||||
|
"addresses": addresses,
|
||||||
|
"rejected": bad,
|
||||||
|
"base32": {"identity": h(alice.keys[0].pk), "encoded": sm.b32(alice.keys[0].pk),
|
||||||
|
"fingerprint": sm.fingerprint(alice.keys[0].pk),
|
||||||
|
"fingerprint_note": "the first 20 base32 characters in groups of "
|
||||||
|
"four: 100 bits, so a collision costs ~2^100"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- §5.5 frontmatter -------------------------------------------------------
|
||||||
|
|
||||||
|
def frontmatter_vectors() -> list[dict]:
|
||||||
|
cases = [
|
||||||
|
("no-block", "hello\n", "no leading '---\\n', so the whole body is text"),
|
||||||
|
("simple", "---\nSubject: hi\n---\nbody\n", "one field, then the text"),
|
||||||
|
("empty-block", "---\n---\n---\nliteral dashes\n",
|
||||||
|
"an empty block escapes a body that genuinely starts with '---'"),
|
||||||
|
("case-and-duplicates", "---\nSubject: first\nsubject: second\nX-Mood: calm\n---\nb",
|
||||||
|
"keys are lowercased for comparison and the first occurrence wins"),
|
||||||
|
("value-trimmed", "---\nSubject: spaced out \n---\n",
|
||||||
|
"the value is the rest of the line, trimmed"),
|
||||||
|
("colon-in-value", "---\nSubject: re: the thing\n---\n",
|
||||||
|
"only the first colon separates"),
|
||||||
|
("no-colon", "---\nSubject hi\n---\nbody\n",
|
||||||
|
"a malformed line invalidates the whole block, which becomes text"),
|
||||||
|
("bad-key", "---\nSub ject: hi\n---\nbody\n", "key charset is [A-Za-z0-9-]"),
|
||||||
|
("unterminated", "---\nSubject: hi\nbody\n", "no closing '---'"),
|
||||||
|
("too-many-keys", "---\n" + "".join(f"K{i}: v\n" for i in range(65)) + "---\nb",
|
||||||
|
"over 64 keys"),
|
||||||
|
("too-large", "---\nK: " + "v" * 4100 + "\n---\nb", "over 4 KiB"),
|
||||||
|
("injected-field", "---\nSubject: hi\nAccept: " + "a" * 8 + "\n---\nb",
|
||||||
|
"what a line break in a sender-supplied value produces: a second field, "
|
||||||
|
"signed and indistinguishable from an intended one. A client building "
|
||||||
|
"frontmatter MUST refuse a value containing a line break."),
|
||||||
|
]
|
||||||
|
out = []
|
||||||
|
for name, body, note in cases:
|
||||||
|
fields, rest = sm.parse_frontmatter(body)
|
||||||
|
out.append({"name": name, "note": note, "body": body,
|
||||||
|
"fields": fields, "text": rest,
|
||||||
|
"block_accepted": bool(fields) or rest != body})
|
||||||
|
assert out[2]["text"] == "---\nliteral dashes\n"
|
||||||
|
assert out[3]["fields"]["subject"] == "first"
|
||||||
|
assert all(not o["block_accepted"] for o in out if o["name"] in
|
||||||
|
{"no-colon", "bad-key", "unterminated", "too-many-keys", "too-large"})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# --- rejections -------------------------------------------------------------
|
||||||
|
|
||||||
|
def reject_vectors(alice: sm.Account, bob: sm.Account) -> dict:
|
||||||
|
"""Inputs an implementation MUST refuse. These are the vectors that catch a
|
||||||
|
client which decrypts without verifying, or trusts `to` without checking it."""
|
||||||
|
to = alice.keys[0].pk
|
||||||
|
esk = bytes([0xA1]) * 32
|
||||||
|
with ephemeral(esk) as epk:
|
||||||
|
valid = sm.seal(bob.me, to, b"valid\n", when=WHEN, pad=False)
|
||||||
|
_, _, payload = plaintext_of(bob.me, to, b"valid\n", WHEN, epk, False)
|
||||||
|
assert sm.unseal(alice.keys, valid)["body"] == b"valid\n"
|
||||||
|
|
||||||
|
def mutate(env: bytes, at: int, raw: bytes) -> bytes:
|
||||||
|
return env[:at] + raw + env[at + len(raw):]
|
||||||
|
|
||||||
|
def repack(new_payload: bytes) -> bytes:
|
||||||
|
"""Re-seal a tampered payload so it passes the AEAD and fails deeper in."""
|
||||||
|
return encrypt(new_payload, to, esk)
|
||||||
|
|
||||||
|
bad_sig = repack(payload[:-sm.SIG_LEN] + bytes(sm.SIG_LEN))
|
||||||
|
wrong_to = repack(plaintext_of(bob.me, bob.me.pk, b"valid\n", WHEN, epk, False)[2])
|
||||||
|
long_body = repack(payload[:sm.PAYLOAD_HEADER - 4] + struct.pack(">I", 1 << 20)
|
||||||
|
+ payload[sm.PAYLOAD_HEADER:])
|
||||||
|
_, _, future = plaintext_of(bob.me, to, b"later\n", FUTURE, epk, False)
|
||||||
|
|
||||||
|
envelopes = [
|
||||||
|
("bad-magic", mutate(valid, 0, b"XMOL"), "magic is not 'SMOL' (§5.1)"),
|
||||||
|
("bad-envelope-version", mutate(valid, 4, b"\x02"),
|
||||||
|
"unknown envelope version; the sealed-box construction may differ"),
|
||||||
|
("truncated", valid[:sm.ENVELOPE_HEADER],
|
||||||
|
"shorter than the 69-byte header plus a 16-byte tag"),
|
||||||
|
("tag-flipped", valid[:-1] + bytes([valid[-1] ^ 0x01]),
|
||||||
|
"the Poly1305 tag no longer authenticates"),
|
||||||
|
("aad-tampered", mutate(valid, 40, bytes([valid[40] ^ 0x01])),
|
||||||
|
"epk is covered by the AAD, so altering it breaks the tag rather than "
|
||||||
|
"silently re-addressing the message"),
|
||||||
|
("not-for-us", repack(payload)[:5] + bob.me.pk + valid[37:],
|
||||||
|
"`to` names a key we do not hold; a client MUST check it against its own"),
|
||||||
|
("bad-signature", bad_sig,
|
||||||
|
"decrypts cleanly and the signature is zeroed. An implementation that "
|
||||||
|
"skips §5.3 verification will accept forged mail here."),
|
||||||
|
("signature-bound-to-other-recipient", wrong_to,
|
||||||
|
"a payload signed for bob, resealed to alice. The signature covers `to`, "
|
||||||
|
"so re-addressing a recorded message must not verify."),
|
||||||
|
("body-len-overflows", long_body, "body_len exceeds the payload (§5.3)"),
|
||||||
|
("payload-version", repack(b"\x02" + payload[1:]), "unknown payload version"),
|
||||||
|
("dated-in-the-future", repack(future),
|
||||||
|
f"time is {FUTURE}, more than 86400s ahead of any clock before 2100 (§5.3)"),
|
||||||
|
("low-order-epk", mutate(valid, 37, bytes.fromhex(LOW_ORDER[2])),
|
||||||
|
"a low-order ephemeral key, which §2 requires be refused"),
|
||||||
|
]
|
||||||
|
out = []
|
||||||
|
for name, env, why in envelopes:
|
||||||
|
out.append({"name": name, "why": why, "envelope": h(env),
|
||||||
|
"envelope_len": len(env),
|
||||||
|
"rejected_with": raises(lambda e=env: sm.unseal(alice.keys, e))})
|
||||||
|
|
||||||
|
low = [{"x25519_public": p,
|
||||||
|
"rejected_with": raises(lambda p=p: sm.agree(alice.keys[0].x_priv,
|
||||||
|
bytes.fromhex(p)))}
|
||||||
|
for p in LOW_ORDER]
|
||||||
|
keys = [{"identity_key": k,
|
||||||
|
"rejected_with": raises(lambda k=k: sm.ed_to_x25519_pub(bytes.fromhex(k)))}
|
||||||
|
for k in ["0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000"]]
|
||||||
|
return {
|
||||||
|
"note": "Inputs a conformant implementation refuses. The envelope cases are "
|
||||||
|
"mutations of one valid message, listed with the reference's own "
|
||||||
|
"error, so a client can be tested against its own failure path.",
|
||||||
|
"valid_baseline": h(valid),
|
||||||
|
"envelopes": out,
|
||||||
|
"low_order_x25519": low,
|
||||||
|
"low_order_note": "§2 and §4: a received ephemeral public key that is a "
|
||||||
|
"low-order point MUST be refused, and an all-zero X25519 "
|
||||||
|
"output MUST be refused. libsodium reports the first as "
|
||||||
|
"the second, since the agreement output is zero.",
|
||||||
|
"invalid_identity_keys": keys,
|
||||||
|
"invalid_identity_note": "Keys that do not decode as Ed25519 points, or are "
|
||||||
|
"of small order, so §2's map to X25519 has no "
|
||||||
|
"valid result.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- assembly ---------------------------------------------------------------
|
||||||
|
|
||||||
|
def build() -> dict:
|
||||||
|
alice = sm.Account(ALICE_MASTER, 1) # rotated once, so §7 has both keys
|
||||||
|
bob = sm.Account(BOB_MASTER, 0)
|
||||||
|
return {
|
||||||
|
"spec": "smolmail/1.2",
|
||||||
|
"generated_by": "vectors.py, from smolmail.py",
|
||||||
|
"note": "All values are hex unless the field name says otherwise. `alice[n]` "
|
||||||
|
"and `bob[n]` name a master and a rotation index from `identity`. "
|
||||||
|
"Masters are test data: never use them.",
|
||||||
|
"labels": {name: getattr(sm, name).decode() for name in
|
||||||
|
["LABEL_IDENTITY", "LABEL_ACCEPT", "LABEL_SEAL", "LABEL_MSG",
|
||||||
|
"LABEL_ID", "LABEL_MAC", "LABEL_AUTH", "LABEL_ROTATE",
|
||||||
|
"LABEL_REGISTER", "PROLOGUE"]},
|
||||||
|
"constants": {"default_port": sm.DEFAULT_PORT, "magic": sm.MAGIC.decode(),
|
||||||
|
"version": sm.VERSION, "envelope_header": sm.ENVELOPE_HEADER,
|
||||||
|
"payload_header": sm.PAYLOAD_HEADER, "pad_to": sm.PAD_TO,
|
||||||
|
"max_chain": sm.MAX_CHAIN, "max_skew": sm.MAX_SKEW,
|
||||||
|
"noise_protocol": sm.NOISE_PROTOCOL.decode()},
|
||||||
|
"identity": identity_vectors(),
|
||||||
|
"accept": accept_vectors(alice, bob),
|
||||||
|
"addressing": addressing_vectors(alice),
|
||||||
|
"envelopes": envelope_vectors(alice, bob),
|
||||||
|
"session": session_vectors(alice),
|
||||||
|
"rotation": rotation_vectors(alice),
|
||||||
|
"frontmatter": frontmatter_vectors(),
|
||||||
|
"rejects": reject_vectors(alice, bob),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(value: str, width: int = 76) -> str:
|
||||||
|
return "\n".join(value[i:i + width] for i in range(0, len(value), width))
|
||||||
|
|
||||||
|
|
||||||
|
def field(key: str, value, limit: int = 160) -> str:
|
||||||
|
"""Long hex goes in a fenced block; everything short stays on one line."""
|
||||||
|
if isinstance(value, str) and len(value) > limit:
|
||||||
|
return f"\n`{key}`\n\n```\n{wrap(value)}\n```\n"
|
||||||
|
if isinstance(value, bool):
|
||||||
|
value = "true" if value else "false"
|
||||||
|
return f"- `{key}`: `{value}`\n" if value != "" else f"- `{key}`: (empty)\n"
|
||||||
|
|
||||||
|
|
||||||
|
def markdown(v: dict) -> str:
|
||||||
|
L = v["labels"]
|
||||||
|
md = [
|
||||||
|
"<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->",
|
||||||
|
"",
|
||||||
|
f"# smolmail Test Vectors, version {v['spec'].split('/')[1]}",
|
||||||
|
"",
|
||||||
|
"Known-answer tests for [SPEC.md](SPEC.md), generated from the reference "
|
||||||
|
"implementation by [vectors.py](vectors.py). [vectors.json](vectors.json) "
|
||||||
|
"carries the same values in a form a test harness can load; this file is "
|
||||||
|
"for reading alongside the specification.",
|
||||||
|
"",
|
||||||
|
"Work through them in order. Derivation first (§2), because every later "
|
||||||
|
"vector depends on it, then an envelope you can seal and unseal (§5), then "
|
||||||
|
"the wire frames (§4, §6.1, §7). Finish with the rejections: they are the "
|
||||||
|
"ones that catch an implementation which decrypts without verifying.",
|
||||||
|
"",
|
||||||
|
"Hex throughout, unless a field name says otherwise. The two masters are "
|
||||||
|
"test data and the private keys here are public. Never use them for "
|
||||||
|
"anything.",
|
||||||
|
"",
|
||||||
|
"## Labels (§11)",
|
||||||
|
"",
|
||||||
|
"Every hash, signature and MAC is domain-separated by one of these, "
|
||||||
|
"prefixed to the input as raw bytes with no separator or length:",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
md += [f"- `{k}` = `{val}`" for k, val in L.items()]
|
||||||
|
md += ["", "## §2 Identity", "",
|
||||||
|
"`seed_n = HKDF-SHA256(master, salt = \"\", info = "
|
||||||
|
f"\"{L['LABEL_IDENTITY']}\" || n)`, with `n` a big-endian u32. The "
|
||||||
|
"Ed25519 public key of that seed *is* the identity. The X25519 keypair "
|
||||||
|
"comes from the same key via libsodium's conversions, and the accept key "
|
||||||
|
"is derived without the index so it survives rotation.", ""]
|
||||||
|
for who in v["identity"]:
|
||||||
|
md.append(f"### {who['name']}, rotation index {who['rotation_index']}")
|
||||||
|
md.append("")
|
||||||
|
for key in ["master", "rotation_index", "seed", "identity_key",
|
||||||
|
"x25519_private", "x25519_public", "base32", "fingerprint", "uri"]:
|
||||||
|
md.append(field(key, who[key]).rstrip("\n"))
|
||||||
|
md.append("")
|
||||||
|
md += ["### Accept keys and tokens (§5.8)", "",
|
||||||
|
v["accept"]["note"], ""]
|
||||||
|
md += [field(k, val).rstrip("\n") for k, val in v["accept"].items() if k != "note"]
|
||||||
|
md += ["",
|
||||||
|
"`alice_token_for_bob_rotated` is the token alice would issue to bob's "
|
||||||
|
"index-1 key. It differs from the one bob already holds, which is why "
|
||||||
|
"§5.8 says a token is derived from the correspondent's identity *as it "
|
||||||
|
"stood when they were accepted*: bob's rotation does not invalidate the "
|
||||||
|
"token he was given.", ""]
|
||||||
|
|
||||||
|
a = v["addressing"]
|
||||||
|
md += ["## §3 Addressing", "", a["note"], "", "### Usernames", "",
|
||||||
|
"| username | valid |", "| --- | --- |"]
|
||||||
|
md += [f"| `{u['username'] or '(empty)'}` | {'yes' if u['valid'] else 'no'} |"
|
||||||
|
for u in a["usernames"]]
|
||||||
|
md += ["", "### Addresses", "",
|
||||||
|
"| input | user | host | port | identity |", "| --- | --- | --- | --- | --- |"]
|
||||||
|
for ad in a["addresses"]:
|
||||||
|
ident = f"`{ad['identity'][:16]}…`" if ad["identity"] else "resolved"
|
||||||
|
md.append(f"| `{ad['input']}` | `{ad['user']}` | `{ad['host']}` | "
|
||||||
|
f"{ad['port']} | {ident} |")
|
||||||
|
md += ["", "Refused:", ""]
|
||||||
|
md += [f"- `{text}` — {why}" for text, why in a["rejected"].items()]
|
||||||
|
md += ["", "### Fingerprint", ""]
|
||||||
|
md += [field(k, val).rstrip("\n") for k, val in a["base32"].items()]
|
||||||
|
|
||||||
|
md += ["", "## §5 Messages", "",
|
||||||
|
"Each vector pins the ephemeral `esk` that §5.2 would draw at random and "
|
||||||
|
"the `time` that §5.3 would take from the clock. Nothing else in the "
|
||||||
|
"protocol is randomised, so with those two fixed an envelope reproduces "
|
||||||
|
"byte for byte.", "",
|
||||||
|
"Build one in this order: agree on `x25519_shared`, derive `seal_key`, "
|
||||||
|
"assemble `payload_header`, sign `signed_preimage`, concatenate the "
|
||||||
|
"payload, then encrypt it under `aad` with an all-zero nonce. If your "
|
||||||
|
"`seal_key` matches and your envelope does not, the fault is in the "
|
||||||
|
"payload layout; if `x25519_shared` already differs, it is in §2's map.",
|
||||||
|
"",
|
||||||
|
"`accept_mac` is what a sender holding the recipient's token attaches to "
|
||||||
|
"`SEND` (§5.8); `message_id` is what the server returns. The full payload "
|
||||||
|
"plaintext and the whole `SEND` request body are in vectors.json as "
|
||||||
|
"`payload` and `send_body`.", ""]
|
||||||
|
for e in v["envelopes"]:
|
||||||
|
md += [f"### {e['name']}", "", e["note"], ""]
|
||||||
|
for key in ["sender", "recipient", "body", "body_len", "time", "padded",
|
||||||
|
"esk", "epk", "x25519_shared", "seal_key", "aad",
|
||||||
|
"payload_header", "signed_preimage", "signature",
|
||||||
|
"payload_len", "envelope_len", "envelope", "envelope_sha256",
|
||||||
|
"message_id", "accept_mac"]:
|
||||||
|
md.append(field(key, e[key]).rstrip("\n"))
|
||||||
|
md.append("")
|
||||||
|
|
||||||
|
s = v["session"]
|
||||||
|
md += ["## §4 and §6.1 Session frames", "", s["note"], ""]
|
||||||
|
for key in ["handshake_hash", "server_static", "auth_signed_preimage",
|
||||||
|
"auth_signature", "auth_frame"]:
|
||||||
|
md.append(field(key, s[key]).rstrip("\n"))
|
||||||
|
md += ["", s["auth_frame_note"], ""]
|
||||||
|
for key in ["register_signed_preimage", "register_signature", "register_frame"]:
|
||||||
|
md.append(field(key, s[key]).rstrip("\n"))
|
||||||
|
md += ["", s["register_frame_note"], ""]
|
||||||
|
|
||||||
|
r = v["rotation"]
|
||||||
|
md += ["## §7 Key rotation", "", r["note"], ""]
|
||||||
|
for key in ["username", "old_key", "new_key", "time", "signed_preimage",
|
||||||
|
"sig_old", "sig_new", "certificate", "resolve_body"]:
|
||||||
|
md.append(field(key, r[key]).rstrip("\n"))
|
||||||
|
md += ["", "Chain walking:", ""]
|
||||||
|
md += [f"- {c['case']} → **{'accept' if c['accepted'] else 'refuse'}**"
|
||||||
|
+ (f" ({c['why']})" if c.get("why") else "") for c in r["walk_chain"]]
|
||||||
|
|
||||||
|
md += ["", "## §5.5 Frontmatter", "",
|
||||||
|
"A flat `Key: value` block, not YAML. A malformed line invalidates the "
|
||||||
|
"whole block, which is then shown as ordinary body text: frontmatter "
|
||||||
|
"fails closed toward display, never toward silent discard.", "",
|
||||||
|
"| case | input | fields | text | note |", "| --- | --- | --- | --- | --- |"]
|
||||||
|
def clip(text: str, keep: int = 48) -> str:
|
||||||
|
return repr(text) if len(text) <= keep else repr(text[:keep]) + "…"
|
||||||
|
|
||||||
|
for f in v["frontmatter"]:
|
||||||
|
md.append(f"| `{f['name']}` | `{clip(f['body'])}` | `{f['fields']}` | "
|
||||||
|
f"`{clip(f['text'])}` | {f['note']} |")
|
||||||
|
md += ["", "Long inputs are clipped here; vectors.json has them whole."]
|
||||||
|
|
||||||
|
rj = v["rejects"]
|
||||||
|
md += ["", "## Rejections", "", rj["note"], "",
|
||||||
|
"Each envelope is a mutation of this valid one:", "",
|
||||||
|
field("valid_baseline", rj["valid_baseline"]).rstrip("\n"), ""]
|
||||||
|
for case in rj["envelopes"]:
|
||||||
|
md += [f"### {case['name']}", "", case["why"], "",
|
||||||
|
field("envelope", case["envelope"]).rstrip("\n"),
|
||||||
|
field("rejected_with", case["rejected_with"]).rstrip("\n"), ""]
|
||||||
|
md += ["### Low-order X25519 keys", "", rj["low_order_note"], ""]
|
||||||
|
md += [f"- `{p['x25519_public']}`" for p in rj["low_order_x25519"]]
|
||||||
|
md += ["", "### Invalid identity keys", "", rj["invalid_identity_note"], ""]
|
||||||
|
md += [f"- `{k['identity_key']}` — {k['rejected_with']}"
|
||||||
|
for k in rj["invalid_identity_keys"]]
|
||||||
|
md += ["", "## License", "",
|
||||||
|
"These vectors are licensed under "
|
||||||
|
"[CC BY-SA 4.0](LICENSES/CC-BY-SA-4.0.txt), with the specification.", ""]
|
||||||
|
return re.sub(r"\n{3,}", "\n\n", "\n".join(md))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str]) -> int:
|
||||||
|
check = "--check" in argv
|
||||||
|
built = build()
|
||||||
|
rendered = markdown(built)
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
files = {"vectors.json": json.dumps(built, indent=2) + "\n",
|
||||||
|
"VECTORS.md": rendered}
|
||||||
|
if not check:
|
||||||
|
for name, text in files.items():
|
||||||
|
with open(os.path.join(here, name), "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(text)
|
||||||
|
print(f"wrote {name} ({len(text)} bytes)")
|
||||||
|
counts = {k: len(built[k]) for k in ["identity", "envelopes", "frontmatter"]}
|
||||||
|
print(f"{counts['identity']} identities, {counts['envelopes']} envelopes, "
|
||||||
|
f"{len(built['rejects']['envelopes'])} rejections, "
|
||||||
|
f"{counts['frontmatter']} frontmatter cases: all verified")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
for name, text in files.items():
|
||||||
|
path = os.path.join(here, name)
|
||||||
|
try:
|
||||||
|
with open(path, encoding="utf-8") as fh:
|
||||||
|
have = fh.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"{name}: missing")
|
||||||
|
failed = True
|
||||||
|
continue
|
||||||
|
if have == text:
|
||||||
|
print(f"{name}: ok")
|
||||||
|
continue
|
||||||
|
failed = True
|
||||||
|
print(f"{name}: differs from what smolmail.py now produces")
|
||||||
|
for line in list(difflib.unified_diff(
|
||||||
|
have.splitlines(), text.splitlines(),
|
||||||
|
fromfile=f"{name} (committed)", tofile=f"{name} (regenerated)",
|
||||||
|
lineterm=""))[:40]:
|
||||||
|
print(f" {line}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue