Skip to content

Commit

Permalink
Improved k256 point-scalar multiplication (#82)
Browse files Browse the repository at this point in the history
* Move point-scalar multiplication to its own module

* Add endomorphism multiplication and an advanced windowed method from Ristretto

* Make `from_bytes_unchecked()` for Scalar and Field pribate to the crate
  • Loading branch information
fjarri authored Jul 21, 2020
1 parent 1db2618 commit 0586693
Show file tree
Hide file tree
Showing 12 changed files with 501 additions and 67 deletions.
1 change: 1 addition & 0 deletions k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ criterion = "0.3"
default = ["arithmetic", "std"]
arithmetic = []
digest = ["ecdsa/digest"]
endomorphism-mul = []
field-montgomery = []
force-32-bit = []
rand = ["elliptic-curve/rand_core"]
Expand Down
80 changes: 19 additions & 61 deletions k256/src/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub(crate) mod scalar;
mod util;

use core::convert::TryInto;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use elliptic_curve::{
subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption},
weierstrass::FixedBaseScalarMul,
Expand Down Expand Up @@ -35,6 +35,15 @@ pub(crate) const CURVE_EQUATION_B: FieldElement = FieldElement::from_bytes_unche
0, 0, 0, 0, 0, 0, 0, CURVE_EQUATION_B_SINGLE as u8,
]);

#[rustfmt::skip]
#[cfg(feature = "endomorphism-mul")]
const ENDOMORPHISM_BETA: FieldElement = FieldElement::from_bytes_unchecked(&[
0x7a, 0xe9, 0x6a, 0x2b, 0x65, 0x7c, 0x07, 0x10,
0x6e, 0x64, 0x47, 0x9e, 0xac, 0x34, 0x34, 0xe9,
0x9c, 0xf0, 0x49, 0x75, 0x12, 0xf5, 0x89, 0x95,
0xc1, 0x39, 0x6c, 0x28, 0x71, 0x95, 0x01, 0xee,
]);

/// A point on the secp256k1 curve in affine coordinates.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
Expand Down Expand Up @@ -348,7 +357,8 @@ impl ProjectivePoint {
}

/// Doubles this point.
fn double(&self) -> ProjectivePoint {
#[inline]
pub fn double(&self) -> ProjectivePoint {
// We implement the complete addition formula from Renes-Costello-Batina 2015
// (https://eprint.iacr.org/2015/1060 Algorithm 9).

Expand Down Expand Up @@ -390,38 +400,14 @@ impl ProjectivePoint {
self.add_mixed(&other.neg())
}

/// Returns `[k] self`.
fn mul(&self, k: &Scalar) -> ProjectivePoint {
const LOG_MUL_WINDOW_SIZE: usize = 4;
const MUL_STEPS: usize = (256 - 1) / LOG_MUL_WINDOW_SIZE + 1;
const MUL_PRECOMP_SIZE: usize = 1 << LOG_MUL_WINDOW_SIZE;

let mut precomp = [ProjectivePoint::identity(); MUL_PRECOMP_SIZE];
let mask = (1u32 << LOG_MUL_WINDOW_SIZE) - 1u32;

precomp[0] = ProjectivePoint::identity();
precomp[1] = *self;
for i in 2..MUL_PRECOMP_SIZE {
precomp[i] = precomp[i - 1] + self;
}

let mut acc = ProjectivePoint::identity();
for idx in (0..MUL_STEPS).rev() {
for _j in 0..LOG_MUL_WINDOW_SIZE {
acc = acc.double();
}
let di = ((k >> (idx * LOG_MUL_WINDOW_SIZE)).truncate_to_u32() & mask) as usize;

// Constant-time array indexing
let mut elem = ProjectivePoint::identity();
for i in 0..MUL_PRECOMP_SIZE {
elem = ProjectivePoint::conditional_select(&elem, &(precomp[di]), i.ct_eq(&di));
}

acc += elem;
/// Calculates SECP256k1 endomorphism: `self * lambda`.
#[cfg(feature = "endomorphism-mul")]
pub fn endomorphism(&self) -> Self {
Self {
x: self.x * &ENDOMORPHISM_BETA,
y: self.y,
z: self.z,
}

acc
}
}

Expand Down Expand Up @@ -525,34 +511,6 @@ impl SubAssign<AffinePoint> for ProjectivePoint {
}
}

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

fn mul(self, other: &Scalar) -> ProjectivePoint {
ProjectivePoint::mul(self, other)
}
}

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

fn mul(self, other: &Scalar) -> ProjectivePoint {
ProjectivePoint::mul(&self, other)
}
}

