mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 21:19:24 +00:00
Compare commits
5 Commits
f2d399ba1e
...
5526b8d439
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5526b8d439 | ||
|
|
beac35c119 | ||
|
|
62bb75e09a | ||
|
|
45bd376c08 | ||
|
|
da190759a9 |
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -2875,7 +2875,7 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
[[package]]
|
||||
name = "ec-divisors"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=59e3ae73b51c214afbc304efca8d748b3da62977#59e3ae73b51c214afbc304efca8d748b3da62977"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962"
|
||||
dependencies = [
|
||||
"dalek-ff-group",
|
||||
"ff",
|
||||
@@ -2990,7 +2990,6 @@ dependencies = [
|
||||
"ciphersuite 0.4.2",
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"ec-divisors",
|
||||
"ff-group-tests",
|
||||
"generalized-bulletproofs-ec-gadgets",
|
||||
"generic-array 1.2.0",
|
||||
@@ -3728,7 +3727,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "generalized-bulletproofs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=59e3ae73b51c214afbc304efca8d748b3da62977#59e3ae73b51c214afbc304efca8d748b3da62977"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"ciphersuite 0.4.99",
|
||||
@@ -3743,7 +3742,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "generalized-bulletproofs-circuit-abstraction"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=59e3ae73b51c214afbc304efca8d748b3da62977#59e3ae73b51c214afbc304efca8d748b3da62977"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962"
|
||||
dependencies = [
|
||||
"ciphersuite 0.4.99",
|
||||
"generalized-bulletproofs",
|
||||
@@ -3754,7 +3753,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "generalized-bulletproofs-ec-gadgets"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=59e3ae73b51c214afbc304efca8d748b3da62977#59e3ae73b51c214afbc304efca8d748b3da62977"
|
||||
source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962"
|
||||
dependencies = [
|
||||
"ciphersuite 0.4.99",
|
||||
"generalized-bulletproofs-circuit-abstraction",
|
||||
@@ -9543,14 +9542,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"ciphersuite 0.4.2",
|
||||
"ec-divisors",
|
||||
"ff-group-tests",
|
||||
"generalized-bulletproofs-ec-gadgets",
|
||||
"generic-array 1.2.0",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"k256",
|
||||
"prime-field",
|
||||
"rand_core 0.6.4",
|
||||
"short-weierstrass",
|
||||
"std-shims",
|
||||
]
|
||||
|
||||
@@ -10927,6 +10927,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
name = "short-weierstrass"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ec-divisors",
|
||||
"ff",
|
||||
"group",
|
||||
"rand_core 0.6.4",
|
||||
|
||||
@@ -40,6 +40,8 @@ ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
|
||||
[features]
|
||||
alloc = ["std-shims"]
|
||||
std = [
|
||||
"alloc",
|
||||
|
||||
"std-shims/std",
|
||||
|
||||
"rand_core/std",
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use core::fmt::Debug;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(unused_imports)]
|
||||
use std_shims::prelude::*;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
use std_shims::io::{self, Read};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
@@ -23,7 +23,7 @@ use group::{
|
||||
Group, GroupOps,
|
||||
prime::PrimeGroup,
|
||||
};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
use group::GroupEncoding;
|
||||
|
||||
/// Unified trait defining a ciphersuite around an elliptic curve.
|
||||
@@ -69,7 +69,7 @@ pub trait Ciphersuite:
|
||||
}
|
||||
|
||||
/// Read a canonical scalar from something implementing std::io::Read.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_F<R: Read>(reader: &mut R) -> io::Result<Self::F> {
|
||||
let mut encoding = <Self::F as PrimeField>::Repr::default();
|
||||
@@ -86,7 +86,7 @@ pub trait Ciphersuite:
|
||||
///
|
||||
/// The provided implementation is safe so long as `GroupEncoding::to_bytes` always returns a
|
||||
/// canonical serialization.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
||||
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
|
||||
|
||||
@@ -34,17 +34,17 @@ generic-array = { version = "1", default-features = false, features = ["alloc"]
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
rand_chacha = { version = "0.3", default-features = false }
|
||||
|
||||
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
|
||||
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
|
||||
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
|
||||
|
||||
dkg = { path = ".." }
|
||||
|
||||
ciphersuite-kp256 = { path = "../../ciphersuite/kp256", default-features = false, optional = true }
|
||||
secq256k1 = { path = "../../evrf/secq256k1", optional = true }
|
||||
dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, optional = true }
|
||||
embedwards25519 = { path = "../../evrf/embedwards25519", optional = true }
|
||||
embedwards25519 = { path = "../../evrf/embedwards25519", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
||||
@@ -52,7 +52,7 @@ rand = { version = "0.8", default-features = false, features = ["std"] }
|
||||
ciphersuite = { path = "../../ciphersuite", default-features = false, features = ["std"] }
|
||||
embedwards25519 = { path = "../../evrf/embedwards25519", default-features = false, features = ["std"] }
|
||||
dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, features = ["std"] }
|
||||
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", features = ["tests"] }
|
||||
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", features = ["tests"] }
|
||||
dkg-recovery = { path = "../recovery" }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -3,7 +3,7 @@ use core::marker::PhantomData;
|
||||
use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray};
|
||||
|
||||
use generalized_bulletproofs_circuit_abstraction::Variable;
|
||||
use generalized_bulletproofs_ec_gadgets::{DiscreteLogParameters, Divisor, PointWithDlog};
|
||||
use generalized_bulletproofs_ec_gadgets::{DiscreteLogParameter, Divisor, PointWithDlog};
|
||||
|
||||
use crate::Curves;
|
||||
|
||||
@@ -50,11 +50,11 @@ impl Tape {
|
||||
Note the `x` coefficients are only from the power of two, and `i >= 1`.
|
||||
*/
|
||||
let dlog =
|
||||
self.read_from_tape::<<C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>();
|
||||
self.read_from_tape::<<C::EmbeddedCurveParameters as DiscreteLogParameter>::ScalarBits>();
|
||||
|
||||
struct PointIterator<'a, C: Curves>(
|
||||
&'a mut Tape,
|
||||
GenericArray<Variable, <C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>,
|
||||
GenericArray<Variable, <C::EmbeddedCurveParameters as DiscreteLogParameter>::ScalarBits>,
|
||||
PhantomData<C>,
|
||||
);
|
||||
impl<'a, C: Curves> Iterator for PointIterator<'a, C> {
|
||||
|
||||
@@ -23,12 +23,14 @@ zeroize = { version = "^1.5", default-features = false, features = ["zeroize_der
|
||||
generic-array = { version = "1", default-features = false }
|
||||
|
||||
prime-field = { path = "../../prime-field", default-features = false }
|
||||
short-weierstrass = { path = "../../short-weierstrass", default-features = false }
|
||||
curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] }
|
||||
dalek-ff-group = { path = "../../dalek-ff-group", version = "0.4", default-features = false }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
|
||||
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4"
|
||||
@@ -38,6 +40,6 @@ rand_core = { version = "0.6", features = ["std"] }
|
||||
ff-group-tests = { path = "../../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims", "zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"]
|
||||
std = ["std-shims/std", "zeroize/std", "prime-field/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
alloc = ["std-shims", "zeroize/alloc", "prime-field/alloc", "short-weierstrass/alloc", "curve25519-dalek/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
|
||||
std = ["alloc", "std-shims/std", "zeroize/std", "prime-field/std", "short-weierstrass/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -7,12 +7,17 @@ use std_shims::prelude::*;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use std_shims::io::{self, Read};
|
||||
|
||||
use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2};
|
||||
use prime_field::{subtle::Choice, zeroize::Zeroize};
|
||||
use ciphersuite::group::{
|
||||
ff::{PrimeField, FromUniformBytes},
|
||||
ff::{Field, PrimeField, FromUniformBytes},
|
||||
Group,
|
||||
};
|
||||
|
||||
use curve25519_dalek::Scalar as DalekScalar;
|
||||
pub use dalek_ff_group::Scalar as FieldElement;
|
||||
|
||||
use short_weierstrass::{ShortWeierstrass, Affine, Projective};
|
||||
|
||||
prime_field::odd_prime_field!(
|
||||
Scalar,
|
||||
"0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b",
|
||||
@@ -20,33 +25,57 @@ prime_field::odd_prime_field!(
|
||||
false
|
||||
);
|
||||
|
||||
pub use dalek_ff_group::Scalar as FieldElement;
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Embedwards25519;
|
||||
|
||||
mod point;
|
||||
pub use point::Point;
|
||||
#[allow(deprecated)] // No other way to construct arbitrary `FieldElement` at compile-time :/
|
||||
impl ShortWeierstrass for Embedwards25519 {
|
||||
type FieldElement = FieldElement;
|
||||
const A: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
|
||||
"ead3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"
|
||||
)));
|
||||
const B: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
|
||||
"5f07603a853f20370b682036210d463e64903a23ea669d07ca26cfc13f594209"
|
||||
)));
|
||||
const PRIME_ORDER: bool = true;
|
||||
const GENERATOR: Affine<Self> = Affine::from_xy_unchecked(
|
||||
FieldElement::ONE,
|
||||
FieldElement(DalekScalar::from_bits(hex_literal::hex!(
|
||||
"2e4118080a484a3dfbafe2199a0e36b7193581d676c0dadfa376b0265616020c"
|
||||
))),
|
||||
);
|
||||
type Scalar = Scalar;
|
||||
|
||||
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
|
||||
use core::hint::black_box;
|
||||
use prime_field::zeroize::Zeroize;
|
||||
type Repr = [u8; 32];
|
||||
// Use an all-zero encoding for the identity as `0` isn't the `x` coordinate of an on-curve point
|
||||
const IDENTITY: [u8; 32] = [0; 32];
|
||||
fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr {
|
||||
// The LE `x` coordinate, with if `y` is odd in the unused 256th bit
|
||||
let mut res = [0; 32];
|
||||
res.as_mut().copy_from_slice(x.to_repr().as_ref());
|
||||
res[31] |= odd_y.unwrap_u8() << 7;
|
||||
res
|
||||
}
|
||||
fn decode_compressed(bytes: &Self::Repr) -> (<Self::FieldElement as PrimeField>::Repr, Choice) {
|
||||
// Extract and clear the sign bit
|
||||
let mut bytes = *bytes;
|
||||
let odd_y = Choice::from(bytes[31] >> 7);
|
||||
bytes[31] &= u8::MAX >> 1;
|
||||
|
||||
let bit_ref = black_box(bit_ref);
|
||||
// Copy from the point's representation to the field's
|
||||
let mut repr = <Self::FieldElement as PrimeField>::Repr::default();
|
||||
repr.as_mut().copy_from_slice(&bytes);
|
||||
|
||||
let mut bit = black_box(*bit_ref);
|
||||
let res = black_box(u8::from(bit));
|
||||
bit.zeroize();
|
||||
debug_assert!((res | 1) == 1);
|
||||
|
||||
bit_ref.zeroize();
|
||||
res
|
||||
(repr, odd_y)
|
||||
}
|
||||
// No points have a torsion element as this a prime-order curve
|
||||
fn has_torsion_element(_point: Projective<Self>) -> Choice {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ciphersuite for Embedwards25519.
|
||||
///
|
||||
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
|
||||
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
|
||||
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, zeroize::Zeroize)]
|
||||
pub struct Embedwards25519;
|
||||
pub type Point = Projective<Embedwards25519>;
|
||||
|
||||
impl ciphersuite::Ciphersuite for Embedwards25519 {
|
||||
type F = Scalar;
|
||||
type G = Point;
|
||||
@@ -58,6 +87,10 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
|
||||
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
|
||||
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
|
||||
/// substrings of each other.
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
use blake2::Digest;
|
||||
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(
|
||||
@@ -67,7 +100,7 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
|
||||
|
||||
// We override the provided impl, which compares against the reserialization, because
|
||||
// we already require canonicity
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
@@ -81,9 +114,38 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
|
||||
}
|
||||
}
|
||||
|
||||
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameters for Embedwards25519 {
|
||||
type ScalarBits = U<{ Scalar::NUM_BITS as usize }>;
|
||||
type XCoefficients = Quot<Sum<Self::ScalarBits, U1>, U2>;
|
||||
type XCoefficientsMinusOne = Diff<Self::XCoefficients, U1>;
|
||||
type YxCoefficients = Diff<Quot<Sum<Sum<Self::ScalarBits, U1>, U1>, U2>, U2>;
|
||||
#[cfg(feature = "alloc")]
|
||||
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameter for Embedwards25519 {
|
||||
type ScalarBits = generic_array::typenum::U<{ <Scalar as PrimeField>::NUM_BITS as usize }>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_curve() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator() {
|
||||
use ciphersuite::group::{Group, GroupEncoding};
|
||||
assert_eq!(
|
||||
Point::generator(),
|
||||
Point::from_bytes(&hex_literal::hex!(
|
||||
"0100000000000000000000000000000000000000000000000000000000000000"
|
||||
))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_x_is_off_curve() {
|
||||
assert!(bool::from(
|
||||
Affine::<Embedwards25519>::decompress(FieldElement::ZERO, 1.into()).is_none()
|
||||
));
|
||||
}
|
||||
|
||||
// Checks random won't infinitely loop
|
||||
#[test]
|
||||
fn random() {
|
||||
use ciphersuite::group::Group;
|
||||
Point::random(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
@@ -1,441 +0,0 @@
|
||||
use core::{
|
||||
ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
|
||||
iter::Sum,
|
||||
};
|
||||
|
||||
use prime_field::{
|
||||
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable},
|
||||
zeroize::Zeroize,
|
||||
rand_core::RngCore,
|
||||
};
|
||||
|
||||
use ciphersuite::group::{
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
Group, GroupEncoding,
|
||||
prime::PrimeGroup,
|
||||
};
|
||||
|
||||
use crate::{u8_from_bool, Scalar, FieldElement};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn B() -> FieldElement {
|
||||
FieldElement::from_repr(hex_literal::hex!(
|
||||
"5f07603a853f20370b682036210d463e64903a23ea669d07ca26cfc13f594209"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn recover_y(x: FieldElement) -> CtOption<FieldElement> {
|
||||
// x**3 - 3 * x + B
|
||||
((x.square() * x) - (x.double() + x) + B()).sqrt()
|
||||
}
|
||||
|
||||
/// Point.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Point {
|
||||
x: FieldElement, // / Z
|
||||
y: FieldElement, // / Z
|
||||
z: FieldElement,
|
||||
}
|
||||
|
||||
impl Zeroize for Point {
|
||||
fn zeroize(&mut self) {
|
||||
self.x.zeroize();
|
||||
self.y.zeroize();
|
||||
self.z.zeroize();
|
||||
let identity = Self::identity();
|
||||
self.x = identity.x;
|
||||
self.y = identity.y;
|
||||
self.z = identity.z;
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for Point {
|
||||
fn ct_eq(&self, other: &Self) -> Choice {
|
||||
let x1 = self.x * other.z;
|
||||
let x2 = other.x * self.z;
|
||||
|
||||
let y1 = self.y * other.z;
|
||||
let y2 = other.y * self.z;
|
||||
|
||||
// Both identity or equivalent over their denominators
|
||||
(self.z.is_zero() & other.z.is_zero()) | (x1.ct_eq(&x2) & y1.ct_eq(&y2))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Point {
|
||||
fn eq(&self, other: &Point) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
impl ConditionallySelectable for Point {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||
Point {
|
||||
x: FieldElement::conditional_select(&a.x, &b.x, choice),
|
||||
y: FieldElement::conditional_select(&a.y, &b.y, choice),
|
||||
z: FieldElement::conditional_select(&a.z, &b.z, choice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Point;
|
||||
#[allow(non_snake_case)]
|
||||
fn add(self, other: Self) -> Self {
|
||||
// add-2015-rcb
|
||||
|
||||
let a = -FieldElement::from(3u64);
|
||||
let B = B();
|
||||
let b3 = B + B + B;
|
||||
|
||||
let X1 = self.x;
|
||||
let Y1 = self.y;
|
||||
let Z1 = self.z;
|
||||
let X2 = other.x;
|
||||
let Y2 = other.y;
|
||||
let Z2 = other.z;
|
||||
|
||||
let t0 = X1 * X2;
|
||||
let t1 = Y1 * Y2;
|
||||
let t2 = Z1 * Z2;
|
||||
let t3 = X1 + Y1;
|
||||
let t4 = X2 + Y2;
|
||||
let t3 = t3 * t4;
|
||||
let t4 = t0 + t1;
|
||||
let t3 = t3 - t4;
|
||||
let t4 = X1 + Z1;
|
||||
let t5 = X2 + Z2;
|
||||
let t4 = t4 * t5;
|
||||
let t5 = t0 + t2;
|
||||
let t4 = t4 - t5;
|
||||
let t5 = Y1 + Z1;
|
||||
let X3 = Y2 + Z2;
|
||||
let t5 = t5 * X3;
|
||||
let X3 = t1 + t2;
|
||||
let t5 = t5 - X3;
|
||||
let Z3 = a * t4;
|
||||
let X3 = b3 * t2;
|
||||
let Z3 = X3 + Z3;
|
||||
let X3 = t1 - Z3;
|
||||
let Z3 = t1 + Z3;
|
||||
let Y3 = X3 * Z3;
|
||||
let t1 = t0 + t0;
|
||||
let t1 = t1 + t0;
|
||||
let t2 = a * t2;
|
||||
let t4 = b3 * t4;
|
||||
let t1 = t1 + t2;
|
||||
let t2 = t0 - t2;
|
||||
let t2 = a * t2;
|
||||
let t4 = t4 + t2;
|
||||
let t0 = t1 * t4;
|
||||
let Y3 = Y3 + t0;
|
||||
let t0 = t5 * t4;
|
||||
let X3 = t3 * X3;
|
||||
let X3 = X3 - t0;
|
||||
let t0 = t3 * t1;
|
||||
let Z3 = t5 * Z3;
|
||||
let Z3 = Z3 + t0;
|
||||
Point { x: X3, y: Y3, z: Z3 }
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Point {
|
||||
fn add_assign(&mut self, other: Point) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Point> for Point {
|
||||
type Output = Point;
|
||||
fn add(self, other: &Point) -> Point {
|
||||
self + *other
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Point> for Point {
|
||||
fn add_assign(&mut self, other: &Point) {
|
||||
*self += *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Point {
|
||||
type Output = Point;
|
||||
fn neg(self) -> Self {
|
||||
Point { x: self.x, y: -self.y, z: self.z }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Point {
|
||||
type Output = Point;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn sub(self, other: Self) -> Self {
|
||||
self + other.neg()
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Point {
|
||||
fn sub_assign(&mut self, other: Point) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&Point> for Point {
|
||||
type Output = Point;
|
||||
fn sub(self, other: &Point) -> Point {
|
||||
self - *other
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<&Point> for Point {
|
||||
fn sub_assign(&mut self, other: &Point) {
|
||||
*self -= *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Group for Point {
|
||||
type Scalar = Scalar;
|
||||
fn random(mut rng: impl RngCore) -> Self {
|
||||
loop {
|
||||
let mut bytes = [0; 32];
|
||||
rng.fill_bytes(bytes.as_mut());
|
||||
let opt = Self::from_bytes(&bytes);
|
||||
if opt.is_some().into() {
|
||||
return opt.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn identity() -> Self {
|
||||
Point { x: FieldElement::ZERO, y: FieldElement::ONE, z: FieldElement::ZERO }
|
||||
}
|
||||
fn generator() -> Self {
|
||||
// Point with the lowest valid x-coordinate
|
||||
Point {
|
||||
x: FieldElement::from_repr(hex_literal::hex!(
|
||||
"0100000000000000000000000000000000000000000000000000000000000000"
|
||||
))
|
||||
.unwrap(),
|
||||
y: FieldElement::from_repr(hex_literal::hex!(
|
||||
"2e4118080a484a3dfbafe2199a0e36b7193581d676c0dadfa376b0265616020c"
|
||||
))
|
||||
.unwrap(),
|
||||
z: FieldElement::ONE,
|
||||
}
|
||||
}
|
||||
fn is_identity(&self) -> Choice {
|
||||
self.z.ct_eq(&FieldElement::ZERO)
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
fn double(&self) -> Self {
|
||||
// dbl-2007-bl-2
|
||||
let X1 = self.x;
|
||||
let Y1 = self.y;
|
||||
let Z1 = self.z;
|
||||
|
||||
let w = (X1 - Z1) * (X1 + Z1);
|
||||
let w = w.double() + w;
|
||||
let s = (Y1 * Z1).double();
|
||||
let ss = s.square();
|
||||
let sss = s * ss;
|
||||
let R = Y1 * s;
|
||||
let RR = R.square();
|
||||
let B_ = (X1 * R).double();
|
||||
let h = w.square() - B_.double();
|
||||
let X3 = h * s;
|
||||
let Y3 = w * (B_ - h) - RR.double();
|
||||
let Z3 = sss;
|
||||
|
||||
let res = Self { x: X3, y: Y3, z: Z3 };
|
||||
// If self is identity, res will not be well-formed
|
||||
// Accordingly, we return self if self was the identity
|
||||
Self::conditional_select(&res, self, self.is_identity())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum<Point> for Point {
|
||||
fn sum<I: Iterator<Item = Point>>(iter: I) -> Point {
|
||||
let mut res = Self::identity();
|
||||
for i in iter {
|
||||
res += i;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum<&'a Point> for Point {
|
||||
fn sum<I: Iterator<Item = &'a Point>>(iter: I) -> Point {
|
||||
Point::sum(iter.cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for Point {
|
||||
type Output = Point;
|
||||
fn mul(self, mut other: Scalar) -> Point {
|
||||
// Precompute the optimal amount that's a multiple of 2
|
||||
let mut table = [Point::identity(); 16];
|
||||
table[1] = self;
|
||||
for i in 2 .. 16 {
|
||||
table[i] = table[i - 1] + self;
|
||||
}
|
||||
|
||||
let mut res = Self::identity();
|
||||
let mut bits = 0;
|
||||
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
|
||||
bits <<= 1;
|
||||
let mut bit = u8_from_bool(bit.deref_mut());
|
||||
bits |= bit;
|
||||
bit.zeroize();
|
||||
|
||||
if ((i + 1) % 4) == 0 {
|
||||
if i != 3 {
|
||||
for _ in 0 .. 4 {
|
||||
res = res.double();
|
||||
}
|
||||
}
|
||||
|
||||
let mut term = table[0];
|
||||
for (j, candidate) in table[1 ..].iter().enumerate() {
|
||||
let j = j + 1;
|
||||
term = Self::conditional_select(&term, candidate, usize::from(bits).ct_eq(&j));
|
||||
}
|
||||
res += term;
|
||||
bits = 0;
|
||||
}
|
||||
}
|
||||
other.zeroize();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Scalar> for Point {
|
||||
fn mul_assign(&mut self, other: Scalar) {
|
||||
*self = *self * other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<&Scalar> for Point {
|
||||
type Output = Point;
|
||||
fn mul(self, other: &Scalar) -> Point {
|
||||
self * *other
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<&Scalar> for Point {
|
||||
fn mul_assign(&mut self, other: &Scalar) {
|
||||
*self *= *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupEncoding for Point {
|
||||
type Repr = [u8; 32];
|
||||
|
||||
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
|
||||
// Extract and clear the sign bit
|
||||
let mut bytes = *bytes;
|
||||
let sign = Choice::from(bytes[31] >> 7);
|
||||
bytes[31] &= u8::MAX >> 1;
|
||||
|
||||
// Parse x, recover y
|
||||
FieldElement::from_repr(bytes).and_then(|x| {
|
||||
let is_identity = x.is_zero();
|
||||
|
||||
let y = recover_y(x).map(|mut y| {
|
||||
y = <_>::conditional_select(&y, &-y, y.is_odd().ct_eq(&!sign));
|
||||
y
|
||||
});
|
||||
|
||||
// If this the identity, set y to 1
|
||||
let y =
|
||||
CtOption::conditional_select(&y, &CtOption::new(FieldElement::ONE, 1.into()), is_identity);
|
||||
// If this the identity, set y to 1 and z to 0 (instead of 1)
|
||||
let z = <_>::conditional_select(&FieldElement::ONE, &FieldElement::ZERO, is_identity);
|
||||
// Create the point if we have a y solution
|
||||
let point = y.map(|y| Point { x, y, z });
|
||||
|
||||
let not_negative_zero = !(is_identity & sign);
|
||||
// Only return the point if it isn't -0
|
||||
CtOption::conditional_select(
|
||||
&CtOption::new(Point::identity(), 0.into()),
|
||||
&point,
|
||||
not_negative_zero,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
|
||||
Point::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Self::Repr {
|
||||
let Some(z) = Option::<FieldElement>::from(self.z.invert()) else {
|
||||
return [0; 32];
|
||||
};
|
||||
let x = self.x * z;
|
||||
let y = self.y * z;
|
||||
|
||||
let mut res = [0; 32];
|
||||
res.as_mut().copy_from_slice(&x.to_repr());
|
||||
|
||||
// The following conditional select normalizes the sign to 0 when x is 0
|
||||
let y_sign = u8::conditional_select(&y.is_odd().unwrap_u8(), &0, x.ct_eq(&FieldElement::ZERO));
|
||||
res[31] |= y_sign << 7;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimeGroup for Point {}
|
||||
|
||||
impl ec_divisors::DivisorCurve for Point {
|
||||
type FieldElement = FieldElement;
|
||||
type XyPoint = ec_divisors::Projective<Self>;
|
||||
|
||||
fn interpolator_for_scalar_mul() -> &'static ec_divisors::Interpolator<Self::FieldElement> {
|
||||
static PRECOMPUTE: std_shims::sync::LazyLock<ec_divisors::Interpolator<FieldElement>> =
|
||||
std_shims::sync::LazyLock::new(|| {
|
||||
ec_divisors::Interpolator::new(usize::try_from(130).unwrap())
|
||||
});
|
||||
&PRECOMPUTE
|
||||
}
|
||||
|
||||
fn a() -> Self::FieldElement {
|
||||
-FieldElement::from(3u64)
|
||||
}
|
||||
fn b() -> Self::FieldElement {
|
||||
B()
|
||||
}
|
||||
|
||||
fn to_xy(point: Self) -> Option<(Self::FieldElement, Self::FieldElement)> {
|
||||
let z: Self::FieldElement = Option::from(point.z.invert())?;
|
||||
Some((point.x * z, point.y * z))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_curve() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator() {
|
||||
assert_eq!(
|
||||
Point::generator(),
|
||||
Point::from_bytes(&hex_literal::hex!(
|
||||
"0100000000000000000000000000000000000000000000000000000000000000"
|
||||
))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_x_is_invalid() {
|
||||
assert!(Option::<FieldElement>::from(recover_y(FieldElement::ZERO)).is_none());
|
||||
}
|
||||
|
||||
// Checks random won't infinitely loop
|
||||
#[test]
|
||||
fn random() {
|
||||
Point::random(&mut rand_core::OsRng);
|
||||
}
|
||||
@@ -18,13 +18,14 @@ hex-literal = { version = "0.4", default-features = false }
|
||||
|
||||
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true }
|
||||
|
||||
generic-array = { version = "1", default-features = false }
|
||||
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
|
||||
prime-field = { path = "../../prime-field", default-features = false }
|
||||
short-weierstrass = { path = "../../short-weierstrass", default-features = false }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
|
||||
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "59e3ae73b51c214afbc304efca8d748b3da62977", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4"
|
||||
@@ -34,6 +35,6 @@ rand_core = { version = "0.6", features = ["std"] }
|
||||
ff-group-tests = { path = "../../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims", "k256/alloc", "prime-field/alloc", "ciphersuite/alloc"]
|
||||
std = ["std-shims/std", "k256/std", "prime-field/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
alloc = ["std-shims", "generic-array/alloc", "k256/alloc", "prime-field/alloc", "short-weierstrass/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
|
||||
std = ["alloc", "std-shims/std", "k256/std", "prime-field/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -4,16 +4,22 @@
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std_shims::prelude::*;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
use std_shims::io::{self, Read};
|
||||
|
||||
// Doesn't use the `generic-array 0.14` exported by `k256::elliptic_curve` as we need `1.0`
|
||||
use generic_array::{
|
||||
typenum::{U, U33},
|
||||
GenericArray,
|
||||
};
|
||||
use k256::elliptic_curve::{
|
||||
subtle::{Choice, ConstantTimeEq, ConditionallySelectable},
|
||||
zeroize::Zeroize,
|
||||
generic_array::typenum::{Sum, Diff, Quot, U, U1, U2},
|
||||
group::{
|
||||
ff::{PrimeField, FromUniformBytes},
|
||||
Group,
|
||||
},
|
||||
sec1::Tag,
|
||||
};
|
||||
|
||||
prime_field::odd_prime_field!(
|
||||
@@ -25,34 +31,87 @@ prime_field::odd_prime_field!(
|
||||
|
||||
pub use k256::Scalar as FieldElement;
|
||||
|
||||
mod point;
|
||||
pub use point::Point;
|
||||
use short_weierstrass::{ShortWeierstrass, Affine, Projective};
|
||||
|
||||
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
|
||||
use core::hint::black_box;
|
||||
use prime_field::zeroize::Zeroize;
|
||||
|
||||
let bit_ref = black_box(bit_ref);
|
||||
|
||||
let mut bit = black_box(*bit_ref);
|
||||
let res = black_box(u8::from(bit));
|
||||
bit.zeroize();
|
||||
debug_assert!((res | 1) == 1);
|
||||
|
||||
bit_ref.zeroize();
|
||||
res
|
||||
}
|
||||
|
||||
/// Ciphersuite for Secq256k1.
|
||||
///
|
||||
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
|
||||
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
|
||||
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Secq256k1;
|
||||
impl Zeroize for Secq256k1 {
|
||||
fn zeroize(&mut self) {}
|
||||
}
|
||||
|
||||
impl ShortWeierstrass for Secq256k1 {
|
||||
type FieldElement = FieldElement;
|
||||
const A: FieldElement = FieldElement::ZERO;
|
||||
const B: FieldElement = {
|
||||
let two = FieldElement::ONE.add(&FieldElement::ONE);
|
||||
let four = two.add(&two);
|
||||
let six = four.add(&two);
|
||||
six.add(&FieldElement::ONE)
|
||||
};
|
||||
const PRIME_ORDER: bool = true;
|
||||
const GENERATOR: Affine<Self> = Affine::from_xy_unchecked(FieldElement::ONE, {
|
||||
let y_be =
|
||||
hex_literal::hex!("0c7c97045a2074634909abdf82c9bd0248916189041f2af0c1b800d1ffc278c0");
|
||||
let mut res = FieldElement::ZERO;
|
||||
let mut i = 0;
|
||||
while i < 32 {
|
||||
let mut j = 0;
|
||||
while j < 8 {
|
||||
// Shift over the existing result
|
||||
res = res.add(&res);
|
||||
// Add this bit, if set
|
||||
if ((y_be[i] >> (8 - 1 - j)) & 1) == 1 {
|
||||
res = res.add(&FieldElement::ONE);
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
res
|
||||
});
|
||||
type Scalar = Scalar;
|
||||
|
||||
type Repr = GenericArray<u8, U33>;
|
||||
/// Use the SEC1-encoded identity point, which happens to be all zeroes
|
||||
const IDENTITY: Self::Repr = GenericArray::from_array([0; 33]);
|
||||
fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr {
|
||||
let mut res = GenericArray::default();
|
||||
res[0] =
|
||||
<_>::conditional_select(&(Tag::CompressedEvenY as u8), &(Tag::CompressedOddY as u8), odd_y);
|
||||
{
|
||||
let res: &mut [u8] = res.as_mut();
|
||||
res[1 ..].copy_from_slice(x.to_repr().as_ref());
|
||||
}
|
||||
res
|
||||
}
|
||||
fn decode_compressed(bytes: &Self::Repr) -> (<Self::FieldElement as PrimeField>::Repr, Choice) {
|
||||
// Parse out if `y` is odd
|
||||
let odd_y = bytes[0].ct_eq(&(Tag::CompressedOddY as u8));
|
||||
// Check if the tag was malleated
|
||||
let expected_tag =
|
||||
<_>::conditional_select(&(Tag::CompressedEvenY as u8), &(Tag::CompressedOddY as u8), odd_y);
|
||||
let invalid = !bytes[0].ct_eq(&expected_tag);
|
||||
|
||||
// Copy the alleged `x` coordinate, overwriting with `0xffffff...` if the sign byte was
|
||||
// malleated (causing the `x` coordinate to be invalid)
|
||||
let mut x = <Self::FieldElement as PrimeField>::Repr::default();
|
||||
{
|
||||
let x: &mut [u8] = x.as_mut();
|
||||
for i in 0 .. 32 {
|
||||
x[i] = <_>::conditional_select(&bytes[1 + i], &u8::MAX, invalid);
|
||||
}
|
||||
}
|
||||
|
||||
(x, odd_y)
|
||||
}
|
||||
// No points have a torsion element as this a prime-order curve
|
||||
fn has_torsion_element(_point: Projective<Self>) -> Choice {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Point = Projective<Secq256k1>;
|
||||
|
||||
impl ciphersuite::Ciphersuite for Secq256k1 {
|
||||
type F = Scalar;
|
||||
type G = Point;
|
||||
@@ -64,6 +123,10 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
|
||||
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
|
||||
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
|
||||
/// substrings of each other.
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
use blake2::Digest;
|
||||
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(
|
||||
@@ -73,7 +136,7 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
|
||||
|
||||
// We override the provided impl, which compares against the reserialization, because
|
||||
// we already require canonicity
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
@@ -87,9 +150,35 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameters for Secq256k1 {
|
||||
#[cfg(feature = "alloc")]
|
||||
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameter for Secq256k1 {
|
||||
type ScalarBits = U<{ Scalar::NUM_BITS as usize }>;
|
||||
type XCoefficients = Quot<Sum<Self::ScalarBits, U1>, U2>;
|
||||
type XCoefficientsMinusOne = Diff<Self::XCoefficients, U1>;
|
||||
type YxCoefficients = Diff<Quot<Sum<Sum<Self::ScalarBits, U1>, U1>, U2>, U2>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_curve() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator() {
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
assert_eq!(
|
||||
Point::generator(),
|
||||
Point::from_bytes(GenericArray::from_slice(&hex_literal::hex!(
|
||||
"020000000000000000000000000000000000000000000000000000000000000001"
|
||||
)))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_x_is_off_curve() {
|
||||
assert!(bool::from(Affine::<Secq256k1>::decompress(FieldElement::ZERO, 1.into()).is_none()));
|
||||
}
|
||||
|
||||
// Checks random won't infinitely loop
|
||||
#[test]
|
||||
fn random() {
|
||||
Point::random(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
use core::{
|
||||
ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
|
||||
iter::Sum,
|
||||
};
|
||||
|
||||
use k256::elliptic_curve::{
|
||||
zeroize::Zeroize,
|
||||
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable},
|
||||
generic_array::{typenum::U33, GenericArray},
|
||||
rand_core::RngCore,
|
||||
group::{
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
Group, GroupEncoding,
|
||||
prime::PrimeGroup,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{u8_from_bool, Scalar, FieldElement};
|
||||
|
||||
fn recover_y(x: FieldElement) -> CtOption<FieldElement> {
|
||||
// x**3 + B since a = 0
|
||||
((x.square() * x) + FieldElement::from(7u64)).sqrt()
|
||||
}
|
||||
|
||||
/// Point.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Point {
|
||||
x: FieldElement, // / Z
|
||||
y: FieldElement, // / Z
|
||||
z: FieldElement,
|
||||
}
|
||||
|
||||
impl Zeroize for Point {
|
||||
fn zeroize(&mut self) {
|
||||
self.x.zeroize();
|
||||
self.y.zeroize();
|
||||
self.z.zeroize();
|
||||
let identity = Self::identity();
|
||||
self.x = identity.x;
|
||||
self.y = identity.y;
|
||||
self.z = identity.z;
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for Point {
|
||||
fn ct_eq(&self, other: &Self) -> Choice {
|
||||
let x1 = self.x * other.z;
|
||||
let x2 = other.x * self.z;
|
||||
|
||||
let y1 = self.y * other.z;
|
||||
let y2 = other.y * self.z;
|
||||
|
||||
// Identity or equivalent
|
||||
(self.z.is_zero() & other.z.is_zero()) | (x1.ct_eq(&x2) & y1.ct_eq(&y2))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Point {
|
||||
fn eq(&self, other: &Point) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
impl ConditionallySelectable for Point {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||
Point {
|
||||
x: FieldElement::conditional_select(&a.x, &b.x, choice),
|
||||
y: FieldElement::conditional_select(&a.y, &b.y, choice),
|
||||
z: FieldElement::conditional_select(&a.z, &b.z, choice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Point;
|
||||
#[allow(non_snake_case)]
|
||||
fn add(self, other: Self) -> Self {
|
||||
// add-2015-rcb
|
||||
|
||||
let a = FieldElement::ZERO;
|
||||
let B = FieldElement::from(7u64);
|
||||
let b3 = B + B + B;
|
||||
|
||||
let X1 = self.x;
|
||||
let Y1 = self.y;
|
||||
let Z1 = self.z;
|
||||
let X2 = other.x;
|
||||
let Y2 = other.y;
|
||||
let Z2 = other.z;
|
||||
|
||||
let t0 = X1 * X2;
|
||||
let t1 = Y1 * Y2;
|
||||
let t2 = Z1 * Z2;
|
||||
let t3 = X1 + Y1;
|
||||
let t4 = X2 + Y2;
|
||||
let t3 = t3 * t4;
|
||||
let t4 = t0 + t1;
|
||||
let t3 = t3 - t4;
|
||||
let t4 = X1 + Z1;
|
||||
let t5 = X2 + Z2;
|
||||
let t4 = t4 * t5;
|
||||
let t5 = t0 + t2;
|
||||
let t4 = t4 - t5;
|
||||
let t5 = Y1 + Z1;
|
||||
let X3 = Y2 + Z2;
|
||||
let t5 = t5 * X3;
|
||||
let X3 = t1 + t2;
|
||||
let t5 = t5 - X3;
|
||||
let Z3 = a * t4;
|
||||
let X3 = b3 * t2;
|
||||
let Z3 = X3 + Z3;
|
||||
let X3 = t1 - Z3;
|
||||
let Z3 = t1 + Z3;
|
||||
let Y3 = X3 * Z3;
|
||||
let t1 = t0 + t0;
|
||||
let t1 = t1 + t0;
|
||||
let t2 = a * t2;
|
||||
let t4 = b3 * t4;
|
||||
let t1 = t1 + t2;
|
||||
let t2 = t0 - t2;
|
||||
let t2 = a * t2;
|
||||
let t4 = t4 + t2;
|
||||
let t0 = t1 * t4;
|
||||
let Y3 = Y3 + t0;
|
||||
let t0 = t5 * t4;
|
||||
let X3 = t3 * X3;
|
||||
let X3 = X3 - t0;
|
||||
let t0 = t3 * t1;
|
||||
let Z3 = t5 * Z3;
|
||||
let Z3 = Z3 + t0;
|
||||
Point { x: X3, y: Y3, z: Z3 }
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Point {
|
||||
fn add_assign(&mut self, other: Point) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Point> for Point {
|
||||
type Output = Point;
|
||||
fn add(self, other: &Point) -> Point {
|
||||
self + *other
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Point> for Point {
|
||||
fn add_assign(&mut self, other: &Point) {
|
||||
*self += *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Point {
|
||||
type Output = Point;
|
||||
fn neg(self) -> Self {
|
||||
Point { x: self.x, y: -self.y, z: self.z }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Point {
|
||||
type Output = Point;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn sub(self, other: Self) -> Self {
|
||||
self + other.neg()
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Point {
|
||||
fn sub_assign(&mut self, other: Point) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&Point> for Point {
|
||||
type Output = Point;
|
||||
fn sub(self, other: &Point) -> Point {
|
||||
self - *other
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<&Point> for Point {
|
||||
fn sub_assign(&mut self, other: &Point) {
|
||||
*self -= *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Group for Point {
|
||||
type Scalar = Scalar;
|
||||
fn random(mut rng: impl RngCore) -> Self {
|
||||
loop {
|
||||
let mut bytes = GenericArray::default();
|
||||
rng.fill_bytes(bytes.as_mut());
|
||||
let opt = Self::from_bytes(&bytes);
|
||||
if opt.is_some().into() {
|
||||
return opt.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn identity() -> Self {
|
||||
Point { x: FieldElement::ZERO, y: FieldElement::ONE, z: FieldElement::ZERO }
|
||||
}
|
||||
fn generator() -> Self {
|
||||
// Point with the lowest valid x-coordinate
|
||||
Point {
|
||||
x: FieldElement::from_repr(
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
y: FieldElement::from_repr(
|
||||
hex_literal::hex!("0C7C97045A2074634909ABDF82C9BD0248916189041F2AF0C1B800D1FFC278C0")
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
z: FieldElement::ONE,
|
||||
}
|
||||
}
|
||||
fn is_identity(&self) -> Choice {
|
||||
self.z.ct_eq(&FieldElement::ZERO)
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
fn double(&self) -> Self {
|
||||
// dbl-2007-bl
|
||||
|
||||
let a = FieldElement::ZERO;
|
||||
|
||||
let X1 = self.x;
|
||||
let Y1 = self.y;
|
||||
let Z1 = self.z;
|
||||
|
||||
let XX = X1 * X1;
|
||||
let ZZ = Z1 * Z1;
|
||||
let w = (a * ZZ) + XX.double() + XX;
|
||||
let s = (Y1 * Z1).double();
|
||||
let ss = s * s;
|
||||
let sss = s * ss;
|
||||
let R = Y1 * s;
|
||||
let RR = R * R;
|
||||
let B = X1 + R;
|
||||
let B = (B * B) - XX - RR;
|
||||
let h = (w * w) - B.double();
|
||||
let X3 = h * s;
|
||||
let Y3 = w * (B - h) - RR.double();
|
||||
let Z3 = sss;
|
||||
|
||||
let res = Self { x: X3, y: Y3, z: Z3 };
|
||||
// If self is identity, res will not be well-formed
|
||||
// Accordingly, we return self if self was the identity
|
||||
Self::conditional_select(&res, self, self.is_identity())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum<Point> for Point {
|
||||
fn sum<I: Iterator<Item = Point>>(iter: I) -> Point {
|
||||
let mut res = Self::identity();
|
||||
for i in iter {
|
||||
res += i;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum<&'a Point> for Point {
|
||||
fn sum<I: Iterator<Item = &'a Point>>(iter: I) -> Point {
|
||||
Point::sum(iter.cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for Point {
|
||||
type Output = Point;
|
||||
fn mul(self, mut other: Scalar) -> Point {
|
||||
// Precompute the optimal amount that's a multiple of 2
|
||||
let mut table = [Point::identity(); 16];
|
||||
table[1] = self;
|
||||
for i in 2 .. 16 {
|
||||
table[i] = table[i - 1] + self;
|
||||
}
|
||||
|
||||
let mut res = Self::identity();
|
||||
let mut bits = 0;
|
||||
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
|
||||
bits <<= 1;
|
||||
let mut bit = u8_from_bool(bit.deref_mut());
|
||||
bits |= bit;
|
||||
bit.zeroize();
|
||||
|
||||
if ((i + 1) % 4) == 0 {
|
||||
if i != 3 {
|
||||
for _ in 0 .. 4 {
|
||||
res = res.double();
|
||||
}
|
||||
}
|
||||
|
||||
let mut term = table[0];
|
||||
for (j, candidate) in table[1 ..].iter().enumerate() {
|
||||
let j = j + 1;
|
||||
term = Self::conditional_select(&term, candidate, usize::from(bits).ct_eq(&j));
|
||||
}
|
||||
res += term;
|
||||
bits = 0;
|
||||
}
|
||||
}
|
||||
other.zeroize();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Scalar> for Point {
|
||||
fn mul_assign(&mut self, other: Scalar) {
|
||||
*self = *self * other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<&Scalar> for Point {
|
||||
type Output = Point;
|
||||
fn mul(self, other: &Scalar) -> Point {
|
||||
self * *other
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<&Scalar> for Point {
|
||||
fn mul_assign(&mut self, other: &Scalar) {
|
||||
*self *= *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupEncoding for Point {
|
||||
type Repr = GenericArray<u8, U33>;
|
||||
|
||||
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
|
||||
// Extract and clear the sign bit
|
||||
let sign = Choice::from(bytes[0] & 1);
|
||||
|
||||
// Parse x, recover y
|
||||
FieldElement::from_repr(*GenericArray::from_slice(&bytes[1 ..])).and_then(|x| {
|
||||
let is_identity = x.is_zero();
|
||||
|
||||
let y = recover_y(x).map(|mut y| {
|
||||
y.conditional_negate(y.is_odd().ct_eq(&!sign));
|
||||
y
|
||||
});
|
||||
|
||||
// If this the identity, set y to 1
|
||||
let y =
|
||||
CtOption::conditional_select(&y, &CtOption::new(FieldElement::ONE, 1.into()), is_identity);
|
||||
// If this the identity, set y to 1 and z to 0 (instead of 1)
|
||||
let z = <_>::conditional_select(&FieldElement::ONE, &FieldElement::ZERO, is_identity);
|
||||
// Create the point if we have a y solution
|
||||
let point = y.map(|y| Point { x, y, z });
|
||||
|
||||
let not_negative_zero = !(is_identity & sign);
|
||||
// Only return the point if it isn't -0 and the sign byte wasn't malleated
|
||||
CtOption::conditional_select(
|
||||
&CtOption::new(Point::identity(), 0.into()),
|
||||
&point,
|
||||
not_negative_zero & ((bytes[0] & 1).ct_eq(&bytes[0])),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
|
||||
Point::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Self::Repr {
|
||||
let Some(z) = Option::<FieldElement>::from(self.z.invert()) else {
|
||||
return *GenericArray::from_slice(&[0; 33]);
|
||||
};
|
||||
let x = self.x * z;
|
||||
let y = self.y * z;
|
||||
|
||||
let mut res = *GenericArray::from_slice(&[0; 33]);
|
||||
res[1 ..].as_mut().copy_from_slice(&x.to_repr());
|
||||
|
||||
// The following conditional select normalizes the sign to 0 when x is 0
|
||||
let y_sign = u8::conditional_select(&y.is_odd().unwrap_u8(), &0, x.ct_eq(&FieldElement::ZERO));
|
||||
res[0] |= y_sign;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimeGroup for Point {}
|
||||
|
||||
impl ec_divisors::DivisorCurve for Point {
|
||||
type FieldElement = FieldElement;
|
||||
type XyPoint = ec_divisors::Projective<Self>;
|
||||
|
||||
fn interpolator_for_scalar_mul() -> &'static ec_divisors::Interpolator<Self::FieldElement> {
|
||||
static PRECOMPUTE: std_shims::sync::LazyLock<ec_divisors::Interpolator<FieldElement>> =
|
||||
std_shims::sync::LazyLock::new(|| {
|
||||
ec_divisors::Interpolator::new(usize::try_from(130).unwrap())
|
||||
});
|
||||
&PRECOMPUTE
|
||||
}
|
||||
|
||||
fn a() -> Self::FieldElement {
|
||||
FieldElement::from(0u64)
|
||||
}
|
||||
fn b() -> Self::FieldElement {
|
||||
FieldElement::from(7u64)
|
||||
}
|
||||
|
||||
fn to_xy(point: Self) -> Option<(Self::FieldElement, Self::FieldElement)> {
|
||||
let z: Self::FieldElement = Option::from(point.z.invert())?;
|
||||
Some((point.x * z, point.y * z))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_curve() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator() {
|
||||
assert_eq!(
|
||||
Point::generator(),
|
||||
Point::from_bytes(GenericArray::from_slice(&hex_literal::hex!(
|
||||
"000000000000000000000000000000000000000000000000000000000000000001"
|
||||
)))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_x_is_invalid() {
|
||||
assert!(Option::<FieldElement>::from(recover_y(FieldElement::ZERO)).is_none());
|
||||
}
|
||||
|
||||
// Checks random won't infinitely loop
|
||||
#[test]
|
||||
fn random() {
|
||||
Point::random(&mut rand_core::OsRng);
|
||||
}
|
||||
@@ -158,16 +158,20 @@ pub fn test_encoding<G: PrimeGroup>() {
|
||||
let bytes = point.to_bytes();
|
||||
let mut repr = G::Repr::default();
|
||||
repr.as_mut().copy_from_slice(bytes.as_ref());
|
||||
let decoded = G::from_bytes(&repr).unwrap();
|
||||
let decoded = G::from_bytes(&repr).expect("couldn't decode encoded point");
|
||||
assert_eq!(point, decoded, "{msg} couldn't be encoded and decoded");
|
||||
assert_eq!(
|
||||
point,
|
||||
G::from_bytes_unchecked(&repr).unwrap(),
|
||||
"{msg} couldn't be encoded and decoded",
|
||||
G::from_bytes_unchecked(&repr)
|
||||
.expect("couldn't decode encoded point with `from_bytes_unchecked`"),
|
||||
"{msg} couldn't be encoded and decoded with `from_bytes_unchecked`",
|
||||
);
|
||||
decoded
|
||||
};
|
||||
assert!(bool::from(test(G::identity(), "identity").is_identity()));
|
||||
assert!(
|
||||
bool::from(test(G::identity(), "identity").is_identity()),
|
||||
"decoded identity was no longer the identity"
|
||||
);
|
||||
test(G::generator(), "generator");
|
||||
test(G::generator() + G::generator(), "(generator * 2)");
|
||||
}
|
||||
|
||||
@@ -27,5 +27,5 @@ ff-group-tests = { version = "0.13", path = "../ff-group-tests", optional = true
|
||||
|
||||
[features]
|
||||
alloc = ["zeroize/alloc", "crypto-bigint/alloc", "ff/alloc"]
|
||||
std = ["zeroize/std", "subtle/std", "rand_core/std", "ff/std", "ff-group-tests"]
|
||||
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "ff/std", "ff-group-tests"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -21,7 +21,9 @@ rand_core = { version = "0.6", default-features = false }
|
||||
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
||||
group = { version = "0.13", default-features = false }
|
||||
|
||||
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
|
||||
|
||||
[features]
|
||||
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc"]
|
||||
std = ["zeroize/std", "subtle/std", "rand_core/std", "ff/std"]
|
||||
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc", "ec-divisors"]
|
||||
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "ff/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -5,7 +5,7 @@ use zeroize::DefaultIsZeroes;
|
||||
|
||||
use rand_core::RngCore;
|
||||
|
||||
use group::ff::Field;
|
||||
use group::ff::{Field, PrimeField};
|
||||
|
||||
use crate::{ShortWeierstrass, Projective};
|
||||
|
||||
@@ -23,8 +23,14 @@ impl<C: ShortWeierstrass> Clone for Affine<C> {
|
||||
impl<C: ShortWeierstrass> Copy for Affine<C> {}
|
||||
|
||||
impl<C: ShortWeierstrass> Affine<C> {
|
||||
pub fn try_from(projective: Projective<C>) -> CtOption<Self> {
|
||||
projective.z.invert().map(|z_inv| Self { x: projective.x * z_inv, y: projective.y * z_inv })
|
||||
pub fn try_from(projective: &Projective<C>) -> CtOption<Self> {
|
||||
let z_inv = projective.z.invert().unwrap_or(C::FieldElement::ZERO);
|
||||
let if_non_identity = Self { x: projective.x * z_inv, y: projective.y * z_inv };
|
||||
let identity = z_inv.ct_eq(&C::FieldElement::ZERO);
|
||||
// If this isn't a valid affine point, switch to a valid affine point
|
||||
let value = <_>::conditional_select(&if_non_identity, &C::GENERATOR, identity);
|
||||
// This is valid if the projective point wasn't the identity
|
||||
CtOption::new(value, !identity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,24 +79,36 @@ impl<C: ShortWeierstrass> Affine<C> {
|
||||
/// Create an affine point from `x, y` coordinates, without performing any checks.
|
||||
///
|
||||
/// This should NOT be used. It is solely intended for trusted data at compile-time. It MUST NOT
|
||||
/// be used with any untrusted/unvalidated data.
|
||||
pub const fn from_xy_unchecked(x: C::FieldElement, y: C::FieldElement) -> Self { Self { x, y } }
|
||||
/// be used with any untrusted/unvalidated data. Providing any point not within the largest
|
||||
/// prime-order subgroup has completely undefined behavior.
|
||||
pub const fn from_xy_unchecked(x: C::FieldElement, y: C::FieldElement) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Decompress a point from it's `x`-coordinate and the sign of the `y` coordinate.
|
||||
pub fn decompress(x: C::FieldElement, odd_y: Choice) -> CtOption<Self> {
|
||||
let y_square = ((x.square() + C::A) * x) + C::B;
|
||||
y_square.sqrt().and_then(|mut y| {
|
||||
y = <_>::conditional_select(&y, &-y, y.is_odd().ct_ne(&odd_y));
|
||||
// Handles the exceptional case of `y = 0, odd_y = 1`
|
||||
CtOption::new(Self { x, y }, y.is_odd().ct_eq(&odd_y))
|
||||
})
|
||||
}
|
||||
|
||||
/// The `x, y` coordinates of this point.
|
||||
pub fn coordinates(self) -> (C::FieldElement, C::FieldElement) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Sample a random on-curve point with an unknown discrete logarithm w.r.t. any other points.
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
loop {
|
||||
let x = C::FieldElement::random(&mut rng);
|
||||
let y_square = ((x.square() + C::A) * x) + C::B;
|
||||
let Some(mut y) = Option::<C::FieldElement>::from(y_square.sqrt()) else { continue };
|
||||
if (rng.next_u64() % 2) == 1 {
|
||||
y = -y;
|
||||
/// Sample a random on-curve point with an unknown discrete logarithm w.r.t. any other points.
|
||||
///
|
||||
/// This runs in time variable to the amount of samples required to find a point.
|
||||
pub fn random(mut rng: impl RngCore) -> Self {
|
||||
loop {
|
||||
let x = C::FieldElement::random(&mut rng);
|
||||
let y_is_odd = Choice::from((rng.next_u64() % 2) as u8);
|
||||
let Some(res) = Option::<Self>::from(Self::decompress(x, y_is_odd)) else { continue };
|
||||
return res;
|
||||
}
|
||||
return Self { x, y };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use subtle::Choice;
|
||||
use zeroize::Zeroize;
|
||||
use group::ff::PrimeField;
|
||||
|
||||
@@ -21,10 +22,30 @@ pub trait ShortWeierstrass: 'static + Sized + Debug {
|
||||
const A: Self::FieldElement;
|
||||
/// The constant `B` from the curve equation.
|
||||
const B: Self::FieldElement;
|
||||
/// A generator of this elliptic curve.
|
||||
/// If the elliptic curve is of prime order.
|
||||
const PRIME_ORDER: bool;
|
||||
/// A generator of this elliptic curve's largest prime-order subgroup.
|
||||
const GENERATOR: Affine<Self>;
|
||||
/// The scalar type.
|
||||
/// The scalar type for the elliptic curve's largest prime-order subgroup.
|
||||
///
|
||||
/// This may be omitted by specifying `()`.
|
||||
type Scalar;
|
||||
|
||||
/// The type used for encoding points.
|
||||
type Repr: 'static + Send + Sync + Copy + Default + AsRef<[u8]> + AsMut<[u8]>;
|
||||
/// The representation of the identity point.
|
||||
const IDENTITY: Self::Repr;
|
||||
/// Encode a compresed, on-curve point to its byte encoding.
|
||||
///
|
||||
/// The space of potential outputs MUST exclude `Self::IDENTITY`.
|
||||
fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr;
|
||||
/// Decode the `x` coordinate and if the `y` coordinate is odd from a compressed representation.
|
||||
///
|
||||
/// This MAY return any value if the bytes represent the identity.
|
||||
fn decode_compressed(bytes: &Self::Repr) -> (<Self::FieldElement as PrimeField>::Repr, Choice);
|
||||
|
||||
/// If the point is outside the largest prime-order subgroup and isn't the identity point.
|
||||
///
|
||||
/// This SHOULD immediately return `Choice::new(0)` for curves of prime order.
|
||||
fn has_torsion_element(point: Projective<Self>) -> Choice;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use core::{hint::black_box, borrow::Borrow, ops::*, iter::Sum};
|
||||
|
||||
use subtle::{Choice, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
|
||||
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
|
||||
use zeroize::{Zeroize, DefaultIsZeroes};
|
||||
|
||||
use rand_core::RngCore;
|
||||
|
||||
use group::{
|
||||
ff::{Field, PrimeFieldBits},
|
||||
Group,
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
Group, GroupEncoding,
|
||||
prime::PrimeGroup,
|
||||
};
|
||||
|
||||
use crate::{ShortWeierstrass, Affine};
|
||||
@@ -76,16 +77,11 @@ impl<C: ShortWeierstrass> ConditionallyNegatable for Projective<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ShortWeierstrass> Add for Projective<C> {
|
||||
type Output = Self;
|
||||
impl<C: ShortWeierstrass> Projective<C> {
|
||||
// add-1998-cmo-2
|
||||
/*
|
||||
We don't use the complete formulas from 2015 as they require the curve is prime order, when we
|
||||
do not.
|
||||
*/
|
||||
fn add(self, p2: Self) -> Self {
|
||||
fn add_if_composite_order(self, other: Self) -> Self {
|
||||
let Self { x: X1, y: Y1, z: Z1 } = self;
|
||||
let Self { x: X2, y: Y2, z: Z2 } = p2;
|
||||
let Self { x: X2, y: Y2, z: Z2 } = other;
|
||||
|
||||
let Y1Z2 = Y1 * Z2;
|
||||
let X1Z2 = X1 * Z2;
|
||||
@@ -103,18 +99,84 @@ impl<C: ShortWeierstrass> Add for Projective<C> {
|
||||
|
||||
let res = Self { x: X3, y: Y3, z: Z3 };
|
||||
|
||||
let same_x_coordinate = (self.x * p2.z).ct_eq(&(p2.x * self.z));
|
||||
let same_y_coordinate = (self.y * p2.z).ct_eq(&(p2.y * self.z));
|
||||
let same_x_coordinate = (self.x * other.z).ct_eq(&(other.x * self.z));
|
||||
let same_y_coordinate = (self.y * other.z).ct_eq(&(other.y * self.z));
|
||||
let res = <_>::conditional_select(
|
||||
&res,
|
||||
&Self::IDENTITY,
|
||||
(self.is_identity_internal() & p2.is_identity_internal()) |
|
||||
(self.is_identity_internal() & other.is_identity_internal()) |
|
||||
(same_x_coordinate & (!same_y_coordinate)),
|
||||
);
|
||||
let res =
|
||||
<_>::conditional_select(&res, &self.double_internal(), same_x_coordinate & same_y_coordinate);
|
||||
let res = <_>::conditional_select(&res, &p2, self.is_identity_internal());
|
||||
<_>::conditional_select(&res, &self, p2.is_identity_internal())
|
||||
let res = <_>::conditional_select(&res, &other, self.is_identity_internal());
|
||||
<_>::conditional_select(&res, &self, other.is_identity_internal())
|
||||
}
|
||||
|
||||
// add-2015-rcb
|
||||
fn add_if_prime_order(self, other: Self) -> Self {
|
||||
let b3 = C::B + C::B + C::B;
|
||||
|
||||
let X1 = self.x;
|
||||
let Y1 = self.y;
|
||||
let Z1 = self.z;
|
||||
let X2 = other.x;
|
||||
let Y2 = other.y;
|
||||
let Z2 = other.z;
|
||||
|
||||
let t0 = X1 * X2;
|
||||
let t1 = Y1 * Y2;
|
||||
let t2 = Z1 * Z2;
|
||||
let t3 = X1 + Y1;
|
||||
let t4 = X2 + Y2;
|
||||
let t3 = t3 * t4;
|
||||
let t4 = t0 + t1;
|
||||
let t3 = t3 - t4;
|
||||
let t4 = X1 + Z1;
|
||||
let t5 = X2 + Z2;
|
||||
let t4 = t4 * t5;
|
||||
let t5 = t0 + t2;
|
||||
let t4 = t4 - t5;
|
||||
let t5 = Y1 + Z1;
|
||||
let X3 = Y2 + Z2;
|
||||
let t5 = t5 * X3;
|
||||
let X3 = t1 + t2;
|
||||
let t5 = t5 - X3;
|
||||
let Z3 = C::A * t4;
|
||||
let X3 = b3 * t2;
|
||||
let Z3 = X3 + Z3;
|
||||
let X3 = t1 - Z3;
|
||||
let Z3 = t1 + Z3;
|
||||
let Y3 = X3 * Z3;
|
||||
let t1 = t0 + t0;
|
||||
let t1 = t1 + t0;
|
||||
let t2 = C::A * t2;
|
||||
let t4 = b3 * t4;
|
||||
let t1 = t1 + t2;
|
||||
let t2 = t0 - t2;
|
||||
let t2 = C::A * t2;
|
||||
let t4 = t4 + t2;
|
||||
let t0 = t1 * t4;
|
||||
let Y3 = Y3 + t0;
|
||||
let t0 = t5 * t4;
|
||||
let X3 = t3 * X3;
|
||||
let X3 = X3 - t0;
|
||||
let t0 = t3 * t1;
|
||||
let Z3 = t5 * Z3;
|
||||
let Z3 = Z3 + t0;
|
||||
|
||||
Projective { x: X3, y: Y3, z: Z3 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ShortWeierstrass> Add for Projective<C> {
|
||||
type Output = Self;
|
||||
fn add(self, other: Self) -> Self {
|
||||
if C::PRIME_ORDER {
|
||||
self.add_if_prime_order(other)
|
||||
} else {
|
||||
self.add_if_composite_order(other)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<C: ShortWeierstrass> Sub for Projective<C> {
|
||||
@@ -280,19 +342,6 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>, S: Borrow<C::Scalar>> MulAssig
|
||||
*self = *self * scalar.borrow();
|
||||
}
|
||||
}
|
||||
/*
|
||||
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Mul<&C::Scalar> for Projective<C> {
|
||||
type Output = Self;
|
||||
fn mul(self, scalar: &C::Scalar) -> Self {
|
||||
self * *scalar
|
||||
}
|
||||
}
|
||||
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> MulAssign<&C::Scalar> for Projective<C> {
|
||||
fn mul_assign(&mut self, scalar: &C::Scalar) {
|
||||
*self *= *scalar;
|
||||
}
|
||||
}
|
||||
*/
|
||||
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Group for Projective<C> {
|
||||
type Scalar = C::Scalar;
|
||||
fn random(rng: impl RngCore) -> Self {
|
||||
@@ -311,3 +360,80 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Group for Projective<C> {
|
||||
self.double_internal()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ShortWeierstrass> GroupEncoding for Projective<C> {
|
||||
type Repr = C::Repr;
|
||||
fn from_bytes(bytes: &C::Repr) -> CtOption<Self> {
|
||||
// If this point is the identity point
|
||||
let identity = bytes.as_ref().ct_eq(C::IDENTITY.as_ref());
|
||||
|
||||
let (x, odd_y) = C::decode_compressed(bytes);
|
||||
|
||||
let result = C::FieldElement::from_repr(x)
|
||||
.and_then(|x| Affine::decompress(x, odd_y).map(Projective::from));
|
||||
// Set the identity, if the identity
|
||||
let identity = CtOption::new(Projective::IDENTITY, identity);
|
||||
let result = result.or_else(|| identity);
|
||||
|
||||
let mut result_is_valid = result.is_some();
|
||||
let result = result.unwrap_or(Projective::IDENTITY);
|
||||
// Constrain points to the prime-order subgroup
|
||||
result_is_valid &= !C::has_torsion_element(result);
|
||||
|
||||
CtOption::new(result, result_is_valid)
|
||||
}
|
||||
fn from_bytes_unchecked(bytes: &C::Repr) -> CtOption<Self> {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
fn to_bytes(&self) -> C::Repr {
|
||||
let affine_on_curve = Affine::try_from(self);
|
||||
let identity = affine_on_curve.is_none();
|
||||
|
||||
let compressed_if_not_identity = {
|
||||
let affine_on_curve = affine_on_curve.unwrap_or(C::GENERATOR);
|
||||
let (x, y) = affine_on_curve.coordinates();
|
||||
C::encode_compressed(x, y.is_odd())
|
||||
};
|
||||
|
||||
let mut res = C::Repr::default();
|
||||
{
|
||||
let res = res.as_mut();
|
||||
for (dst, (if_not_identity, if_identity)) in
|
||||
res.iter_mut().zip(compressed_if_not_identity.as_ref().iter().zip(C::IDENTITY.as_ref()))
|
||||
{
|
||||
*dst = <_>::conditional_select(if_not_identity, if_identity, identity);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// We implement `PrimeGroup` due to constraining to a prime-order subgroup
|
||||
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> PrimeGroup for Projective<C> {}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod alloc {
|
||||
use core::borrow::Borrow;
|
||||
use ff::{PrimeField, PrimeFieldBits};
|
||||
use crate::{ShortWeierstrass, Affine, Projective};
|
||||
|
||||
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> ec_divisors::DivisorCurve for Projective<C> {
|
||||
type FieldElement = C::FieldElement;
|
||||
type XyPoint = ec_divisors::Projective<Self>;
|
||||
|
||||
fn interpolator_for_scalar_mul() -> impl Borrow<ec_divisors::Interpolator<C::FieldElement>> {
|
||||
ec_divisors::Interpolator::new((<C::Scalar as PrimeField>::NUM_BITS as usize).div_ceil(2) + 2)
|
||||
}
|
||||
|
||||
fn a() -> C::FieldElement {
|
||||
C::A
|
||||
}
|
||||
fn b() -> C::FieldElement {
|
||||
C::B
|
||||
}
|
||||
|
||||
fn to_xy(point: Self) -> Option<(C::FieldElement, C::FieldElement)> {
|
||||
Option::<Affine<C>>::from(Affine::try_from(&point)).map(Affine::<_>::coordinates)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = fals
|
||||
[features]
|
||||
alloc = ["ciphersuite/alloc"]
|
||||
std = [
|
||||
"alloc",
|
||||
"ciphersuite/std",
|
||||
"dalek-ff-group?/std",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user