@@ -212,12 +212,13 @@ fn crypto_core_ed25519_is_valid_point_internal(p: &Ed25519Point, ignore_high_bit
212212 return false ;
213213 }
214214
215- // Check 4: Use curve25519-dalek decompression for point-on-curve check.
216- // This will also reject points not in the prime-order subgroup if the feature
217- // `serde` is not enabled for curve25519-dalek, but we explicitly checked
218- // identity.
215+ // Check 4: Use curve25519-dalek decompression for point-on-curve check and
216+ // reject points with a torsion component (not in the main subgroup).
219217 match CompressedEdwardsY :: from_slice ( p) {
220- Ok ( compressed) => compressed. decompress ( ) . is_some ( ) ,
218+ Ok ( compressed) => match compressed. decompress ( ) {
219+ Some ( point) => point. is_torsion_free ( ) ,
220+ None => false ,
221+ } ,
221222 Err ( _) => false , // Should not happen if length is correct, but handle defensively.
222223 }
223224}
@@ -472,6 +473,47 @@ mod tests {
472473 "Small-order point (identity element) should be rejected by stricter validation"
473474 ) ;
474475
476+ // Test torsion points and mixed-order points to ensure we reject anything
477+ // outside the main subgroup.
478+ use curve25519_dalek:: traits:: IsIdentity ;
479+
480+ let torsion_point = curve25519_dalek:: constants:: EIGHT_TORSION
481+ . iter ( )
482+ . find ( |point| {
483+ let torsion_bytes = point. compress ( ) . to_bytes ( ) ;
484+ let mixed_point = curve25519_dalek:: constants:: ED25519_BASEPOINT_POINT + * point;
485+ let mixed_bytes = mixed_point. compress ( ) . to_bytes ( ) ;
486+ torsion_bytes[ 31 ] & 0x80 == 0
487+ && mixed_bytes[ 31 ] & 0x80 == 0
488+ && !point. is_identity ( )
489+ && !mixed_point. is_torsion_free ( )
490+ } )
491+ . copied ( )
492+ . expect ( "expected a non-identity torsion point with canonical encoding" ) ;
493+
494+ let torsion_bytes = torsion_point. compress ( ) . to_bytes ( ) ;
495+ assert ! (
496+ !crypto_core_ed25519_is_valid_point( & torsion_bytes) ,
497+ "Torsion point should be rejected"
498+ ) ;
499+ assert ! (
500+ !crypto_core_ed25519_is_valid_point_relaxed( & torsion_bytes) ,
501+ "Torsion point should be rejected even with relaxed validation"
502+ ) ;
503+
504+ let mixed_bytes =
505+ ( curve25519_dalek:: constants:: ED25519_BASEPOINT_POINT + torsion_point)
506+ . compress ( )
507+ . to_bytes ( ) ;
508+ assert ! (
509+ !crypto_core_ed25519_is_valid_point( & mixed_bytes) ,
510+ "Mixed-order point should be rejected"
511+ ) ;
512+ assert ! (
513+ !crypto_core_ed25519_is_valid_point_relaxed( & mixed_bytes) ,
514+ "Mixed-order point should be rejected even with relaxed validation"
515+ ) ;
516+
475517 // Test a point that is not on the curve (but is canonically encoded)
476518 // Example: A point generated randomly is unlikely to be on the curve.
477519 // We expect this to be rejected by the decompression check.
0 commit comments