docs(spec): address review notes on signing, fragments, and TLVs

Clarify that signature preimages use padded BinaryProtocol.encode; dispatch
reassembled fragments by decoded type; and narrow unknown-TLV skip to the
families that actually tolerate it. Bump spec to 1.0.1.
This commit is contained in:
Amirhossein Rezaei 2026-07-27 16:19:54 +03:30
parent 9ee0292446
commit 54b78b9ca7
8 changed files with 78 additions and 22 deletions

View File

@ -1,6 +1,6 @@
# 01 — Wire Format
**Spec:** 1.0.0
**Spec:** 1.0.1
**Canonical source:** `localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift`
All multi-byte integers on the mesh wire are **network byte order (big-endian)**
@ -127,13 +127,20 @@ Decompression:
`MessagePadding` buckets: **256, 512, 1024, 2048**.
- Pad bytes are all equal to the pad length (1…255).
- If more than 255 pad bytes would be required to reach the next bucket, the
frame is left **unpadded**.
- Bucket selection (`optimalBlockSize`): choose the smallest bucket such that
`encodedSize + 16 ≤ bucket` (the `+16` accounts for AEAD tag headroom used by
the helper even when the frame is not a Noise ciphertext). If no bucket fits,
or more than 255 pad bytes would be required, the frame is left **unpadded**.
- Decode tries the buffer as-is, then strips padding and retries.
**Only** `noiseHandshake` and `noiseEncrypted` frames are padded on the BLE
outbound path. All other types travel at natural length — payload length is
observable for those types.
Two different call sites use this helper — do not conflate them:
| Path | Padding? |
|------|----------|
| **BLE outbound encode** (`padsBLEFrame`) | **Only** `noiseHandshake` / `noiseEncrypted`. All other types travel at natural length on the air (payload length observable). |
| **Packet signature preimage** (`toBinaryDataForSigning``BinaryProtocol.encode` default `padding: true`) | Padding **MAY** be present for *any* signed type (including announces). Verifiers **MUST** use the same padded canonical bytes. |
See §9 for the signature preimage rules.
---
@ -187,12 +194,24 @@ packet signatures; they are distinct from the Noise static key.
## 9. Packet signatures
- Algorithm: **Ed25519** (`Curve25519.Signing`), 64-byte signature.
- Canonical bytes: encode the packet with `signature = nil`, `ttl = 0`,
`isRSR = false` (TTL and RSR are mutable in flight and excluded).
- Canonical preimage (`BitchatPacket.toBinaryDataForSigning()`):
1. Copy the packet with `signature = nil`, `ttl = 0`, `isRSR = false`
(TTL and RSR are mutable in flight and excluded from the signed bytes).
2. Encode with `BinaryProtocol.encode(..., padding: true)` — the **default**.
Apply §6 PKCS#7-style padding / bucket selection to that encoding.
3. Sign or verify those exact bytes. An unpadded encode of the same fields
**will not** verify against reference-client signatures whenever padding
was applied (common for compact announces that land in the 256-byte bucket).
- Relays **MUST** decrement TTL without recomputing the signature.
- Announces, leaves, public file transfers, and other authenticated public
types set `hasSignature` when the reference clients emit them.
BLE air frames for non-Noise types are often sent **without** this padding
(§6 table). That is independent of the signature preimage: the signature
covers the padded canonical encoding, then the on-air frame for that type may
omit pad bytes. Verifiers rebuild the preimage themselves; they do not require
the received ATT blob to still carry the pad.
Optional helper `bitchat-announce-v1` binding bytes exist in the Noise service
for nickname/key binding tests; live mesh announces sign the **full packet**
canonical form above, verified against the Ed25519 key carried in the announce
@ -214,7 +233,9 @@ TLV payload.
## 11. Implementer checklist
- [ ] Round-trip v1 packet with no recipient, no signature, empty payload.
- [ ] Round-trip v1 with recipient + signature.
- [ ] Round-trip v1 with recipient + signature using **padded** canonical bytes.
- [ ] Confirm an unpadded announce preimage fails verification against a
reference signature when padding would have applied.
- [ ] Round-trip v2 with route hops; confirm route bytes are outside `payloadLength`.
- [ ] Compress a low-entropy >100 B payload; confirm preamble + flag.
- [ ] Reject version `0x00` / `0x03`.

View File

@ -1,6 +1,6 @@
# 02 — BLE Transport
**Spec:** 1.0.0
**Spec:** 1.0.1
**Canonical source:** `bitchat/Services/BLE/BLEService.swift`,
`BLEOutboundFragmentPlanner.swift`, `BLEFragmentAssemblyBuffer.swift`,
`TransportConfig.swift`
@ -149,7 +149,16 @@ Validation:
- Oversize fragments **MUST NOT** destroy an assembly they did not create
On completion, concatenate chunks in index order and run `BinaryProtocol.decode`
on the result; then dispatch as `originalType`.
on the result. Dispatch using the **decoded inner packet's `type` field**
(`originalPacket.type`), then re-enter the normal receive path with that
packet (reference: `BLEFragmentHandler` sets `ttl = 0` and reinjects).
The fragment-header `originalType` byte is **not authenticated** and **MUST
NOT** be used as the sole dispatch key. Implementations **SHOULD** require
`originalType == decoded.type` and drop the assembly on mismatch; at minimum
they **MUST** ignore the header value for parser/policy selection and follow
the decoded type so a spoofed header cannot steer valid inner bytes into the
wrong handler.
---
@ -191,4 +200,5 @@ interoperability timing):
- [ ] Reassemble ATT notifications into BinaryProtocol frames before parsing.
- [ ] Fragment at ≤469 B chunks with the 13-byte fragment header.
- [ ] Cap assemblies (128 / 30 s) and reject conflicting fragment metadata.
- [ ] After reassembly, dispatch by decoded packet type (not header `originalType`).
- [ ] Originate mesh packets with TTL 7 unless a documented exception applies.

