phase-0: noq/iroh investigation — quinn→noq shift recorded

- noq 1.2.0 published (iroh's extracted quinn fork); iroh 1.1.0 is
  built on noq 1.2.0 and re-exports noq as its public API
- TLS seam verified API-compatible with quinn 0.11: QuicServerConfig/
  QuicClientConfig try_from + NoInitialCipherSuite — port is mechanical
- one rustls 0.23 tree across all QUIC paths (both pin 0.23.33);
  ADR-084 posture strengthens (no ring/aws-lc provider split possible)
- iroh key surface unchanged (32-byte Ed25519 SecretKey) —
  key-not-config exception intact
- OQ-TLS-08 opened: quinn feature → noq feature rename + honest MSRV
  floor (noq 1.88, iroh 1.91); OQ-TLS-04/07 updated; survey + checklist
  extended

Verified: cargo fmt --check, cargo test, cargo doc --no-deps
This commit is contained in:
2026-09-09 16:45:41 +00:00
parent a570bee0fe
commit 6bd19994f8
+156 -13
View File
@@ -15,8 +15,17 @@ input to Phase 1 (Architecture), where the Architect will produce
Drafted 2026-09-09. The crate is the TLS layer of the alk* stack: shared TLS
setup types — server and client `rustls` configs, cert resolvers, verifiers,
and ACME state-machine wiring — transport-agnostic and shareable across
transports (quinn, `tokio-rustls` TCP+TLS, and anything else that consumes a
`rustls` config).
transports (noq QUIC below, `tokio-rustls` TCP+TLS, and anything else
that consumes a `rustls` config).
The 2026-09-09 same-day revision adds the noq investigation
(§Prior art: noq and the quinn→noq shift): the alknet rewrite will move
QUIC from quinn to noq (iroh's extracted internal quinn fork, published
as `noq 1.2.0`, with iroh 1.1.0 built on it), which adds a `for_noq()`
accessor question (OQ-TLS-08) and an MSRV-floor decision. The
transport picture for the rewrite is three paths — TCP+TLS, QUIC+TLS,
and iroh (relay-assisted p2p QUIC) — sharing one rustls 0.23 +
aws-lc-rs stack.
Unlike alktunnels (which was entirely new protocol code), this crate is an
**extraction** of working, battle-tested TLS code from alknet. The prior art
@@ -102,7 +111,10 @@ pointers; read the referenced ADRs for the full rationale.
`for_quinn()` / `into_rustls_config()`; this crate does not dial. The
iroh transport is the key-not-config exception on both sides — iroh
has its own TLS and takes an `iroh::SecretKey`; there is no
`for_iroh()` here.
`for_iroh()` here. **Updated 2026-09-09:** the noq investigation
(§Prior art: noq and the quinn→noq shift) adds a same-shaped
`for_noq()` accessor next to `for_quinn()` and confirms the iroh
exception still holds on iroh 1.1/noq 1.2.
- **Fingerprint normalization (alknet ADR-030 §6).** `ed25519:<hex>` for
RFC 7250 raw keys, `SHA256:<hex>` for cert DER — identical across
quinn, iroh, and TCP+TLS paths, so a `PeerEntry.fingerprints` entry
@@ -291,10 +303,12 @@ The extracted crate pins: `rustls 0.23` (aws_lc_rs feature), `quinn
0.11`, `tokio-rustls 0.26`, `rustls-acme 0.12` (aws-lc-rs feature),
`rustls-native-certs 0.8`, `webpki-roots 0.26`, `rcgen 0.13`,
`ed25519-dalek 2`, `thiserror 2`, `tokio 1`. As of 2026-09 the rustls
0.23 line is still current (0.23.43, July 2026) — no forced major bump.
Phase 1 should re-verify at packaging time and record any bump as a
line in the dependency ADR. Dependency shape per the spec: `rustls`
always present (the core library); `quinn`/`tcp`/`acme` feature-gated;
0.23 line is still current (0.23.44 stable; iroh and noq pin
`rustls 0.23.33` — same 0.23 major, so one unified 0.23 tree across
quinn/noq/iroh paths is achievable). Phase 1 should re-verify at
packaging time and record any bump as a line in the dependency ADR.
Dependency shape per the spec: `rustls` always present (the core
library); `quinn`/`tcp`/`acme` feature-gated;
`rustls-native-certs` + `webpki-roots` always present (the CA path is
needed by any client dialing public X.509 endpoints regardless of
transport); `futures` acme-gated (the event loop's `StreamExt`).
@@ -315,6 +329,86 @@ with the pin — if Phase 1 later decides `rust-version` should track
reality (e.g. 1.88+), that is a one-line change plus unpinning. Either
way, the decision is recorded here rather than inherited silently.
**MSRV pressure from the noq/iroh direction.** noq 1.2 declares
rust-version 1.88 and iroh 1.1 declares 1.91 (both edition 2024). The
quinn 0.11 path today keeps the tree at 1.85; the noq path raises the
crate's de facto floor to 1.88+ for consumers that enable the quic
feature. Since quic/iroh consumers (the rewrite) sit on iroh 1.91
anyway, this argues for Phase 1 setting `rust-version = 1.88`
crate-wide (matching noq's floor, below iroh's) and unpinning `time`
see OQ-TLS-08. The alknet-tls extraction source (quinn-only, no iroh
dep) can stay at its own floor; the floors only unify at the
assembly layer.
## Prior art: noq and the quinn→noq shift (2026-09-09)
The alknet rewrite will move QUIC from `quinn` to `noq` — iroh's
extracted internal quinn fork, now a standalone published project.
Facts verified against `/workspace/noq` and `/workspace/iroh` (both
clean clones of upstream):
- **noq is published and current**: crates.io `noq 1.2.0` (Feb 2026
created, Aug 2026 updated, ~1.6M downloads; `noq-proto`/`noq-udp`
siblings). Workspace layout mirrors quinn's (`noq` async facade +
sans-io `noq-proto`). rust-version **1.88**, edition 2024.
- **iroh 1.1.0 (crates.io, Aug 2026) is built on noq 1.2.0** and
re-exports `noq::*`, `noq_proto::*`, `noq_udp::*` as part of its
public API. The QUIC + iroh transports in the rewrite will share one
QUIC implementation by construction.
- **The TLS seam is API-compatible with quinn 0.11** — this is the
load-bearing fact for this crate:
`noq_proto::crypto::rustls::QuicServerConfig::try_from(
rustls::ServerConfig)` / `QuicClientConfig::try_from(
rustls::ClientConfig)`, failing with
`noq_proto::crypto::rustls::NoInitialCipherSuite` — the same
constructor shape and the same failure type ADR-088's `QuinnWrap`
variant wraps. Porting `for_quinn()``for_noq()` is mechanical
(type renames in one module; no config-construction change).
- **One rustls, one provider, both QUIC paths.** iroh 1.1 depends on
`rustls 0.23.33` (default-features off) and noq 1.2 on
`rustls ^0.23.33` — the same rustls 0.23 line this crate pins. The
`tls-aws-lc-rs` / `tls-ring` feature split maps onto noq's
`aws-lc-rs` / `ring` features; our aws-lc-rs-only posture (ADR-084)
composes cleanly (`noq` feature `rustls-aws-lc-rs`).
- **The iroh key surface is unchanged for us.**
`iroh_base::SecretKey` is still 32 raw Ed25519 bytes
(`from_bytes`/`to_bytes`), the key-not-config exception still holds
(no `for_iroh()`, no rustls config handed to iroh), and the
fingerprint normalization story (`ed25519:<hex>`) is unaffected.
Implications for this crate:
- **The `quinn` feature becomes (or is joined by) a `noq` feature.**
The transport-gate story stays the same — feature-gated wrapping —
but the accessor name and dep change. Phase 1 decides: rename
`for_quinn``for_noq` outright (clean, pre-consumer window) or
carry both features during a transition (alknet's rewrite is the
only consumer; there is nothing to transition with — leaning one
accessor, see OQ-TLS-08).
- **`QuinnWrap`'s error source is the same shape under a new name.**
ADR-088's variant wraps `NoInitialCipherSuite`; under noq it wraps
`noq_proto::crypto::rustls::NoInitialCipherSuite` — same type name,
different crate path. The ADR-088 shape survives the rename.
- **Consolidation win for ADR-084.** With quinn and iroh on one QUIC
stack (noq) and one rustls version (0.23), the "one provider, all
paths" invariant gets *stronger* — there is no longer a risk of
quinn's ring-default and iroh's aws-lc-rs-default diverging inside
one binary.
- **tokio-rustls (the `tcp` feature) is unaffected** — noq does not
touch the TCP+TLS path.
## Prior art: the three-transport picture
The transport story the rewrite assembles: **TCP+TLS** (tokio-rustls,
this crate's `tcp` feature), **QUIC+TLS** (noq, this crate's quic
accessor), and **iroh** (relay-assisted p2p QUIC — iroh's own TLS
inside, fed the Ed25519 key). A hub/endpoint may have public
ips/ports or not; iroh is the "or not" half. For this crate the
consequences are: two `TlsServerConfig`s per endpoint-type split
(ADR-086 §3) unchanged; the quic accessor feeds either a bare noq
endpoint or (via iroh's own builder) nothing at all; and the identity
type must keep `iroh_base::SecretKey`-shaped byte access (OQ-TLS-07).
## Open Questions
Numbered OQ-TLS-01.. so they can be promoted into
@@ -386,6 +480,7 @@ endpoint and a TCP+TLS acceptor directly — the ADR-082 story's most
literal form. `self` forces callers through `rustls_config()` for the
second transport. Note `TlsClientConfig::for_quinn(self)` is natural
(one dial = one config build), so the two accessors need not agree.
This question carries over unchanged to `for_noq()` (OQ-TLS-08).
**Status:** open, small — lean `&self` on the server accessor to match
the spec sketch, `self` on the client (consumed by `into_rustls_config`
@@ -423,14 +518,50 @@ revisit with the rewrite's graceful-shutdown design.
The extracted client's iroh dial only reuses `TlsError` for error
shaping; the server-side iroh path reads `Ed25519SecretKey` directly.
The rewrite may re-shape how iroh consumes the identity (especially if
OQ-TLS-01 moves the types). No code needed here beyond keeping
OQ-TLS-01 moves the types). Verified 2026-09-09 against iroh 1.1:
`iroh_base::SecretKey` is still 32 raw Ed25519 bytes
(`from_bytes`/`to_bytes`), so the requirement is unchanged — keep
`Ed25519SecretKey` (or its successor) constructible from raw bytes and
`Clone` — but the rewrite's iroh dial design should be checked against
whatever OQ-TLS-01 decides.
`Clone`. No code needed here beyond that; the rewrite's iroh dial
design should be checked against whatever OQ-TLS-01 decides.
**Status:** open, deferred-shaped — blocked on the rewrite's dial/iroh
design landing; track so the identity type keeps iroh's needs
(`as_bytes()`-style access) in view.
(`to_bytes()`-style access) in view.
### OQ-TLS-08: `quinn` feature vs `noq` feature — and the MSRV floor
The rewrite moves QUIC to noq 1.2 (iroh's extracted fork; §Prior art).
The extracted `quinn` feature + `for_quinn()` accessors face a decision:
- **A: rename outright.** Drop `quinn` before any consumer exists —
feature `noq = ["dep:noq"]`, accessors `for_noq()`, `TlsError`
variant wrapping `noq_proto::crypto::rustls::NoInitialCipherSuite`.
Clean; the pre-consumer window (this crate, pre-rewrite) is exactly
when renames are free. No published alk* consumer uses `for_quinn`.
- **B: both features during a transition.** `quinn` and `noq` side by
side. There is no third consumer to transition with — alknet's
rewrite is the only one — so this doubles the accessor surface for
nothing.
Leaning A. Sub-decision: with the noq feature as the QUIC path, the
crate's de facto MSRV floor rises to noq's 1.88 (iroh consumers sit at
1.91 anyway). Phase 1 should set `rust-version = 1.88` crate-wide and
unpin `time` (the 1.85 pin exists only to keep the aspirational 1.85
claim — with an honest 1.88 floor, default resolution is fine and one
less pin to maintain). The `tcp` feature (tokio-rustls) has no 1.88
pressure of its own; a unified 1.88 crate floor is the simple shape.
Also record in the Phase 1 ADR: noq 1.2's `rustls-aws-lc-rs` feature is
the wiring for ADR-084's provider posture on the QUIC path, and iroh
1.1's `tls-aws-lc-rs` feature is the matching iroh-side wiring — both
feature-select the *same* rustls 0.23 + aws-lc-rs stack this crate
already pins, so no new provider decision is needed.
**Status:** open — needs a Phase 1 ADR (feature-gate + accessor naming
is one-way-door API surface). Lean A with the 1.88 floor; final call
belongs to the Phase 1 ADR alongside OQ-TLS-04's accessor-shape
decision.
## Survey / prior-art list
@@ -449,6 +580,12 @@ All internal; this crate has no external research debt:
key-not-config exception).
- `crates/alknet-endpoint/src/accept/tcp_tls.rs` — the server-side
TCP+TLS accept loop (this crate provides the acceptor, not the loop).
- `/workspace/noq` — the noq clone (upstream n0-computer/noq, 1.2.0):
`noq-proto/src/crypto/rustls.rs` for the `QuicServerConfig`/
`QuicClientConfig` seam and `NoInitialCipherSuite` (§Prior art: noq).
- `/workspace/iroh` — the iroh clone (upstream, 1.1.0): `Cargo.toml`
noq/rustls pinning and the `tls-aws-lc-rs` feature; `iroh-base/src/
key.rs` for the SecretKey byte surface (§Prior art).
- alknet ADRs: 082 (extraction), 083 (endpoint takes no TLS config),
084 (aws-lc-rs), 027 (identity model, `acme-tls/1`), 030 §6
(fingerprint normalization), 086 §3 (split ALPN lists), 087
@@ -469,8 +606,14 @@ All internal; this crate has no external research debt:
API surface freeze before consumers, no alknet imports,
caller-owned ALPNs, iroh key-not-config
- [x] Version/dependency posture recorded (§Version and dependency
posture) — rustls 0.23 line current; feature-gate shape per spec
- [x] Open questions enumerated (OQ-TLS-01..07) — all small; two are
posture) — rustls 0.23 line current; feature-gate shape per spec;
MSRV verified under the real 1.85 toolchain (`time` pin) with the
honest-floor decision deferred to OQ-TLS-08
- [x] noq/iroh investigation (2026-09-09) — noq 1.2 published, iroh 1.1
built on it; TLS seam API-compatible with quinn 0.11 (mechanical
port); one rustls 0.23 tree across all QUIC paths; key-not-config
exception intact; OQ-TLS-08 opened (feature rename + MSRV floor)
- [x] Open questions enumerated (OQ-TLS-01..08) — all small; two are
deferred-shaped (OQ-TLS-01 finalize, OQ-TLS-07); none require a
POC (the code exists and runs; there is nothing to de-risk)
- [ ] Open questions promoted to Phase 1