impl MulAssign<Scalar> for ProjectivePoint {
fn mul_assign(&mut self, rhs: Scalar) {
*self = ProjectivePoint::mul(self, &rhs);
}
}

impl MulAssign<&Scalar> for ProjectivePoint {
fn mul_assign(&mut self, rhs: &Scalar) {
*self = ProjectivePoint::mul(self, rhs);
}
}

impl Neg for ProjectivePoint {
type Output = ProjectivePoint;

Expand Down
2 changes: 1 addition & 1 deletion k256/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl FieldElement {

/// Attempts to parse the given byte array as an SEC-1-encoded field element.
/// Does not check the result for being in the correct range.
pub const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
Self(FieldElementImpl::from_bytes_unchecked(bytes))
}

Expand Down
2 changes: 1 addition & 1 deletion k256/src/arithmetic/field/field_10x26.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl FieldElement10x26 {

/// Attempts to parse the given byte array as an SEC-1-encoded field element.
/// Does not check the result for being in the correct range.
pub const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
let w0 = (bytes[31] as u32)
| ((bytes[30] as u32) << 8)
| ((bytes[29] as u32) << 16)
Expand Down
2 changes: 1 addition & 1 deletion k256/src/arithmetic/field/field_5x52.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl FieldElement5x52 {

/// Attempts to parse the given byte array as an SEC-1-encoded field element.
/// Does not check the result for being in the correct range.
pub const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
let w0 = (bytes[31] as u64)
| ((bytes[30] as u64) << 8)
| ((bytes[29] as u64) << 16)
Expand Down
2 changes: 1 addition & 1 deletion k256/src/arithmetic/field/field_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl FieldElementImpl {
Self::new_normalized(&FieldElementUnsafeImpl::one())
}

pub const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
let value = FieldElementUnsafeImpl::from_bytes_unchecked(bytes);
Self::new_normalized(&value)
}
Expand Down
2 changes: 1 addition & 1 deletion k256/src/arithmetic/field/field_montgomery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl FieldElementMontgomery {
R
}

pub const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
Self(bytes_to_words(bytes)).mul(&R2)
}

Expand Down
35 changes: 35 additions & 0 deletions k256/src/arithmetic/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ impl Scalar {
self.0.truncate_to_u32()
}

/// Attempts to parse the given byte array as a scalar.
/// Does not check the result for being in the correct range.
#[cfg(feature = "endomorphism-mul")]
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
Self(ScalarImpl::from_bytes_unchecked(bytes))
}

/// Attempts to parse the given byte array as an SEC-1-encoded scalar.
///
/// Returns None if the byte array does not contain a big-endian integer in the range
Expand Down Expand Up @@ -224,6 +231,18 @@ impl Scalar {
}
}
}

/// If `flag` evaluates to `true`, adds `(1 << bit)` to `self`.
pub fn conditional_add_bit(&self, bit: usize, flag: Choice) -> Self {
Self(self.0.conditional_add_bit(bit, flag))
}

/// Multiplies `self` by `b` (without modulo reduction) divide the result by `2^shift`
/// (rounding to the nearest integer).
/// Variable time in `shift`.
pub fn mul_shift_var(&self, b: &Scalar, shift: usize) -> Self {
Self(self.0.mul_shift_var(&(b.0), shift))
}
}

impl Shr<usize> for Scalar {
Expand Down Expand Up @@ -268,6 +287,14 @@ impl Neg for Scalar {
}
}

impl Neg for &Scalar {
type Output = Scalar;

fn neg(self) -> Scalar {
self.negate()
}
}

