For the complete documentation index, see llms.txt. This page is also available as Markdown.

90360 bc medium unauthenticated 28 byte noise negotiation forces ml kem and ml dsa work before peer authentication

Submitted on Aug 24th 2026 at 17:06:04 UTC by @crypt0n1337 for Audit Comp | Quantus

  • Report ID: #90360

  • Report Type: Blockchain/DLT

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-quantus-chain

  • Impacts:

    • Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours

Description

Severity and impact

Medium - M9: increasing network processing node resource consumption by at least 30% without brute force actions.

An unauthenticated client can select the /noise protocol and close before sending pqXX message 1. The Quantus listener performs expensive post-quantum cryptography before it reads that message or authenticates the peer.

On the exact frozen full-node binary, a local E2E test at only 5 connections per second increased victim CPU by 187.87% over an equal-rate TCP control. After subtracting TCP connection cost, the Quantus-specific excess was 316.02% of the preceding quiet CPU baseline. This is above the 30% Medium threshold.

The attacker sent about 140 bytes per second, performed no post-quantum cryptography, and maintained approximately 0.5 concurrent sockets. This is deterministic protocol use, not key guessing, identity enumeration, Sybil activity, or high-volume brute force.

Affected target

  • Frozen commit: 3b243b870a5442a6e5f443056f67bbba87d472f5

  • Reported node version: 0.8.0-exodus-3b243b870a5

  • Exact locally built node SHA-256: 509C2FF68AC6D637B7305258AF0B1F470BB591BC49CDF6863E8A9BC2EB4BCBC0

  • Affected component: client/litep2p

No public node, mainnet node, or public testnet node was tested. All traffic stayed on 127.0.0.1.

Root cause

After multistream selects /noise, the TCP listener enters handshake():

1

Construct NoiseContext

client/litep2p/src/crypto/noise/mod.rs:772 constructs NoiseContext.

2

Generate an ML-KEM-768 keypair

NoiseContext::new generates a fresh ML-KEM-768 keypair.

3

Derive and sign with the ML-DSA-87 identity key

It calls the ML-DSA-87 identity key sign() method, which derives the keypair and signs the KEM public key.

4

Derive the ML-DSA-87 keypair again

It then calls public(), which derives the ML-DSA-87 keypair again.

5

Wait for pqXX message 1

Only afterward, at noise/mod.rs:811, does the listener wait for pqXX message 1.

Therefore, the remote peer can force one ML-KEM-768 key generation, two ML-DSA-87 key derivations, and one ML-DSA signature without providing a peer identity or any pqXX payload.

The exact unauthenticated input is 28 application bytes:

The client does not need to read the response. It can close the socket after 100 ms.

Why this is not the public litep2p pending-connection issue

Upstream litep2p issue #282 describes accumulation of pending sockets, file-descriptor exhaustion, and listener fairness. This report does not claim those effects.

The final E2E closes every socket after 100 ms, holds approximately 0.5 sockets on average, and shows stable file descriptors and threads. The measured impact remains after subtracting an equal-rate and equal-lifetime TCP control. The additional cost also matches the direct Quantus PQ context differential of approximately 1.47 ms per connection.

The distinct root is the Quantus modification introduced with Quantus PR #574, which replaced classical handshake cryptography with ML-KEM and ML-DSA but performs that work before pqXX message 1. The public PR security review discusses identity binding and key handling, but it does not disclose this remote CPU amplification. I found no public issue, PR, or audit finding that discloses the exact root reported here.

Security impact

Any unauthenticated remote host that can reach the P2P port can impose repeated post-quantum key-generation and signing work at negligible bandwidth and without performing equivalent cryptography. The measured relative CPU increase exceeds the program's 30% Medium M9 threshold even after generic TCP cost is removed.

The local PoC uses a short matched quiet window rather than a literal preceding 24-hour production trace because the competition prohibits testing project nodes. This comparator limitation is disclosed. It does not alter the deterministic source-level cost or the repeated exact-frozen E2E differential.

Move expensive post-quantum context creation behind a cheap pre-authentication admission step. Possible controls include:

  • a per-IP and global rate limit for pre-authentication handshakes;

  • a bounded pending-handshake CPU budget;

  • a stateless reachability cookie before ML-KEM key generation and ML-DSA signing;

  • metrics for pending handshakes and pre-authentication CPU rejection.

Do not reuse long-lived KEM session material without a separate cryptographic design review.

Proof of Concept

E2E proof of concept

Save the inline patch at the end of this section as poc-submit.patch.

  • SHA-256: 63BF6E9F13FE5E3B8E0AEE39BCC3CF4F8D583438008F07DE6587F0DC41405BC2

  • Scope of patch: one test-only change in client/litep2p/src/lib.rs

  • git apply --check --whitespace=error-all: PASS

  • Clean-worktree release build: PASS

1

Apply and build the PoC

The clean E2E rebuild completed successfully and produced:

2

Start an exact frozen local node

Build and run the frozen node without benchmark features:

The reproduced node remained at block 0 with no peers. Its P2P port was exposed only on loopback.

3

Run the matched TCP negative control

Fresh E2E output:

4

Run the /noise positive control

Fresh E2E output:

5

Compare the results

The same node, rate, duration, socket lifetime, and number of successful connections were used in both cases.

The PoC sends 8,428 bytes in approximately 60 seconds, or about 140 bytes per second. At 5 connections per second with a 100 ms socket lifetime, it holds about 0.5 sockets on average. Threads and file descriptors remain stable.

Reproducibility

Two earlier exact-frozen runs with the same 5 connections per second and 100 ms lifetime produced consistent results:

Run
TCP control
/noise CPU
Increase over TCP
Adjusted excess vs quiet

1

0.44819%

1.14560%

155.61%

261.84%

2

0.44819%

1.17861%

163.00%

274.23%

The fresh clean-worktree E2E above produced an even stronger 187.87% increase over TCP and 316.02% adjusted excess over quiet.

Inline PoC patch

The following is the complete poc-submit.patch (SHA-256 63BF6E9F13FE5E3B8E0AEE39BCC3CF4F8D583438008F07DE6587F0DC41405BC2).

Was this helpful?