View File

@ -1,6 +1,6 @@
# 03 — Noise Handshake and Encrypted Transport
**Spec:** 1.0.0
**Spec:** 1.0.1
**Canonical source:** `bitchat/Noise/NoiseProtocol.swift`,
`NoiseSession.swift`, `NoiseEncryptionService.swift`,
`bitchat/Protocols/BitchatProtocol.swift` (`NoisePayloadType`)

View File

@ -1,6 +1,6 @@
# 04 — Payload Layouts
**Spec:** 1.0.0
**Spec:** 1.0.1
TLV conventions differ by packet family — do not mix length widths.
@ -10,7 +10,23 @@ TLV conventions differ by packet family — do not mix length widths.
| Courier envelope / prekey bundle | `uint16` BE |
| File transfer content TLV | `uint32` BE (canonical); other file TLVs `uint16` BE |
Unknown TLV types **MUST** be skipped (forward compatibility).
### Unknown-TLV policy (do not over-promise)
Forward-compatible **skip** of unknown TLV types applies only where the
reference decoder actually skips:
| Family | Unknown TLV behaviour |
|--------|------------------------|
| Announce | **Skip** and continue |
| Authenticated peer state | **Skip** and continue |
| Courier envelope | **Skip** and continue |
| Prekey bundle | **Skip** and continue |
| File transfer | **Skip** and continue |
| Private message (`PrivateMessagePacket`) | **Reject** entire payload (`nil`) |
Adding a new private-message TLV under a SemVer **minor** bump would break
current reference DM decoders. Treat private-message TLV extensions as a
**MAJOR** wire change (or change the decoder first), not as a silent skip.
---
@ -197,6 +213,10 @@ TLV `[type:u8][len:u8][value]`:
Prefixed by `NoisePayloadType.privateMessage` (`0x01`) when inside Noise.
Unlike announce/courier TLVs, any unknown type byte causes
`PrivateMessagePacket.decode` to return `nil` immediately (no skip). Both
`messageID` and `content` are required.
---
## 10. Noise inner: authenticated peer state
@ -235,3 +255,4 @@ the encode/decode in:
- [ ] File content TLV uses 4-byte length; tolerate legacy widths on decode.
- [ ] Prekey bundle signature verifies over domain-prefixed signable bytes.
- [ ] Private DM content is Noise-typed, not a public `0x02` packet.
- [ ] Confirm unknown private-message TLVs reject; unknown announce TLVs skip.

View File

@ -1,6 +1,6 @@
# 05 — Nostr Bridge
**Spec:** 1.0.0
**Spec:** 1.0.1
**Canonical prose:** `WHITEPAPER.md` §5.3, §6.4; `README.md`;
`docs/GeohashPresenceSpec.md`

View File

@ -1,6 +1,6 @@
# BitChat Protocol Specification
**Spec version:** [`1.0.0`](VERSION)
**Spec version:** [`1.0.1`](VERSION)
**Status:** Draft extracted from the reference implementation
**Canonical codec:** `localPackages/BitFoundation`
**Architecture overview:** [`WHITEPAPER.md`](../WHITEPAPER.md)
@ -39,9 +39,10 @@ wire types) as authoritative until this document is amended. Open a PR against
- **PATCH** — clarifications, errata, conformance notes with no wire change.
- Spec version is **independent** of App Store / Android release numbers.
Unknown TLV types and unknown high capability bits **MUST** be skipped so
older clients can carry newer packets opaquely where the outer type is already
understood.
Unknown TLV types and unknown high capability bits are handled
per-family: most public TLV decoders skip unknowns so older clients can carry
newer packets opaquely, but some inner payloads (notably private-message TLVs)
reject unknowns — see [`04-payloads.md`](04-payloads.md).
## Suggested reading order for implementers

View File

@ -1 +1 @@
1.0.0
1.0.1

View File

@ -1,6 +1,6 @@
# Conformance
**Spec:** 1.0.0
**Spec:** 1.0.1
This file is a living checklist. Golden hex vectors for every mesh type are a
planned follow-up (see issue
@ -40,7 +40,8 @@ just test
- [ ] Encode/decode v1 and v2 packets with correct endianness.
- [ ] Honor optional recipient, signature, compression, route flags.
- [ ] Exclude TTL/`isRSR` from signature canonicalization.
- [ ] Exclude TTL/`isRSR` from signature canonicalization; include PKCS#7
padding from `encode(padding: true)` in the preimage.
- [ ] Derive 8-byte peer IDs as `SHA256(noiseStatic)[0..<8]`.
### BLE
@ -48,6 +49,7 @@ just test
- [ ] Use release GATT service UUID `…4B5C` and characteristic `…4C5D`.
- [ ] Advertise service UUID only (no local name).
- [ ] Fragment with 13-byte header; reassemble with first-wins metadata.
- [ ] Dispatch reassembled packets by decoded type, not header `originalType`.
- [ ] Default origination TTL = 7.
### Noise
@ -64,6 +66,7 @@ just test
- [ ] Capability bitfield little-endian.
- [ ] File TLV content length 4-byte BE; private files via Noise `0x20`.
- [ ] Courier recipient tag HMAC construction.
- [ ] Unknown-TLV skip only where listed; private-message unknown tags reject.
### Nostr