You're correct. Guess and check subtraction works, alternatively you can multiply by 8 then 1/8 to get back the original point minus the small order component and subtract. (note:for cofactor=8)
cv = Curve.get_curve('Ed25519')
#generator for low order group G
single=cv.decode_point(base64.b16decode(b'26E8958FC2B227B045C3F489F2EF98F0D5DFAC05D3C63339B13802886D53FC05'))
inv8=modinv(8,cv.order)
lo_list=[(cv.generator+single)*i-cv.generator*i for i in range(8,16)]
def get_lo(X):
#get the low order group information from a point
X_lo=X-X*8*inv8 #isolate LO info
return lo_list.index(X_lo) #find it in the list
Notice that to generate the list I had to get around the checks for equality to the 8 small order points by including G
.
This leaks the 3 least significant bits of the scalar which doesn't get you the key by itself.
Attacks
Getting the rest of the key requires that the victim manipulate the scalar in some way before multiplication to get information on higher bits. If the victim is willing to add or multiply by an attacker supplied value prior to multiplication then the full scalar can be recovered.
One scenario in which this could occur is if the victim expects keys to be derived from their public key (EG:K_derived=v*K_p) and the attacker can supply the v
used. This can be done for privacy reasons (the derived keys can't be connected to the original key).
In the context of zero knowledge proofs the point could be multiplied by some value correlated with secret information. In schnorr signatures, leaking bits of the randomly chosen scalar can reveal bits correlated with the key and done enough times the attacker can reassemble the whole key. This attack doesn't apply there but can apply in other complex multi-round zero knowledge proofs.
Attack demo
Here's some attack code that implements this attack.
Curve25519 keys are clamped to prevent this. The scalar domain is close to 2^252
. Scalar clamping clears the lowest three bits, sets bit 255 and clears bit 256. The result is of the form 8*(2^252+X)
where 2^252 > X ≥ 0
. The 2^252
is there to mitigate side channel attacks in stupid implementations.
scalars of the form 8*X
implicitly clear the small order component when multiplying. A scalar can be converted to this form by multiplying by inverse(cofactor)
and then multiplying by the cofactor without reducing modulo the group order.
Point cannonicity
another problem with the low order part of points is that equivalent points that differ only in their low order component are ... well ... different. In CryptoNote based cryptocurrencies this led to a double spend vulnerability since implementations expected that equivalent public keys would have the same byte representation but they discarded cofactor information.