The submitted decapsulation discards its re-encryption check and returns the derived key for every ciphertext
Minor break · Demonstrated · Cause: code
Property: IND-CCA security of the submitted code
Cost against claim: one decapsulation query against IND-CCA2 at 128, 256 and 512 bits, in all four implementations
In every submitted tree (reference, AVX2, NEON, AArch64) the decapsulation writes the key derived from the decrypted message to its output before the re-encryption comparison, then moves the rejection value into a local buffer instead of the output and clears the failure flag. The output is therefore H2(Dec(sk, c) ‖ H1(pk)) for every ciphertext, valid or not. Flipping one low bit of a challenge ciphertext leaves the decrypted message unchanged, so a single decapsulation query returns the challenge's own key and tells a real session key from a random one.
| Parameter set | Claim | Attack cost | Verdict |
|---|---|---|---|
| Aigis-Enc+-512 | IND-CCA2, 128 classical | one decapsulation query | Minor break |
| Aigis-Enc+-1024 | IND-CCA2, 256 classical | one decapsulation query | Minor break |
| Aigis-Enc+-2048 | IND-CCA2, 512 classical | one decapsulation query | Minor break |
What causes it
The specification is sound on this point: Algorithm 20 (Aigis-Enc+.Decap) sets K = H3(s, c) at line 8 and replaces it with K' only when the re-encryption c' equals c (lines 9 and 10), the implicit-rejection transform that Theorems 2 and 3 analyse. In kem.c, mkem_dec copies K' to the output unconditionally (line 71), computes the comparison (line 74), then writes the rejection key into the scratch buffer buf rather than into ss (line 80) and sets fail to 0 (line 81). The NGCC wrapper kem_dec adds no check and always returns 0. The official test vectors decapsulate honest ciphertexts only, so they cannot reveal the defect.
Evidence
- On the unmodified reference build of Aigis-Enc+-512, 6 of 8 single-bit flips in the u part of a valid ciphertext returned the encapsulated key unchanged; the other 2 changed the decrypted message.
- A second member of PQC-X rebuilt the code from a fresh copy and, through the NGCC interface, decapsulated every single-bit flip of valid ciphertexts on the reference builds of the three sets and on the AVX2 build of Aigis-Enc+-512: 211,968 modified ciphertexts (official test-vector ciphertexts, and fresh ones on the build with the specification's default SHA3 instantiation), plus 2,500 random ones, each also run through a line-by-line transcription of Algorithm 20. Every modified ciphertext fails the re-encryption comparison; the code returns the key derived from the decrypted message in every case, never the value of Algorithm 20, and never signals a failure. Flipping the lowest bit of a u coefficient of a valid ciphertext returned that ciphertext's own key in 15,360 of 15,360 cases. The official test vectors reproduce byte for byte (10 of 10 per set), and honest round trips agree with Algorithm 20 (50 of 50 per build). NEON and AArch64 were checked by reading the code: their decapsulation is the same as AVX2's and the reference's respectively.
Cause in the submitted code; the specification is not affected.
Limits
Key recovery is argued, not run: since the output is a deterministic function of the decrypted message and H1(pk) is public, the decapsulation is a plaintext-checking oracle, and the published key-mismatch attacks on LWE-type encryption recover the secret key from thousands of such queries, far inside the 280 the call allows. The fix is to move the constant-time replacement to the output; in the reference and AArch64 trees the rejection secret must also be read at its specified offset (next entry).
Designers' response
The designers posted an updated implementation on the NGCC PKC forum (2026-09-22), stating that the two decryption errors reported on ngcc.dev are fixed. PQC-X has not checked it.
What PQC-X adds
The defect is confirmed in the AVX2, NEON and AArch64 trees as well as in the reference, and the shipped decapsulation is compared with a transcription of Algorithm 20 on every single-bit modification of valid ciphertexts: it never returns the specified value. The consequence beyond a distinguisher, a plaintext-checking oracle that exposes the secret key to standard key-mismatch attacks, is argued.
Credit
First public report: M.-J. Saarinen (ngcc.dev kem-01-1, 2026-09-21). Found independently by PQC-X.