impl Add<&Scalar> for &Scalar {
type Output = Scalar;

Expand Down Expand Up @@ -483,6 +510,14 @@ mod tests {
assert_eq!(a, a_back);
}

#[test]
#[cfg(feature = "endomorphism-mul")]
fn fuzzy_roundtrip_to_bytes_unchecked(a in scalar()) {
let bytes = a.to_bytes();
let a_back = Scalar::from_bytes_unchecked(&bytes);
assert_eq!(a, a_back);
}

#[test]
fn fuzzy_add(a in scalar(), b in scalar()) {
let a_bi = a.to_biguint().unwrap();
Expand Down
65 changes: 65 additions & 0 deletions k256/src/arithmetic/scalar/scalar_4x64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ impl Scalar4x64 {
self.0[0] as u32
}

#[cfg(feature = "endomorphism-mul")]
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
// Interpret the bytes as a big-endian integer w.
let w3 =
((bytes[0] as u64) << 56) | ((bytes[1] as u64) << 48) | ((bytes[2] as u64) << 40) | ((bytes[3] as u64) << 32) |
((bytes[4] as u64) << 24) | ((bytes[5] as u64) << 16) | ((bytes[6] as u64) << 8) | (bytes[7] as u64);
let w2 =
((bytes[8] as u64) << 56) | ((bytes[9] as u64) << 48) | ((bytes[10] as u64) << 40) | ((bytes[11] as u64) << 32) |
((bytes[12] as u64) << 24) | ((bytes[13] as u64) << 16) | ((bytes[14] as u64) << 8) | (bytes[15] as u64);
let w1 =
((bytes[16] as u64) << 56) | ((bytes[17] as u64) << 48) | ((bytes[18] as u64) << 40) | ((bytes[19] as u64) << 32) |
((bytes[20] as u64) << 24) | ((bytes[21] as u64) << 16) | ((bytes[22] as u64) << 8) | (bytes[23] as u64);
let w0 =
((bytes[24] as u64) << 56) | ((bytes[25] as u64) << 48) | ((bytes[26] as u64) << 40) | ((bytes[27] as u64) << 32) |
((bytes[28] as u64) << 24) | ((bytes[29] as u64) << 16) | ((bytes[30] as u64) << 8) | (bytes[31] as u64);
Self([w0, w1, w2, w3])
}

/// Attempts to parse the given byte array as an SEC-1-encoded scalar.
///
/// Returns None if the byte array does not contain a big-endian integer in the range
Expand Down Expand Up @@ -313,6 +331,53 @@ impl Scalar4x64 {

Self(res)
}

pub fn conditional_add_bit(&self, bit: usize, flag: Choice) -> Self {
debug_assert!(bit < 256);

// Construct Scalar(1 << bit).
// Since the 255-th bit of the modulus is 1, this will always be within range.
let bit_lo = bit & 0x3F;
let w = Self([
(((bit >> 6) == 0) as u64) << bit_lo,
(((bit >> 6) == 1) as u64) << bit_lo,
(((bit >> 6) == 2) as u64) << bit_lo,
(((bit >> 6) == 3) as u64) << bit_lo,
]);

Self::conditional_select(self, &(self.add(&w)), flag)
}

pub fn mul_shift_var(&self, b: &Self, shift: usize) -> Self {
debug_assert!(shift >= 256);

fn ifelse(c: bool, x: u64, y: u64) -> u64 { if c {x} else {y} }

let l = self.mul_wide(b);
let shiftlimbs = shift >> 6;
let shiftlow = shift & 0x3F;
let shifthigh = 64 - shiftlow;
let r0 = ifelse(
shift < 512,
(l.0[shiftlimbs] >> shiftlow) | ifelse(shift < 448 && shiftlow != 0, l.0[1 + shiftlimbs] << shifthigh, 0),
0);
let r1 = ifelse(
shift < 448,
(l.0[1 + shiftlimbs] >> shiftlow) | ifelse(shift < 448 && shiftlow != 0, l.0[2 + shiftlimbs] << shifthigh, 0),
0);
let r2 = ifelse(
shift < 384,
(l.0[2 + shiftlimbs] >> shiftlow) | ifelse(shift < 320 && shiftlow != 0, l.0[3 + shiftlimbs] << shifthigh, 0),
0);
let r3 = ifelse(shift < 320, l.0[3 + shiftlimbs] >> shiftlow, 0);

let res = Self([r0, r1, r2, r3]);

// Check the highmost discarded bit and round up if it is set.
let c = (l.0[(shift - 1) >> 6] >> ((shift - 1) & 0x3f)) & 1;
res.conditional_add_bit(0, Choice::from(c as u8))
}

}

#[cfg(feature = "zeroize")]
Expand Down
Loading

0 comments on commit 0586693

Please sign in to comment.