Remove Noise protocol references from wiki docs and tests

Delete 8 Noise-specific documentation pages (noise-xx.md,
transport-keys.md, adr-001/003/006, framing-codec.md) and update
~30 remaining wiki pages to reflect QUIC+TLS as the sole transport.
Remove obsolete Noise-based integration tests (auth_service.rs,
mls_group.rs). Code-side Noise removal was done in f334ed3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 08:25:23 +01:00
parent f334ed3d43
commit 9fdb37876a
36 changed files with 125 additions and 2201 deletions

View File

@@ -30,30 +30,6 @@ Because the ephemeral keys exist only for the duration of the handshake,
compromising the server's long-term TLS certificate key (currently self-signed
in quicnprotochat) does not reveal past session keys.
### Noise\_XX
Inside the QUIC stream, the Noise\_XX handshake
(`Noise_XX_25519_ChaChaPoly_BLAKE2s`) provides an additional layer of forward
secrecy. The Noise\_XX pattern uses both ephemeral and static X25519 keys:
```text
→ e Initiator sends ephemeral public key
← e, ee, s, es Responder: ephemeral, DH(e,e), static, DH(e,s)
→ s, se Initiator: static, DH(s,e)
```
The `ee` DH (ephemeral-ephemeral) provides forward secrecy: even if both
parties' static keys (`s`) are later compromised, the ephemeral keys that
contributed to `ee` have already been discarded.
The `es` and `se` DH operations mix in the static keys for authentication, but
the session key depends on the ephemeral contribution. An attacker who
compromises only the static key learns the identity of the parties but cannot
recover the session key without the ephemeral key.
See [X25519 Transport Keys](transport-keys.md) for details on the static
keypair.
## Application Layer Forward Secrecy
### MLS Epoch Ratchet
@@ -125,9 +101,9 @@ operates at two independent layers:
|
v
+------------------------------------------------------+
| TLS 1.3 / Noise_XX |
| Forward secrecy via ephemeral ECDHE / X25519 DH |
| Even if TLS cert or Noise static key is compromised,|
| TLS 1.3 (QUIC) |
| Forward secrecy via ephemeral ECDHE |
| Even if TLS cert is compromised, |
| past transport sessions are protected. |
+------------------------------------------------------+
|
@@ -201,5 +177,4 @@ uniformly to all group members.
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- when keys are created and destroyed
- [Post-Compromise Security](post-compromise-security.md) -- the complementary property (protecting the future)
- [Threat Model](threat-model.md) -- attacker models and what FS protects against
- [X25519 Transport Keys](transport-keys.md) -- Noise ephemeral DH details
- [Ed25519 Identity Keys](identity-keys.md) -- long-term key that FS protects against compromising

View File

