Skip to content
Closed
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coverage:
status:
patch: off
project: off
16 changes: 16 additions & 0 deletions elliptic-curve-crate/src/coordinates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Generic coordinate system support

use crate::ScalarBytes;
use generic_array::ArrayLength;

/// Trait for obtaining the coordinates of an affine point
pub trait AffineCoordinates {
/// Size of a byte array representing an affine coordinate
type ScalarSize: ArrayLength<u8>;

/// x-coordinate
fn x(&self) -> ScalarBytes<Self::ScalarSize>;

/// y-coordinate
fn y(&self) -> ScalarBytes<Self::ScalarSize>;
}
Comment on lines +6 to +16
Copy link
Contributor

@str4d str4d Jun 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This abstraction directly exposes the coordinates instead of the effect of using them. Making it a separate trait is better than having it be part of the core traits, but it still implies that coordinates are an API surface that is supported and encouraged.

I would prefer instead e.g. an EcdsaPrimitive trait that requires implementing the inner part of the ECDSA algorithm that requires coordinates, and then EcdsaCurve: EcdsaPrimitive that provides the full algorithm, which users can import without exposing the inner API unnecessarily. This is similar to the rsa::PrivateKey: rsa::DecryptionPrimitive split.

Separately, using ScalarBytes for the coordinates strongly encourages type errors during implementation of the trait. The coordinates are field elements, and scalars (of the affine point's curve) are field elements (of a different field), but the coordinates are not scalars (in the sense that elliptic curve scalars are generally understood).

10 changes: 6 additions & 4 deletions elliptic-curve-crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@
#[cfg(feature = "std")]
extern crate std;

pub use generic_array::{self, typenum::consts};

pub mod coordinates;
pub mod error;
pub mod secret_key;

pub use generic_array::{self, typenum::consts};
pub use subtle;

// TODO(tarcieri): other curve forms
#[cfg(feature = "weierstrass")]
pub mod weierstrass;

pub use self::{error::Error, secret_key::SecretKey};
pub use self::{coordinates::AffineCoordinates, error::Error, secret_key::SecretKey};

/// Byte array containing a serialized scalar value
/// Byte array containing a serialized scalar value (i.e. an integer)
pub type ScalarBytes<Size> = generic_array::GenericArray<u8, Size>;
22 changes: 21 additions & 1 deletion elliptic-curve-crate/src/weierstrass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ pub mod curve;
pub mod point;
pub mod public_key;

pub use curve::{Curve, ScalarBytes};
pub use curve::Curve;
pub use point::{
CompressedCurvePoint, CompressedPointSize, UncompressedCurvePoint, UncompressedPointSize,
};
pub use public_key::PublicKey;

use crate::{consts::U1, coordinates::AffineCoordinates, ScalarBytes};
use core::ops::Add;
use generic_array::ArrayLength;
use subtle::{ConditionallySelectable, CtOption};

/// Fixed-base scalar multiplication
pub trait FixedBaseScalarMul: Curve
where
<Self::ScalarSize as Add>::Output: Add<U1>,
CompressedPointSize<Self::ScalarSize>: ArrayLength<u8>,
UncompressedPointSize<Self::ScalarSize>: ArrayLength<u8>,
{
/// Affine point type for this elliptic curve
type Point: AffineCoordinates + ConditionallySelectable + Default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed-base scalar multiplication doesn't require exposing affine coordinates, so this is a more restrictive abstraction than necessary. I suspect we can address the type bounds issue in another way.


/// Multiply the given scalar by the generator point for this elliptic
/// curve.
fn mul_base(scalar: &ScalarBytes<Self::ScalarSize>) -> CtOption<Self::Point>;
}
3 changes: 0 additions & 3 deletions elliptic-curve-crate/src/weierstrass/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,5 @@ pub trait Curve: Clone + Debug + Default + Eq + Ord + Send + Sync {
type ScalarSize: ArrayLength<u8> + Add + Add<U1> + Eq + Ord + Unsigned;
}

/// Alias for [`ScalarBytes`] type for a given Weierstrass curve
pub type ScalarBytes<C> = crate::ScalarBytes<<C as Curve>::ScalarSize>;

/// Alias for [`SecretKey`] type for a given Weierstrass curve
pub type SecretKey<C> = crate::secret_key::SecretKey<<C as Curve>::ScalarSize>;
41 changes: 38 additions & 3 deletions elliptic-curve-crate/src/weierstrass/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
//! <https://www.secg.org/sec1-v2.pdf>

use super::Curve;
use crate::coordinates::AffineCoordinates;
use core::ops::Add;
use generic_array::{typenum::U1, ArrayLength, GenericArray};
use generic_array::{
typenum::{Unsigned, U1},
ArrayLength, GenericArray,
};

/// Size of a compressed elliptic curve point for the given curve when
/// serialized using `Elliptic-Curve-Point-to-Octet-String` encoding
Expand Down Expand Up @@ -39,7 +43,23 @@ impl<C: Curve> CompressedCurvePoint<C>
where
CompressedPointSize<C::ScalarSize>: ArrayLength<u8>,
{
/// Create a new compressed elliptic curve point
/// Compress and serialize an affine point on an elliptic curve
pub fn from_affine_point<P>(affine_point: P) -> Self
where
P: AffineCoordinates,
{
// Is the y-coordinate odd in the SEC-1 sense: `self mod 2 == 1`?
let is_y_odd = affine_point.y().as_ref().last().expect("last byte") & 1 == 1;

let mut encoded = GenericArray::default();
encoded[0] = if is_y_odd { 0x03 } else { 0x02 };
encoded[1..].copy_from_slice(&affine_point.x());

Self::from_bytes(encoded).expect("we encoded it correctly")
}

/// Deserialize a compressed elliptic curve point from the SEC-1 compressed
/// encoding (`Elliptic-Curve-Point-to-Octet-String`)
pub fn from_bytes<B>(into_bytes: B) -> Option<Self>
where
B: Into<GenericArray<u8, CompressedPointSize<C::ScalarSize>>>,
Expand Down Expand Up @@ -114,7 +134,22 @@ where
<C::ScalarSize as Add>::Output: Add<U1>,
UncompressedPointSize<C::ScalarSize>: ArrayLength<u8>,
{
/// Create a new uncompressed elliptic curve point
/// Serialize an affine point on an elliptic curve
pub fn from_affine_point<P>(affine_point: P) -> Self
where
P: AffineCoordinates,
{
let scalar_size = C::ScalarSize::to_usize();
let mut encoded = GenericArray::default();
encoded[0] = 0x04;
encoded[1..(scalar_size + 1)].copy_from_slice(&affine_point.x());
encoded[(scalar_size + 1)..].copy_from_slice(&affine_point.y());

UncompressedCurvePoint::from_bytes(encoded).expect("we encoded it correctly")
}

/// Deserialize a compressed elliptic curve point from the SEC-1 uncompressed
/// encoding (`Elliptic-Curve-Point-to-Octet-String`)
pub fn from_bytes<B>(into_bytes: B) -> Option<Self>
where
B: Into<GenericArray<u8, UncompressedPointSize<C::ScalarSize>>>,
Expand Down
35 changes: 32 additions & 3 deletions elliptic-curve-crate/src/weierstrass/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
//! Public keys for Weierstrass curves: wrapper around compressed or
//! uncompressed elliptic curve points.

use super::point::{
CompressedCurvePoint, CompressedPointSize, UncompressedCurvePoint, UncompressedPointSize,
};
use super::Curve;
use super::{
point::{
CompressedCurvePoint, CompressedPointSize, UncompressedCurvePoint, UncompressedPointSize,
},
FixedBaseScalarMul,
};
use crate::SecretKey;
use core::fmt::{self, Debug};
use core::ops::Add;
use generic_array::{
typenum::{Unsigned, U1},
ArrayLength, GenericArray,
};
use subtle::CtOption;

/// Size of an untagged point for given elliptic curve.
// TODO(tarcieri): const generics
Expand Down Expand Up @@ -101,6 +106,30 @@ where
}
}

impl<C: Curve> PublicKey<C>
where
C: FixedBaseScalarMul,
<C::ScalarSize as Add>::Output: Add<U1>,
CompressedPointSize<C::ScalarSize>: ArrayLength<u8>,
UncompressedPointSize<C::ScalarSize>: ArrayLength<u8>,
{
/// Compute the [`PublicKey`] for the provided [`SecretKey`].
///
/// The `compress` flag requests point compression.
pub fn from_secret_key(
secret_key: &SecretKey<C::ScalarSize>,
compress: bool,
) -> CtOption<Self> {
C::mul_base(secret_key.secret_scalar()).map(|affine_point| {
if compress {
CompressedCurvePoint::from_affine_point(affine_point).into()
} else {
UncompressedCurvePoint::from_affine_point(affine_point).into()
}
})
}
}

impl<C: Curve> AsRef<[u8]> for PublicKey<C>
where
<C::ScalarSize as Add>::Output: Add<U1>,
Expand Down
6 changes: 3 additions & 3 deletions k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ path = "../elliptic-curve-crate"
default-features = false
features = ["weierstrass"]

[dependencies.subtle]
version = "2.2.2"
[dependencies.zeroize]
version = "1"
optional = true
default-features = false

Expand All @@ -29,7 +29,7 @@ fiat-crypto = "0.1.0"

[features]
default = ["arithmetic", "std"]
arithmetic = ["subtle"]
arithmetic = []
test-vectors = []
std = ["elliptic-curve/std"]

Expand Down
95 changes: 74 additions & 21 deletions k256/src/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ 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 crate::{CompressedCurvePoint, PublicKey, UncompressedCurvePoint};
use elliptic_curve::{
consts::U32,
coordinates::AffineCoordinates,
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
weierstrass::FixedBaseScalarMul,
};

use crate::{CompressedCurvePoint, PublicKey, ScalarBytes, Secp256k1, UncompressedCurvePoint};
use field::{FieldElement, MODULUS};

/// b = 7 in Montgomery form (aR mod p, where R = 2**256.
Expand Down Expand Up @@ -47,6 +51,12 @@ impl ConstantTimeEq for AffinePoint {
}
}

impl Default for AffinePoint {
fn default() -> AffinePoint {
AffinePoint::generator()
}
}

impl PartialEq for AffinePoint {
fn eq(&self, other: &AffinePoint) -> bool {
self.ct_eq(other).into()
Expand Down Expand Up @@ -124,25 +134,47 @@ impl AffinePoint {
}
}

/// Returns the SEC-1 compressed encoding of this point.
pub fn to_compressed_pubkey(&self) -> CompressedCurvePoint {
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());
/// Returns a [`PublicKey`] with the SEC-1 compressed encoding of this point.
pub fn to_compressed_pubkey(&self) -> PublicKey {
PublicKey::Compressed(self.clone().into())
}

/// Returns a [`PublicKey`] with the SEC-1 uncompressed encoding of this point.
pub fn to_uncompressed_pubkey(&self) -> PublicKey {
PublicKey::Uncompressed(self.clone().into())
}
}

impl AffineCoordinates for AffinePoint {
type ScalarSize = U32;

fn x(&self) -> ScalarBytes {
self.x.into()
}

fn y(&self) -> ScalarBytes {
self.y.into()
}
}

CompressedCurvePoint::from_bytes(GenericArray::clone_from_slice(&encoded[..]))
.expect("we encoded it correctly")
impl From<AffinePoint> for CompressedCurvePoint {
fn from(point: AffinePoint) -> CompressedCurvePoint {
CompressedCurvePoint::from_affine_point(point)
}
}

/// Returns the SEC-1 uncompressed encoding of this point.
pub fn to_uncompressed_pubkey(&self) -> UncompressedCurvePoint {
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());
impl From<AffinePoint> for UncompressedCurvePoint {
fn from(point: AffinePoint) -> UncompressedCurvePoint {
UncompressedCurvePoint::from_affine_point(point)
}
}

UncompressedCurvePoint::from_bytes(GenericArray::clone_from_slice(&encoded[..]))
.expect("we encoded it correctly")
impl FixedBaseScalarMul for Secp256k1 {
type Point = AffinePoint;

fn mul_base(scalar_bytes: &ScalarBytes) -> CtOption<Self::Point> {
Scalar::from_bytes((*scalar_bytes).into())
.and_then(|scalar| (&ProjectivePoint::generator() * &scalar).to_affine())
}
}

Expand Down Expand Up @@ -492,9 +524,13 @@ mod tests {

use super::{AffinePoint, ProjectivePoint, Scalar, CURVE_EQUATION_B};
use crate::{
arithmetic::test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS},
PublicKey,
arithmetic::test_vectors::{
group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS},
mul_base::MUL_BASE_TEST_VECTORS,
},
PublicKey, ScalarBytes, Secp256k1, UncompressedCurvePoint,
};
use elliptic_curve::weierstrass::FixedBaseScalarMul;

const CURVE_EQUATION_B_BYTES: &str =
"0000000000000000000000000000000000000000000000000000000000000007";
Expand Down Expand Up @@ -723,4 +759,21 @@ mod tests {
);
}
}

#[test]
fn fixed_base_scalar_mul() {
for vector in MUL_BASE_TEST_VECTORS {
let m = hex::decode(&vector.m).unwrap();
let x = hex::decode(&vector.x).unwrap();
let y = hex::decode(&vector.y).unwrap();

let point = UncompressedCurvePoint::from(
Secp256k1::mul_base(ScalarBytes::from_slice(&m)).unwrap(),
)
.into_bytes();

assert_eq!(x, &point[1..=32]);
assert_eq!(y, &point[33..]);
}
}
}
9 changes: 8 additions & 1 deletion k256/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Field arithmetic modulo p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1

use crate::ScalarBytes;
use core::convert::TryInto;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

#[cfg(feature = "getrandom")]
use getrandom::getrandom;
Expand Down Expand Up @@ -555,6 +556,12 @@ impl<'a> Neg for &'a FieldElement {
}
}

impl From<FieldElement> for ScalarBytes {
fn from(fe: FieldElement) -> Self {
fe.to_bytes().into()
}
}

#[cfg(test)]
mod tests {
use fiat_crypto::secp256k1_64::{
Expand Down
Loading