mod field;
mod scalar;
mod util;
#[cfg(any(feature = "test-vectors", test))]
pub mod test_vectors;
pub use self::scalar::Scalar;
use core::convert::TryInto;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use elliptic_curve::generic_array::GenericArray;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use super::{NistP256, PublicKey};
use elliptic_curve::weierstrass::{CompressedCurvePoint, UncompressedCurvePoint};
use field::{FieldElement, MODULUS};
const CURVE_EQUATION_A: FieldElement = FieldElement::zero()
.subtract(&FieldElement::one())
.subtract(&FieldElement::one())
.subtract(&FieldElement::one());
const CURVE_EQUATION_B: FieldElement = FieldElement([
0xd89c_df62_29c4_bddf,
0xacf0_05cd_7884_3090,
0xe5a2_20ab_f721_2ed6,
0xdc30_061d_0487_4834,
]);
#[derive(Clone, Copy, Debug)]
pub struct AffinePoint {
x: FieldElement,
y: FieldElement,
}
impl ConditionallySelectable for AffinePoint {
fn conditional_select(a: &AffinePoint, b: &AffinePoint, choice: Choice) -> AffinePoint {
AffinePoint {
x: FieldElement::conditional_select(&a.x, &b.x, choice),
y: FieldElement::conditional_select(&a.y, &b.y, choice),
}
}
}
impl ConstantTimeEq for AffinePoint {
fn ct_eq(&self, other: &AffinePoint) -> Choice {
self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y)
}
}
impl PartialEq for AffinePoint {
fn eq(&self, other: &AffinePoint) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for AffinePoint {}
impl AffinePoint {
pub fn generator() -> AffinePoint {
AffinePoint {
x: FieldElement::from_bytes([
0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4,
0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45,
0xd8, 0x98, 0xc2, 0x96,
])
.unwrap(),
y: FieldElement::from_bytes([
0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0x0f,
0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68,
0x37, 0xbf, 0x51, 0xf5,
])
.unwrap(),
}
}
pub fn from_pubkey(pubkey: &PublicKey) -> CtOption<Self> {
match pubkey {
PublicKey::Compressed(point) => {
let bytes = point.as_bytes();
let y_is_odd = Choice::from(bytes[0] & 0x01);
let x = FieldElement::from_bytes(bytes[1..33].try_into().unwrap());
x.and_then(|x| {
let alpha = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B;
let beta = alpha.sqrt();
beta.map(|beta| {
let y = FieldElement::conditional_select(
&(MODULUS - &beta),
&beta,
!(beta.is_odd() ^ y_is_odd),
);
AffinePoint { x, y }
})
})
}
PublicKey::Uncompressed(point) => {
let bytes = point.as_bytes();
let x = FieldElement::from_bytes(bytes[1..33].try_into().unwrap());
let y = FieldElement::from_bytes(bytes[33..65].try_into().unwrap());
x.and_then(|x| {
y.and_then(|y| {
let lhs = y * &y;
let rhs = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B;
CtOption::new(AffinePoint { x, y }, lhs.ct_eq(&rhs))
})
})
}
}
}
pub fn to_compressed_pubkey(&self) -> CompressedCurvePoint<NistP256> {
let mut encoded = [0; 33];
encoded[0] = if self.y.is_odd().into() { 0x03 } else { 0x02 };
encoded[1..33].copy_from_slice(&self.x.to_bytes());
CompressedCurvePoint::from_bytes(GenericArray::clone_from_slice(&encoded[..]))
.expect("we encoded it correctly")
}
pub fn to_uncompressed_pubkey(&self) -> UncompressedCurvePoint<NistP256> {
let mut encoded = [0; 65];
encoded[0] = 0x04;
encoded[1..33].copy_from_slice(&self.x.to_bytes());
encoded[33..65].copy_from_slice(&self.y.to_bytes());
UncompressedCurvePoint::from_bytes(GenericArray::clone_from_slice(&encoded[..]))
.expect("we encoded it correctly")
}
}
impl Neg for AffinePoint {
type Output = AffinePoint;
fn neg(self) -> Self::Output {
AffinePoint {
x: self.x,
y: -self.y,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ProjectivePoint {
x: FieldElement,
y: FieldElement,
z: FieldElement,
}
impl From<AffinePoint> for ProjectivePoint {
fn from(p: AffinePoint) -> Self {
ProjectivePoint {
x: p.x,
y: p.y,
z: FieldElement::one(),
}
}
}
impl ConditionallySelectable for ProjectivePoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
ProjectivePoint {
x: FieldElement::conditional_select(&a.x, &b.x, choice),
y: FieldElement::conditional_select(&a.y, &b.y, choice),
z: FieldElement::conditional_select(&a.z, &b.z, choice),
}
}
}
impl ConstantTimeEq for ProjectivePoint {
fn ct_eq(&self, other: &Self) -> Choice {
self.to_affine().ct_eq(&other.to_affine())
}
}
impl PartialEq for ProjectivePoint {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl ProjectivePoint {
pub const fn identity() -> ProjectivePoint {
ProjectivePoint {
x: FieldElement::zero(),
y: FieldElement::one(),
z: FieldElement::zero(),
}
}
pub fn generator() -> ProjectivePoint {
AffinePoint::generator().into()
}
pub fn to_affine(&self) -> CtOption<AffinePoint> {
self.z.invert().map(|zinv| AffinePoint {
x: self.x * &zinv,
y: self.y * &zinv,
})
}
fn neg(&self) -> ProjectivePoint {
ProjectivePoint {
x: self.x,
y: self.y.neg(),
z: self.z,
}
}
fn add(&self, other: &ProjectivePoint) -> ProjectivePoint {
let xx = self.x * &other.x; let yy = self.y * &other.y; let zz = self.z * &other.z; let xy_pairs = ((self.x + &self.y) * &(other.x + &other.y)) - &(xx + &yy); let yz_pairs = ((self.y + &self.z) * &(other.y + &other.z)) - &(yy + &zz); let xz_pairs = ((self.x + &self.z) * &(other.x + &other.z)) - &(xx + &zz);
let bzz_part = xz_pairs - &(CURVE_EQUATION_B * &zz); let bzz3_part = bzz_part.double() + &bzz_part; let yy_m_bzz3 = yy - &bzz3_part; let yy_p_bzz3 = yy + &bzz3_part;
let zz3 = zz.double() + &zz; let bxz_part = (CURVE_EQUATION_B * &xz_pairs) - &(zz3 + &xx); let bxz3_part = bxz_part.double() + &bxz_part; let xx3_m_zz3 = xx.double() + &xx - &zz3;
ProjectivePoint {
x: (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), y: (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), z: (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), }
}
fn add_mixed(&self, other: &AffinePoint) -> ProjectivePoint {
let xx = self.x * &other.x; let yy = self.y * &other.y; let xy_pairs = ((self.x + &self.y) * &(other.x + &other.y)) - &(xx + &yy); let yz_pairs = (other.y * &self.z) + &self.y; let xz_pairs = (other.x * &self.z) + &self.x;
let bz_part = xz_pairs - &(CURVE_EQUATION_B * &self.z); let bz3_part = bz_part.double() + &bz_part; let yy_m_bzz3 = yy - &bz3_part; let yy_p_bzz3 = yy + &bz3_part;
let z3 = self.z.double() + &self.z; let bxz_part = (CURVE_EQUATION_B * &xz_pairs) - &(z3 + &xx); let bxz3_part = bxz_part.double() + &bxz_part; let xx3_m_zz3 = xx.double() + &xx - &z3;
ProjectivePoint {
x: (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), y: (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), z: (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), }
}
pub fn double(&self) -> ProjectivePoint {
let xx = self.x.square(); let yy = self.y.square(); let zz = self.z.square(); let xy2 = (self.x * &self.y).double(); let xz2 = (self.x * &self.z).double();
let bzz_part = (CURVE_EQUATION_B * &zz) - &xz2; let bzz3_part = bzz_part.double() + &bzz_part; let yy_m_bzz3 = yy - &bzz3_part; let yy_p_bzz3 = yy + &bzz3_part; let y_frag = yy_p_bzz3 * &yy_m_bzz3; let x_frag = yy_m_bzz3 * &xy2;
let zz3 = zz.double() + &zz; let bxz2_part = (CURVE_EQUATION_B * &xz2) - &(zz3 + &xx); let bxz6_part = bxz2_part.double() + &bxz2_part; let xx3_m_zz3 = xx.double() + &xx - &zz3;
let y = y_frag + &(xx3_m_zz3 * &bxz6_part); let yz2 = (self.y * &self.z).double(); let x = x_frag - &(bxz6_part * &yz2); let z = (yz2 * &yy).double().double();
ProjectivePoint { x, y, z }
}
fn sub(&self, other: &ProjectivePoint) -> ProjectivePoint {
self.add(&other.neg())
}
fn sub_mixed(&self, other: &AffinePoint) -> ProjectivePoint {
self.add_mixed(&other.neg())
}
fn mul(&self, k: &Scalar) -> ProjectivePoint {
let mut ret = ProjectivePoint::identity();
for limb in k.0.iter().rev() {
for i in (0..64).rev() {
ret = ret.double();
ret.conditional_assign(&(ret + self), Choice::from(((limb >> i) & 1u64) as u8));
}
}
ret
}
}
impl Add<&ProjectivePoint> for &ProjectivePoint {
type Output = ProjectivePoint;
fn add(self, other: &ProjectivePoint) -> ProjectivePoint {
ProjectivePoint::add(self, other)
}
}
impl Add<&ProjectivePoint> for ProjectivePoint {
type Output = ProjectivePoint;
fn add(self, other: &ProjectivePoint) -> ProjectivePoint {
ProjectivePoint::add(&self, other)
}
}
impl AddAssign<ProjectivePoint> for ProjectivePoint {
fn add_assign(&mut self, rhs: ProjectivePoint) {
*self = ProjectivePoint::add(self, &rhs);
}
}
impl AddAssign<&ProjectivePoint> for ProjectivePoint {
fn add_assign(&mut self, rhs: &ProjectivePoint) {
*self = ProjectivePoint::add(self, rhs);
}
}
impl Add<&AffinePoint> for &ProjectivePoint {
type Output = ProjectivePoint;
fn add(self, other: &AffinePoint) -> ProjectivePoint {
ProjectivePoint::add_mixed(self, other)
}
}
impl Add<&AffinePoint> for ProjectivePoint {
type Output = ProjectivePoint;
fn add(self, other: &AffinePoint) -> ProjectivePoint {
ProjectivePoint::add_mixed(&self, other)
}
}
impl AddAssign<AffinePoint> for ProjectivePoint {
fn add_assign(&mut self, rhs: AffinePoint) {
*self = ProjectivePoint::add_mixed(self, &rhs);
}
}
impl Sub<&ProjectivePoint> for &ProjectivePoint {
type Output = ProjectivePoint;
fn sub(self, other: &ProjectivePoint) -> ProjectivePoint {
ProjectivePoint::sub(self, other)
}
}
impl Sub<&ProjectivePoint> for ProjectivePoint {
type Output = ProjectivePoint;
fn sub(self, other: &ProjectivePoint) -> ProjectivePoint {
ProjectivePoint::sub(&self, other)
}
}
impl SubAssign<ProjectivePoint> for ProjectivePoint {
fn sub_assign(&mut self, rhs: ProjectivePoint) {
*self = ProjectivePoint::sub(self, &rhs);
}
}
impl SubAssign<&ProjectivePoint> for ProjectivePoint {
fn sub_assign(&mut self, rhs: &ProjectivePoint) {
*self = ProjectivePoint::sub(self, rhs);
}
}
impl Sub<&AffinePoint> for &ProjectivePoint {
type Output = ProjectivePoint;
fn sub(self, other: &AffinePoint) -> ProjectivePoint {
ProjectivePoint::sub_mixed(self, other)
}
}
impl Sub<&AffinePoint> for ProjectivePoint {
type Output = ProjectivePoint;
fn sub(self, other: &AffinePoint) -> ProjectivePoint {
ProjectivePoint::sub_mixed(&self, other)
}
}
impl SubAssign<AffinePoint> for ProjectivePoint {
fn sub_assign(&mut self, rhs: AffinePoint) {
*self = ProjectivePoint::sub_mixed(self, &rhs);
}
}
impl Mul<&Scalar> for &ProjectivePoint {
type Output = ProjectivePoint;
fn mul(self, other: &Scalar) -> ProjectivePoint {
ProjectivePoint::mul(self, other)
}
}
impl Mul<&Scalar> for ProjectivePoint {
type Output = ProjectivePoint;
fn mul(self, other: &Scalar) -> ProjectivePoint {
ProjectivePoint::mul(&self, other)
}
}
impl MulAssign<Scalar> for ProjectivePoint {
fn mul_assign(&mut self, rhs: Scalar) {
*self = ProjectivePoint::mul(self, &rhs);
}
}
impl MulAssign<&Scalar> for ProjectivePoint {
fn mul_assign(&mut self, rhs: &Scalar) {
*self = ProjectivePoint::mul(self, rhs);
}
}
impl Neg for ProjectivePoint {
type Output = ProjectivePoint;
fn neg(self) -> ProjectivePoint {
ProjectivePoint::neg(&self)
}
}
impl<'a> Neg for &'a ProjectivePoint {
type Output = ProjectivePoint;
fn neg(self) -> ProjectivePoint {
ProjectivePoint::neg(self)
}
}
#[cfg(test)]
mod tests {
use core::convert::TryInto;
use super::{AffinePoint, ProjectivePoint, Scalar, CURVE_EQUATION_A, CURVE_EQUATION_B};
use crate::{
arithmetic::test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS},
PublicKey,
};
const CURVE_EQUATION_A_BYTES: &str =
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC";
const CURVE_EQUATION_B_BYTES: &str =
"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B";
const UNCOMPRESSED_BASEPOINT: &str =
"046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5";
const COMPRESSED_BASEPOINT: &str =
"036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296";
#[test]
fn verify_constants() {
assert_eq!(
hex::encode(CURVE_EQUATION_A.to_bytes()).to_uppercase(),
CURVE_EQUATION_A_BYTES
);
assert_eq!(
hex::encode(CURVE_EQUATION_B.to_bytes()).to_uppercase(),
CURVE_EQUATION_B_BYTES
);
}
#[test]
fn uncompressed_round_trip() {
let pubkey = PublicKey::from_bytes(&hex::decode(UNCOMPRESSED_BASEPOINT).unwrap()).unwrap();
let point = AffinePoint::from_pubkey(&pubkey).unwrap();
assert_eq!(point, AffinePoint::generator());
let res: PublicKey = point.to_uncompressed_pubkey().into();
assert_eq!(res, pubkey);
}
#[test]
fn compressed_round_trip() {
let pubkey = PublicKey::from_bytes(&hex::decode(COMPRESSED_BASEPOINT).unwrap()).unwrap();
let point = AffinePoint::from_pubkey(&pubkey).unwrap();
assert_eq!(point, AffinePoint::generator());
let res: PublicKey = point.to_compressed_pubkey().into();
assert_eq!(res, pubkey);
}
#[test]
fn uncompressed_to_compressed() {
let encoded = PublicKey::from_bytes(&hex::decode(UNCOMPRESSED_BASEPOINT).unwrap()).unwrap();
let res = AffinePoint::from_pubkey(&encoded)
.unwrap()
.to_compressed_pubkey();
assert_eq!(
hex::encode(res.as_bytes()).to_uppercase(),
COMPRESSED_BASEPOINT
);
}
#[test]
fn compressed_to_uncompressed() {
let encoded = PublicKey::from_bytes(&hex::decode(COMPRESSED_BASEPOINT).unwrap()).unwrap();
let res = AffinePoint::from_pubkey(&encoded)
.unwrap()
.to_uncompressed_pubkey();
assert_eq!(
hex::encode(res.as_bytes()).to_uppercase(),
UNCOMPRESSED_BASEPOINT
);
}
#[test]
fn affine_negation() {
let basepoint = AffinePoint::generator();
assert_eq!(-(-basepoint), basepoint);
}
#[test]
fn affine_to_projective() {
let basepoint_affine = AffinePoint::generator();
let basepoint_projective = ProjectivePoint::generator();
assert_eq!(
ProjectivePoint::from(basepoint_affine),
basepoint_projective,
);
assert_eq!(basepoint_projective.to_affine().unwrap(), basepoint_affine);
assert!(bool::from(
ProjectivePoint::identity().to_affine().is_none()
));
}
#[test]
fn projective_identity_addition() {
let identity = ProjectivePoint::identity();
let generator = ProjectivePoint::generator();
assert_eq!(identity + &generator, generator);
assert_eq!(generator + &identity, generator);
}
#[test]
fn projective_mixed_addition() {
let identity = ProjectivePoint::identity();
let basepoint_affine = AffinePoint::generator();
let basepoint_projective = ProjectivePoint::generator();
assert_eq!(identity + &basepoint_affine, basepoint_projective);
assert_eq!(
basepoint_projective + &basepoint_affine,
basepoint_projective + &basepoint_projective
);
}
#[test]
fn test_vector_repeated_add() {
let generator = ProjectivePoint::generator();
let mut p = generator;
for i in 0..ADD_TEST_VECTORS.len() {
let affine = p.to_affine().unwrap();
assert_eq!(
(
hex::encode(affine.x.to_bytes()).to_uppercase().as_str(),
hex::encode(affine.y.to_bytes()).to_uppercase().as_str(),
),
ADD_TEST_VECTORS[i]
);
p = p + &generator;
}
}
#[test]
fn test_vector_repeated_add_mixed() {
let generator = AffinePoint::generator();
let mut p = ProjectivePoint::generator();
for i in 0..ADD_TEST_VECTORS.len() {
let affine = p.to_affine().unwrap();
assert_eq!(
(
hex::encode(affine.x.to_bytes()).to_uppercase().as_str(),
hex::encode(affine.y.to_bytes()).to_uppercase().as_str(),
),
ADD_TEST_VECTORS[i]
);
p = p + &generator;
}
}
#[test]
fn test_vector_double_generator() {
let generator = ProjectivePoint::generator();
let mut p = generator;
for i in 0..2 {
let affine = p.to_affine().unwrap();
assert_eq!(
(
hex::encode(affine.x.to_bytes()).to_uppercase().as_str(),
hex::encode(affine.y.to_bytes()).to_uppercase().as_str(),
),
ADD_TEST_VECTORS[i]
);
p = p.double();
}
}
#[test]
fn projective_add_vs_double() {
let generator = ProjectivePoint::generator();
assert_eq!(generator + &generator, generator.double());
}
#[test]
fn projective_add_and_sub() {
let basepoint_affine = AffinePoint::generator();
let basepoint_projective = ProjectivePoint::generator();
assert_eq!(
(basepoint_projective + &basepoint_projective) - &basepoint_projective,
basepoint_projective
);
assert_eq!(
(basepoint_projective + &basepoint_affine) - &basepoint_affine,
basepoint_projective
);
}
#[test]
fn projective_double_and_sub() {
let generator = ProjectivePoint::generator();
assert_eq!(generator.double() - &generator, generator);
}
#[test]
fn test_vector_scalar_mult() {
let generator = ProjectivePoint::generator();
for (k, coords) in ADD_TEST_VECTORS
.iter()
.enumerate()
.map(|(k, coords)| (Scalar::from(k as u64 + 1), *coords))
.chain(MUL_TEST_VECTORS.iter().cloned().map(|(k, x, y)| {
(
Scalar::from_bytes(hex::decode(k).unwrap()[..].try_into().unwrap()).unwrap(),
(x, y),
)
}))
{
let res = (generator * &k).to_affine().unwrap();
assert_eq!(
(
hex::encode(res.x.to_bytes()).to_uppercase().as_str(),
hex::encode(res.y.to_bytes()).to_uppercase().as_str(),
),
coords,
);
}
}
}