Skip to content

Commit

Permalink
Fix curv25519 coordinates (ZenGo-X#162)
Browse files Browse the repository at this point in the history
* Add test vectors for curve25519 coordinates

* Make (q-1)/4 actually (q-1)/4 and not (q-3)/4

* Fix curve25519 coordinates by clearing the parity bit
  • Loading branch information
elichai authored Aug 22, 2022
1 parent 8b3b7a5 commit bc626bb
Showing 1 changed file with 83 additions and 14 deletions.
97 changes: 83 additions & 14 deletions src/elliptic/curves/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ impl ECPoint for Ed25519Point {
}

fn from_coords(x: &BigInt, y: &BigInt) -> Result<Ed25519Point, NotOnCurve> {
let expected_x = xrecover(y);
let is_odd = x.is_odd();
let expected_x = xrecover(y, is_odd);
if &expected_x != x {
return Err(NotOnCurve);
}
Expand All @@ -340,26 +341,43 @@ impl ECPoint for Ed25519Point {
padding
}
};
// BigInt uses Big-Endian but the 25519 libraries use Little-Endian, so we reverse the bytes
padded.reverse();
// All curve25519 libs serialize a point by taking the `y` coordinate,
// and putting the is_odd flag of the `x` coordinate in the most significant bit.
padded[31] |= (is_odd as u8) << 7;

Self::deserialize(&padded).map_err(|_e| NotOnCurve)
}

fn x_coord(&self) -> Option<BigInt> {
let y = self.y_coord().unwrap();
Some(xrecover(&y))
self.coords().map(|c| c.x)
}

fn y_coord(&self) -> Option<BigInt> {
let mut bytes = self.ge.to_bytes().to_vec();
let mut bytes = self.ge.to_bytes();
// According to https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
// the most significant bit in a point encoding says if x is even or odd
// so we clear that bit as it's not part of the field element.
bytes[31] &= 0b01111111;

// reverse because BigInt is Big-Endian while the field element is Little-Endian
bytes.reverse();
Some(BigInt::from_bytes(&bytes))
}

fn coords(&self) -> Option<PointCoords> {
let y = self
.y_coord()
.expect("coordinates are always defined for edwards curves");
Some(PointCoords { x: xrecover(&y), y })
let mut bytes = self.ge.to_bytes();
// According to https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
// the most significant bit in a point encoding says if x is odd or even
let is_odd = (bytes[31] >> 7) == 1;
bytes[31] &= 0b01111111;

// reverse because BigInt is Big-Endian while the field element is Little-Endian
bytes.reverse();
let y = BigInt::from_bytes(&bytes);
let x = xrecover(&y, is_odd);
Some(PointCoords { x, y })
}

