Skip to content

[Post-Quantum] TEC-01: libp2p Noise handshake uses quantum-vulnerable X25519 key exchange #411

Description

@0g-peterzhb

Source: Tectonic Quantum Readiness Audit v1.0 — Finding TEC-01
Tracking: this issue (#411)
Verified at: commit 6ad0e54 on 2026-06-11
Status: upstream-blocked (no PQ/hybrid Noise in rust-libp2p yet) — config seam can be scaffolded now; proportionate severity Low–Medium, below the audit's Medium/High.


Vulnerable code

The libp2p transport authenticates every node-to-node connection with a Noise XX handshake whose static Diffie-Hellman keys are X25519:

node/network/src/service.rs:513-521

/// Generate authenticated XX Noise config from identity keys
fn generate_noise_config(
    identity_keypair: &Keypair,
) -> noise::NoiseAuthenticated<noise::XX, noise::X25519Spec, ()> {
    let static_dh_keys = noise::Keypair::<noise::X25519Spec>::new()
        .into_authentic(identity_keypair)
        .expect("signing can fail only once during starting a node");
    noise::NoiseConfig::xx(static_dh_keys).into_authenticated()
}

This config is applied as the authentication layer of the transport upgrade in build_transport:

node/network/src/service.rs:428-432

        transport
            .upgrade(core::upgrade::Version::V1)
            .authenticate(generate_noise_config(&local_private_key))
            .multiplex(core::upgrade::SelectUpgrade::new(
                yamux_config,

The node's long-term identity keypair is secp256k1 (constrained by discv5), and into_authentic signs the ephemeral X25519 static key with it — so the handshake's peer authentication is rooted in secp256k1:

node/network/src/service.rs:469-493

/// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5.
// ...
    let local_private_key = Keypair::generate_secp256k1();

Dependency correction. The audit cited git commit 5e3519fb66b92c7f7c0dc744ab360fd8b669fe54 as the source of libp2p. That is wrong — this repo does not vendor libp2p from git. It pins the published crate from crates.io:

node/network/Cargo.toml:46-49

[dependencies.libp2p]
version = "0.45.1"
default-features = false
features = ["websocket", "identify", "mplex", "yamux", "noise", "gossipsub", "dns-tokio", "tcp-tokio", "plaintext", "secp256k1"]

Cargo.lock resolves this to libp2p 0.45.1 (checksum 4172…9029) and libp2p-noise 0.36.0 (checksum cf2c…15ad), both source = "registry+https://github.com/rust-lang/crates.io-index". The handshake code above is in the crates.io release, not a forked git revision.

The vulnerability

Primitive: X25519 elliptic-curve Diffie-Hellman (Curve25519, a 256-bit prime-order group) used for Noise session-key agreement, plus secp256k1 ECDSA used to authenticate the static DH key (into_authentic).

Why quantum-vulnerable: Both are discrete-log problems over elliptic-curve groups. A cryptographically relevant quantum computer (CRQC) running Shor's algorithm solves ECDLP in polynomial time, recovering the X25519 private scalar and the secp256k1 identity key. This is a full break, not a security-margin reduction; the symmetric/AEAD half of Noise (ChaCha20-Poly1305) only faces Grover, which halves the effective key strength (256→128-bit security) and remains comfortably safe — so the AEAD is not the concern here. The concern is the asymmetric handshake.

What a CRQC attacker could actually do here:

  • Harvest-Now-Decrypt-Later (HNDL) on session confidentiality: record a Noise session today, later recover the X25519 ephemeral/static keys with Shor, derive the session keys, and decrypt the captured traffic.
  • Live impersonation / MITM: with the secp256k1 identity key broken, forge a peer's authenticated handshake. This is a live-attack capability (requires a CRQC at attack time), not HNDL.

Honest impact assessment. The audit rated this Medium severity / High urgency on the basis of confidentiality exposure. That is overstated for this channel, for a concrete reason: the traffic carried over these Noise sessions is public-by-design storage-network data, so there is little secret confidentiality to harvest.

  • Gossipsub payloads are file/chunk announcementsNewFile, FindFile, AnnounceFile, AnnounceChunks (node/network/src/types/pubsub.rs:213-228) — i.e. public availability metadata, not user secrets.
  • RPC carries chunk data that is already publicly retrievable from the storage network by design.

There is no private user content whose long-term confidentiality is meaningfully degraded by decrypting these sessions years later, so the HNDL confidentiality blast radius is low. The genuinely quantum-relevant weakness is peer authentication: an attacker with a CRQC could impersonate node identities or MITM connections. But that is a live capability gated on CRQC existence (no harvest advantage), and the protocol already disseminates the same data over a public gossip mesh with application-layer signatures on announcements.

Proportionate rating: Low–Medium severity, Low urgency. This is a real long-horizon item to track, but it is not a confidentiality emergency and the audit's High urgency is not justified for a public-data transport.

Constraint

This cannot be fixed unilaterally in this repository today. The Noise handshake and its supported DH suites are entirely defined by the libp2p-noise crate (pinned 0.36.0 via libp2p 0.45.1 from crates.io). libp2p-noise 0.36.0 offers only the classical X25519/X25519Spec suite — there is no ML-KEM, hybrid, or pqXX variant to switch to. We consume the published crate, not a vendored/modifiable copy, so we cannot add a PQ KEM ourselves without forking and maintaining the crypto crate. Additionally, the handshake must remain interoperable with every other node on the network, so any change to the suite is effectively a network-wide upgrade, not a local toggle that one node can flip.

Potential solution

Blocked on upstream — track the dependency.

  • Migration trigger to watch: hybrid post-quantum Noise (e.g. X25519 + ML-KEM-768, a pqXX-style pattern) landing in rust-libp2p. Tracking issue: libp2p/rust-libp2p#6236 ("Post-quantum / hybrid Noise handshake"). When a release ships hybrid Noise:

    1. Bump libp2p (and transitively libp2p-noise) in node/network/Cargo.toml — note this is a large major-version jump from 0.45.1, so expect transport/swarm API churn beyond just Noise.
    2. Change the return type and body of generate_noise_config (node/network/src/service.rs:513-521) to construct the hybrid/pqXX config instead of NoiseConfig::xx over X25519Spec.
    3. Benchmark: the hybrid initiator is expected to be roughly ~2x the XX handshake cost (extra KEM encapsulation + larger handshake messages); validate connect latency and CPU under churn before enabling network-wide.
  • Scaffolding that CAN be done now (low cost): introduce a config seam so the suite selection is not hard-coded. Add a network config field (e.g. noise_handshake: Classic | Hybrid) and have build_transport choose which generate_noise_config* to call. With current libp2p-noise 0.36.0 only the Classic arm compiles, but this isolates the swap point and lets a future hybrid build flip behind a flag for staged rollout/testing without touching call sites. This is the only meaningful scaffolding available; the actual PQ primitive cannot be added until upstream ships it.

  • Not recommended: forking libp2p-noise to graft in ML-KEM ourselves. Maintaining custom transport crypto for a public-data channel is disproportionate to the Low–Medium risk and creates an interop fork.

Checklist

  • Confirm proportionate severity downgrade (Low–Medium / Low urgency) with the security team, given the public-by-design nature of the traffic.
  • Correct the audit's dependency provenance: libp2p is crates.io 0.45.1 / libp2p-noise 0.36.0, not git rev 5e3519fb….
  • Subscribe to / watch libp2p/rust-libp2p#6236 as the migration trigger.
  • (Optional, now) Add a noise_handshake config seam in network config + build_transport so the suite is selectable rather than hard-coded.
  • (On upstream release) Bump libp2p, switch generate_noise_config to the hybrid variant, and benchmark handshake cost (~2x) before a coordinated network upgrade.
  • Track the secp256k1 identity-key migration separately — peer authentication is the genuinely quantum-relevant exposure and is shared with discv5; it cannot move to PQ until both discv5 and the identity scheme support it.

Locations verified against source at 6ad0e54; drafted by Claude Code from the third-party Tectonic PQ audit.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    post-quantumPost-quantum cryptography readiness (from Tectonic audit)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions