From 06cc7111db5d159c315671b217e1922764aa9552 Mon Sep 17 00:00:00 2001 From: zz-sol Date: Wed, 17 Jun 2026 08:25:25 -0400 Subject: [PATCH] improve test coverage (#50) --- .../serial/scalar_mul/vartime_triple_base.rs | 22 ++++++ .../vector/scalar_mul/vartime_triple_base.rs | 47 +++++++++++++ .../solana-ed25519/src/ed_sigs/tests/heea.rs | 69 +++++++++++++++++-- .../solana-ed25519/src/short_weierstrass.rs | 38 ++++++++++ 4 files changed, 172 insertions(+), 4 deletions(-) diff --git a/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs b/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs index 35ad9ef..dbf38fa 100644 --- a/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs +++ b/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs @@ -304,6 +304,28 @@ mod test { assert_eq!(result, expected); } + #[test] + fn test_checked_wrapper_accepts_full_width_scalars() { + let mut a1_bytes = [0u8; 32]; + a1_bytes[0] = 7; + a1_bytes[16] = 1; + let a1 = Scalar::from_canonical_bytes(a1_bytes).unwrap(); + + let mut a2_bytes = [0u8; 32]; + a2_bytes[0] = 11; + a2_bytes[24] = 1; + let a2 = Scalar::from_canonical_bytes(a2_bytes).unwrap(); + + let b = random_scalar(); + let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(23u64); + let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(29u64); + + let result = crate::backend::vartime_triple_base_mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; + + assert_eq!(result, expected); + } + // Proptest for vartime_triple_scalar_mul_basepoint equivalence proptest::proptest! { #[test] diff --git a/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs b/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs index 2ab049e..613e98e 100644 --- a/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs +++ b/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs @@ -187,3 +187,50 @@ pub mod spec { Q.into() } } + +#[cfg(all(test, target_arch = "x86_64"))] +mod test { + use super::spec_avx2; + use crate::backend::serial; + use crate::constants; + use crate::scalar::Scalar; + + fn scalar_from_low_128(low: [u8; 16]) -> Scalar { + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&low); + Scalar::from_canonical_bytes(bytes).unwrap() + } + + #[test] + fn avx2_triple_base_matches_serial_and_naive() { + if !std::is_x86_feature_detected!("avx2") { + return; + } + + let a1 = scalar_from_low_128([ + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, + 0x11, 0x00, + ]); + let a2 = scalar_from_low_128([ + 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, + 0x69, 0x78, + ]); + let b = Scalar::from_bytes_mod_order([ + 0x42, 0x91, 0x0a, 0xbe, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x10, 0x20, 0x30, 0x40, + ]); + + let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(31u64); + let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(37u64); + + let avx2 = spec_avx2::mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); + let serial = serial::scalar_mul::vartime_triple_base::mul_128_128_256_prechecked( + &a1, &A1, &a2, &A2, &b, + ); + let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; + + assert_eq!(avx2, serial); + assert_eq!(avx2, expected); + } +} diff --git a/curve25519/solana-ed25519/src/ed_sigs/tests/heea.rs b/curve25519/solana-ed25519/src/ed_sigs/tests/heea.rs index 5633dec..476eca2 100644 --- a/curve25519/solana-ed25519/src/ed_sigs/tests/heea.rs +++ b/curve25519/solana-ed25519/src/ed_sigs/tests/heea.rs @@ -1,11 +1,11 @@ -use crate::ed_sigs::SigningKey; -use crate::ed_sigs::VerificationKey; +use crate::constants; #[cfg(feature = "std")] use crate::ed_sigs::tests::small_order::SMALL_ORDER_SIGS; +use crate::ed_sigs::{Error, Signature, SigningKey, VerificationKey}; +use crate::edwards::CompressedEdwardsY; +use crate::scalar::Scalar; #[cfg(feature = "std")] use core::convert::TryFrom; -#[cfg(feature = "std")] -use ed25519::Signature; #[test] fn test_verify_zebra_invalid_signature() { @@ -82,6 +82,67 @@ fn test_default_verification_matches_zebra() { ); } +#[test] +fn test_signature_verifier_trait_impl() { + let signing_key = SigningKey::from([3u8; 32]); + let verification_key = VerificationKey::from(&signing_key); + let msg = b"signature::Verifier trait path"; + let signature = signing_key.sign(msg); + + assert!( + ed25519::signature::Verifier::verify(&verification_key, msg, &signature).is_ok(), + "trait-based verification should accept a valid signature" + ); + assert!( + ed25519::signature::Verifier::verify(&verification_key, b"wrong message", &signature) + .is_err(), + "trait-based verification should reject an invalid signature" + ); +} + +#[test] +fn test_verify_zebra_prehashed_rejects_noncanonical_s() { + let signing_key = SigningKey::from([4u8; 32]); + let verification_key = VerificationKey::from(&signing_key); + let mut sig_bytes: [u8; 64] = signing_key.sign(b"noncanonical s").into(); + sig_bytes[32..].copy_from_slice(&constants::BASEPOINT_ORDER.to_bytes()); + let signature = Signature::from(sig_bytes); + + assert_eq!( + verification_key.verify_zebra_prehashed(&signature, Scalar::from(1u64)), + Err(Error::InvalidSignature) + ); +} + +#[test] +fn test_verify_zebra_prehashed_rejects_undecodable_r() { + let signing_key = SigningKey::from([5u8; 32]); + let verification_key = VerificationKey::from(&signing_key); + let mut sig_bytes: [u8; 64] = signing_key.sign(b"undecodable r").into(); + let invalid_r = first_undecodable_r(); + assert!(CompressedEdwardsY(invalid_r).decompress().is_none()); + + sig_bytes[..32].copy_from_slice(&invalid_r); + let signature = Signature::from(sig_bytes); + + assert_eq!( + verification_key.verify_zebra_prehashed(&signature, Scalar::from(1u64)), + Err(Error::InvalidSignature) + ); +} + +fn first_undecodable_r() -> [u8; 32] { + for candidate in 0u16..=u16::MAX { + let mut bytes = [0u8; 32]; + bytes[..2].copy_from_slice(&candidate.to_le_bytes()); + if CompressedEdwardsY(bytes).decompress().is_none() { + return bytes; + } + } + + panic!("failed to find an undecodable compressed Edwards-Y encoding"); +} + #[cfg(feature = "std")] #[test] fn test_verify_dalek_matches_legacy_edge_cases() { diff --git a/curve25519/solana-ed25519/src/short_weierstrass.rs b/curve25519/solana-ed25519/src/short_weierstrass.rs index 898c6ec..ac0c892 100644 --- a/curve25519/solana-ed25519/src/short_weierstrass.rs +++ b/curve25519/solana-ed25519/src/short_weierstrass.rs @@ -241,6 +241,7 @@ mod tests { use crate::edwards::EdwardsPoint; use crate::field::FieldElement; use crate::scalar::Scalar; + use crate::traits::Identity; use rand::Rng; fn sw_scalar_mul(point: &SwPoint, scalar: &Scalar) -> SwPoint { @@ -413,4 +414,41 @@ mod tests { Some(SwPoint::Identity) ); } + + #[test] + fn sw_affine_bytes_round_trip_valid_points() { + let mut rng = rand::thread_rng(); + + for _ in 0..32 { + let scalar = random_scalar(&mut rng); + let point = constants::ED25519_BASEPOINT_POINT * scalar; + let sw = SwPoint::from_edwards(&point); + let encoded = sw.to_affine_le_bytes(); + + assert_eq!( + SwPoint::from_affine_le_bytes(encoded.0, encoded.1), + Some(sw) + ); + } + } + + #[test] + fn sw_affine_bytes_reject_off_curve_input() { + let x = FieldElement::ZERO.to_bytes(); + let y = FieldElement::ONE.to_bytes(); + + assert!(SwPoint::from_affine_le_bytes(x, y).is_none()); + } + + #[test] + fn sw_identity_is_additive_neutral_element() { + let base = SwPoint::from_edwards(&constants::ED25519_BASEPOINT_POINT); + + assert_eq!(SwPoint::identity().add(&base), base); + assert_eq!(base.add(&SwPoint::identity()), base); + assert_eq!( + SwPoint::identity().to_edwards(), + Some(EdwardsPoint::identity()) + ); + } }