mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 08:37:02 +00:00
Separate protocol layers: link vs session message types
Refactor protocol.rs to cleanly separate two protocol layers: Link layer (hop-by-hop, peer-to-peer): - LinkMessageType: TreeAnnounce, FilterAnnounce, LookupRequest, LookupResponse, SessionDatagram - New SessionDatagram struct for encapsulating session payloads Session layer (end-to-end): - SessionMessageType: SessionSetup, SessionAck, DataPacket, CoordsRequired, PathBroken Also: - Remove obsolete Hello/Challenge/Auth/AuthAck types (replaced by Noise IK) - Update all design docs for consistency with two-layer architecture - Add "Link vs Session Encryption" documentation All 219 tests pass.
This commit is contained in:
+102
-139
@@ -111,153 +111,107 @@ signature = schnorr_sign(nsec, SHA256(message))
|
||||
**Challenge-response authentication** is used during connection establishment
|
||||
to prove a node controls the private key corresponding to its claimed npub.
|
||||
|
||||
### Peer Authentication Protocol
|
||||
### Peer Authentication Protocol (Noise IK)
|
||||
|
||||
When two nodes establish a connection, they perform mutual authentication to
|
||||
verify each other's identity. This prevents impersonation attacks where an
|
||||
adversary claims to be a node it doesn't control.
|
||||
When two nodes establish a connection, they perform mutual authentication using
|
||||
the Noise Protocol Framework with the IK pattern. This provides:
|
||||
|
||||
- Mutual authentication (both parties prove identity)
|
||||
- Forward secrecy (ephemeral keys protect past sessions)
|
||||
- Encrypted link (all subsequent messages are encrypted)
|
||||
|
||||
> **Note**: This authentication protocol is not derived from Yggdrasil, which
|
||||
> relies on transport-layer security (TLS/QUIC) for identity binding. FIPS
|
||||
> requires an explicit application-layer protocol because it supports transports
|
||||
> without built-in encryption or key exchange (radio links, serial connections).
|
||||
> For initial implementation, peer authentication is always performed regardless
|
||||
> of transport capabilities; this may be optimized in future versions.
|
||||
>
|
||||
> **Terminology note**: *Peer authentication* (this section) is hop-by-hop—it
|
||||
> verifies that a direct peer is who they claim to be. This is distinct from
|
||||
> *crypto sessions* (see [fips-protocol-flow.md](fips-protocol-flow.md) §6),
|
||||
> which provide end-to-end authenticated encryption between source and
|
||||
> destination using Noise KK. Both layers are necessary: peer auth secures the
|
||||
> local link; crypto sessions secure the full path.
|
||||
> verifies that a direct peer is who they claim to be and establishes an
|
||||
> encrypted link. This is distinct from *session-layer encryption* which
|
||||
> provides end-to-end authenticated encryption between FIPS addresses. Both
|
||||
> layers operate independently: peer auth secures each link; session encryption
|
||||
> secures the full path.
|
||||
|
||||
#### Protocol: Noise_IK_secp256k1_ChaChaPoly_SHA256
|
||||
|
||||
The IK pattern is chosen because:
|
||||
|
||||
- **Outbound connections**: Initiator knows responder's static key from config
|
||||
- **Inbound connections**: Responder learns initiator's identity from message 1
|
||||
|
||||
```text
|
||||
Pre-message (known before handshake):
|
||||
<- s (responder's static key known to initiator)
|
||||
|
||||
Handshake:
|
||||
-> e, es, s, ss (msg1: initiator sends ephemeral + encrypted static)
|
||||
<- e, ee, se (msg2: responder sends ephemeral)
|
||||
```
|
||||
|
||||
```text
|
||||
Initiator (A) Responder (B)
|
||||
│ │
|
||||
│───── AuthInit { a_npub, nonce_a } ─────────►│
|
||||
│ knows B's npub from config │
|
||||
│ │
|
||||
│◄──── AuthChallenge { b_npub, nonce_b, │
|
||||
│ sig_b(nonce_a | a_npub) } ─────────│
|
||||
│ │
|
||||
│───── AuthComplete { │
|
||||
│ sig_a(nonce_b | b_npub) } ────────►│
|
||||
│ │
|
||||
[A verifies B after msg 2] [B verifies A after msg 3]
|
||||
│───── msg1 (82 bytes) ──────────────────────►│
|
||||
│ e: ephemeral pubkey (33) │
|
||||
│ s: encrypted static (33+16) │
|
||||
│ │ learns A's identity
|
||||
│◄───── msg2 (33 bytes) ──────────────────────│
|
||||
│ e: ephemeral pubkey (33) │
|
||||
│ │
|
||||
▼ ▼
|
||||
Authenticated Authenticated
|
||||
Noise session established Noise session established
|
||||
(symmetric keys derived) (symmetric keys derived)
|
||||
```
|
||||
|
||||
**Protocol flow:**
|
||||
#### Cryptographic Primitives
|
||||
|
||||
1. **AuthInit**: Initiator sends its npub and a 32-byte random nonce
|
||||
2. **AuthChallenge**: Responder sends its npub, its own nonce, and a signature
|
||||
proving it controls its nsec (signing the initiator's nonce and npub)
|
||||
3. **AuthComplete**: Initiator sends a signature proving it controls its nsec
|
||||
(signing the responder's nonce and npub)
|
||||
| Component | Choice | Notes |
|
||||
|-----------|---------------------|---------------------------|
|
||||
| Curve | secp256k1 | Nostr-native |
|
||||
| DH | ECDH on secp256k1 | Standard EC Diffie-Hellman|
|
||||
| AEAD | ChaCha20-Poly1305 | Same as NIP-44 |
|
||||
| Hash | SHA-256 | Nostr-native |
|
||||
| KDF | HKDF-SHA256 | Standard Noise KDF |
|
||||
|
||||
After message 2, the initiator can verify the responder's identity. After
|
||||
message 3, the responder can verify the initiator's identity. Both nodes have
|
||||
now proven they control their claimed private keys.
|
||||
#### Crossing Connection Handling
|
||||
|
||||
### Crossing Connection Handling
|
||||
|
||||
When both nodes have each other as static peers, both may initiate authentication
|
||||
simultaneously ("crossing hellos"). This is resolved using deterministic
|
||||
tie-breaking based on npub ordering:
|
||||
When both nodes simultaneously initiate connections, a deterministic tie-breaker
|
||||
resolves which connection survives:
|
||||
|
||||
```text
|
||||
A (lower npub) B (higher npub)
|
||||
│ │
|
||||
│─── AuthInit { a_npub, nonce_a } ────────►│
|
||||
│◄── AuthInit { b_npub, nonce_b } ─────────│ (crossing)
|
||||
│ │
|
||||
A < B: ignore B's init, B > A: switch to responder,
|
||||
wait for challenge send AuthChallenge
|
||||
│ │
|
||||
│◄── AuthChallenge { b_npub, ... } ────────│
|
||||
│─── AuthComplete { ... } ────────────────►│
|
||||
│ │
|
||||
▼ ▼
|
||||
Authenticated Authenticated
|
||||
Rule: Smaller node_id's OUTBOUND connection wins
|
||||
|
||||
If our_node_id < their_node_id:
|
||||
- Our outbound wins, close our inbound
|
||||
If our_node_id > their_node_id:
|
||||
- Our inbound wins, close our outbound
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
Both nodes independently reach the same conclusion without coordination.
|
||||
|
||||
- If a node receives AuthInit while its own AuthInit is pending to the same peer:
|
||||
- If local npub < remote npub: Continue as initiator, ignore incoming AuthInit
|
||||
- If local npub > remote npub: Abort own initiation, switch to responder role
|
||||
#### Post-Authentication State
|
||||
|
||||
This ensures exactly one handshake completes with minimal wasted effort.
|
||||
After the Noise handshake completes:
|
||||
|
||||
### Authentication Message Structures
|
||||
- **NoiseSession** holds symmetric keys for encrypt/decrypt
|
||||
- Link is fully encrypted (all subsequent messages use AEAD)
|
||||
- Peer's verified identity is extracted from the handshake
|
||||
- Node transitions from `PeerConnection` to `ActivePeer` state
|
||||
|
||||
```rust
|
||||
struct AuthInit {
|
||||
npub: [u8; 32], // Initiator's public key (x-only)
|
||||
nonce: [u8; 32], // Random challenge
|
||||
}
|
||||
#### Link vs Session Encryption
|
||||
|
||||
struct AuthChallenge {
|
||||
npub: [u8; 32], // Responder's public key (x-only)
|
||||
nonce: [u8; 32], // Responder's challenge
|
||||
signature: [u8; 64], // sig(initiator_nonce || initiator_npub)
|
||||
}
|
||||
FIPS uses two independent encryption layers:
|
||||
|
||||
struct AuthComplete {
|
||||
signature: [u8; 64], // sig(responder_nonce || responder_npub)
|
||||
}
|
||||
```
|
||||
| Layer | Scope | Keys | Purpose |
|
||||
|-------------|-------------|--------------------------|--------------------------------------|
|
||||
| **Link** | Hop-by-hop | Per-peer Noise session | Encrypt all traffic on this link |
|
||||
| **Session** | End-to-end | Per-destination session | Encrypt payload across multiple hops |
|
||||
|
||||
### Signature Construction
|
||||
A packet from A→D through B traverses:
|
||||
|
||||
Signatures are constructed with domain separation to prevent cross-protocol
|
||||
signature reuse:
|
||||
1. A encrypts payload with A↔D session key
|
||||
2. A encrypts that with A↔B link key, sends to B
|
||||
3. B decrypts link layer, routes, re-encrypts with B↔C or B↔D link key
|
||||
4. D decrypts link layer, then decrypts session layer to get payload
|
||||
|
||||
```text
|
||||
digest = SHA256("fips-peer-auth-v1" || peer_nonce || peer_npub)
|
||||
signature = schnorr_sign(nsec, digest)
|
||||
```
|
||||
|
||||
**Domain separation**: The `"fips-peer-auth-v1"` prefix ensures that signatures
|
||||
created for FIPS peer authentication cannot be replayed in other contexts (e.g.,
|
||||
a Nostr event signature or FIPS crypto session). If the authentication protocol
|
||||
is revised, the version string changes (e.g., `"fips-peer-auth-v2"`).
|
||||
|
||||
**Nonce freshness**: The 32-byte random nonce from the peer ensures that
|
||||
signatures cannot be pre-computed. Each authentication attempt requires a
|
||||
fresh signature over the peer's unique challenge.
|
||||
|
||||
**Binding to peer identity**: The signature includes the peer's npub, binding
|
||||
the response to that specific peer. This prevents relay attacks where an
|
||||
adversary forwards a challenge from one node and uses the response with another.
|
||||
|
||||
### Authentication Failure Handling
|
||||
|
||||
If authentication fails at any step:
|
||||
|
||||
- **Invalid signature**: Connection is terminated immediately
|
||||
- **Wrong npub**: The node is not who it claimed to be; terminate
|
||||
- **Expired timestamp**: Possible replay attack; terminate
|
||||
- **Timeout**: Peer did not respond in time; terminate
|
||||
|
||||
Nodes should implement rate limiting on authentication attempts to prevent
|
||||
denial-of-service attacks that exhaust computational resources through
|
||||
repeated signature verifications.
|
||||
|
||||
### Post-Authentication State
|
||||
|
||||
After successful authentication, each node stores:
|
||||
|
||||
- The peer's verified npub and derived node_id
|
||||
- The link over which the peer was authenticated
|
||||
- Timestamp of successful authentication
|
||||
|
||||
This state is used for:
|
||||
|
||||
- Routing decisions (only forward to authenticated peers)
|
||||
- TreeAnnounce signature verification (cached public key lookup)
|
||||
- Session resumption on transient disconnections (within a timeout window)
|
||||
Intermediate nodes can route but cannot read session-layer payloads.
|
||||
|
||||
---
|
||||
|
||||
@@ -627,36 +581,45 @@ A single node may have multiple transports of different types:
|
||||
|
||||
## 6. Protocol Messages
|
||||
|
||||
FIPS uses two independent message type spaces corresponding to the two protocol
|
||||
layers. Messages are exchanged over Noise-encrypted links after peer authentication.
|
||||
|
||||
### Wire Format
|
||||
|
||||
```
|
||||
```text
|
||||
┌────────┬────────┬────────────────────────────────────┐
|
||||
│ Type │ Length │ Payload │
|
||||
│ 1 byte │ 2 bytes│ Variable │
|
||||
└────────┴────────┴────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Message Types
|
||||
### Link Layer Messages (Peer-to-Peer)
|
||||
|
||||
| Type | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 0x00 | Dummy | Keepalive/padding |
|
||||
| 0x01 | TreeAnnounce | Spanning tree state |
|
||||
| 0x02 | BloomUpdate | Bloom filter update |
|
||||
| 0x03 | Lookup | Destination lookup request |
|
||||
| 0x04 | LookupResponse | Coordinates for requested key |
|
||||
| 0x05 | PathBroken | Route failure notification |
|
||||
| 0x06 | SessionSetup | Routing session + crypto handshake init |
|
||||
| 0x07 | SessionAck | Routing session ack + crypto response |
|
||||
| 0x08 | CoordsRequired | Router cache miss notification |
|
||||
| 0x09 | AuthInit | Peer authentication initiation |
|
||||
| 0x0a | AuthChallenge | Peer authentication challenge + response |
|
||||
| 0x0b | AuthComplete | Peer authentication completion |
|
||||
| 0x10 | Traffic | Encrypted application data |
|
||||
| 0x11 | TrafficAck | Delivery acknowledgement |
|
||||
Exchanged between directly connected peers over Noise-encrypted links.
|
||||
Peer authentication uses Noise IK handshake (see §1) before any messages.
|
||||
|
||||
See [fips-routing.md](fips-routing.md) Part 4 for routing session details and
|
||||
[fips-protocol-flow.md](fips-protocol-flow.md) §5-6 for combined establishment.
|
||||
| Type | Name | Description |
|
||||
|------|----------------|--------------------------------------------|
|
||||
| 0x10 | TreeAnnounce | Spanning tree state announcement |
|
||||
| 0x20 | FilterAnnounce | Bloom filter reachability update |
|
||||
| 0x30 | LookupRequest | Request to discover node coordinates |
|
||||
| 0x31 | LookupResponse | Response with target's coordinates |
|
||||
| 0x40 | SessionDatagram| Encapsulated session-layer payload |
|
||||
|
||||
### Session Layer Messages (End-to-End)
|
||||
|
||||
Carried inside `SessionDatagram`, encrypted with end-to-end session keys.
|
||||
Intermediate nodes route based on destination but cannot read the payload.
|
||||
|
||||
| Type | Name | Description |
|
||||
|------|----------------|--------------------------------------------|
|
||||
| 0x00 | SessionSetup | Session establishment with coordinates |
|
||||
| 0x01 | SessionAck | Session acknowledgement |
|
||||
| 0x10 | DataPacket | Encrypted IPv6 datagram |
|
||||
| 0x20 | CoordsRequired | Router cache miss notification |
|
||||
| 0x21 | PathBroken | Route failure notification |
|
||||
|
||||
See [fips-routing.md](fips-routing.md) Part 4 for routing session details.
|
||||
|
||||
### TreeAnnounce
|
||||
|
||||
|
||||
@@ -516,62 +516,59 @@ the crypto portion.
|
||||
|
||||
---
|
||||
|
||||
## 6. Crypto Session Handshake
|
||||
## 6. Session-Layer Encryption
|
||||
|
||||
The crypto session uses the Noise Protocol Framework with secp256k1, aligning
|
||||
with Nostr's cryptographic primitives.
|
||||
FIPS uses two independent Noise Protocol handshakes at different layers:
|
||||
|
||||
### 6.1 Design Decision: Noise with secp256k1
|
||||
| Layer | Scope | Pattern | Purpose |
|
||||
|---------|-------------|----------|-------------------------------------------|
|
||||
| Link | Hop-by-hop | Noise IK | Authenticate peers, encrypt link |
|
||||
| Session | End-to-end | Noise IK | Authenticate endpoints, encrypt payload |
|
||||
|
||||
**Decision**: Use Noise Protocol Framework adapted for secp256k1.
|
||||
Both use `Noise_IK_secp256k1_ChaChaPoly_SHA256` with the same cryptographic
|
||||
primitives, but with separate keys and sessions.
|
||||
|
||||
Rationale:
|
||||
### 6.1 Why Two Layers?
|
||||
|
||||
- Well-analyzed framework with formal security proofs
|
||||
- Used successfully in WireGuard, Lightning (BOLT 8), Signal
|
||||
- Lightning Network already adapted Noise for secp256k1 (precedent)
|
||||
- Reuses Nostr's existing key infrastructure (npub/nsec)
|
||||
- Provides forward secrecy via ephemeral keys
|
||||
**Link encryption** protects against passive observers on each hop but allows
|
||||
intermediate nodes to see routing information (destination address).
|
||||
|
||||
### 6.2 Pattern Selection
|
||||
**Session encryption** protects the actual payload end-to-end. Intermediate
|
||||
nodes forward opaque ciphertext without being able to read the contents.
|
||||
|
||||
Since both parties know each other's static public key (npub) before the
|
||||
handshake begins (from DNS lookup / identity cache), the **Noise KK** pattern
|
||||
is appropriate:
|
||||
### 6.2 Session Noise Handshake
|
||||
|
||||
The session-layer Noise IK handshake is carried inside `SessionSetup`/`SessionAck`
|
||||
messages, which themselves travel through the link-encrypted channel:
|
||||
|
||||
```text
|
||||
Noise_KK_secp256k1_ChaChaPoly_SHA256
|
||||
|
||||
KK:
|
||||
-> e, es, ss
|
||||
<- e, ee, se
|
||||
Initiator knows destination npub (from DNS lookup)
|
||||
│
|
||||
▼
|
||||
SessionSetup { coords, handshake_payload: Noise IK msg1 }
|
||||
│
|
||||
▼ (travels through link-encrypted hops)
|
||||
│
|
||||
Responder processes msg1, learns initiator identity
|
||||
│
|
||||
▼
|
||||
SessionAck { coords, handshake_payload: Noise IK msg2 }
|
||||
│
|
||||
▼
|
||||
Session keys established (independent of link keys)
|
||||
```
|
||||
|
||||
**Message 1 (Initiator → Responder):**
|
||||
### 6.3 Cryptographic Primitives
|
||||
|
||||
- `e`: Initiator's ephemeral public key
|
||||
- `es`: DH(initiator_ephemeral, responder_static)
|
||||
- `ss`: DH(initiator_static, responder_static)
|
||||
Both link and session layers use the same stack:
|
||||
|
||||
**Message 2 (Responder → Initiator):**
|
||||
|
||||
- `e`: Responder's ephemeral public key
|
||||
- `ee`: DH(initiator_ephemeral, responder_ephemeral)
|
||||
- `se`: DH(responder_ephemeral, initiator_static)
|
||||
|
||||
After both messages, both parties derive identical symmetric keys for
|
||||
encryption in each direction.
|
||||
|
||||
### 6.3 Why KK (not IK or XX)
|
||||
|
||||
| Pattern | Knowledge | Messages | FIPS Fit |
|
||||
|---------|-----------|----------|----------|
|
||||
| **KK** | Both know both static keys | 1 RT | Best - we have npubs |
|
||||
| IK | Initiator knows responder | 1 RT | Works but asymmetric |
|
||||
| XX | Neither knows | 2 RT | Unnecessary overhead |
|
||||
|
||||
KK provides mutual authentication in a single round-trip since FIPS always
|
||||
knows the peer's npub before initiating (from DNS/identity cache).
|
||||
| Component | Choice | Notes |
|
||||
|----------------|---------------------|----------------------------|
|
||||
| Curve | secp256k1 | Nostr-native |
|
||||
| DH | ECDH on secp256k1 | Standard EC Diffie-Hellman |
|
||||
| Cipher | ChaCha20-Poly1305 | AEAD, same as NIP-44 |
|
||||
| Hash | SHA-256 | Nostr-native |
|
||||
| Key derivation | HKDF-SHA256 | Standard Noise KDF |
|
||||
|
||||
### 6.4 Cryptographic Primitives
|
||||
|
||||
@@ -683,11 +680,11 @@ this should be updated to reflect AEAD-only authentication.
|
||||
## 7. Peer Connection Establishment
|
||||
|
||||
Before any of the traffic flows described above can occur, nodes must establish
|
||||
authenticated peer connections. This section provides a brief overview; see
|
||||
[fips-design.md](fips-design.md) §1 for the full peer authentication protocol
|
||||
and [fips-architecture.md](fips-architecture.md) for the startup sequence.
|
||||
authenticated peer connections using Noise IK. See [fips-design.md](fips-design.md)
|
||||
§1 for full protocol details and [fips-architecture.md](fips-architecture.md)
|
||||
for the startup sequence.
|
||||
|
||||
### 7.1 Connection Flow Summary
|
||||
### 7.1 Connection Flow Summary (Noise IK)
|
||||
|
||||
**Outbound (to static peer):**
|
||||
|
||||
@@ -698,43 +695,38 @@ Config: npub + transport hint (e.g., "udp:192.168.1.1:4000")
|
||||
Create link via transport
|
||||
│
|
||||
▼
|
||||
Send AuthInit { our_npub, nonce }
|
||||
Noise IK msg1 (82 bytes): ephemeral + encrypted static key
|
||||
│
|
||||
▼
|
||||
Receive AuthChallenge { peer_npub, peer_nonce, signature }
|
||||
Receive msg2 (33 bytes): peer's ephemeral key
|
||||
│
|
||||
▼
|
||||
Verify signature, send AuthComplete { signature }
|
||||
│
|
||||
▼
|
||||
Peer authenticated → begins tree gossip
|
||||
Noise session established → link encrypted → begins tree gossip
|
||||
```
|
||||
|
||||
**Inbound (peer connects to us):**
|
||||
|
||||
```text
|
||||
Transport receives data from unknown address
|
||||
Transport receives Noise msg1 from unknown address
|
||||
│
|
||||
▼
|
||||
Receive AuthInit { peer_npub, nonce }
|
||||
Process msg1 → learn peer's identity from encrypted static key
|
||||
│
|
||||
▼
|
||||
Send AuthChallenge { our_npub, our_nonce, signature }
|
||||
Send msg2 (33 bytes): our ephemeral key
|
||||
│
|
||||
▼
|
||||
Receive AuthComplete { signature }
|
||||
│
|
||||
▼
|
||||
Verify signature → peer authenticated → begins tree gossip
|
||||
Noise session established → link encrypted → begins tree gossip
|
||||
```
|
||||
|
||||
### 7.2 Post-Authentication
|
||||
|
||||
After successful peer authentication:
|
||||
After successful Noise handshake:
|
||||
|
||||
1. **TreeAnnounce exchange**: Both peers send their current tree state
|
||||
2. **FilterAnnounce exchange**: Both peers send their bloom filters
|
||||
3. **Peer is Active**: Can now participate in routing and forwarding
|
||||
1. **Link encrypted**: All subsequent messages use AEAD encryption
|
||||
2. **TreeAnnounce exchange**: Both peers send their current tree state
|
||||
3. **FilterAnnounce exchange**: Both peers send their bloom filters
|
||||
4. **Peer is Active**: Can now participate in routing and forwarding
|
||||
|
||||
The first TreeAnnounce from a new peer may trigger parent reselection if that
|
||||
peer offers a better path to root.
|
||||
@@ -746,34 +738,36 @@ peer offers a better path to root.
|
||||
This section tracks items that need reconciliation with existing design docs
|
||||
or earlier sections of this document.
|
||||
|
||||
### 8.1 Completed Updates (Session 40)
|
||||
### 8.1 Completed Updates (Session 47)
|
||||
|
||||
| Location | Status | Notes |
|
||||
|----------|--------|-------|
|
||||
| §3.1 | ✓ Done | Updated to AEAD authentication |
|
||||
| fips-routing.md Part 4 | ✓ Done | Renamed to "Routing Session Establishment", added terminology note |
|
||||
| fips-routing.md SessionSetup/Ack | ✓ Done | Added `handshake_payload` for crypto handshake |
|
||||
| fips-design.md §7 Encryption | ✓ Done | Updated to reference Noise KK instead of NIP-44 |
|
||||
| fips-design.md §6 Messages | ✓ Done | Added SessionSetup, SessionAck, CoordsRequired types |
|
||||
| fips-design.md §1 Peer Auth | ✓ Done | Added terminology note distinguishing peer auth from crypto sessions |
|
||||
| fips-architecture.md Config | ✓ Done | Renamed to "Routing Session", added "Crypto Session" section |
|
||||
| Location | Status | Notes |
|
||||
|----------------------------------|--------|----------------------------------------------------|
|
||||
| fips-design.md §1 Peer Auth | ✓ Done | Replaced custom handshake with Noise IK |
|
||||
| fips-design.md §6 Messages | ✓ Done | Split into LinkMessageType + SessionMessageType |
|
||||
| protocol.rs | ✓ Done | Removed Hello/Challenge/Auth/AuthAck types |
|
||||
| protocol.rs | ✓ Done | Added SessionDatagram for link-layer encapsulation |
|
||||
| This document §6 | ✓ Done | Updated to two-layer Noise IK architecture |
|
||||
| This document §7 | ✓ Done | Updated connection flow for Noise IK |
|
||||
|
||||
### 8.2 Cross-References Added
|
||||
### 8.2 Previous Updates (Session 40)
|
||||
|
||||
All design docs now reference fips-protocol-flow.md in their References sections:
|
||||
|
||||
- fips-design.md
|
||||
- fips-routing.md
|
||||
- fips-architecture.md
|
||||
| Location | Status | Notes |
|
||||
|----------------------------------|--------|-----------------------------------------------------|
|
||||
| §3.1 | ✓ Done | Updated to AEAD authentication |
|
||||
| fips-routing.md Part 4 | ✓ Done | Renamed to "Routing Session Establishment" |
|
||||
| fips-routing.md SessionSetup/Ack | ✓ Done | Added `handshake_payload` for crypto handshake |
|
||||
| fips-design.md §7 Encryption | ✓ Done | Updated to reference Noise instead of NIP-44 |
|
||||
| fips-architecture.md Config | ✓ Done | Renamed to "Routing Session", added "Crypto Session"|
|
||||
|
||||
### 8.3 Design Doc Alignment Summary
|
||||
|
||||
The following decisions from this document have been propagated:
|
||||
|
||||
1. **Session terminology** (§5.4): "Routing Session" vs "Crypto Session" distinction
|
||||
1. **Two-layer architecture**: Link layer (Noise IK peer auth) and session layer
|
||||
(Noise IK end-to-end) operate independently with separate keys
|
||||
2. **Session terminology** (§5.4): "Routing Session" vs "Crypto Session" distinction
|
||||
now consistent across all docs
|
||||
2. **Combined establishment** (§5.5): SessionSetup/SessionAck carry optional
|
||||
`handshake_payload` for Noise KK handshake
|
||||
3. **Noise KK** (§6): fips-design.md encryption section updated, new config
|
||||
parameters added to fips-architecture.md
|
||||
4. **Peer auth vs crypto session**: fips-design.md §1 clarifies the distinction
|
||||
3. **Combined establishment** (§5.5): SessionSetup/SessionAck carry optional
|
||||
`handshake_payload` for session-layer Noise IK handshake
|
||||
4. **Message type split**: LinkMessageType for hop-by-hop, SessionMessageType for
|
||||
end-to-end (carried inside SessionDatagram)
|
||||
|
||||
@@ -228,8 +228,7 @@ loop {
|
||||
```text
|
||||
PeerSlot::Connecting(PeerConnection)
|
||||
│
|
||||
│ AuthInit/AuthResponse exchange
|
||||
│ Noise KK handshake
|
||||
│ Noise IK handshake (2 messages)
|
||||
▼
|
||||
PeerSlot::Active(ActivePeer)
|
||||
│
|
||||
@@ -240,13 +239,13 @@ PeerSlot::Active(ActivePeer)
|
||||
|
||||
**PeerConnection** contains:
|
||||
|
||||
- Noise handshake state (ephemeral keys, handshake hash)
|
||||
- Retry tracking (attempts, last_sent)
|
||||
- Noise IK handshake state (ephemeral keys, handshake hash)
|
||||
- Expected identity (for outbound) or discovered identity (for inbound)
|
||||
- Direction (Inbound vs Outbound)
|
||||
|
||||
**ActivePeer** contains:
|
||||
|
||||
- Session keys (for encrypt/decrypt)
|
||||
- NoiseSession (symmetric keys for encrypt/decrypt)
|
||||
- Tree position (declaration, coordinates)
|
||||
- Bloom filter (what's reachable through this peer)
|
||||
- Statistics (last_seen, link_stats)
|
||||
@@ -342,9 +341,10 @@ struct Node {
|
||||
|
||||
For inbound connections from unknown addresses:
|
||||
|
||||
1. Parse packet → extract sender's identity (in AuthInit)
|
||||
2. Create new PeerConnection
|
||||
3. Add to `peers` and `addr_to_peer`
|
||||
1. Receive Noise IK msg1 → decrypt to extract sender's static key (identity)
|
||||
2. Create new PeerConnection with discovered identity
|
||||
3. Add to `connections` (by LinkId) and `addr_to_link`
|
||||
4. After handshake completes, promote to ActivePeer (indexed by NodeId)
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
+3
-3
@@ -41,9 +41,9 @@ pub use transport::udp::UdpTransport;
|
||||
|
||||
// Re-export protocol types
|
||||
pub use protocol::{
|
||||
Auth, AuthAck, Challenge, CoordsRequired, DataFlags, DataPacket, FilterAnnounce, Hello,
|
||||
LookupRequest, LookupResponse, MessageType, PathBroken, ProtocolError, SessionAck,
|
||||
SessionFlags, SessionSetup, TreeAnnounce,
|
||||
CoordsRequired, DataFlags, DataPacket, FilterAnnounce, LinkMessageType, LookupRequest,
|
||||
LookupResponse, PathBroken, ProtocolError, SessionAck, SessionDatagram, SessionFlags,
|
||||
SessionMessageType, SessionSetup, TreeAnnounce,
|
||||
};
|
||||
|
||||
// Re-export cache types
|
||||
|
||||
+205
-198
@@ -1,15 +1,29 @@
|
||||
//! FIPS Protocol Messages
|
||||
//!
|
||||
//! Wire format message types for FIPS protocol communication, including
|
||||
//! authentication handshake, spanning tree announcements, Bloom filter
|
||||
//! propagation, discovery protocol, and data packets.
|
||||
//! Wire format definitions for FIPS protocol communication across two layers:
|
||||
//!
|
||||
//! ## Link Layer (peer-to-peer, hop-by-hop)
|
||||
//!
|
||||
//! Messages exchanged between directly connected peers over Noise-encrypted
|
||||
//! links. Includes spanning tree gossip, bloom filter propagation, discovery
|
||||
//! protocol, and forwarding of session-layer datagrams.
|
||||
//!
|
||||
//! Link-layer peer authentication uses Noise IK (see `noise.rs`), which
|
||||
//! establishes the encrypted channel before any of these messages are sent.
|
||||
//!
|
||||
//! ## Session Layer (end-to-end, between FIPS addresses)
|
||||
//!
|
||||
//! Messages exchanged between source and destination FIPS nodes, encrypted
|
||||
//! with session keys that intermediate nodes cannot read. Includes session
|
||||
//! establishment, IPv6 datagram encapsulation, and routing errors.
|
||||
//!
|
||||
//! Session-layer datagrams are carried as opaque payloads through the link
|
||||
//! layer, encrypted end-to-end independently of per-hop link encryption.
|
||||
|
||||
use crate::bloom::BloomFilter;
|
||||
use crate::tree::{ParentDeclaration, TreeCoordinate};
|
||||
use crate::{FipsAddress, NodeId};
|
||||
use rand::Rng;
|
||||
use secp256k1::schnorr::Signature;
|
||||
use secp256k1::XOnlyPublicKey;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -19,55 +33,47 @@ pub const PROTOCOL_VERSION: u8 = 1;
|
||||
/// Data packet header size in bytes (excluding payload).
|
||||
pub const DATA_HEADER_SIZE: usize = 36;
|
||||
|
||||
/// Message type identifiers.
|
||||
// ============================================================================
|
||||
// Link Layer Message Types (peer-to-peer, hop-by-hop)
|
||||
// ============================================================================
|
||||
|
||||
/// Link-layer message type identifiers.
|
||||
///
|
||||
/// These messages are exchanged between directly connected peers over
|
||||
/// Noise-encrypted links. Peer authentication happens via Noise IK
|
||||
/// handshake before any of these messages are sent.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum MessageType {
|
||||
// Authentication (0x00-0x0F)
|
||||
Hello = 0x00,
|
||||
Challenge = 0x01,
|
||||
Auth = 0x02,
|
||||
AuthAck = 0x03,
|
||||
|
||||
pub enum LinkMessageType {
|
||||
// Tree protocol (0x10-0x1F)
|
||||
/// Spanning tree state announcement.
|
||||
TreeAnnounce = 0x10,
|
||||
|
||||
// Bloom filter (0x20-0x2F)
|
||||
/// Bloom filter reachability update.
|
||||
FilterAnnounce = 0x20,
|
||||
|
||||
// Discovery (0x30-0x3F)
|
||||
/// Request to discover a node's coordinates.
|
||||
LookupRequest = 0x30,
|
||||
/// Response with target's coordinates.
|
||||
LookupResponse = 0x31,
|
||||
|
||||
// Session (0x40-0x4F)
|
||||
SessionSetup = 0x40,
|
||||
SessionAck = 0x41,
|
||||
|
||||
// Data (0x50-0x5F)
|
||||
DataPacket = 0x50,
|
||||
|
||||
// Errors (0x60-0x6F)
|
||||
CoordsRequired = 0x60,
|
||||
PathBroken = 0x61,
|
||||
// Forwarding (0x40-0x4F)
|
||||
/// Encapsulated session-layer datagram for forwarding.
|
||||
/// Payload is opaque to intermediate nodes (end-to-end encrypted).
|
||||
SessionDatagram = 0x40,
|
||||
}
|
||||
|
||||
impl MessageType {
|
||||
impl LinkMessageType {
|
||||
/// Try to convert from a byte.
|
||||
pub fn from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
0x00 => Some(MessageType::Hello),
|
||||
0x01 => Some(MessageType::Challenge),
|
||||
0x02 => Some(MessageType::Auth),
|
||||
0x03 => Some(MessageType::AuthAck),
|
||||
0x10 => Some(MessageType::TreeAnnounce),
|
||||
0x20 => Some(MessageType::FilterAnnounce),
|
||||
0x30 => Some(MessageType::LookupRequest),
|
||||
0x31 => Some(MessageType::LookupResponse),
|
||||
0x40 => Some(MessageType::SessionSetup),
|
||||
0x41 => Some(MessageType::SessionAck),
|
||||
0x50 => Some(MessageType::DataPacket),
|
||||
0x60 => Some(MessageType::CoordsRequired),
|
||||
0x61 => Some(MessageType::PathBroken),
|
||||
0x10 => Some(LinkMessageType::TreeAnnounce),
|
||||
0x20 => Some(LinkMessageType::FilterAnnounce),
|
||||
0x30 => Some(LinkMessageType::LookupRequest),
|
||||
0x31 => Some(LinkMessageType::LookupResponse),
|
||||
0x40 => Some(LinkMessageType::SessionDatagram),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -78,27 +84,84 @@ impl MessageType {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MessageType {
|
||||
impl fmt::Display for LinkMessageType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
MessageType::Hello => "Hello",
|
||||
MessageType::Challenge => "Challenge",
|
||||
MessageType::Auth => "Auth",
|
||||
MessageType::AuthAck => "AuthAck",
|
||||
MessageType::TreeAnnounce => "TreeAnnounce",
|
||||
MessageType::FilterAnnounce => "FilterAnnounce",
|
||||
MessageType::LookupRequest => "LookupRequest",
|
||||
MessageType::LookupResponse => "LookupResponse",
|
||||
MessageType::SessionSetup => "SessionSetup",
|
||||
MessageType::SessionAck => "SessionAck",
|
||||
MessageType::DataPacket => "DataPacket",
|
||||
MessageType::CoordsRequired => "CoordsRequired",
|
||||
MessageType::PathBroken => "PathBroken",
|
||||
LinkMessageType::TreeAnnounce => "TreeAnnounce",
|
||||
LinkMessageType::FilterAnnounce => "FilterAnnounce",
|
||||
LinkMessageType::LookupRequest => "LookupRequest",
|
||||
LinkMessageType::LookupResponse => "LookupResponse",
|
||||
LinkMessageType::SessionDatagram => "SessionDatagram",
|
||||
};
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Layer Message Types (end-to-end, between FIPS addresses)
|
||||
// ============================================================================
|
||||
|
||||
/// Session-layer message type identifiers.
|
||||
///
|
||||
/// These messages are exchanged end-to-end between FIPS nodes, encrypted
|
||||
/// with session keys that intermediate nodes cannot read. They are carried
|
||||
/// as payloads inside `LinkMessageType::SessionDatagram`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum SessionMessageType {
|
||||
// Session establishment (0x00-0x0F)
|
||||
/// Session setup with coordinates (warms router caches).
|
||||
SessionSetup = 0x00,
|
||||
/// Session acknowledgement.
|
||||
SessionAck = 0x01,
|
||||
|
||||
// Data (0x10-0x1F)
|
||||
/// Encrypted IPv6 datagram payload.
|
||||
DataPacket = 0x10,
|
||||
|
||||
// Errors (0x20-0x2F)
|
||||
/// Router cache miss - needs coordinates.
|
||||
CoordsRequired = 0x20,
|
||||
/// Routing failure (local minimum or unreachable).
|
||||
PathBroken = 0x21,
|
||||
}
|
||||
|
||||
impl SessionMessageType {
|
||||
/// Try to convert from a byte.
|
||||
pub fn from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
0x00 => Some(SessionMessageType::SessionSetup),
|
||||
0x01 => Some(SessionMessageType::SessionAck),
|
||||
0x10 => Some(SessionMessageType::DataPacket),
|
||||
0x20 => Some(SessionMessageType::CoordsRequired),
|
||||
0x21 => Some(SessionMessageType::PathBroken),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to a byte.
|
||||
pub fn to_byte(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SessionMessageType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
SessionMessageType::SessionSetup => "SessionSetup",
|
||||
SessionMessageType::SessionAck => "SessionAck",
|
||||
SessionMessageType::DataPacket => "DataPacket",
|
||||
SessionMessageType::CoordsRequired => "CoordsRequired",
|
||||
SessionMessageType::PathBroken => "PathBroken",
|
||||
};
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy type alias for compatibility during transition
|
||||
#[deprecated(note = "Use LinkMessageType or SessionMessageType instead")]
|
||||
pub type MessageType = LinkMessageType;
|
||||
|
||||
/// Errors related to protocol message handling.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProtocolError {
|
||||
@@ -127,102 +190,9 @@ pub enum ProtocolError {
|
||||
TtlExpired,
|
||||
}
|
||||
|
||||
// ============ Authentication Messages ============
|
||||
|
||||
/// Initial hello message from initiator.
|
||||
///
|
||||
/// The first message in the 4-step authentication handshake.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Hello {
|
||||
/// Initiator's public key.
|
||||
pub pubkey: XOnlyPublicKey,
|
||||
}
|
||||
|
||||
impl Hello {
|
||||
/// Create a new Hello message.
|
||||
pub fn new(pubkey: XOnlyPublicKey) -> Self {
|
||||
Self { pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
/// Challenge from responder, also contains responder's identity.
|
||||
///
|
||||
/// The second message in the authentication handshake.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Challenge {
|
||||
/// Responder's public key.
|
||||
pub pubkey: XOnlyPublicKey,
|
||||
/// Random challenge for initiator to sign.
|
||||
pub challenge: [u8; 32],
|
||||
}
|
||||
|
||||
impl Challenge {
|
||||
/// Create a new Challenge with a specific challenge value.
|
||||
pub fn new(pubkey: XOnlyPublicKey, challenge: [u8; 32]) -> Self {
|
||||
Self { pubkey, challenge }
|
||||
}
|
||||
|
||||
/// Generate a Challenge with a random challenge value.
|
||||
pub fn generate(pubkey: XOnlyPublicKey) -> Self {
|
||||
let mut challenge = [0u8; 32];
|
||||
rand::thread_rng().fill(&mut challenge);
|
||||
Self { pubkey, challenge }
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentication response from initiator.
|
||||
///
|
||||
/// The third message in the authentication handshake. Contains the
|
||||
/// initiator's challenge and their response to the responder's challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Auth {
|
||||
/// Challenge for responder to sign.
|
||||
pub challenge: [u8; 32],
|
||||
/// Initiator's response to responder's challenge.
|
||||
pub response: Signature,
|
||||
/// Timestamp included in signed response (Unix seconds).
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl Auth {
|
||||
/// Create a new Auth message.
|
||||
pub fn new(challenge: [u8; 32], response: Signature, timestamp: u64) -> Self {
|
||||
Self {
|
||||
challenge,
|
||||
response,
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a new Auth with a random challenge.
|
||||
pub fn generate(response: Signature, timestamp: u64) -> Self {
|
||||
let mut challenge = [0u8; 32];
|
||||
rand::thread_rng().fill(&mut challenge);
|
||||
Self {
|
||||
challenge,
|
||||
response,
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Final acknowledgement from responder.
|
||||
///
|
||||
/// The fourth and final message in the authentication handshake.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuthAck {
|
||||
/// Responder's response to initiator's challenge.
|
||||
pub response: Signature,
|
||||
/// Timestamp included in signed response (Unix seconds).
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl AuthAck {
|
||||
/// Create a new AuthAck message.
|
||||
pub fn new(response: Signature, timestamp: u64) -> Self {
|
||||
Self { response, timestamp }
|
||||
}
|
||||
}
|
||||
// ============================================================================
|
||||
// Link Layer Messages
|
||||
// ============================================================================
|
||||
|
||||
// ============ Tree Protocol Messages ============
|
||||
|
||||
@@ -413,7 +383,58 @@ impl LookupResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Session Messages ============
|
||||
// ============ Session Datagram (Link-Layer Encapsulation) ============
|
||||
|
||||
/// Encapsulated session-layer datagram for forwarding.
|
||||
///
|
||||
/// This is a link-layer message that carries an opaque, end-to-end encrypted
|
||||
/// session-layer payload. Intermediate nodes route based on the destination
|
||||
/// address but cannot decrypt the payload.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionDatagram {
|
||||
/// Destination FIPS address (for routing decisions).
|
||||
pub dest_addr: FipsAddress,
|
||||
/// Hop limit (decremented at each hop).
|
||||
pub hop_limit: u8,
|
||||
/// Encrypted session-layer payload (opaque to intermediate nodes).
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SessionDatagram {
|
||||
/// Create a new session datagram.
|
||||
pub fn new(dest_addr: FipsAddress, payload: Vec<u8>) -> Self {
|
||||
Self {
|
||||
dest_addr,
|
||||
hop_limit: 64,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the hop limit.
|
||||
pub fn with_hop_limit(mut self, hop_limit: u8) -> Self {
|
||||
self.hop_limit = hop_limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Decrement hop limit, returning false if exhausted.
|
||||
pub fn decrement_hop_limit(&mut self) -> bool {
|
||||
if self.hop_limit > 0 {
|
||||
self.hop_limit -= 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the datagram can be forwarded.
|
||||
pub fn can_forward(&self) -> bool {
|
||||
self.hop_limit > 0
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Layer Messages (end-to-end, between FIPS addresses)
|
||||
// ============================================================================
|
||||
|
||||
/// Session flags for setup options.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
@@ -708,37 +729,54 @@ mod tests {
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_id(v)).collect()).unwrap()
|
||||
}
|
||||
|
||||
// ===== MessageType Tests =====
|
||||
// ===== LinkMessageType Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_message_type_roundtrip() {
|
||||
fn test_link_message_type_roundtrip() {
|
||||
let types = [
|
||||
MessageType::Hello,
|
||||
MessageType::Challenge,
|
||||
MessageType::Auth,
|
||||
MessageType::AuthAck,
|
||||
MessageType::TreeAnnounce,
|
||||
MessageType::FilterAnnounce,
|
||||
MessageType::LookupRequest,
|
||||
MessageType::LookupResponse,
|
||||
MessageType::SessionSetup,
|
||||
MessageType::SessionAck,
|
||||
MessageType::DataPacket,
|
||||
MessageType::CoordsRequired,
|
||||
MessageType::PathBroken,
|
||||
LinkMessageType::TreeAnnounce,
|
||||
LinkMessageType::FilterAnnounce,
|
||||
LinkMessageType::LookupRequest,
|
||||
LinkMessageType::LookupResponse,
|
||||
LinkMessageType::SessionDatagram,
|
||||
];
|
||||
|
||||
for ty in types {
|
||||
let byte = ty.to_byte();
|
||||
let restored = MessageType::from_byte(byte);
|
||||
let restored = LinkMessageType::from_byte(byte);
|
||||
assert_eq!(restored, Some(ty));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_type_invalid() {
|
||||
assert!(MessageType::from_byte(0xFF).is_none());
|
||||
assert!(MessageType::from_byte(0x99).is_none());
|
||||
fn test_link_message_type_invalid() {
|
||||
assert!(LinkMessageType::from_byte(0xFF).is_none());
|
||||
assert!(LinkMessageType::from_byte(0x00).is_none());
|
||||
}
|
||||
|
||||
// ===== SessionMessageType Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_session_message_type_roundtrip() {
|
||||
let types = [
|
||||
SessionMessageType::SessionSetup,
|
||||
SessionMessageType::SessionAck,
|
||||
SessionMessageType::DataPacket,
|
||||
SessionMessageType::CoordsRequired,
|
||||
SessionMessageType::PathBroken,
|
||||
];
|
||||
|
||||
for ty in types {
|
||||
let byte = ty.to_byte();
|
||||
let restored = SessionMessageType::from_byte(byte);
|
||||
assert_eq!(restored, Some(ty));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_session_message_type_invalid() {
|
||||
assert!(SessionMessageType::from_byte(0xFF).is_none());
|
||||
assert!(SessionMessageType::from_byte(0x99).is_none());
|
||||
}
|
||||
|
||||
// ===== SessionFlags Tests =====
|
||||
@@ -862,21 +900,6 @@ mod tests {
|
||||
assert_eq!(&bytes[8..40], target.as_bytes());
|
||||
}
|
||||
|
||||
// ===== Challenge Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_challenge_generate() {
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
let keypair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
|
||||
let pubkey = keypair.x_only_public_key().0;
|
||||
|
||||
let challenge1 = Challenge::generate(pubkey);
|
||||
let challenge2 = Challenge::generate(pubkey);
|
||||
|
||||
// Challenges should be different (random)
|
||||
assert_ne!(challenge1.challenge, challenge2.challenge);
|
||||
}
|
||||
|
||||
// ===== FilterAnnounce Tests =====
|
||||
|
||||
#[test]
|
||||
@@ -933,22 +956,6 @@ mod tests {
|
||||
assert!(err.last_known_coords.is_some());
|
||||
}
|
||||
|
||||
// ===== Auth Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_auth_generate() {
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
let keypair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
|
||||
let digest = [0u8; 32];
|
||||
let sig = secp.sign_schnorr(&digest, &keypair);
|
||||
|
||||
let auth1 = Auth::generate(sig, 1000);
|
||||
let auth2 = Auth::generate(sig, 1000);
|
||||
|
||||
// Random challenges should differ
|
||||
assert_ne!(auth1.challenge, auth2.challenge);
|
||||
}
|
||||
|
||||
// ===== TreeAnnounce Tests =====
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user