Skip to content

network,node: complete strict-PQ activation (schemeGate + pre-dedup handshake + classical-compat)#137

Merged
Darkhorse7stars merged 1 commit into
mainfrom
fix/strict-pq-complete-layers
Jun 7, 2026
Merged

network,node: complete strict-PQ activation (schemeGate + pre-dedup handshake + classical-compat)#137
Darkhorse7stars merged 1 commit into
mainfrom
fix/strict-pq-complete-layers

Conversation

@Darkhorse7stars

Copy link
Copy Markdown
Member

What

main already carries the strict-PQ peer-identity fix (NewLocalIdentityFromStakingKey + adoptVerifiedPQIdentity). This PR adds the three further layers that are each required for strict-PQ consensus and chain creation to actually work end-to-end. All three are proven on a live devnet running the equivalent fix (node v1.10.18-strictpq): a full ML-DSA validator mesh forms and the EVM/DEX/FHE chains are created on the sovereign L1.

This is a clean additive change off current main — it keeps main's identity fix untouched and only fills the gaps.

1. schemeGate nil-gate — network/network.go

Under strict-PQ the TLS layer is transport-only: peer leaf certs are ephemeral ECDSA, which schemeFromCert classifies as classical, so the SchemeGate refuses every peer at the upgrade. When PQHandshakeConfig is active we now pass a nil gate to the TLS upgraders; identity is enforced by the PQ handshake instead. Non-PQ paths keep the real gate. Without this, no strict-PQ peer survives the TLS upgrade.

2. pre-dedup PQ handshake — network/network.go + network/peer/peer.go

The PQ handshake ran inside peer.Start, under peersLock, with initiator/responder roles fixed by dial direction. On simultaneous mutual dials every node acts as a lone initiator (keeps its outbound, drops the peer's inbound at the connecting-dedup) and deadlocks → 0 peers. We now run the handshake per-conn in network.upgrade, before the dedup and without the lock, so each TCP conn completes independently and the dedup keys on the resulting stable ML-DSA NodeID.

RunPQHandshakeConn shares main's binding via a new verifyPQIdentityBinding helper, so the pre-dedup path and the original in-Start path enforce byte-identical identity semantics (re-derive NodeID from the peer's ML-DSA key under ids.Empty, require == claimed). handshake.go is untouched. A PQ handshake failure now tears the conn down from upgrade (before dedup) instead of from Start — same outcome, just earlier and lock-free.

3. classical-compat allow-list — node/node.go

ClassicalCompatRegistry was nil at both factory sites, so strict-PQ refused every classical secp256k1 P-chain credential and the bootstrap control key could not issue CreateNetwork/CreateChainTx. We seed it (strict-PQ only — gated on RequireTypedTxAuth) from the genesis P-chain allocation owners plus ids.ShortEmpty (the mempool's current originator, vms/platformvm/txs/mempool/mempool.go). This unblocks creating the EVM/DEX/FHE chains.

Testing

  • CGO_ENABLED=0 go build ./... — clean.
  • CGO_ENABLED=0 go test ./network/peer/ — pass (handshake round-trips, classical KEM/ECDSA refusal, the 3 AdoptVerifiedPQIdentity binding tests, full SchemeGate suite, both upgrader nil-gate tests).
  • CGO_ENABLED=0 go test ./network/ ./vms/txs/auth/ ./vms/platformvm/genesis/ ./vms/platformvm/txs/mempool/ — all pass.
  • Equivalent layers proven live on devnet (v1.10.18-strictpq): ML-DSA validator mesh + EVM chain serving eth_chainId.

Notes

  • Supersedes fix(network): strict-PQ peer handshake forms the validator mesh (devnet P-chain 0-blocks) #136 (same layers, but on a branch that had drifted 331 commits behind main; this is the clean re-land).
  • Follow-up, tracked separately (not in this PR): config.pemBytesOrFile short-circuits on a blank *-content flag and never falls through to the *-file path — silently degrading a strict-PQ validator to an ECDSA NodeID. Out of scope here (config, not these three consensus/chain-creation layers).

…andshake + classical-compat)

main carries the strict-PQ peer-identity fix (NewLocalIdentityFromStakingKey +
adoptVerifiedPQIdentity) but is missing three further layers that are each
required for strict-PQ consensus and chain creation to actually work. All three
are proven on a live devnet running the equivalent fix (node v1.10.18-strictpq):
a full ML-DSA validator mesh forms and the EVM/DEX/FHE chains are created on the
sovereign L1.

1. schemeGate nil-gate (network/network.go)
   Under strict-PQ the TLS layer is transport-only: peer leaf certs are
   ephemeral ECDSA, which schemeFromCert classifies as classical, so the
   SchemeGate refuses every peer at the upgrade. Pass a nil gate to the TLS
   upgraders when PQHandshakeConfig is active; identity is enforced by the PQ
   handshake instead. Non-PQ paths keep the real gate.

2. pre-dedup PQ handshake (network/network.go + network/peer/peer.go)
   The PQ handshake ran inside peer.Start, under peersLock, with init/responder
   roles fixed by dial direction. On simultaneous mutual dials every node acts
   as a lone initiator (keeps its outbound, drops the peer's inbound at the
   connecting-dedup) and deadlocks -> 0 peers. Run the handshake per-conn in
   network.upgrade, before the dedup and without the lock, so each TCP conn
   completes independently and the dedup keys on the resulting stable ML-DSA
   NodeID. RunPQHandshakeConn shares main's binding via a new
   verifyPQIdentityBinding helper, so the pre-dedup and in-Start paths enforce
   byte-identical identity semantics.

3. classical-compat allow-list (node/node.go)
   The ClassicalCompatRegistry was nil, so strict-PQ refused every classical
   secp256k1 P-chain credential and the bootstrap control key could not issue
   CreateNetwork/CreateChainTx. Seed it (strict-PQ only) from the genesis
   P-chain allocation owners plus ids.ShortEmpty (the mempool's current
   originator). This unblocks creating the EVM/DEX/FHE chains.

Builds clean with CGO_ENABLED=0; network/peer, network, vms/txs/auth and
platformvm genesis/mempool tests pass. main's identity fix is unchanged.

Supersedes #136 (which carried these layers on a branch that had diverged 331
commits behind main). Follow-up, tracked separately: config.pemBytesOrFile
short-circuits on a blank *-content flag and silently degrades a strict-PQ
validator to an ECDSA NodeID.
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.

2 participants