From 7a85385bb4601f749ab6bb463823b9bbe87dd443 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 19 May 2026 17:32:56 -0400 Subject: [PATCH] =?UTF-8?q?spec:=20=C2=A76.5.1=20=E2=80=94=20Link=20DATA?= =?UTF-8?q?=20proof=20is=20signed=20with=20an=20Ed25519=20key,=20not=20the?= =?UTF-8?q?=20link-derived=20signing=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The §6.5.1 bullet said the Link DATA proof's signing key is "the link-derived signing key" — misleading. The link's HKDF output (§6.4.1) splits into a symmetric HMAC `signing_key` (for the link Token form, §3.1) and an `encryption_key`; that signing_key cannot produce an Ed25519 signature. Per RNS 1.2.9, the Link DATA proof is signed with the link's Ed25519 `sig_prv` (`RNS/Link.py:1212-1213` `sign()` uses `self.sig_prv`): - responder side: `owner.identity.sig_prv` (long-term identity Ed25519 private key, `Link.py:279`) - initiator side: a fresh ephemeral Ed25519 keypair generated at link creation (`Ed25519PrivateKey.generate()`, `Link.py:286`) §6.5.1 now states this explicitly and distinguishes it from the symmetric §6.4.1 signing_key. Closes #11. Co-Authored-By: Claude Opus 4.7 (1M context) --- SPEC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPEC.md b/SPEC.md index 89b07d5..e51788f 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1298,7 +1298,7 @@ implicit body = signature(64) Where: - `packet_hash = Identity.full_hash(original_packet.get_hashable_part())` — the full SHA-256 (32 bytes, **not** truncated to 16) of the prove-target packet's hashable part. `get_hashable_part` is the same recipe used for `link_id` derivation in §6.3, so the proof binds to the version of the packet that survived any HEADER_1↔HEADER_2 conversion in transit (the high nibble of flags, hops byte, and any HEADER_2 transport_id are stripped before hashing). -- `signature` is the destination's (or link's) Ed25519 signature **over `packet_hash`**, NOT over the proof body itself. The signing key is the destination's long-term Ed25519 private key for an opportunistic DATA proof, or the link-derived signing key for a Link DATA proof. +- `signature` is the destination's (or link's) Ed25519 signature **over `packet_hash`**, NOT over the proof body itself. The signing key is the destination's long-term Ed25519 private key for an opportunistic DATA proof, or **the link's Ed25519 signing key** (`Link.sig_prv`, `RNS/Link.py:279` / `:286` in RNS 1.2.9) for a Link DATA proof — the owner identity's long-term key on the responder side, the link's ephemeral Ed25519 keypair on the initiator side. (The HKDF-derived `signing_key` from §6.4.1 is a separate **symmetric HMAC key** for the link Token form §3.1 — it cannot produce an Ed25519 signature and is not used here.) The two forms are distinguished **purely by length** at the receiver. `PacketReceipt.validate_proof` (`RNS/Packet.py:497-548`) dispatches on `len(proof) == 96` (explicit) vs `len(proof) == 64` (implicit); lengths matching neither are rejected outright. There is no flag bit or context byte that signals which form is being used — wire length is the only signal.