fn serialize_compressed(&self) -> GenericArray<u8, Self::CompressedPointLength> {
Expand Down Expand Up @@ -479,7 +497,7 @@ impl ECPoint for Ed25519Point {

#[allow(clippy::many_single_char_names)]
//helper function, based on https://ed25519.cr.yp.to/python/ed25519.py
fn xrecover(y_coor: &BigInt) -> BigInt {
fn xrecover(y_coor: &BigInt, is_odd: bool) -> BigInt {
// let d = "37095705934669439343138083508754565189542113879843219016388785533085940283555";
// let d_bn = BigInt::from(d.as_bytes());
let q = BigInt::from(2u32).pow(255u32) - BigInt::from(19u32);
Expand All @@ -498,15 +516,15 @@ fn xrecover(y_coor: &BigInt) -> BigInt {

let mut x = expmod(&x_sqr, &q_plus_3_div_8, &q);
if BigInt::mod_sub(&(x.clone() * x.clone()), &x_sqr, &q) != BigInt::zero() {
let q_minus_1_div_4 = (q.clone() - BigInt::from(3i32)) / BigInt::from(4i32);
let q_minus_1_div_4 = (q.clone() - BigInt::from(1i32)) / BigInt::from(4i32);
let i = expmod(&BigInt::from(2i32), &q_minus_1_div_4, &q);
x = BigInt::mod_mul(&x, &i, &q);
}
if BigInt::modulus(&x, &BigInt::from(2i32)) != BigInt::zero() {
x = q - x.clone();
if x.is_odd() != is_odd {
q - x
} else {
x
}

x
}

//helper function, based on https://ed25519.cr.yp.to/python/ed25519.py
Expand All @@ -523,3 +541,54 @@ fn expmod(b: &BigInt, e: &BigInt, m: &BigInt) -> BigInt {
}
t
}

#[cfg(test)]
mod tests {
use crate::arithmetic::traits::Converter;
use crate::elliptic::curves::{Ed25519, Point, Scalar};
use crate::BigInt;

#[test]
fn test_vectors_coordinates() {
// These coordinates were generated in dalek-curve25519 using the following code:
// let mut p = super::constants::ED25519_BASEPOINT_POINT;
// for _ in 0..15 {
// let recip = p.Z.invert();
// let x = &p.X * &recip;
// let y = &p.Y * &recip;
// println!("({:?}, {:?}),", x.to_bytes(), y.to_bytes());
// p = p.double();
// }
#[rustfmt::skip]
let dalek = [
([26, 213, 37, 143, 96, 45, 86, 201, 178, 167, 37, 149, 96, 199, 44, 105, 92, 220, 214, 253, 49, 226, 164, 192, 254, 83, 110, 205, 211, 54, 105, 33], [88, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102]),
([14, 206, 67, 40, 78, 161, 197, 131, 95, 164, 215, 21, 69, 142, 13, 8, 172, 231, 51, 24, 125, 59, 4, 61, 108, 4, 90, 159, 76, 56, 171, 54], [201, 163, 248, 106, 174, 70, 95, 14, 86, 81, 56, 100, 81, 15, 57, 151, 86, 31, 162, 201, 232, 94, 162, 29, 194, 41, 35, 9, 243, 205, 96, 34]),
([112, 248, 201, 196, 87, 166, 58, 73, 71, 21, 206, 147, 193, 158, 115, 26, 249, 32, 53, 122, 184, 212, 37, 131, 70, 241, 207, 86, 219, 168, 61, 32], [47, 17, 50, 202, 97, 171, 56, 223, 240, 15, 47, 234, 50, 40, 242, 76, 108, 113, 213, 128, 133, 184, 14, 71, 225, 149, 21, 203, 39, 232, 208, 71]),
([200, 132, 165, 8, 188, 253, 135, 59, 153, 139, 105, 128, 123, 198, 58, 235, 147, 207, 78, 248, 92, 45, 134, 66, 182, 113, 215, 151, 95, 225, 66, 103], [180, 185, 55, 252, 169, 91, 47, 30, 147, 228, 30, 98, 252, 60, 120, 129, 143, 243, 138, 102, 9, 111, 173, 110, 121, 115, 229, 201, 0, 6, 211, 33]),
([248, 249, 40, 108, 109, 89, 178, 89, 116, 35, 191, 231, 51, 141, 87, 9, 145, 156, 36, 8, 21, 43, 226, 184, 238, 58, 229, 39, 6, 134, 164, 35], [235, 39, 103, 193, 55, 171, 122, 216, 39, 156, 7, 142, 255, 17, 106, 176, 120, 110, 173, 58, 46, 15, 152, 159, 114, 195, 127, 130, 242, 150, 150, 112]),
([38, 79, 126, 151, 246, 64, 221, 79, 252, 82, 120, 249, 144, 49, 3, 230, 125, 86, 57, 11, 29, 86, 130, 133, 249, 26, 66, 23, 105, 108, 207, 57], [105, 210, 6, 58, 79, 57, 45, 249, 56, 64, 140, 76, 231, 5, 18, 180, 120, 139, 248, 192, 236, 147, 222, 122, 107, 206, 44, 225, 14, 169, 52, 68]),
([11, 164, 60, 176, 15, 122, 81, 241, 120, 214, 217, 106, 253, 70, 232, 184, 168, 121, 29, 135, 249, 144, 242, 156, 19, 41, 248, 11, 32, 100, 250, 5], [38, 9, 218, 23, 175, 149, 214, 251, 106, 25, 13, 110, 94, 18, 241, 153, 76, 170, 168, 111, 121, 134, 244, 114, 40, 0, 38, 249, 234, 158, 25, 61]),
([135, 221, 207, 240, 91, 73, 162, 93, 64, 122, 35, 38, 164, 122, 131, 138, 183, 139, 210, 26, 191, 234, 2, 36, 8, 95, 123, 169, 177, 190, 157, 55], [252, 134, 75, 8, 238, 231, 160, 253, 33, 69, 9, 52, 193, 97, 50, 35, 252, 155, 85, 72, 83, 153, 247, 99, 208, 153, 206, 1, 224, 159, 235, 40]),
([86, 165, 194, 12, 221, 188, 184, 32, 109, 87, 97, 181, 251, 120, 181, 212, 73, 84, 144, 38, 193, 203, 233, 230, 191, 236, 29, 78, 237, 7, 126, 94], [199, 246, 108, 86, 49, 32, 20, 14, 168, 217, 39, 193, 154, 61, 27, 125, 14, 38, 211, 129, 170, 235, 245, 107, 121, 2, 241, 81, 92, 117, 85, 15]),
([10, 52, 205, 130, 60, 51, 9, 84, 210, 97, 57, 48, 155, 253, 239, 33, 38, 212, 112, 250, 238, 249, 49, 51, 115, 132, 208, 179, 129, 191, 236, 46], [232, 147, 139, 0, 100, 247, 156, 184, 116, 224, 230, 73, 72, 77, 77, 72, 182, 25, 161, 64, 183, 217, 50, 65, 124, 130, 55, 161, 45, 220, 210, 84]),
([104, 43, 74, 91, 213, 199, 81, 145, 29, 225, 42, 75, 196, 71, 241, 188, 122, 179, 203, 200, 182, 124, 172, 144, 5, 253, 243, 249, 82, 58, 17, 107], [61, 193, 39, 243, 89, 67, 149, 144, 197, 150, 121, 245, 244, 149, 101, 41, 6, 156, 81, 5, 24, 218, 184, 46, 121, 126, 105, 89, 113, 1, 235, 26]),
([247, 23, 19, 189, 251, 188, 210, 236, 69, 179, 21, 49, 233, 175, 130, 132, 61, 40, 198, 252, 17, 245, 65, 181, 139, 211, 18, 118, 82, 231, 26, 60], [78, 54, 17, 7, 162, 21, 32, 81, 196, 42, 195, 98, 139, 94, 127, 166, 15, 249, 69, 133, 108, 17, 134, 183, 126, 229, 215, 249, 195, 145, 28, 5]),
([234, 214, 222, 41, 58, 0, 185, 2, 89, 203, 38, 196, 186, 153, 177, 151, 47, 142, 0, 146, 38, 79, 82, 235, 71, 27, 137, 139, 36, 192, 19, 125], [213, 32, 91, 128, 166, 128, 32, 149, 195, 233, 159, 142, 135, 158, 30, 158, 122, 199, 204, 117, 108, 165, 241, 145, 26, 168, 1, 44, 171, 118, 169, 89]),
([222, 201, 177, 49, 16, 22, 170, 53, 20, 106, 212, 181, 52, 130, 113, 210, 74, 93, 154, 31, 83, 38, 60, 229, 142, 141, 51, 127, 255, 169, 213, 23], [137, 175, 246, 164, 100, 213, 16, 224, 29, 173, 239, 68, 189, 218, 131, 172, 122, 168, 240, 28, 7, 249, 195, 67, 108, 63, 183, 211, 135, 34, 2, 115]),
([138, 75, 231, 56, 188, 218, 194, 176, 133, 225, 74, 254, 45, 68, 132, 203, 32, 107, 45, 191, 17, 156, 215, 190, 211, 62, 95, 191, 104, 188, 168, 7], [1, 137, 40, 34, 106, 120, 170, 41, 3, 200, 116, 149, 3, 62, 220, 189, 7, 19, 168, 162, 32, 45, 179, 24, 112, 66, 253, 122, 196, 215, 73, 114]),
];

let mut p = Point::<Ed25519>::generator().to_point();
for (mut x, mut y) in dalek {
// coordinates are in little endian, but BigInt uses Big Endian.
x.reverse();
y.reverse();
let x_big = BigInt::from_bytes(&x);
let y_big = BigInt::from_bytes(&y);
let coordinates = p.coords().unwrap();
assert_eq!(coordinates.y, y_big);
assert_eq!(coordinates.x, x_big);
p = p * Scalar::from(2u16);
}
}
}

0 comments on commit bc626bb

Please sign in to comment.