mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-22 07:16:03 +00:00
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:
parent
9ee0292446
commit
54b78b9ca7
@ -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`.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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`
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
1.0.0
|
||||
1.0.1
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user