Skip to content

Commit

Permalink
Add endomorphism multiplication and an advanced windowed method from …
Browse files Browse the repository at this point in the history
…Ristretto
  • Loading branch information
fjarri committed Jul 20, 2020
1 parent ce6cabe commit 9a7add9
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 30 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
19 changes: 19 additions & 0 deletions k256/src/arithmetic.rs
Original file line number Diff line number Diff line change
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 @@ -390,6 +399,16 @@ impl ProjectivePoint {
fn sub_mixed(&self, other: &AffinePoint) -> ProjectivePoint {
self.add_mixed(&other.neg())
}

/// 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,
}
}
}

impl Add<&ProjectivePoint> for &ProjectivePoint {
Expand Down
33 changes: 33 additions & 0 deletions k256/src/arithmetic/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ 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.
pub 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 @@ -218,6 +224,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 @@ -262,6 +280,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 @@ -471,6 +497,13 @@ mod tests {
assert_eq!(a, a_back);
}

#[test]
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
64 changes: 64 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,23 @@ impl Scalar4x64 {
self.0[0] as u32
}

pub 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 @@ -314,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
89 changes: 89 additions & 0 deletions k256/src/arithmetic/scalar/scalar_8x32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,27 @@ impl Scalar8x32 {
self.0[0]
}

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

/// 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 @@ -423,6 +444,74 @@ impl Scalar8x32 {

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 & 0x1F;
let w = Self([
(((bit >> 5) == 0) as u32) << bit_lo,
(((bit >> 5) == 1) as u32) << bit_lo,
(((bit >> 5) == 2) as u32) << bit_lo,
(((bit >> 5) == 3) as u32) << bit_lo,
(((bit >> 5) == 4) as u32) << bit_lo,
(((bit >> 5) == 5) as u32) << bit_lo,
(((bit >> 5) == 6) as u32) << bit_lo,
(((bit >> 5) == 7) as u32) << 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: u32, y: u32) -> u32 { if c {x} else {y} }

let l = self.mul_wide(b);
let shiftlimbs = shift >> 5;
let shiftlow = shift & 0x1F;
let shifthigh = 32 - shiftlow;
let r0 = ifelse(
shift < 512,
(l.0[shiftlimbs] >> shiftlow) | ifelse(shift < 480 && shiftlow != 0, l.0[1 + shiftlimbs] << shifthigh, 0),
0);
let r1 = ifelse(
shift < 480,
(l.0[1 + shiftlimbs] >> shiftlow) | ifelse(shift < 448 && shiftlow != 0, l.0[2 + shiftlimbs] << shifthigh, 0),
0);
let r2 = ifelse(
shift < 448,
(l.0[2 + shiftlimbs] >> shiftlow) | ifelse(shift < 416 && shiftlow != 0, l.0[3 + shiftlimbs] << shifthigh, 0),
0);
let r3 = ifelse(
shift < 416,
(l.0[3 + shiftlimbs] >> shiftlow) | ifelse(shift < 384 && shiftlow != 0, l.0[4 + shiftlimbs] << shifthigh, 0),
0);
let r4 = ifelse(
shift < 384,
(l.0[4 + shiftlimbs] >> shiftlow) | ifelse(shift < 352 && shiftlow != 0, l.0[5 + shiftlimbs] << shifthigh, 0),
0);
let r5 = ifelse(
shift < 352,
(l.0[5 + shiftlimbs] >> shiftlow) | ifelse(shift < 320 && shiftlow != 0, l.0[6 + shiftlimbs] << shifthigh, 0),
0);
let r6 = ifelse(
shift < 320,
(l.0[6 + shiftlimbs] >> shiftlow) | ifelse(shift < 288 && shiftlow != 0, l.0[7 + shiftlimbs] << shifthigh, 0),
0);
let r7 = ifelse(
shift < 288, l.0[7 + shiftlimbs] >> shiftlow, 0);

let res = Self([r0, r1, r2, r3, r4, r5, r6, r7]);

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

}

#[cfg(feature = "zeroize")]
Expand Down
2 changes: 1 addition & 1 deletion k256/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub mod test_vectors;
pub use elliptic_curve;

#[cfg(feature = "arithmetic")]
pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint};
pub use arithmetic::{field::FieldElement, scalar::Scalar, AffinePoint, ProjectivePoint};

use elliptic_curve::{generic_array::typenum::U32, weierstrass::Curve};

Expand Down
Loading

0 comments on commit 9a7add9

Please sign in to comment.