Implementation of fundamental circuitry and primitive Jubjub curve arithmetic.
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
SqrtField,
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
use super::{
|
||||
JubjubParams,
|
||||
Unknown,
|
||||
PrimeOrder,
|
||||
Fs,
|
||||
FsRepr,
|
||||
montgomery
|
||||
};
|
||||
|
||||
use rand::{
|
||||
Rng
|
||||
};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// Represents the affine point (X/Z, Y/Z) via the extended
|
||||
// twisted Edwards coordinates.
|
||||
pub struct Point<E: Engine, Subgroup> {
|
||||
x: E::Fr,
|
||||
y: E::Fr,
|
||||
t: E::Fr,
|
||||
z: E::Fr,
|
||||
_marker: PhantomData<Subgroup>
|
||||
}
|
||||
|
||||
fn convert_subgroup<E: Engine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
|
||||
{
|
||||
Point {
|
||||
x: from.x,
|
||||
y: from.y,
|
||||
t: from.t,
|
||||
z: from.z,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
|
||||
{
|
||||
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
|
||||
{
|
||||
convert_subgroup(&p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> Clone for Point<E, Subgroup>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
convert_subgroup(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> PartialEq for Point<E, Subgroup> {
|
||||
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
|
||||
// p1 = (x1/z1, y1/z1)
|
||||
// p2 = (x2/z2, y2/z2)
|
||||
// Deciding that these two points are equal is a matter of
|
||||
// determining that x1/z1 = x2/z2, or equivalently that
|
||||
// x1*z2 = x2*z1, and similarly for y.
|
||||
|
||||
let mut x1 = self.x;
|
||||
x1.mul_assign(&other.z);
|
||||
|
||||
let mut y1 = self.y;
|
||||
y1.mul_assign(&other.z);
|
||||
|
||||
let mut x2 = other.x;
|
||||
x2.mul_assign(&self.z);
|
||||
|
||||
let mut y2 = other.y;
|
||||
y2.mul_assign(&self.z);
|
||||
|
||||
x1 == x2 && y1 == y2
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Point<E, Unknown> {
|
||||
/// This guarantees the point is in the prime order subgroup
|
||||
pub fn mul_by_cofactor(&self, params: &JubjubParams<E>) -> Point<E, PrimeOrder>
|
||||
{
|
||||
let tmp = self.double(params)
|
||||
.double(params)
|
||||
.double(params);
|
||||
|
||||
convert_subgroup(&tmp)
|
||||
}
|
||||
|
||||
pub fn rand<R: Rng>(rng: &mut R, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
loop {
|
||||
// given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2)
|
||||
let x: E::Fr = rng.gen();
|
||||
let mut x2 = x;
|
||||
x2.square();
|
||||
|
||||
let mut num = E::Fr::one();
|
||||
num.add_assign(&x2);
|
||||
|
||||
x2.mul_assign(¶ms.edwards_d);
|
||||
|
||||
let mut den = E::Fr::one();
|
||||
den.sub_assign(&x2);
|
||||
|
||||
match den.inverse() {
|
||||
Some(invden) => {
|
||||
num.mul_assign(&invden);
|
||||
|
||||
match num.sqrt() {
|
||||
Some(mut y) => {
|
||||
if y.into_repr().is_odd() != rng.gen() {
|
||||
y.negate();
|
||||
}
|
||||
|
||||
let mut t = x;
|
||||
t.mul_assign(&y);
|
||||
|
||||
return Point {
|
||||
x: x,
|
||||
y: y,
|
||||
t: t,
|
||||
z: E::Fr::one(),
|
||||
_marker: PhantomData
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> Point<E, Subgroup> {
|
||||
/// Convert from a Montgomery point
|
||||
pub fn from_montgomery(
|
||||
m: &montgomery::Point<E, Subgroup>,
|
||||
params: &JubjubParams<E>
|
||||
) -> Self
|
||||
{
|
||||
match m.into_xy() {
|
||||
None => {
|
||||
// Map the point at infinity to the neutral element.
|
||||
Point::zero()
|
||||
},
|
||||
Some((x, y)) => {
|
||||
// The map from a Montgomery curve is defined as:
|
||||
// (x, y) -> (u, v) where
|
||||
// u = x / y
|
||||
// v = (x - 1) / (x + 1)
|
||||
//
|
||||
// This map is not defined for y = 0 and x = -1.
|
||||
//
|
||||
// y = 0 is a valid point only for x = 0:
|
||||
// y^2 = x^3 + A.x^2 + x
|
||||
// 0 = x^3 + A.x^2 + x
|
||||
// 0 = x(x^2 + A.x + 1)
|
||||
// We have: x = 0 OR x^2 + A.x + 1 = 0
|
||||
// x^2 + A.x + 1 = 0
|
||||
// (2.x + A)^2 = A^2 - 4 (Complete the square.)
|
||||
// The left hand side is a square, and so if A^2 - 4
|
||||
// is nonsquare, there is no solution. Indeed, A^2 - 4
|
||||
// is nonsquare.
|
||||
//
|
||||
// (0, 0) is a point of order 2, and so we map it to
|
||||
// (0, -1) in the twisted Edwards curve, which is the
|
||||
// only point of order 2 that is not the neutral element.
|
||||
if y.is_zero() {
|
||||
// This must be the point (0, 0) as above.
|
||||
let mut neg1 = E::Fr::one();
|
||||
neg1.negate();
|
||||
|
||||
Point {
|
||||
x: E::Fr::zero(),
|
||||
y: neg1,
|
||||
t: E::Fr::zero(),
|
||||
z: E::Fr::one(),
|
||||
_marker: PhantomData
|
||||
}
|
||||
} else {
|
||||
// Otherwise, as stated above, the mapping is still
|
||||
// not defined at x = -1. However, x = -1 is not
|
||||
// on the curve when A - 2 is nonsquare:
|
||||
// y^2 = x^3 + A.x^2 + x
|
||||
// y^2 = (-1) + A + (-1)
|
||||
// y^2 = A - 2
|
||||
// Indeed, A - 2 is nonsquare.
|
||||
|
||||
let mut u = x;
|
||||
u.mul_assign(&y.inverse().expect("y is nonzero"));
|
||||
|
||||
let mut v = x;
|
||||
v.sub_assign(&E::Fr::one());
|
||||
{
|
||||
let mut tmp = x;
|
||||
tmp.add_assign(&E::Fr::one());
|
||||
v.mul_assign(&tmp.inverse().expect("A - 2 is nonsquare"));
|
||||
}
|
||||
|
||||
// The resulting x-coordinate needs to be scaled.
|
||||
u.mul_assign(¶ms.scale);
|
||||
|
||||
let mut t = u;
|
||||
t.mul_assign(&v);
|
||||
|
||||
Point {
|
||||
x: u,
|
||||
y: v,
|
||||
t: t,
|
||||
z: E::Fr::one(),
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to cast this as a prime order element, failing if it's
|
||||
/// not in the prime order subgroup.
|
||||
pub fn as_prime_order(&self, params: &JubjubParams<E>) -> Option<Point<E, PrimeOrder>> {
|
||||
if self.mul(Fs::char(), params) == Point::zero() {
|
||||
Some(convert_subgroup(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Point {
|
||||
x: E::Fr::zero(),
|
||||
y: E::Fr::one(),
|
||||
t: E::Fr::zero(),
|
||||
z: E::Fr::one(),
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_xy(&self) -> (E::Fr, E::Fr)
|
||||
{
|
||||
let zinv = self.z.inverse().unwrap();
|
||||
|
||||
let mut x = self.x;
|
||||
x.mul_assign(&zinv);
|
||||
|
||||
let mut y = self.y;
|
||||
y.mul_assign(&zinv);
|
||||
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Self {
|
||||
let mut p = self.clone();
|
||||
|
||||
p.x.negate();
|
||||
p.t.negate();
|
||||
|
||||
p
|
||||
}
|
||||
|
||||
pub fn double(&self, params: &JubjubParams<E>) -> Self {
|
||||
self.add(self, params)
|
||||
}
|
||||
|
||||
pub fn add(&self, other: &Self, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
// A = x1 * x2
|
||||
let mut a = self.x;
|
||||
a.mul_assign(&other.x);
|
||||
|
||||
// B = y1 * y2
|
||||
let mut b = self.y;
|
||||
b.mul_assign(&other.y);
|
||||
|
||||
// C = d * t1 * t2
|
||||
let mut c = params.edwards_d;
|
||||
c.mul_assign(&self.t);
|
||||
c.mul_assign(&other.t);
|
||||
|
||||
// D = z1 * z2
|
||||
let mut d = self.z;
|
||||
d.mul_assign(&other.z);
|
||||
|
||||
// H = B - aA
|
||||
// = B + A
|
||||
let mut h = b;
|
||||
h.add_assign(&a);
|
||||
|
||||
// E = (x1 + y1) * (x2 + y2) - A - B
|
||||
// = (x1 + y1) * (x2 + y2) - H
|
||||
let mut e = self.x;
|
||||
e.add_assign(&self.y);
|
||||
{
|
||||
let mut tmp = other.x;
|
||||
tmp.add_assign(&other.y);
|
||||
e.mul_assign(&tmp);
|
||||
}
|
||||
e.sub_assign(&h);
|
||||
|
||||
// F = D - C
|
||||
let mut f = d;
|
||||
f.sub_assign(&c);
|
||||
|
||||
// G = D + C
|
||||
let mut g = d;
|
||||
g.add_assign(&c);
|
||||
|
||||
// x3 = E * F
|
||||
let mut x3 = e;
|
||||
x3.mul_assign(&f);
|
||||
|
||||
// y3 = G * H
|
||||
let mut y3 = g;
|
||||
y3.mul_assign(&h);
|
||||
|
||||
// t3 = E * H
|
||||
let mut t3 = e;
|
||||
t3.mul_assign(&h);
|
||||
|
||||
// z3 = F * G
|
||||
let mut z3 = f;
|
||||
z3.mul_assign(&g);
|
||||
|
||||
Point {
|
||||
x: x3,
|
||||
y: y3,
|
||||
t: t3,
|
||||
z: z3,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul<S: Into<FsRepr>>(&self, scalar: S, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
let mut res = Self::zero();
|
||||
|
||||
for b in BitIterator::new(scalar.into()) {
|
||||
res = res.double(params);
|
||||
|
||||
if b {
|
||||
res = res.add(self, params);
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{XorShiftRng, SeedableRng, Rand};
|
||||
use super::{JubjubParams, Point, PrimeOrder, Fs};
|
||||
use pairing::bls12_381::{Bls12};
|
||||
use pairing::{Engine, Field};
|
||||
|
||||
fn is_on_curve<E: Engine>(
|
||||
x: E::Fr,
|
||||
y: E::Fr,
|
||||
params: &JubjubParams<E>
|
||||
) -> bool
|
||||
{
|
||||
let mut x2 = x;
|
||||
x2.square();
|
||||
|
||||
let mut y2 = y;
|
||||
y2.square();
|
||||
|
||||
// -x^2 + y^2
|
||||
let mut lhs = y2;
|
||||
lhs.sub_assign(&x2);
|
||||
|
||||
// 1 + d x^2 y^2
|
||||
let mut rhs = y2;
|
||||
rhs.mul_assign(&x2);
|
||||
rhs.mul_assign(¶ms.edwards_d);
|
||||
rhs.add_assign(&E::Fr::one());
|
||||
|
||||
lhs == rhs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rand() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
let (x, y) = Point::rand(&mut rng, ¶ms).into_xy();
|
||||
|
||||
assert!(is_on_curve(x, y, ¶ms));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identities() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
let z = Point::<Bls12, PrimeOrder>::zero();
|
||||
assert!(z.double(¶ms) == z);
|
||||
assert!(z.negate() == z);
|
||||
|
||||
for _ in 0..100 {
|
||||
let r = Point::rand(&mut rng, ¶ms);
|
||||
|
||||
assert!(r.add(&Point::zero(), ¶ms) == r);
|
||||
assert!(r.add(&r.negate(), ¶ms) == Point::zero());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_associativity() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let a = Point::rand(&mut rng, ¶ms);
|
||||
let b = Point::rand(&mut rng, ¶ms);
|
||||
let c = Point::rand(&mut rng, ¶ms);
|
||||
|
||||
assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_order() {
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
// The neutral element is in the prime order subgroup.
|
||||
assert!(Point::<Bls12, PrimeOrder>::zero().as_prime_order(params).is_some());
|
||||
|
||||
for _ in 0..50 {
|
||||
// Pick a random point and multiply it by the cofactor
|
||||
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||
|
||||
// Any point multiplied by the cofactor will be in the prime
|
||||
// order subgroup
|
||||
assert!(base.as_prime_order(params).is_some());
|
||||
}
|
||||
|
||||
// It's very likely that at least one out of 50 random points on the curve
|
||||
// is not in the prime order subgroup.
|
||||
let mut at_least_one_not_in_prime_order_subgroup = false;
|
||||
for _ in 0..50 {
|
||||
// Pick a random point.
|
||||
let base = Point::rand(rng, params);
|
||||
|
||||
at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none();
|
||||
}
|
||||
assert!(at_least_one_not_in_prime_order_subgroup);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul_associativity() {
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
// Pick a random point and multiply it by the cofactor
|
||||
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||
|
||||
let mut a = Fs::rand(rng);
|
||||
let b = Fs::rand(rng);
|
||||
let c = Fs::rand(rng);
|
||||
|
||||
let res1 = base.mul(a, params).mul(b, params).mul(c, params);
|
||||
let res2 = base.mul(b, params).mul(c, params).mul(a, params);
|
||||
let res3 = base.mul(c, params).mul(a, params).mul(b, params);
|
||||
a.mul_assign(&b);
|
||||
a.mul_assign(&c);
|
||||
let res4 = base.mul(a, params);
|
||||
|
||||
assert!(res1 == res2);
|
||||
assert!(res2 == res3);
|
||||
assert!(res3 == res4);
|
||||
|
||||
let (x, y) = res1.into_xy();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
let (x, y) = res2.into_xy();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
let (x, y) = res3.into_xy();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_montgomery_conversion() {
|
||||
use super::montgomery;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..200 {
|
||||
// compute base in montgomery
|
||||
let base = montgomery::Point::rand(rng, params);
|
||||
|
||||
// sample random exponent
|
||||
let exp = Fs::rand(rng);
|
||||
|
||||
// exponentiate in montgomery, convert to edwards
|
||||
let ed_expected = Point::from_montgomery(&base.mul(exp, params), params);
|
||||
|
||||
// convert to edwards and exponentiate
|
||||
let ed_exponentiated = Point::from_montgomery(&base, params).mul(exp, params);
|
||||
|
||||
let (x, y) = ed_expected.into_xy();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
assert!(ed_exponentiated == ed_expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_back_and_forth() {
|
||||
use super::montgomery;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..200 {
|
||||
// compute base in montgomery
|
||||
let base = montgomery::Point::rand(rng, params);
|
||||
|
||||
// convert to edwards
|
||||
let base_ed = Point::from_montgomery(&base, params);
|
||||
|
||||
{
|
||||
let (x, y) = base_ed.into_xy();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
}
|
||||
|
||||
// convert back to montgomery
|
||||
let base_mont = montgomery::Point::from_edwards(&base_ed, params);
|
||||
|
||||
assert!(base == base_mont);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1240
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
//! Jubjub is an elliptic curve defined over the BLS12-381 scalar field, Fr.
|
||||
//! It is a Montgomery curve that takes the form `y^2 = x^3 + Ax^2 + x` where
|
||||
//! `A = 40962`. This is the smallest integer choice of A such that:
|
||||
//!
|
||||
//! * `(A - 2) / 4` is a small integer (`10240`).
|
||||
//! * `A^2 - 4` is quadratic residue.
|
||||
//! * The group order of the curve and its quadratic twist has a large prime factor.
|
||||
//!
|
||||
//! Jubjub has `s = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7`
|
||||
//! as the prime subgroup order, with cofactor 8. (The twist has cofactor 4.)
|
||||
//!
|
||||
//! This curve is birationally equivalent to a twisted Edwards curve of the
|
||||
//! form `-x^2 + y^2 = 1 + dx^2y^2` with `d = -(10240/10241)`. In fact, this equivalence
|
||||
//! forms a group isomorphism, so points can be freely converted between the Montgomery
|
||||
//! and twisted Edwards forms.
|
||||
|
||||
use pairing::{
|
||||
Engine,
|
||||
PrimeField
|
||||
};
|
||||
|
||||
use pairing::bls12_381::{
|
||||
Bls12,
|
||||
Fr
|
||||
};
|
||||
|
||||
mod fs;
|
||||
|
||||
pub use self::fs::{Fs, FsRepr};
|
||||
|
||||
pub mod edwards;
|
||||
pub mod montgomery;
|
||||
|
||||
/// These are the pre-computed parameters of the Jubjub
|
||||
/// curve.
|
||||
pub struct JubjubParams<E: Engine> {
|
||||
edwards_d: E::Fr,
|
||||
montgomery_a: E::Fr,
|
||||
|
||||
scale: E::Fr
|
||||
}
|
||||
|
||||
pub enum Unknown { }
|
||||
pub enum PrimeOrder { }
|
||||
|
||||
impl JubjubParams<Bls12> {
|
||||
pub fn new() -> Self {
|
||||
JubjubParams {
|
||||
// d = -(10240/10241)
|
||||
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
|
||||
// A = 40962
|
||||
montgomery_a: Fr::from_str("40962").unwrap(),
|
||||
// scaling factor = sqrt(4 / (a - d))
|
||||
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pairing::{Field, SqrtField, LegendreSymbol, PrimeField};
|
||||
use pairing::bls12_381::{Fr};
|
||||
use super::JubjubParams;
|
||||
|
||||
#[test]
|
||||
fn test_params() {
|
||||
let params = JubjubParams::new();
|
||||
|
||||
// a = -1
|
||||
let mut a = Fr::one();
|
||||
a.negate();
|
||||
|
||||
{
|
||||
// The twisted Edwards addition law is complete when d is nonsquare
|
||||
// and a is square.
|
||||
|
||||
assert!(params.edwards_d.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||
assert!(a.legendre() == LegendreSymbol::QuadraticResidue);
|
||||
}
|
||||
|
||||
// Check that A^2 - 4 is nonsquare:
|
||||
let mut tmp = params.montgomery_a;
|
||||
tmp.square();
|
||||
tmp.sub_assign(&Fr::from_str("4").unwrap());
|
||||
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||
|
||||
// Check that A - 2 is nonsquare:
|
||||
let mut tmp = params.montgomery_a;
|
||||
tmp.sub_assign(&Fr::from_str("2").unwrap());
|
||||
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
|
||||
|
||||
// Check the validity of the scaling factor
|
||||
let mut tmp = a;
|
||||
tmp.sub_assign(¶ms.edwards_d);
|
||||
tmp = tmp.inverse().unwrap();
|
||||
tmp.mul_assign(&Fr::from_str("4").unwrap());
|
||||
tmp = tmp.sqrt().unwrap();
|
||||
assert_eq!(tmp, params.scale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
SqrtField,
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
use super::{
|
||||
JubjubParams,
|
||||
Unknown,
|
||||
PrimeOrder,
|
||||
Fs,
|
||||
FsRepr,
|
||||
edwards
|
||||
};
|
||||
|
||||
use rand::{
|
||||
Rng
|
||||
};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// Represents the affine point (X/Z, Y/Z) via the extended
|
||||
// twisted Edwards coordinates.
|
||||
pub struct Point<E: Engine, Subgroup> {
|
||||
x: E::Fr,
|
||||
y: E::Fr,
|
||||
infinity: bool,
|
||||
_marker: PhantomData<Subgroup>
|
||||
}
|
||||
|
||||
fn convert_subgroup<E: Engine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
|
||||
{
|
||||
Point {
|
||||
x: from.x,
|
||||
y: from.y,
|
||||
infinity: from.infinity,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
|
||||
{
|
||||
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
|
||||
{
|
||||
convert_subgroup(&p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> Clone for Point<E, Subgroup>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
convert_subgroup(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> PartialEq for Point<E, Subgroup> {
|
||||
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
|
||||
match (self.infinity, other.infinity) {
|
||||
(true, true) => true,
|
||||
(true, false) | (false, true) => false,
|
||||
(false, false) => {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Point<E, Unknown> {
|
||||
/// This guarantees the point is in the prime order subgroup
|
||||
pub fn mul_by_cofactor(&self, params: &JubjubParams<E>) -> Point<E, PrimeOrder>
|
||||
{
|
||||
let tmp = self.double(params)
|
||||
.double(params)
|
||||
.double(params);
|
||||
|
||||
convert_subgroup(&tmp)
|
||||
}
|
||||
|
||||
pub fn rand<R: Rng>(rng: &mut R, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
loop {
|
||||
// given an x on the curve, y^2 = x^3 + A*x^2 + x
|
||||
let x: E::Fr = rng.gen();
|
||||
|
||||
let mut x2 = x;
|
||||
x2.square();
|
||||
|
||||
let mut rhs = x2;
|
||||
rhs.mul_assign(¶ms.montgomery_a);
|
||||
rhs.add_assign(&x);
|
||||
x2.mul_assign(&x);
|
||||
rhs.add_assign(&x2);
|
||||
|
||||
match rhs.sqrt() {
|
||||
Some(mut y) => {
|
||||
if y.into_repr().is_odd() != rng.gen() {
|
||||
y.negate();
|
||||
}
|
||||
|
||||
return Point {
|
||||
x: x,
|
||||
y: y,
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Subgroup> Point<E, Subgroup> {
|
||||
/// Convert from an Edwards point
|
||||
pub fn from_edwards(
|
||||
e: &edwards::Point<E, Subgroup>,
|
||||
params: &JubjubParams<E>
|
||||
) -> Self
|
||||
{
|
||||
let (x, y) = e.into_xy();
|
||||
|
||||
if y == E::Fr::one() {
|
||||
// The only solution for y = 1 is x = 0. (0, 1) is
|
||||
// the neutral element, so we map this to the point
|
||||
// at infinity.
|
||||
|
||||
Point::zero()
|
||||
} else {
|
||||
// The map from a twisted Edwards curve is defined as
|
||||
// (x, y) -> (u, v) where
|
||||
// u = (1 + y) / (1 - y)
|
||||
// v = u / x
|
||||
//
|
||||
// This mapping is not defined for y = 1 and for x = 0.
|
||||
//
|
||||
// We have that y != 1 above. If x = 0, the only
|
||||
// solutions for y are 1 (contradiction) or -1.
|
||||
if x.is_zero() {
|
||||
// (0, -1) is the point of order two which is not
|
||||
// the neutral element, so we map it to (0, 0) which is
|
||||
// the only affine point of order 2.
|
||||
|
||||
Point {
|
||||
x: E::Fr::zero(),
|
||||
y: E::Fr::zero(),
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
}
|
||||
} else {
|
||||
// The mapping is defined as above.
|
||||
//
|
||||
// (x, y) -> (u, v) where
|
||||
// u = (1 + y) / (1 - y)
|
||||
// v = u / x
|
||||
|
||||
let mut u = E::Fr::one();
|
||||
u.add_assign(&y);
|
||||
{
|
||||
let mut tmp = E::Fr::one();
|
||||
tmp.sub_assign(&y);
|
||||
u.mul_assign(&tmp.inverse().unwrap())
|
||||
}
|
||||
|
||||
let mut v = u;
|
||||
v.mul_assign(&x.inverse().unwrap());
|
||||
|
||||
// Scale it into the correct curve constants
|
||||
v.mul_assign(¶ms.scale);
|
||||
|
||||
Point {
|
||||
x: u,
|
||||
y: v,
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to cast this as a prime order element, failing if it's
|
||||
/// not in the prime order subgroup.
|
||||
pub fn as_prime_order(&self, params: &JubjubParams<E>) -> Option<Point<E, PrimeOrder>> {
|
||||
if self.mul(Fs::char(), params) == Point::zero() {
|
||||
Some(convert_subgroup(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Point {
|
||||
x: E::Fr::zero(),
|
||||
y: E::Fr::zero(),
|
||||
infinity: true,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_xy(&self) -> Option<(E::Fr, E::Fr)>
|
||||
{
|
||||
if self.infinity {
|
||||
None
|
||||
} else {
|
||||
Some((self.x, self.y))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Self {
|
||||
let mut p = self.clone();
|
||||
|
||||
p.y.negate();
|
||||
|
||||
p
|
||||
}
|
||||
|
||||
pub fn double(&self, params: &JubjubParams<E>) -> Self {
|
||||
if self.infinity {
|
||||
return Point::zero();
|
||||
}
|
||||
|
||||
if self.y == E::Fr::zero() {
|
||||
return Point::zero();
|
||||
}
|
||||
|
||||
let mut delta = E::Fr::one();
|
||||
{
|
||||
let mut tmp = params.montgomery_a;
|
||||
tmp.mul_assign(&self.x);
|
||||
tmp.double();
|
||||
delta.add_assign(&tmp);
|
||||
}
|
||||
{
|
||||
let mut tmp = self.x;
|
||||
tmp.square();
|
||||
delta.add_assign(&tmp);
|
||||
tmp.double();
|
||||
delta.add_assign(&tmp);
|
||||
}
|
||||
{
|
||||
let mut tmp = self.y;
|
||||
tmp.double();
|
||||
delta.mul_assign(&tmp.inverse().expect("y is nonzero so this must be nonzero"));
|
||||
}
|
||||
|
||||
let mut x3 = delta;
|
||||
x3.square();
|
||||
x3.sub_assign(¶ms.montgomery_a);
|
||||
x3.sub_assign(&self.x);
|
||||
x3.sub_assign(&self.x);
|
||||
|
||||
let mut y3 = x3;
|
||||
y3.sub_assign(&self.x);
|
||||
y3.mul_assign(&delta);
|
||||
y3.add_assign(&self.y);
|
||||
y3.negate();
|
||||
|
||||
Point {
|
||||
x: x3,
|
||||
y: y3,
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, other: &Self, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
match (self.infinity, other.infinity) {
|
||||
(true, true) => Point::zero(),
|
||||
(true, false) => other.clone(),
|
||||
(false, true) => self.clone(),
|
||||
(false, false) => {
|
||||
if self.x == other.x {
|
||||
if self.y == other.y {
|
||||
self.double(params)
|
||||
} else {
|
||||
Point::zero()
|
||||
}
|
||||
} else {
|
||||
let mut delta = other.y;
|
||||
delta.sub_assign(&self.y);
|
||||
{
|
||||
let mut tmp = other.x;
|
||||
tmp.sub_assign(&self.x);
|
||||
delta.mul_assign(&tmp.inverse().expect("self.x != other.x, so this must be nonzero"));
|
||||
}
|
||||
|
||||
let mut x3 = delta;
|
||||
x3.square();
|
||||
x3.sub_assign(¶ms.montgomery_a);
|
||||
x3.sub_assign(&self.x);
|
||||
x3.sub_assign(&other.x);
|
||||
|
||||
let mut y3 = x3;
|
||||
y3.sub_assign(&self.x);
|
||||
y3.mul_assign(&delta);
|
||||
y3.add_assign(&self.y);
|
||||
y3.negate();
|
||||
|
||||
Point {
|
||||
x: x3,
|
||||
y: y3,
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul<S: Into<FsRepr>>(&self, scalar: S, params: &JubjubParams<E>) -> Self
|
||||
{
|
||||
let mut res = Self::zero();
|
||||
|
||||
for b in BitIterator::new(scalar.into()) {
|
||||
res = res.double(params);
|
||||
|
||||
if b {
|
||||
res = res.add(self, params);
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{XorShiftRng, SeedableRng, Rand};
|
||||
use super::{JubjubParams, Point, PrimeOrder, Unknown, Fs};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Engine, Field, PrimeField};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
fn is_on_curve<E: Engine>(
|
||||
x: E::Fr,
|
||||
y: E::Fr,
|
||||
params: &JubjubParams<E>
|
||||
) -> bool
|
||||
{
|
||||
let mut lhs = y;
|
||||
lhs.square();
|
||||
|
||||
let mut x2 = x;
|
||||
x2.square();
|
||||
|
||||
let mut x3 = x2;
|
||||
x3.mul_assign(&x);
|
||||
|
||||
let mut rhs = x2;
|
||||
rhs.mul_assign(¶ms.montgomery_a);
|
||||
rhs.add_assign(&x);
|
||||
rhs.add_assign(&x3);
|
||||
|
||||
lhs == rhs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rand() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
let (x, y) = Point::rand(&mut rng, ¶ms).into_xy().unwrap();
|
||||
|
||||
assert!(is_on_curve(x, y, ¶ms));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identities() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
let z = Point::<Bls12, PrimeOrder>::zero();
|
||||
assert!(z.double(¶ms) == z);
|
||||
assert!(z.negate() == z);
|
||||
|
||||
for _ in 0..100 {
|
||||
let r = Point::rand(&mut rng, ¶ms);
|
||||
|
||||
assert!(r.add(&Point::zero(), ¶ms) == r);
|
||||
assert!(r.add(&r.negate(), ¶ms) == Point::zero());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_associativity() {
|
||||
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = JubjubParams::new();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let a = Point::rand(&mut rng, ¶ms);
|
||||
let b = Point::rand(&mut rng, ¶ms);
|
||||
let c = Point::rand(&mut rng, ¶ms);
|
||||
|
||||
assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_order() {
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
// The neutral element is in the prime order subgroup.
|
||||
assert!(Point::<Bls12, PrimeOrder>::zero().as_prime_order(params).is_some());
|
||||
|
||||
for _ in 0..50 {
|
||||
// Pick a random point and multiply it by the cofactor
|
||||
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||
|
||||
// Any point multiplied by the cofactor will be in the prime
|
||||
// order subgroup
|
||||
assert!(base.as_prime_order(params).is_some());
|
||||
}
|
||||
|
||||
// It's very likely that at least one out of 50 random points on the curve
|
||||
// is not in the prime order subgroup.
|
||||
let mut at_least_one_not_in_prime_order_subgroup = false;
|
||||
for _ in 0..50 {
|
||||
// Pick a random point.
|
||||
let base = Point::rand(rng, params);
|
||||
|
||||
at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none();
|
||||
}
|
||||
assert!(at_least_one_not_in_prime_order_subgroup);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul_associativity() {
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
// Pick a random point and multiply it by the cofactor
|
||||
let base = Point::rand(rng, params).mul_by_cofactor(params);
|
||||
|
||||
let mut a = Fs::rand(rng);
|
||||
let b = Fs::rand(rng);
|
||||
let c = Fs::rand(rng);
|
||||
|
||||
let res1 = base.mul(a, params).mul(b, params).mul(c, params);
|
||||
let res2 = base.mul(b, params).mul(c, params).mul(a, params);
|
||||
let res3 = base.mul(c, params).mul(a, params).mul(b, params);
|
||||
a.mul_assign(&b);
|
||||
a.mul_assign(&c);
|
||||
let res4 = base.mul(a, params);
|
||||
|
||||
assert!(res1 == res2);
|
||||
assert!(res2 == res3);
|
||||
assert!(res3 == res4);
|
||||
|
||||
let (x, y) = res1.into_xy().unwrap();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
let (x, y) = res2.into_xy().unwrap();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
let (x, y) = res3.into_xy().unwrap();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edwards_conversion() {
|
||||
use super::edwards;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
// compute base in edwards
|
||||
let base = edwards::Point::rand(rng, params);
|
||||
|
||||
// sample random exponent
|
||||
let exp = Fs::rand(rng);
|
||||
|
||||
// exponentiate in edwards
|
||||
let mont_expected = Point::from_edwards(&base.mul(exp, params), params);
|
||||
|
||||
// convert to montgomery and exponentiate
|
||||
let mont_exp = Point::from_edwards(&base, params).mul(exp, params);
|
||||
|
||||
assert!(mont_exp == mont_expected);
|
||||
|
||||
let (x, y) = mont_expected.into_xy().unwrap();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_back_and_forth() {
|
||||
use super::edwards;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
// compute base in edwards
|
||||
let base = edwards::Point::rand(rng, params);
|
||||
|
||||
// convert to montgomery
|
||||
let base_mont = Point::from_edwards(&base, params);
|
||||
|
||||
{
|
||||
let (x, y) = base_mont.into_xy().unwrap();
|
||||
assert!(is_on_curve(x, y, params));
|
||||
}
|
||||
|
||||
// convert back to edwards
|
||||
let base_ed = edwards::Point::from_montgomery(&base_mont, params);
|
||||
|
||||
assert!(base == base_ed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_awkward_points() {
|
||||
use super::edwards;
|
||||
|
||||
//let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
let params = &JubjubParams::new();
|
||||
|
||||
let mut awkward_points: Vec<Point<Bls12, Unknown>> = vec![];
|
||||
|
||||
{
|
||||
let mut push_point = |x, y| {
|
||||
let x = Fr::from_str(x).unwrap();
|
||||
let y = Fr::from_str(y).unwrap();
|
||||
|
||||
assert!(is_on_curve(x, y, params));
|
||||
|
||||
awkward_points.push(Point {
|
||||
x: x,
|
||||
y: y,
|
||||
infinity: false,
|
||||
_marker: PhantomData
|
||||
});
|
||||
};
|
||||
|
||||
// p is a point of order 8
|
||||
|
||||
// push p
|
||||
push_point(
|
||||
"26700795483254565448379661158233243896148151268643422869645920428793919977699",
|
||||
"38240351061652197568958466618399906060451208175623222883988435386266133962140"
|
||||
);
|
||||
|
||||
// push 2p
|
||||
push_point(
|
||||
"1",
|
||||
"40876724960280933289965479552128619538703197557433544801868355907127087029496"
|
||||
);
|
||||
|
||||
// push 3p
|
||||
push_point(
|
||||
"48853380121562139410032601262067414539517111118072400994428343856767649516850",
|
||||
"32041076745907035847439769934443325418710075447471957144325987857573529479623"
|
||||
);
|
||||
|
||||
// push 4p
|
||||
push_point(
|
||||
"0",
|
||||
"0"
|
||||
);
|
||||
|
||||
// push 5p
|
||||
push_point(
|
||||
"48853380121562139410032601262067414539517111118072400994428343856767649516850",
|
||||
"20394798429219154632007970573742640418980477053055680678277670842365051704890"
|
||||
);
|
||||
|
||||
// push 6p
|
||||
push_point(
|
||||
"1",
|
||||
"11559150214845257189482260956057346298987354943094093020735302792811494155017"
|
||||
);
|
||||
|
||||
// push 7p
|
||||
push_point(
|
||||
"26700795483254565448379661158233243896148151268643422869645920428793919977699",
|
||||
"14195524113473992910489273889786059777239344324904414938615223313672447222373"
|
||||
);
|
||||
}
|
||||
|
||||
// push 8p (point at infinity)
|
||||
awkward_points.push(Point::zero());
|
||||
|
||||
for point in &awkward_points {
|
||||
let ed = edwards::Point::from_montgomery(point, params);
|
||||
let mut ed_tmp = ed.clone();
|
||||
let mut mont_tmp = point.clone();
|
||||
for _ in 0..8 {
|
||||
let mont_again = Point::from_edwards(&ed_tmp, params);
|
||||
assert!(mont_again == mont_tmp);
|
||||
|
||||
let ed_again = edwards::Point::from_montgomery(&mont_tmp, params);
|
||||
assert!(ed_again == ed_tmp);
|
||||
|
||||
ed_tmp = ed_tmp.add(&ed, params);
|
||||
mont_tmp = mont_tmp.add(point, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user