Browse Source

Merge pull request #1 from str4d/from-pairing

Migrate curve traits and tests, and WNAF, from pairing
master
ebfull 6 years ago committed by GitHub
parent
commit
2ffa94f714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      Cargo.toml
  2. 196
      src/lib.rs
  3. 421
      src/tests/mod.rs
  4. 181
      src/wnaf.rs

7
Cargo.toml

@ -1,7 +1,10 @@
[package]
name = "group"
version = "0.0.0"
authors = ["Sean Bowe <[email protected]>"]
authors = [
"Sean Bowe <[email protected]>",
"Jack Grigg <[email protected]>",
]
license = "MIT/Apache-2.0"
description = "Elliptic curve group traits and utilities"
@ -10,3 +13,5 @@ homepage = "https://github.com/ebfull/group"
repository = "https://github.com/ebfull/group"
[dependencies]
ff = "0.4"
rand = "0.4"

196
src/lib.rs

@ -0,0 +1,196 @@
extern crate ff;
extern crate rand;
use ff::{PrimeField, PrimeFieldDecodingError, ScalarEngine, SqrtField};
use std::error::Error;
use std::fmt;
pub mod tests;
mod wnaf;
pub use self::wnaf::Wnaf;
/// Projective representation of an elliptic curve point guaranteed to be
/// in the correct prime order subgroup.
pub trait CurveProjective:
PartialEq
+ Eq
+ Sized
+ Copy
+ Clone
+ Send
+ Sync
+ fmt::Debug
+ fmt::Display
+ rand::Rand
+ 'static
{
type Engine: ScalarEngine<Fr = Self::Scalar>;
type Scalar: PrimeField + SqrtField;
type Base: SqrtField;
type Affine: CurveAffine<Projective = Self, Scalar = Self::Scalar>;
/// Returns the additive identity.
fn zero() -> Self;
/// Returns a fixed generator of unknown exponent.
fn one() -> Self;
/// Determines if this point is the point at infinity.
fn is_zero(&self) -> bool;
/// Normalizes a slice of projective elements so that
/// conversion to affine is cheap.
fn batch_normalization(v: &mut [Self]);
/// Checks if the point is already "normalized" so that
/// cheap affine conversion is possible.
fn is_normalized(&self) -> bool;
/// Doubles this element.
fn double(&mut self);
/// Adds another element to this element.
fn add_assign(&mut self, other: &Self);
/// Subtracts another element from this element.
fn sub_assign(&mut self, other: &Self) {
let mut tmp = *other;
tmp.negate();
self.add_assign(&tmp);
}
/// Adds an affine element to this element.
fn add_assign_mixed(&mut self, other: &Self::Affine);
/// Negates this element.
fn negate(&mut self);
/// Performs scalar multiplication of this element.
fn mul_assign<S: Into<<Self::Scalar as PrimeField>::Repr>>(&mut self, other: S);
/// Converts this element into its affine representation.
fn into_affine(&self) -> Self::Affine;
/// Recommends a wNAF window table size given a scalar. Always returns a number
/// between 2 and 22, inclusive.
fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> usize;
/// Recommends a wNAF window size given the number of scalars you intend to multiply
/// a base by. Always returns a number between 2 and 22, inclusive.
fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize;
}
/// Affine representation of an elliptic curve point guaranteed to be
/// in the correct prime order subgroup.
pub trait CurveAffine:
Copy + Clone + Sized + Send + Sync + fmt::Debug + fmt::Display + PartialEq + Eq + 'static
{
type Engine: ScalarEngine<Fr = Self::Scalar>;
type Scalar: PrimeField + SqrtField;
type Base: SqrtField;
type Projective: CurveProjective<Affine = Self, Scalar = Self::Scalar>;
type Uncompressed: EncodedPoint<Affine = Self>;
type Compressed: EncodedPoint<Affine = Self>;
/// Returns the additive identity.
fn zero() -> Self;
/// Returns a fixed generator of unknown exponent.
fn one() -> Self;
/// Determines if this point represents the point at infinity; the
/// additive identity.
fn is_zero(&self) -> bool;
/// Negates this element.
fn negate(&mut self);
/// Performs scalar multiplication of this element with mixed addition.
fn mul<S: Into<<Self::Scalar as PrimeField>::Repr>>(&self, other: S) -> Self::Projective;
/// Converts this element into its affine representation.
fn into_projective(&self) -> Self::Projective;
/// Converts this element into its compressed encoding, so long as it's not
/// the point at infinity.
fn into_compressed(&self) -> Self::Compressed {
<Self::Compressed as EncodedPoint>::from_affine(*self)
}
/// Converts this element into its uncompressed encoding, so long as it's not
/// the point at infinity.
fn into_uncompressed(&self) -> Self::Uncompressed {
<Self::Uncompressed as EncodedPoint>::from_affine(*self)
}
}
/// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`.
pub trait EncodedPoint:
Sized + Send + Sync + AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + 'static
{
type Affine: CurveAffine;
/// Creates an empty representation.
fn empty() -> Self;
/// Returns the number of bytes consumed by this representation.
fn size() -> usize;
/// Converts an `EncodedPoint` into a `CurveAffine` element,
/// if the encoding represents a valid element.
fn into_affine(&self) -> Result<Self::Affine, GroupDecodingError>;
/// Converts an `EncodedPoint` into a `CurveAffine` element,
/// without guaranteeing that the encoding represents a valid
/// element. This is useful when the caller knows the encoding is
/// valid already.
///
/// If the encoding is invalid, this can break API invariants,
/// so caution is strongly encouraged.
fn into_affine_unchecked(&self) -> Result<Self::Affine, GroupDecodingError>;
/// Creates an `EncodedPoint` from an affine point, as long as the
/// point is not the point at infinity.
fn from_affine(affine: Self::Affine) -> Self;
}
/// An error that may occur when trying to decode an `EncodedPoint`.
#[derive(Debug)]
pub enum GroupDecodingError {
/// The coordinate(s) do not lie on the curve.
NotOnCurve,
/// The element is not part of the r-order subgroup.
NotInSubgroup,
/// One of the coordinates could not be decoded
CoordinateDecodingError(&'static str, PrimeFieldDecodingError),
/// The compression mode of the encoded element was not as expected
UnexpectedCompressionMode,
/// The encoding contained bits that should not have been set
UnexpectedInformation,
}
impl Error for GroupDecodingError {
fn description(&self) -> &str {
match *self {
GroupDecodingError::NotOnCurve => "coordinate(s) do not lie on the curve",
GroupDecodingError::NotInSubgroup => "the element is not part of an r-order subgroup",
GroupDecodingError::CoordinateDecodingError(..) => "coordinate(s) could not be decoded",
GroupDecodingError::UnexpectedCompressionMode => {
"encoding has unexpected compression mode"
}
GroupDecodingError::UnexpectedInformation => "encoding has unexpected information",
}
}
}
impl fmt::Display for GroupDecodingError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
GroupDecodingError::CoordinateDecodingError(description, ref err) => {
write!(f, "{} decoding error: {}", description, err)
}
_ => write!(f, "{}", self.description()),
}
}
}

