Skip to content

Hybrid post-quantum v2 transport (#184) — integration - #233

Draft
djh58 wants to merge 4 commits into
1.x.xfrom
feature/pq-transport
Draft

djh58 wants to merge 4 commits into
1.x.xfrom
feature/pq-transport

Conversation

@djh58

@djh58 djh58 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Integration branch for #184, hybrid post-quantum v2 transport (ML-KEM-1024 + ECDH), plus the fixes made along the way. Each part lands here as its own reviewed PR, with full CI and agentic review. This PR is where the combined change is reviewed and merged into 1.x.x.

Draft until every part below has merged. Nothing here turns the hybrid transport on by default; that's part 9 (#222), after its release gate.

Parts

Part PR Status
1. I2P post-quantum leasesets (#185) #223 merged
2. Foundations: release tooling, Python ML-KEM, PQ CI, canary (#186) #224 open, bottom of the stack
3. ML-KEM library and runtime backend selection (#190) #225 open, stacked on #224
4. Hybrid handshake with key confirmation (#199) #230 open, stacked on #225
5. Outcomes, close causes, fallback, load shedding (#206) #235 open, stacked on #230
6. Option, startup line, RPC, GUI (#209) #236 open, stacked on #235
7. End-to-end tests (#212) #239 open, stacked on #236 (G3 to add)
8. Flood harness, benchmark, operator docs (#215) not opened yet in progress

Fixes found along the way

PR What
#228 (merged) RPC docs CI checks out the event commit, not the moving merge ref
#229 (merged) Cadence benchmark no longer builds an unbounded chain (OOM)
#231 Required Merge Gate waits for a newer run instead of failing on an earlier one's result
#237 Backport bitcoin#35175: build with Boost 1.91+ (multi_index index lists)

Before marking ready

Target Branch

  • This PR targets a maintainer-requested release branch: 1.x.x.

Part of #184

🤖 Generated with Claude Code

djh58 and others added 3 commits October 6, 2026 11:16
feature/pq-transport collects the #184 hybrid post-quantum transport work
before it goes to 1.x.x. Add it to the branch lists of Full Validation,
Core Checks and the Required Merge Gate, so pull requests into it and
merges to it get the same validation as 1.x.x. Remove the entry when the
branch merges into 1.x.x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pull request build of the development RPC docs checked out
refs/pull/<n>/merge and then required it to be the event's merge commit.
That ref moves whenever the base branch or the pull request's stack
changes, so a run that overlapped such a change checked out a different
merge commit and failed "Validate version source checkout".

Check out the event's merge commit by hash instead, as the release
entries already do, and drop the now unused --development-checkout-ref
plumbing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…explicit rejection (#223)

* net: support bounded interruptible SAM session creation

Proxy::Connect() waits up to -timeout for a connection and does not
watch any interrupt, so a caller that has to finish several dependent
steps by one deadline, and stop promptly on shutdown, cannot use it.

Add a Proxy::Connect(deadline, interrupt) overload for that. It waits
for the connection in slices of at most MAX_CONNECT_POLL_INTERVAL
(100 ms), rechecking the interrupt and the deadline between them, and
creates no socket if either is already over. It does not use
nConnectTimeout; callers cap the deadline as they need. Proxy::Connect()
and ConnectDirectly() behave as before.

The new netbase tests use a socket whose connect stays in progress to
check that the overload connects, gives up at the deadline rather than
at -timeout, notices an interrupt within one slice, never waits longer
than a slice, and that the existing overload still waits once for
-timeout. TCP and Unix socket proxies are both covered.

Part of #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i2p: prefer hybrid leasesets and retry explicit router rejection once

Backport Bitcoin Core #35696: ask the SAM router for the post-quantum
hybrid leaseset encryption type, i2cp.leaseSetEncType=6,4
(MLKEM768-X25519, then ECIES-X25519), instead of 4,0, for persistent
and transient sessions. Java I2P 2.10.0 or newer and i2pd 2.58.0 or
newer built with OpenSSL 3.5 or newer use it; other i2pd versions
ignore it.

Java I2P older than 2.10.0 fails the session instead. qbit-only, as
upstream has no retry: on an explicit rejection of 6,4, a well-formed
SESSION STATUS whose RESULT is present, not OK and not one of
DUPLICATED_ID, DUPLICATED_DEST, INVALID_ID or INVALID_KEY, close that
control socket and retry once on a fresh connection with a fresh HELLO
and session id, keeping the persistent destination, with 4,0. A SAM
reply with RESULT=TIMEOUT is such a rejection and is retried. No reply
(a timeout waiting for one, EOF, a socket error), a malformed reply, a
missing RESULT or a lost success reply is never retried. The original
rejection is logged at debug level with RESULT and a sanitized MESSAGE
capped at 200 characters. m_session_id and m_control_sock are set only
on success.

Only a rejection whose MESSAGE names the encryption type is evidence
that the router lacks 6,4. Java I2P 2.9.0 replies "... Failed to build
tunnels - ...: Unsupported crypto type: 6". The MESSAGE, parsed like
the logged one but before it is sanitized or capped, names the type if
it contains "crypto type", "encryption type", "enc type" or "enctype",
ASCII case-insensitively.

A process-lifetime, mutex-protected record per router (its TCP
endpoint or Unix socket path), shared by all sessions, counts the
sessions in a row in which the router rejected 6,4 naming the
encryption type and accepted 4,0. After two, later sessions through it
start with 4,0 and never retry, until qbit restarts. A session created
with 6,4 starts the count anew, so one transient rejection does not
turn 6,4 off, and a failed retry counts for nothing. The record keeps
at most 64 routers and forgets the least recently updated one. At the
first such session through a router, this is logged once at warning
level, with RESULT shown only if it is one of the SAM protocol's
RESULT values, otherwise "other":

  I2P: the router rejected leaseSetEncType=6,4 (RESULT=I2P_ERROR) and
  accepted 4,0, so this session has no post-quantum leaseset. Java I2P
  older than 2.10.0 does this; upgrade the router to get it.

An info line says when later sessions through the router start using
4,0 until qbit restarts.

A rejection that does not name the encryption type, such as a passing
failure to build tunnels, RESULT=TIMEOUT or CANT_REACH_PEER, is
retried the same way but is no evidence either way: it neither counts
nor starts the count anew, and does not warn. One info line says that
the session uses 4,0 because of that rejection, with RESULT shown as
in the warning and the sanitized MESSAGE, and that the next session
asks for 6,4 again.

Session creation now has one deadline, SAM_CREATE_TIMEOUT (180 s), over
connecting, HELLO, the optional DEST GENERATE and both SESSION CREATE
attempts, through new Hello() and SendRequestAndGetReply() overloads
that take it. Each connect uses Proxy::Connect(deadline, interrupt),
capped by -timeout as before, so shutdown interrupts it promptly. Other
SAM requests behave as before. Session constructors take an optional
creation timeout for tests.

An unexpected reply to SESSION CREATE is now reported with the
sanitized request, "SESSION CREATE ...", instead of the raw one, which
carried the private key.

The new i2p tests use a recording mock SAM router to cover creation
with 6,4, the retry, the two-sessions-in-a-row rule and its reset,
rejections that name the encryption type (with the reply of Java I2P
2.9.0) and ones that do not, which are retried but neutral, the phrase
matching, the bounded record, sessions through a remembered router
including concurrent ones, rejections that are not retried, a failing
retry, transport failures and lost replies, interruption during
reconnect, HELLO and CREATE, an exhausted deadline across steps, during
DEST GENERATE and while connecting, the -timeout cap on connecting, and
that the persistent private key is never logged. Deadline checks use
the timeouts the mock router saw rather than wall-clock margins. Tests
and the i2p fuzz target forget the router record between runs. The
existing tests are unchanged.

Part of #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* doc: document I2P hybrid router requirements and compatibility retry

State in i2p.md which routers use the post-quantum leaseset type that
qbit now asks for, that Java I2P older than 2.10.0 rejects it naming
the encryption type, and how qbit retries once with 4,0 and warns at
the first such session through a router. After two such sessions in a
row, qbit uses 4,0 with that router until qbit restarts; a session that
gets the post-quantum type starts the count anew. Other rejections, a
SAM RESULT=TIMEOUT reply among them, are retried but neither count nor
warn. Tell users to restart qbit after upgrading the router. Note that
the retry is qbit-only, that no reply from the router is not retried,
and the single 3-minute limit on creating a session.

Add a release-note fragment, to be renamed to the pull request number.

Part of #185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
StarvationDistance() cast nanobench's complexityN() to size_t. It is -1
unless -asymptote sets it, so a plain bench_qbit run (including ctest's
bench_sanity_check) built a header chain of about 2^64 blocks and grew
until the host ran out of memory: 27 GiB before it was killed.

Use complexityN() only when -asymptote sets it to at least 1, and
default to 10,000 legacy blocks since the last AuxPoW block otherwise.
Both Cadence benchmarks now run in about 17 MB.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@djh58

djh58 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Current state, merge order and remaining work: #238.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant