From a7011d22c144f381f6c3250cfd8ff2f43695d1f6 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 1 Aug 2022 20:22:09 -0600 Subject: [PATCH 1/4] weierstrass: initial crate Implements `AffinePoint` and `ProjectivePoint` generically for both the `p256` and `p384` crates, with the potential to use it for `bp256`/`bp384`/`p521`. --- .github/workflows/weierstrass.yml | 71 +++ Cargo.lock | 13 +- Cargo.toml | 3 +- p256/Cargo.toml | 5 +- p256/src/arithmetic.rs | 127 +++--- p256/src/arithmetic/affine.rs | 500 --------------------- p256/src/arithmetic/field.rs | 59 ++- p256/src/arithmetic/hash2curve.rs | 443 +++++++++--------- p256/src/arithmetic/projective.rs | 691 ---------------------------- p256/src/arithmetic/scalar.rs | 6 +- p256/src/ecdh.rs | 8 +- p256/src/lib.rs | 3 +- p256/tests/affine.rs | 121 +++++ p256/tests/projective.rs | 160 +++++++ p384/Cargo.toml | 5 +- p384/src/arithmetic.rs | 76 +++- p384/src/arithmetic/affine.rs | 415 ----------------- p384/src/arithmetic/field.rs | 35 ++ p384/src/arithmetic/projective.rs | 673 ---------------------------- p384/src/arithmetic/scalar.rs | 6 +- p384/src/ecdh.rs | 8 +- p384/src/lib.rs | 2 +- p384/tests/affine.rs | 82 ++++ p384/tests/projective.rs | 142 ++++++ weierstrass/CHANGELOG.md | 5 + weierstrass/Cargo.toml | 24 + weierstrass/LICENSE-APACHE | 201 +++++++++ weierstrass/LICENSE-MIT | 25 ++ weierstrass/README.md | 87 ++++ weierstrass/src/affine.rs | 386 ++++++++++++++++ weierstrass/src/lib.rs | 45 ++ weierstrass/src/projective.rs | 719 ++++++++++++++++++++++++++++++ 32 files changed, 2524 insertions(+), 2622 deletions(-) create mode 100644 .github/workflows/weierstrass.yml delete mode 100644 p256/src/arithmetic/affine.rs delete mode 100644 p256/src/arithmetic/projective.rs create mode 100644 p256/tests/affine.rs create mode 100644 p256/tests/projective.rs delete mode 100644 p384/src/arithmetic/affine.rs delete mode 100644 p384/src/arithmetic/projective.rs create mode 100644 p384/tests/affine.rs create mode 100644 p384/tests/projective.rs create mode 100644 weierstrass/CHANGELOG.md create mode 100644 weierstrass/Cargo.toml create mode 100644 weierstrass/LICENSE-APACHE create mode 100644 weierstrass/LICENSE-MIT create mode 100644 weierstrass/README.md create mode 100644 weierstrass/src/affine.rs create mode 100644 weierstrass/src/lib.rs create mode 100644 weierstrass/src/projective.rs diff --git a/.github/workflows/weierstrass.yml b/.github/workflows/weierstrass.yml new file mode 100644 index 00000000..389eb2a6 --- /dev/null +++ b/.github/workflows/weierstrass.yml @@ -0,0 +1,71 @@ +name: weierstrass + +on: + pull_request: + paths: + - ".github/workflows/weierstrass.yml" + - "weierstrass/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: weierstrass + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + RUSTDOCFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.57.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.57.0 # MSRV + - stable + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo check --all-features + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --all-features + + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + - run: cargo doc --all-features diff --git a/Cargo.lock b/Cargo.lock index 4b9882dc..7c197b61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,7 +611,7 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "p256" -version = "0.11.1" +version = "0.12.0-pre" dependencies = [ "blobby", "criterion", @@ -622,11 +622,12 @@ dependencies = [ "rand_core", "serdect", "sha2", + "weierstrass", ] [[package]] name = "p384" -version = "0.11.2" +version = "0.12.0-pre" dependencies = [ "blobby", "criterion", @@ -637,6 +638,7 @@ dependencies = [ "rand_core", "serdect", "sha2", + "weierstrass", ] [[package]] @@ -1190,6 +1192,13 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weierstrass" +version = "0.0.0" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index f35c52e9..88e5ae12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ members = [ "k256", "p256", "p384", - "p521" + "p521", + "weierstrass" ] [profile.dev] diff --git a/p256/Cargo.toml b/p256/Cargo.toml index f1c7a040..cc8fe284 100644 --- a/p256/Cargo.toml +++ b/p256/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p256" -version = "0.11.1" +version = "0.12.0-pre" description = """ Pure Rust implementation of the NIST P-256 (a.k.a. secp256r1, prime256v1) elliptic curve with support for ECDH, ECDSA signing/verification, and general @@ -17,7 +17,8 @@ edition = "2021" rust-version = "1.57" [dependencies] -elliptic-curve = { version = "0.12.3", default-features = false, features = ["hazmat", "sec1"] } +elliptic-curve = { version = "0.12", default-features = false, features = ["hazmat", "sec1"] } +weierstrass = { version = "0", path = "../weierstrass" } # optional dependencies ecdsa-core = { version = "0.14", package = "ecdsa", optional = true, default-features = false, features = ["der"] } diff --git a/p256/src/arithmetic.rs b/p256/src/arithmetic.rs index 22b63c2a..879ac9bd 100644 --- a/p256/src/arithmetic.rs +++ b/p256/src/arithmetic.rs @@ -1,63 +1,82 @@ //! Pure Rust implementation of group operations on secp256r1. -pub(crate) mod affine; pub(crate) mod field; #[cfg(feature = "hash2curve")] mod hash2curve; -pub(crate) mod projective; pub(crate) mod scalar; pub(crate) mod util; -use affine::AffinePoint; -use field::{FieldElement, MODULUS}; -use projective::ProjectivePoint; -use scalar::Scalar; - -/// a = -3 -const CURVE_EQUATION_A: FieldElement = FieldElement::ZERO - .subtract(&FieldElement::ONE) - .subtract(&FieldElement::ONE) - .subtract(&FieldElement::ONE); - -/// b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B -const CURVE_EQUATION_B: FieldElement = FieldElement([ - 0xd89c_df62_29c4_bddf, - 0xacf0_05cd_7884_3090, - 0xe5a2_20ab_f721_2ed6, - 0xdc30_061d_0487_4834, -]); - -#[cfg(test)] -mod tests { - use super::{CURVE_EQUATION_A, CURVE_EQUATION_B}; - use hex_literal::hex; - - const CURVE_EQUATION_A_BYTES: &[u8] = - &hex!("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"); - - const CURVE_EQUATION_B_BYTES: &[u8] = - &hex!("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"); - - #[test] - fn verify_constants() { - assert_eq!( - CURVE_EQUATION_A.to_bytes().as_slice(), - CURVE_EQUATION_A_BYTES - ); - assert_eq!( - CURVE_EQUATION_B.to_bytes().as_slice(), - CURVE_EQUATION_B_BYTES - ); - } - - #[test] - fn generate_secret_key() { - use crate::SecretKey; - use elliptic_curve::rand_core::OsRng; - - let key = SecretKey::random(&mut OsRng); - - // Sanity check - assert!(!key.to_be_bytes().iter().all(|b| *b == 0)) - } +use self::{field::FieldElement, scalar::Scalar}; +use crate::NistP256; +use elliptic_curve::{ + AffineArithmetic, PrimeCurveArithmetic, ProjectiveArithmetic, ScalarArithmetic, +}; +use weierstrass::WeierstrassCurve; + +/// Elliptic curve point in affine coordinates. +pub type AffinePoint = weierstrass::AffinePoint; + +/// Elliptic curve point in projective coordinates. +pub type ProjectivePoint = weierstrass::ProjectivePoint; + +impl WeierstrassCurve for NistP256 { + type FieldElement = FieldElement; + + const ZERO: FieldElement = FieldElement::ZERO; + const ONE: FieldElement = FieldElement::ONE; + + /// a = -3 + const EQUATION_A: FieldElement = FieldElement::ZERO + .sub(&FieldElement::ONE) + .sub(&FieldElement::ONE) + .sub(&FieldElement::ONE); + + /// b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B + const EQUATION_B: FieldElement = FieldElement([ + 0xd89c_df62_29c4_bddf, + 0xacf0_05cd_7884_3090, + 0xe5a2_20ab_f721_2ed6, + 0xdc30_061d_0487_4834, + ]); + + /// Base point of P-256. + /// + /// Defined in FIPS 186-4 § D.1.2.3: + /// + /// ```text + /// Gₓ = 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296 + /// Gᵧ = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5 + /// ``` + const GENERATOR: (FieldElement, FieldElement) = ( + FieldElement([ + 0xf4a1_3945_d898_c296, + 0x7703_7d81_2deb_33a0, + 0xf8bc_e6e5_63a4_40f2, + 0x6b17_d1f2_e12c_4247, + ]) + .to_montgomery(), + FieldElement([ + 0xcbb6_4068_37bf_51f5, + 0x2bce_3357_6b31_5ece, + 0x8ee7_eb4a_7c0f_9e16, + 0x4fe3_42e2_fe1a_7f9b, + ]) + .to_montgomery(), + ); +} + +impl AffineArithmetic for NistP256 { + type AffinePoint = AffinePoint; +} + +impl ProjectiveArithmetic for NistP256 { + type ProjectivePoint = ProjectivePoint; +} + +impl PrimeCurveArithmetic for NistP256 { + type CurveGroup = ProjectivePoint; +} + +impl ScalarArithmetic for NistP256 { + type Scalar = Scalar; } diff --git a/p256/src/arithmetic/affine.rs b/p256/src/arithmetic/affine.rs deleted file mode 100644 index 68aa4397..00000000 --- a/p256/src/arithmetic/affine.rs +++ /dev/null @@ -1,500 +0,0 @@ -//! Affine points - -#![allow(clippy::op_ref)] - -use super::{FieldElement, ProjectivePoint, CURVE_EQUATION_A, CURVE_EQUATION_B, MODULUS}; -use crate::{CompressedPoint, EncodedPoint, FieldBytes, NistP256, PublicKey, Scalar}; -use core::ops::{Mul, Neg}; -use elliptic_curve::{ - bigint::Encoding, - group::{prime::PrimeCurveAffine, GroupEncoding}, - sec1::{self, FromEncodedPoint, ToCompactEncodedPoint, ToEncodedPoint}, - subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, - zeroize::DefaultIsZeroes, - AffineArithmetic, AffineXCoordinate, Curve, DecompactPoint, DecompressPoint, Error, Result, -}; - -#[cfg(feature = "serde")] -use serdect::serde::{de, ser, Deserialize, Serialize}; - -impl AffineArithmetic for NistP256 { - type AffinePoint = AffinePoint; -} - -/// NIST P-256 (secp256r1) curve point expressed in affine coordinates. -/// -/// # `serde` support -/// -/// When the `serde` feature of this crate is enabled, the `Serialize` and -/// `Deserialize` traits are impl'd for this type. -/// -/// The serialization uses the [SEC1] `Elliptic-Curve-Point-to-Octet-String` -/// encoding, serialized as binary. -/// -/// When serialized with a text-based format, the SEC1 representation is -/// subsequently hex encoded. -/// -/// [SEC1]: https://www.secg.org/sec1-v2.pdf -#[derive(Clone, Copy, Debug)] -#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] -pub struct AffinePoint { - /// x-coordinate - pub(crate) x: FieldElement, - - /// y-coordinate - pub(crate) y: FieldElement, - - /// Is this point the point at infinity? 0 = no, 1 = yes - /// - /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const` - /// constructors for `IDENTITY` and `GENERATOR`. - pub(super) infinity: u8, -} - -impl AffinePoint { - /// Additive identity of the group: the point at infinity. - pub const IDENTITY: Self = Self { - x: FieldElement::ZERO, - y: FieldElement::ZERO, - infinity: 1, - }; - - /// Base point of P-256. - /// - /// Defined in FIPS 186-4 § D.1.2.3: - /// - /// ```text - /// Gₓ = 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296 - /// Gᵧ = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5 - /// ``` - pub const GENERATOR: Self = Self { - x: FieldElement([ - 0xf4a1_3945_d898_c296, - 0x7703_7d81_2deb_33a0, - 0xf8bc_e6e5_63a4_40f2, - 0x6b17_d1f2_e12c_4247, - ]) - .to_montgomery(), - y: FieldElement([ - 0xcbb6_4068_37bf_51f5, - 0x2bce_3357_6b31_5ece, - 0x8ee7_eb4a_7c0f_9e16, - 0x4fe3_42e2_fe1a_7f9b, - ]) - .to_montgomery(), - infinity: 0, - }; -} - -impl PrimeCurveAffine for AffinePoint { - type Scalar = Scalar; - type Curve = ProjectivePoint; - - fn identity() -> AffinePoint { - Self::IDENTITY - } - - fn generator() -> AffinePoint { - Self::GENERATOR - } - - fn is_identity(&self) -> Choice { - Choice::from(self.infinity) - } - - fn to_curve(&self) -> ProjectivePoint { - ProjectivePoint::from(*self) - } -} - -impl AffineXCoordinate for AffinePoint { - fn x(&self) -> FieldBytes { - self.x.to_bytes() - } -} - -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), - infinity: u8::conditional_select(&a.infinity, &b.infinity, choice), - } - } -} - -impl ConstantTimeEq for AffinePoint { - fn ct_eq(&self, other: &AffinePoint) -> Choice { - self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) & self.infinity.ct_eq(&other.infinity) - } -} - -impl Default for AffinePoint { - fn default() -> Self { - Self::IDENTITY - } -} - -impl DefaultIsZeroes for AffinePoint {} - -impl Eq for AffinePoint {} - -impl PartialEq for AffinePoint { - fn eq(&self, other: &AffinePoint) -> bool { - self.ct_eq(other).into() - } -} - -impl Mul 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: &Scalar) -> ProjectivePoint { - ProjectivePoint::from(self) * scalar - } -} - -impl Neg for AffinePoint { - type Output = AffinePoint; - - fn neg(self) -> Self::Output { - AffinePoint { - x: self.x, - y: -self.y, - infinity: self.infinity, - } - } -} - -impl DecompressPoint for AffinePoint { - fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { - FieldElement::from_bytes(x_bytes).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().ct_eq(&y_is_odd), - ); - - Self { x, y, infinity: 0 } - }) - }) - } -} - -impl GroupEncoding for AffinePoint { - type Repr = CompressedPoint; - - /// NOTE: not constant-time with respect to identity point - fn from_bytes(bytes: &Self::Repr) -> CtOption { - EncodedPoint::from_bytes(bytes) - .map(|point| CtOption::new(point, Choice::from(1))) - .unwrap_or_else(|_| { - // SEC1 identity encoding is technically 1-byte 0x00, but the - // `GroupEncoding` API requires a fixed-width `Repr` - let is_identity = bytes.ct_eq(&Self::Repr::default()); - CtOption::new(EncodedPoint::identity(), is_identity) - }) - .and_then(|point| Self::from_encoded_point(&point)) - } - - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - // No unchecked conversion possible for compressed points - Self::from_bytes(bytes) - } - - fn to_bytes(&self) -> Self::Repr { - let encoded = self.to_encoded_point(true); - let mut result = CompressedPoint::default(); - result[..encoded.len()].copy_from_slice(encoded.as_bytes()); - result - } -} - -impl DecompactPoint for AffinePoint { - fn decompact(x_bytes: &FieldBytes) -> CtOption { - FieldElement::from_bytes(x_bytes).and_then(|x| { - let montgomery_y = (x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B).sqrt(); - montgomery_y.map(|montgomery_y| { - // Convert to canonical form for comparisons - let y = montgomery_y.to_canonical(); - let p_y = MODULUS.subtract(&y); - let (_, borrow) = p_y.informed_subtract(&y); - let recovered_y = if borrow == 0 { y } else { p_y }; - AffinePoint { - x, - y: recovered_y.to_montgomery(), - infinity: 0, - } - }) - }) - } -} - -impl FromEncodedPoint for AffinePoint { - /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded [`AffinePoint`]. - /// - /// # Returns - /// - /// `None` value if `encoded_point` is not on the secp256r1 curve. - fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { - match encoded_point.coordinates() { - sec1::Coordinates::Identity => CtOption::new(Self::identity(), 1.into()), - sec1::Coordinates::Compact { x } => AffinePoint::decompact(x), - 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; - let rhs = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B; - let point = AffinePoint { x, y, infinity: 0 }; - CtOption::new(point, lhs.ct_eq(&rhs)) - }) - }) - } - } - } -} - -impl ToEncodedPoint for AffinePoint { - fn to_encoded_point(&self, compress: bool) -> EncodedPoint { - EncodedPoint::conditional_select( - &EncodedPoint::from_affine_coordinates( - &self.x.to_bytes(), - &self.y.to_bytes(), - compress, - ), - &EncodedPoint::identity(), - self.is_identity(), - ) - } -} - -impl ToCompactEncodedPoint for AffinePoint { - /// Serialize this value as a SEC1 compact [`EncodedPoint`] - fn to_compact_encoded_point(&self) -> CtOption { - // Convert to canonical form for comparisons - let y = self.y.to_canonical(); - let (p_y, borrow) = MODULUS.informed_subtract(&y); - assert_eq!(borrow, 0); - let (_, borrow) = p_y.informed_subtract(&y); - - // Reuse the CompressedPoint type since it's the same size as a compact point - let mut bytes = CompressedPoint::default(); - bytes[0] = sec1::Tag::Compact.into(); - bytes[1..(::UInt::BYTE_SIZE + 1)].copy_from_slice(&self.x.to_bytes()); - CtOption::new( - EncodedPoint::from_bytes(bytes).expect("compact key"), - borrow.ct_eq(&0), - ) - } -} - -impl TryFrom for AffinePoint { - type Error = Error; - - fn try_from(point: EncodedPoint) -> Result { - AffinePoint::try_from(&point) - } -} - -impl TryFrom<&EncodedPoint> for AffinePoint { - type Error = Error; - - fn try_from(point: &EncodedPoint) -> Result { - Option::from(AffinePoint::from_encoded_point(point)).ok_or(Error) - } -} - -impl From for EncodedPoint { - fn from(affine_point: AffinePoint) -> EncodedPoint { - affine_point.to_encoded_point(false) - } -} - -impl From for AffinePoint { - fn from(public_key: PublicKey) -> AffinePoint { - *public_key.as_affine() - } -} - -impl From<&PublicKey> for AffinePoint { - fn from(public_key: &PublicKey) -> AffinePoint { - AffinePoint::from(*public_key) - } -} - -impl TryFrom for PublicKey { - type Error = Error; - - fn try_from(affine_point: AffinePoint) -> Result { - PublicKey::from_affine(affine_point) - } -} - -impl TryFrom<&AffinePoint> for PublicKey { - type Error = Error; - - fn try_from(affine_point: &AffinePoint) -> Result { - PublicKey::try_from(*affine_point) - } -} - -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -impl Serialize for AffinePoint { - fn serialize(&self, serializer: S) -> core::result::Result - where - S: ser::Serializer, - { - self.to_encoded_point(true).serialize(serializer) - } -} - -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -impl<'de> Deserialize<'de> for AffinePoint { - fn deserialize(deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - EncodedPoint::deserialize(deserializer)? - .try_into() - .map_err(de::Error::custom) - } -} - -#[cfg(test)] -mod tests { - use super::AffinePoint; - use crate::EncodedPoint; - use elliptic_curve::{ - group::{prime::PrimeCurveAffine, GroupEncoding}, - sec1::{FromEncodedPoint, ToCompactEncodedPoint, ToEncodedPoint}, - }; - use hex_literal::hex; - - const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!( - "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 - 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5" - ); - - const COMPRESSED_BASEPOINT: &[u8] = - &hex!("036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"); - - // Tag compact with 05 as the first byte, to trigger tag based compaction - const COMPACT_BASEPOINT: &[u8] = - &hex!("058e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01"); - - // Tag uncompact basepoint with 04 as the first byte as it is uncompressed - const UNCOMPACT_BASEPOINT: &[u8] = &hex!( - "048e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c0 - 13ca9d8667de0c07aa71d98b3c8065d2e97ab7bb9cb8776bcc0577a7ac58acd4e" - ); - - #[test] - fn uncompressed_round_trip() { - let pubkey = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - assert_eq!(point, AffinePoint::generator()); - - let res: EncodedPoint = point.into(); - assert_eq!(res, pubkey); - } - - #[test] - fn compressed_round_trip() { - let pubkey = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - assert_eq!(point, AffinePoint::generator()); - - let res: EncodedPoint = point.to_encoded_point(true); - assert_eq!(res, pubkey); - } - - #[test] - fn uncompressed_to_compressed() { - let encoded = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); - - let res = AffinePoint::from_encoded_point(&encoded) - .unwrap() - .to_encoded_point(true); - - assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT); - } - - #[test] - fn compressed_to_uncompressed() { - let encoded = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); - - let res = AffinePoint::from_encoded_point(&encoded) - .unwrap() - .to_encoded_point(false); - - assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT); - } - - #[test] - fn affine_negation() { - let basepoint = AffinePoint::generator(); - assert_eq!(-(-basepoint), basepoint); - } - - #[test] - fn compact_round_trip() { - let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); - assert!(pubkey.is_compact()); - - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - let res = point.to_compact_encoded_point().unwrap(); - assert_eq!(res, pubkey) - } - - #[test] - fn uncompact_to_compact() { - let pubkey = EncodedPoint::from_bytes(UNCOMPACT_BASEPOINT).unwrap(); - assert_eq!(false, pubkey.is_compact()); - - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - let res = point.to_compact_encoded_point().unwrap(); - assert_eq!(res.as_bytes(), COMPACT_BASEPOINT) - } - - #[test] - fn compact_to_uncompact() { - let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); - assert!(pubkey.is_compact()); - - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - // Do not do compact encoding as we want to keep uncompressed point - let res = point.to_encoded_point(false); - assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT); - } - - #[test] - fn identity_encoding() { - // This is technically an invalid SEC1 encoding, but is preferable to panicking. - assert_eq!([0; 33], AffinePoint::IDENTITY.to_bytes().as_slice()); - assert!(bool::from( - AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes()) - .unwrap() - .is_identity() - )) - } -} diff --git a/p256/src/arithmetic/field.rs b/p256/src/arithmetic/field.rs index 8a53ab6b..39921a05 100644 --- a/p256/src/arithmetic/field.rs +++ b/p256/src/arithmetic/field.rs @@ -8,7 +8,7 @@ use crate::{ }; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use elliptic_curve::{ - ff::Field, + ff::{Field, PrimeField}, rand_core::RngCore, subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, zeroize::DefaultIsZeroes, @@ -156,7 +156,7 @@ impl FieldElement { } /// Returns self - rhs mod p - pub const fn subtract(&self, rhs: &Self) -> Self { + pub const fn sub(&self, rhs: &Self) -> Self { let (result, _) = Self::sub_inner( self.0[0], self.0[1], self.0[2], self.0[3], 0, rhs.0[0], rhs.0[1], rhs.0[2], rhs.0[3], 0, @@ -164,13 +164,6 @@ impl FieldElement { result } - /// Returns self - rhs mod p - pub(crate) const fn informed_subtract(&self, rhs: &Self) -> (Self, u64) { - Self::sub_inner( - self.0[0], self.0[1], self.0[2], self.0[3], 0, rhs.0[0], rhs.0[1], rhs.0[2], rhs.0[3], - 0, - ) - } #[inline] #[allow(clippy::too_many_arguments)] const fn sub_inner( @@ -452,6 +445,34 @@ impl Field for FieldElement { } } +impl PrimeField for FieldElement { + type Repr = FieldBytes; + + const NUM_BITS: u32 = 256; + const CAPACITY: u32 = 0; // TODO(tarcieri): bogus! needs real value + const S: u32 = 0; // TODO(tarcieri): bogus! needs real value + + fn from_repr(bytes: FieldBytes) -> CtOption { + Self::from_bytes(&bytes) + } + + fn to_repr(&self) -> FieldBytes { + self.to_bytes() + } + + fn is_odd(&self) -> Choice { + self.is_odd() + } + + fn multiplicative_generator() -> Self { + todo!() + } + + fn root_of_unity() -> Self { + todo!() + } +} + impl ConditionallySelectable for FieldElement { fn conditional_select(a: &FieldElement, b: &FieldElement, choice: Choice) -> FieldElement { FieldElement([ @@ -482,6 +503,14 @@ impl DefaultIsZeroes for FieldElement {} impl Eq for FieldElement {} +impl From for FieldElement { + fn from(n: u64) -> FieldElement { + let mut fe = FieldElement::default(); + fe.0[0] = n; + fe.to_montgomery() + } +} + impl PartialEq for FieldElement { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() @@ -528,7 +557,7 @@ impl Sub for FieldElement { type Output = FieldElement; fn sub(self, other: FieldElement) -> FieldElement { - FieldElement::subtract(&self, &other) + FieldElement::sub(&self, &other) } } @@ -536,7 +565,7 @@ impl Sub<&FieldElement> for FieldElement { type Output = FieldElement; fn sub(self, other: &FieldElement) -> FieldElement { - FieldElement::subtract(&self, other) + FieldElement::sub(&self, other) } } @@ -544,19 +573,19 @@ impl Sub<&FieldElement> for &FieldElement { type Output = FieldElement; fn sub(self, other: &FieldElement) -> FieldElement { - FieldElement::subtract(self, other) + FieldElement::sub(self, other) } } impl SubAssign for FieldElement { fn sub_assign(&mut self, other: FieldElement) { - *self = FieldElement::subtract(self, &other); + *self = FieldElement::sub(self, &other); } } impl SubAssign<&FieldElement> for FieldElement { fn sub_assign(&mut self, other: &FieldElement) { - *self = FieldElement::subtract(self, other); + *self = FieldElement::sub(self, other); } } @@ -746,7 +775,7 @@ mod tests { ) { let a = FieldElement([a0, a1, a2, 0]); let b = FieldElement([b0, b1, b2, 0]); - assert_eq!(a.add(&b).subtract(&a), b); + assert_eq!(a.add(&b).sub(&a), b); } } } diff --git a/p256/src/arithmetic/hash2curve.rs b/p256/src/arithmetic/hash2curve.rs index f5c50b40..7dfaa2fd 100644 --- a/p256/src/arithmetic/hash2curve.rs +++ b/p256/src/arithmetic/hash2curve.rs @@ -1,10 +1,11 @@ -use elliptic_curve::bigint::{ArrayEncoding, U256}; -use elliptic_curve::consts::U48; -use elliptic_curve::generic_array::GenericArray; -use elliptic_curve::group::cofactor::CofactorGroup; -use elliptic_curve::hash2curve::OsswuMap; -use elliptic_curve::hash2curve::{FromOkm, GroupDigest, MapToCurve, OsswuMapParams, Sgn0}; -use elliptic_curve::subtle::{Choice, CtOption}; +use elliptic_curve::{ + bigint::{ArrayEncoding, U256}, + consts::U48, + generic_array::GenericArray, + hash2curve::{FromOkm, GroupDigest, MapToCurve, OsswuMap, OsswuMapParams, Sgn0}, + subtle::Choice, + DecompressPoint, +}; use crate::{AffinePoint, NistP256, ProjectivePoint, Scalar}; @@ -89,28 +90,10 @@ impl MapToCurve for FieldElement { fn map_to_curve(&self) -> Self::Output { let (qx, qy) = self.osswu(); - AffinePoint { - x: qx, - y: qy, - infinity: 0, - } - .into() - } -} - -impl CofactorGroup for ProjectivePoint { - type Subgroup = ProjectivePoint; - - fn clear_cofactor(&self) -> Self::Subgroup { - *self - } - - fn into_subgroup(self) -> CtOption { - CtOption::new(self, 1.into()) - } - - fn is_torsion_free(&self) -> Choice { - 1.into() + // TODO(tarcieri): assert that `qy` is correct? less circuitous conversion? + AffinePoint::decompress(&qx.to_bytes(), qy.is_odd()) + .unwrap() + .into() } } @@ -134,214 +117,226 @@ impl FromOkm for Scalar { } } -#[test] -fn hash_to_curve() { - use elliptic_curve::hash2curve::{self, ExpandMsgXmd}; - use hex_literal::hex; - use sha2::Sha256; - - struct TestVector { - msg: &'static [u8], - p_x: [u8; 32], - p_y: [u8; 32], - u_0: [u8; 32], - u_1: [u8; 32], - q0_x: [u8; 32], - q0_y: [u8; 32], - q1_x: [u8; 32], - q1_y: [u8; 32], - } - - const DST: &[u8] = b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_"; - - const TEST_VECTORS: &[TestVector] = &[ - TestVector { - msg: b"", - p_x: hex!("2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4"), - p_y: hex!("8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415"), - u_0: hex!("ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009"), - u_1: hex!("8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a"), - q0_x: hex!("ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5"), - q0_y: hex!("dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1"), - q1_x: hex!("51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5"), - q1_y: hex!("b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac"), - }, - TestVector { - msg: b"abc", - p_x: hex!("0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f"), - p_y: hex!("5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e"), - u_0: hex!("afe47f2ea2b10465cc26ac403194dfb68b7f5ee865cda61e9f3e07a537220af1"), - u_1: hex!("379a27833b0bfe6f7bdca08e1e83c760bf9a338ab335542704edcd69ce9e46e0"), - q0_x: hex!("5219ad0ddef3cc49b714145e91b2f7de6ce0a7a7dc7406c7726c7e373c58cb48"), - q0_y: hex!("7950144e52d30acbec7b624c203b1996c99617d0b61c2442354301b191d93ecf"), - q1_x: hex!("019b7cb4efcfeaf39f738fe638e31d375ad6837f58a852d032ff60c69ee3875f"), - q1_y: hex!("589a62d2b22357fed5449bc38065b760095ebe6aeac84b01156ee4252715446e"), - }, - TestVector { - msg: b"abcdef0123456789", - p_x: hex!("65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80"), - p_y: hex!("cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3"), - u_0: hex!("0fad9d125a9477d55cf9357105b0eb3a5c4259809bf87180aa01d651f53d312c"), - u_1: hex!("b68597377392cd3419d8fcc7d7660948c8403b19ea78bbca4b133c9d2196c0fb"), - q0_x: hex!("a17bdf2965eb88074bc01157e644ed409dac97cfcf0c61c998ed0fa45e79e4a2"), - q0_y: hex!("4f1bc80c70d411a3cc1d67aeae6e726f0f311639fee560c7f5a664554e3c9c2e"), - q1_x: hex!("7da48bb67225c1a17d452c983798113f47e438e4202219dd0715f8419b274d66"), - q1_y: hex!("b765696b2913e36db3016c47edb99e24b1da30e761a8a3215dc0ec4d8f96e6f9"), - }, - TestVector { - msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", - p_x: hex!("4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d"), - p_y: hex!("98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e"), - u_0: hex!("3bbc30446f39a7befad080f4d5f32ed116b9534626993d2cc5033f6f8d805919"), - u_1: hex!("76bb02db019ca9d3c1e02f0c17f8baf617bbdae5c393a81d9ce11e3be1bf1d33"), - q0_x: hex!("c76aaa823aeadeb3f356909cb08f97eee46ecb157c1f56699b5efebddf0e6398"), - q0_y: hex!("776a6f45f528a0e8d289a4be12c4fab80762386ec644abf2bffb9b627e4352b1"), - q1_x: hex!("418ac3d85a5ccc4ea8dec14f750a3a9ec8b85176c95a7022f391826794eb5a75"), - q1_y: hex!("fd6604f69e9d9d2b74b072d14ea13050db72c932815523305cb9e807cc900aff"), - }, - TestVector { - msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - p_x: hex!("457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5"), - p_y: hex!("ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc"), - u_0: hex!("4ebc95a6e839b1ae3c63b847798e85cb3c12d3817ec6ebc10af6ee51adb29fec"), - u_1: hex!("4e21af88e22ea80156aff790750121035b3eefaa96b425a8716e0d20b4e269ee"), - q0_x: hex!("d88b989ee9d1295df413d4456c5c850b8b2fb0f5402cc5c4c7e815412e926db8"), - q0_y: hex!("bb4a1edeff506cf16def96afff41b16fc74f6dbd55c2210e5b8f011ba32f4f40"), - q1_x: hex!("a281e34e628f3a4d2a53fa87ff973537d68ad4fbc28d3be5e8d9f6a2571c5a4b"), - q1_y: hex!("f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184"), - }, - ]; - - for test_vector in TEST_VECTORS { - // in parts - let mut u = [FieldElement::default(), FieldElement::default()]; - hash2curve::hash_to_field::, FieldElement>( - &[test_vector.msg], - DST, - &mut u, - ) - .unwrap(); - assert_eq!(u[0].to_bytes().as_slice(), test_vector.u_0); - assert_eq!(u[1].to_bytes().as_slice(), test_vector.u_1); - - let q0 = u[0].map_to_curve(); - let aq0 = q0.to_affine(); - assert_eq!(aq0.x.to_bytes().as_slice(), test_vector.q0_x); - assert_eq!(aq0.y.to_bytes().as_slice(), test_vector.q0_y); - - let q1 = u[1].map_to_curve(); - let aq1 = q1.to_affine(); - assert_eq!(aq1.x.to_bytes().as_slice(), test_vector.q1_x); - assert_eq!(aq1.y.to_bytes().as_slice(), test_vector.q1_y); - - let p = q0.clear_cofactor() + q1.clear_cofactor(); - let ap = p.to_affine(); - assert_eq!(ap.x.to_bytes().as_slice(), test_vector.p_x); - assert_eq!(ap.y.to_bytes().as_slice(), test_vector.p_y); - - // complete run - let pt = - NistP256::hash_from_bytes::>(&[test_vector.msg], DST).unwrap(); - let apt = pt.to_affine(); - assert_eq!(apt.x.to_bytes().as_slice(), test_vector.p_x); - assert_eq!(apt.y.to_bytes().as_slice(), test_vector.p_y); - } -} - -/// Taken from . -#[test] -fn hash_to_scalar_voprf() { - use elliptic_curve::hash2curve::ExpandMsgXmd; - use elliptic_curve::Field; +#[cfg(test)] +mod tests { + use crate::{FieldElement, NistP256, Scalar, U256}; + use elliptic_curve::{ + bigint::{ArrayEncoding, NonZero, U384}, + consts::U48, + generic_array::GenericArray, + group::cofactor::CofactorGroup, + hash2curve::{self, ExpandMsgXmd, FromOkm, GroupDigest, MapToCurve}, + sec1::{self, ToEncodedPoint}, + Curve, Field, + }; use hex_literal::hex; + use proptest::{num::u64::ANY, prelude::ProptestConfig, proptest}; use sha2::Sha256; - struct TestVector { - dst: &'static [u8], - key_info: &'static [u8], - seed: &'static [u8], - sk_sm: &'static [u8], - } + #[allow(dead_code)] // TODO(tarcieri): fix commented out code + #[test] + fn hash_to_curve() { + struct TestVector { + msg: &'static [u8], + p_x: [u8; 32], + p_y: [u8; 32], + u_0: [u8; 32], + u_1: [u8; 32], + q0_x: [u8; 32], + q0_y: [u8; 32], + q1_x: [u8; 32], + q1_y: [u8; 32], + } - const TEST_VECTORS: &[TestVector] = &[ - TestVector { - dst: b"DeriveKeyPairVOPRF09-\x00\x00\x03", - key_info: b"test key", - seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), - sk_sm: &hex!("88a91851d93ab3e4f2636babc60d6ce9d1aee2b86dece13fa8590d955a08d987"), - }, - TestVector { - dst: b"DeriveKeyPairVOPRF09-\x01\x00\x03", - key_info: b"test key", - seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), - sk_sm: &hex!("c8a626b52be02b06e9cdb1a05490392938642a30b1451b0cd1be1d3612b336b5"), - }, - TestVector { - dst: b"DeriveKeyPairVOPRF09-\x02\x00\x03", - key_info: b"test key", - seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), - sk_sm: &hex!("b75567bfc40aaaf7735c35c6ad5d55a725c9d42ac66df2e1dbd2027bde289264"), - }, - ]; - - 'outer: for test_vector in TEST_VECTORS { - let key_info_len = u16::try_from(test_vector.key_info.len()) - .unwrap() - .to_be_bytes(); - - for counter in 0_u8..=u8::MAX { - let scalar = NistP256::hash_to_scalar::>( - &[ - test_vector.seed, - &key_info_len, - test_vector.key_info, - &counter.to_be_bytes(), - ], - test_vector.dst, + const DST: &[u8] = b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_"; + + const TEST_VECTORS: &[TestVector] = &[ + TestVector { + msg: b"", + p_x: hex!("2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4"), + p_y: hex!("8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415"), + u_0: hex!("ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009"), + u_1: hex!("8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a"), + q0_x: hex!("ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5"), + q0_y: hex!("dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1"), + q1_x: hex!("51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5"), + q1_y: hex!("b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac"), + }, + TestVector { + msg: b"abc", + p_x: hex!("0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f"), + p_y: hex!("5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e"), + u_0: hex!("afe47f2ea2b10465cc26ac403194dfb68b7f5ee865cda61e9f3e07a537220af1"), + u_1: hex!("379a27833b0bfe6f7bdca08e1e83c760bf9a338ab335542704edcd69ce9e46e0"), + q0_x: hex!("5219ad0ddef3cc49b714145e91b2f7de6ce0a7a7dc7406c7726c7e373c58cb48"), + q0_y: hex!("7950144e52d30acbec7b624c203b1996c99617d0b61c2442354301b191d93ecf"), + q1_x: hex!("019b7cb4efcfeaf39f738fe638e31d375ad6837f58a852d032ff60c69ee3875f"), + q1_y: hex!("589a62d2b22357fed5449bc38065b760095ebe6aeac84b01156ee4252715446e"), + }, + TestVector { + msg: b"abcdef0123456789", + p_x: hex!("65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80"), + p_y: hex!("cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3"), + u_0: hex!("0fad9d125a9477d55cf9357105b0eb3a5c4259809bf87180aa01d651f53d312c"), + u_1: hex!("b68597377392cd3419d8fcc7d7660948c8403b19ea78bbca4b133c9d2196c0fb"), + q0_x: hex!("a17bdf2965eb88074bc01157e644ed409dac97cfcf0c61c998ed0fa45e79e4a2"), + q0_y: hex!("4f1bc80c70d411a3cc1d67aeae6e726f0f311639fee560c7f5a664554e3c9c2e"), + q1_x: hex!("7da48bb67225c1a17d452c983798113f47e438e4202219dd0715f8419b274d66"), + q1_y: hex!("b765696b2913e36db3016c47edb99e24b1da30e761a8a3215dc0ec4d8f96e6f9"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + p_x: hex!("4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d"), + p_y: hex!("98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e"), + u_0: hex!("3bbc30446f39a7befad080f4d5f32ed116b9534626993d2cc5033f6f8d805919"), + u_1: hex!("76bb02db019ca9d3c1e02f0c17f8baf617bbdae5c393a81d9ce11e3be1bf1d33"), + q0_x: hex!("c76aaa823aeadeb3f356909cb08f97eee46ecb157c1f56699b5efebddf0e6398"), + q0_y: hex!("776a6f45f528a0e8d289a4be12c4fab80762386ec644abf2bffb9b627e4352b1"), + q1_x: hex!("418ac3d85a5ccc4ea8dec14f750a3a9ec8b85176c95a7022f391826794eb5a75"), + q1_y: hex!("fd6604f69e9d9d2b74b072d14ea13050db72c932815523305cb9e807cc900aff"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + p_x: hex!("457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5"), + p_y: hex!("ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc"), + u_0: hex!("4ebc95a6e839b1ae3c63b847798e85cb3c12d3817ec6ebc10af6ee51adb29fec"), + u_1: hex!("4e21af88e22ea80156aff790750121035b3eefaa96b425a8716e0d20b4e269ee"), + q0_x: hex!("d88b989ee9d1295df413d4456c5c850b8b2fb0f5402cc5c4c7e815412e926db8"), + q0_y: hex!("bb4a1edeff506cf16def96afff41b16fc74f6dbd55c2210e5b8f011ba32f4f40"), + q1_x: hex!("a281e34e628f3a4d2a53fa87ff973537d68ad4fbc28d3be5e8d9f6a2571c5a4b"), + q1_y: hex!("f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184"), + }, + ]; + + for test_vector in TEST_VECTORS { + // in parts + let mut u = [FieldElement::default(), FieldElement::default()]; + hash2curve::hash_to_field::, FieldElement>( + &[test_vector.msg], + DST, + &mut u, ) .unwrap(); - if !bool::from(scalar.is_zero()) { - assert_eq!(scalar.to_bytes().as_slice(), test_vector.sk_sm); - continue 'outer; + /// Assert that the provided projective point matches the given test vector. + // TODO(tarcieri): use coordinate APIs. See zkcrypto/group#30 + macro_rules! assert_point_eq { + ($actual:expr, $expected_x:expr, $expected_y:expr) => { + let point = $actual.to_affine().to_encoded_point(false); + let (actual_x, actual_y) = match point.coordinates() { + sec1::Coordinates::Uncompressed { x, y } => (x, y), + _ => unreachable!(), + }; + + assert_eq!(&$expected_x, actual_x.as_slice()); + assert_eq!(&$expected_y, actual_y.as_slice()); + }; } - } - panic!("deriving key failed"); - } -} + assert_eq!(u[0].to_bytes().as_slice(), test_vector.u_0); + assert_eq!(u[1].to_bytes().as_slice(), test_vector.u_1); -#[test] -fn from_okm_fuzz() { - use elliptic_curve::bigint::{NonZero, U384}; - use elliptic_curve::Curve; - use proptest::num::u64::ANY; - use proptest::{prelude::ProptestConfig, proptest}; + let q0 = u[0].map_to_curve(); + assert_point_eq!(q0, test_vector.q0_x, test_vector.q0_y); - let mut wide_order = GenericArray::default(); - wide_order[16..].copy_from_slice(&NistP256::ORDER.to_be_byte_array()); - let wide_order = NonZero::new(U384::from_be_byte_array(wide_order)).unwrap(); + let q1 = u[1].map_to_curve(); + assert_point_eq!(q1, test_vector.q1_x, test_vector.q1_y); - let simple_from_okm = move |data: GenericArray| -> Scalar { - let data = U384::from_be_slice(&data); + let p = q0.clear_cofactor() + q1.clear_cofactor(); + assert_point_eq!(p, test_vector.p_x, test_vector.p_y); - let scalar = data % wide_order; - let reduced_scalar = U256::from_be_slice(&scalar.to_be_byte_array()[16..]); + // complete run + let pt = + NistP256::hash_from_bytes::>(&[test_vector.msg], DST).unwrap(); + assert_point_eq!(pt, test_vector.p_x, test_vector.p_y); + } + } - Scalar(reduced_scalar) - }; + /// Taken from . + #[test] + fn hash_to_scalar_voprf() { + struct TestVector { + dst: &'static [u8], + key_info: &'static [u8], + seed: &'static [u8], + sk_sm: &'static [u8], + } + + const TEST_VECTORS: &[TestVector] = &[ + TestVector { + dst: b"DeriveKeyPairVOPRF09-\x00\x00\x03", + key_info: b"test key", + seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), + sk_sm: &hex!("88a91851d93ab3e4f2636babc60d6ce9d1aee2b86dece13fa8590d955a08d987"), + }, + TestVector { + dst: b"DeriveKeyPairVOPRF09-\x01\x00\x03", + key_info: b"test key", + seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), + sk_sm: &hex!("c8a626b52be02b06e9cdb1a05490392938642a30b1451b0cd1be1d3612b336b5"), + }, + TestVector { + dst: b"DeriveKeyPairVOPRF09-\x02\x00\x03", + key_info: b"test key", + seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"), + sk_sm: &hex!("b75567bfc40aaaf7735c35c6ad5d55a725c9d42ac66df2e1dbd2027bde289264"), + }, + ]; + + 'outer: for test_vector in TEST_VECTORS { + let key_info_len = u16::try_from(test_vector.key_info.len()) + .unwrap() + .to_be_bytes(); + + for counter in 0_u8..=u8::MAX { + let scalar = NistP256::hash_to_scalar::>( + &[ + test_vector.seed, + &key_info_len, + test_vector.key_info, + &counter.to_be_bytes(), + ], + test_vector.dst, + ) + .unwrap(); + + if !bool::from(scalar.is_zero()) { + assert_eq!(scalar.to_bytes().as_slice(), test_vector.sk_sm); + continue 'outer; + } + } - proptest!(ProptestConfig::with_cases(1000), |(b0 in ANY, b1 in ANY, b2 in ANY, b3 in ANY, b4 in ANY, b5 in ANY)| { - let mut data = GenericArray::default(); - data[..8].copy_from_slice(&b0.to_be_bytes()); - data[8..16].copy_from_slice(&b1.to_be_bytes()); - data[16..24].copy_from_slice(&b2.to_be_bytes()); - data[24..32].copy_from_slice(&b3.to_be_bytes()); - data[32..40].copy_from_slice(&b4.to_be_bytes()); - data[40..].copy_from_slice(&b5.to_be_bytes()); - - let from_okm = Scalar::from_okm(&data); - let simple_from_okm = simple_from_okm(data); - assert_eq!(from_okm, simple_from_okm); - }); + panic!("deriving key failed"); + } + } + + #[test] + fn from_okm_fuzz() { + let mut wide_order = GenericArray::default(); + wide_order[16..].copy_from_slice(&NistP256::ORDER.to_be_byte_array()); + let wide_order = NonZero::new(U384::from_be_byte_array(wide_order)).unwrap(); + + let simple_from_okm = move |data: GenericArray| -> Scalar { + let data = U384::from_be_slice(&data); + + let scalar = data % wide_order; + let reduced_scalar = U256::from_be_slice(&scalar.to_be_byte_array()[16..]); + + Scalar(reduced_scalar) + }; + + proptest!(ProptestConfig::with_cases(1000), |(b0 in ANY, b1 in ANY, b2 in ANY, b3 in ANY, b4 in ANY, b5 in ANY)| { + let mut data = GenericArray::default(); + data[..8].copy_from_slice(&b0.to_be_bytes()); + data[8..16].copy_from_slice(&b1.to_be_bytes()); + data[16..24].copy_from_slice(&b2.to_be_bytes()); + data[24..32].copy_from_slice(&b3.to_be_bytes()); + data[32..40].copy_from_slice(&b4.to_be_bytes()); + data[40..].copy_from_slice(&b5.to_be_bytes()); + + let from_okm = Scalar::from_okm(&data); + let simple_from_okm = simple_from_okm(data); + assert_eq!(from_okm, simple_from_okm); + }); + } } diff --git a/p256/src/arithmetic/projective.rs b/p256/src/arithmetic/projective.rs deleted file mode 100644 index 28f46d13..00000000 --- a/p256/src/arithmetic/projective.rs +++ /dev/null @@ -1,691 +0,0 @@ -//! Projective points - -#![allow(clippy::op_ref)] - -use super::{AffinePoint, FieldElement, Scalar, CURVE_EQUATION_B}; -use crate::{CompressedPoint, EncodedPoint, NistP256, PublicKey}; -use core::{ - iter::Sum, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, -}; -use elliptic_curve::{ - group::{ - ff::Field, - prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup}, - Curve, Group, GroupEncoding, - }, - ops::LinearCombination, - rand_core::RngCore, - sec1::{FromEncodedPoint, ToEncodedPoint}, - subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, - weierstrass, - zeroize::DefaultIsZeroes, - Error, PrimeCurveArithmetic, ProjectiveArithmetic, Result, -}; - -impl ProjectiveArithmetic for NistP256 { - type ProjectivePoint = ProjectivePoint; -} - -impl PrimeCurveArithmetic for NistP256 { - type CurveGroup = ProjectivePoint; -} - -/// A point on the secp256r1 curve in projective coordinates. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] -pub struct ProjectivePoint { - x: FieldElement, - y: FieldElement, - z: FieldElement, -} - -impl ProjectivePoint { - /// Additive identity of the group: the point at infinity. - pub const IDENTITY: Self = Self { - x: FieldElement::ZERO, - y: FieldElement::ONE, - z: FieldElement::ZERO, - }; - - /// Base point of P-256. - pub const GENERATOR: Self = Self { - x: AffinePoint::GENERATOR.x, - y: AffinePoint::GENERATOR.y, - z: FieldElement::ONE, - }; - - /// Returns the additive identity of P-256, also known as the "neutral element" or - /// "point at infinity". - #[deprecated(since = "0.10.1", note = "use `ProjectivePoint::IDENTITY` instead")] - pub const fn identity() -> ProjectivePoint { - Self::IDENTITY - } - - /// Returns the base point of P-256. - #[deprecated(since = "0.10.1", note = "use `ProjectivePoint::GENERATOR` instead")] - pub fn generator() -> ProjectivePoint { - Self::GENERATOR - } - - /// Returns the affine representation of this point. - pub fn to_affine(&self) -> AffinePoint { - self.z - .invert() - .map(|zinv| AffinePoint { - x: self.x * &zinv, - y: self.y * &zinv, - infinity: 0, - }) - .unwrap_or(AffinePoint::IDENTITY) - } - - /// Returns `-self`. - fn neg(&self) -> ProjectivePoint { - ProjectivePoint { - x: self.x, - y: self.y.neg(), - z: self.z, - } - } - - /// Returns `self + other`. - fn add(&self, other: &ProjectivePoint) -> ProjectivePoint { - weierstrass::add( - (self.x, self.y, self.z), - (other.x, other.y, other.z), - CURVE_EQUATION_B, - ) - .into() - } - - /// Returns `self + other`. - fn add_mixed(&self, other: &AffinePoint) -> ProjectivePoint { - let ret = Self::from(weierstrass::add_mixed( - (self.x, self.y, self.z), - (other.x, other.y), - CURVE_EQUATION_B, - )); - - Self::conditional_select(&ret, self, other.is_identity()) - } - - /// Doubles this point. - pub fn double(&self) -> ProjectivePoint { - weierstrass::double((self.x, self.y, self.z), CURVE_EQUATION_B).into() - } - - /// Returns `self - other`. - fn sub(&self, other: &ProjectivePoint) -> ProjectivePoint { - self.add(&other.neg()) - } - - /// Returns `self - other`. - fn sub_mixed(&self, other: &AffinePoint) -> ProjectivePoint { - self.add_mixed(&other.neg()) - } - - /// Returns `[k] self`. - fn mul(&self, k: &Scalar) -> ProjectivePoint { - let mut pc = [ProjectivePoint::default(); 16]; - pc[0] = ProjectivePoint::IDENTITY; - pc[1] = *self; - for i in 2..16 { - pc[i] = if i % 2 == 0 { - pc[i / 2].double() - } else { - pc[i - 1].add(self) - }; - } - let mut q = ProjectivePoint::IDENTITY; - let k = k.to_bytes(); - let mut pos = 256 - 4; - loop { - let slot = (k[31 - (pos >> 3) as usize] >> (pos & 7)) & 0xf; - let mut t = ProjectivePoint::IDENTITY; - for (i, pci) in pc.iter().enumerate().skip(1) { - t.conditional_assign( - pci, - Choice::from(((slot as usize ^ i).wrapping_sub(1) >> 8) as u8 & 1), - ); - } - q = q.add(&t); - if pos == 0 { - break; - } - q = q.double().double().double().double(); - pos -= 4; - } - q - } -} - -impl Group for ProjectivePoint { - type Scalar = Scalar; - - fn random(mut rng: impl RngCore) -> Self { - Self::GENERATOR * Scalar::random(&mut rng) - } - - fn identity() -> Self { - Self::IDENTITY - } - - fn generator() -> Self { - Self::GENERATOR - } - - fn is_identity(&self) -> Choice { - self.ct_eq(&Self::IDENTITY) - } - - #[must_use] - fn double(&self) -> Self { - ProjectivePoint::double(self) - } -} - -impl GroupEncoding for ProjectivePoint { - type Repr = CompressedPoint; - - fn from_bytes(bytes: &Self::Repr) -> CtOption { - ::from_bytes(bytes).map(Into::into) - } - - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - // No unchecked conversion possible for compressed points - Self::from_bytes(bytes) - } - - fn to_bytes(&self) -> Self::Repr { - self.to_affine().to_bytes() - } -} - -impl PrimeGroup for ProjectivePoint {} - -impl Curve for ProjectivePoint { - type AffineRepr = AffinePoint; - - fn to_affine(&self) -> AffinePoint { - ProjectivePoint::to_affine(self) - } -} - -impl PrimeCurve for ProjectivePoint { - type Affine = AffinePoint; -} - -impl LinearCombination for ProjectivePoint {} - -impl From for ProjectivePoint { - fn from(p: AffinePoint) -> Self { - let projective = ProjectivePoint { - x: p.x, - y: p.y, - z: FieldElement::ONE, - }; - Self::conditional_select(&projective, &Self::IDENTITY, p.is_identity()) - } -} - -impl From<&AffinePoint> for ProjectivePoint { - fn from(p: &AffinePoint) -> Self { - Self::from(*p) - } -} - -impl From for AffinePoint { - fn from(p: ProjectivePoint) -> AffinePoint { - p.to_affine() - } -} - -impl From<&ProjectivePoint> for AffinePoint { - fn from(p: &ProjectivePoint) -> AffinePoint { - p.to_affine() - } -} - -impl From> for ProjectivePoint { - #[inline] - fn from((x, y, z): weierstrass::ProjectivePoint) -> ProjectivePoint { - Self { x, y, z } - } -} - -impl FromEncodedPoint for ProjectivePoint { - fn from_encoded_point(p: &EncodedPoint) -> CtOption { - AffinePoint::from_encoded_point(p).map(ProjectivePoint::from) - } -} - -impl ToEncodedPoint 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 { - 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 DefaultIsZeroes for ProjectivePoint {} - -impl Eq for ProjectivePoint {} - -impl PartialEq for ProjectivePoint { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -impl Default for ProjectivePoint { - fn default() -> Self { - Self::IDENTITY - } -} - -impl Add 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 Add<&ProjectivePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn add(self, other: &ProjectivePoint) -> ProjectivePoint { - ProjectivePoint::add(&self, other) - } -} - -impl AddAssign 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 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 Add<&AffinePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn add(self, other: &AffinePoint) -> ProjectivePoint { - ProjectivePoint::add_mixed(&self, other) - } -} - -impl AddAssign for ProjectivePoint { - fn add_assign(&mut self, rhs: AffinePoint) { - *self = ProjectivePoint::add_mixed(self, &rhs); - } -} - -impl AddAssign<&AffinePoint> for ProjectivePoint { - fn add_assign(&mut self, rhs: &AffinePoint) { - *self = ProjectivePoint::add_mixed(self, rhs); - } -} - -impl Sum for ProjectivePoint { - fn sum>(iter: I) -> Self { - iter.fold(ProjectivePoint::IDENTITY, |a, b| a + b) - } -} - -impl<'a> Sum<&'a ProjectivePoint> for ProjectivePoint { - fn sum>(iter: I) -> Self { - iter.cloned().sum() - } -} - -impl Sub 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 Sub<&ProjectivePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn sub(self, other: &ProjectivePoint) -> ProjectivePoint { - ProjectivePoint::sub(&self, other) - } -} - -impl SubAssign 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 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 Sub<&AffinePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn sub(self, other: &AffinePoint) -> ProjectivePoint { - ProjectivePoint::sub_mixed(&self, other) - } -} - -impl SubAssign for ProjectivePoint { - fn sub_assign(&mut self, rhs: AffinePoint) { - *self = ProjectivePoint::sub_mixed(self, &rhs); - } -} - -impl SubAssign<&AffinePoint> for ProjectivePoint { - fn sub_assign(&mut self, rhs: &AffinePoint) { - *self = ProjectivePoint::sub_mixed(self, rhs); - } -} - -impl Mul 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 Mul<&Scalar> for ProjectivePoint { - type Output = ProjectivePoint; - - fn mul(self, other: &Scalar) -> ProjectivePoint { - ProjectivePoint::mul(&self, other) - } -} - -impl MulAssign 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) - } -} - -impl From for ProjectivePoint { - fn from(public_key: PublicKey) -> ProjectivePoint { - AffinePoint::from(public_key).into() - } -} - -impl From<&PublicKey> for ProjectivePoint { - fn from(public_key: &PublicKey) -> ProjectivePoint { - AffinePoint::from(public_key).into() - } -} - -impl TryFrom for PublicKey { - type Error = Error; - - fn try_from(point: ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -impl TryFrom<&ProjectivePoint> for PublicKey { - type Error = Error; - - fn try_from(point: &ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -#[cfg(test)] -mod tests { - use super::{AffinePoint, ProjectivePoint, Scalar}; - use crate::test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}; - use elliptic_curve::group::{ff::PrimeField, prime::PrimeCurveAffine, GroupEncoding}; - - #[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(), basepoint_affine); - assert!(!bool::from(basepoint_projective.to_affine().is_identity())); - - assert!(bool::from( - ProjectivePoint::IDENTITY.to_affine().is_identity() - )); - } - - #[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(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_bytes(), expected_x.into()); - assert_eq!(affine.y.to_bytes(), expected_y.into()); - - 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(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_bytes(), expected_x.into()); - assert_eq!(affine.y.to_bytes(), expected_y.into()); - - p += &generator; - } - } - - #[test] - fn test_vector_add_mixed_identity() { - let generator = ProjectivePoint::GENERATOR; - let p0 = generator + ProjectivePoint::IDENTITY; - let p1 = generator + AffinePoint::IDENTITY; - assert_eq!(p0, p1); - } - - #[test] - fn test_vector_double_generator() { - let generator = ProjectivePoint::GENERATOR; - let mut p = generator; - - for i in 0..2 { - let affine = p.to_affine(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_bytes(), expected_x.into()); - assert_eq!(affine.y.to_bytes(), expected_y.into()); - - 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_repr(k.into()).unwrap(), (x, y))), - ) - { - let res = (generator * &k).to_affine(); - assert_eq!(res.x.to_bytes(), coords.0.into()); - assert_eq!(res.y.to_bytes(), coords.1.into()); - } - } - - #[test] - fn projective_identity_to_bytes() { - // This is technically an invalid SEC1 encoding, but is preferable to panicking. - assert_eq!([0; 33], ProjectivePoint::IDENTITY.to_bytes().as_slice()); - } -} diff --git a/p256/src/arithmetic/scalar.rs b/p256/src/arithmetic/scalar.rs index 1afe77ca..877199f5 100644 --- a/p256/src/arithmetic/scalar.rs +++ b/p256/src/arithmetic/scalar.rs @@ -18,7 +18,7 @@ use elliptic_curve::{ CtOption, }, zeroize::DefaultIsZeroes, - Curve, IsHigh, ScalarArithmetic, ScalarCore, + Curve, IsHigh, ScalarCore, }; #[cfg(feature = "bits")] @@ -48,10 +48,6 @@ pub const MU: [u64; 5] = [ 0x0000_0000_0000_0001, ]; -impl ScalarArithmetic for NistP256 { - type Scalar = Scalar; -} - /// Scalars are elements in the finite field modulo n. /// /// # Trait impls diff --git a/p256/src/ecdh.rs b/p256/src/ecdh.rs index c1ee881f..94aa94aa 100644 --- a/p256/src/ecdh.rs +++ b/p256/src/ecdh.rs @@ -38,16 +38,10 @@ pub use elliptic_curve::ecdh::diffie_hellman; -use crate::{AffinePoint, NistP256}; +use crate::NistP256; /// NIST P-256 Ephemeral Diffie-Hellman Secret. pub type EphemeralSecret = elliptic_curve::ecdh::EphemeralSecret; /// Shared secret value computed via ECDH key agreement. pub type SharedSecret = elliptic_curve::ecdh::SharedSecret; - -impl From<&AffinePoint> for SharedSecret { - fn from(affine: &AffinePoint) -> SharedSecret { - affine.x.to_bytes().into() - } -} diff --git a/p256/src/lib.rs b/p256/src/lib.rs index bb323c4a..a51c455b 100644 --- a/p256/src/lib.rs +++ b/p256/src/lib.rs @@ -38,9 +38,8 @@ pub use elliptic_curve::{self, bigint::U256}; #[cfg(feature = "arithmetic")] pub use arithmetic::{ - affine::AffinePoint, - projective::ProjectivePoint, scalar::{blinded::BlindedScalar, Scalar}, + AffinePoint, ProjectivePoint, }; #[cfg(feature = "expose-field")] diff --git a/p256/tests/affine.rs b/p256/tests/affine.rs new file mode 100644 index 00000000..d3cc2677 --- /dev/null +++ b/p256/tests/affine.rs @@ -0,0 +1,121 @@ +//! Affine arithmetic tests. + +// TODO(tarcieri): point compaction support + +#![cfg(all(feature = "arithmetic"))] + +use elliptic_curve::{ + group::{prime::PrimeCurveAffine, GroupEncoding}, + sec1::{FromEncodedPoint, ToEncodedPoint}, +}; +use hex_literal::hex; +use p256::{AffinePoint, EncodedPoint}; + +const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!( + "04 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296 + 4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5" +); + +const COMPRESSED_BASEPOINT: &[u8] = + &hex!("03 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"); + +// // Tag compact with 05 as the first byte, to trigger tag based compaction +// const COMPACT_BASEPOINT: &[u8] = +// &hex!("05 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01"); +// +// // Tag uncompact basepoint with 04 as the first byte as it is uncompressed +// const UNCOMPACT_BASEPOINT: &[u8] = &hex!( +// "04 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c0 +// 13ca9d8667de0c07aa71d98b3c8065d2e97ab7bb9cb8776bcc0577a7ac58acd4e" +// ); + +#[test] +fn uncompressed_round_trip() { + let pubkey = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + assert_eq!(point, AffinePoint::generator()); + + let res: EncodedPoint = point.into(); + assert_eq!(res, pubkey); +} + +#[test] +fn compressed_round_trip() { + let pubkey = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + assert_eq!(point, AffinePoint::generator()); + + let res: EncodedPoint = point.to_encoded_point(true); + assert_eq!(res, pubkey); +} + +#[test] +fn uncompressed_to_compressed() { + let encoded = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); + + let res = AffinePoint::from_encoded_point(&encoded) + .unwrap() + .to_encoded_point(true); + + assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT); +} + +#[test] +fn compressed_to_uncompressed() { + let encoded = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); + + let res = AffinePoint::from_encoded_point(&encoded) + .unwrap() + .to_encoded_point(false); + + assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT); +} + +#[test] +fn affine_negation() { + let basepoint = AffinePoint::generator(); + assert_eq!(-(-basepoint), basepoint); +} + +// TODO(tarcieri): fix point compaction +// #[test] +// fn compact_round_trip() { +// let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); +// assert!(pubkey.is_compact()); +// +// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); +// let res = point.to_compact_encoded_point().unwrap(); +// assert_eq!(res, pubkey) +// } + +// #[test] +// fn uncompact_to_compact() { +// let pubkey = EncodedPoint::from_bytes(UNCOMPACT_BASEPOINT).unwrap(); +// assert_eq!(false, pubkey.is_compact()); +// +// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); +// let res = point.to_compact_encoded_point().unwrap(); +// assert_eq!(res.as_bytes(), COMPACT_BASEPOINT) +// } + +// #[test] +// fn compact_to_uncompact() { +// let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); +// assert!(pubkey.is_compact()); +// +// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); +// // Do not do compact encoding as we want to keep uncompressed point +// let res = point.to_encoded_point(false); +// assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT); +// } + +#[test] +fn identity_encoding() { + // This is technically an invalid SEC1 encoding, but is preferable to panicking. + assert_eq!([0; 33], AffinePoint::IDENTITY.to_bytes().as_slice()); + assert!(bool::from( + AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes()) + .unwrap() + .is_identity() + )) +} diff --git a/p256/tests/projective.rs b/p256/tests/projective.rs new file mode 100644 index 00000000..c149f5aa --- /dev/null +++ b/p256/tests/projective.rs @@ -0,0 +1,160 @@ +//! Projective arithmetic tests. + +#![cfg(all(feature = "arithmetic", feature = "test-vectors"))] + +use elliptic_curve::{ + group::{ff::PrimeField, GroupEncoding}, + sec1::{self, ToEncodedPoint}, +}; +use p256::test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}; +use p256::{AffinePoint, ProjectivePoint, Scalar}; + +/// Assert that the provided projective point matches the given test vector. +// TODO(tarcieri): use coordinate APIs. See zkcrypto/group#30 +macro_rules! assert_point_eq { + ($actual:expr, $expected:expr) => { + let (expected_x, expected_y) = $expected; + + let point = $actual.to_affine().to_encoded_point(false); + let (actual_x, actual_y) = match point.coordinates() { + sec1::Coordinates::Uncompressed { x, y } => (x, y), + _ => unreachable!(), + }; + + assert_eq!(&expected_x, actual_x.as_slice()); + assert_eq!(&expected_y, actual_y.as_slice()); + }; +} + +#[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(), basepoint_affine); + assert!(!bool::from(basepoint_projective.to_affine().is_identity())); + + assert!(bool::from( + ProjectivePoint::IDENTITY.to_affine().is_identity() + )); +} + +#[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() { + assert_point_eq!(p, ADD_TEST_VECTORS[i]); + 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() { + assert_point_eq!(p, ADD_TEST_VECTORS[i]); + p += &generator; + } +} + +#[test] +fn test_vector_add_mixed_identity() { + let generator = ProjectivePoint::GENERATOR; + let p0 = generator + ProjectivePoint::IDENTITY; + let p1 = generator + AffinePoint::IDENTITY; + assert_eq!(p0, p1); +} + +#[test] +fn test_vector_double_generator() { + let generator = ProjectivePoint::GENERATOR; + let mut p = generator; + + for i in 0..2 { + assert_point_eq!(p, 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_repr(k.into()).unwrap(), (x, y))), + ) + { + let p = generator * &k; + assert_point_eq!(p, coords); + } +} + +#[test] +fn projective_identity_to_bytes() { + // This is technically an invalid SEC1 encoding, but is preferable to panicking. + assert_eq!([0; 33], ProjectivePoint::IDENTITY.to_bytes().as_slice()); +} diff --git a/p384/Cargo.toml b/p384/Cargo.toml index 25fedb9e..9d31b22d 100644 --- a/p384/Cargo.toml +++ b/p384/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p384" -version = "0.11.2" +version = "0.12.0-pre" description = """ Pure Rust implementation of the NIST P-384 (a.k.a. secp384r1) elliptic curve with support for ECDH, ECDSA signing/verification, and general purpose curve @@ -17,7 +17,8 @@ edition = "2021" rust-version = "1.57" [dependencies] -elliptic-curve = { version = "0.12.3", default-features = false, features = ["hazmat", "sec1"] } +elliptic-curve = { version = "0.12", default-features = false, features = ["hazmat", "sec1"] } +weierstrass = { version = "0", path = "../weierstrass" } # optional dependencies ecdsa-core = { version = "0.14", package = "ecdsa", optional = true, default-features = false, features = ["der"] } diff --git a/p384/src/arithmetic.rs b/p384/src/arithmetic.rs index 6ffbcb7b..5af11b18 100644 --- a/p384/src/arithmetic.rs +++ b/p384/src/arithmetic.rs @@ -8,25 +8,69 @@ #[macro_use] mod macros; -pub(crate) mod affine; pub(crate) mod field; -pub(crate) mod projective; pub(crate) mod scalar; -use self::{ - affine::AffinePoint, - field::{FieldElement, MODULUS}, - projective::ProjectivePoint, - scalar::Scalar, +use self::{field::FieldElement, scalar::Scalar}; +use crate::NistP384; +use elliptic_curve::{ + AffineArithmetic, PrimeCurveArithmetic, ProjectiveArithmetic, ScalarArithmetic, }; +use weierstrass::WeierstrassCurve; -/// a = -3 (0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc) -const CURVE_EQUATION_A: FieldElement = FieldElement::ZERO - .sub(&FieldElement::ONE) - .sub(&FieldElement::ONE) - .sub(&FieldElement::ONE); +/// Elliptic curve point in affine coordinates. +pub type AffinePoint = weierstrass::AffinePoint; -/// b = b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 -/// 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef -const CURVE_EQUATION_B: FieldElement = - FieldElement::from_be_hex("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"); +/// Elliptic curve point in projective coordinates. +pub type ProjectivePoint = weierstrass::ProjectivePoint; + +impl WeierstrassCurve for NistP384 { + type FieldElement = FieldElement; + + const ZERO: FieldElement = FieldElement::ZERO; + const ONE: FieldElement = FieldElement::ONE; + + /// a = -3 (0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc) + const EQUATION_A: FieldElement = FieldElement::ZERO + .sub(&FieldElement::ONE) + .sub(&FieldElement::ONE) + .sub(&FieldElement::ONE); + + /// b = b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 + /// 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef + const EQUATION_B: FieldElement = FieldElement::from_be_hex("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"); + + /// Base point of P-384. + /// + /// Defined in FIPS 186-4 § D.1.2.4: + /// + /// ```text + /// Gₓ = aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 + /// 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7 + /// Gᵧ = 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c + /// e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f + /// ``` + /// + /// NOTE: coordinate field elements have been translated into the Montgomery + /// domain. + const GENERATOR: (FieldElement, FieldElement) = ( + FieldElement::from_be_hex("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"), + FieldElement::from_be_hex("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"), + ); +} + +impl AffineArithmetic for NistP384 { + type AffinePoint = AffinePoint; +} + +impl ProjectiveArithmetic for NistP384 { + type ProjectivePoint = ProjectivePoint; +} + +impl PrimeCurveArithmetic for NistP384 { + type CurveGroup = ProjectivePoint; +} + +impl ScalarArithmetic for NistP384 { + type Scalar = Scalar; +} diff --git a/p384/src/arithmetic/affine.rs b/p384/src/arithmetic/affine.rs deleted file mode 100644 index 5f4a3f3f..00000000 --- a/p384/src/arithmetic/affine.rs +++ /dev/null @@ -1,415 +0,0 @@ -//! Affine points on the NIST P-384 elliptic curve. - -#![allow(clippy::op_ref)] - -use core::ops::{Mul, Neg}; - -use super::{FieldElement, ProjectivePoint, CURVE_EQUATION_A, CURVE_EQUATION_B, MODULUS}; -use crate::{CompressedPoint, EncodedPoint, FieldBytes, NistP384, PublicKey, Scalar}; -use elliptic_curve::{ - group::{prime::PrimeCurveAffine, GroupEncoding}, - sec1::{self, FromEncodedPoint, ToEncodedPoint}, - subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, - zeroize::DefaultIsZeroes, - AffineArithmetic, AffineXCoordinate, DecompressPoint, Error, Result, -}; - -#[cfg(feature = "serde")] -use serdect::serde::{de, ser, Deserialize, Serialize}; - -impl AffineArithmetic for NistP384 { - type AffinePoint = AffinePoint; -} - -/// NIST P-384 (secp384r1) curve point expressed in affine coordinates. -/// -/// # `serde` support -/// -/// When the `serde` feature of this crate is enabled, the `Serialize` and -/// `Deserialize` traits are impl'd for this type. -/// -/// The serialization uses the [SEC1] `Elliptic-Curve-Point-to-Octet-String` -/// encoding, serialized as binary. -/// -/// When serialized with a text-based format, the SEC1 representation is -/// subsequently hex encoded. -/// -/// [SEC1]: https://www.secg.org/sec1-v2.pdf -#[derive(Clone, Copy, Debug)] -#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] -pub struct AffinePoint { - /// x-coordinate - pub x: FieldElement, - - /// y-coordinate - pub y: FieldElement, - - /// Is this point the point at infinity? 0 = no, 1 = yes - /// - /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const` - /// constructors for `IDENTITY` and `GENERATOR`. - pub infinity: u8, -} - -impl AffinePoint { - /// Base point of P-384. - /// - /// Defined in FIPS 186-4 § D.1.2.4: - /// - /// ```text - /// Gₓ = aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 - /// 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7 - /// Gᵧ = 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c - /// e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f - /// ``` - /// - /// NOTE: coordinate field elements have been translated into the Montgomery - /// domain. - pub const GENERATOR: Self = Self { - x: FieldElement::from_be_hex("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"), - y: FieldElement::from_be_hex("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"), - infinity: 0, - }; - - /// Additive identity of the group: the point at infinity. - pub const IDENTITY: Self = Self { - x: FieldElement::ZERO, - y: FieldElement::ZERO, - infinity: 1, - }; -} - -impl PrimeCurveAffine for AffinePoint { - type Curve = ProjectivePoint; - type Scalar = Scalar; - - fn identity() -> AffinePoint { - Self::IDENTITY - } - - fn generator() -> AffinePoint { - Self::GENERATOR - } - - fn is_identity(&self) -> Choice { - Choice::from(self.infinity) - } - - fn to_curve(&self) -> ProjectivePoint { - ProjectivePoint::from(*self) - } -} - -impl AffineXCoordinate for AffinePoint { - fn x(&self) -> FieldBytes { - self.x.to_sec1() - } -} - -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), - infinity: u8::conditional_select(&a.infinity, &b.infinity, choice), - } - } -} - -impl ConstantTimeEq for AffinePoint { - fn ct_eq(&self, other: &AffinePoint) -> Choice { - self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) & self.infinity.ct_eq(&other.infinity) - } -} - -impl Default for AffinePoint { - fn default() -> Self { - Self::IDENTITY - } -} - -impl DefaultIsZeroes for AffinePoint {} - -impl Eq for AffinePoint {} - -impl PartialEq for AffinePoint { - fn eq(&self, other: &AffinePoint) -> bool { - self.ct_eq(other).into() - } -} - -impl Mul 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: &Scalar) -> ProjectivePoint { - ProjectivePoint::from(self) * scalar - } -} - -impl Neg for AffinePoint { - type Output = AffinePoint; - - fn neg(self) -> Self::Output { - AffinePoint { - x: self.x, - y: -self.y, - infinity: self.infinity, - } - } -} - -impl DecompressPoint for AffinePoint { - fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { - FieldElement::from_sec1(*x_bytes).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( - &(FieldElement(MODULUS) - &beta), - &beta, - beta.is_odd().ct_eq(&y_is_odd), - ); - - Self { x, y, infinity: 0 } - }) - }) - } -} - -impl GroupEncoding for AffinePoint { - type Repr = CompressedPoint; - - /// NOTE: not constant-time with respect to identity point - fn from_bytes(bytes: &Self::Repr) -> CtOption { - EncodedPoint::from_bytes(bytes) - .map(|point| CtOption::new(point, Choice::from(1))) - .unwrap_or_else(|_| { - // SEC1 identity encoding is technically 1-byte 0x00, but the - // `GroupEncoding` API requires a fixed-width `Repr` - let is_identity = bytes.ct_eq(&Self::Repr::default()); - CtOption::new(EncodedPoint::identity(), is_identity) - }) - .and_then(|point| Self::from_encoded_point(&point)) - } - - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - // No unchecked conversion possible for compressed points - Self::from_bytes(bytes) - } - - fn to_bytes(&self) -> Self::Repr { - let encoded = self.to_encoded_point(true); - let mut result = CompressedPoint::default(); - result[..encoded.len()].copy_from_slice(encoded.as_bytes()); - result - } -} - -impl FromEncodedPoint for AffinePoint { - /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded - /// [`AffinePoint`]. - /// - /// # Returns - /// - /// `None` value if `encoded_point` is not on the secp384r1 curve. - fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { - match encoded_point.coordinates() { - sec1::Coordinates::Identity => CtOption::new(Self::identity(), 1.into()), - // TODO(tarcieri): point decompaction support - sec1::Coordinates::Compact { .. } => CtOption::new(AffinePoint::IDENTITY, 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_sec1(*x); - let y = FieldElement::from_sec1(*y); - - x.and_then(|x| { - y.and_then(|y| { - // Check that the point is on the curve - let lhs = y * &y; - let rhs = x * &x * &x + &(CURVE_EQUATION_A * &x) + &CURVE_EQUATION_B; - let point = AffinePoint { x, y, infinity: 0 }; - CtOption::new(point, lhs.ct_eq(&rhs)) - }) - }) - } - } - } -} - -impl ToEncodedPoint for AffinePoint { - fn to_encoded_point(&self, compress: bool) -> EncodedPoint { - EncodedPoint::conditional_select( - &EncodedPoint::from_affine_coordinates(&self.x.to_sec1(), &self.y.to_sec1(), compress), - &EncodedPoint::identity(), - self.is_identity(), - ) - } -} - -impl TryFrom for AffinePoint { - type Error = Error; - - fn try_from(point: EncodedPoint) -> Result { - AffinePoint::try_from(&point) - } -} - -impl TryFrom<&EncodedPoint> for AffinePoint { - type Error = Error; - - fn try_from(point: &EncodedPoint) -> Result { - Option::from(AffinePoint::from_encoded_point(point)).ok_or(Error) - } -} - -impl From for EncodedPoint { - fn from(affine_point: AffinePoint) -> EncodedPoint { - affine_point.to_encoded_point(false) - } -} - -impl From for AffinePoint { - fn from(public_key: PublicKey) -> AffinePoint { - *public_key.as_affine() - } -} - -impl From<&PublicKey> for AffinePoint { - fn from(public_key: &PublicKey) -> AffinePoint { - AffinePoint::from(*public_key) - } -} - -impl TryFrom for PublicKey { - type Error = Error; - - fn try_from(affine_point: AffinePoint) -> Result { - PublicKey::from_affine(affine_point) - } -} - -impl TryFrom<&AffinePoint> for PublicKey { - type Error = Error; - - fn try_from(affine_point: &AffinePoint) -> Result { - PublicKey::try_from(*affine_point) - } -} - -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -impl Serialize for AffinePoint { - fn serialize(&self, serializer: S) -> core::result::Result - where - S: ser::Serializer, - { - self.to_encoded_point(true).serialize(serializer) - } -} - -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] -impl<'de> Deserialize<'de> for AffinePoint { - fn deserialize(deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - EncodedPoint::deserialize(deserializer)? - .try_into() - .map_err(de::Error::custom) - } -} - -#[cfg(test)] -mod tests { - use elliptic_curve::{ - group::{prime::PrimeCurveAffine, GroupEncoding}, - sec1::{FromEncodedPoint, ToEncodedPoint}, - }; - use hex_literal::hex; - - use super::AffinePoint; - use crate::EncodedPoint; - - const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!( - "04 aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 - 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7 - 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c - e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f" - ); - - const COMPRESSED_BASEPOINT: &[u8] = &hex!( - "03 aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 - 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7" - ); - - #[test] - fn uncompressed_round_trip() { - let pubkey = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - assert_eq!(point, AffinePoint::generator()); - - let res: EncodedPoint = point.into(); - assert_eq!(res, pubkey); - } - - #[test] - fn compressed_round_trip() { - let pubkey = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); - let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); - assert_eq!(point, AffinePoint::generator()); - - let res: EncodedPoint = point.to_encoded_point(true); - assert_eq!(res, pubkey); - } - - #[test] - fn uncompressed_to_compressed() { - let encoded = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); - - let res = AffinePoint::from_encoded_point(&encoded) - .unwrap() - .to_encoded_point(true); - - assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT); - } - - #[test] - fn compressed_to_uncompressed() { - let encoded = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); - - let res = AffinePoint::from_encoded_point(&encoded) - .unwrap() - .to_encoded_point(false); - - assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT); - } - - #[test] - fn affine_negation() { - let basepoint = AffinePoint::generator(); - assert_eq!(-(-basepoint), basepoint); - } - - #[test] - fn identity_encoding() { - // This is technically an invalid SEC1 encoding, but is preferable to panicking. - assert_eq!([0; 49], AffinePoint::IDENTITY.to_bytes().as_slice()); - assert!(bool::from( - AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes()) - .unwrap() - .is_identity() - )) - } -} diff --git a/p384/src/arithmetic/field.rs b/p384/src/arithmetic/field.rs index 858bac92..97c9b285 100644 --- a/p384/src/arithmetic/field.rs +++ b/p384/src/arithmetic/field.rs @@ -29,6 +29,7 @@ use crate::FieldBytes; use core::ops::{AddAssign, MulAssign, Neg, SubAssign}; use elliptic_curve::{ bigint::{self, Encoding, Limb, U384}, + ff::PrimeField, subtle::{Choice, ConstantTimeEq, CtOption}, }; @@ -122,6 +123,40 @@ impl FieldElement { } } +impl From for FieldElement { + fn from(n: u64) -> FieldElement { + Self::from_uint(U384::from(n)).unwrap() + } +} + +impl PrimeField for FieldElement { + type Repr = FieldBytes; + + const NUM_BITS: u32 = 384; + const CAPACITY: u32 = 0; // TODO(tarcieri): bogus! needs real value + const S: u32 = 0; // TODO(tarcieri): bogus! needs real value + + fn from_repr(bytes: FieldBytes) -> CtOption { + Self::from_be_bytes(bytes) + } + + fn to_repr(&self) -> FieldBytes { + self.to_be_bytes() + } + + fn is_odd(&self) -> Choice { + self.is_odd() + } + + fn multiplicative_generator() -> Self { + todo!() + } + + fn root_of_unity() -> Self { + todo!() + } +} + #[cfg(test)] mod tests { use super::FieldElement; diff --git a/p384/src/arithmetic/projective.rs b/p384/src/arithmetic/projective.rs deleted file mode 100644 index 2da22232..00000000 --- a/p384/src/arithmetic/projective.rs +++ /dev/null @@ -1,673 +0,0 @@ -//! Projective points - -#![allow(clippy::needless_range_loop, clippy::op_ref)] - -use core::{ - iter::Sum, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, -}; - -use elliptic_curve::{ - group::{ - ff::Field, - prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup}, - Curve, Group, GroupEncoding, - }, - ops::LinearCombination, - rand_core::RngCore, - sec1::{FromEncodedPoint, ToEncodedPoint}, - subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, - weierstrass, - zeroize::DefaultIsZeroes, - Error, PrimeCurveArithmetic, ProjectiveArithmetic, Result, -}; - -use super::{AffinePoint, FieldElement, Scalar, CURVE_EQUATION_B}; -use crate::{CompressedPoint, EncodedPoint, NistP384, PublicKey}; - -impl ProjectiveArithmetic for NistP384 { - type ProjectivePoint = ProjectivePoint; -} - -impl PrimeCurveArithmetic for NistP384 { - type CurveGroup = ProjectivePoint; -} - -/// A point on the secp384r1 curve in projective coordinates. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] -pub struct ProjectivePoint { - x: FieldElement, - y: FieldElement, - z: FieldElement, -} - -impl ProjectivePoint { - /// Base point of P-384. - pub const GENERATOR: Self = Self { - x: AffinePoint::GENERATOR.x, - y: AffinePoint::GENERATOR.y, - z: FieldElement::ONE, - }; - /// Additive identity of the group: the point at infinity. - pub const IDENTITY: Self = Self { - x: FieldElement::ZERO, - y: FieldElement::ONE, - z: FieldElement::ZERO, - }; - - /// Returns the additive identity of P-384, also known as the "neutral - /// element" or "point at infinity". - #[deprecated(since = "0.10.1", note = "use `ProjectivePoint::IDENTITY` instead")] - pub const fn identity() -> ProjectivePoint { - Self::IDENTITY - } - - /// Returns the base point of P-384. - #[deprecated(since = "0.10.1", note = "use `ProjectivePoint::GENERATOR` instead")] - pub fn generator() -> ProjectivePoint { - Self::GENERATOR - } - - /// Returns the affine representation of this point, or `None` if it is the - /// identity. - pub fn to_affine(&self) -> AffinePoint { - self.z - .invert() - .map(|zinv| AffinePoint { - x: self.x * &zinv, - y: self.y * &zinv, - infinity: 0, - }) - .unwrap_or(AffinePoint::IDENTITY) - } - - /// Returns `-self`. - fn neg(&self) -> ProjectivePoint { - ProjectivePoint { - x: self.x, - y: self.y.neg(), - z: self.z, - } - } - - /// Returns `self + other`. - fn add(&self, other: &ProjectivePoint) -> ProjectivePoint { - weierstrass::add( - (self.x, self.y, self.z), - (other.x, other.y, other.z), - CURVE_EQUATION_B, - ) - .into() - } - - /// Returns `self + other`. - fn add_mixed(&self, other: &AffinePoint) -> ProjectivePoint { - let ret = Self::from(weierstrass::add_mixed( - (self.x, self.y, self.z), - (other.x, other.y), - CURVE_EQUATION_B, - )); - - Self::conditional_select(&ret, self, other.is_identity()) - } - - /// Doubles this point. - pub fn double(&self) -> ProjectivePoint { - weierstrass::double((self.x, self.y, self.z), CURVE_EQUATION_B).into() - } - - /// Returns `self - other`. - fn sub(&self, other: &ProjectivePoint) -> ProjectivePoint { - self.add(&other.neg()) - } - - /// Returns `self - other`. - fn sub_mixed(&self, other: &AffinePoint) -> ProjectivePoint { - self.add_mixed(&other.neg()) - } - - /// Returns `[k] self`. - fn mul(&self, k: &Scalar) -> ProjectivePoint { - let mut pc = [ProjectivePoint::default(); 16]; - pc[0] = ProjectivePoint::IDENTITY; - pc[1] = *self; - for i in 2..16 { - pc[i] = if i % 2 == 0 { - pc[i / 2].double() - } else { - pc[i - 1].add(self) - }; - } - let mut q = ProjectivePoint::IDENTITY; - let k = k.to_le_bytes(); - let mut pos = 384 - 4; - loop { - let slot = (k[(pos >> 3) as usize] >> (pos & 7)) & 0xf; - let mut t = ProjectivePoint::IDENTITY; - for i in 1..16 { - t.conditional_assign( - &pc[i], - Choice::from(((slot as usize ^ i).wrapping_sub(1) >> 8) as u8 & 1), - ); - } - q = q.add(&t); - if pos == 0 { - break; - } - q = q.double().double().double().double(); - pos -= 4; - } - q - } -} - -impl Group for ProjectivePoint { - type Scalar = Scalar; - - fn random(mut rng: impl RngCore) -> Self { - Self::GENERATOR * Scalar::random(&mut rng) - } - - fn identity() -> Self { - Self::IDENTITY - } - - fn generator() -> Self { - Self::GENERATOR - } - - fn is_identity(&self) -> Choice { - self.ct_eq(&Self::IDENTITY) - } - - #[must_use] - fn double(&self) -> Self { - ProjectivePoint::double(self) - } -} - -impl GroupEncoding for ProjectivePoint { - type Repr = CompressedPoint; - - fn from_bytes(bytes: &Self::Repr) -> CtOption { - ::from_bytes(bytes).map(Into::into) - } - - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - // No unchecked conversion possible for compressed points - Self::from_bytes(bytes) - } - - fn to_bytes(&self) -> Self::Repr { - self.to_affine().to_bytes() - } -} - -impl PrimeGroup for ProjectivePoint {} - -impl Curve for ProjectivePoint { - type AffineRepr = AffinePoint; - - fn to_affine(&self) -> AffinePoint { - ProjectivePoint::to_affine(self) - } -} - -impl PrimeCurve for ProjectivePoint { - type Affine = AffinePoint; -} - -impl LinearCombination for ProjectivePoint {} - -impl From for ProjectivePoint { - fn from(p: AffinePoint) -> Self { - let projective = ProjectivePoint { - x: p.x, - y: p.y, - z: FieldElement::ONE, - }; - Self::conditional_select(&projective, &Self::IDENTITY, p.is_identity()) - } -} - -impl From<&AffinePoint> for ProjectivePoint { - fn from(p: &AffinePoint) -> Self { - Self::from(*p) - } -} - -impl From for AffinePoint { - fn from(p: ProjectivePoint) -> AffinePoint { - p.to_affine() - } -} - -impl From<&ProjectivePoint> for AffinePoint { - fn from(p: &ProjectivePoint) -> AffinePoint { - p.to_affine() - } -} - -impl From> for ProjectivePoint { - #[inline] - fn from((x, y, z): weierstrass::ProjectivePoint) -> ProjectivePoint { - Self { x, y, z } - } -} - -impl FromEncodedPoint for ProjectivePoint { - fn from_encoded_point(p: &EncodedPoint) -> CtOption { - AffinePoint::from_encoded_point(p).map(ProjectivePoint::from) - } -} - -impl ToEncodedPoint 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 { - 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 DefaultIsZeroes for ProjectivePoint {} - -impl Eq for ProjectivePoint {} - -impl PartialEq for ProjectivePoint { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -impl Default for ProjectivePoint { - fn default() -> Self { - Self::IDENTITY - } -} - -impl Add 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 Add<&ProjectivePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn add(self, other: &ProjectivePoint) -> ProjectivePoint { - ProjectivePoint::add(&self, other) - } -} - -impl AddAssign 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 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 Add<&AffinePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn add(self, other: &AffinePoint) -> ProjectivePoint { - ProjectivePoint::add_mixed(&self, other) - } -} - -impl AddAssign for ProjectivePoint { - fn add_assign(&mut self, rhs: AffinePoint) { - *self = ProjectivePoint::add_mixed(self, &rhs); - } -} - -impl AddAssign<&AffinePoint> for ProjectivePoint { - fn add_assign(&mut self, rhs: &AffinePoint) { - *self = ProjectivePoint::add_mixed(self, rhs); - } -} - -impl Sum for ProjectivePoint { - fn sum>(iter: I) -> Self { - iter.fold(ProjectivePoint::IDENTITY, |a, b| a + b) - } -} - -impl<'a> Sum<&'a ProjectivePoint> for ProjectivePoint { - fn sum>(iter: I) -> Self { - iter.cloned().sum() - } -} - -impl Sub 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 Sub<&ProjectivePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn sub(self, other: &ProjectivePoint) -> ProjectivePoint { - ProjectivePoint::sub(&self, other) - } -} - -impl SubAssign 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 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 Sub<&AffinePoint> for ProjectivePoint { - type Output = ProjectivePoint; - - fn sub(self, other: &AffinePoint) -> ProjectivePoint { - ProjectivePoint::sub_mixed(&self, other) - } -} - -impl SubAssign for ProjectivePoint { - fn sub_assign(&mut self, rhs: AffinePoint) { - *self = ProjectivePoint::sub_mixed(self, &rhs); - } -} - -impl SubAssign<&AffinePoint> for ProjectivePoint { - fn sub_assign(&mut self, rhs: &AffinePoint) { - *self = ProjectivePoint::sub_mixed(self, rhs); - } -} - -impl Mul 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 Mul<&Scalar> for ProjectivePoint { - type Output = ProjectivePoint; - - fn mul(self, other: &Scalar) -> ProjectivePoint { - ProjectivePoint::mul(&self, other) - } -} - -impl MulAssign 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) - } -} - -impl From for ProjectivePoint { - fn from(public_key: PublicKey) -> ProjectivePoint { - AffinePoint::from(public_key).into() - } -} - -impl From<&PublicKey> for ProjectivePoint { - fn from(public_key: &PublicKey) -> ProjectivePoint { - AffinePoint::from(public_key).into() - } -} - -impl TryFrom for PublicKey { - type Error = Error; - - fn try_from(point: ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -impl TryFrom<&ProjectivePoint> for PublicKey { - type Error = Error; - - fn try_from(point: &ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -#[cfg(test)] -mod tests { - use super::{AffinePoint, ProjectivePoint, Scalar}; - use crate::test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}; - use elliptic_curve::{group::prime::PrimeCurveAffine, PrimeField}; - - #[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(), basepoint_affine); - assert!(!bool::from(basepoint_projective.to_affine().is_identity())); - assert!(bool::from( - ProjectivePoint::IDENTITY.to_affine().is_identity() - )); - } - - #[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 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(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_sec1(), expected_x.into()); - assert_eq!(affine.y.to_sec1(), expected_y.into()); - - 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(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_sec1(), expected_x.into()); - assert_eq!(affine.y.to_sec1(), expected_y.into()); - - p += &generator; - } - } - - #[test] - fn test_vector_add_mixed_identity() { - let generator = ProjectivePoint::GENERATOR; - let p0 = generator + ProjectivePoint::IDENTITY; - let p1 = generator + AffinePoint::IDENTITY; - assert_eq!(p0, p1); - } - - #[test] - fn test_vector_double_generator() { - let generator = ProjectivePoint::GENERATOR; - let mut p = generator; - - for i in 0..2 { - let affine = p.to_affine(); - - let (expected_x, expected_y) = ADD_TEST_VECTORS[i]; - assert_eq!(affine.x.to_sec1(), expected_x.into()); - assert_eq!(affine.y.to_sec1(), expected_y.into()); - - 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_repr(k.into()).unwrap(), (x, y))), - ) - { - let res = (generator * &k).to_affine(); - assert_eq!(res.x.to_sec1(), coords.0.into()); - assert_eq!(res.y.to_sec1(), coords.1.into()); - } - } -} diff --git a/p384/src/arithmetic/scalar.rs b/p384/src/arithmetic/scalar.rs index db7994e2..f11d434d 100644 --- a/p384/src/arithmetic/scalar.rs +++ b/p384/src/arithmetic/scalar.rs @@ -19,7 +19,7 @@ use elliptic_curve::{ ff::PrimeField, ops::Reduce, subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}, - Curve as _, Error, IsHigh, Result, ScalarArithmetic, ScalarCore, + Curve as _, Error, IsHigh, Result, ScalarCore, }; #[cfg(feature = "bits")] @@ -31,10 +31,6 @@ use serdect::serde::{de, ser, Deserialize, Serialize}; #[cfg(doc)] use core::ops::{Add, Mul, Sub}; -impl ScalarArithmetic for NistP384 { - type Scalar = Scalar; -} - /// Scalars are elements in the finite field modulo `n`. /// /// # Trait impls diff --git a/p384/src/ecdh.rs b/p384/src/ecdh.rs index 622bfaa4..1e9ec85c 100644 --- a/p384/src/ecdh.rs +++ b/p384/src/ecdh.rs @@ -38,16 +38,10 @@ pub use elliptic_curve::ecdh::diffie_hellman; -use crate::{AffinePoint, NistP384}; +use crate::NistP384; /// NIST P-384 Ephemeral Diffie-Hellman Secret. pub type EphemeralSecret = elliptic_curve::ecdh::EphemeralSecret; /// Shared secret value computed via ECDH key agreement. pub type SharedSecret = elliptic_curve::ecdh::SharedSecret; - -impl From<&AffinePoint> for SharedSecret { - fn from(affine: &AffinePoint) -> SharedSecret { - affine.x.to_sec1().into() - } -} diff --git a/p384/src/lib.rs b/p384/src/lib.rs index 627b44c3..0f8fdc13 100644 --- a/p384/src/lib.rs +++ b/p384/src/lib.rs @@ -37,7 +37,7 @@ pub mod test_vectors; pub use elliptic_curve::{self, bigint::U384}; #[cfg(feature = "arithmetic")] -pub use arithmetic::{affine::AffinePoint, projective::ProjectivePoint, scalar::Scalar}; +pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint}; #[cfg(feature = "expose-field")] pub use arithmetic::field::FieldElement; diff --git a/p384/tests/affine.rs b/p384/tests/affine.rs new file mode 100644 index 00000000..f9313e6e --- /dev/null +++ b/p384/tests/affine.rs @@ -0,0 +1,82 @@ +//! Affine arithmetic tests. + +#![cfg(all(feature = "arithmetic", feature = "test-vectors"))] + +use elliptic_curve::{ + group::{prime::PrimeCurveAffine, GroupEncoding}, + sec1::{FromEncodedPoint, ToEncodedPoint}, +}; +use hex_literal::hex; + +use p384::{AffinePoint, EncodedPoint}; + +const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!( + "04 aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 + 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7 + 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c + e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f" +); + +const COMPRESSED_BASEPOINT: &[u8] = &hex!( + "03 aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 + 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7" +); + +#[test] +fn uncompressed_round_trip() { + let pubkey = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + assert_eq!(point, AffinePoint::generator()); + + let res: EncodedPoint = point.into(); + assert_eq!(res, pubkey); +} + +#[test] +fn compressed_round_trip() { + let pubkey = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + assert_eq!(point, AffinePoint::generator()); + + let res: EncodedPoint = point.to_encoded_point(true); + assert_eq!(res, pubkey); +} + +#[test] +fn uncompressed_to_compressed() { + let encoded = EncodedPoint::from_bytes(UNCOMPRESSED_BASEPOINT).unwrap(); + + let res = AffinePoint::from_encoded_point(&encoded) + .unwrap() + .to_encoded_point(true); + + assert_eq!(res.as_bytes(), COMPRESSED_BASEPOINT); +} + +#[test] +fn compressed_to_uncompressed() { + let encoded = EncodedPoint::from_bytes(COMPRESSED_BASEPOINT).unwrap(); + + let res = AffinePoint::from_encoded_point(&encoded) + .unwrap() + .to_encoded_point(false); + + assert_eq!(res.as_bytes(), UNCOMPRESSED_BASEPOINT); +} + +#[test] +fn affine_negation() { + let basepoint = AffinePoint::generator(); + assert_eq!(-(-basepoint), basepoint); +} + +#[test] +fn identity_encoding() { + // This is technically an invalid SEC1 encoding, but is preferable to panicking. + assert_eq!([0; 49], AffinePoint::IDENTITY.to_bytes().as_slice()); + assert!(bool::from( + AffinePoint::from_bytes(&AffinePoint::IDENTITY.to_bytes()) + .unwrap() + .is_identity() + )) +} diff --git a/p384/tests/projective.rs b/p384/tests/projective.rs new file mode 100644 index 00000000..cdcbe8e5 --- /dev/null +++ b/p384/tests/projective.rs @@ -0,0 +1,142 @@ +//! Projective arithmetic tests. + +#![cfg(all(feature = "arithmetic", feature = "test-vectors"))] + +use elliptic_curve::{ + sec1::{self, ToEncodedPoint}, + PrimeField, +}; +use p384::{ + test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}, + AffinePoint, ProjectivePoint, Scalar, +}; + +/// Assert that the provided projective point matches the given test vector. +// TODO(tarcieri): use coordinate APIs. See zkcrypto/group#30 +macro_rules! assert_point_eq { + ($actual:expr, $expected:expr) => { + let (expected_x, expected_y) = $expected; + + let point = $actual.to_affine().to_encoded_point(false); + let (actual_x, actual_y) = match point.coordinates() { + sec1::Coordinates::Uncompressed { x, y } => (x, y), + _ => unreachable!(), + }; + + assert_eq!(&expected_x, actual_x.as_slice()); + assert_eq!(&expected_y, actual_y.as_slice()); + }; +} + +#[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(), basepoint_affine); + assert!(!bool::from(basepoint_projective.to_affine().is_identity())); + assert!(bool::from( + ProjectivePoint::IDENTITY.to_affine().is_identity() + )); +} + +#[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 test_vector_repeated_add() { + let generator = ProjectivePoint::GENERATOR; + let mut p = generator; + + for i in 0..ADD_TEST_VECTORS.len() { + assert_point_eq!(p, ADD_TEST_VECTORS[i]); + 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() { + assert_point_eq!(p, ADD_TEST_VECTORS[i]); + p += &generator; + } +} + +#[test] +fn test_vector_add_mixed_identity() { + let generator = ProjectivePoint::GENERATOR; + let p0 = generator + ProjectivePoint::IDENTITY; + let p1 = generator + AffinePoint::IDENTITY; + assert_eq!(p0, p1); +} + +#[test] +fn test_vector_double_generator() { + let generator = ProjectivePoint::GENERATOR; + let mut p = generator; + + for i in 0..2 { + assert_point_eq!(p, 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_repr(k.into()).unwrap(), (x, y))), + ) + { + let p = generator * &k; + assert_point_eq!(p, coords); + } +} diff --git a/weierstrass/CHANGELOG.md b/weierstrass/CHANGELOG.md new file mode 100644 index 00000000..d6637e04 --- /dev/null +++ b/weierstrass/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/weierstrass/Cargo.toml b/weierstrass/Cargo.toml new file mode 100644 index 00000000..0ede2cf1 --- /dev/null +++ b/weierstrass/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "weierstrass" +version = "0.0.0" +description = """ +Pure Rust implementation of complete addition formulas for prime order elliptic +curves (Renes-Costello-Batina 2015). Generic over field elements and curve +equation coefficients +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/weierstrass" +repository = "https://github.com/RustCrypto/elliptic-curves/tree/master/weierstrass" +readme = "README.md" +categories = ["cryptography", "no-std"] +keywords = ["crypto", "ecc"] +edition = "2021" +rust-version = "1.57" + +[dependencies] +elliptic-curve = { version = "0.12.3", default-features = false, features = ["arithmetic", "sec1"] } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/weierstrass/LICENSE-APACHE b/weierstrass/LICENSE-APACHE new file mode 100644 index 00000000..78173fa2 --- /dev/null +++ b/weierstrass/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/weierstrass/LICENSE-MIT b/weierstrass/LICENSE-MIT new file mode 100644 index 00000000..d4ce0652 --- /dev/null +++ b/weierstrass/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020-2022 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/weierstrass/README.md b/weierstrass/README.md new file mode 100644 index 00000000..71214980 --- /dev/null +++ b/weierstrass/README.md @@ -0,0 +1,87 @@ +# RustCrypto: Weierstrass Formulas + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Pure Rust implementation of complete addition formulas for prime order elliptic +curves ([Renes-Costello-Batina 2015]). Generic over field elements and curve +equation coefficients. + +[Documentation][docs-link] + +## About + +This crate provides a generic implementation of complete formulas for prime +order elliptic curves which are defined by the short [Weierstrass equation]: + +```text +y² = x³ + ax + b +``` + +It's used to implement the following elliptic curves: + +- [`p256`]: NIST P-256 +- [`p384`]: NIST P-384 + +## ⚠️ Security Warning + +The elliptic curve arithmetic contained in this crate has never been +independently audited! + +This crate has been designed with the goal of ensuring that secret-dependent +operations are performed in constant time (using the `subtle` crate and +constant-time formulas). However, it has not been thoroughly assessed to ensure +that generated assembly is constant time on common CPU architectures. + +USE AT YOUR OWN RISK! + +## Minimum Supported Rust Version + +Rust **1.57** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +All crates licensed under either of: + +- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +- [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://buildstats.info/crate/weierstrass +[crate-link]: https://crates.io/crates/weierstrass +[docs-image]: https://docs.rs/weierstrass/badge.svg +[docs-link]: https://docs.rs/weierstrass/ +[build-image]: https://github.com/RustCrypto/elliptic-curves/actions/workflows/weierstrass.yml/badge.svg +[build-link]: https://github.com/RustCrypto/elliptic-curves/actions/workflows/weierstrass.yml +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.57+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260040-elliptic-curves + +[//]: # (links) + +[Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060 +[Weierstrass equation]: https://crypto.stanford.edu/pbc/notes/elliptic/weier.html +[`p256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p256 +[`p384`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p256 diff --git a/weierstrass/src/affine.rs b/weierstrass/src/affine.rs new file mode 100644 index 00000000..4ff0d9ba --- /dev/null +++ b/weierstrass/src/affine.rs @@ -0,0 +1,386 @@ +//! Affine curve points. + +#![allow(clippy::op_ref)] + +use crate::{ProjectivePoint, WeierstrassCurve}; +use core::{ + borrow::Borrow, + ops::{Mul, Neg}, +}; +use elliptic_curve::{ + ff::{Field, PrimeField}, + generic_array::ArrayLength, + group::{prime::PrimeCurveAffine, GroupEncoding}, + sec1::{ + self, CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint, + UncompressedPointSize, + }, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + AffineXCoordinate, DecompressPoint, Error, FieldBytes, FieldSize, PublicKey, Result, Scalar, +}; + +/// Point on a Weierstrass curve in affine coordinates. +#[derive(Clone, Copy, Debug)] +pub struct AffinePoint { + /// x-coordinate + pub(crate) x: C::FieldElement, + + /// y-coordinate + pub(crate) y: C::FieldElement, + + /// Is this point the point at infinity? 0 = no, 1 = yes + /// + /// This is a proxy for [`Choice`], but uses `u8` instead to permit `const` + /// constructors for `IDENTITY` and `GENERATOR`. + pub(crate) infinity: u8, +} + +impl AffinePoint { + /// Additive identity of the group a.k.a. the point at infinity. + pub const IDENTITY: Self = Self { + x: C::ZERO, + y: C::ZERO, + infinity: 1, + }; + + /// Base point of the curve. + pub const GENERATOR: Self = Self { + x: C::GENERATOR.0, + y: C::GENERATOR.1, + infinity: 0, + }; + + /// Is this point the point at infinity? + pub fn is_identity(&self) -> Choice { + Choice::from(self.infinity) + } +} + +impl AffineXCoordinate for AffinePoint +where + C: WeierstrassCurve, +{ + fn x(&self) -> FieldBytes { + self.x.to_repr() + } +} + +impl ConditionallySelectable for AffinePoint +where + C: WeierstrassCurve, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + x: C::FieldElement::conditional_select(&a.x, &b.x, choice), + y: C::FieldElement::conditional_select(&a.y, &b.y, choice), + infinity: u8::conditional_select(&a.infinity, &b.infinity, choice), + } + } +} + +impl ConstantTimeEq for AffinePoint +where + C: WeierstrassCurve, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) & self.infinity.ct_eq(&other.infinity) + } +} + +impl Default for AffinePoint +where + C: WeierstrassCurve, +{ + fn default() -> Self { + Self::IDENTITY + } +} + +impl DefaultIsZeroes for AffinePoint where C: WeierstrassCurve {} + +impl DecompressPoint for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, +{ + fn decompress(x_bytes: &FieldBytes, y_is_odd: Choice) -> CtOption { + C::FieldElement::from_repr(*x_bytes).and_then(|x| { + let alpha = x * &x * &x + &(C::EQUATION_A * &x) + &C::EQUATION_B; + let beta = alpha.sqrt(); + + beta.map(|beta| { + let y = C::FieldElement::conditional_select( + &-beta, + &beta, + beta.is_odd().ct_eq(&y_is_odd), + ); + + Self { x, y, infinity: 0 } + }) + }) + } +} + +impl Eq for AffinePoint {} + +impl FromEncodedPoint for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, +{ + /// Attempts to parse the given [`EncodedPoint`] as an SEC1-encoded + /// [`AffinePoint`]. + /// + /// # Returns + /// + /// `None` value if `encoded_point` is not on the secp384r1 curve. + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + match encoded_point.coordinates() { + sec1::Coordinates::Identity => CtOption::new(Self::IDENTITY, 1.into()), + // TODO(tarcieri): point decompaction support + sec1::Coordinates::Compact { .. } => CtOption::new(Self::IDENTITY, 0.into()), + sec1::Coordinates::Compressed { x, y_is_odd } => { + Self::decompress(x, Choice::from(y_is_odd as u8)) + } + sec1::Coordinates::Uncompressed { x, y } => { + let x = C::FieldElement::from_repr(*x); + let y = C::FieldElement::from_repr(*y); + + x.and_then(|x| { + y.and_then(|y| { + // Check that the point is on the curve + let lhs = y * &y; + let rhs = x * &x * &x + &(C::EQUATION_A * &x) + &C::EQUATION_B; + let point = AffinePoint { x, y, infinity: 0 }; + CtOption::new(point, lhs.ct_eq(&rhs)) + }) + }) + } + } + } +} + +impl From> for AffinePoint +where + C: WeierstrassCurve, +{ + fn from(p: ProjectivePoint) -> AffinePoint { + p.to_affine() + } +} + +impl From<&ProjectivePoint> for AffinePoint +where + C: WeierstrassCurve, +{ + fn from(p: &ProjectivePoint) -> AffinePoint { + p.to_affine() + } +} + +impl From> for AffinePoint +where + C: WeierstrassCurve, +{ + fn from(public_key: PublicKey) -> AffinePoint { + *public_key.as_affine() + } +} + +impl From<&PublicKey> for AffinePoint +where + C: WeierstrassCurve, +{ + fn from(public_key: &PublicKey) -> AffinePoint { + AffinePoint::from(*public_key) + } +} + +impl From> for EncodedPoint +where + C: WeierstrassCurve, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + fn from(affine: AffinePoint) -> EncodedPoint { + affine.to_encoded_point(false) + } +} + +impl GroupEncoding for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + type Repr = CompressedPoint; + + /// NOTE: not constant-time with respect to identity point + fn from_bytes(bytes: &Self::Repr) -> CtOption { + EncodedPoint::::from_bytes(bytes) + .map(|point| CtOption::new(point, Choice::from(1))) + .unwrap_or_else(|_| { + // SEC1 identity encoding is technically 1-byte 0x00, but the + // `GroupEncoding` API requires a fixed-width `Repr` + let is_identity = bytes.ct_eq(&Self::Repr::default()); + CtOption::new(EncodedPoint::::identity(), is_identity) + }) + .and_then(|point| Self::from_encoded_point(&point)) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // No unchecked conversion possible for compressed points + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + let encoded = self.to_encoded_point(true); + let mut result = CompressedPoint::::default(); + result[..encoded.len()].copy_from_slice(encoded.as_bytes()); + result + } +} + +impl PartialEq for AffinePoint +where + C: WeierstrassCurve, +{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl PrimeCurveAffine for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + type Curve = ProjectivePoint; + type Scalar = Scalar; + + fn identity() -> AffinePoint { + Self::IDENTITY + } + + fn generator() -> AffinePoint { + Self::GENERATOR + } + + fn is_identity(&self) -> Choice { + self.is_identity() + } + + fn to_curve(&self) -> ProjectivePoint { + ProjectivePoint::from(*self) + } +} + +impl ToEncodedPoint for AffinePoint +where + C: WeierstrassCurve, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + EncodedPoint::::conditional_select( + &EncodedPoint::::from_affine_coordinates( + &self.x.to_repr(), + &self.y.to_repr(), + compress, + ), + &EncodedPoint::::identity(), + self.is_identity(), + ) + } +} + +impl TryFrom> for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, +{ + type Error = Error; + + fn try_from(point: EncodedPoint) -> Result> { + AffinePoint::try_from(&point) + } +} + +impl TryFrom<&EncodedPoint> for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, +{ + type Error = Error; + + fn try_from(point: &EncodedPoint) -> Result> { + Option::from(AffinePoint::::from_encoded_point(point)).ok_or(Error) + } +} + +impl TryFrom> for PublicKey +where + C: WeierstrassCurve, +{ + type Error = Error; + + fn try_from(affine_point: AffinePoint) -> Result> { + PublicKey::from_affine(affine_point) + } +} + +impl TryFrom<&AffinePoint> for PublicKey +where + C: WeierstrassCurve, +{ + type Error = Error; + + fn try_from(affine_point: &AffinePoint) -> Result> { + PublicKey::::try_from(*affine_point) + } +} + +// +// Arithmetic trait impls +// + +impl Mul for AffinePoint +where + C: WeierstrassCurve, + S: Borrow>, +{ + type Output = ProjectivePoint; + + fn mul(self, scalar: S) -> ProjectivePoint { + ProjectivePoint::::from(self) * scalar + } +} + +impl Neg for AffinePoint +where + C: WeierstrassCurve, +{ + type Output = Self; + + fn neg(self) -> Self { + AffinePoint { + x: self.x, + y: -self.y, + infinity: self.infinity, + } + } +} diff --git a/weierstrass/src/lib.rs b/weierstrass/src/lib.rs new file mode 100644 index 00000000..ddfd3f79 --- /dev/null +++ b/weierstrass/src/lib.rs @@ -0,0 +1,45 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] +#![doc = include_str!("../README.md")] + +mod affine; +mod projective; + +pub use crate::{affine::AffinePoint, projective::ProjectivePoint}; +pub use elliptic_curve::{self, Field, FieldBytes, PrimeCurve, PrimeField}; + +use elliptic_curve::{AffineArithmetic, ProjectiveArithmetic, ScalarArithmetic}; + +/// Weierstrass curve parameters. +pub trait WeierstrassCurve: + PrimeCurve + + ScalarArithmetic + + AffineArithmetic> + + ProjectiveArithmetic> +{ + /// Base field element type. + type FieldElement: PrimeField>; + + /// Zero element of the base field. + // TODO(tarcieri): use `Field` trait instead. See zkcrypto/ff#87 + const ZERO: Self::FieldElement; + + /// Multiplicative identity of the base field. + // TODO(tarcieri): use `Field` trait instead. See zkcrypto/ff#87 + const ONE: Self::FieldElement; + + /// Coefficient `a` in the curve equation. + const EQUATION_A: Self::FieldElement; + + /// Coefficient `b` in the curve equation. + const EQUATION_B: Self::FieldElement; + + /// Generator point's affine coordinates: (x, y). + const GENERATOR: (Self::FieldElement, Self::FieldElement); +} diff --git a/weierstrass/src/projective.rs b/weierstrass/src/projective.rs new file mode 100644 index 00000000..bd358df0 --- /dev/null +++ b/weierstrass/src/projective.rs @@ -0,0 +1,719 @@ +//! Projective curve points. + +#![allow(clippy::needless_range_loop, clippy::op_ref)] + +use crate::{AffinePoint, Field, WeierstrassCurve}; +use core::{ + borrow::Borrow, + iter::Sum, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; +use elliptic_curve::{ + bigint::{ArrayEncoding, Encoding}, + generic_array::ArrayLength, + group::{ + self, + cofactor::CofactorGroup, + prime::{PrimeCurve, PrimeGroup}, + Group, GroupEncoding, + }, + ops::LinearCombination, + rand_core::RngCore, + sec1::{CompressedPoint, ModulusSize, UncompressedPointSize}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + Error, FieldBytes, FieldSize, PublicKey, Result, Scalar, +}; + +/// Point on a Weierstrass curve in projective coordinates. +#[derive(Clone, Copy, Debug)] +pub struct ProjectivePoint { + x: C::FieldElement, + y: C::FieldElement, + z: C::FieldElement, +} + +impl ProjectivePoint +where + C: WeierstrassCurve, +{ + /// Additive identity of the group a.k.a. the point at infinity. + pub const IDENTITY: Self = Self { + x: C::ZERO, + y: C::ONE, + z: C::ZERO, + }; + + /// Base point of the curve. + pub const GENERATOR: Self = Self { + x: C::GENERATOR.0, + y: C::GENERATOR.1, + z: C::ONE, + }; + + /// Returns the affine representation of this point, or `None` if it is the identity. + pub fn to_affine(&self) -> AffinePoint { + self.z + .invert() + .map(|zinv| AffinePoint { + x: self.x * &zinv, + y: self.y * &zinv, + infinity: 0, + }) + .unwrap_or(AffinePoint::IDENTITY) + } + + /// Returns `-self`. + pub fn neg(&self) -> Self { + Self { + x: self.x, + y: -self.y, + z: self.z, + } + } + + /// Returns `self + other`. + pub fn add(&self, other: &Self) -> Self { + // We implement the complete addition formula from Renes-Costello-Batina 2015 + // (https://eprint.iacr.org/2015/1060 Algorithm 4). The comments after each line + // indicate which algorithm steps are being performed. + + let xx = self.x * &other.x; // 1 + let yy = self.y * &other.y; // 2 + let zz = self.z * &other.z; // 3 + let xy_pairs = ((self.x + &self.y) * &(other.x + &other.y)) - &(xx + &yy); // 4, 5, 6, 7, 8 + let yz_pairs = ((self.y + &self.z) * &(other.y + &other.z)) - &(yy + &zz); // 9, 10, 11, 12, 13 + let xz_pairs = ((self.x + &self.z) * &(other.x + &other.z)) - &(xx + &zz); // 14, 15, 16, 17, 18 + + let bzz_part = xz_pairs - &(C::EQUATION_B * &zz); // 19, 20 + let bzz3_part = bzz_part.double() + &bzz_part; // 21, 22 + let yy_m_bzz3 = yy - &bzz3_part; // 23 + let yy_p_bzz3 = yy + &bzz3_part; // 24 + + let zz3 = zz.double() + &zz; // 26, 27 + let bxz_part = (C::EQUATION_B * &xz_pairs) - &(zz3 + &xx); // 25, 28, 29 + let bxz3_part = bxz_part.double() + &bxz_part; // 30, 31 + let xx3_m_zz3 = xx.double() + &xx - &zz3; // 32, 33, 34 + + Self { + x: (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), // 35, 39, 40 + y: (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), // 36, 37, 38 + z: (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), // 41, 42, 43 + } + } + + /// Returns `self + other`. + fn add_mixed(&self, other: &AffinePoint) -> Self { + // We implement the complete mixed addition formula from Renes-Costello-Batina + // 2015 (Algorithm 5). The comments after each line indicate which algorithm + // steps are being performed. + + let xx = self.x * &other.x; // 1 + let yy = self.y * &other.y; // 2 + let xy_pairs = ((self.x + &self.y) * &(other.x + &other.y)) - &(xx + &yy); // 3, 4, 5, 6, 7 + let yz_pairs = (other.y * &self.z) + &self.y; // 8, 9 (t4) + let xz_pairs = (other.x * &self.z) + &self.x; // 10, 11 (y3) + + let bz_part = xz_pairs - &(C::EQUATION_B * &self.z); // 12, 13 + let bz3_part = bz_part.double() + &bz_part; // 14, 15 + let yy_m_bzz3 = yy - &bz3_part; // 16 + let yy_p_bzz3 = yy + &bz3_part; // 17 + + let z3 = self.z.double() + &self.z; // 19, 20 + let bxz_part = (C::EQUATION_B * &xz_pairs) - &(z3 + &xx); // 18, 21, 22 + let bxz3_part = bxz_part.double() + &bxz_part; // 23, 24 + let xx3_m_zz3 = xx.double() + &xx - &z3; // 25, 26, 27 + + let mut ret = Self { + x: (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), // 28, 32, 33 + y: (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), // 29, 30, 31 + z: (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), // 34, 35, 36 + }; + ret.conditional_assign(self, other.is_identity()); + ret + } + + /// Doubles this point. + pub fn double(&self) -> Self { + // We implement the exception-free point doubling formula from + // Renes-Costello-Batina 2015 (Algorithm 6). The comments after each line + // indicate which algorithm steps are being performed. + + let xx = self.x.square(); // 1 + let yy = self.y.square(); // 2 + let zz = self.z.square(); // 3 + let xy2 = (self.x * &self.y).double(); // 4, 5 + let xz2 = (self.x * &self.z).double(); // 6, 7 + + let bzz_part = (C::EQUATION_B * &zz) - &xz2; // 8, 9 + let bzz3_part = bzz_part.double() + &bzz_part; // 10, 11 + let yy_m_bzz3 = yy - &bzz3_part; // 12 + let yy_p_bzz3 = yy + &bzz3_part; // 13 + let y_frag = yy_p_bzz3 * &yy_m_bzz3; // 14 + let x_frag = yy_m_bzz3 * &xy2; // 15 + + let zz3 = zz.double() + &zz; // 16, 17 + let bxz2_part = (C::EQUATION_B * &xz2) - &(zz3 + &xx); // 18, 19, 20 + let bxz6_part = bxz2_part.double() + &bxz2_part; // 21, 22 + let xx3_m_zz3 = xx.double() + &xx - &zz3; // 23, 24, 25 + + let y = y_frag + &(xx3_m_zz3 * &bxz6_part); // 26, 27 + let yz2 = (self.y * &self.z).double(); // 28, 29 + let x = x_frag - &(bxz6_part * &yz2); // 30, 31 + let z = (yz2 * &yy).double().double(); // 32, 33, 34 + + Self { x, y, z } + } + + /// Returns `self - other`. + pub fn sub(&self, other: &Self) -> Self { + self.add(&other.neg()) + } + + /// Returns `self - other`. + fn sub_mixed(&self, other: &AffinePoint) -> Self { + self.add_mixed(&other.neg()) + } + + /// Returns `[k] self`. + fn mul(&self, k: &Scalar) -> Self { + let k: C::UInt = (*k).into(); + let k = k.to_le_byte_array(); + + let mut pc = [Self::default(); 16]; + pc[0] = Self::IDENTITY; + pc[1] = *self; + + for i in 2..16 { + pc[i] = if i % 2 == 0 { + pc[i / 2].double() + } else { + pc[i - 1].add(self) + }; + } + + let mut q = Self::IDENTITY; + let mut pos = C::UInt::BIT_SIZE - 4; + + loop { + let slot = (k[(pos >> 3) as usize] >> (pos & 7)) & 0xf; + + let mut t = ProjectivePoint::IDENTITY; + + for i in 1..16 { + t.conditional_assign( + &pc[i], + Choice::from(((slot as usize ^ i).wrapping_sub(1) >> 8) as u8 & 1), + ); + } + + q = q.add(&t); + + if pos == 0 { + break; + } + + q = q.double().double().double().double(); + pos -= 4; + } + + q + } +} + +impl CofactorGroup for ProjectivePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + type Subgroup = Self; + + fn clear_cofactor(&self) -> Self::Subgroup { + *self + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, 1.into()) + } + + fn is_torsion_free(&self) -> Choice { + 1.into() + } +} + +impl ConditionallySelectable for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + x: C::FieldElement::conditional_select(&a.x, &b.x, choice), + y: C::FieldElement::conditional_select(&a.y, &b.y, choice), + z: C::FieldElement::conditional_select(&a.z, &b.z, choice), + } + } +} + +impl ConstantTimeEq for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.to_affine().ct_eq(&other.to_affine()) + } +} + +impl Default for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn default() -> Self { + Self::IDENTITY + } +} + +impl DefaultIsZeroes for ProjectivePoint where C: WeierstrassCurve {} + +impl Eq for ProjectivePoint where C: WeierstrassCurve {} + +impl From> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn from(p: AffinePoint) -> Self { + let projective = ProjectivePoint { + x: p.x, + y: p.y, + z: C::ONE, + }; + Self::conditional_select(&projective, &Self::IDENTITY, p.is_identity()) + } +} + +impl From<&AffinePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn from(p: &AffinePoint) -> Self { + Self::from(*p) + } +} + +impl From> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn from(public_key: PublicKey) -> ProjectivePoint { + AffinePoint::from(public_key).into() + } +} + +impl From<&PublicKey> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn from(public_key: &PublicKey) -> ProjectivePoint { + AffinePoint::::from(public_key).into() + } +} + +impl Group for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + Self::GENERATOR * as Field>::random(&mut rng) + } + + fn identity() -> Self { + Self::IDENTITY + } + + fn generator() -> Self { + Self::GENERATOR + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Self::IDENTITY) + } + + #[must_use] + fn double(&self) -> Self { + ProjectivePoint::double(self) + } +} + +impl GroupEncoding for ProjectivePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + type Repr = CompressedPoint; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + as GroupEncoding>::from_bytes(bytes).map(Into::into) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // No unchecked conversion possible for compressed points + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.to_affine().to_bytes() + } +} + +impl group::Curve for ProjectivePoint +where + C: WeierstrassCurve, +{ + type AffineRepr = AffinePoint; + + fn to_affine(&self) -> AffinePoint { + ProjectivePoint::to_affine(self) + } +} + +impl LinearCombination for ProjectivePoint where C: WeierstrassCurve {} + +impl PrimeGroup for ProjectivePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ +} + +impl PrimeCurve for ProjectivePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + type Affine = AffinePoint; +} + +impl PartialEq for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl TryFrom> for PublicKey +where + C: WeierstrassCurve, +{ + type Error = Error; + + fn try_from(point: ProjectivePoint) -> Result> { + AffinePoint::::from(point).try_into() + } +} + +impl TryFrom<&ProjectivePoint> for PublicKey +where + C: WeierstrassCurve, +{ + type Error = Error; + + fn try_from(point: &ProjectivePoint) -> Result> { + AffinePoint::::from(point).try_into() + } +} + +// +// Arithmetic trait impls +// + +impl Add> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::add(&self, &other) + } +} + +impl Add<&ProjectivePoint> for &ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: &ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::add(self, other) + } +} + +impl Add<&ProjectivePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: &ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::add(&self, other) + } +} + +impl AddAssign> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn add_assign(&mut self, rhs: ProjectivePoint) { + *self = ProjectivePoint::add(self, &rhs); + } +} + +impl AddAssign<&ProjectivePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn add_assign(&mut self, rhs: &ProjectivePoint) { + *self = ProjectivePoint::add(self, rhs); + } +} + +impl Add> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: AffinePoint) -> ProjectivePoint { + ProjectivePoint::add_mixed(&self, &other) + } +} + +impl Add<&AffinePoint> for &ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: &AffinePoint) -> ProjectivePoint { + ProjectivePoint::add_mixed(self, other) + } +} + +impl Add<&AffinePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn add(self, other: &AffinePoint) -> ProjectivePoint { + ProjectivePoint::add_mixed(&self, other) + } +} + +impl AddAssign> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn add_assign(&mut self, rhs: AffinePoint) { + *self = ProjectivePoint::add_mixed(self, &rhs); + } +} + +impl AddAssign<&AffinePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn add_assign(&mut self, rhs: &AffinePoint) { + *self = ProjectivePoint::add_mixed(self, rhs); + } +} + +impl Sum for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sum>(iter: I) -> Self { + iter.fold(ProjectivePoint::IDENTITY, |a, b| a + b) + } +} + +impl<'a, C> Sum<&'a ProjectivePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sum>>(iter: I) -> Self { + iter.cloned().sum() + } +} + +impl Sub> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::sub(&self, &other) + } +} + +impl Sub<&ProjectivePoint> for &ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: &ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::sub(self, other) + } +} + +impl Sub<&ProjectivePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: &ProjectivePoint) -> ProjectivePoint { + ProjectivePoint::sub(&self, other) + } +} + +impl SubAssign> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sub_assign(&mut self, rhs: ProjectivePoint) { + *self = ProjectivePoint::sub(self, &rhs); + } +} + +impl SubAssign<&ProjectivePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sub_assign(&mut self, rhs: &ProjectivePoint) { + *self = ProjectivePoint::sub(self, rhs); + } +} + +impl Sub> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: AffinePoint) -> ProjectivePoint { + ProjectivePoint::sub_mixed(&self, &other) + } +} + +impl Sub<&AffinePoint> for &ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: &AffinePoint) -> ProjectivePoint { + ProjectivePoint::sub_mixed(self, other) + } +} + +impl Sub<&AffinePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn sub(self, other: &AffinePoint) -> ProjectivePoint { + ProjectivePoint::sub_mixed(&self, other) + } +} + +impl SubAssign> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sub_assign(&mut self, rhs: AffinePoint) { + *self = ProjectivePoint::sub_mixed(self, &rhs); + } +} + +impl SubAssign<&AffinePoint> for ProjectivePoint +where + C: WeierstrassCurve, +{ + fn sub_assign(&mut self, rhs: &AffinePoint) { + *self = ProjectivePoint::sub_mixed(self, rhs); + } +} + +impl Mul for ProjectivePoint +where + C: WeierstrassCurve, + S: Borrow>, +{ + type Output = Self; + + fn mul(self, scalar: S) -> Self { + ProjectivePoint::mul(&self, scalar.borrow()) + } +} + +impl Mul<&Scalar> for &ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn mul(self, scalar: &Scalar) -> ProjectivePoint { + ProjectivePoint::mul(self, scalar) + } +} + +impl MulAssign for ProjectivePoint +where + C: WeierstrassCurve, + S: Borrow>, +{ + fn mul_assign(&mut self, scalar: S) { + *self = ProjectivePoint::mul(self, scalar.borrow()); + } +} + +impl Neg for ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn neg(self) -> ProjectivePoint { + ProjectivePoint::neg(&self) + } +} + +impl<'a, C> Neg for &'a ProjectivePoint +where + C: WeierstrassCurve, +{ + type Output = ProjectivePoint; + + fn neg(self) -> ProjectivePoint { + ProjectivePoint::neg(self) + } +} From dcac7b3216563d14d99ffd0bd7f735531db540fd Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 2 Aug 2022 19:11:44 -0600 Subject: [PATCH 2/4] serde support --- Cargo.lock | 1 + weierstrass/Cargo.toml | 7 ++++++ weierstrass/src/affine.rs | 43 +++++++++++++++++++++++++++++++++++ weierstrass/src/projective.rs | 3 +-- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c197b61..07a16366 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1197,6 +1197,7 @@ name = "weierstrass" version = "0.0.0" dependencies = [ "elliptic-curve", + "serdect", ] [[package]] diff --git a/weierstrass/Cargo.toml b/weierstrass/Cargo.toml index 0ede2cf1..271683a6 100644 --- a/weierstrass/Cargo.toml +++ b/weierstrass/Cargo.toml @@ -19,6 +19,13 @@ rust-version = "1.57" [dependencies] elliptic-curve = { version = "0.12.3", default-features = false, features = ["arithmetic", "sec1"] } +# optional dependencies +serdect = { version = "0.1", optional = true, default-features = false } + +[features] +std = ["elliptic-curve/std"] +serde = ["elliptic-curve/serde", "serdect"] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/weierstrass/src/affine.rs b/weierstrass/src/affine.rs index 4ff0d9ba..9c8efc34 100644 --- a/weierstrass/src/affine.rs +++ b/weierstrass/src/affine.rs @@ -20,6 +20,9 @@ use elliptic_curve::{ AffineXCoordinate, DecompressPoint, Error, FieldBytes, FieldSize, PublicKey, Result, Scalar, }; +#[cfg(feature = "serde")] +use serdect::serde::{de, ser, Deserialize, Serialize}; + /// Point on a Weierstrass curve in affine coordinates. #[derive(Clone, Copy, Debug)] pub struct AffinePoint { @@ -384,3 +387,43 @@ where } } } + +// +// serde support +// + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl Serialize for AffinePoint +where + C: WeierstrassCurve, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + fn serialize(&self, serializer: S) -> core::result::Result + where + S: ser::Serializer, + { + self.to_encoded_point(true).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl<'de, C> Deserialize<'de> for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, + FieldSize: ModulusSize, + CompressedPoint: Copy, +{ + fn deserialize(deserializer: D) -> core::result::Result + where + D: de::Deserializer<'de>, + { + EncodedPoint::::deserialize(deserializer)? + .try_into() + .map_err(de::Error::custom) + } +} diff --git a/weierstrass/src/projective.rs b/weierstrass/src/projective.rs index bd358df0..48977c35 100644 --- a/weierstrass/src/projective.rs +++ b/weierstrass/src/projective.rs @@ -177,8 +177,7 @@ where /// Returns `[k] self`. fn mul(&self, k: &Scalar) -> Self { - let k: C::UInt = (*k).into(); - let k = k.to_le_byte_array(); + let k = Into::::into(*k).to_le_byte_array(); let mut pc = [Self::default(); 16]; pc[0] = Self::IDENTITY; From c4a9fc519b326ae17ca8fa9920840150361dc5ee Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 2 Aug 2022 19:48:39 -0600 Subject: [PATCH 3/4] PrimeField definitions --- p256/src/arithmetic/field.rs | 16 ++++++++++++---- p384/src/arithmetic/field.rs | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/p256/src/arithmetic/field.rs b/p256/src/arithmetic/field.rs index 39921a05..9317abce 100644 --- a/p256/src/arithmetic/field.rs +++ b/p256/src/arithmetic/field.rs @@ -449,8 +449,8 @@ impl PrimeField for FieldElement { type Repr = FieldBytes; const NUM_BITS: u32 = 256; - const CAPACITY: u32 = 0; // TODO(tarcieri): bogus! needs real value - const S: u32 = 0; // TODO(tarcieri): bogus! needs real value + const CAPACITY: u32 = 255; + const S: u32 = 1; fn from_repr(bytes: FieldBytes) -> CtOption { Self::from_bytes(&bytes) @@ -465,11 +465,19 @@ impl PrimeField for FieldElement { } fn multiplicative_generator() -> Self { - todo!() + 6.into() } fn root_of_unity() -> Self { - todo!() + Self::from_repr( + [ + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, + ] + .into(), + ) + .unwrap() } } diff --git a/p384/src/arithmetic/field.rs b/p384/src/arithmetic/field.rs index 97c9b285..d0789ee5 100644 --- a/p384/src/arithmetic/field.rs +++ b/p384/src/arithmetic/field.rs @@ -133,8 +133,8 @@ impl PrimeField for FieldElement { type Repr = FieldBytes; const NUM_BITS: u32 = 384; - const CAPACITY: u32 = 0; // TODO(tarcieri): bogus! needs real value - const S: u32 = 0; // TODO(tarcieri): bogus! needs real value + const CAPACITY: u32 = 383; + const S: u32 = 1; fn from_repr(bytes: FieldBytes) -> CtOption { Self::from_be_bytes(bytes) @@ -149,11 +149,20 @@ impl PrimeField for FieldElement { } fn multiplicative_generator() -> Self { - todo!() + 19.into() } fn root_of_unity() -> Self { - todo!() + Self::from_repr( + [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xff, 0xff, 0xff, 0xfe, + ] + .into(), + ) + .unwrap() } } From 4d56c72555fe47f4a5319f25579e93b1dca200fd Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 2 Aug 2022 20:09:45 -0600 Subject: [PATCH 4/4] Point compaction support --- p256/tests/affine.rs | 81 ++++++++++++++++++----------------- weierstrass/src/affine.rs | 89 ++++++++++++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 60 deletions(-) diff --git a/p256/tests/affine.rs b/p256/tests/affine.rs index d3cc2677..daedbada 100644 --- a/p256/tests/affine.rs +++ b/p256/tests/affine.rs @@ -6,7 +6,7 @@ use elliptic_curve::{ group::{prime::PrimeCurveAffine, GroupEncoding}, - sec1::{FromEncodedPoint, ToEncodedPoint}, + sec1::{FromEncodedPoint, ToCompactEncodedPoint, ToEncodedPoint}, }; use hex_literal::hex; use p256::{AffinePoint, EncodedPoint}; @@ -19,15 +19,15 @@ const UNCOMPRESSED_BASEPOINT: &[u8] = &hex!( const COMPRESSED_BASEPOINT: &[u8] = &hex!("03 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"); -// // Tag compact with 05 as the first byte, to trigger tag based compaction -// const COMPACT_BASEPOINT: &[u8] = -// &hex!("05 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01"); -// -// // Tag uncompact basepoint with 04 as the first byte as it is uncompressed -// const UNCOMPACT_BASEPOINT: &[u8] = &hex!( -// "04 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c0 -// 13ca9d8667de0c07aa71d98b3c8065d2e97ab7bb9cb8776bcc0577a7ac58acd4e" -// ); +// Tag compact with 05 as the first byte, to trigger tag based compaction +const COMPACT_BASEPOINT: &[u8] = + &hex!("05 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c01"); + +// Tag uncompact basepoint with 04 as the first byte as it is uncompressed +const UNCOMPACT_BASEPOINT: &[u8] = &hex!( + "04 8e38fc4ffe677662dde8e1a63fbcd45959d2a4c3004d27e98c4fedf2d0c14c0 + 13ca9d8667de0c07aa71d98b3c8065d2e97ab7bb9cb8776bcc0577a7ac58acd4e" +); #[test] fn uncompressed_round_trip() { @@ -77,37 +77,36 @@ fn affine_negation() { assert_eq!(-(-basepoint), basepoint); } -// TODO(tarcieri): fix point compaction -// #[test] -// fn compact_round_trip() { -// let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); -// assert!(pubkey.is_compact()); -// -// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); -// let res = point.to_compact_encoded_point().unwrap(); -// assert_eq!(res, pubkey) -// } - -// #[test] -// fn uncompact_to_compact() { -// let pubkey = EncodedPoint::from_bytes(UNCOMPACT_BASEPOINT).unwrap(); -// assert_eq!(false, pubkey.is_compact()); -// -// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); -// let res = point.to_compact_encoded_point().unwrap(); -// assert_eq!(res.as_bytes(), COMPACT_BASEPOINT) -// } - -// #[test] -// fn compact_to_uncompact() { -// let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); -// assert!(pubkey.is_compact()); -// -// let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); -// // Do not do compact encoding as we want to keep uncompressed point -// let res = point.to_encoded_point(false); -// assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT); -// } +#[test] +fn compact_round_trip() { + let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); + assert!(pubkey.is_compact()); + + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + let res = point.to_compact_encoded_point().unwrap(); + assert_eq!(res, pubkey) +} + +#[test] +fn uncompact_to_compact() { + let pubkey = EncodedPoint::from_bytes(UNCOMPACT_BASEPOINT).unwrap(); + assert_eq!(false, pubkey.is_compact()); + + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + let res = point.to_compact_encoded_point().unwrap(); + assert_eq!(res.as_bytes(), COMPACT_BASEPOINT) +} + +#[test] +fn compact_to_uncompact() { + let pubkey = EncodedPoint::from_bytes(COMPACT_BASEPOINT).unwrap(); + assert!(pubkey.is_compact()); + + let point = AffinePoint::from_encoded_point(&pubkey).unwrap(); + // Do not do compact encoding as we want to keep uncompressed point + let res = point.to_encoded_point(false); + assert_eq!(res.as_bytes(), UNCOMPACT_BASEPOINT); +} #[test] fn identity_encoding() { diff --git a/weierstrass/src/affine.rs b/weierstrass/src/affine.rs index 9c8efc34..3d816194 100644 --- a/weierstrass/src/affine.rs +++ b/weierstrass/src/affine.rs @@ -8,16 +8,18 @@ use core::{ ops::{Mul, Neg}, }; use elliptic_curve::{ + bigint::ArrayEncoding, ff::{Field, PrimeField}, generic_array::ArrayLength, group::{prime::PrimeCurveAffine, GroupEncoding}, sec1::{ - self, CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint, - UncompressedPointSize, + self, CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToCompactEncodedPoint, + ToEncodedPoint, UncompressedPointSize, }, - subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}, zeroize::DefaultIsZeroes, - AffineXCoordinate, DecompressPoint, Error, FieldBytes, FieldSize, PublicKey, Result, Scalar, + AffineXCoordinate, DecompactPoint, DecompressPoint, Error, FieldBytes, FieldSize, PublicKey, + Result, Scalar, }; #[cfg(feature = "serde")] @@ -39,7 +41,10 @@ pub struct AffinePoint { pub(crate) infinity: u8, } -impl AffinePoint { +impl AffinePoint +where + C: WeierstrassCurve, +{ /// Additive identity of the group a.k.a. the point at infinity. pub const IDENTITY: Self = Self { x: C::ZERO, @@ -58,6 +63,19 @@ impl AffinePoint { pub fn is_identity(&self) -> Choice { Choice::from(self.infinity) } + + /// Conditionally negate [`AffinePoint`] for use with point compaction. + fn to_compact(self) -> Self { + let neg_self = -self; + let choice = C::UInt::from_be_byte_array(self.y.to_repr()) + .ct_gt(&C::UInt::from_be_byte_array(neg_self.y.to_repr())); + + Self { + x: self.x, + y: C::FieldElement::conditional_select(&self.y, &neg_self.y, choice), + infinity: self.infinity, + } + } } impl AffineXCoordinate for AffinePoint @@ -125,7 +143,17 @@ where } } -impl Eq for AffinePoint {} +impl DecompactPoint for AffinePoint +where + C: WeierstrassCurve, + FieldBytes: Copy, +{ + fn decompact(x_bytes: &FieldBytes) -> CtOption { + Self::decompress(x_bytes, Choice::from(0)).map(|point| point.to_compact()) + } +} + +impl Eq for AffinePoint where C: WeierstrassCurve {} impl FromEncodedPoint for AffinePoint where @@ -143,23 +171,14 @@ where fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { match encoded_point.coordinates() { sec1::Coordinates::Identity => CtOption::new(Self::IDENTITY, 1.into()), - // TODO(tarcieri): point decompaction support - sec1::Coordinates::Compact { .. } => CtOption::new(Self::IDENTITY, 0.into()), + sec1::Coordinates::Compact { x } => Self::decompact(x), sec1::Coordinates::Compressed { x, y_is_odd } => { Self::decompress(x, Choice::from(y_is_odd as u8)) } sec1::Coordinates::Uncompressed { x, y } => { - let x = C::FieldElement::from_repr(*x); - let y = C::FieldElement::from_repr(*y); - - x.and_then(|x| { - y.and_then(|y| { - // Check that the point is on the curve - let lhs = y * &y; - let rhs = x * &x * &x + &(C::EQUATION_A * &x) + &C::EQUATION_B; - let point = AffinePoint { x, y, infinity: 0 }; - CtOption::new(point, lhs.ct_eq(&rhs)) - }) + C::FieldElement::from_repr(*y).and_then(|y| { + Self::decompress(x, y.is_odd()) + .and_then(|point| CtOption::new(point, point.y.ct_eq(&y))) }) } } @@ -287,6 +306,27 @@ where } } +impl ToCompactEncodedPoint for AffinePoint +where + C: WeierstrassCurve, + FieldSize: ModulusSize, + CompressedPoint: Copy, + as ArrayLength>::ArrayType: Copy, +{ + /// Serialize this value as a SEC1 compact [`EncodedPoint`] + fn to_compact_encoded_point(&self) -> CtOption> { + let point = self.to_compact(); + + let mut bytes = CompressedPoint::::default(); + bytes[0] = sec1::Tag::Compact.into(); + bytes[1..].copy_from_slice(&point.x.to_repr()); + + let encoded = EncodedPoint::::from_bytes(bytes); + let is_some = Choice::from(encoded.is_ok() as u8); + CtOption::new(encoded.unwrap_or_default(), is_some) + } +} + impl ToEncodedPoint for AffinePoint where C: WeierstrassCurve, @@ -388,6 +428,17 @@ where } } +impl Neg for &AffinePoint +where + C: WeierstrassCurve, +{ + type Output = AffinePoint; + + fn neg(self) -> AffinePoint { + -(*self) + } +} + // // serde support //