Skip to content

Commit 3ec6ef9

Browse files
committed
fix: ed25519 subgroup validation
Ensure ed25519 point validation rejects points with torsion components by requiring torsion-free Edwards points after decompression. Add regression tests covering torsion points and main+torsion mixed-order points for both strict and relaxed validators.
1 parent 083fce4 commit 3ec6ef9

1 file changed

Lines changed: 47 additions & 5 deletions

File tree

src/classic/crypto_core.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)