Optimize SIDH code for about a 25% speed improvement.

This commit is contained in:
Adam Ierymenko 2021-11-11 15:29:35 -05:00
parent c7b2a3703e
commit 184c4aede1
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
14 changed files with 358 additions and 120 deletions

View file

@ -17,7 +17,7 @@ pub mod random;
pub mod secret;
pub mod hex;
pub mod varint;
pub mod sidh;
pub mod sidhp751;
pub use aes_gmac_siv;
pub use rand_core;

View file

@ -6,8 +6,8 @@
// - Erkan Tairi <erkan.tairi@gmail.com>
//
use crate::sidh::field::PrimeFieldElement;
use crate::sidh::fp::Fp751Element;
use crate::sidhp751::field::PrimeFieldElement;
use crate::sidhp751::fp::Fp751Element;
/// The x-coordinate of `P_A = [3^239](11, oddsqrt(11^3 + 11))` on `E_0(F_p)`.
pub const AFFINE_X_PA: PrimeFieldElement = PrimeFieldElement{ A: Fp751Element([0x27914862, 0xd56fe526, 0x96b5baea, 0x1fad60dc, 0xbf07ab91, 0x1e137d0, 0x52161964, 0x404d3e92, 0xcd09a337, 0x3c5385e4, 0x69e4af73, 0x44764267, 0x989dfe33, 0x9790c6db, 0xd2aa8b5e, 0xe06e1c04, 0xedea73b9, 0x38c08185, 0xa4396ca6, 0xaa41f678, 0x2229e9a0, 0x92b9259b, 0x26818be0, 0x2f93]) };

View file

