Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions cryptography/src/ed25519/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ const PRIVATE_KEY_LENGTH: usize = 32;
const PUBLIC_KEY_LENGTH: usize = 32;
const SIGNATURE_LENGTH: usize = 64;

// Group order l = 2^252 + 27742317777372353535851937790883648493, little-endian.
const L: [u8; 32] = [
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10,
];

fn is_canonical_s(s: &[u8; 32]) -> bool {
// Constant-time s < L via subtraction with borrow (little-endian, LSB first).
// borrow is 1 when the subtraction at position i underflowed, 0 otherwise.
// After all 32 bytes, a non-zero borrow means s < L.
let mut borrow: u16 = 0;
for i in 0..32 {
let diff = (s[i] as u16).wrapping_sub(L[i] as u16).wrapping_sub(borrow);
borrow = (diff >> 8) & 1;
}
borrow != 0
}

/// Ed25519 Private Key.
#[derive(Clone, Debug)]
pub struct PrivateKey {
Expand Down Expand Up @@ -260,6 +279,10 @@ impl Read for Signature {

fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let raw = <[u8; Self::SIZE]>::read(buf)?;
let s: &[u8; 32] = raw[32..].try_into().unwrap();
if !is_canonical_s(s) {
return Err(CodecError::Invalid(CURVE_NAME, "non-canonical S"));
}
Ok(Self { raw })
}
}
Expand Down Expand Up @@ -781,12 +804,26 @@ mod tests {
}

#[test]
fn test_high_s_fails() {
let (_, public_key, message, signature) = vector_1();
fn test_high_s_rejected_at_decode() {
let (_, _, _, signature) = vector_1();
let mut bad_signature = signature.to_vec();
bad_signature[63] |= 0x80; // make S non-canonical
let bad_signature = Signature::decode(bad_signature.as_ref()).unwrap();
assert!(!public_key.verify_inner(None, &message, &bad_signature));
bad_signature[63] |= 0x80; // set high bit of S — makes S >= 2^255 > l
assert!(Signature::decode(bad_signature.as_ref()).is_err());
}

#[test]
fn test_s_equal_to_l_rejected_at_decode() {
let mut bad_signature = vec![0u8; SIGNATURE_LENGTH];
bad_signature[32..].copy_from_slice(&L);
assert!(Signature::decode(bad_signature.as_ref()).is_err());
}

#[test]
fn test_s_above_l_rejected_at_decode() {
let mut bad_signature = vec![0u8; SIGNATURE_LENGTH];
bad_signature[32..].copy_from_slice(&L);
bad_signature[32] = bad_signature[32].wrapping_add(1);
assert!(Signature::decode(bad_signature.as_ref()).is_err());
}

#[test]
Expand Down