Correct SPEC.md §6.2 LRPROOF body order and §6.3 link_id offsets
Two source-cited corrections found while drafting the link send flow: §6.2 — the LRPROOF body is signature(64) || responder_X25519_pub(32) || [signalling], not link_id || responder_X25519_pub || signature || [signalling]. The link_id appears in the packet header (dest_hash position) per RNS/Packet.py:182-184 when context==LRPROOF, not in the body. The responder's long-term Ed25519 pub is also NOT on the wire — both sides know it from a prior announce, and it is included only in the signature input. Citations: RNS/Link.py:373 (signer), :376 (proof_data), :417 (validator). §6.3 — get_hashable_part offsets N are 2 for HEADER_1 and 18 for HEADER_2 (skip flags+hops, and additionally skip transport_id for HEADER_2), producing the same hashable_part on both sides regardless of relay conversion. Previously listed as 18/34, which would have stripped the dest_hash. Citation: RNS/Packet.py:354-361. Both corrections are direct upstream source citations (criterion #2 from agent.md §1) so they are recorded as verified. todo.md adds an entry to write tools/verify_link_handshake.py to lock them in with a runtime test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ac898a414d
commit
8480555320
2 changed files with 30 additions and 8 deletions
25
SPEC.md
25
SPEC.md
|
|
@ -292,30 +292,39 @@ Both initiator-side keys are **fresh ephemeral keys** (not the initiator's long-
|
|||
|
||||
### 6.2 LRPROOF (responder → initiator)
|
||||
|
||||
A `packet_type = PROOF (3)` with `context = 0xff`, addressed to the initiator's transport_id (or to `link_id` when 1 hop away). Body:
|
||||
A `packet_type = PROOF (3)` with `context = 0xff`, addressed to the link itself — i.e. `dest_hash` in the packet header is the 16-byte `link_id` (`RNS/Packet.py:182-184`: when context is `LRPROOF`, `header += destination.link_id` and the body is appended unencrypted).
|
||||
|
||||
Body (`proof_data` at `RNS/Link.py:376`):
|
||||
|
||||
```
|
||||
link_id(16) || responder_X25519_pub(32) || signature(64) || [signalling(3)]
|
||||
signature(64) || responder_X25519_pub(32) || [signalling(3)]
|
||||
```
|
||||
|
||||
Only the responder's X25519 is fresh-ephemeral; the responder signs with its **long-term** Ed25519 private key (asymmetric with the initiator). Signature input:
|
||||
Only the responder's X25519 is fresh-ephemeral; the responder signs with its **long-term** Ed25519 private key (asymmetric with the initiator). The responder's long-term Ed25519 public key is **not** sent on the wire — both sides already know it from the responder's prior announce, and it is included implicitly in the signature input. Signature input (`RNS/Link.py:373` for the signer, `:417` for the validator):
|
||||
|
||||
```
|
||||
signed_data = link_id || responder_X25519_pub || responder_long_term_Ed25519_pub || [signalling]
|
||||
```
|
||||
|
||||
The full wire packet is therefore: `flags(1) || hops(1) || link_id(16) || context=0xff(1) || signature(64) || responder_X25519_pub(32) || [signalling(3)]`.
|
||||
|
||||
### 6.3 link_id derivation
|
||||
|
||||
```
|
||||
link_id = SHA256(hashable_part_of_LINKREQUEST_packet)[:16]
|
||||
|
||||
hashable_part = (flags & 0x0F) || raw[N:]
|
||||
where N = 18 for HEADER_1, 34 for HEADER_2
|
||||
```
|
||||
|
||||
The "hashable part" deliberately strips `header_type`, `context_flag`, `transport_type` (top 4 bits of flags — modifiable by transit relays) and the `hops` byte (modified by every relay). This produces the same `link_id` whether computed at the initiator (HEADER_1) or at the responder (HEADER_2 if the LINKREQUEST went through a relay) — both sides agree on the 16-byte ID.
|
||||
`hashable_part` is built by `Packet.get_hashable_part` (`RNS/Packet.py:354-361`):
|
||||
|
||||
For LINKREQUEST packets specifically, the trailing 3 signalling bytes (if present, indicated by body length > 64) are stripped from the END of `hashable_part` before hashing.
|
||||
```
|
||||
hashable_part = byte(flags & 0x0F) || raw[N:]
|
||||
where N = 2 for HEADER_1 (strip flags + hops)
|
||||
N = 18 for HEADER_2 (strip flags + hops + transport_id)
|
||||
```
|
||||
|
||||
The "hashable part" deliberately strips `header_type`, `context_flag`, `transport_type` (top 4 bits of flags — modifiable by transit relays), the `hops` byte (modified by every relay), and (for HEADER_2) the `transport_id` (added by the originator and re-written by each relay). What remains in both cases is the low nibble of flags + dest_hash + context + body, so the resulting `link_id` is the same whether the LINKREQUEST is hashed at the initiator (HEADER_1) or at the responder after one or more transport relays (HEADER_2). Both sides agree on the 16-byte ID.
|
||||
|
||||
For LINKREQUEST packets specifically, the trailing signalling bytes (if present, indicated by `len(packet.data) > Link.ECPUBSIZE` in `link_id_from_lr_packet` at `RNS/Link.py:340-347`) are stripped from the END of `hashable_part` before hashing, so the link_id is invariant under MTU-discovery signalling.
|
||||
|
||||
### 6.4 Session key derivation
|
||||
|
||||
|
|
|
|||
13
todo.md
13
todo.md
|
|
@ -71,6 +71,19 @@ to remove their markers:
|
|||
`RATCHET_EXPIRY = 60*60*24*30` (`RNS/Identity.py:69`).
|
||||
SPEC.md §7.4 corrected.
|
||||
|
||||
## Open `⚠️` items needing a runtime verifier
|
||||
|
||||
- [ ] **Lock in the §6.2 / §6.3 corrections with `verify_link_handshake.py`.**
|
||||
The wire-byte order of the LRPROOF body (`signature || responder_X25519_pub || signalling`,
|
||||
not `link_id || responder_X25519_pub || signature || signalling`) and
|
||||
the `link_id` derivation offsets (`N=2` for HEADER_1, `N=18` for HEADER_2,
|
||||
not 18/34) were corrected against direct upstream source citations
|
||||
(`RNS/Link.py:376`, `RNS/Packet.py:354-361`) in `SPEC.md` §6.2/§6.3
|
||||
while writing `flows/send-link-lxmf.md`. They are source-cited but
|
||||
not yet exercised by a runtime verifier. Add `tools/verify_link_handshake.py`
|
||||
that drives an upstream LINKREQUEST → LRPROOF → ACTIVE handshake and
|
||||
asserts byte-level layouts + `link_id` invariance under HEADER_1↔HEADER_2.
|
||||
|
||||
## Spec polishing (lower priority)
|
||||
|
||||
- [ ] **Split `SPEC.md` into per-layer files** as the document grows
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue