#![cfg(test)]
use super::*;
const NON_CANONICAL_SPELLINGS: usize = 38;
const DALEK_DECODES: usize = 24;
const DALEK_DECODES_NON_WEAK: usize = 20;
fn non_canonical_y(k: u8, sign_bit: bool) -> [u8; 32] {
let mut bytes = CURVE25519_FIELD_MODULUS_LE;
bytes[0] += k;
if sign_bit {
bytes[31] |= 0x80;
}
bytes
}
fn canonical_y(k: u8, sign_bit: bool) -> [u8; 32] {
let mut bytes = [0u8; 32];
bytes[0] = k;
if sign_bit {
bytes[31] |= 0x80;
}
bytes
}
fn non_canonical_spellings() -> impl Iterator<Item = ([u8; 32], [u8; 32])> {
(0u8..=18).flat_map(|k| {
[false, true]
.into_iter()
.map(move |sign| (non_canonical_y(k, sign), canonical_y(k, sign)))
})
}
#[test]
fn dalek_accepts_the_non_canonical_encodings_this_rule_exists_to_refuse() {
let mut decoded = 0;
let mut decoded_non_weak = 0;
let mut seen = 0;
for (non_canonical, canonical) in non_canonical_spellings() {
seen += 1;
assert_ne!(
non_canonical, canonical,
"the two spellings must be different byte strings"
);
let from_non_canonical = ed25519_dalek::VerifyingKey::from_bytes(&non_canonical);
let from_canonical = ed25519_dalek::VerifyingKey::from_bytes(&canonical);
assert_eq!(
from_non_canonical.is_ok(),
from_canonical.is_ok(),
"the two spellings must decode alike, or they are not one point"
);
let (Ok(non_canonical_key), Ok(canonical_key)) = (from_non_canonical, from_canonical)
else {
continue;
};
decoded += 1;
assert_eq!(
non_canonical_key.to_montgomery().to_bytes(),
canonical_key.to_montgomery().to_bytes(),
"the non-canonical spelling must denote the same point as the canonical one"
);
assert_eq!(
non_canonical_key.to_bytes(),
non_canonical,
"dalek preserves the non-canonical spelling verbatim"
);
if !non_canonical_key.is_weak() {
decoded_non_weak += 1;
}
}
assert_eq!(seen, NON_CANONICAL_SPELLINGS, "the sweep must be complete");
assert_eq!(
decoded, DALEK_DECODES,
"ed25519-dalek's acceptance of out-of-range y-coordinates has changed"
);
assert_eq!(
decoded_non_weak, DALEK_DECODES_NON_WEAK,
"the set this rule newly refuses has changed size"
);
}
#[test]
fn is_canonical_y_coordinate_is_exclusive_at_the_modulus() {
let mut modulus_minus_one = CURVE25519_FIELD_MODULUS_LE;
modulus_minus_one[0] -= 1;
for sign_bit in [false, true] {
let with_sign = |mut bytes: [u8; 32]| {
if sign_bit {
bytes[31] |= 0x80;
}
bytes
};
assert!(is_canonical_y_coordinate(&with_sign([0u8; 32])), "y = 0");
assert!(
is_canonical_y_coordinate(&with_sign(canonical_y(1, false))),
"y = 1"
);
assert!(
is_canonical_y_coordinate(&with_sign(modulus_minus_one)),
"y = p - 1 is the largest canonical y"
);
assert!(
!is_canonical_y_coordinate(&with_sign(CURVE25519_FIELD_MODULUS_LE)),
"y = p is a non-canonical encoding of zero and must be refused"
);
}
}
#[test]
fn is_usable_ed25519_public_key_refuses_every_non_canonical_spelling() {
let mut refused = 0;
let mut refused_that_dalek_would_accept = 0;
for (non_canonical, _canonical) in non_canonical_spellings() {
assert!(
!is_usable_ed25519_public_key(&non_canonical),
"a non-canonical y-coordinate was accepted as a key: {}",
crate::hex_lower(&non_canonical)
);
refused += 1;
if let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&non_canonical)
&& !key.is_weak()
{
refused_that_dalek_would_accept += 1;
}
}
assert_eq!(
refused, NON_CANONICAL_SPELLINGS,
"every spelling must have been tried"
);
assert_eq!(
refused_that_dalek_would_accept, DALEK_DECODES_NON_WEAK,
"this rule must be the only thing refusing these, or it proves nothing"
);
}
#[test]
fn is_usable_ed25519_public_key_refuses_the_all_zero_key() {
let all_zero = [0u8; 32];
assert!(
is_canonical_y_coordinate(&all_zero),
"y = 0 is canonical; this key must be refused by the small-order clause"
);
assert!(
ed25519_dalek::VerifyingKey::from_bytes(&all_zero).is_ok(),
"dalek decodes the all-zero key, so decoding is not what refuses it either"
);
assert!(!is_usable_ed25519_public_key(&all_zero));
}
#[test]
fn is_usable_ed25519_public_key_refuses_the_identity_point() {
let identity = canonical_y(1, false);
assert!(is_canonical_y_coordinate(&identity), "y = 1 is canonical");
assert!(
ed25519_dalek::VerifyingKey::from_bytes(&identity).is_ok(),
"dalek decodes the identity point"
);
assert!(!is_usable_ed25519_public_key(&identity));
let mut order_two = CURVE25519_FIELD_MODULUS_LE;
order_two[0] -= 1;
assert!(is_canonical_y_coordinate(&order_two));
assert!(!is_usable_ed25519_public_key(&order_two));
}
#[test]
fn is_usable_ed25519_public_key_accepts_real_keys() {
let mut accepted = 0;
for seed in 0u8..16 {
let public_key = identity_from_seed([seed; 32]).public_key_bytes();
assert!(
is_usable_ed25519_public_key(&public_key),
"a freshly derived public key must be usable"
);
accepted += 1;
}
assert_eq!(accepted, 16, "the control must have fired");
}
#[test]
fn verify_refuses_every_non_canonical_public_key_spelling() {
let id = identity_from_seed([31u8; 32]);
let signature = id.sign(b"msg");
let mut refused = 0;
for (non_canonical, _canonical) in non_canonical_spellings() {
assert!(
matches!(
Ed25519Identity::verify(&non_canonical, b"msg", &signature),
Err(TrustError::InvalidSignature)
),
"verify accepted a non-canonically encoded public key: {}",
crate::hex_lower(&non_canonical)
);
refused += 1;
}
assert_eq!(refused, NON_CANONICAL_SPELLINGS);
}
#[test]
fn the_narrowing_is_unreachable_through_verify() {
let mut compared = 0;
for seed in 0u8..4 {
let id = identity_from_seed([70 + seed; 32]);
let message: &[u8] = b"a message somebody actually signed";
let signature = id.sign(message);
for (non_canonical, _canonical) in non_canonical_spellings() {
let before = ed25519_dalek::VerifyingKey::from_bytes(&non_canonical)
.is_ok_and(|key| key.verify_strict(message, &signature.into()).is_ok());
let after = Ed25519Identity::verify(&non_canonical, message, &signature).is_ok();
assert!(
!before,
"a non-canonical key that the OLD path accepted now exists — the \
narrowing is reachable and this change is no longer behaviour-preserving \
on constructible input: {}",
crate::hex_lower(&non_canonical)
);
assert_eq!(before, after, "old and new paths must agree");
compared += 1;
}
}
assert_eq!(
compared,
4 * NON_CANONICAL_SPELLINGS,
"the comparison must have fired for every spelling under every signature"
);
}
#[test]
fn verify_still_accepts_every_key_the_predicate_accepts() {
let mut verified = 0;
for seed in 0u8..16 {
let id = identity_from_seed([seed; 32]);
let public_key = id.public_key_bytes();
assert!(is_usable_ed25519_public_key(&public_key));
let signature = id.sign(b"the message");
Ed25519Identity::verify(&public_key, b"the message", &signature)
.expect("a genuine signature under a usable key must still verify");
verified += 1;
}
assert_eq!(verified, 16, "the control must have fired");
}