@@ -138,26 +138,6 @@ The Ed25519 public key bytes (`public_key_bytes()`) are used as the
KeyPackages indexed by this key, and the Delivery Service routes messages to
queues indexed by the same key.
## Distinction from the X25519 Noise Keypair
It is critical to understand that the Ed25519 identity key and the X25519
transport key are **separate keys on different curves serving different
purposes**:
| Property | Ed25519 Identity Key | X25519 Noise Key |
|----------|---------------------|-----------------|
| Curve | Twisted Edwards (Ed25519) | Montgomery (Curve25519) |
| Operation | Digital signatures | Diffie-Hellman key exchange |
| Purpose | MLS credentials, AS registration | Noise\_XX mutual authentication |
| Lifetime | Permanent (per client) | Per server process or per connection |
| Persistence | Serialized to state file | Not serialized (M6 deferred) |
| Source | `identity.rs` | `keypair.rs` |
Although both curves are related (Curve25519 is birationally equivalent to
Ed25519's curve), the keys are **not interchangeable**. Converting between them
requires explicit birational mapping, which quicnprotochat intentionally avoids
to maintain clean separation of concerns.
## Serialization
`IdentityKeypair` implements `Serialize` and `Deserialize` (serde) by
@@ -193,7 +173,6 @@ is deterministically re-derived on load.
## Related Pages
- [Cryptography Overview](overview.md) -- algorithm inventory
- [X25519 Transport Keys](transport-keys.md) -- the other keypair
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- full lifecycle diagram
- [Post-Compromise Security](post-compromise-security.md) -- how MLS credentials interact with PCS
- [Threat Model](threat-model.md) -- what identity keys protect and do not protect

View File

@@ -13,9 +13,6 @@ Key Type Creation Distribution Use
Ed25519 Identity Once per client AS registration MLS signing, Zeroizing<[u8;32]>
(OsRng) + MLS credential credential binding on struct drop
X25519 Noise Per server process Noise_XX handshake DH key exchange ZeroizeOnDrop
or per client conn (in-band) (transport session) on struct drop
HPKE Init Key Per KeyPackage Uploaded to AS Decrypt Welcome Consumed by openmls;
(openmls backend) in KeyPackage (join_group) deleted from keystore
@@ -93,73 +90,6 @@ The fingerprint (`SHA-256(public_key_bytes)`) is derived from the public key and
is used as a compact identifier in logs. It is not secret and does not require
zeroization.
## X25519 Noise Key
**Source:** `crates/quicnprotochat-core/src/keypair.rs`
The X25519 Noise key provides mutual authentication during the Noise\_XX
handshake. It is shorter-lived than the identity key and is not currently
persisted.
### Lifecycle
```text
+-----------------+
| OsRng |
| (getrandom) |
+--------+--------+
|
generate()
|
+--------v--------+
| NoiseKeypair |
| private: Secret | <-- StaticSecret (ZeroizeOnDrop)
| public: PubKey | <-- 32-byte public key
+--------+--------+
|
+--------------+--------------+
| |
private_bytes() public_bytes()
-> Zeroizing<[u8;32]> -> [u8; 32]
| |
Passed to snow::Builder Exchanged during
local_private_key() Noise_XX handshake
| |
Zeroizing copy drops Stored by peer
immediately after use (not secret)
| |
+-------------+---------------+
|
Noise handshake completes
|
+-------------v--------------+
| Transport session holds |
| derived symmetric keys |
| (managed by snow) |
+-------------+--------------+
|
Connection closes
|
+-------------v--------------+
| NoiseKeypair dropped |
| StaticSecret::drop() |
| overwrites scalar with 0 |
+----------------------------+
```
### Key Properties
- **Generation:** `StaticSecret::random_from_rng(OsRng)` generates a 32-byte
Curve25519 scalar.
- **Dual zeroization:** The `StaticSecret` itself implements `ZeroizeOnDrop`,
and `private_bytes()` returns a `Zeroizing<[u8; 32]>` wrapper.
- **Debug redaction:** The `Debug` impl shows only the first 4 bytes of the
public key and prints `[redacted]` for the private key.
- **No serialization:** `NoiseKeypair` does not implement `Serialize`. Persistence
is deferred to M6.
- **Current lifetime:** Per server process start (server) or per connection
attempt (client). After M6, keys may be persisted with passphrase encryption.
## HPKE Init Keys
**Source:** `crates/quicnprotochat-core/src/keystore.rs` and
@@ -364,8 +294,6 @@ attacks.
|----------|----------------------|------|
| Ed25519 seed | `Zeroizing<[u8; 32]>` | `IdentityKeypair` drop |
| Ed25519 seed (accessor) | Plain `[u8; 32]` copy | Caller responsibility |
| X25519 private | `ZeroizeOnDrop` (x25519-dalek) | `NoiseKeypair` drop |
| X25519 private (accessor) | `Zeroizing<[u8; 32]>` | Accessor drop |
| HPKE init private | Managed by openmls/`DiskKeyStore` | After Welcome processing |
| MLS epoch keys | Managed by openmls internally | After Commit processing |
| Hybrid IKM | `Zeroizing<Vec<u8>>` | After HKDF derivation |
@@ -396,7 +324,6 @@ attacks.
- [Cryptography Overview](overview.md) -- algorithm inventory
- [Ed25519 Identity Keys](identity-keys.md) -- identity key details
- [X25519 Transport Keys](transport-keys.md) -- transport key details
- [Forward Secrecy](forward-secrecy.md) -- how key deletion enables FS
- [Post-Compromise Security](post-compromise-security.md) -- epoch advancement
- [Post-Quantum Readiness](post-quantum-readiness.md) -- hybrid KEM integration

View File

@@ -10,10 +10,8 @@ the security margin it provides.
| Algorithm | Purpose | Crate | Security Level |
|-----------|---------|-------|----------------|
| Ed25519 | Identity signing, MLS credentials | `ed25519-dalek 2` | 128-bit classical |
| X25519 | Noise DH, MLS HPKE key exchange | `x25519-dalek 2` | 128-bit classical |
| ChaCha20-Poly1305 | Noise AEAD | `chacha20poly1305 0.10` | 256-bit key |
| X25519 | MLS HPKE key exchange, Hybrid KEM | `x25519-dalek 2` | 128-bit classical |
| AES-128-GCM | MLS AEAD | `openmls` (via RustCrypto) | 128-bit |
| BLAKE2s | Noise hash function | `snow 0.9` (built-in) | 128-bit |
| SHA-256 | Key fingerprints, HKDF | `sha2 0.10` | 128-bit collision resistance |
| ML-KEM-768 | Post-quantum KEM | `ml-kem 0.2` | NIST Level 3 (~192-bit PQ) |
| HKDF-SHA256 | Key derivation | `hkdf 0.12` | Depends on input entropy |
@@ -26,19 +24,11 @@ the security margin it provides.
### Transport Layer
The transport layer uses two independent encryption substrates:
1. **QUIC/TLS 1.3** (via `quinn 0.11` + `rustls 0.23`): Provides the
outermost encrypted tunnel. The TLS 1.3 handshake negotiates an ephemeral
ECDHE key exchange (X25519 or P-256, depending on the peer) and an AEAD
cipher (AES-128-GCM or ChaCha20-Poly1305). This layer protects connection
metadata from passive network observers.
2. **Noise\_XX** (via `snow 0.9`): Runs inside the QUIC stream. The Noise
pattern `Noise_XX_25519_ChaChaPoly_BLAKE2s` provides mutual authentication
using static X25519 keys, with ChaCha20-Poly1305 as the AEAD and BLAKE2s
as the hash function. See [X25519 Transport Keys](transport-keys.md) for
details on the keypair.
**QUIC/TLS 1.3** (via `quinn 0.11` + `rustls 0.23`): Provides the encrypted
transport tunnel. The TLS 1.3 handshake negotiates an ephemeral ECDHE key
exchange (X25519 or P-256, depending on the peer) and an AEAD cipher
(AES-128-GCM or ChaCha20-Poly1305). This layer protects connection metadata
from passive network observers.
### Application Layer
@@ -81,7 +71,6 @@ and is considered adequate for the foreseeable future.
Layer Classical Security Post-Quantum Security
--------------------------------------------------------------------
QUIC/TLS 1.3 128-bit (ECDHE) None (classical only)
Noise_XX 128-bit (X25519) None (classical only)
MLS (content) 128-bit (AES-128-GCM) None (classical only)
Hybrid KEM (M5+) 128-bit (X25519) ~192-bit (ML-KEM-768)
```
@@ -94,7 +83,6 @@ security properties these algorithms enable.
## Related Pages
- [Ed25519 Identity Keys](identity-keys.md) -- long-term signing keypair
- [X25519 Transport Keys](transport-keys.md) -- Noise handshake keypair
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- creation through destruction
- [Forward Secrecy](forward-secrecy.md) -- past message protection
- [Post-Compromise Security](post-compromise-security.md) -- future message recovery

View File

@@ -163,7 +163,7 @@ hybrid KEM for HPKE init key exchange:
via the MLS group context extensions. Classical-only clients can still
participate in groups that do not require PQ protection.
## The PQ Gap (ADR-006)
## The PQ Gap
There is an important asymmetry in quicnprotochat's post-quantum protection:
@@ -171,7 +171,6 @@ There is an important asymmetry in quicnprotochat's post-quantum protection:
Layer Classical Protection Post-Quantum Protection
---------------------------------------------------------------------
QUIC/TLS 1.3 Yes (ECDHE) No
Noise_XX Yes (X25519) No
MLS content (M5+) Yes (X25519 DHKEM) Yes (hybrid KEM)
```
@@ -182,8 +181,8 @@ MLS content (M5+) Yes (X25519 DHKEM) Yes (hybrid KEM)
the message payload.
- **Transport metadata** (who connects to the server, when, message sizes) is
protected only by classical cryptography. A quantum attacker who recorded the
TLS/Noise handshake transcripts could, in theory, recover the transport session
protected only by classical ECDHE. A quantum attacker who recorded the
TLS handshake transcripts could, in theory, recover the transport session
keys and observe the metadata.
This is the **PQ gap**: content is safe, but metadata is not.
@@ -195,10 +194,6 @@ the IETF and is supported by some TLS libraries, but `rustls` does not yet
support it in a stable release. When `rustls` adds ML-KEM support, quicnprotochat
will adopt it to close the PQ gap at the transport layer.
Similarly, post-quantum Noise patterns are an active research area but are not
yet standardized. The `snow` crate does not currently support post-quantum DH
primitives.
## Harvest-Now, Decrypt-Later Risk
The "harvest-now, decrypt-later" (HNDL) threat model assumes an adversary who:
@@ -213,7 +208,7 @@ In quicnprotochat's case:
ML-KEM-768, which resists quantum attacks. Even if the recorded traffic is
decrypted at the transport layer, the MLS ciphertext inside is still protected.
- **Transport metadata is at risk.** An HNDL attacker who records TLS/Noise
- **Transport metadata is at risk.** An HNDL attacker who records TLS
handshakes today could, with a future quantum computer, recover the transport
session keys and observe:
- Which clients connected to the server and when.

View File

@@ -17,12 +17,12 @@ payloads. Cannot modify traffic.
- Connection metadata: which IP addresses connect to the server and when.
- Message timing and sizes: observable patterns (e.g., message frequency,
payload lengths) that could reveal communication patterns.
- Encrypted payloads: TLS 1.3 ciphertext containing Noise ciphertext containing
MLS ciphertext. Three layers of encryption must be broken to access content.
- Encrypted payloads: TLS 1.3 ciphertext containing MLS ciphertext. Both layers
of encryption must be broken to access content.
**What they cannot learn:**
- Message content: protected by MLS encryption inside Noise inside TLS.
- Message content: protected by MLS encryption inside TLS.
- Group membership details: MLS Commits are encrypted.
- Which specific recipient a message is destined for (from the network
perspective, all messages go to the server).
@@ -51,7 +51,7 @@ state-level adversary).
**What they cannot do (assuming no cert MITM):**
- Decrypt TLS/Noise traffic: both use authenticated ephemeral key exchange.
- Decrypt TLS traffic: TLS 1.3 uses authenticated ephemeral key exchange.
- Forge MLS messages: MLS Commits and application messages are signed by the
sender's Ed25519 identity key. The attacker does not possess any member's
signing key.
@@ -145,8 +145,8 @@ The healing window is the time between the compromise and the next Commit. See
| Message integrity | MLS signing (Ed25519) | Forgery by server or network |
| Group membership changes | MLS Commits (signed, authenticated) | Unauthorized modification |
| Key exchange material | Single-use HPKE init keys | Replay, forward compromise |
| Transport confidentiality | TLS 1.3 + Noise\_XX (double encryption) | Passive eavesdropper |
| Transport integrity | TLS 1.3 AEAD + Noise AEAD | Active network attacker |
| Transport confidentiality | TLS 1.3 (QUIC) | Passive eavesdropper |
| Transport integrity | TLS 1.3 AEAD | Active network attacker |
| Past messages | Forward secrecy (epoch key deletion) | Future client compromise |
| Future messages | Post-compromise security (ratchet tree update) | Past client compromise |
@@ -223,16 +223,16 @@ log of public key bindings.
### Classical-Only Transport
As discussed in [Post-Quantum Readiness](post-quantum-readiness.md), the
transport layer (TLS 1.3, Noise\_XX) uses classical-only cryptography. An
adversary performing harvest-now-decrypt-later (HNDL) could record transport
traffic today and decrypt it with a future quantum computer, revealing transport
transport layer (QUIC/TLS 1.3) uses classical-only ECDHE. An adversary
performing harvest-now-decrypt-later (HNDL) could record transport traffic
today and decrypt it with a future quantum computer, revealing transport
metadata.
**Impact:** Future exposure of transport metadata (not content, assuming
hybrid KEM is active for MLS).
**Mitigation path:** Adopt post-quantum TLS (ML-KEM in TLS 1.3 handshake) when
`rustls` supports it. Investigate post-quantum Noise patterns.
`rustls` supports it.
## Future Mitigations
@@ -313,14 +313,14 @@ communication patterns from traffic analysis.
| Threat | Current Protection | Gap | Planned Fix |
|--------|-------------------|-----|-------------|
| Passive eavesdropper | TLS + Noise + MLS (3 layers) | Traffic analysis | Padding, Tor |
| Active MITM | TLS 1.3 + Noise\_XX | Self-signed certs | Cert pinning, CA |
| Passive eavesdropper | TLS 1.3 + MLS (2 layers) | Traffic analysis | Padding, Tor |
| Active MITM | TLS 1.3 (QUIC) | Self-signed certs | Cert pinning, CA |
| Compromised server | MLS E2E encryption | Metadata visible | Sealed Sender, PIR |
| Compromised client | FS + PCS | Current epoch exposed | Periodic Updates |
| Spam/flooding | None | No auth on DS | AUTHZ\_PLAN |
| Key substitution | None | BasicCredential only | Key Transparency |
| Quantum adversary (content) | Hybrid KEM (M5+) | Pre-M5 messages | Deploy hybrid ASAP |
| Quantum adversary (transport) | None | Classical TLS/Noise | PQ TLS, PQ Noise |
| Quantum adversary (transport) | None | Classical TLS (ECDHE) | PQ TLS |
## Related Pages

