Unify Noise IK for both link and session layers

Design change: Session layer now uses Noise IK pattern (previously KK),
matching the link layer. Rationale: initiator knows destination npub,
responder learns initiator identity from handshake - same asymmetry as
link connections.

Document consistency fixes:
- Auth* events → Noise IK msg1/msg2 terminology in state machines
- PeerId → NodeId in routing code examples
- BloomUpdate → FilterAnnounce terminology
- Updated cross-references and tables
This commit is contained in:
Johnathan Corgan
2026-02-02 02:25:39 +00:00
parent e582f50de7
commit a8115f7622
6 changed files with 58 additions and 56 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ concepts, and finally the wire-level protocol details.
| Document | Description |
|------------------------------------------------------|-----------------------------------------------------------|
| [fips-session-protocol.md](fips-session-protocol.md) | End-to-end session flow, Noise KK encryption, terminology |
| [fips-session-protocol.md](fips-session-protocol.md) | End-to-end session flow, Noise IK encryption, terminology |
### 3. Routing (How Packets Find Their Way)
+28 -25
View File
@@ -307,28 +307,32 @@ Inbound:
### Peer Lifecycle
The peer lifecycle uses Noise IK for authentication. Noise IK is a 2-message
handshake where the initiator knows the responder's static key. See
[fips-wire-protocol.md](fips-wire-protocol.md) §2 for wire format details.
```
┌─────────────────────────────────────────┐
│ Disconnected │
└─────────────────────────────────────────┘
│ │
[outbound] │ │ [inbound data]
[outbound] │ │ [inbound msg1]
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Connecting │ │AwaitingAuthInit
│(conn-oriented) │ │
│ Connecting │ │ ReceivedMsg1
│(conn-oriented) │ │ (send msg2)
└──────────────────┘ └──────────────────┘
│ │
[link ready] │ [recv AuthInit]
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│AwaitingChallenge │ │AwaitingComplete
(sent AuthInit) │ │(sent AuthChallenge)
└──────────────────┘ └──────────────────┘
[link ready] │ │ [recv encrypted]
[send msg1] │ │ [verify]
▼ │
┌──────────────────┐
AwaitingMsg2 │
│ (sent msg1) │ │
└──────────────────┘ │
│ │
[recv AuthChallenge] │ │ [recv AuthComplete]
[verify, send │ │ [verify]
AuthComplete] │ │
[recv msg2] │ │
[verify] │ │
▼ ▼
┌─────────────────────────────────────────┐
│ Active │
@@ -347,18 +351,18 @@ Inbound:
- `Disconnected`: No active connection; for static peers, retry with backoff
- `Connecting`: Link establishment in progress (connection-oriented transports only)
- `AwaitingAuthInit`: Inbound connection, waiting for peer's AuthInit
- `AwaitingChallenge`: Sent AuthInit, waiting for AuthChallenge
- `AwaitingComplete`: Sent AuthChallenge, waiting for AuthComplete
- `ReceivedMsg1`: Inbound; received Noise IK msg1, sent msg2, awaiting first encrypted frame
- `AwaitingMsg2`: Outbound; sent Noise IK msg1, waiting for msg2
- `Active`: Authenticated; participating in tree gossip and filter exchange
**Crossing connection handling:**
When in `AwaitingChallenge` and we receive an AuthInit from the same peer:
When in `AwaitingMsg2` and we receive a msg1 from the same peer (both sides
initiated simultaneously):
- If local npub < remote npub: Ignore incoming AuthInit, remain initiator
- If local npub > remote npub: Switch to responder role, send AuthChallenge,
transition to `AwaitingComplete`
- If local npub < remote npub: Ignore incoming msg1, remain initiator
- If local npub > remote npub: Switch to responder role, send msg2,
transition to `ReceivedMsg1`
**Events:**
@@ -367,11 +371,10 @@ PeerEvent
├── Discovered { link_id, transport_addr, hint: Option<PublicKey> }
├── LinkConnected
├── LinkFailed { reason }
├── AuthInitReceived { npub, nonce }
├── AuthChallengeReceived { npub, nonce, signature }
├── AuthCompleteReceived { signature }
├── AuthSuccess { npub, node_id }
├── AuthFailed { reason }
├── Msg1Received { noise_payload }
├── Msg2Received { noise_payload }
├── HandshakeComplete { npub, node_id }
├── HandshakeFailed { reason }
├── TreeAnnounceReceived { declaration, ancestry }
├── FilterAnnounceReceived { filter, sequence, ttl }
├── Timeout { kind: TimeoutKind }
@@ -945,7 +948,7 @@ configure the same underlying cache but are grouped by purpose.
### Crypto Session Management
> **Note**: Crypto sessions provide end-to-end authenticated encryption using
> Noise KK. See [fips-session-protocol.md](fips-session-protocol.md) §6 for details.
> Noise IK. See [fips-session-protocol.md](fips-session-protocol.md) §6 for details.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
+7 -6
View File
@@ -103,7 +103,7 @@ adapts these for multi-transport operation and Nostr identity integration.
**Encryption**: Link and session encryption use the [Noise Protocol
Framework](https://noiseprotocol.org/), the same foundation used by WireGuard,
Lightning Network, and other production systems. FIPS uses the IK pattern for
link authentication and KK pattern for end-to-end sessions.
link authentication and end-to-end sessions.
**Cryptographic primitives**: FIPS reuses Nostr's cryptographic stack—secp256k1
for keys, Schnorr signatures, SHA-256 for hashing, and ChaCha20-Poly1305 for
@@ -172,7 +172,7 @@ FIPS uses independent encryption at two layers:
| Layer | Scope | Pattern | Purpose |
|-------------|-------------|----------|--------------------------------------|
| **Link** | Hop-by-hop | Noise IK | Encrypt all traffic on each link |
| **Session** | End-to-end | Noise KK | Encrypt payload across multiple hops |
| **Session** | End-to-end | Noise IK | Encrypt payload across multiple hops |
### Link Layer (Peer-to-Peer)
@@ -188,8 +188,9 @@ the first handshake message.
### Session Layer (End-to-End)
For traffic between non-adjacent nodes, FIPS establishes end-to-end encrypted
sessions using Noise KK. Both parties know each other's npub before initiating,
enabling mutual authentication in a single round-trip.
sessions using Noise IK. The initiator knows the destination's npub; the
responder learns the initiator's identity from the handshake—the same asymmetry
as link-layer connections.
A packet from A to D through intermediate node B:
@@ -460,7 +461,7 @@ FIPS assumes adversaries with varying capabilities:
authentication and forward secrecy. An observer on the underlying transport
sees only encrypted packets.
**End-to-end encryption**: Session-layer Noise KK encrypts payloads between
**End-to-end encryption**: Session-layer Noise IK encrypts payloads between
endpoints. Intermediate routers cannot read application data.
**Signature verification**: All protocol messages (TreeAnnounce, LookupResponse)
@@ -529,7 +530,7 @@ maintaining control over their own identities and peering relationships.
| Document | Description |
|----------|-------------|
| [fips-session-protocol.md](fips-session-protocol.md) | End-to-end session flow, Noise KK encryption |
| [fips-session-protocol.md](fips-session-protocol.md) | End-to-end session flow, Noise IK encryption |
| [fips-wire-protocol.md](fips-wire-protocol.md) | Link-layer transport, Noise IK handshake |
| [fips-gossip-protocol.md](fips-gossip-protocol.md) | TreeAnnounce, FilterAnnounce, Lookup formats |
| [fips-routing.md](fips-routing.md) | Bloom filters, discovery, greedy routing |
+6 -6
View File
@@ -92,7 +92,7 @@ node's npub, truncated or used directly as the filter key.
Each node maintains a Bloom filter for each peer direction:
```rust
peer_filters: HashMap<PeerId, BloomFilter>
peer_filters: HashMap<NodeId, BloomFilter>
```
The filter for peer P answers: "Which destinations are reachable through P?"
@@ -248,7 +248,7 @@ Note: Coordinates are ordered self-to-root, so common ancestry is a suffix.
### Greedy Routing Algorithm
```rust
fn greedy_next_hop(&self, dest_coords: &[NodeId]) -> PeerId {
fn greedy_next_hop(&self, dest_coords: &[NodeId]) -> NodeId {
// Check if we are the destination
if dest_coords[0] == self.node_id {
return LOCAL_DELIVERY;
@@ -257,7 +257,7 @@ fn greedy_next_hop(&self, dest_coords: &[NodeId]) -> PeerId {
// Check if destination is a direct peer
for peer in &self.peers {
if peer.node_id == dest_coords[0] {
return peer.id;
return peer.node_id;
}
}
@@ -265,7 +265,7 @@ fn greedy_next_hop(&self, dest_coords: &[NodeId]) -> PeerId {
self.peers
.iter()
.min_by_key(|p| tree_distance(&p.coords, dest_coords))
.map(|p| p.id)
.map(|p| p.node_id)
.expect("no peers")
}
```
@@ -331,7 +331,7 @@ struct SessionSetup {
// Crypto session establishment (see fips-session-protocol.md §6)
// Opaque to routers; only processed by destination
handshake_payload: Option<Vec<u8>>, // Noise KK message 1
handshake_payload: Option<Vec<u8>>, // Noise IK message 1
}
struct SessionFlags {
@@ -346,7 +346,7 @@ struct SessionAck {
src_coords: Vec<NodeId>, // Acknowledger's coords (for return caching)
// Crypto session response (see fips-session-protocol.md §6)
handshake_payload: Option<Vec<u8>>, // Noise KK message 2
handshake_payload: Option<Vec<u8>>, // Noise IK message 2
}
/// Minimal data packet
+15 -17
View File
@@ -169,7 +169,7 @@ Payloads within a session are:
1. **Encrypted** with the session key (provides confidentiality)
2. **Authenticated** via AEAD tag (session keys bound to npub identities)
Authentication derives from the Noise KK handshake binding session keys to
Authentication derives from the Noise IK handshake binding session keys to
both parties' static keys. See §6 for cryptographic details.
### 3.2 Session Establishment Trigger
@@ -203,10 +203,9 @@ paths.
### 3.4 Session Establishment Flow
FIPS uses Noise KK for session establishment. Both parties know each other's
static keys: the initiator from DNS lookup, the responder from the source
address in the SessionSetup. The KK pattern provides mutual authentication
from the first message.
FIPS uses Noise IK for session establishment. The initiator knows the
destination's npub; the responder learns the initiator's identity from the
handshake. This is the same asymmetry as link-layer connections.
The handshake is carried inside SessionSetup/SessionAck messages (see §5.5),
which also establish routing session state at intermediate nodes.
@@ -268,7 +267,7 @@ FIPS uses two distinct session concepts at different layers:
### 5.1 Crypto Session
- Established between two npub identities
- Provides confidentiality and authenticity via Noise KK
- Provides confidentiality and authenticity via Noise IK
- Survives route changes and transport failover
- Keyed by: `(local_npub, remote_npub)`
@@ -291,14 +290,14 @@ crypto handshake, minimizing round-trips.
2. SessionSetup + Crypto Init
└─► Source sends SessionSetup containing:
- src/dest coordinates (for router caching)
- Noise KK handshake initiation (for destination)
- Noise IK handshake initiation (for destination)
└─► Routers cache coordinates as packet transits
└─► Destination receives crypto init, begins handshake
3. SessionAck + Crypto Response
└─► Destination sends SessionAck containing:
- Its coordinates (for reverse path caching)
- Noise KK handshake response
- Noise IK handshake response
└─► Routers cache reverse path
└─► Source completes crypto handshake
@@ -321,7 +320,7 @@ FIPS uses two independent Noise Protocol handshakes at different layers:
| Layer | Scope | Pattern | Purpose |
|---------|-------------|----------|-------------------------------------------|
| Link | Hop-by-hop | Noise IK | Authenticate peers, encrypt link |
| Session | End-to-end | Noise KK | Authenticate endpoints, encrypt payload |
| Session | End-to-end | Noise IK | Authenticate endpoints, encrypt payload |
Both use `Noise_IK_secp256k1_ChaChaPoly_SHA256` with the same cryptographic
primitives, but with separate keys and sessions.
@@ -336,21 +335,21 @@ nodes forward opaque ciphertext without being able to read the contents.
### 6.2 Session Noise Handshake
The session-layer Noise KK handshake is carried inside `SessionSetup`/`SessionAck`
The session-layer Noise IK handshake is carried inside `SessionSetup`/`SessionAck`
messages, which themselves travel through the link-encrypted channel:
```text
Initiator knows destination npub (from DNS lookup)
SessionSetup { coords, handshake_payload: Noise KK msg1 }
SessionSetup { coords, handshake_payload: Noise IK msg1 }
▼ (travels through link-encrypted hops)
Responder processes msg1, learns initiator identity
SessionAck { coords, handshake_payload: Noise KK msg2 }
SessionAck { coords, handshake_payload: Noise IK msg2 }
Session keys established (independent of link keys)
@@ -386,7 +385,7 @@ SessionSetup {
dest_addr: Ipv6Addr,
// Crypto portion (opaque to routers, processed by destination)
handshake_payload: Vec<u8>, // Noise KK message 1
handshake_payload: Vec<u8>, // Noise IK message 1
}
SessionAck {
@@ -394,7 +393,7 @@ SessionAck {
src_coords: Vec<NodeId>, // Responder's coordinates
// Crypto portion
handshake_payload: Vec<u8>, // Noise KK message 2
handshake_payload: Vec<u8>, // Noise IK message 2
}
```
@@ -427,13 +426,12 @@ The ephemeral keys (`e` in Noise notation) provide forward secrecy:
Lightning's adaptation of Noise for secp256k1 (BOLT 8) provides a proven
reference implementation:
- Uses Noise XK pattern (different from our KK)
- Uses Noise XK pattern (different from our IK)
- Same secp256k1 + ChaCha20-Poly1305 + SHA-256 stack
- Handles the secp256k1 ECDH correctly
- Open source implementations available in multiple languages
FIPS can reference BOLT 8's cryptographic details while using the KK pattern
appropriate for our mutual-knowledge scenario.
FIPS can reference BOLT 8's cryptographic details while using the IK pattern.
### 6.9 Data Packet Authentication
+1 -1
View File
@@ -196,7 +196,7 @@ D receives and merges:
```
D adds B's node_id to its bloom filter
D sends BloomUpdate to parent A
D sends FilterAnnounce to parent A
A merges D's bloom filter with its view of D's subtree
A now knows "B is reachable through D" (probabilistically)