When auditing elliptic curve libraries—especially ECDH-related code—double-check whether the implementation performs sufficient validation on points received from untrusted peers. If not, an attacker can send crafted points and (with the right kind of oracle) recover the server's private key in the worst case.
There is a well-known attack called the small-subgroup attack. The attacker sends a point from a subgroup of small order (e.g., order 2, 4, or 8). Because the subgroup is tiny, the ECDH shared secret computed from that point has only a handful of possibilities and can be brute-forced. Repeating with different small subgroups can reveal the victim's private key modulo small factors. Defenses include subgroup membership checks and using curves with cofactor 1.
Another (related) attack is the invalid-curve attack. It builds on the same idea, but the attacker chooses points that are not even on the intended curve (often on twists or related curves). The victim computes scalar multiplication anyway, and the attacker learns congruences about the private key. With enough congruences, the private key can be reconstructed using the Chinese Remainder Theorem. The key defense is to verify that the incoming point satisfies the intended curve equation (and, for cofactor curves, also enforce subgroup membership).
I am writing this article because I found that many existing resources don't explain the details (especially the math) clearly. Therefore, I will try to make every step explicit.
Threat model (why the attacker can learn anything)#
Both attacks need a way for the attacker to distinguish which candidate shared secret was used. In practice this typically comes from one of the following:
- The implementation directly uses some representation of as a symmetric key.
- The protocol provides an explicit key confirmation step.
- The victim decrypts/authenticates attacker-controlled ciphertext and reveals success/failure (even indirectly via timing, error messages, or parsing behavior).
If the protocol is designed so that an attacker cannot observe any signal that depends on the derived key, then “brute-forcing the tiny subgroup” does not immediately translate into a private-key recovery.
What the oracle looks like in real systems#
In applied cryptography, the “oracle” is rarely an explicit API like is_key_correct(). It tends to show up as one of these patterns:
- Decrypt-then-parse: the server decrypts attacker-controlled ciphertext and the attacker can tell whether the plaintext “made sense” (JSON parse succeeds, protobuf decode succeeds, etc.).
- MAC/AEAD verification: the server checks a MAC/tag derived from the ECDH key and reveals success/failure (even if only as a generic error code or timing difference).
- Key confirmation / handshake transcripts: the protocol includes a message whose validity depends on the derived key (many handshake designs do).
- Application-level behavior: e.g., different response size, different latency, different error logs, or conditional side effects.
The important point is: ECDH itself is just math, but protocol glue can easily expose a key-dependent signal.
Some (sub)group theory: Lagrange's theorem and Cauchy's theorem#
We will need some group theory results to understand why small-subgroup and invalid-curve attacks work. This part is missing from most of the articles / CTF writeups on the Internet, so pay attention. We are going to cover two theorems regarding subgroups: Lagrange's theorem and Cauchy's theorem.
Lagrange's theorem:
Reference: http://abstract.ups.edu/aata/cosets-section-lagranges-theorem.html
Here is a short intro to cosets and Lagrange's theorem on Youtube. Basically, the theorem says: if is a subgroup of , then divides . For example, if , then the only possible subgroup orders are 1, 2, 3, 4, 6, and 12. Excluding the trivial subgroup and itself, the non-trivial subgroup orders are 2, 3, 4, and 6. Note this does not mean there are only four non-trivial subgroups; multiple subgroups with the same order can exist.
One way to understand why this is true is via cosets: cosets partition the group and all cosets have the same size. Each coset has the same size as . Visually, you can think of as a chocolate bar and each coset as a chunk. Because the cosets are disjoint and cover , we have , so divides $|G|.
Lagrange's theorem has two important corollaries:
Reference: http://abstract.ups.edu/aata/cosets-section-lagranges-theorem.html
The first corollary is very easy to understand. The second corollary says: any group with prime order is cyclic, and any element other than the identity is a generator. Elliptic curves over finite fields form finite abelian groups, so these corollaries apply. In particular, the order of any point divides the order of the curve group, and if the curve group has prime order then every non-identity point generates the group.
These two corollaries are important results, keep that in mind, we will use them a lot when studying elliptic curves.
Important: Lagrange's theorem is an if-then theorem. It does not guarantee the existence of a subgroup of a particular order. Equivalently, the reverse is false in general: even if divides , a subgroup of order might not exist. A useful partial converse is given by Cauchy's theorem for prime divisors.
Cauchy's theorem:
Reference: http://abstract.ups.edu/aata/sylow-section-sylow-theorems.html
For example, say group has order 6. We can factor 6 into prime decomposition: . Cauchy's theorem guarantees that subgroups with order 2 and 3 exist. In summary, Lagrange's theorem tells you what subgroup orders are possible, and Cauchy's theorem tells you that subgroups of prime order (for primes dividing ) actually exist.
Recall the second corollary of Lagrange's theorem says any group of prime order is cyclic, and any element other than identity is a generator. Combining it with Cauchy's theorem, the prime-order subgroups we get are all cyclic, and any (non-trivial) element in the subgroup is a generator. Recall that the first corollary of Lagrange's theorem says the order of an element in a group divides the order of the group. Since we are working with prime-order subgroups, it is easy to see that the order of an element is either 1 or , where is the order of the prime-order subgroup. The order-1 case corresponds to the identity element, so we can conclude that if a prime-order subgroup has order , then any non-trivial element in it has order as well.
You might ask, why bother studying subgroups though? Abstractly, think of taking subgroup as reducing a big problem to a small one. In the two attacks we are going to talk about next, you will see how taking subgroups converts a hard problem into easy problem (computationally).
Understanding Elliptic Curve Diffie-Hellman (ECDH)#
ECDH is like traditional Diffie-Hellman (DH), but replaces the original discrete log problem (DLP) with the elliptic curve discrete log problem (ECDLP). In short, DLP means: given , it is hard to recover . In comparison, ECDLP means: given where is a point on an elliptic curve over a finite field, it is hard to recover .
In general it is recommended to use ECDH over DH since it has shorter keys, up-to-date standards, and fewer attack vectors. However, if you don't implement ECDH correctly, it is still vulnerable to attacks such as the small-subgroup attack and invalid-curve attack.
The motivation of DH is to exchange a shared key over insecure channel and use that shared key for future symmetric cryptography communication. The same idea applies to ECDH. This type of system is called "hybrid encryption", inheriting the security of public-key cryptography and the speed of symmetric cryptography.
In ECDH, Alice and Bob agree on a point on some elliptic curve. Each computes a public key from a private scalar: Alice computes and Bob computes .
The next step is key exchange. They can publish and on the Internet (an insecure channel), since an attacker can't efficiently recover or from those public keys.
In the end they compute a shared secret using their private keys. Alice computes , and Bob computes . These are equal because scalar multiplication is associative and integer multiplication commutes. In practice, is fed through a KDF to derive a symmetric key.
Small subgroup attack#
Small subgroup attack is a building block of invalid curve attack, so we discuss it first.
First, for a classic small-subgroup attack to work, the curve group must have a non-trivial small-order subgroup. Curves with cofactor avoid this entire class of subgroup pitfalls.
Note: secp256k1 has cofactor , so the classic small-subgroup issue does not apply to it. However, secp256k1 can still be affected by invalid-curve style problems if the implementation skips on-curve checks.
Consider an ECDH protocol where Bob does not validate incoming points from Alice sufficiently. Usually a standardized curve will have order of the form , where is a large prime and is some small integer called the cofactor (commonly 1, 2, 4, or 8 depending on the curve).
When Alice conducts a small-subgroup attack, she tries to pick a point of small order (typically ). A subtle but important point: it is not guaranteed that there exists a point of order exactly on every curve, even when divides the group order. (This is precisely why Lagrange's theorem is only a one-way implication.) For the attack, any small-order point is sufficient.
If Bob computes and uses it (directly or indirectly) as key material, then because has order , the value depends only on and has only possibilities:
If Alice has an oracle that distinguishes the correct derived key (e.g., decrypt-and-parse behavior, a MAC check, or explicit key confirmation), she can brute-force all candidates and learn . Repeating with multiple small primes leaks more information.
What “small subgroup” looks like in practice#
In practice, small-subgroup issues show up most often in one of these situations:
- Curves with cofactor (some Edwards/Montgomery curves, some pairing-friendly curves, some custom curves).
- Protocols that accept raw points from the peer and perform scalar multiplication without validation.
- Implementations that conflate “the curve group” with “the prime-order subgroup”. Many protocols intend to operate in a large prime-order subgroup; cofactor points are an “extra” structure that must be handled explicitly.
Even when a curve is standardized and widely used, your implementation details matter: accepting unchecked points or skipping subgroup checks can reintroduce the problem at the application layer.
Why small-order points exist (and how to find them)#
If the curve group has order with , then for any prime dividing , Cauchy's theorem applied to the full curve group implies there exists an element of order . That already gives a tiny subgroup to attack.
In practice, one common way to find small-order points is cofactor projection:
- Sample a random point .
- Compute where .
- Then lies in the “cofactor part” of the group and its order divides .
- If , retry.
Because is small, you can quickly determine the exact order of by checking until you hit .
Defenses#
To prevent small-subgroup attacks:
- Prefer cofactor-1 curves when possible.
- Always validate that the received point is on the curve (see invalid-curve attack below).
- Enforce subgroup membership for cofactor curves. If the large prime-order subgroup has order (prime), then checking and ensures is in the correct subgroup.
Practical notes:
- Don’t confuse cofactor clearing with validation. Multiplying by ("cofactor clearing") can be part of some designs, but it does not replace “reject invalid public keys” unless the protocol is specifically designed around it.
- Beware of “all-zero shared secret” style failures. Some widely-deployed constructions treat a special shared secret (often the all-zero output) as invalid and abort, because it can be induced by low-order inputs in certain settings. Whether this applies depends on the curve and the key agreement construction, but the meta-lesson is the same: handle edge cases explicitly and consistently.
Invalid curve attack#
If Bob does not verify whether is actually on the intended elliptic curve (call it the valid curve), Alice can send a fake point that lies on some other curve (an invalid curve). For example, if the valid curve is secp256k1 (i.e., ), then Alice can choose curves where .
Why keep the term fixed and only change the constant term? For short Weierstrass curves , the affine addition/doubling formulas depend on but not on . Since secp256k1 has , the same formulas “work” across for any , so an implementation that skips the curve-equation check may compute without obvious errors even though is off-curve. Check this answer for a derivation.
Invalid-curve attacks are more powerful because the attacker can choose points from many different curves (often twists), obtaining congruences modulo many different small primes.
What “invalid curve” means operationally#
Operationally, “invalid-curve” almost always boils down to: the implementation performs scalar multiplication on an input that was never verified to be a valid group element.
Common root causes:
- Skipping on-curve checks for performance (especially in hand-rolled code).
- Parsing shortcuts that accept non-canonical or malformed encodings.
- Using formulas that happen to run on off-curve inputs (the math doesn’t crash, but the security proof no longer applies).
The fix is straightforward: treat peer public keys as untrusted input and validate them before doing key agreement.
Back to the actual attack. Alice chooses a point on an invalid curve with small prime order and sends it to Bob. Bob computes and derives a key from it.
If Alice has an oracle that distinguishes the correct key, she can brute-force the possibilities and learn a congruence relation for some . One round only reveals the private key modulo a small prime.
To recover , Alice chooses point with order , point with order , and so on, and repeats the above process. In the end, Alice gets a system of congruences:
Since all moduli are prime, they are coprime to each other, satisfying the criteria for the Chinese Remainder Theorem (CRT). Alice can solve for . If Alice collects enough congruences so that the product exceeds the range of possible private keys, then is uniquely determined. In other words, Alice recovers Bob's private key completely.
To prevent this attack, Bob must verify that all points sent by Alice satisfy the Weierstrass equation of the valid curve. For curves with cofactor , Bob must also enforce subgroup membership; otherwise, even on-curve points can live in small subgroups.
Practical note: on many short Weierstrass curves used for ECDH (including common NIST-style curves and secp256k1), the cofactor is 1, so subgroup membership is “automatic” once you validate on-curve and reject the identity. On cofactor- curves, you need both.
Practical implementation advice (do this / avoid this)#
If this is an applied cryptography review, here are the patterns that most often make the difference in real codebases:
Concrete public-key validation checklist:
- Decode safely: reject malformed/non-canonical encodings; reject out-of-range coordinates.
- Reject the identity: ensure (or the curve's equivalent “point at infinity” representation).
- On-curve check: verify the curve equation holds for .
- Subgroup check (when ): ensure is in the intended large prime-order subgroup (e.g., ).
Do this:
- Use a well-reviewed library API that validates keys (or provides a “check key” function) rather than doing point math on raw bytes.
- Keep errors uniform for all key-agreement failures (same error code, similar timing) to avoid accidentally creating a high-quality oracle.
- Derive keys via a KDF from the ECDH shared secret (e.g., HKDF) and use an AEAD/MAC with proper key separation.
- Reject peer keys early (during parsing/validation) rather than after partially processing them.
Avoid this:
- Treating “ECDH output bytes” as a symmetric key directly without a KDF.
- Hand-rolling parsing for compressed/uncompressed point formats unless you really need to.
- Returning different error messages for “bad encoding” vs “bad curve equation” vs “MAC failed”.
Encoding gotchas to explicitly test#
When reviewing code, these are good test cases to add (or ensure the library already handles):
- Non-canonical encodings (wrong length, forbidden prefixes, extra leading zeros).
- Points with coordinates outside the field range.
- The identity element / point at infinity (if representable in the chosen encoding).
- Off-curve points.
- For cofactor- curves: low-order points and points not in the prime-order subgroup.
Other references not mentioned in article#
- https://safecurves.cr.yp.to/twist.html
- https://crypto.stackexchange.com/questions/18222/difference-between-ecdh-with-cofactor-key-and-ecdh-without-cofactor-key/26844#26844
- https://www.hackthebox.com/blog/business-ctf-2022-400-curves-write-up
- https://crypto.stackexchange.com/questions/43614/how-to-find-the-generator-of-an-elliptic-curve