View File

@@ -1,191 +0,0 @@
# X25519 Transport Keys
The X25519 transport keypair is used for mutual authentication in the Noise\_XX
handshake. Unlike the [Ed25519 identity key](identity-keys.md), which is a
signing key, the X25519 key performs Diffie-Hellman key exchange to establish
encrypted transport sessions.
**Source:** `crates/quicnprotochat-core/src/keypair.rs`
## Structure
The `NoiseKeypair` struct holds two fields:
```rust
pub struct NoiseKeypair {
/// Private scalar -- zeroized on drop via x25519_dalek's ZeroizeOnDrop impl.
private: StaticSecret,
/// Corresponding public key -- derived from private at construction time.
public: PublicKey,
}
```
| Field | Type | Size | Secret? |
|-------|------|------|---------|
| `private` | `x25519_dalek::StaticSecret` | 32 bytes | Yes -- `ZeroizeOnDrop` |
| `public` | `x25519_dalek::PublicKey` | 32 bytes | No -- safe to log/transmit |
## Key Generation
A fresh keypair is generated from the OS CSPRNG:
```rust
use quicnprotochat_core::keypair::NoiseKeypair;
let keypair = NoiseKeypair::generate();
// private: random 32-byte scalar from OsRng
// public: derived via Curve25519 scalar multiplication
```
Internally:
```rust
pub fn generate() -> Self {
let private = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&private);
Self { private, public }
}
```
The `StaticSecret::random_from_rng` call uses the operating system's CSPRNG
(`getrandom` on Linux, `SecRandomCopyBytes` on macOS) and is suitable for
generating long-lived static identity keys.
## Accessing Key Material
### Private Key Bytes
The `private_bytes()` method returns the raw 32-byte private scalar wrapped in
`Zeroizing<[u8; 32]>`:
```rust
pub fn private_bytes(&self) -> Zeroizing<[u8; 32]> {
Zeroizing::new(self.private.to_bytes())
}
```
The `Zeroizing` wrapper ensures the caller's copy of the key material is
overwritten with zeros when it goes out of scope. The intended usage pattern is
to pass the bytes directly to `snow::Builder` and let the wrapper drop
immediately:
```rust
let private = keypair.private_bytes();
let session = snow::Builder::new(params)
.local_private_key(&private[..])
.build_initiator()?;
// private is zeroized here when it falls out of scope.
```
### Public Key Bytes
The `public_bytes()` method returns a plain `[u8; 32]`:
```rust
pub fn public_bytes(&self) -> [u8; 32] {
self.public.to_bytes()
}
```
The public key is not secret and may be freely cloned, logged, or transmitted
over the wire.
## Zeroization Strategy
The `NoiseKeypair` has two layers of zeroization protection:
1. **`StaticSecret` (inner):** The `x25519_dalek` crate implements
`ZeroizeOnDrop` on `StaticSecret`. When the `NoiseKeypair` struct is dropped,
the private scalar is automatically overwritten with zeros.
2. **`Zeroizing<[u8; 32]>` (accessor):** When callers use `private_bytes()`, the
returned copy is also wrapped in `Zeroizing`, so the caller's copy is zeroed
on drop too.
This dual-layer approach ensures that key material does not linger in memory
whether the key is accessed by value or held in the struct.
## Debug Redaction
The `Debug` implementation intentionally redacts the private key and shows only
the first 4 bytes of the public key as a sanity identifier:
```rust
impl std::fmt::Debug for NoiseKeypair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let pub_bytes = self.public_bytes();
write!(
f,
"NoiseKeypair {{ public: {:02x}{:02x}{:02x}{:02x}..., private: [redacted] }}",
pub_bytes[0], pub_bytes[1], pub_bytes[2], pub_bytes[3],
)
}
}
```
This prevents accidental leakage of secret material through logging or
`println!("{:?}", keypair)`.
## Role in Noise\_XX
The Noise\_XX handshake pattern performs mutual authentication: both initiator
and responder prove possession of their static X25519 keys. The handshake
proceeds in three messages:
```text
→ e (initiator sends ephemeral public key)
← e, ee, s, es (responder sends ephemeral + static, DH results)
→ s, se (initiator sends static, final DH result)
```
After the handshake completes, both parties have:
- Authenticated each other's static X25519 public keys.
- Derived symmetric transport keys from the DH shared secrets.
- Established forward secrecy via the ephemeral keys (which are discarded).
The `NoiseKeypair` provides the `s` (static) key in this pattern. Ephemeral keys
(`e`) are generated internally by `snow` during each handshake.
## Ephemeral vs Static
In the context of Noise\_XX:
- **Ephemeral keys** are generated per-handshake by `snow` and discarded after
key derivation. They provide forward secrecy.
- **Static keys** (the `NoiseKeypair`) are longer-lived and provide identity.
In the current implementation, the server generates a new `NoiseKeypair` per
process start, and the client generates one per connection.
## Persistence
`NoiseKeypair` **intentionally does not implement `Serialize`**. Key persistence
to disk is deferred to milestone M6, which will add:
- Appropriate file permission checks (e.g., `0600` on Unix).
- Optional passphrase-based encryption of the key file.
- A key rotation mechanism.
Until M6, the transport key is ephemeral to the process lifetime. This is
acceptable because the Noise key is not used for MLS group membership -- that
role belongs to the [Ed25519 identity key](identity-keys.md).
## Comparison with Ed25519 Identity Key
| Property | X25519 Noise Key | Ed25519 Identity Key |
|----------|-----------------|---------------------|
| Curve | Montgomery (Curve25519) | Twisted Edwards (Ed25519) |
| Operation | Diffie-Hellman key exchange | Digital signatures |
| Purpose | Noise\_XX mutual authentication | MLS credentials, AS registration |
| Lifetime | Per process / per connection | Permanent (per client) |
| Serialization | Not implemented | Serde (seed bytes) |
| Zeroize | `ZeroizeOnDrop` (x25519-dalek) | `Zeroizing<[u8; 32]>` (manual) |
| Source file | `keypair.rs` | `identity.rs` |
## Related Pages
- [Cryptography Overview](overview.md) -- algorithm inventory
- [Ed25519 Identity Keys](identity-keys.md) -- the other keypair
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- full lifecycle diagram
- [Forward Secrecy](forward-secrecy.md) -- how ephemeral DH provides FS at the transport layer
- [Noise\_XX Handshake](../protocol-layers/noise-xx.md) -- protocol details