mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Update design docs for session 142 implementation changes
Comprehensive documentation review across 12 files to reflect: - Noise XK at FSP (was IK), 3-message handshake, SessionMsg3 wire format - Epoch exchange in Noise handshakes for peer restart detection - Per-link MTU, min_mtu/path_mtu in lookup packets - MtuExceeded (0x22) error signal wire format and behavior - LookupResponse proof now covers target_coords - Discovery reverse-path routing as primary (not greedy) - Control socket architecture and fipsctl binary - FSP handshake state machine (Initiating/AwaitingMsg3/Established) - Root timeout framing updated for heartbeat cascading
This commit is contained in:
@@ -89,10 +89,15 @@ FMP signals routing failures asynchronously:
|
||||
- **PathBroken**: Greedy routing reached a dead end. FSP responds by sending
|
||||
a standalone CoordsWarmup (rate-limited), re-discovering the destination's
|
||||
current coordinates, and resetting the warmup counter.
|
||||
- **MtuExceeded**: A transit node cannot forward a SessionDatagram because
|
||||
the packet exceeds the next-hop link MTU. FSP uses the reported bottleneck
|
||||
MTU to adjust its session-layer path MTU estimate. MtuExceeded is the
|
||||
reactive complement to the proactive `path_mtu` field in SessionDatagram.
|
||||
|
||||
Both signals are generated by transit nodes (not the destination) and travel
|
||||
back to the source inside a new SessionDatagram. They are plaintext (not
|
||||
end-to-end encrypted) because transit nodes have no session with the source.
|
||||
All three signals are generated by transit nodes (not the destination) and
|
||||
travel back to the source inside a new SessionDatagram. They are plaintext
|
||||
(not end-to-end encrypted) because transit nodes have no session with the
|
||||
source.
|
||||
|
||||
### Local Delivery
|
||||
|
||||
@@ -106,18 +111,29 @@ node, FMP delivers it to FSP for session-layer processing.
|
||||
Sessions are established on demand when the first datagram needs to be sent to
|
||||
a destination with no existing session.
|
||||
|
||||
FSP uses Noise IK for session key agreement. The initiator knows the
|
||||
destination's npub (from DNS lookup or native API); the responder learns the
|
||||
initiator's identity from the handshake. This is the same asymmetry as
|
||||
link-layer peer connections.
|
||||
FSP uses Noise XK for session key agreement. The initiator knows the
|
||||
destination's npub (required for XK's pre-message `s` token); the
|
||||
responder learns the initiator's identity from msg3 (not msg1, unlike
|
||||
IK at the link layer). This provides stronger initiator identity hiding
|
||||
— the initiator's static key is encrypted under the established shared
|
||||
secret rather than under only the responder's static key.
|
||||
|
||||
The handshake is carried in SessionSetup and SessionAck messages:
|
||||
The handshake is a three-message flow carried in SessionSetup, SessionAck,
|
||||
and SessionMsg3:
|
||||
|
||||
1. **Initiator** sends SessionSetup containing Noise IK msg1 and both
|
||||
parties' tree coordinates
|
||||
2. **Responder** processes msg1, learns initiator identity, sends SessionAck
|
||||
containing Noise IK msg2 and both parties' tree coordinates
|
||||
3. Both parties derive identical symmetric session keys
|
||||
1. **Initiator** sends SessionSetup containing Noise XK msg1 (ephemeral key
|
||||
only) and both parties' tree coordinates
|
||||
2. **Responder** processes msg1, sends SessionAck containing Noise XK msg2
|
||||
(ephemeral key + encrypted epoch) and both parties' tree coordinates.
|
||||
The responder transitions to AwaitingMsg3 state.
|
||||
3. **Initiator** processes msg2, sends SessionMsg3 containing the encrypted
|
||||
static key and encrypted epoch. Both parties derive identical symmetric
|
||||
session keys and the session is established.
|
||||
|
||||
Each side's epoch (an 8-byte random value generated at startup) is
|
||||
exchanged encrypted in msg2 and msg3. On subsequent handshakes, an epoch
|
||||
mismatch indicates the peer has restarted, triggering session
|
||||
re-establishment.
|
||||
|
||||
Packets that trigger session establishment are queued (with bounded buffer)
|
||||
and transmitted after the session is established.
|
||||
@@ -187,22 +203,30 @@ transport addresses or routing paths. A session survives:
|
||||
|
||||
## End-to-End Encryption
|
||||
|
||||
### Noise IK Pattern
|
||||
### Noise XK Pattern
|
||||
|
||||
FSP uses the same Noise IK pattern as FMP link encryption, but with
|
||||
independent keys and sessions. The full Noise descriptor is
|
||||
`Noise_IK_secp256k1_ChaChaPoly_SHA256`.
|
||||
FSP uses Noise XK for session encryption, distinct from the Noise IK
|
||||
pattern used at the link layer. The full Noise descriptor is
|
||||
`Noise_XK_secp256k1_ChaChaPoly_SHA256`.
|
||||
|
||||
The IK pattern:
|
||||
The XK pattern (pre-message: `← s`):
|
||||
|
||||
- **msg1** (`→ e, es, s, ss`): Initiator sends ephemeral key, encrypts static
|
||||
key to responder. Four DH operations establish session keys.
|
||||
- **msg2** (`← e, ee, se`): Responder sends ephemeral key. Both parties now
|
||||
share identical session keys.
|
||||
- **msg1** (`→ e, es`): Initiator sends ephemeral key only. The initiator's
|
||||
static identity is not revealed in this message.
|
||||
- **msg2** (`← e, ee`): Responder sends ephemeral key and encrypted epoch.
|
||||
- **msg3** (`→ s, se`): Initiator sends encrypted static key and encrypted
|
||||
epoch. Both parties now share identical session keys.
|
||||
|
||||
After the handshake, Noise produces two directional symmetric keys
|
||||
(`send_key`, `recv_key`) used with ChaCha20-Poly1305 for all subsequent data.
|
||||
|
||||
The XK pattern requires the initiator to know the responder's static key
|
||||
in advance (the `← s` pre-message), which is satisfied by the discovery
|
||||
or DNS lookup that precedes session establishment. In exchange, XK
|
||||
provides stronger initiator identity protection than IK — the initiator's
|
||||
static key is encrypted under the full shared secret (after three DH
|
||||
operations) rather than under only the responder's static key.
|
||||
|
||||
### Cryptographic Primitives
|
||||
|
||||
| Component | Choice | Notes |
|
||||
@@ -218,7 +242,7 @@ These choices prioritize compatibility with the Nostr cryptographic stack.
|
||||
### secp256k1 Parity Normalization
|
||||
|
||||
Nostr npubs encode x-only public keys (32 bytes, no y-coordinate parity). The
|
||||
Noise IK pre-message mixes the responder's static key as a 33-byte compressed
|
||||
Noise XK pre-message mixes the responder's static key as a 33-byte compressed
|
||||
key, and the default secp256k1 ECDH hash includes a parity-dependent version
|
||||
byte.
|
||||
|
||||
@@ -229,12 +253,14 @@ actual key parity.
|
||||
|
||||
### Privacy Note
|
||||
|
||||
Noise IK does not provide initiator anonymity if the responder's static key is
|
||||
compromised. An attacker who obtains the responder's nsec can decrypt the
|
||||
initiator's identity from captured handshake messages. Noise XK would protect
|
||||
initiator identity in this scenario but requires an additional round-trip (3
|
||||
handshake messages vs. 2). The privacy/latency tradeoff may be revisited with
|
||||
deployment experience.
|
||||
Noise XK provides stronger initiator identity protection than IK. In XK, the
|
||||
initiator's static key is encrypted in msg3 under the full shared secret
|
||||
(derived from three DH operations), so an attacker who compromises only the
|
||||
responder's nsec cannot decrypt the initiator's identity from captured
|
||||
handshake messages (they would also need the responder's ephemeral key).
|
||||
This is the primary reason FSP uses XK rather than IK — session-layer
|
||||
traffic traverses untrusted intermediate nodes, making initiator identity
|
||||
protection more valuable than at the link layer.
|
||||
|
||||
### Data Packet Authentication
|
||||
|
||||
@@ -460,7 +486,7 @@ feedback:
|
||||
|
||||
1. The source sets `path_mtu` in each SessionDatagram envelope to its
|
||||
outbound link MTU.
|
||||
2. Each transit node applies `min(current, transport.mtu())` before
|
||||
2. Each transit node applies `min(current, transport.link_mtu(addr))` before
|
||||
forwarding.
|
||||
3. The destination receives the forward-path minimum and sends a
|
||||
PathMtuNotification (2-byte body: u16 LE path_mtu) back to the source.
|
||||
@@ -492,7 +518,9 @@ MMP session metrics session=npub1tdwa...84le rtt=4.3ms loss=0.6% jitter=0.2ms go
|
||||
|
||||
| Feature | Status |
|
||||
| ------- | ------ |
|
||||
| Session establishment (Noise IK) | **Implemented** |
|
||||
| Session establishment (Noise XK) | **Implemented** |
|
||||
| Peer restart detection (epoch exchange) | **Implemented** |
|
||||
| MtuExceeded handling | **Implemented** |
|
||||
| End-to-end encryption (ChaCha20-Poly1305) | **Implemented** |
|
||||
| Explicit counter replay protection | **Implemented** |
|
||||
| Hybrid coordinate warmup (CP + CoordsWarmup) | **Implemented** |
|
||||
|
||||
Reference in New Issue
Block a user