Cofactor 8 & Point Validation
Ed25519 has cofactor 8, so points can carry a torsion component. Why key images must be checked for the prime-order subgroup, the hash-to-point map, and the bugs that ignoring this caused.
This course is for people who want to write Monero code, not just understand it. We assume the internals course cold and go straight to the edges where real bugs and consensus rules live. We start with the single most under-appreciated fact about Ed25519: its cofactor is 8, and ignoring that has caused real vulnerabilities.
Group Order vs Curve Order
The full Ed25519 curve has order 8ℓ, where ℓ is the large prime subgroup order. The factor 8 is the cofactor. This means the group is not of prime order: it contains a small torsion subgroup of order 8. A valid-looking, correctly-encoded point can be:
- A point in the prime-order subgroup (what we want), or
- A prime-order point plus a torsion component, or
- A pure small-order point.
Encoding/decoding does not by itself guarantee subgroup membership. Any protocol that assumes "decoded point ⇒ prime-order point" is potentially broken.
Why This Threatens the Key Image
Recall the double-spend defense: each output has exactly one valid key image I = x·Hp(P), and nodes keep a spent-image set. The security argument depends on uniqueness — one output, one image. But if you can add a torsion point T (order dividing 8) to a key image and still pass verification, you get I' = I + T: a different image that the network hasn't seen, letting you spend the same output again. That's a double-spend.
This is not hypothetical — it's exactly the class of bug Monero has had to patch. The defense is to reject key images that are not in the prime-order subgroup: a node checks that multiplying the key image by ℓ yields the identity (equivalently, that it has no torsion component). Contributors touching transaction validation must preserve this check.
The Hash-to-Point Map
Hp(·) is not a hash followed by "interpret as a point" — most byte strings aren't valid points. Monero uses a constant-time map (historically ge_fromfe_frombytes_vartime, an Elligator/Shallue–van de Woestijne-style construction) that takes a field element and deterministically produces a curve point. Two properties matter for contributors:
- The output's discrete log with respect to
Gmust be unknown (otherwise key images become linkable — see the next lesson's logic and the internals course). - The map can land on a point with a torsion component, so downstream code must clear the cofactor (multiply by 8, or validate subgroup membership) where the protocol requires a prime-order point.
Other Places Cofactor Bites
- Equality/linkability checks: comparisons that should be "equal in the prime-order group" can be fooled by torsion unless you reduce first.
- Multiplying by the cofactor (×8) is the standard way to "clear" a point into the prime-order subgroup, but it also multiplies any intended value by 8 — so you can't sprinkle it blindly; you reduce where the spec says to.
- Ristretto is a modern abstraction that removes the cofactor problem entirely by construction; Monero predates it and instead handles cofactor explicitly in consensus code, which is why these checks are load-bearing.
Lesson one of contributing: a point that decodes is not necessarily a point you can trust. Next, the privacy side of getting details right — Decoy Selection & the Output Distribution.
Comments
Log in or create a free account to comment.
No comments yet — be the first.