The wrapper draws one ephemeral key at initialisation and reuses it in every session, so completed sessions lose forward secrecy
Minor break · Demonstrated · Cause: code
Property: forward secrecy
Cost against claim: session key recomputed from the recorded transcript and the long-term secret keys, against the perfect forward secrecy claimed
The submitted wrapper draws the ephemeral seed inside kex_init, folds the ephemeral key into the composite key pair that it returns as the party's long-term key, and no message pass ever draws a new one: the composite decapsulation key is the same in every session. A passive adversary records the two ciphertexts of a session; once it later holds the two private keys as the interface outputs them, it decapsulates both ciphertexts and recomputes the session key exactly. The protocol also discloses each party's frozen ephemeral seed to its peer, so an adversary who once completed a session with each of two parties needs only their certified static keys to recover every past session key between them.
| Parameter set | Claim | Attack cost | Verdict |
|---|---|---|---|
| AFS-KEX-C128 | perfect forward secrecy (characteristic 9); 128 classical | none: two decapsulations and one key derivation from the recorded transcript and the long-term secret keys | Minor break |
| AFS-KEX-C256 | perfect forward secrecy (characteristic 9); 256 classical | none: the same computation, by source identity of the wrapper | Minor break |
| AFS-KEX-C512 | perfect forward secrecy (characteristic 9); 512 classical | none: the same computation, by source identity of the wrapper | Minor break |
What causes it
Fig. 3 and §2.2 sample seed_A in Round 1 and seed_B in Round 2 of every session, and characteristic 9 (§1.3, §2.7) rests on that freshness; the security proofs cover that protocol. §4 then describes the profile submitted under the NGCC interface, in which each party holds the peer's combined public key cpk before the handshake, and the code implements that profile: kex_init_self (KEX_AlgorithmInstance.c, lines 56 to 108) draws seed_e, forms csk = s_s + s_e and cpk = t_s + t_e, exports (cpk, pk_s) as the public key and (csk, cpk, id, z, sk_s) as the private key, and keeps seed_e in the state. The pass functions draw fresh encapsulation coins only, and the EKeyGen calls of passes 3 and 4 merely rebuild the peer's ephemeral key for the binding check. The message sizes of Table 6 (768, 784, 16 and 0 bytes at C128) follow from cpk never being sent. The interface cannot refresh the ephemeral key: a second kex_init also replaces the certified static key, and a fresh seed written into the state makes the peer's binding check fail.
Evidence
Run on the reference AFS-KEX-C128 build, rebuilt from the submission with the official test vectors reproduced byte for byte. Two sessions from one kex_init transmit the same composite public key, and the session key recomputed from the recorded transcript and the two exported private keys equals the true session key (an exact 128-bit match). A second member of PQC-X rebuilt the code, re-ran both programs with identical logs, and ran an independent test: both sessions complete, the seed each party discloses to its peer is the same in both sessions and equals the one stored by kex_init, the composite secret key equals the static secret plus the ephemeral secret derived from that seed, a second kex_init changes the static key too, and a fresh seed placed in the state makes pass 4 fail. The fifteen copies of the wrapper (reference, AVX2 performance and resource variants, Cortex-M4; three sets each) are identical, which carries the finding to AFS-KEX-C256 and AFS-KEX-C512.
Cause in the submitted code; the specification is not affected.
Limits
The protocol of Fig. 3, which the security proofs cover, is not affected: with an ephemeral key drawn in every session the attack disappears. The behaviour was run on AFS-KEX-C128 only; the C256 and C512 wrappers were checked identical in source.
Designers' response
On the NGCC public-key forum (2026-09-22) the designers acknowledged that the wrapper generated the composite and ephemeral key pairs at initialisation, and announced a revised implementation that draws them inside the message passes, keeps the session secrets in the session state and erases them afterwards. PQC-X has not checked it.
What PQC-X adds
The reach of the defect, measured on the submitted interface. The protocol hands each party's frozen ephemeral seed to every peer, so an adversary who once completed a session with each of two parties needs only their certified static keys, not the wrapper's exported private keys, to recover their past session keys. The interface offers no repair short of a new certified key: a second kex_init replaces the static key as well, and a fresh seed placed in the state fails the peer's binding check. The frozen key also revives the decryption-failure oracle at C128 (observation afs-kex-i2). All fifteen wrapper copies were checked identical.
Credit
First public report: M.-J. Saarinen (ngcc.dev kex-02-1, 2026-09-21). Found independently by PQC-X.