421
src/tests/mod.rs

@ -0,0 +1,421 @@
use rand::{Rand, Rng, SeedableRng, XorShiftRng};
use {CurveAffine, CurveProjective, EncodedPoint};
pub fn curve_tests<G: CurveProjective>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
// Negation edge case with zero.
{
let mut z = G::zero();
z.negate();
assert!(z.is_zero());
}
// Doubling edge case with zero.
{
let mut z = G::zero();
z.double();
assert!(z.is_zero());
}
// Addition edge cases with zero
{
let mut r = G::rand(&mut rng);
let rcopy = r;
r.add_assign(&G::zero());
assert_eq!(r, rcopy);
r.add_assign_mixed(&G::Affine::zero());
assert_eq!(r, rcopy);
let mut z = G::zero();
z.add_assign(&G::zero());
assert!(z.is_zero());
z.add_assign_mixed(&G::Affine::zero());
assert!(z.is_zero());
let mut z2 = z;
z2.add_assign(&r);
z.add_assign_mixed(&r.into_affine());
assert_eq!(z, z2);
assert_eq!(z, r);
}
// Transformations
{
let a = G::rand(&mut rng);
let b = a.into_affine().into_projective();
let c = a.into_affine()
.into_projective()
.into_affine()
.into_projective();
assert_eq!(a, b);
assert_eq!(b, c);
}
random_addition_tests::<G>();
random_multiplication_tests::<G>();
random_doubling_tests::<G>();
random_negation_tests::<G>();
random_transformation_tests::<G>();
random_wnaf_tests::<G>();
random_encoding_tests::<G::Affine>();
}
fn random_wnaf_tests<G: CurveProjective>() {
use ff::PrimeField;
use wnaf::*;
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
{
let mut table = vec![];
let mut wnaf = vec![];
for w in 2..14 {
for _ in 0..100 {
let g = G::rand(&mut rng);
let s = G::Scalar::rand(&mut rng).into_repr();
let mut g1 = g;
g1.mul_assign(s);
wnaf_table(&mut table, g, w);
wnaf_form(&mut wnaf, s, w);
let g2 = wnaf_exp(&table, &wnaf);
assert_eq!(g1, g2);
}
}
}
{
fn only_compiles_if_send<S: Send>(_: &S) {}
for _ in 0..100 {
let g = G::rand(&mut rng);
let s = G::Scalar::rand(&mut rng).into_repr();
let mut g1 = g;
g1.mul_assign(s);
let g2 = {
let mut wnaf = Wnaf::new();
wnaf.base(g, 1).scalar(s)
};
let g3 = {
let mut wnaf = Wnaf::new();
wnaf.scalar(s).base(g)
};
let g4 = {
let mut wnaf = Wnaf::new();
let mut shared = wnaf.base(g, 1).shared();
only_compiles_if_send(&shared);
shared.scalar(s)
};
let g5 = {
let mut wnaf = Wnaf::new();
let mut shared = wnaf.scalar(s).shared();
only_compiles_if_send(&shared);
shared.base(g)
};
let g6 = {
let mut wnaf = Wnaf::new();
{
// Populate the vectors.
wnaf.base(rng.gen(), 1).scalar(rng.gen());
}
wnaf.base(g, 1).scalar(s)
};
let g7 = {
let mut wnaf = Wnaf::new();
{
// Populate the vectors.
wnaf.base(rng.gen(), 1).scalar(rng.gen());
}
wnaf.scalar(s).base(g)
};
let g8 = {
let mut wnaf = Wnaf::new();
{
// Populate the vectors.
wnaf.base(rng.gen(), 1).scalar(rng.gen());
}
let mut shared = wnaf.base(g, 1).shared();
only_compiles_if_send(&shared);
shared.scalar(s)
};
let g9 = {
let mut wnaf = Wnaf::new();
{
// Populate the vectors.
wnaf.base(rng.gen(), 1).scalar(rng.gen());
}
let mut shared = wnaf.scalar(s).shared();
only_compiles_if_send(&shared);
shared.base(g)
};
assert_eq!(g1, g2);
assert_eq!(g1, g3);
assert_eq!(g1, g4);
assert_eq!(g1, g5);
assert_eq!(g1, g6);
assert_eq!(g1, g7);
assert_eq!(g1, g8);
assert_eq!(g1, g9);
}
}
}
fn random_negation_tests<G: CurveProjective>() {
use ff::Field;
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let r = G::rand(&mut rng);
let s = G::Scalar::rand(&mut rng);
let mut sneg = s;
sneg.negate();
let mut t1 = r;
t1.mul_assign(s);
let mut t2 = r;
t2.mul_assign(sneg);
let mut t3 = t1;
t3.add_assign(&t2);
assert!(t3.is_zero());
let mut t4 = t1;
t4.add_assign_mixed(&t2.into_affine());
assert!(t4.is_zero());
t1.negate();
assert_eq!(t1, t2);
}
}
fn random_doubling_tests<G: CurveProjective>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let mut a = G::rand(&mut rng);
let mut b = G::rand(&mut rng);
// 2(a + b)
let mut tmp1 = a;
tmp1.add_assign(&b);
tmp1.double();
// 2a + 2b
a.double();
b.double();
let mut tmp2 = a;
tmp2.add_assign(&b);
let mut tmp3 = a;
tmp3.add_assign_mixed(&b.into_affine());
assert_eq!(tmp1, tmp2);
assert_eq!(tmp1, tmp3);
}
}
fn random_multiplication_tests<G: CurveProjective>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let mut a = G::rand(&mut rng);
let mut b = G::rand(&mut rng);
let a_affine = a.into_affine();
let b_affine = b.into_affine();
let s = G::Scalar::rand(&mut rng);
// s ( a + b )
let mut tmp1 = a;
tmp1.add_assign(&b);
tmp1.mul_assign(s);
// sa + sb
a.mul_assign(s);
b.mul_assign(s);
let mut tmp2 = a;
tmp2.add_assign(&b);
// Affine multiplication
let mut tmp3 = a_affine.mul(s);
tmp3.add_assign(&b_affine.mul(s));
assert_eq!(tmp1, tmp2);
assert_eq!(tmp1, tmp3);
}
}
fn random_addition_tests<G: CurveProjective>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let a = G::rand(&mut rng);
let b = G::rand(&mut rng);
let c = G::rand(&mut rng);
let a_affine = a.into_affine();
let b_affine = b.into_affine();
let c_affine = c.into_affine();
// a + a should equal the doubling
{
let mut aplusa = a;
aplusa.add_assign(&a);
let mut aplusamixed = a;
aplusamixed.add_assign_mixed(&a.into_affine());
let mut adouble = a;
adouble.double();
assert_eq!(aplusa, adouble);
assert_eq!(aplusa, aplusamixed);
}
let mut tmp = vec![G::zero(); 6];
// (a + b) + c
tmp[0] = a;
tmp[0].add_assign(&b);
tmp[0].add_assign(&c);
// a + (b + c)
tmp[1] = b;
tmp[1].add_assign(&c);
tmp[1].add_assign(&a);
// (a + c) + b
tmp[2] = a;
tmp[2].add_assign(&c);
tmp[2].add_assign(&b);
// Mixed addition
// (a + b) + c
tmp[3] = a_affine.into_projective();
tmp[3].add_assign_mixed(&b_affine);
tmp[3].add_assign_mixed(&c_affine);
// a + (b + c)
tmp[4] = b_affine.into_projective();
tmp[4].add_assign_mixed(&c_affine);
tmp[4].add_assign_mixed(&a_affine);
// (a + c) + b
tmp[5] = a_affine.into_projective();
tmp[5].add_assign_mixed(&c_affine);
tmp[5].add_assign_mixed(&b_affine);
// Comparisons
for i in 0..6 {
for j in 0..6 {
assert_eq!(tmp[i], tmp[j]);
assert_eq!(tmp[i].into_affine(), tmp[j].into_affine());
}
assert!(tmp[i] != a);
assert!(tmp[i] != b);
assert!(tmp[i] != c);
assert!(a != tmp[i]);
assert!(b != tmp[i]);
assert!(c != tmp[i]);
}
}
}
fn random_transformation_tests<G: CurveProjective>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let g = G::rand(&mut rng);
let g_affine = g.into_affine();
let g_projective = g_affine.into_projective();
assert_eq!(g, g_projective);
}
// Batch normalization
for _ in 0..10 {
let mut v = (0..1000).map(|_| G::rand(&mut rng)).collect::<Vec<_>>();
for i in &v {
assert!(!i.is_normalized());
}
use rand::distributions::{IndependentSample, Range};
let between = Range::new(0, 1000);
// Sprinkle in some normalized points
for _ in 0..5 {
v[between.ind_sample(&mut rng)] = G::zero();
}
for _ in 0..5 {
let s = between.ind_sample(&mut rng);
v[s] = v[s].into_affine().into_projective();
}
let expected_v = v.iter()
.map(|v| v.into_affine().into_projective())
.collect::<Vec<_>>();
G::batch_normalization(&mut v);
for i in &v {
assert!(i.is_normalized());
}
assert_eq!(v, expected_v);
}
}
fn random_encoding_tests<G: CurveAffine>() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
assert_eq!(
G::zero().into_uncompressed().into_affine().unwrap(),
G::zero()
);
assert_eq!(
G::zero().into_compressed().into_affine().unwrap(),
G::zero()
);
for _ in 0..1000 {
let mut r = G::Projective::rand(&mut rng).into_affine();
let uncompressed = r.into_uncompressed();
let de_uncompressed = uncompressed.into_affine().unwrap();
assert_eq!(de_uncompressed, r);
let compressed = r.into_compressed();
let de_compressed = compressed.into_affine().unwrap();
assert_eq!(de_compressed, r);
r.negate();
let compressed = r.into_compressed();
let de_compressed = compressed.into_affine().unwrap();
assert_eq!(de_compressed, r);
}
}

