The initiator's ephemeral decapsulation key survives a rejected response, so pass 3 can be retried on the same state
Minor break · Argued · Cause: code
Property: key recovery
Cost against claim: on the order of 103 retries on one reused initiator state recover dk and that session's key (costed, not run)
In the reference Loom-AKE, a failed decapsulation leaves the initiator's ephemeral KEM key dk in place: the native context stays in its waiting state and the NGCC API leaves the caller's pass-1 state blob byte for byte unchanged, so one dk can process any number of responder messages. A caller that retries on that state hands an active attacker a plaintext-checking oracle, at most one bit per attempt, from which dk follows after on the order of 103 attempts, and with it the key of the session whose honest ciphertext was withheld. dk is not erased after a successful decapsulation either.
| Parameter set | Claim | Attack cost | Verdict |
|---|---|---|---|
| LoomKEX-128 | IND-CCA of the session, 128 classical | about 103 retries on one state (s carries about 1.5 x 103 bits); costed, not run | Minor break |
| LoomKEX-256 | IND-CCA of the session, 256 classical | on the order of 103 retries on one state; costed, not run | Minor break |
| LoomKEX-512 | IND-CCA of the session, 512 classical | several 103 retries on one state (s carries about 6.6 x 103 bits); costed, not run | Minor break |
What causes it
Section 4.1.5 (p. 33) and section 4.4 (p. 48) require dk to process at most one KEM ciphertext and to be erased after it, whether decapsulation succeeds or returns failure, enforced by the implementation's state machine; the simulation in Theorem 4.13 (pp. 36 to 37) relies on it. The reference enforces none of it, identically at the three levels: crypto_loom_auth_init (loom/loom.c) returns an error on a failed decapsulation with its state and skkem untouched, destroy_ctx frees without wiping, the NGCC adapter kex_generate_pass3_msg_a wipes only its own copy and leaves the caller's PASS1 blob, and loom_kex_ctx_pack serialises skkem at every stage. The signed transcript covers only the accepted ciphertext, so rejected attempts leave no trace, and the specification's own advice of a simple protocol-level retry after decryption failures (p. 5) invites the reuse.
Evidence
Behavioural test on the unmodified reference at the 128 and 512 levels: one initiator context processed 1000 distinct tampered responder messages under the same dk, all rejected with state and dk unchanged, then accepted the honest message and completed the handshake with equal shared secrets. Through the NGCC API, the caller's pass-1 blob stayed identical over 20 failed pass-3 calls and then completed with the honest message, and the pass-3 blob after success still carries dk. The key recovery itself was costed, not run. A second member of PQC-X rebuilt the reference from a checked copy, reproduced the official KATs byte for byte, wrote and ran these tests and re-ran the attacker's state check.
Cause in the submitted code; the specification is not affected.
Limits
The submitted KAT harness aborts on the first error. Exposure needs a caller that waits for another responder message on the same state, which the API permits and the retry advice suggests but section 4.4 forbids; a small retry cap removes it. The payoff is one session's key, not a long-term key.
Credit
First public report: M.-J. Saarinen (ngcc.dev kex-05-2, 2026-09-21). Found independently by PQC-X.