mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-05 20:30:57 +00:00
Merge remote-tracking branch 'hdevalence/feature/ristretto_r2' into develop
This commit is contained in:
commit
b1386b591a
13 changed files with 1918 additions and 934 deletions
|
|
@ -51,6 +51,7 @@ nightly = ["radix_51", "subtle/nightly"]
|
|||
default = ["std"]
|
||||
std = ["rand", "subtle/std"]
|
||||
alloc = []
|
||||
# This isn't used at the moment, but keep it around for future yolocrypto features.
|
||||
yolocrypto = []
|
||||
bench = []
|
||||
# Radix-51 arithmetic using u128
|
||||
|
|
|
|||
3
Makefile
Normal file
3
Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
doc:
|
||||
cargo rustdoc --features "nightly yolocrypto" -- --html-in-header katex-header.html
|
||||
10
katex-header.html
Normal file
10
katex-header.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/katex.min.css" integrity="sha384-B41nY7vEWuDrE9Mr+J2nBL0Liu+nl/rBXTdpQal730oTHdlrlXHzYMOhDU60cwde" crossorigin="anonymous">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/katex.min.js" integrity="sha384-L9gv4ooDLrYwW0QCM6zY3EKSSPrsuUncpx26+erN0pJX4wv1B1FzVW1SvpcJPx/8" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/contrib/auto-render.min.js" integrity="sha384-RkgGHBDdR8eyBOoWeZ/vpGg1cOvSAJRflCUDACusAAIVwkwPrOUYykglPeqWakZu" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() { renderMathInElement(document.body); });
|
||||
</script>
|
||||
<style>
|
||||
.katex { font-size: 1em !important; }
|
||||
.docblock code, .dockblock-short code { font-size: 0.85em !important; }
|
||||
</style>
|
||||
|
|
@ -19,8 +19,7 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use edwards::CompressedEdwardsY;
|
||||
#[cfg(feature = "yolocrypto")]
|
||||
use decaf::{DecafPoint, DecafBasepointTable};
|
||||
use ristretto::{RistrettoPoint, RistrettoBasepointTable};
|
||||
use montgomery::CompressedMontgomeryU;
|
||||
use scalar::Scalar;
|
||||
|
||||
|
|
@ -61,10 +60,9 @@ pub const BASE_COMPRESSED_MONTGOMERY: CompressedMontgomeryU =
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
|
||||
|
||||
/// The Ed25519 basepoint, as a `DecafPoint`. This is called `_POINT` to distinguish it from
|
||||
/// The Ed25519 basepoint, as a `RistrettoPoint`. This is called `_POINT` to distinguish it from
|
||||
/// `_TABLE`, which provides fast scalar multiplication.
|
||||
#[cfg(feature = "yolocrypto")] pub const DECAF_ED25519_BASEPOINT_POINT: DecafPoint =
|
||||
DecafPoint(ED25519_BASEPOINT_POINT);
|
||||
pub const RISTRETTO_BASEPOINT_POINT: RistrettoPoint = RistrettoPoint(ED25519_BASEPOINT_POINT);
|
||||
|
||||
/// `l` is the order of base point, i.e. 2^252 +
|
||||
/// 27742317777372353535851937790883648493, in little-endian form
|
||||
|
|
@ -87,10 +85,9 @@ pub const l_minus_2: Scalar = Scalar([ 0xeb, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12,
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 ]);
|
||||
|
||||
#[cfg(feature = "yolocrypto")]
|
||||
/// The Ed25519 basepoint
|
||||
pub const DECAF_ED25519_BASEPOINT_TABLE: DecafBasepointTable
|
||||
= DecafBasepointTable(ED25519_BASEPOINT_TABLE);
|
||||
/// The Ed25519 basepoint, as a RistrettoPoint
|
||||
pub const RISTRETTO_BASEPOINT_TABLE: RistrettoBasepointTable
|
||||
= RistrettoBasepointTable(ED25519_BASEPOINT_TABLE);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
|
@ -215,6 +212,14 @@ mod test {
|
|||
assert_eq!(&constants::d * &four, constants::d4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqrt_ad_minus_one() {
|
||||
let a = FieldElement::minus_one();
|
||||
let ad_minus_one = &(&a * &constants::d) + &a;
|
||||
let should_be_ad_minus_one = constants::sqrt_ad_minus_one.square();
|
||||
assert_eq!(should_be_ad_minus_one, ad_minus_one);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_minus_d() {
|
||||
let a = FieldElement::minus_one();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ pub const a_minus_d: FieldElement32 = FieldElement32([
|
|||
10913609, -13857413, 15372611, -6949391, -114729,
|
||||
8787816, 6275908, 3247719, 18696448, 12055116, ]);
|
||||
|
||||
pub const sqrt_ad_minus_one: FieldElement32 = FieldElement32([
|
||||
24849947, -153582, -23613485, 6347715, -21072328, -667138, -25271143, -15367704, -870347, 14525639
|
||||
]);
|
||||
|
||||
pub const invsqrt_a_minus_d: FieldElement32 = FieldElement32([
|
||||
6111485, 4156064, -27798727, 12243468, -25904040,
|
||||
120897, 20826367, -7060776, 6093568, -1986012
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ pub const d4: FieldElement64 = FieldElement64([1468021120295602, 186546288051685
|
|||
|
||||
pub const a_minus_d: FieldElement64 = FieldElement64([1321844580190025, 1785434093556034, 589740348686294, 217950738957124, 809005158844672]);
|
||||
|
||||
pub const sqrt_ad_minus_one: FieldElement64 = FieldElement64([
|
||||
2241493124984347, 425987919032274, 2207028919301688, 1220490630685848, 974799131293748
|
||||
]);
|
||||
|
||||
pub const invsqrt_a_minus_d: FieldElement64 = FieldElement64([
|
||||
278908739862762, 821645201101625, 8113234426968, 1777959178193151, 2118520810568447
|
||||
]);
|
||||
|
|
|
|||
871
src/decaf.rs
871
src/decaf.rs
|
|
@ -1,871 +0,0 @@
|
|||
// -*- mode: rust; -*-
|
||||
//
|
||||
// This file is part of curve25519-dalek.
|
||||
// Copyright (c) 2016-2017 Isis Lovecruft, Henry de Valence
|
||||
// See LICENSE for licensing information.
|
||||
//
|
||||
// Authors:
|
||||
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
|
||||
//! An implementation of Mike Hamburg's Decaf cofactor-eliminating
|
||||
//! point-compression scheme, providing a prime-order group on top of
|
||||
//! a non-prime-order elliptic curve.
|
||||
//!
|
||||
//! Note: this code is currently feature-gated with the `yolocrypto`
|
||||
//! feature flag, because our implementation is still unfinished.
|
||||
|
||||
// We allow non snake_case names because coordinates in projective space are
|
||||
// traditionally denoted by the capitalisation of their respective
|
||||
// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my
|
||||
// affine and projective cakes and eat both of them too.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use rand::Rng;
|
||||
|
||||
use digest::Digest;
|
||||
use generic_array::typenum::U32;
|
||||
|
||||
use constants;
|
||||
use field::FieldElement;
|
||||
|
||||
use core::ops::{Add, Sub, Neg};
|
||||
use core::ops::{AddAssign, SubAssign};
|
||||
use core::ops::{Mul, MulAssign};
|
||||
|
||||
use edwards;
|
||||
use edwards::ExtendedPoint;
|
||||
use edwards::CompletedPoint;
|
||||
use edwards::EdwardsBasepointTable;
|
||||
use edwards::Identity;
|
||||
use scalar::Scalar;
|
||||
|
||||
use subtle::ConditionallyAssignable;
|
||||
use subtle::ConditionallyNegatable;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Compressed points
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// A point serialized using Mike Hamburg's Decaf scheme.
|
||||
///
|
||||
/// XXX think about how this API should work
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct CompressedDecaf(pub [u8; 32]);
|
||||
|
||||
/// The result of compressing a `DecafPoint`.
|
||||
impl CompressedDecaf {
|
||||
/// Convert this `CompressedDecaf` to an array of bytes.
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// View this `CompressedDecaf` as an array of bytes.
|
||||
pub fn as_bytes<'a>(&'a self) -> &'a [u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Attempt to decompress to an `DecafPoint`.
|
||||
pub fn decompress(&self) -> Option<DecafPoint> {
|
||||
// XXX should decoding be CT ?
|
||||
// XXX need to check that xy is nonnegative and reject otherwise
|
||||
let s = FieldElement::from_bytes(self.as_bytes());
|
||||
|
||||
// Check that s = |s| and reject otherwise.
|
||||
let mut abs_s = s;
|
||||
let neg = abs_s.is_negative_decaf();
|
||||
abs_s.conditional_negate(neg);
|
||||
if abs_s != s { return None; }
|
||||
|
||||
let ss = s.square();
|
||||
let X = &s + &s; // X = 2s
|
||||
let Z = &FieldElement::one() - &ss; // Z = 1+as^2
|
||||
let ZZ = Z.square();
|
||||
let u = &ZZ- &(&constants::d4 * &ss); // u = Z^2 - 4ds^2
|
||||
let uss = &u * &ss;
|
||||
let ussZZ = &uss * &ZZ;
|
||||
|
||||
if Z.is_zero() == 1u8 { return None; }
|
||||
|
||||
// Batch inversion: set b = 1/sqrt(us^2 Z^2)
|
||||
let (ussZZ_is_nonzero_square, b) = ussZZ.invsqrt();
|
||||
if (ussZZ_is_nonzero_square | uss.is_zero()) == 0u8 {
|
||||
return None; // us^2 is nonzero nonsquare
|
||||
}
|
||||
|
||||
let mut v = &b * &Z; // now v = 1/sqrt(us^2)
|
||||
let Zinv = &b * &(&v * &uss); // now Zinv = b^2 Z us^2 = 1/Z
|
||||
|
||||
// Now v = 1/sqrt(us^2) if us^2 is a nonzero square, 0 if us^2 is zero.
|
||||
let uv = &v * &u;
|
||||
if uv.is_negative_decaf() == 1u8 {
|
||||
v.negate();
|
||||
}
|
||||
let mut two_minus_Z = -&Z; two_minus_Z.0[0] += 2;
|
||||
let mut w = &v * &(&s * &two_minus_Z);
|
||||
w.conditional_assign(&FieldElement::one(), s.is_zero());
|
||||
let Y = &w * &Z;
|
||||
let T = &w * &X;
|
||||
|
||||
// "To decode the point, one must decode it to affine form
|
||||
// instead of projective, and check that xy is non-negative."
|
||||
|
||||
// Use the value of 1/Z previously computed in the batch inversion
|
||||
let xy = &T * &Zinv;
|
||||
if (Y.is_nonzero() & xy.is_nonnegative_decaf()) == 1u8 {
|
||||
Some(DecafPoint(ExtendedPoint{ X: X, Y: Y, Z: Z, T: T }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Identity for CompressedDecaf {
|
||||
fn identity() -> CompressedDecaf {
|
||||
CompressedDecaf([0u8; 32])
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Serde support
|
||||
// ------------------------------------------------------------------------
|
||||
// Serializes to and from `DecafPoint` directly, doing compression
|
||||
// and decompression internally. This means that users can create
|
||||
// structs containing `DecafPoint`s and use Serde's derived
|
||||
// serializers to serialize those structures.
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{self, Serialize, Deserialize, Serializer, Deserializer};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::de::Visitor;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for DecafPoint {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
serializer.serialize_bytes(self.compress().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> Deserialize<'de> for DecafPoint {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
struct DecafPointVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DecafPointVisitor {
|
||||
type Value = DecafPoint;
|
||||
|
||||
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
formatter.write_str("a valid point in Decaf format")
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<DecafPoint, E>
|
||||
where E: serde::de::Error
|
||||
{
|
||||
if v.len() == 32 {
|
||||
let arr32 = array_ref!(v, 0, 32); // &[u8;32] from &[u8]
|
||||
CompressedDecaf(*arr32)
|
||||
.decompress()
|
||||
.ok_or(serde::de::Error::custom("decompression failed"))
|
||||
} else {
|
||||
Err(serde::de::Error::invalid_length(v.len(), &self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(DecafPointVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Internal point representations
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// A point in a prime-order group.
|
||||
///
|
||||
// XXX think about how this API should work
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DecafPoint(pub ExtendedPoint);
|
||||
|
||||
impl DecafPoint {
|
||||
/// Compress in Decaf format.
|
||||
pub fn compress(&self) -> CompressedDecaf {
|
||||
// Q: Do we want to encode twisted or untwisted?
|
||||
//
|
||||
// Notes:
|
||||
// Recall that the twisted Edwards curve E_{a,d} is of the form
|
||||
//
|
||||
// ax^2 + y^2 = 1 + dx^2y^2.
|
||||
//
|
||||
// Internally, we operate on the curve with a = -1, d =
|
||||
// -121665/121666, a.k.a., the twist. But maybe we would like
|
||||
// to use Decaf on the untwisted curve with a = 1, d =
|
||||
// 121665/121666. (why? interop?)
|
||||
//
|
||||
// Fix i, a square root of -1 (mod p).
|
||||
//
|
||||
// The map x -> ix is an isomorphism from E_{a,d} to E_{-a,-d}.
|
||||
// Its inverse is x -> -ix.
|
||||
// let untwisted_X = &self.X * &constants::MSQRT_M1;
|
||||
// etc.
|
||||
//
|
||||
// Step 0: pre-rotation, needed for Decaf with E[8] = Z/8.
|
||||
//
|
||||
// We want to select a point (x,y) in the coset P + E[4] with
|
||||
// y nonzero and xy nonnegative. The naive approach is as
|
||||
// follows. First, compute xy = T/Z and check that Y is
|
||||
// nonzero and xy is nonnegative. If not, then "rotate" the
|
||||
// original point by adding (x,y) + (i,0) = (iy,ix) = (x',y'):
|
||||
// this rotated point has x'y' = -xy. Then perform the normal
|
||||
// Decaf encoding, as described in Appendix A.1 of the Decaf
|
||||
// paper, using the rotated point (x',y').
|
||||
//
|
||||
// This is straightforward but requires an extra inversion.
|
||||
// We would like to batch the inversion in xy = T/Z with the
|
||||
// inverse square root in the computation of
|
||||
//
|
||||
// r = invsqrt((a-d)*(Z+Y)*(Z-Y))
|
||||
// = invsqrt(a-d)*invsqrt(Z^2-Y^2),
|
||||
//
|
||||
// but the X and Y we are trying to decode depend on whether
|
||||
// we rotated the coset representative!
|
||||
//
|
||||
// However, it is possible to batch these inversions. Credit:
|
||||
// the following explanation (and trick) is adapted from an
|
||||
// email from Mike Hamburg, but of course any errors are ours.
|
||||
//
|
||||
// Let the initial point be ( X_0 : Y_0 : Z_0 : T_0).
|
||||
// The rotated point is then (iY_0 : iX_0 : Z_0 : -T_0).
|
||||
//
|
||||
// We want to relate the computation of:
|
||||
//
|
||||
// invsqrt(Z^2 - Y^2) = invsqrt(Z_0^2 - Y_0^2) [non-rotated]
|
||||
// invsqrt(Z^2 - Y^2) = invsqrt(Z_0^2 + X_0^2) [rotated]
|
||||
//
|
||||
// The curve equation in extended coordinates is
|
||||
//
|
||||
// 0 = (-X^2 + Y^2)*Z^2 - Z^4 - d*X^2*Y^2,
|
||||
//
|
||||
// so
|
||||
// 0 = (-X^2 + Y^2)*Z^2 - Z^4 - d*T^2*Z^2 since XY=TZ
|
||||
// = (-X^2 + Y^2 - Z^2 - d*T^2)*Z^2
|
||||
// = ( X^2 - Y^2 + Z^2 + d*T^2)*Z^2 mult by -1
|
||||
// -T^2*Z^2 = (X^2 - Y^2 + Z^2 + d*T^2)*Z^2 - T^2*Z^2 sub T^2*Z^2
|
||||
// -T^2*Z^2 = (X^2 - Y^2 + Z^2 - T^2)*Z^2 + d*T^2*Z^2
|
||||
// (-1-d)*T^2*Z^2 = (X^2 - Y^2 + Z^2 - T^2)*Z^2
|
||||
//
|
||||
// for any point (X:Y:Z:T) in extended coordinates. Therefore,
|
||||
//
|
||||
// (Z^2 - Y^2)*(Z^2 + X^2) = Z^4 + Z^2*X^2 - Y^2*Z^2 - Y^2*X^2
|
||||
// = Z^4 + Z^2*X^2 - Y^2*Z^2 - T^2*Z^2 since XY=TZ
|
||||
// = Z^2*(X^2 - Y^2 + Z^2 - T^2)
|
||||
// = (-1-d)*T^2*Z^2.
|
||||
//
|
||||
// Taking square roots of both sides and rearranging, we get
|
||||
//
|
||||
// invsqrt(Z^2 - Y^2) = invsqrt(-1-d)*(Z^2+X^2)*(1/TZ)*invsqrt(Z^2+X^2)
|
||||
// `-----------' `---------------------'
|
||||
// curve constant batchable
|
||||
//
|
||||
// for any point (X:Y:Z:T) in extended coordinates.
|
||||
//
|
||||
// Therefore, we can do the computation with only one inverse
|
||||
// square root like so:
|
||||
//
|
||||
// W <--- invsqrt((T_0 * Z_0)^2 * (Z_0^2+X_0^2))
|
||||
// = 1/(T_0 * Z_0 * sqrt(Z_0^2 + X_0^2))
|
||||
//
|
||||
// xy <--- T_0^2 * W^2 * (T_0 * Z_0) * (Z_0^2 + X_0^2)
|
||||
// = T_0 / Z_0 = xy
|
||||
//
|
||||
// if Y_0 nonzero and xy nonnegative:
|
||||
// (X : Y : Z : T) <--- (X_0 : Y_0 : Z_0 : T_0)
|
||||
// r <--- (1/(-1-d)) * (Z_0^2 + X_0^2) * W
|
||||
// = invsqrt(a-d) * invsqrt(Z_0^2 - Y_0^2) since a = -1
|
||||
// = invsqrt(a-d) * invsqrt(Z^2 - Y^2)
|
||||
// otherwise:
|
||||
// (X : Y : Z : T) <--- (i*Y_0 : i*X_0 : Z_0 : -T_0)
|
||||
// r <--- invsqrt(a-d) * (T_0 * Z_0) * W
|
||||
// = invsqrt(a-d) * invsqrt(Z_0^2 + X_0^2)
|
||||
// = invsqrt(a-d) * invsqrt(Z^2 - Y^2)
|
||||
//
|
||||
// The rest of the compression follows the steps in the
|
||||
// appendix of the Decaf paper.
|
||||
|
||||
let mut X = self.0.X;
|
||||
let mut Y = self.0.Y;
|
||||
let mut T = self.0.T;
|
||||
let Z = &self.0.Z;
|
||||
|
||||
let TZ = &T * Z;
|
||||
let ZZ_plus_XX = &Z.square() + &X.square();
|
||||
let tmp = &TZ.square() * &ZZ_plus_XX;
|
||||
let (tmp_is_nonzero_square, W) = tmp.invsqrt();
|
||||
// tmp should always be a square (why? related to being in the
|
||||
// image of the isogeny?)
|
||||
debug_assert_eq!(tmp_is_nonzero_square | tmp.is_zero(), 1u8);
|
||||
|
||||
let xy = &T.square() * &(&W.square() * &(&TZ * &ZZ_plus_XX));
|
||||
let rotate = 1u8 & !(Y.is_nonzero() & xy.is_nonnegative_decaf());
|
||||
|
||||
let mut r = &W * &(&ZZ_plus_XX * &constants::inv_a_minus_d);
|
||||
let r_rot = &W * &(&TZ * &constants::invsqrt_a_minus_d);
|
||||
|
||||
let iX = &X * &constants::SQRT_M1;
|
||||
let iY = &Y * &constants::SQRT_M1;
|
||||
|
||||
r.conditional_assign(&r_rot, rotate);
|
||||
X.conditional_assign(&iY, rotate);
|
||||
Y.conditional_assign(&iX, rotate);
|
||||
T.conditional_negate(rotate);
|
||||
|
||||
// Step 2: Compute u = (a-d)r
|
||||
let u = &constants::a_minus_d * &r;
|
||||
|
||||
// Step 3: Negate r if -2uZ is negative.
|
||||
let uZ = &u * Z;
|
||||
let m2uZ = -&(&uZ + &uZ);
|
||||
r.conditional_negate(m2uZ.is_negative_decaf());
|
||||
|
||||
// Step 4: Compute s = | u(r(aZX - dYT)+Y)/a|
|
||||
// = |u(r(-ZX - dYT)+Y)| since a = -1
|
||||
let minus_ZX = -&(Z * &X);
|
||||
let dYT = &constants::d * &(&Y * &T);
|
||||
// Compute s = u(r(aZX - dYT)+Y) and cnegate for abs
|
||||
let mut s = &u * &(&(&r * &(&minus_ZX - &dYT)) + &Y);
|
||||
let neg = s.is_negative_decaf();
|
||||
s.conditional_negate(neg);
|
||||
CompressedDecaf(s.to_bytes())
|
||||
}
|
||||
|
||||
/// Return the coset self + E[4], for debugging.
|
||||
fn coset4(&self) -> [ExtendedPoint; 4] {
|
||||
[ self.0
|
||||
, &self.0 + &constants::EIGHT_TORSION[2]
|
||||
, &self.0 + &constants::EIGHT_TORSION[4]
|
||||
, &self.0 + &constants::EIGHT_TORSION[6]
|
||||
]
|
||||
}
|
||||
|
||||
/// Computes the Elligator map as described in the Decaf paper.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This method is not public because it's just used for hashing
|
||||
/// to a point -- proper elligator support is deferred for now.
|
||||
pub fn elligator_decaf_flavour(r_0: &FieldElement) -> DecafPoint {
|
||||
// Follows Appendix C of the Decaf paper.
|
||||
// Use n = 2 as the quadratic nonresidue so that n*x = x + x.
|
||||
let minus_one = -&FieldElement::one();
|
||||
|
||||
// 1. Compute r <--- nr_0^2.
|
||||
let r_0_squared = r_0.square();
|
||||
let r = &r_0_squared + &r_0_squared;
|
||||
|
||||
// 2. Compute D <--- (dr + (a-d)) * (dr - (d + ar))
|
||||
let dr = &constants::d * &r;
|
||||
// D = (dr + (a-d)) * (dr - (d + ar))
|
||||
// = (dr + (a-d)) * (dr - (d-r)) since a=-1
|
||||
// writing as
|
||||
// = (dr + (a-d)) * dr - (dr + (a-d)) * (d - r)
|
||||
// avoids two consecutive additions (could cause overflow)
|
||||
let dr_plus_amd = &dr + &constants::a_minus_d;
|
||||
let D = &(&dr_plus_amd * &dr) - &(&dr_plus_amd * &(&constants::d - &r));
|
||||
|
||||
// 3. Compute N <--- (r+1) * (a-2d)
|
||||
let N = &(&r + &FieldElement::one()) * &(&minus_one - &constants::d2);
|
||||
|
||||
// 4. Compute
|
||||
// / +1, 1 / sqrt(ND) if ND is square
|
||||
// c, e <--- | +1, 0 if N or D = 0
|
||||
// \ -1, nr_0 / sqrt(nND) otherwise
|
||||
let ND = &N * &D;
|
||||
let nND = &ND + &ND;
|
||||
let mut c = FieldElement::one();
|
||||
let mut e = FieldElement::zero();
|
||||
let (ND_is_nonzero_square, ND_invsqrt) = ND.invsqrt();
|
||||
e.conditional_assign(&ND_invsqrt, ND_is_nonzero_square);
|
||||
let (nND_is_nonzero_square, nND_invsqrt) = nND.invsqrt();
|
||||
let nr_0_nND_invsqrt = &nND_invsqrt * &(r_0 + r_0);
|
||||
c.conditional_assign(&minus_one, nND_is_nonzero_square);
|
||||
e.conditional_assign(&nr_0_nND_invsqrt, nND_is_nonzero_square);
|
||||
|
||||
// 5. Compute s <--- c*|N*e|
|
||||
let mut s = &N * &e;
|
||||
let neg = s.is_negative_decaf();
|
||||
s.conditional_negate(neg);
|
||||
s *= &c;
|
||||
|
||||
// 6. Compute t <--- -c*N*(r-1)* ((a-2d)*e)^2 -1
|
||||
let a_minus_2d_e_sq = (&(&minus_one - &constants::d2) * &e).square();
|
||||
let c_N_r_minus_1 = &c * &(&N * &(&r + &minus_one));
|
||||
let t = &minus_one - &(&c_N_r_minus_1 * &a_minus_2d_e_sq);
|
||||
|
||||
// 7. Apply the isogeny:
|
||||
// (x,y) = ((2s)/(1+as^2), (1-as^2)/(t))
|
||||
let as_sq = &minus_one * &s.square();
|
||||
let P = CompletedPoint{
|
||||
X: &s + &s,
|
||||
Z: &FieldElement::one() + &as_sq,
|
||||
Y: &FieldElement::one() - &as_sq,
|
||||
T: t,
|
||||
};
|
||||
|
||||
// Convert to extended and return.
|
||||
DecafPoint(P.to_extended())
|
||||
}
|
||||
|
||||
/// Return a `DecafPoint` chosen uniformly at random using a user-provided RNG.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `rng`: any RNG which implements the `rand::Rng` interface.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A random element of the Decaf group.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// Uses the Decaf-flavoured Elligator 2 map, so that the discrete log of the
|
||||
/// output point with respect to any other point should be unknown.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn random<T: Rng>(rng: &mut T) -> Self {
|
||||
let mut field_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut field_bytes);
|
||||
let r_0 = FieldElement::from_bytes(&field_bytes);
|
||||
DecafPoint::elligator_decaf_flavour(&r_0)
|
||||
}
|
||||
|
||||
/// Hash a slice of bytes into a `DecafPoint`.
|
||||
///
|
||||
/// Takes a type parameter `D`, which is any `Digest` producing 32
|
||||
/// bytes (256 bits) of output.
|
||||
///
|
||||
/// Convenience wrapper around `from_hash`.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// Uses the Decaf-flavoured Elligator 2 map, so that the discrete log of the
|
||||
/// output point with respect to any other point should be unknown.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate curve25519_dalek;
|
||||
/// # use curve25519_dalek::decaf::DecafPoint;
|
||||
/// extern crate sha2;
|
||||
/// use sha2::Sha256;
|
||||
///
|
||||
/// # // Need fn main() here in comment so the doctest compiles
|
||||
/// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests
|
||||
/// # fn main() {
|
||||
/// let msg = "To really appreciate architecture, you may even need to commit a murder";
|
||||
/// let P = DecafPoint::hash_from_bytes::<Sha256>(msg.as_bytes());
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
pub fn hash_from_bytes<D>(input: &[u8]) -> DecafPoint
|
||||
where D: Digest<OutputSize = U32> + Default
|
||||
{
|
||||
let mut hash = D::default();
|
||||
hash.input(input);
|
||||
DecafPoint::from_hash(hash)
|
||||
}
|
||||
|
||||
/// Construct a `DecafPoint` from an existing `Digest` instance.
|
||||
///
|
||||
/// Use this instead of `hash_from_bytes` if it is more convenient
|
||||
/// to stream data into the `Digest` than to pass a single byte
|
||||
/// slice.
|
||||
pub fn from_hash<D>(hash: D) -> DecafPoint
|
||||
where D: Digest<OutputSize = U32> + Default
|
||||
{
|
||||
// XXX this seems clumsy
|
||||
let mut output = [0u8; 32];
|
||||
output.copy_from_slice(hash.result().as_slice());
|
||||
let r_0 = FieldElement::from_bytes(&output);
|
||||
DecafPoint::elligator_decaf_flavour(&r_0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Identity for DecafPoint {
|
||||
fn identity() -> DecafPoint {
|
||||
DecafPoint(ExtendedPoint::identity())
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Equality
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// XXX check whether there's a simple way to do equality checking
|
||||
/// with cofactor 8, not just cofactor 4, and add a CT equality function?
|
||||
impl PartialEq for DecafPoint {
|
||||
fn eq(&self, other: &DecafPoint) -> bool {
|
||||
let self_compressed = self.compress();
|
||||
let other_compressed = other.compress();
|
||||
self_compressed == other_compressed
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DecafPoint {}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Arithmetic
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
impl<'a, 'b> Add<&'b DecafPoint> for &'a DecafPoint {
|
||||
type Output = DecafPoint;
|
||||
|
||||
fn add(self, other: &'b DecafPoint) -> DecafPoint {
|
||||
DecafPoint(&self.0 + &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> AddAssign<&'b DecafPoint> for DecafPoint {
|
||||
fn add_assign(&mut self, _rhs: &DecafPoint) {
|
||||
*self = (self as &DecafPoint) + _rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Sub<&'b DecafPoint> for &'a DecafPoint {
|
||||
type Output = DecafPoint;
|
||||
|
||||
fn sub(self, other: &'b DecafPoint) -> DecafPoint {
|
||||
DecafPoint(&self.0 - &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> SubAssign<&'b DecafPoint> for DecafPoint {
|
||||
fn sub_assign(&mut self, _rhs: &DecafPoint) {
|
||||
*self = (self as &DecafPoint) - _rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Neg for &'a DecafPoint {
|
||||
type Output = DecafPoint;
|
||||
|
||||
fn neg(self) -> DecafPoint {
|
||||
DecafPoint(-&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> MulAssign<&'b Scalar> for DecafPoint {
|
||||
fn mul_assign(&mut self, scalar: &'b Scalar) {
|
||||
let result = (self as &DecafPoint) * scalar;
|
||||
*self = result;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a DecafPoint {
|
||||
type Output = DecafPoint;
|
||||
/// Scalar multiplication: compute `scalar * self`.
|
||||
fn mul(self, scalar: &'b Scalar) -> DecafPoint {
|
||||
DecafPoint(&self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'b DecafPoint> for &'a Scalar {
|
||||
type Output = DecafPoint;
|
||||
|
||||
/// Scalar multiplication: compute `self * scalar`.
|
||||
fn mul(self, point: &'b DecafPoint) -> DecafPoint {
|
||||
DecafPoint(self * &point.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a vector of (possibly secret) scalars and a vector of
|
||||
/// (possibly secret) points, compute `c_1 P_1 + ... + c_n P_n`.
|
||||
///
|
||||
/// This function has the same behaviour as
|
||||
/// `vartime::multiscalar_mult` but is constant-time.
|
||||
///
|
||||
/// # Input
|
||||
///
|
||||
/// A vector of `Scalar`s and a vector of `DecafPoints`. It is an
|
||||
/// error to call this function with two vectors of different lengths.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> DecafPoint
|
||||
where I: IntoIterator<Item = &'a Scalar>,
|
||||
J: IntoIterator<Item = &'b DecafPoint>,
|
||||
{
|
||||
let extended_points = points.into_iter().map(|P| &P.0);
|
||||
DecafPoint(edwards::multiscalar_mult(scalars, extended_points))
|
||||
}
|
||||
|
||||
/// Precomputation
|
||||
#[derive(Clone)]
|
||||
pub struct DecafBasepointTable(pub EdwardsBasepointTable);
|
||||
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a DecafBasepointTable {
|
||||
type Output = DecafPoint;
|
||||
|
||||
fn mul(self, scalar: &'b Scalar) -> DecafPoint {
|
||||
DecafPoint(&self.0 * scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'a DecafBasepointTable> for &'b Scalar {
|
||||
type Output = DecafPoint;
|
||||
|
||||
fn mul(self, basepoint_table: &'a DecafBasepointTable) -> DecafPoint {
|
||||
DecafPoint(self * &basepoint_table.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DecafBasepointTable {
|
||||
/// Create a precomputed table of multiples of the given `basepoint`.
|
||||
pub fn create(basepoint: &DecafPoint) -> DecafBasepointTable {
|
||||
DecafBasepointTable(EdwardsBasepointTable::create(&basepoint.0))
|
||||
}
|
||||
|
||||
/// Get the basepoint for this table as a `DecafPoint`.
|
||||
pub fn basepoint(&self) -> DecafPoint {
|
||||
DecafPoint(self.0.basepoint())
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Constant-time conditional assignment
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
impl ConditionallyAssignable for DecafPoint {
|
||||
/// Conditionally assign `other` to `self`, if `choice == 1u8`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate subtle;
|
||||
/// # extern crate curve25519_dalek;
|
||||
/// #
|
||||
/// # use subtle::ConditionallyAssignable;
|
||||
/// #
|
||||
/// # use curve25519_dalek::edwards::Identity;
|
||||
/// # use curve25519_dalek::decaf::DecafPoint;
|
||||
/// # use curve25519_dalek::constants;
|
||||
/// # fn main() {
|
||||
/// let A = DecafPoint::identity();
|
||||
/// let B = constants::DECAF_ED25519_BASEPOINT_POINT;
|
||||
///
|
||||
/// let mut P = A;
|
||||
///
|
||||
/// P.conditional_assign(&B, 0u8);
|
||||
/// assert!(P == A);
|
||||
/// P.conditional_assign(&B, 1u8);
|
||||
/// assert!(P == B);
|
||||
/// # }
|
||||
/// ```
|
||||
fn conditional_assign(&mut self, other: &DecafPoint, choice: u8) {
|
||||
self.0.X.conditional_assign(&other.0.X, choice);
|
||||
self.0.Y.conditional_assign(&other.0.Y, choice);
|
||||
self.0.Z.conditional_assign(&other.0.Z, choice);
|
||||
self.0.T.conditional_assign(&other.0.T, choice);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Debug traits
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
impl Debug for CompressedDecaf {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
write!(f, "CompressedDecaf: {:?}", self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DecafPoint {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
let coset = self.coset4();
|
||||
write!(f, "DecafPoint: coset \n{:?}\n{:?}\n{:?}\n{:?}",
|
||||
coset[0], coset[1], coset[2], coset[3])
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Variable-time functions
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
pub mod vartime {
|
||||
//! Variable-time operations on decaf points, useful for non-secret data.
|
||||
use super::*;
|
||||
|
||||
/// Given a vector of public scalars and a vector of (possibly secret)
|
||||
/// points, compute
|
||||
///
|
||||
/// c_1 P_1 + ... + c_n P_n.
|
||||
///
|
||||
/// # Input
|
||||
///
|
||||
/// A vector of `Scalar`s and a vector of `ExtendedPoints`. It is an
|
||||
/// error to call this function with two vectors of different lengths.
|
||||
pub fn multiscalar_mult<'a, 'b, I, J>(scalars: I, points: J) -> DecafPoint
|
||||
where I: IntoIterator<Item = &'a Scalar>,
|
||||
J: IntoIterator<Item = &'b DecafPoint>
|
||||
{
|
||||
let extended_points = points.into_iter().map(|P| &P.0);
|
||||
DecafPoint(edwards::vartime::multiscalar_mult(scalars, extended_points))
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::OsRng;
|
||||
|
||||
use scalar::Scalar;
|
||||
use constants;
|
||||
use edwards::CompressedEdwardsY;
|
||||
use edwards::Identity;
|
||||
use edwards::ValidityCheck;
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde_cbor;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn serde_cbor_basepoint_roundtrip() {
|
||||
let output = serde_cbor::to_vec(&constants::DECAF_ED25519_BASEPOINT_POINT).unwrap();
|
||||
let parsed: DecafPoint = serde_cbor::from_slice(&output).unwrap();
|
||||
assert_eq!(parsed, constants::DECAF_ED25519_BASEPOINT_POINT);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn decaf_decompress_negative_s_fails() {
|
||||
// constants::d is neg, so decompression should fail as |d| != d.
|
||||
let bad_compressed = CompressedDecaf(constants::d.to_bytes());
|
||||
assert!(bad_compressed.decompress().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_decompress_id() {
|
||||
let compressed_id = CompressedDecaf::identity();
|
||||
let id = compressed_id.decompress().unwrap();
|
||||
assert_eq!(id.0.compress(), CompressedEdwardsY::identity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_compress_id() {
|
||||
let id = DecafPoint::identity();
|
||||
assert_eq!(id.compress(), CompressedDecaf::identity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_basepoint_roundtrip() {
|
||||
let bp_compressed_decaf = constants::DECAF_ED25519_BASEPOINT_POINT.compress();
|
||||
let bp_recaf = bp_compressed_decaf.decompress().unwrap().0;
|
||||
// Check that bp_recaf differs from bp by a point of order 4
|
||||
let diff = &constants::ED25519_BASEPOINT_POINT - &bp_recaf;
|
||||
let diff4 = diff.mult_by_pow_2(4); // XXX this is wrong
|
||||
assert_eq!(diff4.compress(), CompressedEdwardsY::identity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encodings_of_small_multiples_of_basepoint() {
|
||||
// Table of encodings of (1+i)*basepoint
|
||||
// Generated using the previous naive implementation.
|
||||
let compressed = [
|
||||
CompressedDecaf([141, 190, 226, 107, 177, 201, 35, 118, 14, 55, 160, 165, 242, 207, 121, 161, 177, 80, 8, 132, 205, 254, 101, 169, 233, 65, 124, 96, 255, 182, 249, 40]),
|
||||
CompressedDecaf([131, 57, 148, 16, 8, 196, 141, 82, 144, 220, 105, 112, 66, 33, 48, 16, 182, 198, 173, 35, 248, 181, 92, 231, 222, 35, 85, 56, 5, 252, 91, 40]),
|
||||
CompressedDecaf([199, 132, 32, 144, 156, 143, 81, 170, 240, 56, 232, 6, 178, 37, 118, 190, 110, 201, 26, 173, 156, 97, 59, 162, 240, 247, 226, 107, 197, 111, 107, 26]),
|
||||
CompressedDecaf([210, 120, 34, 214, 175, 27, 61, 6, 229, 181, 216, 36, 11, 245, 146, 232, 130, 215, 77, 29, 210, 30, 54, 155, 191, 81, 59, 124, 174, 3, 135, 36]),
|
||||
CompressedDecaf([155, 52, 159, 52, 189, 27, 181, 0, 245, 131, 0, 197, 79, 208, 252, 122, 104, 161, 245, 143, 67, 94, 13, 129, 153, 173, 129, 179, 118, 231, 90, 52]),
|
||||
CompressedDecaf([42, 117, 252, 118, 8, 1, 72, 25, 111, 246, 247, 103, 236, 86, 235, 29, 100, 156, 186, 209, 159, 21, 61, 26, 249, 25, 137, 228, 84, 23, 10, 27]),
|
||||
CompressedDecaf([21, 126, 181, 117, 58, 90, 216, 28, 184, 57, 9, 23, 158, 68, 159, 171, 109, 150, 232, 140, 144, 73, 139, 122, 124, 105, 125, 160, 94, 185, 150, 52]),
|
||||
CompressedDecaf([232, 167, 112, 233, 126, 33, 105, 63, 151, 6, 88, 225, 181, 17, 223, 12, 116, 138, 203, 47, 243, 225, 50, 171, 21, 220, 186, 179, 132, 20, 48, 6]),
|
||||
CompressedDecaf([99, 44, 97, 48, 242, 174, 78, 198, 112, 154, 146, 36, 239, 34, 94, 4, 0, 244, 175, 34, 46, 0, 83, 187, 5, 163, 225, 63, 51, 237, 234, 22]),
|
||||
CompressedDecaf([2, 33, 89, 176, 178, 123, 159, 75, 235, 172, 251, 11, 137, 177, 90, 122, 149, 186, 52, 243, 153, 190, 185, 202, 59, 137, 204, 160, 150, 152, 148, 55]),
|
||||
CompressedDecaf([245, 79, 78, 226, 114, 69, 247, 112, 18, 54, 90, 225, 176, 77, 231, 235, 196, 123, 49, 221, 34, 205, 151, 228, 244, 112, 82, 58, 30, 31, 58, 12]),
|
||||
CompressedDecaf([135, 53, 175, 167, 13, 94, 62, 31, 29, 248, 13, 132, 29, 69, 7, 188, 145, 49, 62, 55, 181, 109, 214, 11, 248, 162, 70, 15, 236, 126, 100, 60]),
|
||||
CompressedDecaf([98, 150, 69, 229, 144, 122, 237, 107, 127, 177, 33, 64, 59, 173, 210, 102, 74, 34, 23, 16, 252, 117, 14, 97, 231, 178, 63, 193, 157, 28, 178, 17]),
|
||||
CompressedDecaf([222, 104, 6, 1, 72, 12, 72, 178, 204, 238, 128, 70, 41, 150, 235, 96, 153, 150, 18, 4, 141, 206, 0, 38, 122, 112, 249, 51, 94, 251, 20, 57]),
|
||||
CompressedDecaf([7, 221, 140, 57, 13, 146, 248, 27, 56, 4, 128, 23, 145, 120, 126, 4, 158, 173, 52, 213, 164, 250, 26, 55, 89, 96, 187, 111, 211, 18, 63, 19]),
|
||||
CompressedDecaf([91, 213, 193, 10, 102, 92, 199, 124, 61, 176, 1, 47, 111, 59, 183, 91, 79, 56, 208, 109, 172, 209, 17, 167, 229, 216, 3, 236, 200, 208, 15, 20]),
|
||||
];
|
||||
let mut bp = constants::DECAF_ED25519_BASEPOINT_POINT;
|
||||
for i in 0..16 {
|
||||
assert_eq!(bp.compress(), compressed[i]);
|
||||
bp = &bp + &constants::DECAF_ED25519_BASEPOINT_POINT;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_four_torsion_basepoint() {
|
||||
let bp = constants::DECAF_ED25519_BASEPOINT_POINT;
|
||||
let bp_coset = bp.coset4();
|
||||
for i in 0..4 {
|
||||
assert_eq!(bp, DecafPoint(bp_coset[i]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_four_torsion_random() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let B = &constants::DECAF_ED25519_BASEPOINT_TABLE;
|
||||
let P = B * &Scalar::random(&mut rng);
|
||||
let P_coset = P.coset4();
|
||||
for i in 0..4 {
|
||||
assert_eq!(P, DecafPoint(P_coset[i]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_random_roundtrip() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let B = &constants::DECAF_ED25519_BASEPOINT_TABLE;
|
||||
for _ in 0..100 {
|
||||
let P = B * &Scalar::random(&mut rng);
|
||||
let compressed_P = P.compress();
|
||||
let Q = compressed_P.decompress().unwrap();
|
||||
assert_eq!(P, Q);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decaf_random_is_valid() {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
for _ in 0..100 {
|
||||
let P = DecafPoint::random(&mut rng);
|
||||
// Check that P is on the curve
|
||||
assert!(P.0.is_valid());
|
||||
// Check that P is in the image of the decaf map
|
||||
P.compress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "bench"))]
|
||||
mod bench {
|
||||
use rand::OsRng;
|
||||
use test::Bencher;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[bench]
|
||||
fn decompression(b: &mut Bencher) {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let B = &constants::DECAF_ED25519_BASEPOINT_TABLE;
|
||||
let P = B * &Scalar::random(&mut rng);
|
||||
let P_compressed = P.compress();
|
||||
b.iter(|| P_compressed.decompress().unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn compression(b: &mut Bencher) {
|
||||
let mut rng = OsRng::new().unwrap();
|
||||
let B = &constants::DECAF_ED25519_BASEPOINT_TABLE;
|
||||
let P = B * &Scalar::random(&mut rng);
|
||||
b.iter(|| P.compress());
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ impl CompressedEdwardsY {
|
|||
|
||||
// Flip the sign of X if it's not correct
|
||||
let compressed_sign_bit = self.as_bytes()[31] >> 7;
|
||||
let current_sign_bit = X.is_negative_ed25519();
|
||||
let current_sign_bit = X.is_negative();
|
||||
X.conditional_negate(current_sign_bit ^ compressed_sign_bit);
|
||||
|
||||
Some(ExtendedPoint{ X: X, Y: Y, Z: Z, T: &X * &Y })
|
||||
|
|
@ -442,7 +442,7 @@ impl ProjectivePoint {
|
|||
let mut s: [u8; 32];
|
||||
|
||||
s = y.to_bytes();
|
||||
s[31] ^= (x.is_negative_ed25519() << 7) as u8;
|
||||
s[31] ^= (x.is_negative() << 7) as u8;
|
||||
CompressedEdwardsY(s)
|
||||
}
|
||||
|
||||
|
|
@ -1269,8 +1269,6 @@ pub mod vartime {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(feature = "yolocrypto")]
|
||||
use decaf::DecafPoint;
|
||||
use field::FieldElement;
|
||||
use scalar::Scalar;
|
||||
use subtle::ConditionallyAssignable;
|
||||
|
|
@ -1550,18 +1548,6 @@ mod test {
|
|||
assert!(P1.compress().to_bytes() == P2.compress().to_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "yolocrypto")]
|
||||
fn scalarmult_decafpoint_works_both_ways() {
|
||||
let P: DecafPoint = DecafPoint(constants::ED25519_BASEPOINT_POINT);
|
||||
let s: Scalar = A_SCALAR;
|
||||
|
||||
let P1 = &P * &s;
|
||||
let P2 = &s * &P;
|
||||
|
||||
assert!(P1.compress().as_bytes() == P2.compress().as_bytes());
|
||||
}
|
||||
|
||||
mod vartime {
|
||||
use super::super::*;
|
||||
use super::{A_SCALAR, B_SCALAR, A_TIMES_BASEPOINT, DOUBLE_SCALAR_MULT_RESULT};
|
||||
|
|
|
|||
56
src/field.rs
56
src/field.rs
|
|
@ -23,6 +23,7 @@ use core::cmp::{Eq, PartialEq};
|
|||
use subtle::slices_equal;
|
||||
use subtle::byte_is_nonzero;
|
||||
use subtle::ConditionallyAssignable;
|
||||
use subtle::ConditionallyNegatable;
|
||||
use subtle::Equal;
|
||||
|
||||
use constants;
|
||||
|
|
@ -81,44 +82,11 @@ impl FieldElement {
|
|||
/// # Return
|
||||
///
|
||||
/// If negative, return `1u8`. Otherwise, return `0u8`.
|
||||
pub fn is_negative_ed25519(&self) -> u8 { //FeIsNegative
|
||||
pub fn is_negative(&self) -> u8 {
|
||||
let bytes = self.to_bytes();
|
||||
(bytes[0] & 1) as u8
|
||||
}
|
||||
|
||||
/// Determine if this `FieldElement` is negative, in the
|
||||
/// sense used by Decaf: `x` is nonnegative if the least
|
||||
/// absolute residue for `x` lies in `[0, (p-1)/2]`, and
|
||||
/// is negative otherwise.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `1u8` if negative, `0u8` if nonnegative.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// Uses a trick borrowed from Mike Hamburg's code. Let `x \in
|
||||
/// F_p` and let `y \in Z` be the least absolute residue for `x`.
|
||||
/// Suppose `y ≤ (p-1)/2`. Then `2y < p` so `2y = 2y mod p` and
|
||||
/// `2y mod p` is even. On the other hand, if `y > (p-1)/2` then
|
||||
/// `2y ≥ p`; since `y < p`, `2y \in [p, 2p)`, so `2y mod p =
|
||||
/// 2y-p`, which is odd.
|
||||
///
|
||||
/// Thus we can test whether `y ≤ (p-1)/2` by checking whether `2y
|
||||
/// mod p` is even.
|
||||
pub fn is_negative_decaf(&self) -> u8 {
|
||||
let y = self + self;
|
||||
(y.to_bytes()[0] & 1) as u8
|
||||
}
|
||||
|
||||
/// Determine if this `FieldElement` is nonnegative, in the
|
||||
/// sense used by Decaf: `x` is nonnegative if the least
|
||||
/// absolute residue for `x` lies in `[0, (p-1)/2]`, and
|
||||
/// is negative otherwise.
|
||||
pub fn is_nonnegative_decaf(&self) -> u8 {
|
||||
1u8 & (!self.is_negative_decaf())
|
||||
}
|
||||
|
||||
/// Determine if this `FieldElement` is zero.
|
||||
///
|
||||
/// # Return
|
||||
|
|
@ -232,6 +200,8 @@ impl FieldElement {
|
|||
/// Given `FieldElements` `u` and `v`, attempt to compute
|
||||
/// `sqrt(u/v)` in constant time.
|
||||
///
|
||||
/// This function always returns the nonnegative square root, if it exists.
|
||||
///
|
||||
/// It would be much better to use an `Option` type here, but
|
||||
/// doing so forces the caller to branch, which we don't want to
|
||||
/// do. This seems like the least bad solution.
|
||||
|
|
@ -278,6 +248,10 @@ impl FieldElement {
|
|||
let r_prime = &constants::SQRT_M1 * &r;
|
||||
r.conditional_assign(&r_prime, flipped_sign_sqrt);
|
||||
|
||||
// Choose the nonnegative square root.
|
||||
let r_is_negative = r.is_negative();
|
||||
r.conditional_negate(r_is_negative);
|
||||
|
||||
let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt;
|
||||
|
||||
(was_nonzero_square, r)
|
||||
|
|
@ -465,6 +439,20 @@ mod test {
|
|||
x.conditional_negate(1u8);
|
||||
assert_eq!(x, one);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_is_canonical() {
|
||||
// Encode 1 wrongly as 1 + (2^255 - 19) = 2^255 - 18
|
||||
let one_encoded_wrongly_bytes: [u8;32] = [0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f];
|
||||
// Decode to a field element
|
||||
let one = FieldElement::from_bytes(&one_encoded_wrongly_bytes);
|
||||
// .. then check that the encoding is correct
|
||||
let one_bytes = one.to_bytes();
|
||||
assert_eq!(one_bytes[0], 1);
|
||||
for i in 1..32 {
|
||||
assert_eq!(one_bytes[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "bench"))]
|
||||
|
|
|
|||
|
|
@ -74,9 +74,7 @@ pub mod scalar;
|
|||
pub mod edwards;
|
||||
pub mod montgomery;
|
||||
|
||||
// Feature gate decaf while our implementation is unfinished and probably incorrect.
|
||||
#[cfg(feature = "yolocrypto")]
|
||||
pub mod decaf;
|
||||
pub mod ristretto;
|
||||
|
||||
// Other miscelaneous utilities.
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ impl CompressedMontgomeryU {
|
|||
pub fn to_edwards_x(u: &FieldElement, v: &FieldElement, sign: &u8) -> FieldElement {
|
||||
let mut x: FieldElement = &(u * &v.invert()) * &constants::SQRT_MINUS_APLUS2;
|
||||
let neg_x: FieldElement = -(&x);
|
||||
let current_sign: u8 = x.is_negative_ed25519();
|
||||
let current_sign: u8 = x.is_negative();
|
||||
|
||||
// Negate x to match the sign:
|
||||
x.conditional_assign(&neg_x, current_sign ^ sign);
|
||||
|
|
|
|||
1245
src/ristretto.rs
Normal file
1245
src/ristretto.rs
Normal file
File diff suppressed because it is too large
Load diff
611
vendor/ristretto.sage
vendored
Normal file
611
vendor/ristretto.sage
vendored
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
import binascii
|
||||
class InvalidEncodingException(Exception): pass
|
||||
class NotOnCurveException(Exception): pass
|
||||
class SpecException(Exception): pass
|
||||
|
||||
def lobit(x): return int(x) & 1
|
||||
def hibit(x): return lobit(2*x)
|
||||
def negative(x): return lobit(x)
|
||||
def enc_le(x,n): return bytearray([int(x)>>(8*i) & 0xFF for i in xrange(n)])
|
||||
def dec_le(x): return sum(b<<(8*i) for i,b in enumerate(x))
|
||||
def randombytes(n): return bytearray([randint(0,255) for _ in range(n)])
|
||||
|
||||
def optimized_version_of(spec):
|
||||
"""Decorator: This function is an optimized version of some specification"""
|
||||
def decorator(f):
|
||||
def wrapper(self,*args,**kwargs):
|
||||
def pr(x):
|
||||
if isinstance(x,bytearray): return binascii.hexlify(x)
|
||||
else: return str(x)
|
||||
try: spec_ans = getattr(self,spec,spec)(*args,**kwargs),None
|
||||
except Exception as e: spec_ans = None,e
|
||||
try: opt_ans = f(self,*args,**kwargs),None
|
||||
except Exception as e: opt_ans = None,e
|
||||
if spec_ans[1] is None and opt_ans[1] is not None:
|
||||
raise
|
||||
#raise SpecException("Mismatch in %s: spec returned %s but opt threw %s"
|
||||
# % (f.__name__,str(spec_ans[0]),str(opt_ans[1])))
|
||||
if spec_ans[1] is not None and opt_ans[1] is None:
|
||||
raise
|
||||
#raise SpecException("Mismatch in %s: spec threw %s but opt returned %s"
|
||||
# % (f.__name__,str(spec_ans[1]),str(opt_ans[0])))
|
||||
if spec_ans[0] != opt_ans[0]:
|
||||
raise SpecException("Mismatch in %s: %s != %s"
|
||||
% (f.__name__,pr(spec_ans[0]),pr(opt_ans[0])))
|
||||
if opt_ans[1] is not None: raise
|
||||
else: return opt_ans[0]
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def xsqrt(x,exn=InvalidEncodingException("Not on curve")):
|
||||
"""Return sqrt(x)"""
|
||||
if not is_square(x): raise exn
|
||||
s = sqrt(x)
|
||||
if negative(s): s=-s
|
||||
return s
|
||||
|
||||
def isqrt(x,exn=InvalidEncodingException("Not on curve")):
|
||||
"""Return 1/sqrt(x)"""
|
||||
if x==0: return 0
|
||||
if not is_square(x): raise exn
|
||||
return 1/sqrt(x)
|
||||
|
||||
def isqrt_i(x):
|
||||
"""Return 1/sqrt(x) or 1/sqrt(zeta * x)"""
|
||||
if x==0: return True,0
|
||||
gen = x.parent(-1)
|
||||
while is_square(gen): gen = sqrt(gen)
|
||||
if is_square(x): return True,1/sqrt(x)
|
||||
else: return False,1/sqrt(x*gen)
|
||||
|
||||
class QuotientEdwardsPoint(object):
|
||||
"""Abstract class for point an a quotiented Edwards curve; needs F,a,d,cofactor to work"""
|
||||
def __init__(self,x=0,y=1):
|
||||
x = self.x = self.F(x)
|
||||
y = self.y = self.F(y)
|
||||
if y^2 + self.a*x^2 != 1 + self.d*x^2*y^2:
|
||||
raise NotOnCurveException(str(self))
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(0x%x,0x%x)" % (self.__class__.__name__, self.x, self.y)
|
||||
|
||||
def __iter__(self):
|
||||
yield self.x
|
||||
yield self.y
|
||||
|
||||
def __add__(self,other):
|
||||
x,y = self
|
||||
X,Y = other
|
||||
a,d = self.a,self.d
|
||||
return self.__class__(
|
||||
(x*Y+y*X)/(1+d*x*y*X*Y),
|
||||
(y*Y-a*x*X)/(1-d*x*y*X*Y)
|
||||
)
|
||||
|
||||
def __neg__(self): return self.__class__(-self.x,self.y)
|
||||
def __sub__(self,other): return self + (-other)
|
||||
def __rmul__(self,other): return self*other
|
||||
def __eq__(self,other):
|
||||
"""NB: this is the only method that is different from the usual one"""
|
||||
x,y = self
|
||||
X,Y = other
|
||||
return x*Y == X*y or (self.cofactor==8 and -self.a*x*X == y*Y)
|
||||
def __ne__(self,other): return not (self==other)
|
||||
|
||||
def __mul__(self,exp):
|
||||
exp = int(exp)
|
||||
if exp < 0: exp,self = -exp,-self
|
||||
total = self.__class__()
|
||||
work = self
|
||||
while exp != 0:
|
||||
if exp & 1: total += work
|
||||
work += work
|
||||
exp >>= 1
|
||||
return total
|
||||
|
||||
def xyzt(self):
|
||||
x,y = self
|
||||
z = self.F.random_element()
|
||||
return x*z,y*z,z,x*y*z
|
||||
|
||||
def torque(self):
|
||||
"""Apply cofactor group, except keeping the point even"""
|
||||
if self.cofactor == 8:
|
||||
if self.a == -1: return self.__class__(self.y*self.i, self.x*self.i)
|
||||
if self.a == 1: return self.__class__(-self.y, self.x)
|
||||
else:
|
||||
return self.__class__(-self.x, -self.y)
|
||||
|
||||
|
||||
# Utility functions
|
||||
@classmethod
|
||||
def bytesToGf(cls,bytes,mustBeProper=True,mustBePositive=False):
|
||||
"""Convert little-endian bytes to field element, sanity check length"""
|
||||
if len(bytes) != cls.encLen:
|
||||
raise InvalidEncodingException("wrong length %d" % len(bytes))
|
||||
s = dec_le(bytes)
|
||||
if mustBeProper and s >= cls.F.modulus():
|
||||
raise InvalidEncodingException("%d out of range!" % s)
|
||||
s = cls.F(s)
|
||||
if mustBePositive and negative(s):
|
||||
raise InvalidEncodingException("%d is negative!" % s)
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def gfToBytes(cls,x,mustBePositive=False):
|
||||
"""Convert little-endian bytes to field element, sanity check length"""
|
||||
if negative(x) and mustBePositive: x = -x
|
||||
return enc_le(x,cls.encLen)
|
||||
|
||||
class RistrettoPoint(QuotientEdwardsPoint):
|
||||
"""The new Ristretto group"""
|
||||
def encodeSpec(self):
|
||||
"""Unoptimized specification for encoding"""
|
||||
x,y = self
|
||||
if self.cofactor==8 and (negative(x*y) or y==0): (x,y) = self.torque()
|
||||
if y == -1: y = 1 # Avoid divide by 0; doesn't affect impl
|
||||
|
||||
if negative(x): x,y = -x,-y
|
||||
s = xsqrt(self.mneg*(1-y)/(1+y),exn=Exception("Unimplemented: point is odd: " + str(self)))
|
||||
return self.gfToBytes(s)
|
||||
|
||||
@classmethod
|
||||
def decodeSpec(cls,s):
|
||||
"""Unoptimized specification for decoding"""
|
||||
s = cls.bytesToGf(s,mustBePositive=True)
|
||||
|
||||
a,d = cls.a,cls.d
|
||||
x = xsqrt(4*s^2 / (a*d*(1+a*s^2)^2 - (1-a*s^2)^2))
|
||||
y = (1+a*s^2) / (1-a*s^2)
|
||||
|
||||
if cls.cofactor==8 and (negative(x*y) or y==0):
|
||||
raise InvalidEncodingException("x*y has high bit")
|
||||
|
||||
return cls(x,y)
|
||||
|
||||
@optimized_version_of("encodeSpec")
|
||||
def encode(self):
|
||||
"""Encode, optimized version"""
|
||||
a,d,mneg = self.a,self.d,self.mneg
|
||||
x,y,z,t = self.xyzt()
|
||||
|
||||
if self.cofactor==8:
|
||||
u1 = mneg*(z+y)*(z-y)
|
||||
u2 = x*y # = t*z
|
||||
isr = isqrt(u1*u2^2)
|
||||
i1 = isr*u1 # sqrt(mneg*(z+y)*(z-y))/(x*y)
|
||||
i2 = isr*u2 # 1/sqrt(a*(y+z)*(y-z))
|
||||
z_inv = i1*i2*t # 1/z
|
||||
|
||||
if negative(t*z_inv):
|
||||
if a==-1:
|
||||
x,y = y*self.i,x*self.i
|
||||
den_inv = self.magic * i1
|
||||
else:
|
||||
x,y = -y,x
|
||||
den_inv = self.i * self.magic * i1
|
||||
|
||||
else:
|
||||
den_inv = i2
|
||||
|
||||
if negative(x*z_inv): y = -y
|
||||
s = (z-y) * den_inv
|
||||
else:
|
||||
num = mneg*(z+y)*(z-y)
|
||||
isr = isqrt(num*y^2)
|
||||
if negative(isr^2*num*y*t): y = -y
|
||||
s = isr*y*(z-y)
|
||||
|
||||
|
||||
return self.gfToBytes(s,mustBePositive=True)
|
||||
|
||||
@classmethod
|
||||
@optimized_version_of("decodeSpec")
|
||||
def decode(cls,s):
|
||||
"""Decode, optimized version"""
|
||||
s = cls.bytesToGf(s,mustBePositive=True)
|
||||
|
||||
a,d = cls.a,cls.d
|
||||
yden = 1-a*s^2
|
||||
ynum = 1+a*s^2
|
||||
yden_sqr = yden^2
|
||||
xden_sqr = a*d*ynum^2 - yden_sqr
|
||||
|
||||
isr = isqrt(xden_sqr * yden_sqr)
|
||||
|
||||
xden_inv = isr * yden
|
||||
yden_inv = xden_inv * isr * xden_sqr
|
||||
|
||||
x = 2*s*xden_inv
|
||||
if negative(x): x = -x
|
||||
y = ynum * yden_inv
|
||||
|
||||
if cls.cofactor==8 and (negative(x*y) or y==0):
|
||||
raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y))
|
||||
|
||||
return cls(x,y)
|
||||
|
||||
@classmethod
|
||||
def fromJacobiQuartic(cls,s,t,sgn=1):
|
||||
"""Convert point from its Jacobi Quartic representation"""
|
||||
a,d = cls.a,cls.d
|
||||
assert s^4 - 2*cls.a*(1-2*d/(d-a))*s^2 + 1 == t^2
|
||||
x = 2*s*cls.magic / t
|
||||
y = (1+a*s^2) / (1-a*s^2)
|
||||
return cls(sgn*x,y)
|
||||
|
||||
@classmethod
|
||||
def elligatorSpec(cls,r0):
|
||||
a,d = cls.a,cls.d
|
||||
r = cls.qnr * cls.bytesToGf(r0)^2
|
||||
den = (d*r-a)*(a*r-d)
|
||||
n1 = cls.a*(r+1)*(a+d)*(d-a)/den
|
||||
n2 = r*n1
|
||||
if is_square(n1):
|
||||
sgn,s,t = 1, xsqrt(n1), -(r-1)*(a+d)^2 / den - 1
|
||||
else:
|
||||
sgn,s,t = -1,-xsqrt(n2), r*(r-1)*(a+d)^2 / den - 1
|
||||
|
||||
return cls.fromJacobiQuartic(s,t)
|
||||
|
||||
@classmethod
|
||||
@optimized_version_of("elligatorSpec")
|
||||
def elligator(cls,r0):
|
||||
a,d = cls.a,cls.d
|
||||
r0 = cls.bytesToGf(r0)
|
||||
r = cls.qnr * r0^2
|
||||
den = (d*r-a)*(a*r-d)
|
||||
num = cls.a*(r+1)*(a+d)*(d-a)
|
||||
|
||||
iss,isri = isqrt_i(num*den)
|
||||
if iss: sgn,twiddle = 1,1
|
||||
else: sgn,twiddle = -1,r0*cls.qnr
|
||||
isri *= twiddle
|
||||
s = isri*num
|
||||
t = -sgn*isri*s*(r-1)*(d+a)^2 - 1
|
||||
if negative(s) == iss: s = -s
|
||||
return cls.fromJacobiQuartic(s,t)
|
||||
|
||||
|
||||
class Decaf_1_1_Point(QuotientEdwardsPoint):
|
||||
"""Like current decaf but tweaked for simplicity"""
|
||||
def encodeSpec(self):
|
||||
"""Unoptimized specification for encoding"""
|
||||
a,d = self.a,self.d
|
||||
x,y = self
|
||||
if x==0 or y==0: return(self.gfToBytes(0))
|
||||
|
||||
if self.cofactor==8 and negative(x*y*self.isoMagic):
|
||||
x,y = self.torque()
|
||||
|
||||
isr2 = isqrt(a*(y^2-1)) * sqrt(a*d-1)
|
||||
|
||||
sr = xsqrt(1-a*x^2)
|
||||
assert sr in [isr2*x*y,-isr2*x*y]
|
||||
|
||||
altx = 1/isr2*self.isoMagic
|
||||
if negative(altx): s = (1+x*y*isr2)/(a*x)
|
||||
else: s = (1-x*y*isr2)/(a*x)
|
||||
|
||||
return self.gfToBytes(s,mustBePositive=True)
|
||||
|
||||
@classmethod
|
||||
def decodeSpec(cls,s):
|
||||
"""Unoptimized specification for decoding"""
|
||||
a,d = cls.a,cls.d
|
||||
s = cls.bytesToGf(s,mustBePositive=True)
|
||||
|
||||
if s==0: return cls()
|
||||
isr = isqrt(s^4 + 2*(a-2*d)*s^2 + 1)
|
||||
altx = 2*s*isr*cls.isoMagic
|
||||
if negative(altx): isr = -isr
|
||||
x = 2*s / (1+a*s^2)
|
||||
y = (1-a*s^2) * isr
|
||||
|
||||
if cls.cofactor==8 and (negative(x*y*cls.isoMagic) or y==0):
|
||||
raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y))
|
||||
|
||||
return cls(x,y)
|
||||
|
||||
@optimized_version_of("encodeSpec")
|
||||
def encode(self):
|
||||
"""Encode, optimized version"""
|
||||
a,d = self.a,self.d
|
||||
x,y,z,t = self.xyzt()
|
||||
|
||||
if self.cofactor == 8:
|
||||
# Cofactor 8 version
|
||||
num = (z+y)*(z-y)
|
||||
den = x*y
|
||||
tmp = isqrt(num*(a-d)*den^2)
|
||||
|
||||
if negative(tmp^2*den*num*(a-d)*t^2*self.isoMagic):
|
||||
den,num = num,den
|
||||
tmp *= sqrt(a-d) # witness that cofactor is 8
|
||||
yisr = x*sqrt(a)
|
||||
toggle = (a==1)
|
||||
else:
|
||||
yisr = y*(a*d-1)
|
||||
toggle = False
|
||||
|
||||
tiisr = tmp*num
|
||||
altx = tiisr*t*self.isoMagic
|
||||
if negative(altx) != toggle: tiisr =- tiisr
|
||||
s = tmp*den*yisr*(tiisr*z - 1)
|
||||
|
||||
else:
|
||||
# Much simpler cofactor 4 version
|
||||
num = (x+t)*(x-t)
|
||||
isr = isqrt(num*(a-d)*x^2)
|
||||
ratio = isr*num
|
||||
if negative(ratio*self.isoMagic): ratio=-ratio
|
||||
s = (a-d)*isr*x*(ratio*z - t)
|
||||
|
||||
return self.gfToBytes(s,mustBePositive=True)
|
||||
|
||||
@classmethod
|
||||
@optimized_version_of("decodeSpec")
|
||||
def decode(cls,s):
|
||||
"""Decode, optimized version"""
|
||||
a,d = cls.a,cls.d
|
||||
s = cls.bytesToGf(s,mustBePositive=True)
|
||||
|
||||
if s==0: return cls()
|
||||
s2 = s^2
|
||||
den = 1+a*s2
|
||||
num = den^2 - 4*d*s2
|
||||
isr = isqrt(num*den^2)
|
||||
altx = 2*s*isr*den*cls.isoMagic
|
||||
if negative(altx): isr = -isr
|
||||
x = 2*s *isr^2*den*num
|
||||
y = (1-a*s^2) * isr*den
|
||||
|
||||
if cls.cofactor==8 and (negative(x*y*cls.isoMagic) or y==0):
|
||||
raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y))
|
||||
|
||||
return cls(x,y)
|
||||
|
||||
@classmethod
|
||||
def fromJacobiQuartic(cls,s,t,sgn=1):
|
||||
"""Convert point from its Jacobi Quartic representation"""
|
||||
a,d = cls.a,cls.d
|
||||
if s==0: return cls()
|
||||
x = 2*s / (1+a*s^2)
|
||||
y = (1-a*s^2) / t
|
||||
return cls(x,sgn*y)
|
||||
|
||||
@classmethod
|
||||
def elligatorSpec(cls,r0):
|
||||
a,d = cls.a,cls.d
|
||||
r = cls.qnr * cls.bytesToGf(r0)^2
|
||||
|
||||
den = (d*r-(d-a))*((d-a)*r-d)
|
||||
n1 = (r+1)*(a-2*d)/den
|
||||
n2 = r*n1
|
||||
if is_square(n1):
|
||||
sgn,s,t = 1, xsqrt(n1), -(r-1)*(a-2*d)^2 / den - 1
|
||||
else:
|
||||
sgn,s,t = -1, -xsqrt(n2), r*(r-1)*(a-2*d)^2 / den - 1
|
||||
|
||||
return cls.fromJacobiQuartic(s,t)
|
||||
|
||||
@classmethod
|
||||
@optimized_version_of("elligatorSpec")
|
||||
def elligator(cls,r0):
|
||||
a,d = cls.a,cls.d
|
||||
r0 = cls.bytesToGf(r0)
|
||||
r = cls.qnr * r0^2
|
||||
den = (d*r-(d-a))*((d-a)*r-d)
|
||||
num = (r+1)*(a-2*d)
|
||||
|
||||
iss,isri = isqrt_i(num*den)
|
||||
if iss: sgn,twiddle = 1,1
|
||||
else: sgn,twiddle = -1,r0*cls.qnr
|
||||
isri *= twiddle
|
||||
s = isri*num
|
||||
t = -sgn*isri*s*(r-1)*(a-2*d)^2 - 1
|
||||
if negative(s) == iss: s = -s
|
||||
return cls.fromJacobiQuartic(s,t)
|
||||
|
||||
class Ed25519Point(RistrettoPoint):
|
||||
F = GF(2^255-19)
|
||||
d = F(-121665/121666)
|
||||
a = F(-1)
|
||||
i = sqrt(F(-1))
|
||||
mneg = F(1)
|
||||
qnr = i
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 8
|
||||
encLen = 32
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return cls( 15112221349535400772501151409588531511454012693041857206046113283949847762202, 46316835694926478169428394003475163141307993866256225615783033603165251855960
|
||||
)
|
||||
|
||||
class NegEd25519Point(RistrettoPoint):
|
||||
F = GF(2^255-19)
|
||||
d = F(121665/121666)
|
||||
a = F(1)
|
||||
i = sqrt(F(-1))
|
||||
mneg = F(-1) # TODO checkme vs 1-ad or whatever
|
||||
qnr = i
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 8
|
||||
encLen = 32
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
y = cls.F(4/5)
|
||||
x = sqrt((y^2-1)/(cls.d*y^2-cls.a))
|
||||
if negative(x): x = -x
|
||||
return cls(x,y)
|
||||
|
||||
class IsoEd448Point(RistrettoPoint):
|
||||
F = GF(2^448-2^224-1)
|
||||
d = F(39082/39081)
|
||||
a = F(1)
|
||||
mneg = F(-1)
|
||||
qnr = -1
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 4
|
||||
encLen = 56
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return cls( # RFC has it wrong
|
||||
-345397493039729516374008604150537410266655260075183290216406970281645695073672344430481787759340633221708391583424041788924124567700732,
|
||||
-363419362147803445274661903944002267176820680343659030140745099590306164083365386343198191849338272965044442230921818680526749009182718
|
||||
)
|
||||
|
||||
class TwistedEd448GoldilocksPoint(Decaf_1_1_Point):
|
||||
F = GF(2^448-2^224-1)
|
||||
d = F(-39082)
|
||||
a = F(-1)
|
||||
qnr = -1
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 4
|
||||
encLen = 56
|
||||
isoMagic = IsoEd448Point.magic
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return cls.decodeSpec(Ed448GoldilocksPoint.base().encodeSpec())
|
||||
|
||||
class Ed448GoldilocksPoint(Decaf_1_1_Point):
|
||||
F = GF(2^448-2^224-1)
|
||||
d = F(-39081)
|
||||
a = F(1)
|
||||
qnr = -1
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 4
|
||||
encLen = 56
|
||||
isoMagic = IsoEd448Point.magic
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return -2*cls( # FIXME: make not negative
|
||||
224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710, 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660
|
||||
)
|
||||
|
||||
class IsoEd25519Point(Decaf_1_1_Point):
|
||||
# TODO: twisted iso too!
|
||||
# TODO: twisted iso might have to IMAGINE_TWIST or whatever
|
||||
F = GF(2^255-19)
|
||||
d = F(-121665)
|
||||
a = F(1)
|
||||
i = sqrt(F(-1))
|
||||
qnr = i
|
||||
magic = isqrt(a*d-1)
|
||||
cofactor = 8
|
||||
encLen = 32
|
||||
isoMagic = Ed25519Point.magic
|
||||
isoA = Ed25519Point.a
|
||||
|
||||
@classmethod
|
||||
def base(cls):
|
||||
return cls.decodeSpec(Ed25519Point.base().encode())
|
||||
|
||||
class TestFailedException(Exception): pass
|
||||
|
||||
def test(cls,n):
|
||||
print "Testing curve %s" % cls.__name__
|
||||
|
||||
specials = [1]
|
||||
ii = cls.F(-1)
|
||||
while is_square(ii):
|
||||
specials.append(ii)
|
||||
ii = sqrt(ii)
|
||||
specials.append(ii)
|
||||
for i in specials:
|
||||
if negative(cls.F(i)): i = -i
|
||||
i = enc_le(i,cls.encLen)
|
||||
try:
|
||||
Q = cls.decode(i)
|
||||
QE = Q.encode()
|
||||
if QE != i:
|
||||
raise TestFailedException("Round trip special %s != %s" %
|
||||
(binascii.hexlify(QE),binascii.hexlify(i)))
|
||||
except NotOnCurveException: pass
|
||||
except InvalidEncodingException: pass
|
||||
|
||||
|
||||
P = cls.base()
|
||||
print "base", list(P.encode())
|
||||
for i in xrange(16):
|
||||
Q = P*i
|
||||
print i, list(Q.encode())
|
||||
|
||||
Q = cls()
|
||||
for i in xrange(n):
|
||||
#print i, binascii.hexlify(Q.encode())
|
||||
QQ = cls.decode(Q.encode())
|
||||
if QQ != Q: raise TestFailedException("Round trip %s != %s" % (str(QQ),str(Q)))
|
||||
|
||||
QT = Q
|
||||
QE = Q.encode()
|
||||
for h in xrange(cls.cofactor):
|
||||
QT = QT.torque()
|
||||
if QT.encode() != QE:
|
||||
raise TestFailedException("Can't torque %s,%d" % (str(Q),h+1))
|
||||
|
||||
Q0 = Q + P
|
||||
if Q0 == Q: raise TestFailedException("Addition doesn't work")
|
||||
if Q0-P != Q: raise TestFailedException("Subtraction doesn't work")
|
||||
|
||||
r = randint(1,1000)
|
||||
Q1 = Q0*r
|
||||
Q2 = Q0*(r+1)
|
||||
if Q1 + Q0 != Q2: raise TestFailedException("Scalarmul doesn't work")
|
||||
Q = Q1
|
||||
|
||||
test(Ed25519Point,100)
|
||||
#test(NegEd25519Point,100)
|
||||
#test(IsoEd25519Point,100)
|
||||
#test(IsoEd448Point,100)
|
||||
#test(TwistedEd448GoldilocksPoint,100)
|
||||
#test(Ed448GoldilocksPoint,100)
|
||||
|
||||
|
||||
def testElligator(cls,n):
|
||||
print "Testing elligator on %s" % cls.__name__
|
||||
for i in xrange(n):
|
||||
r = randombytes(cls.encLen)
|
||||
Q = cls.elligator(r)
|
||||
print list(r), list(Q.encode())
|
||||
|
||||
testElligator(Ed25519Point,100)
|
||||
#testElligator(NegEd25519Point,100)
|
||||
#testElligator(IsoEd448Point,100)
|
||||
#testElligator(Ed448GoldilocksPoint,100)
|
||||
#testElligator(TwistedEd448GoldilocksPoint,100)
|
||||
|
||||
def gangtest(classes,n):
|
||||
print "Gang test",[cls.__name__ for cls in classes]
|
||||
specials = [1]
|
||||
ii = classes[0].F(-1)
|
||||
while is_square(ii):
|
||||
specials.append(ii)
|
||||
ii = sqrt(ii)
|
||||
specials.append(ii)
|
||||
|
||||
for i in xrange(n):
|
||||
rets = [bytes((cls.base()*i).encode()) for cls in classes]
|
||||
if len(set(rets)) != 1:
|
||||
print "Divergence in encode at %d" % i
|
||||
for c,ret in zip(classes,rets):
|
||||
print c,binascii.hexlify(ret)
|
||||
print
|
||||
|
||||
if i < len(specials): r0 = enc_le(specials[i],classes[0].encLen)
|
||||
else: r0 = randombytes(classes[0].encLen)
|
||||
|
||||
rets = [bytes((cls.elligator(r0)*i).encode()) for cls in classes]
|
||||
if len(set(rets)) != 1:
|
||||
print "Divergence in elligator at %d" % i
|
||||
for c,ret in zip(classes,rets):
|
||||
print c,binascii.hexlify(ret)
|
||||
print
|
||||
gangtest([IsoEd448Point,TwistedEd448GoldilocksPoint,Ed448GoldilocksPoint],100)
|
||||
gangtest([Ed25519Point,IsoEd25519Point],100)
|
||||
Loading…
Reference in a new issue