181
src/wnaf.rs

@ -0,0 +1,181 @@
use ff::{PrimeField, PrimeFieldRepr};
use super::CurveProjective;
/// Replaces the contents of `table` with a w-NAF window table for the given window size.
pub(crate) fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) {
table.truncate(0);
table.reserve(1 << (window - 1));
let mut dbl = base;
dbl.double();
for _ in 0..(1 << (window - 1)) {
table.push(base);
base.add_assign(&dbl);
}
}
/// Replaces the contents of `wnaf` with the w-NAF representation of a scalar.
pub(crate) fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) {
wnaf.truncate(0);
while !c.is_zero() {
let mut u;
if c.is_odd() {
u = (c.as_ref()[0] % (1 << (window + 1))) as i64;
if u > (1 << window) {
u -= 1 << (window + 1);
}
if u > 0 {
c.sub_noborrow(&S::from(u as u64));
} else {
c.add_nocarry(&S::from((-u) as u64));
}
} else {
u = 0;
}
wnaf.push(u);
c.div2();
}
}
/// Performs w-NAF exponentiation with the provided window table and w-NAF form scalar.
///
/// This function must be provided a `table` and `wnaf` that were constructed with
/// the same window size; otherwise, it may panic or produce invalid results.
pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G {
let mut result = G::zero();
let mut found_one = false;
for n in wnaf.iter().rev() {
if found_one {
result.double();
}
if *n != 0 {
found_one = true;
if *n > 0 {
result.add_assign(&table[(n / 2) as usize]);
} else {
result.sub_assign(&table[((-n) / 2) as usize]);
}
}
}
result
}
/// A "w-ary non-adjacent form" exponentiation context.
#[derive(Debug)]
pub struct Wnaf<W, B, S> {
base: B,
scalar: S,
window_size: W,
}
impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> {
/// Construct a new wNAF context without allocating.
pub fn new() -> Self {
Wnaf {
base: vec![],
scalar: vec![],
window_size: (),
}
}
/// Given a base and a number of scalars, compute a window table and return a `Wnaf` object that
/// can perform exponentiations with `.scalar(..)`.
pub fn base(&mut self, base: G, num_scalars: usize) -> Wnaf<usize, &[G], &mut Vec<i64>> {
// Compute the appropriate window size based on the number of scalars.
let window_size = G::recommended_wnaf_for_num_scalars(num_scalars);
// Compute a wNAF table for the provided base and window size.
wnaf_table(&mut self.base, base, window_size);
// Return a Wnaf object that immutably borrows the computed base storage location,
// but mutably borrows the scalar storage location.
Wnaf {
base: &self.base[..],
scalar: &mut self.scalar,
window_size,
}
}
/// Given a scalar, compute its wNAF representation and return a `Wnaf` object that can perform
/// exponentiations with `.base(..)`.
pub fn scalar(
&mut self,
scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr,
) -> Wnaf<usize, &mut Vec<G>, &[i64]> {
// Compute the appropriate window size for the scalar.
let window_size = G::recommended_wnaf_for_scalar(scalar);
// Compute the wNAF form of the scalar.
wnaf_form(&mut self.scalar, scalar, window_size);
// Return a Wnaf object that mutably borrows the base storage location, but
// immutably borrows the computed wNAF form scalar location.
Wnaf {
base: &mut self.base,
scalar: &self.scalar[..],
window_size,
}
}
}
impl<'a, G: CurveProjective> Wnaf<usize, &'a [G], &'a mut Vec<i64>> {
/// Constructs new space for the scalar representation while borrowing
/// the computed window table, for sending the window table across threads.
pub fn shared(&self) -> Wnaf<usize, &'a [G], Vec<i64>> {
Wnaf {
base: self.base,
scalar: vec![],
window_size: self.window_size,
}
}
}
impl<'a, G: CurveProjective> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> {
/// Constructs new space for the window table while borrowing
/// the computed scalar representation, for sending the scalar representation
/// across threads.
pub fn shared(&self) -> Wnaf<usize, Vec<G>, &'a [i64]> {
Wnaf {
base: vec![],
scalar: self.scalar,
window_size: self.window_size,
}
}
}
impl<B, S: AsRef<[i64]>> Wnaf<usize, B, S> {
/// Performs exponentiation given a base.
pub fn base<G: CurveProjective>(&mut self, base: G) -> G
where
B: AsMut<Vec<G>>,
{
wnaf_table(self.base.as_mut(), base, self.window_size);
wnaf_exp(self.base.as_mut(), self.scalar.as_ref())
}
}
impl<B, S: AsMut<Vec<i64>>> Wnaf<usize, B, S> {
/// Performs exponentiation given a scalar.
pub fn scalar<G: CurveProjective>(
&mut self,
scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr,
) -> G
where
B: AsRef<[G]>,
{
wnaf_form(self.scalar.as_mut(), scalar, self.window_size);
wnaf_exp(self.base.as_ref(), self.scalar.as_mut())
}
}
Loading…
Cancel
Save