@ -9,9 +9,9 @@
//! This module contains internal curve representation and operations
//! for SIDH, which is not part of the public API.
use crate::sidh::fp::Fp751Element;
use crate::sidh::field::{PrimeFieldElement, ExtensionFieldElement};
use crate::sidh::constants::*;
use crate::sidhp751::fp::Fp751Element;
use crate::sidhp751::field::{PrimeFieldElement, ExtensionFieldElement};
use crate::sidhp751::constants::*;
use std::fmt::Debug;
use std::ops::Neg;
@ -19,6 +19,7 @@ use subtle::{ConditionallySelectable, Choice};
#[cfg(test)]
use quickcheck::{Gen, Arbitrary};
use std::mem::zeroed;
// Macro to assign tuples, as Rust does not allow tuples as lvalue.
macro_rules! assign{
@ -41,17 +42,20 @@ const CONST_256: ExtensionFieldElement = ExtensionFieldElement {
/// A point on the projective line `P^1(F_{p^2})`.
///
/// This is used to work projectively with the curve coefficients.
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq)]
pub struct ProjectiveCurveParameters {
pub A: ExtensionFieldElement,
pub C: ExtensionFieldElement,
}
#[allow(non_snake_case)]
struct CachedCurveParameters {
Aplus2C: ExtensionFieldElement,
C4: ExtensionFieldElement,
}
#[allow(non_snake_case)]
struct CachedTripleCurveParameters {
Aminus2C: ExtensionFieldElement,
C2: ExtensionFieldElement,
@ -79,8 +83,10 @@ impl ProjectiveCurveParameters {
C: ExtensionFieldElement::one()
}
}
/// Recover the curve parameters from three points on the curve.
pub fn recover_curve_parameters(affine_xP: &ExtensionFieldElement, affine_xQ: &ExtensionFieldElement, affine_xQmP: &ExtensionFieldElement) ->
#[allow(non_snake_case)]
pub fn recover_curve_parameters(affine_xP: &ExtensionFieldElement, affine_xQ: &ExtensionFieldElement, affine_xQmP: &ExtensionFieldElement) ->
ProjectiveCurveParameters
{
let mut t0 = ExtensionFieldElement::one(); // = 1
@ -101,6 +107,7 @@ impl ProjectiveCurveParameters {
ProjectiveCurveParameters{ A: a, C: c }
}
/// Compute the j-invariant of the given curve.
pub fn j_invariant(&self) -> ExtensionFieldElement {
let a = &self.A;
@ -122,7 +129,9 @@ impl ProjectiveCurveParameters {
v0
}
/// Compute cached parameters `A + 2C, 4C`.
#[allow(non_snake_case)]
fn cached_params(&self) -> CachedCurveParameters {
let mut Aplus2C = &self.C + &self.C; // = 2*C
let C4 = &Aplus2C + &Aplus2C; // = 4*C
@ -130,7 +139,9 @@ impl ProjectiveCurveParameters {
CachedCurveParameters{ Aplus2C, C4 }
}
/// Compute cached parameters `A - 2C, 2C`.
#[allow(non_snake_case)]
fn cached_triple_params(&self) -> CachedTripleCurveParameters {
let C2 = &self.C + &self.C; // = 2*C
let Aminus2C = &self.A - &C2; // = A -2*C
@ -143,6 +154,7 @@ impl ProjectiveCurveParameters {
///
/// This represents a point on the (Kummer line) of a Montgomery curve. The
/// curve is specified by a ProjectiveCurveParameters struct.
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq)]
pub struct ProjectivePoint {
pub X: ExtensionFieldElement,
@ -181,9 +193,11 @@ impl Arbitrary for ProjectivePoint {
impl ProjectivePoint {
/// Creates a new zero `ProejctivePoint`.
pub fn new() -> ProjectivePoint {
ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
unsafe { zeroed() }
//ProjectivePoint{ X: ExtensionFieldElement::zero(), Z: ExtensionFieldElement::zero() }
}
#[allow(non_snake_case)]
pub fn from_affine_prime_field(x: &PrimeFieldElement) -> ProjectivePoint {
let _X = ExtensionFieldElement{ A: x.A, B: ExtensionFieldElement::zero().B };
ProjectivePoint{
@ -203,13 +217,16 @@ impl ProjectivePoint {
let affine_x = &self.Z.inv() * &self.X;
affine_x
}
/// Returns true if both sides are equal. Takes variable time.
pub fn vartime_eq(&self, _rhs: &ProjectivePoint) -> bool {
let t0 = &self.X * &_rhs.Z;
let t1 = &self.Z * &_rhs.X;
t0.vartime_eq(&t1)
}
/// Given `xP = x(P), xQ = x(Q)`, and `xPmQ = x(P-Q)`, compute `xR = x(P+Q)`.
#[allow(non_snake_case)]
fn add(&self, xQ: &ProjectivePoint, xPmQ: &ProjectivePoint) -> ProjectivePoint {
let xP = *self;
// Algorithm 1 of Costello-Smith.
@ -225,7 +242,9 @@ impl ProjectivePoint {
ProjectivePoint{ X: x, Z: z }
}
/// Given `xP = x(P)` and cached curve parameters `Aplus2C = A + 2*C, C4 = 4*C`, compute `xQ = x([2]P)`.
#[allow(non_snake_case)]
fn double(&self, curve: &CachedCurveParameters) -> ProjectivePoint {
let xP = *self;
// Algorithm 2 of Costello-Smith, amended to work with projective curve coefficients.
@ -243,9 +262,11 @@ impl ProjectivePoint {
// = ((X+Z)^2(X-Z)^2 : (4XZ((a + 2)/4) + (X-Z)^2)4XZ )
ProjectivePoint{ X: x, Z: z }
}
/// Calculates the x-coordinate of `2P` and `P+Q` from the x-coordinate of `P, Q` and `P-Q`.
// Params: `C4 = 4*C` and `Aplus2C = (A+2C)`
// Cost: 8M+4S+8A in `Fp2`
#[allow(non_snake_case)]
fn dbl_add(&self, xQ: &ProjectivePoint, xPmQ: &ProjectivePoint, params: &CachedCurveParameters) ->
(ProjectivePoint, ProjectivePoint)
{
@ -281,17 +302,21 @@ impl ProjectivePoint {
(x2P, xPaddQ)
}
/// Given the curve parameters, `xP = x(P)`, and `k >= 0`, compute `xQ = x([2^k]P)`.
#[allow(non_snake_case)]
pub fn pow2k(&self, curve: &ProjectiveCurveParameters, k: u32) -> ProjectivePoint {
let cached_params = curve.cached_params();
let mut xQ = *self;
for _ in 0..k { xQ = xQ.double(&cached_params); }
xQ
}
// Uses the efficient Montgomery tripling formulas from FLOR-SIDH-x64
// Reference: A faster SW implementation of SIDH (github.com/armfazh/flor-sidh-x64).
/// Given `xP = x(P)` and cached tripling curve parameters `Aminus2C = A - 2*C, C2 = 2*C`, compute `xQ = x([3]P)`.
/// Returns `xQ` to allow chaining.
#[allow(non_snake_case)]
fn triple(&self, curve: &CachedTripleCurveParameters) -> ProjectivePoint {
let xP = *self;
let (x1, z1) = (&xP.X, &xP.Z);
@ -319,19 +344,23 @@ impl ProjectivePoint {
ProjectivePoint{ X: x, Z: z }
}
/// Given the curve parameters, `xP = x(P)`, and `k >= 0`, compute `xQ = x([3^k]P)`.
#[allow(non_snake_case)]
pub fn pow3k(&self, curve: &ProjectiveCurveParameters, k: u32) -> ProjectivePoint {
let cached_params = curve.cached_triple_params();
let mut xQ = *self;
for _ in 0..k { xQ = xQ.triple(&cached_params); }
xQ
}
/// Given `x(P)` and a scalar `m` in little-endian bytes, compute `x([m]P)` using the
/// Montgomery ladder. This is described in Algorithm 8 of Costello-Smith.
///
/// This function's execution time is dependent only on the byte-length of the
/// input scalar. All scalars of the same input length execute in uniform time.
/// The scalar can be padded with zero bytes to ensure a uniform length.
#[allow(non_snake_case)]
fn scalar_mul(&self, curve: &ProjectiveCurveParameters, scalar: &[u8]) -> ProjectivePoint {
let xP = *self;
let cached_params = curve.cached_params();
@ -357,12 +386,14 @@ impl ProjectivePoint {
let xQ = x0;
xQ
}
/// Given `P = (x_P, y_P)` in affine coordinates, as well as projective points
/// `x(Q), x(R) = x(P+Q)`, all in the prime-field subgroup of the starting curve
/// `E_0(F_p)`, use the Okeya-Sakurai coordinate recovery strategy to recover `Q =
/// (X_Q : Y_Q : Z_Q)`.
///
/// This is Algorithm 5 of Costello-Smith, with the constants `a = 0, b = 1` hardcoded.
#[allow(non_snake_case)]
fn okeya_sakurai_coordinate_recovery(affine_xP: &PrimeFieldElement, affine_yP: &PrimeFieldElement,
xQ: &ProjectivePrimeFieldPoint, xR: &ProjectivePrimeFieldPoint) ->
(PrimeFieldElement, PrimeFieldElement, PrimeFieldElement)
@ -387,6 +418,7 @@ impl ProjectivePoint {
(X_Q, Y_Q, Z_Q)
}
/// Given `x(P), x(Q), x(P-Q)`, as well as a scalar m in little-endian bytes,
/// compute `x(P + [m]Q)` using the "three-point ladder" of de Feo, Jao, and Plut.
///
@ -427,7 +459,8 @@ impl ProjectivePoint {
//
// return x2
//
pub fn three_point_ladder(xP: &ProjectivePoint, xQ: &ProjectivePoint, xPmQ: &ProjectivePoint,
#[allow(non_snake_case)]
pub fn three_point_ladder(xP: &ProjectivePoint, xQ: &ProjectivePoint, xPmQ: &ProjectivePoint,
curve: &ProjectiveCurveParameters, scalar: &[u8]) -> ProjectivePoint
{
let cached_params = curve.cached_params();
@ -457,8 +490,10 @@ impl ProjectivePoint {
let xR = x2;
xR
}
/// Right-to-left point multiplication, which given the x-coordinate
/// of `P, Q` and `P-Q` calculates the x-coordinate of `R=P+[k]Q`.
#[allow(non_snake_case)]
pub fn right_to_left_ladder(xP: &ProjectivePoint, xQ: &ProjectivePoint, xPmQ: &ProjectivePoint,
curve: &ProjectiveCurveParameters, scalar: &[u8]) -> ProjectivePoint
{
@ -482,8 +517,10 @@ impl ProjectivePoint {
let xR = R1;
xR
}
/// Given the affine x-coordinate `affine_xP` of `P`, compute the x-coordinate
/// `x(\tau(P)-P) of \tau(P)-P`.
#[allow(non_snake_case)]
pub fn distort_and_difference(affine_xP: &PrimeFieldElement) -> ProjectivePoint {
let mut t0 = affine_xP.square(); // = x_P^2
let t1 = &PrimeFieldElement::one() + &t0; // = x_P^2 + 1
@ -496,6 +533,7 @@ impl ProjectivePoint {
let xR = ProjectivePoint{ X: x, Z: z };
xR
}
/// Given an affine point `P = (x_P, y_P)` in the prime-field subgroup of the
/// starting curve `E_0(F_p)`, together with a secret scalar `m`, compute `x(P+[m]Q)`,
/// where `Q = \tau(P)` is the image of `P` under the distortion map.
@ -589,6 +627,7 @@ impl ProjectivePoint {
// These formulas could probably be combined with the formulas for y-recover
// and computed more efficiently, but efficiency isn't the biggest concern
// here, since the bulk of the cost is already in the ladder.
#[allow(non_snake_case)]
pub fn secret_point(affine_xP: &PrimeFieldElement, affine_yP: &PrimeFieldElement, scalar: &[u8]) -> ProjectivePoint {
let mut xQ = ProjectivePrimeFieldPoint::from_affine(affine_xP);
xQ.X = (&xQ.X).neg();
@ -653,6 +692,7 @@ impl ProjectivePoint {
///
/// This represents a point on the (Kummer line) of the prime-field subgroup of
/// the base curve `E_0(F_p)`, defined by `E_0 : y^2 = x^3 + x`.
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq)]
struct ProjectivePrimeFieldPoint {
X: PrimeFieldElement,
@ -691,7 +731,8 @@ impl Arbitrary for ProjectivePrimeFieldPoint {
impl ProjectivePrimeFieldPoint {
/// Creates a new zero `ProjectivePrimeFieldPoint`.
pub fn new() -> ProjectivePrimeFieldPoint {
ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() }
unsafe { zeroed() }
//ProjectivePrimeFieldPoint{ X: PrimeFieldElement::zero(), Z: PrimeFieldElement::zero() }
}
pub fn from_affine(x: &PrimeFieldElement) -> ProjectivePrimeFieldPoint {
@ -702,17 +743,19 @@ impl ProjectivePrimeFieldPoint {
}
pub fn to_affine(&self) -> PrimeFieldElement {
let affine_x = &self.Z.inv() * &self.X;
affine_x
&self.Z.inv() * &self.X
}
/// Returns true if both sides are equal. Takes variable time.
pub fn vartime_eq(&self, _rhs: &ProjectivePrimeFieldPoint) -> bool {
let t0 = &self.X * &_rhs.Z;
let t1 = &self.Z * &_rhs.X;
t0.vartime_eq(&t1)
}
/// Given `xP = x(P), xQ = x(Q)`, and `xPmQ = x(P-Q)`, compute `xR = x(P+Q)`.
fn add(&self, xQ: &ProjectivePrimeFieldPoint, xPmQ: &ProjectivePrimeFieldPoint) ->
#[allow(non_snake_case)]
fn add(&self, xQ: &ProjectivePrimeFieldPoint, xPmQ: &ProjectivePrimeFieldPoint) ->
ProjectivePrimeFieldPoint
{
let xP = *self;
@ -729,10 +772,12 @@ impl ProjectivePrimeFieldPoint {
ProjectivePrimeFieldPoint{ X: x, Z: z }
}
/// Given `xP = x(P)` and cached curve parameter `aPlus2Over4 = (a+2)/4, compute xQ = x([2]P)`.
//
// Note that we don't use projective curve coefficients here because we only
// ever use a fixed curve (in our case, the base curve E_0).
#[allow(non_snake_case)]
fn double(&self, aPlus2Over4: &PrimeFieldElement) -> ProjectivePrimeFieldPoint {
let xP = *self;
// Algorithm 2 of Costello-Smith
@ -747,11 +792,13 @@ impl ProjectivePrimeFieldPoint {
// = ((X+Z)^2(X-Z)^2 : (4XZ((a + 2)/4) + (X-Z)^2)4XZ )
ProjectivePrimeFieldPoint{ X: x, Z: z }
}
/// Calculates the x-coordinate of `2P` and `P+Q` from the x-coordinate of `P, Q` and `P-Q`.
// Assumptions:
// aPlus2Over2 = (A+2)/4.
// z(P-Q) = 1, the Z-coordinate of P-Q is equal to 1.
// Cost: 6M+4S+8A in Fp
#[allow(non_snake_case)]
fn dbl_add(&self, xQ: &ProjectivePrimeFieldPoint, xPmQ: &ProjectivePrimeFieldPoint, aPlus2Over4: &PrimeFieldElement) ->
(ProjectivePrimeFieldPoint, ProjectivePrimeFieldPoint)
{
@ -785,7 +832,8 @@ impl ProjectivePrimeFieldPoint {
(x2P, xPaddQ)
}
/// Given `x(P)` and a scalar m in little-endian bytes, compute `x([m]P), x([m+1]P)`
/// Given `x(P)` and a scalar m in little-endian bytes, compute `x([m]P), x([m+1]P)`
/// using the Montgomery ladder. This is described in Algorithm 8 of Costello-Smith.
///
/// The extra value `x([m+1]P)` is returned to allow y-coordinate recovery, otherwise,
@ -794,6 +842,7 @@ impl ProjectivePrimeFieldPoint {
/// This function's execution time is dependent only on the byte-length of the input
/// scalar. All scalars of the same input length execute in uniform time.
/// The scalar can be padded with zero bytes to ensure a uniform length.
#[allow(non_snake_case)]
fn scalar_mul_prime_field(&self, aPlus2Over4: &PrimeFieldElement, scalar: &[u8]) -> (ProjectivePrimeFieldPoint, ProjectivePrimeFieldPoint)
{
let xP = *self;
@ -831,6 +880,7 @@ impl ProjectivePrimeFieldPoint {
// sage: X3, Y3, Z3 = 3*P
// sage: m = 96550223052359874398280314003345143371473380422728857598463622014420884224892
//
#[allow(non_snake_case)]
#[cfg(test)]
mod test {
use super::*;

View file

@ -9,11 +9,12 @@
//! This module contains finite field arithmetic functionality for SIDH,
//! which is not part of the public API.
use crate::sidh::fp::*;
use crate::sidhp751::fp::*;
use std::fmt::Debug;
use std::cmp::{Eq, PartialEq};
use std::mem::zeroed;
use std::ops::*;
use subtle::ConditionallySelectable;
@ -28,6 +29,7 @@ use quickcheck::{Arbitrary, Gen};
//-----------------------------------------------------------------------------//
/// Represents an element of the extension field `F_{p^2}`.
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq)]
pub struct ExtensionFieldElement {
/// This field element is in Montgomery form, so that the value `A` is
@ -177,11 +179,13 @@ impl Arbitrary for ExtensionFieldElement {
impl ExtensionFieldElement {
/// Construct a zero `ExtensionFieldElement`.
pub fn zero() -> ExtensionFieldElement {
ExtensionFieldElement{
A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
}
unsafe { zeroed() }
//ExtensionFieldElement{
// A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
// B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
//}
}
/// Construct a one `ExtensionFieldElement`.
pub fn one() -> ExtensionFieldElement {
ExtensionFieldElement{
@ -189,6 +193,7 @@ impl ExtensionFieldElement {
B: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
}
}
/// Set output to `1/x`.
pub fn inv(&self) -> ExtensionFieldElement {
let a = &self.A;
@ -227,6 +232,7 @@ impl ExtensionFieldElement {
B: _b
}
}
// Set (y1, y2, y3) = (1/x1, 1/x2, 1/x3).
//
// All xi, yi must be distinct.
@ -244,6 +250,7 @@ impl ExtensionFieldElement {
(_y1, _y2, _y3)
}
/// Set the output to `x^2`.
pub fn square(&self) -> ExtensionFieldElement {
let a = &self.A;
@ -268,11 +275,13 @@ impl ExtensionFieldElement {
B: _b
}
}
/// Returns true if both sides are equal. Takes variable time.
#[inline(always)]
pub fn vartime_eq(&self, _rhs: &ExtensionFieldElement) -> bool {
(&self.A == &_rhs.A) && (&self.B == &_rhs.B)
}
/// Convert the input to wire format.
pub fn to_bytes(&self) -> [u8; 188] {
let mut bytes = [0u8; 188];
@ -280,6 +289,7 @@ impl ExtensionFieldElement {
bytes[94..188].clone_from_slice(&self.B.to_bytes());
bytes
}
/// Read 188 bytes into the given `ExtensionFieldElement`.
pub fn from_bytes(bytes: &[u8]) -> ExtensionFieldElement {
assert!(bytes.len() >= 188, "Too short input to ExtensionFieldElement from_bytes, expected 188 bytes");
@ -294,6 +304,7 @@ impl ExtensionFieldElement {
//-----------------------------------------------------------------------------//
/// Represents an element of the prime field `F_p`.
#[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq)]
pub struct PrimeFieldElement {
/// This field element is in Montgomery form, so that the value `A` is
@ -396,16 +407,19 @@ impl Arbitrary for PrimeFieldElement {
impl PrimeFieldElement {
/// Construct a zero `PrimeFieldElement`.
pub fn zero() -> PrimeFieldElement {
PrimeFieldElement{
A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
}
unsafe { zeroed() }
//PrimeFieldElement{
// A: Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
//}
}
/// Construct a one `PrimeFieldElement`.
pub fn one() -> PrimeFieldElement {
PrimeFieldElement{
A: Fp751Element([0x249ad, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x83100000, 0x375c6c66, 0x5527b1e4, 0x3f4f24d0, 0x697797bf, 0xac5c4e2e, 0xc89db7b2, 0xd2076956, 0x4ca4b439, 0x7512c7e9, 0x10f7926c, 0x24bce5e2, 0x2d5b]),
}
}
/// Set the output to `x^2`.
#[inline(always)]
pub fn square(&self) -> PrimeFieldElement {
@ -416,12 +430,14 @@ impl PrimeFieldElement {
PrimeFieldElement{ A: _a }
}
/// Raise self to `2^(2^k)`-th power, for `k >= 1`, by repeated squarings.
fn pow2k(&self, k: u8) -> PrimeFieldElement {
let mut result = self.square();
for _ in 1..k { result = result.square(); }
result
}
/// Set output to `x^((p-3)/4)`. If `x` is square, this is `1/sqrt(x)`.
fn p34(&self) -> PrimeFieldElement {
// Sliding-window strategy computed with Sage, awk, sed, and tr.
@ -455,6 +471,7 @@ impl PrimeFieldElement {
}
result
}
/// Set output to `sqrt(x)`, if x is a square. If `x` is nonsquare output is undefined.
fn sqrt(&self) -> PrimeFieldElement {
let mut result = self.p34(); // result = (y^2)^((p-3)/4) = y^((p-3)/2)
@ -462,6 +479,7 @@ impl PrimeFieldElement {
// Now result^2 = y^(p+1) = y^2 = x, so result = sqrt(x).
result
}
/// Set output to `1/x`.
pub fn inv(&self) -> PrimeFieldElement {
let mut result = self.square(); // result = x^2
@ -470,7 +488,9 @@ impl PrimeFieldElement {
result = &result * self; // result = x^(p-2)
result
}
/// Returns true if both sides are equal. Takes variable time.
#[inline(always)]
pub fn vartime_eq(&self, _rhs: &PrimeFieldElement) -> bool {
&self.A == &_rhs.A
}
@ -652,12 +672,12 @@ impl Fp751X2 {
#[inline(always)]
pub fn checklt238(scalar: &[u8; 48], result: &mut u32) {
crate::sidh::fp::checklt238(scalar, result);
crate::sidhp751::fp::checklt238(scalar, result);
}
#[inline(always)]
pub fn mulby3(scalar: &mut [u8; 48]) {
crate::sidh::fp::mulby3(scalar);
crate::sidhp751::fp::mulby3(scalar);
}
#[cfg(test)]

View file

@ -8,7 +8,7 @@
use crate::random::SecureRandom;
use std::mem::size_of;
use std::mem::{size_of, MaybeUninit, zeroed};
use std::fmt::Debug;
use std::ops::Neg;
@ -40,28 +40,13 @@ const P751: [u32; FP751_NUM_WORDS] = [4294967295, 4294967295, 4294967295, 429496
const P751P1: [u32; FP751_NUM_WORDS] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4004511744, 1241020584, 3823933061, 335006838, 3667237658, 3605784694, 139368551, 1555191624, 2237838596, 2545605734, 236097695, 3577870108, 28645];
const P751X2: [u32; FP751_NUM_WORDS] = [4294967294, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 3714056191, 2482041169, 3352898826, 670013677, 3039508020, 2916602093, 278737103, 3110383248, 180709896, 796244173, 472195391, 2860772920, 57291];
// Return 1 if x != 0, and 0 otherwise.
#[inline(always)]
fn is_digit_nonzero_ct(x: &u32) -> u32 {
((x | ((0 as u32).wrapping_sub(*x))) >> (RADIX-1)) as u32
}
// Return 1 if x = 0, and 0 otherwise.
#[inline(always)]
fn is_digit_zero_ct(x: &u32) -> u32 {
(1 ^ is_digit_nonzero_ct(x)) as u32
}
// Return 1 if x < y, and 0 otherwise.
#[inline(always)]
fn is_digit_lessthan_ct(x: &u32, y: &u32) -> u32 {
((x ^ ((x ^ y) | ((x.wrapping_sub(*y)) ^ y))) >> (RADIX-1)) as u32
}
fn digit_x_digit(a: &u32, b: &u32, c: &mut [u32]) {
let sizeof_u32 = size_of::<u32>() as u32;
let mask_low = <u32>::max_value() >> (sizeof_u32 * 4);
let mask_high = <u32>::max_value() << (sizeof_u32 * 4);
fn digit_x_digit(a: u32, b: u32, c: &mut [u32; 2]) {
#[allow(non_upper_case_globals)]
const sizeof_u32: u32 = size_of::<u32>() as u32;
#[allow(non_upper_case_globals)]
const mask_low: u32 = <u32>::MAX >> (sizeof_u32 * 4);
#[allow(non_upper_case_globals)]
const mask_high: u32 = <u32>::MAX << (sizeof_u32 * 4);
let al = a & mask_low;
let ah = a >> (sizeof_u32 * 4);
@ -72,78 +57,73 @@ fn digit_x_digit(a: &u32, b: &u32, c: &mut [u32]) {
let albh = al * bh;
let ahbl = ah * bl;
let ahbh = ah * bh;
c[0] = albl & mask_low;
let c0 = albl & mask_low;
let mut res1 = albl >> (sizeof_u32 * 4);
let mut res2 = ahbl & mask_low;
let mut res3 = albh & mask_low;
let mut temp = res1 + res2 + res3;
let mut carry = temp >> (sizeof_u32 * 4);
c[0] ^= temp << (sizeof_u32 * 4);
c[0] = c0 ^ (temp << (sizeof_u32 * 4));
res1 = ahbl >> (sizeof_u32 * 4);
res2 = albh >> (sizeof_u32 * 4);
res3 = ahbh & mask_low;
temp = res1 + res2 + res3 + carry;
c[1] = temp & mask_low;
let c1 = temp & mask_low;
carry = temp & mask_high;
c[1] ^= (ahbh & mask_high) + carry;
c[1] = c1 ^ ((ahbh & mask_high) + carry);
}
#[inline(always)]
fn mul(multiplier: &u32, multiplicant: &u32, uv: &mut [u32]) {
fn mul(multiplier: u32, multiplicant: u32, uv: &mut [u32; 2]) {
digit_x_digit(multiplier, multiplicant, uv);
}
#[inline(always)]
fn addc(carry_in: &u32, addend1: &u32, addend2: &u32) -> (u32, u32) {
let temp = addend1.wrapping_add(*carry_in);
fn addc(carry_in: u32, addend1: u32, addend2: u32) -> (u32, u32) {
//let sum = (addend1 as u64) + (addend2 as u64) + (carry_in as u64);
//((sum > u32::MAX as u64) as u32, sum as u32)
let temp = addend1.wrapping_add(carry_in);
let sum = addend2.wrapping_add(temp);
let carry_out = (is_digit_lessthan_ct(&temp, carry_in)) | is_digit_lessthan_ct(&sum, &temp);
(carry_out, sum)
((temp < carry_in) as u32 | (sum < temp) as u32, sum)
}
#[inline(always)]
fn subc(borrow_in: &u32, minuend: &u32, subtrahend: &u32) -> (u32, u32) {
let temp = minuend.wrapping_sub(*subtrahend);
let borrow = (is_digit_lessthan_ct(minuend, subtrahend)) | (borrow_in & is_digit_zero_ct(&temp));
let difference = temp.wrapping_sub(*borrow_in);
let borrow_out = borrow;
(borrow_out, difference)
fn subc(borrow_in: u32, minuend: u32, subtrahend: u32) -> (u32, u32) {
let temp = minuend.wrapping_sub(subtrahend);
let borrow = ((minuend < subtrahend) as u32) | (borrow_in & (temp == 0) as u32);
let difference = temp.wrapping_sub(borrow_in);
(borrow, difference)
}
#[inline(always)]
pub fn fpadd751(x: &Fp751Element, y: &Fp751Element, z: &mut Fp751Element) {
let mut carry: u32 = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(carry, z.0[i]) = addc(&carry, &x.0[i], &y.0[i])};
assign!{(carry, z.0[i]) = addc(carry, x.0[i], y.0[i])};
}
carry = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(carry, z.0[i]) = subc(&carry, &z.0[i], &P751X2[i])};
assign!{(carry, z.0[i]) = subc(carry, z.0[i], P751X2[i])};
}
let mask = (0 as u32).wrapping_sub(carry);
carry = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(carry, z.0[i]) = addc(&carry, &z.0[i], &(P751X2[i] & mask))};
assign!{(carry, z.0[i]) = addc(carry, z.0[i], (P751X2[i] & mask))};
}
}
#[inline(always)]
pub fn fpsub751(x: &Fp751Element, y: &Fp751Element, z: &mut Fp751Element) {
let mut borrow: u32 = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(borrow, z.0[i]) = subc(&borrow, &x.0[i], &y.0[i])};
assign!{(borrow, z.0[i]) = subc(borrow, x.0[i], y.0[i])};
}
let mask = (0 as u32).wrapping_sub(borrow);
borrow = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(borrow, z.0[i]) = addc(&borrow, &z.0[i], &(P751X2[i] & mask))};
assign!{(borrow, z.0[i]) = addc(borrow, z.0[i], (P751X2[i] & mask))};
}
}
@ -151,14 +131,15 @@ pub fn mul751(x: &Fp751Element, y: &Fp751Element, z: &mut Fp751X2) {
let mut t: u32 = 0;
let mut u: u32 = 0;
let mut v: u32 = 0;
#[allow(non_snake_case)]
let mut UV = [0u32; 2];
let mut carry: u32 = 0;
for i in 0..FP751_NUM_WORDS {
for j in 0..(i+1) {
mul(&x.0[j], &y.0[i-j], &mut UV[..]);
assign!{(carry, v) = addc(&0, &UV[0], &v)};
assign!{(carry, u) = addc(&carry, &UV[1], &u)};
mul(x.0[j], y.0[i - j], &mut UV);
assign! {(carry, v) = addc(0, UV[0], v)};
assign! {(carry, u) = addc(carry, UV[1], u)};
t += carry;
}
z.0[i] = v;
@ -169,9 +150,9 @@ pub fn mul751(x: &Fp751Element, y: &Fp751Element, z: &mut Fp751X2) {
for i in FP751_NUM_WORDS..(2*FP751_NUM_WORDS-1) {
for j in (i-FP751_NUM_WORDS+1)..FP751_NUM_WORDS {
mul(&x.0[j], &y.0[i-j], &mut UV[..]);
assign!{(carry, v) = addc(&0, &UV[0], &v)};
assign!{(carry, u) = addc(&carry, &UV[1], &u)};
mul(x.0[j], y.0[i-j], &mut UV);
assign!{(carry, v) = addc(0, UV[0], v)};
assign!{(carry, u) = addc(carry, UV[1], u)};
t += carry;
}
z.0[i] = v;
@ -186,6 +167,7 @@ pub fn rdc751(x: &Fp751X2, z: &mut Fp751Element) {
let mut t: u32 = 0;
let mut u: u32 = 0;
let mut v: u32 = 0;
#[allow(non_snake_case)]
let mut UV = [0u32; 2];
let mut carry: u32 = 0;
let mut count = P751_ZERO_WORDS;
@ -197,14 +179,14 @@ pub fn rdc751(x: &Fp751X2, z: &mut Fp751Element) {
for i in 0..FP751_NUM_WORDS {
for j in 0..i {
if j < (((i+1) as u32).wrapping_sub(P751_ZERO_WORDS as u32) as usize) {
mul(&z.0[j], &P751P1[i-j], &mut UV[..]);
assign!{(carry, v) = addc(&0, &UV[0], &v)};
assign!{(carry, u) = addc(&carry, &UV[1], &u)};
mul(z.0[j], P751P1[i-j], &mut UV);
assign!{(carry, v) = addc(0, UV[0], v)};
assign!{(carry, u) = addc(carry, UV[1], u)};
t += carry;
}
}
assign!{(carry, v) = addc(&0, &v, &x.0[i])};
assign!{(carry, u) = addc(&carry, &u, &0)};
assign!{(carry, v) = addc(0, v, x.0[i])};
assign!{(carry, u) = addc(carry, u, 0)};
t += carry;
z.0[i] = v;
@ -219,14 +201,14 @@ pub fn rdc751(x: &Fp751X2, z: &mut Fp751Element) {
}
for j in (i-FP751_NUM_WORDS+1)..FP751_NUM_WORDS {
if j < (FP751_NUM_WORDS-count) {
mul(&z.0[j], &P751P1[i-j], &mut UV[..]);
assign!{(carry, v) = addc(&0, &UV[0], &v)};
assign!{(carry, u) = addc(&carry, &UV[1], &u)};
mul(z.0[j], P751P1[i-j], &mut UV);
assign!{(carry, v) = addc(0, UV[0], v)};
assign!{(carry, u) = addc(carry, UV[1], u)};
t += carry;
}
}
assign!{(carry, v) = addc(&0, &v, &x.0[i])};
assign!{(carry, u) = addc(&carry, &u, &0)};
assign!{(carry, v) = addc(0, v, x.0[i])};
assign!{(carry, u) = addc(carry, u, 0)};
t += carry;
z.0[i-FP751_NUM_WORDS] = v;
@ -234,55 +216,49 @@ pub fn rdc751(x: &Fp751X2, z: &mut Fp751Element) {
u = t;
t = 0;
}
assign!{(carry, v) = addc(&0, &v, &x.0[2*FP751_NUM_WORDS-1])};
assign!{(carry, v) = addc(0, v, x.0[2*FP751_NUM_WORDS-1])};
z.0[FP751_NUM_WORDS-1] = v;
}
#[inline(always)]
pub fn srdc751(x: &mut Fp751Element) {
let mut borrow: u32 = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(borrow, x.0[i]) = subc(&borrow, &x.0[i], &P751[i])};
assign!{(borrow, x.0[i]) = subc(borrow, x.0[i], P751[i])};
}
let mask = (0 as u32).wrapping_sub(borrow);
borrow = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(borrow, x.0[i]) = addc(&borrow, &x.0[i], &(P751[i] & mask))};
assign!{(borrow, x.0[i]) = addc(borrow, x.0[i], (P751[i] & mask))};
}
}
#[inline(always)]
pub fn mp_add751(x: &Fp751Element, y: &Fp751Element, z: &mut Fp751Element) {
let mut carry: u32 = 0;
for i in 0..FP751_NUM_WORDS {
assign!{(carry, z.0[i]) = addc(&carry, &x.0[i], &y.0[i])};
assign!{(carry, z.0[i]) = addc(carry, x.0[i], y.0[i])};
}
}
#[inline(always)]
pub fn mp_add751x2(x: &Fp751X2, y: &Fp751X2, z: &mut Fp751X2) {
let mut carry: u32 = 0;
for i in 0..(FP751_NUM_WORDS*2) {
assign!{(carry, z.0[i]) = addc(&carry, &x.0[i], &y.0[i])};
assign!{(carry, z.0[i]) = addc(carry, x.0[i], y.0[i])};
}
}
#[inline(always)]
pub fn mp_sub751x2(x: &Fp751X2, y: &Fp751X2, z: &mut Fp751X2) {
let mut borrow: u32 = 0;
for i in 0..(FP751_NUM_WORDS*2) {
assign!{(borrow, z.0[i]) = subc(&borrow, &x.0[i], &y.0[i])};
assign!{(borrow, z.0[i]) = subc(borrow, x.0[i], y.0[i])};
}
let mask = (0 as u32).wrapping_sub(borrow);
borrow = 0;
for i in FP751_NUM_WORDS..(FP751_NUM_WORDS*2) {
assign!{(borrow, z.0[i]) = addc(&borrow, &z.0[i], &(P751[i-FP751_NUM_WORDS] & mask))};
assign!{(borrow, z.0[i]) = addc(borrow, z.0[i], (P751[i-FP751_NUM_WORDS] & mask))};
}
}
@ -303,7 +279,7 @@ pub fn checklt238(scalar: &[u8; 48], result: &mut u32) {
let mut borrow: u32 = 0;
for i in 0..12 {
assign!{(borrow, ignored) = subc(&borrow, &three238[i], &scalar_u32[i])};
assign!{(borrow, ignored) = subc(borrow, three238[i], scalar_u32[i])};
}
let mask = (0 as u32).wrapping_sub(borrow);
*result = mask;
@ -324,10 +300,10 @@ pub fn mulby3(scalar: &mut [u8; 48]) {
let mut carry: u32 = 0;
let temp = scalar_u32;
for i in 0..12 {
assign!{(carry, scalar_u32[i]) = addc(&carry, &scalar_u32[i], &temp[i])};
assign!{(carry, scalar_u32[i]) = addc(carry, scalar_u32[i], temp[i])};
}
for i in 0..12 {
assign!{(carry, scalar_u32[i]) = addc(&carry, &scalar_u32[i], &temp[i])};
assign!{(carry, scalar_u32[i]) = addc(carry, scalar_u32[i], temp[i])};
}
for i in 0..48 {
@ -346,11 +322,11 @@ pub struct Fp751ElementDist;
impl ConditionallySelectable for Fp751Element {
#[inline(always)]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let mut bytes = [0_u32; FP751_NUM_WORDS];
let mut bytes = unsafe { MaybeUninit::<Fp751Element>::uninit().assume_init() };
for i in 0..FP751_NUM_WORDS {
bytes[i] = u32::conditional_select(&a.0[i], &b.0[i], choice);
bytes.0[i] = u32::conditional_select(&a.0[i], &b.0[i], choice);
}
Fp751Element(bytes)
bytes
}
#[inline(always)]
@ -398,14 +374,17 @@ impl Arbitrary for Fp751Element {
impl Fp751Element {
/// Construct a new zero `Fp751Element`.
#[inline(always)]
pub fn zero() -> Fp751Element {
Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
unsafe { zeroed() }
//Fp751Element([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
}
/// Given an `Fp751Element` in Montgomery form, convert to little-endian bytes.
pub fn to_bytes(&self) -> [u8; 94] {
let mut bytes = [0u8; 94];
let mut a = Fp751Element::zero();
#[allow(non_snake_case)]
let mut aR = Fp751X2::zero();
//let one = Fp751Element([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
@ -440,6 +419,7 @@ impl Fp751Element {
a.0[j as usize] |= (bytes[i as usize] as u32) << (8 * k);
}
#[allow(non_snake_case)]
let aRR = &a * &MONTGOMERY_RSQ; // = a*R*R
let output = aRR.reduce(); // = a*R mod p
output
@ -457,9 +437,11 @@ impl Debug for Fp751X2 {
impl Fp751X2 {
// Construct a zero `Fp751X2`.
#[inline(always)]
pub fn zero() -> Fp751X2 {
Fp751X2([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
unsafe { zeroed() }
//Fp751X2([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
// 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0])
}
}

View file

@ -9,16 +9,18 @@
//! This module contains internal isogeny representation and operations
//! for SIDH, which is not part of the public API.
use crate::sidh::field::ExtensionFieldElement;
use crate::sidh::curve::{ProjectiveCurveParameters, ProjectivePoint};
use crate::sidhp751::field::ExtensionFieldElement;
use crate::sidhp751::curve::{ProjectiveCurveParameters, ProjectivePoint};
/// Represents a 3-isogeny phi, holding the data necessary to evaluate phi.
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub struct ThreeIsogeny {
pub X: ExtensionFieldElement,
pub Z: ExtensionFieldElement,
}
#[allow(non_snake_case)]
impl ThreeIsogeny {
/// Given a three-torsion point `x3 = x(P_3)` on the curve `E_(A:C)`, construct the
/// three-isogeny `phi : E_(A:C) -> E_(A:C)/<P_3> = E_(A':C')`.
@ -45,6 +47,7 @@ impl ThreeIsogeny {
(codomain, isogeny)
}
/// Given a 3-isogeny phi and a point `xP = x(P)`, compute `x(Q)`, the x-coordinate
/// of the image `Q = phi(P)` of `P` under `phi : E_(A:C) -> E_(A':C')`.
///
@ -72,6 +75,7 @@ impl ThreeIsogeny {
/// Represents a 4-isogeny phi, holding the data necessary to evaluate phi.
//
// See compute_four_isogeny for more details.
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub struct FourIsogeny {
pub Xsq_plus_Zsq : ExtensionFieldElement,
@ -81,6 +85,7 @@ pub struct FourIsogeny {
pub Zpow4 : ExtensionFieldElement,
}
#[allow(non_snake_case)]
impl FourIsogeny {
/// Given a four-torsion point `x4 = x(P_4)` on the curve `E_(A:C)`, compute the
/// coefficients of the codomain `E_(A':C')` of the four-isogeny `phi : E_(A:C) ->
@ -118,6 +123,7 @@ impl FourIsogeny {
(codomain, isogeny)
}
/// Given a 4-isogeny phi and a point `xP = x(P)`, compute `x(Q)`, the x-coordinate
/// of the image `Q = phi(P)` of `P` under `phi : E_(A:C) -> E_(A':C')`.
//
@ -160,12 +166,14 @@ impl FourIsogeny {
/// Represents a 4-isogeny phi.
//
// See compute_four_isogeny for details.
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub struct FirstFourIsogeny {
pub A: ExtensionFieldElement,
pub C: ExtensionFieldElement,
}
#[allow(non_snake_case)]
impl FirstFourIsogeny {
/// Compute the "first" four-isogeny from the given curve.
//
@ -183,6 +191,7 @@ impl FirstFourIsogeny {
(codomain, isogeny)
}
/// Given a 4-isogeny phi and a point `xP = x(P)`, compute `x(Q)`, the x-coordinate
/// of the image `Q = phi(P)` of `P` under `phi : E_(A:C) -> E_(A':C')`.
//
@ -210,10 +219,11 @@ impl FirstFourIsogeny {
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod test {
use super::*;
use crate::sidh::fp::Fp751Element;
use crate::sidhp751::fp::Fp751Element;
// Test the first four-isogeny from the base curve E_0(F_{p^2}).
#[test]

View file

@ -23,4 +23,6 @@ pub(crate) mod fp;
pub mod constants;
#[allow(unused_assignments)]
#[allow(non_snake_case)]
pub mod sidh;
mod sidh;
pub use sidh::*;

View file

@ -6,11 +6,11 @@
// - Erkan Tairi <erkan.tairi@gmail.com>
//
use crate::sidh::field::ExtensionFieldElement;
use crate::sidh::curve::{ProjectiveCurveParameters, ProjectivePoint};
use crate::sidh::isogeny::*;
use crate::sidh::constants::*;
use crate::sidh::fp::*;
use crate::sidhp751::field::ExtensionFieldElement;
use crate::sidhp751::curve::{ProjectiveCurveParameters, ProjectivePoint};
use crate::sidhp751::isogeny::*;
use crate::sidhp751::constants::*;
use crate::sidhp751::fp::*;
#[allow(unused_imports)]
use crate::random::SecureRandom;
@ -34,7 +34,6 @@ pub const SHARED_SECRET_SIZE: usize = 188;
const MAX_INT_POINTS_ALICE: usize = 8;
const MAX_INT_POINTS_BOB: usize = 10;
const MAX_ALICE: usize = 185;
/// Alice's isogeny strategy.
pub const ALICE_ISOGENY_STRATEGY: [u8; MAX_ALICE] = [0, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 5,
@ -66,6 +65,7 @@ pub const BOB_ISOGENY_STRATEGY: [u8; MAX_BOB] = [0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
97, 97];
/// Alice's public key.
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub struct SIDHPublicKeyAlice {
pub affine_xP : ExtensionFieldElement,
@ -75,6 +75,7 @@ pub struct SIDHPublicKeyAlice {
impl SIDHPublicKeyAlice {
/// Read a public key from a byte slice. The input must be at least 564 bytes long.
#[allow(non_snake_case)]
pub fn from_bytes(bytes: &[u8]) -> SIDHPublicKeyAlice {
assert!(bytes.len() >= 564, "Too short input to SIDH public key from_bytes, expected 564 bytes");
let affine_xP = ExtensionFieldElement::from_bytes(&bytes[0..188]);
@ -93,6 +94,7 @@ impl SIDHPublicKeyAlice {
}
/// Bob's public key.
#[allow(non_snake_case)]
#[derive(Copy, Clone)]
pub struct SIDHPublicKeyBob {
pub affine_xP : ExtensionFieldElement,
@ -102,6 +104,7 @@ pub struct SIDHPublicKeyBob {
impl SIDHPublicKeyBob {
/// Read a public key from a byte slice. The input must be at least 564 bytes long.
#[allow(non_snake_case)]
pub fn from_bytes(bytes: &[u8]) -> SIDHPublicKeyBob {
assert!(bytes.len() >= 564, "Too short input to SIDH public key from_bytes, expected 564 bytes");
let affine_xP = ExtensionFieldElement::from_bytes(&bytes[0..188]);
@ -142,6 +145,7 @@ impl Arbitrary for SIDHSecretKeyAlice {
impl SIDHSecretKeyAlice {
/// Compute the corresponding public key for the given secret key.
#[allow(non_snake_case)]
pub fn public_key(&self) -> SIDHPublicKeyAlice {
let mut xP = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PB); // = ( x_P : 1) = x(P_B)
let mut xQ = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PB); //
@ -199,7 +203,9 @@ impl SIDHSecretKeyAlice {
SIDHPublicKeyAlice{ affine_xP, affine_xQ, affine_xQmP }
}
/// Compute (Alice's view of) a shared secret using Alice's secret key and Bob's public key.
#[allow(non_snake_case)]
pub fn shared_secret(&self, bob_public: &SIDHPublicKeyBob) -> [u8; SHARED_SECRET_SIZE] {
let current_curve = ProjectiveCurveParameters::recover_curve_parameters(&bob_public.affine_xP, &bob_public.affine_xQ, &bob_public.affine_xQmP);
let xP = ProjectivePoint::from_affine(&bob_public.affine_xP);
@ -264,6 +270,7 @@ impl Arbitrary for SIDHSecretKeyBob {
impl SIDHSecretKeyBob {
/// Compute the public key corresponding to the secret key.
#[allow(non_snake_case)]
pub fn public_key(&self) -> SIDHPublicKeyBob {
let mut xP = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PA); // = ( x_P : 1) = x(P_A)
let mut xQ = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PA); //
@ -315,7 +322,9 @@ impl SIDHSecretKeyBob {
SIDHPublicKeyBob{ affine_xP, affine_xQ, affine_xQmP }
}
/// Compute (Bob's view of) a shared secret using Bob's secret key and Alice's public key.
#[allow(non_snake_case)]
pub fn shared_secret(&self, alice_public: &SIDHPublicKeyAlice) -> [u8; SHARED_SECRET_SIZE] {
let mut current_curve = ProjectiveCurveParameters::recover_curve_parameters(&alice_public.affine_xP, &alice_public.affine_xQ, &alice_public.affine_xQmP);
let xP = ProjectivePoint::from_affine(&alice_public.affine_xP);
@ -416,6 +425,7 @@ mod test {
// Perform Alice's (2-isogeny) key generation, using the slow but simple multiplication-based strategy.
//
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
#[allow(non_snake_case)]
pub fn alice_keygen_slow(secret_key: &SIDHSecretKeyAlice) -> SIDHPublicKeyAlice {
let mut xP = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PB); // = ( x_P : 1) = x(P_B)
let mut xQ = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PB); //
@ -452,9 +462,11 @@ mod test {
SIDHPublicKeyAlice{ affine_xP, affine_xQ, affine_xQmP }
}
// Perform Bob's (3-isogeny) key generation, using the slow but simple multiplication-based strategy.
//
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
#[allow(non_snake_case)]
pub fn bob_keygen_slow(secret_key: &SIDHSecretKeyBob) -> SIDHPublicKeyBob {
let mut xP = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PA); // = ( x_P : 1) = x(P_A)
let mut xQ = ProjectivePoint::from_affine_prime_field(&AFFINE_X_PA); //
@ -484,9 +496,11 @@ mod test {
SIDHPublicKeyBob{ affine_xP, affine_xQ, affine_xQmP }
}
// Perform Alice's key agreement, using the slow but simple multiplication-based strategy.
//
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
#[allow(non_snake_case)]
pub fn alice_shared_secret_slow(bob_public: &SIDHPublicKeyBob, alice_secret: &SIDHSecretKeyAlice) -> [u8; SHARED_SECRET_SIZE] {
let current_curve = ProjectiveCurveParameters::recover_curve_parameters(&bob_public.affine_xP, &bob_public.affine_xQ, &bob_public.affine_xQmP);
let xP = ProjectivePoint::from_affine(&bob_public.affine_xP);
@ -513,9 +527,11 @@ mod test {
let shared_secret = j_inv.to_bytes();
shared_secret
}
// Perform Bob's key agreement, using the slow but simple multiplication-based strategy.
//
// This function just exists to ensure that the fast isogeny-tree strategy works correctly.
#[allow(non_snake_case)]
pub fn bob_shared_secret_slow(alice_public: &SIDHPublicKeyAlice, bob_secret: &SIDHSecretKeyBob) -> [u8; SHARED_SECRET_SIZE] {
let mut current_curve = ProjectiveCurveParameters::recover_curve_parameters(&alice_public.affine_xP, &alice_public.affine_xQ, &alice_public.affine_xQmP);
let xP = ProjectivePoint::from_affine(&alice_public.affine_xP);
@ -593,6 +609,7 @@ mod test {
QuickCheck::new().max_tests(8).quickcheck(shared_secrets_match as fn(SIDHSecretKeyAlice, SIDHSecretKeyBob) -> bool);
}
#[allow(non_snake_case)]
#[test]
fn alice_keygen_fast_vs_slow() {
// m_A = 2*randint(0,2^371)
@ -610,6 +627,7 @@ mod test {
"\nExpected affine_xQmP = {:?}\nfound {:?}", fast_pubkey.affine_xQmP, slow_pubkey.affine_xQmP);
}
#[allow(non_snake_case)]
#[test]
fn bob_keygen_fast_vs_slow() {
// m_B = 3*randint(0,3^238)
@ -627,6 +645,7 @@ mod test {
"\nExpected affine_xQmP = {:?}\nfound {:?}", fast_pubkey.affine_xQmP, slow_pubkey.affine_xQmP);
}
#[allow(non_snake_case)]
#[test]
fn shared_secret() {
// m_A = 2*randint(0,2^371)
@ -653,6 +672,7 @@ mod test {
"\nShared secret mismatch: Alice (slow) has {:?}\nBob (fast) has {:?}", &alice_shared_secret_slow[..], &bob_shared_secret_fast[..]);
}
#[allow(non_snake_case)]
#[test]
fn secret_point() {
// m_A = 2*randint(0,2^371)

View file

@ -0,0 +1,88 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c)2021 ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::sync::atomic::AtomicU32;
use zerotier_core_crypto::c25519::C25519KeyPair;
use zerotier_core_crypto::hash::SHA384_HASH_SIZE;
use zerotier_core_crypto::p521::P521KeyPair;
use zerotier_core_crypto::random::SecureRandom;
use zerotier_core_crypto::secret::Secret;
use zerotier_core_crypto::sidhp751::{SIDHPublicKeyAlice, SIDHPublicKeyBob, SIDHSecretKeyAlice, SIDHSecretKeyBob};
use crate::vl1::Address;
use crate::vl1::protocol::EPHEMERAL_SECRET_SIDH_TTL;
use crate::vl1::symmetricsecret::SymmetricSecret;
const EPHEMERAL_CIPHER_C25519: u8 = 1;
const EPHEMERAL_CIPHER_P521: u8 = 2;
const EPHEMERAL_CIPHER_SIDH_P751: u8 = 3;
#[derive(Copy, Clone)]
enum SIDHSecretKey {
Alice(SIDHPublicKeyAlice, SIDHSecretKeyAlice),
Bob(SIDHPublicKeyBob, SIDHSecretKeyBob)
}
impl SIDHSecretKey {
/// Generate a SIDH key pair.
///
/// SIDH is weird. A key exchange must involve one participant taking a role
/// canonically called Alice and the other wearing the Bob hat, because math.
///
/// If our local address is less than the remote address, we take the Alice role.
/// Otherwise if it's greater or equal we take the Bob role.
///
/// Everything works as long as the two sides take opposite roles. There is no
/// security implication in one side always taking one role.
pub fn generate(local_address: Address, remote_address: Address) -> SIDHSecretKey {
let mut rng = SecureRandom::get();
if local_address < remote_address {
let (p, s) = zerotier_core_crypto::sidh::sidh::generate_alice_keypair(&mut rng);
SIDHSecretKey::Alice(p, s)
} else {
let (p, s) = zerotier_core_crypto::sidh::sidh::generate_bob_keypair(&mut rng);
SIDHSecretKey::Bob(p, s)
}
}
}
/// An ephemeral secret key negotiated to implement forward secrecy.
pub struct EphemeralSecret {
timestamp_ticks: i64,
c25519: C25519KeyPair,
p521: P521KeyPair,
sidhp751: SIDHSecretKey,
}
impl EphemeralSecret {
/// Create a new ephemeral secret key.
///
/// This contains key pairs for the asymmetric key agreement algorithms used and a
/// timestamp used to enforce TTL.
pub fn new(time_ticks: i64, local_address: Address, remote_address: Address) -> Self {
EphemeralSecret {
timestamp_ticks: time_ticks,
c25519: C25519KeyPair::generate(true),
p521: P521KeyPair::generate(true).expect("NIST P-521 key pair generation failed"),
sidhp751: SIDHSecretKey::generate(local_address, remote_address),
}
}
/// Create a public version of this ephemeral secret to share with our counterparty.
pub fn public(&self) -> Vec<u8> {
todo!()
}
}
pub struct EphemeralSymmetricSecret {
secret: SymmetricSecret,
agreement_timestamp_ticks: i64,
local_secret_timestamp_ticks: i64,
encrypt_uses: AtomicU32,
}

View file

@ -23,6 +23,7 @@ pub(crate) mod mac;
pub(crate) mod fragmentedpacket;
pub(crate) mod whoisqueue;
pub(crate) mod ephemeral;
pub(crate) mod symmetricsecret;
pub use address::Address;
pub use mac::MAC;

View file

@ -49,6 +49,18 @@ pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
/// KBKDF usage label for acknowledgement of a shared secret.
pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_ACK: u8 = b'A';
/// Try to re-key ephemeral keys after this time.
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 1000 * 60 * 60; // 1 hour
/// Maximum number of times to use an ephemeral secret before trying to replace it.
pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST security limit
/// Ephemeral secret reject after time.
pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2;
/// Ephemeral secret reject after uses.
pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST security limit
/// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5;

View file

@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c)2021 ZeroTier, Inc.
* https://www.zerotier.com/
*/
use parking_lot::Mutex;
use zerotier_core_crypto::aes_gmac_siv::{AesCtr, AesGmacSiv};
use zerotier_core_crypto::hash::SHA384_HASH_SIZE;
use zerotier_core_crypto::kbkdf::zt_kbkdf_hmac_sha384;
use zerotier_core_crypto::secret::Secret;
use crate::util::pool::{Pool, PoolFactory};
use crate::vl1::protocol::{KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC};
pub struct AesGmacSivPoolFactory(Secret<SHA384_HASH_SIZE>, Secret<SHA384_HASH_SIZE>);
impl PoolFactory<AesGmacSiv> for AesGmacSivPoolFactory {
#[inline(always)]
fn create(&self) -> AesGmacSiv { AesGmacSiv::new(&self.0.0[0..32], &self.1.0[0..32]) }
#[inline(always)]
fn reset(&self, obj: &mut AesGmacSiv) { obj.reset(); }
}
/// A symmetric secret key negotiated between peers.
///
/// This contains the key and several sub-keys and ciphers keyed with sub-keys.
pub struct SymmetricSecret {
pub key: Secret<SHA384_HASH_SIZE>,
pub packet_hmac_key: Secret<SHA384_HASH_SIZE>,
pub hello_dictionary_keyed_cipher: Mutex<AesCtr>,
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
}
impl SymmetricSecret {
pub fn new(base_key: Secret<SHA384_HASH_SIZE>) -> SymmetricSecret {
let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0);
let usage_hello_dictionary_key = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0);
let aes_factory = AesGmacSivPoolFactory(
zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0),
zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0));
SymmetricSecret {
key: base_key,
packet_hmac_key: usage_packet_hmac,
hello_dictionary_keyed_cipher: Mutex::new(AesCtr::new(&usage_hello_dictionary_key.0[0..32])),
aes_gmac_siv: Pool::new(2, aes_factory),
}
}
}