Skip to content

Commit

Permalink
k256+p256: impl PrimeCurve traits (#350)
Browse files Browse the repository at this point in the history
Implements traits from the `group` crate for prime order curves:

- AffinePoint
  - GroupEncoding
  - PrimeCurveAffine
- ProjectivePoint
  - GroupEncoding
  - PrimeGroup
  - PrimeCurve
  • Loading branch information
tarcieri authored Jun 8, 2021
1 parent fcd8b54 commit dff5a67
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 102 deletions.
138 changes: 94 additions & 44 deletions k256/src/arithmetic/affine.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Affine points
use super::{FieldElement, ProjectivePoint, CURVE_EQUATION_B};
use crate::{EncodedPoint, FieldBytes, NonZeroScalar, Secp256k1};
use crate::{CompressedPoint, EncodedPoint, FieldBytes, Scalar, Secp256k1};
use core::ops::{Mul, Neg};
use elliptic_curve::{
generic_array::arr,
group::{prime::PrimeCurveAffine, GroupEncoding},
sec1::{self, FromEncodedPoint, ToEncodedPoint},
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
weierstrass::DecompressPoint,
Expand All @@ -27,9 +28,21 @@ pub struct AffinePoint {
pub(super) infinity: Choice,
}

impl AffinePoint {
impl PrimeCurveAffine for AffinePoint {
type Scalar = Scalar;
type Curve = ProjectivePoint;

/// Returns the identity of the group: the point at infinity.
fn identity() -> Self {
Self {
x: FieldElement::zero(),
y: FieldElement::zero(),
infinity: Choice::from(1),
}
}

/// Returns the base point of SECP256k1.
pub fn generator() -> AffinePoint {
fn generator() -> Self {
// SECP256k1 basepoint in affine coordinates:
// x = 79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
// y = 483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
Expand All @@ -50,19 +63,15 @@ impl AffinePoint {
}
}

/// Returns the identity of the group: the point at infinity.
pub fn identity() -> AffinePoint {
Self {
x: FieldElement::zero(),
y: FieldElement::zero(),
infinity: Choice::from(1),
}
}

/// Is this point the identity point?
pub fn is_identity(&self) -> Choice {
fn is_identity(&self) -> Choice {
self.infinity
}

/// Convert to curve representation.
fn to_curve(&self) -> ProjectivePoint {
ProjectivePoint::from(*self)
}
}

impl ConditionallySelectable for AffinePoint {
Expand Down Expand Up @@ -97,6 +106,40 @@ impl PartialEq for AffinePoint {

impl Eq for AffinePoint {}

impl AffinePoint {
/// Decode this point from a SEC1-encoded point.
pub(crate) fn decode(encoded_point: &EncodedPoint) -> CtOption<Self> {
match encoded_point.coordinates() {
sec1::Coordinates::Identity => CtOption::new(Self::identity(), 1.into()),
sec1::Coordinates::Compact { .. } => {
// TODO(tarcieri): add decompaction support
CtOption::new(Self::default(), 0.into())
}
sec1::Coordinates::Compressed { x, y_is_odd } => {
AffinePoint::decompress(x, Choice::from(y_is_odd as u8))
}
sec1::Coordinates::Uncompressed { x, y } => {
let x = FieldElement::from_bytes(x);
let y = FieldElement::from_bytes(y);

x.and_then(|x| {
y.and_then(|y| {
// Check that the point is on the curve
let lhs = (y * &y).negate(1);
let rhs = x * &x * &x + &CURVE_EQUATION_B;
let point = AffinePoint {
x,
y,
infinity: Choice::from(0),
};
CtOption::new(point, (lhs + &rhs).normalizes_to_zero())
})
})
}
}
}
}

impl DecompressPoint<Secp256k1> for AffinePoint {
fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption<Self> {
FieldElement::from_bytes(x_bytes).and_then(|x| {
Expand All @@ -120,39 +163,35 @@ impl DecompressPoint<Secp256k1> for AffinePoint {
}
}

impl GroupEncoding for AffinePoint {
type Repr = CompressedPoint;

fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
let tag = bytes[0];
let y_is_odd = tag.ct_eq(&sec1::Tag::CompressedOddY.into());
let is_compressed_point = y_is_odd | tag.ct_eq(&sec1::Tag::CompressedEvenY.into());
Self::decompress(FieldBytes::from_slice(&bytes[1..]), y_is_odd)
.and_then(|point| CtOption::new(point, is_compressed_point))
}

fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// No unchecked conversion possible for compressed points
Self::from_bytes(bytes)
}

fn to_bytes(&self) -> Self::Repr {
CompressedPoint::clone_from_slice(self.to_encoded_point(true).as_bytes())
}
}

impl FromEncodedPoint<Secp256k1> for AffinePoint {
/// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded [`AffinePoint`].
///
/// # Returns
///
/// `None` value if `encoded_point` is not on the secp256k1 curve.
fn from_encoded_point(encoded_point: &EncodedPoint) -> Option<Self> {
match encoded_point.coordinates() {
sec1::Coordinates::Identity => Some(Self::identity()),
sec1::Coordinates::Compact { .. } => None, // TODO(tarcieri): add support
sec1::Coordinates::Compressed { x, y_is_odd } => {
AffinePoint::decompress(x, Choice::from(y_is_odd as u8)).into()
}
sec1::Coordinates::Uncompressed { x, y } => {
let x = FieldElement::from_bytes(x);
let y = FieldElement::from_bytes(y);

x.and_then(|x| {
y.and_then(|y| {
// Check that the point is on the curve
let lhs = (y * &y).negate(1);
let rhs = x * &x * &x + &CURVE_EQUATION_B;
let point = AffinePoint {
x,
y,
infinity: Choice::from(0),
};
CtOption::new(point, (lhs + &rhs).normalizes_to_zero())
})
})
.into()
}
}
Self::decode(encoded_point).into()
}
}

Expand All @@ -177,11 +216,19 @@ impl From<AffinePoint> for EncodedPoint {
}
}

impl Mul<NonZeroScalar> for AffinePoint {
type Output = AffinePoint;
impl Mul<Scalar> for AffinePoint {
type Output = ProjectivePoint;

fn mul(self, scalar: Scalar) -> ProjectivePoint {
ProjectivePoint::from(self) * scalar
}
}

impl Mul<&Scalar> for AffinePoint {
type Output = ProjectivePoint;

fn mul(self, scalar: NonZeroScalar) -> Self {
(ProjectivePoint::from(self) * scalar.as_ref()).to_affine()
fn mul(self, scalar: &Scalar) -> ProjectivePoint {
ProjectivePoint::from(self) * scalar
}
}

Expand Down Expand Up @@ -209,7 +256,10 @@ impl Zeroize for AffinePoint {
mod tests {
use super::AffinePoint;
use crate::EncodedPoint;
use elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use elliptic_curve::{
group::prime::PrimeCurveAffine,
sec1::{FromEncodedPoint, ToEncodedPoint},
};
use hex_literal::hex;

const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!(
Expand Down
43 changes: 38 additions & 5 deletions k256/src/arithmetic/projective.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
//! Projective points
use super::{AffinePoint, FieldElement, Scalar, CURVE_EQUATION_B_SINGLE};
use crate::{EncodedPoint, Secp256k1};
use crate::{CompressedPoint, EncodedPoint, Secp256k1};
use core::{
iter::Sum,
ops::{Add, AddAssign, Neg, Sub, SubAssign},
};
use elliptic_curve::{
group::{ff::Field, Curve, Group},
group::{
ff::Field,
prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup},
Curve, Group, GroupEncoding,
},
rand_core::RngCore,
sec1::FromEncodedPoint,
subtle::{Choice, ConditionallySelectable, ConstantTimeEq},
sec1::{FromEncodedPoint, ToEncodedPoint},
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
ProjectiveArithmetic,
};

Expand Down Expand Up @@ -58,6 +62,12 @@ impl FromEncodedPoint<Secp256k1> for ProjectivePoint {
}
}

impl ToEncodedPoint<Secp256k1> for ProjectivePoint {
fn to_encoded_point(&self, compress: bool) -> EncodedPoint {
self.to_affine().to_encoded_point(compress)
}
}

impl ConditionallySelectable for ProjectivePoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
ProjectivePoint {
Expand Down Expand Up @@ -279,6 +289,25 @@ impl Group for ProjectivePoint {
}
}

impl GroupEncoding for ProjectivePoint {
type Repr = CompressedPoint;

fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
<AffinePoint as GroupEncoding>::from_bytes(bytes).map(|point| point.into())
}

fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// No unchecked conversion possible for compressed points
Self::from_bytes(bytes)
}

fn to_bytes(&self) -> Self::Repr {
CompressedPoint::clone_from_slice(self.to_affine().to_encoded_point(true).as_bytes())
}
}

impl PrimeGroup for ProjectivePoint {}

impl Curve for ProjectivePoint {
type AffineRepr = AffinePoint;

Expand All @@ -287,6 +316,10 @@ impl Curve for ProjectivePoint {
}
}

impl PrimeCurve for ProjectivePoint {
type Affine = AffinePoint;
}

impl Default for ProjectivePoint {
fn default() -> Self {
Self::identity()
Expand Down Expand Up @@ -472,7 +505,7 @@ mod tests {
test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS},
Scalar,
};
use elliptic_curve::group::ff::PrimeField;
use elliptic_curve::group::{ff::PrimeField, prime::PrimeCurveAffine};

#[test]
fn affine_to_projective() {
Expand Down
4 changes: 1 addition & 3 deletions k256/src/ecdsa/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ impl VerifyingKey {
/// Serialize this [`VerifyingKey`] as a SEC1-encoded bytestring
/// (with point compression applied)
pub fn to_bytes(&self) -> CompressedPoint {
let mut result = [0u8; 33];
result.copy_from_slice(EncodedPoint::from(self).as_ref());
result
CompressedPoint::clone_from_slice(EncodedPoint::from(self).as_bytes())
}
}

Expand Down
6 changes: 4 additions & 2 deletions k256/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub use arithmetic::FieldElement;
#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))]
pub use elliptic_curve::pkcs8;

use elliptic_curve::{consts::U33, generic_array::GenericArray};

/// K-256 (secp256k1) elliptic curve.
///
/// Specified in Certicom's SECG in "SEC 2: Recommended Elliptic Curve Domain Parameters":
Expand Down Expand Up @@ -117,8 +119,8 @@ impl elliptic_curve::AlgorithmParameters for Secp256k1 {
const OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new("1.3.132.0.10");
}

/// Compressed SEC1-encoded secp256k1 (K-256) point.
pub type CompressedPoint = [u8; 33];
/// Compressed SEC1-encoded secp256k1 (K-256) curve point.
pub type CompressedPoint = GenericArray<u8, U33>;

/// secp256k1 (K-256) field element serialized as bytes.
///
Expand Down
Loading

0 comments on commit dff5a67

Please sign in to comment.