Design
compass-sig-1
A 512-bit message digest at every level caps COMPASS-SIG-384 and -512 at 256 bits
Minor break · Argued · Cause: parameter size
Property: existential unforgeability
Cost against claim: digest collision 2256 against 2384 and 2512
Signing and verification bind the message only through the digest µ = H(pk ‖ m), which is 512 bits at every level. Two messages with the same µ under the target public key share every signing target, so a signature obtained on one is valid for the other. A generic collision on µ costs 2256 hash evaluations plus one signing query, against claims of 2384 and 2512; on a quantum computer about 2171 with quantum memory or 2205 without, against 2192 and 2256.
| Parameter set | Claim | Attack cost | Verdict |
|---|
| COMPASS-SIG-384 | 384 classical, 192 quantum | 2256 classical plus one signing query; ≈2170.7 quantum with quantum memory, ≈2204.8 without | Minor break |
| COMPASS-SIG-512 | 512 classical, 256 quantum | 2256 classical plus one signing query; ≈2170.7 quantum with quantum memory, ≈2204.8 without | Minor break |
| COMPASS-SIG-256 | 256 classical | 2256 (no margin) | Holds |
What causes it
Algorithms 2 and 3 (sections 2.3.2 and 2.3.3) compute µ = H(pk ‖ m) and tie the message to the signature only through c = H(µ ‖ w1); the verifier reads m nowhere else. The specification does not state the length of µ, and the only output length it gives H is n bits (section 2.2.3, hashing to a ball), which is 512 at levels 384 and 512; the reference code fixes 64 bytes at every level (CRHBYTES in params.h), and the official test vectors follow the code.
Evidence
The digest length was read from the specification, from the reference code (params.h, sign.c) and from the official test vectors, and the code was checked to use the message only through µ. Nothing was run: the cost is the generic birthday bound. A second member of PQC-X re-read the specification and the code, confirmed that both readings of the specification give 512 bits at these levels, and recomputed the classical and quantum figures.
Cause in the specification, traced to the specification text.
Limits
Generic and far from practical; fixed by lengthening µ at the higher levels. COMPASS-SIG-128 is unaffected, and COMPASS-SIG-256 sits exactly on its claim.
What PQC-X adds
The quantum cost, about 2171 with quantum memory (below the quantum requirement of both levels) and about 2205 without (below that of level 512), and the trace to the specification's own text: the one output length it gives H is n bits, so the 512-bit digest follows from the specification as much as from the code.
Credit
First public report: M.-J. Saarinen (ngcc.dev sign-06-1, 2026-09-21). Found independently by PQC-X.
Implementation
Bugs in the submitted code; the specification is sound on these points.
compass-sig-i1
The submitted code expands every key pair from a 32-byte seed at levels 384 and 512
Minor break · Argued · Cause: code
Property: key recovery
Cost against claim: seed enumeration 2256 (Grover ≈2128) against 2384 and 2512
Scope: The reference and optimized implementations at levels 384 and 512. An implementation written from Algorithm 1 is not affected.
The submitted code draws 32 random bytes and expands the whole key pair from them with one XOF call, at every level, where the specification's key generation takes an n-bit seed, 512 bits at levels 384 and 512. Enumerating the 32 bytes and comparing the first 32 bytes of the expansion with the public seed in pk recovers the private key after at most 2256 XOF calls, or about 2128 Grover iterations, against claims of 2384 and 2512 classical and 2192 and 2256 quantum.
| Parameter set | Claim | Attack cost | Verdict |
|---|
| COMPASS-SIG-384 | 384 classical, 192 quantum | 2256 XOF calls classical; ≈2128 Grover iterations | Minor break |
| COMPASS-SIG-512 | 512 classical, 256 quantum | 2256 XOF calls classical; ≈2128 Grover iterations | Minor break |
What causes it
Algorithm 1 (KeyGen, section 2.3.1) takes an n-bit seed and derives an n/8-byte public seed and an n/8-byte private seed from it, with n = 512 at levels 384 and 512 (Table 1). The reference and optimized code keep a 32-byte seed at every level (SEEDBYTES in params.h) and expand ρ, ρ′ and the signing key K from it with one XOF call (sign.c). Table 2 of the specification, however, lists key sizes that match the code's 32-byte layout rather than the text's, so the specification disagrees with itself on the key layout; the algorithm's text is what fixes the seed length, and a fix that follows it changes the published key sizes.
Evidence
On the unmodified reference code at levels 128, 384 and 512, key generation was seen to read exactly 32 bytes of randomness. Those 32 bytes alone, with the random generator otherwise wiped, rebuild the public and private keys of all ten official test vectors at each level; the public seed is the first 32 bytes of the expansion and the stored signing key K is bytes 96 to 127; flipping one seed bit changes the public key. A second member of PQC-X rebuilt the reference code, reproduced the official test-vector files byte for byte, and re-ran these checks on a fresh copy. The 2256 enumeration itself is an argument, not a run.
Cause in the submitted code; the specification is not affected.
Limits
Generic and far from practical. Levels 128 and 256 are not affected: there the 256-bit seed meets the claim, with no margin at 256.
What PQC-X adds
The quantum cost, about 2128 Grover iterations of one XOF call, below the 2192 and 2256 quantum requirements as well; the check that the official test-vector keys are rebuilt from their 32 seed bytes alone; and the note that the specification's own size table already lists the 32-byte layout, so a conformant fix changes the published key sizes.
Credit
First public report: M.-J. Saarinen (ngcc.dev sign-06-2, 2026-09-21). Found independently by PQC-X.
compass-sig-i2
The submitted code signs deterministically from a 32-byte key at levels 384 and 512
Minor break · Argued · Cause: code
Property: key recovery
Cost against claim: signing-key search 2256 (Grover ≈2128) against 2384 and 2512
Scope: The reference and optimized implementations at levels 384 and 512, as compiled. An implementation written from Algorithm 2 is not affected.
The submitted code signs deterministically: the masking vector y is derived from a 32-byte key K stored in the private key and from the message digest, with no fresh randomness. Guessing K regenerates y for any published signature, and z − y = c·s then gives the secret vector s, which is enough to sign. The search costs at most 2256 trials of one XOF call and a norm check, about 2128 Grover iterations, against claims of 2384 and 2512 classical and 2192 and 2256 quantum.
| Parameter set | Claim | Attack cost | Verdict |
|---|
| COMPASS-SIG-384 | 384 classical, 192 quantum | 2256 trials classical; ≈2128 Grover iterations | Minor break |
| COMPASS-SIG-512 | 512 classical, 256 quantum | 2256 trials classical; ≈2128 Grover iterations | Minor break |
What causes it
Algorithm 2 (Sign, section 2.3.2) takes an external n-bit random seed as input and refreshes it at each rejection; the specified private key (seed, s, e, t0) holds no signing key, and the specification describes no deterministic mode. The submitted code derives the mask seed as XOF(K ‖ rnd ‖ µ) with a 32-byte K stored in the private key (sign.c), and rnd is the all-zero string unless the compile-time option COMPASS_SIG_RANDOMIZED_SIGNING is enabled, which it is not in the reference and optimized trees (config.h).
Evidence
On the unmodified reference code at levels 128, 384 and 512, the signer draws no randomness: two unrelated states of the random generator give the same signature, equal to the official test vector, for all ten vectors at each level. Recomputing the mask seed from K and µ with the zero nonce regenerates the accepted y, and z − y equals c·s exactly, 10 of 10 at each level; a wrong K is rejected at once because z − y is then not bounded by τ. A second member of PQC-X ran these checks on a fresh copy of the code. The 2256 search itself is an argument, not a run.
Cause in the submitted code; the specification is not affected.
Limits
In the submitted code K is itself a slice of the key-generation expansion, so the seed finding above already yields it; this one stands on its own only if the seed is lengthened and K kept at 32 bytes. Enabling the code's randomized-signing option, which mixes 32 fresh bytes into the mask seed, closes it. Levels 128 and 256 are not affected.
Credit
Found by PQC-X.
compass-sig-i3
The submitted challenge sampler is far from uniform at n = 512
Minor break · Argued · Cause: code
Property: existential unforgeability
Cost against claim: key-only forgery 2305.8 and 2363.5 against 2384 and 2512
Scope: The reference and optimized implementations at levels 384 and 512. An implementation written from Figure 1 is not affected.
The submitted challenge sampler ports the ML-DSA routine to n = 512 without widening its index byte or its 64-bit sign word: coefficients 256 to n−τ−1 are never non-zero and only the first 64 signs are random. The most likely challenge then has probability τ!·2−8τ−64 instead of one over the size of the challenge set. A forger who fixes that challenge and a short z passing the norm checks, computes the matching w1 from public values alone and varies the message until the hash lands on that challenge, needs 2305.8 trials at level 384 and 2363.5 at level 512, against 2384 and 2512.
| Parameter set | Claim | Attack cost | Verdict |
|---|
| COMPASS-SIG-384 | 384 classical, 192 quantum | 2305.8 trials of two hashes and one sampling; ≈2152.9 Grover | Minor break |
| COMPASS-SIG-512 | 512 classical, 256 quantum | 2363.5 trials of two hashes and one sampling; ≈2181.8 Grover | Minor break |
What causes it
Figure 1 (section 2.2.3) draws the swap index j uniformly from {0, …, i} for i from n−τ to n−1, with a fresh sign at each step, which is uniform over the challenge set. The submitted poly_challenge (poly.c, reference and optimized trees) takes j from a single byte, redrawn only while j > i, which never happens since i ≥ n−τ ≥ 392, and takes every sign from one 64-bit word, so steps 65 to τ always write +1. Positions 256 to n−τ−1 are never touched, and at most 64 signs are random.
Evidence
On the unmodified reference code, 100 000 challenges per set: at levels 384 and 512 no non-zero coefficient ever fell in positions 256 to n−τ−1, where a uniform sampler averages 27 and 32, and the number of −1 coefficients averaged 32 against 39 and 60 expected, the weight being always τ; at level 128 the sampler behaves uniformly. The probability of the most likely challenge, τ!·2−8τ−64, was checked by exhaustive enumeration on toy sizes. A second member of PQC-X re-ran the statistics on a fresh copy of the code (100 000 challenges per set), re-derived the formula on eight toy sizes in the regime of the real sets, and checked that a fixed z at the corners of its box passes the code's Euclidean check at both levels. The 2305.8 and 2363.5 costs are computed, not run.
Cause in the submitted code; the specification is not affected.
Limits
Far from practical, and dominated by the two 2256 findings on the same code. The specified sampler is uniform, and with it the same forgery costs 2388.9 and 2517.6 trials, above the claims. Levels 128 and 256 (n = 256, τ ≤ 64) are not affected.
Credit
Found by PQC-X.