diff --git a/.github/workflows/crypto-tests.yml b/.github/workflows/crypto-tests.yml index 5d22a370..508bcf43 100644 --- a/.github/workflows/crypto-tests.yml +++ b/.github/workflows/crypto-tests.yml @@ -35,6 +35,9 @@ jobs: -p ciphersuite-kp256 \ -p multiexp \ -p schnorr-signatures \ + -p prime-field \ + -p secq256k1 \ + -p embedwards25519 \ -p dkg \ -p dkg-recovery \ -p dkg-dealer \ diff --git a/Cargo.lock b/Cargo.lock index ad31a575..074a0e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2298,7 +2298,19 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "serdect", + "serdect 0.2.0", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130" +dependencies = [ + "num-traits", + "serdect 0.3.0", "subtle", "zeroize", ] @@ -2419,7 +2431,7 @@ name = "dalek-ff-group" version = "0.4.4" dependencies = [ "ciphersuite 0.4.2", - "crypto-bigint", + "crypto-bigint 0.5.5", "curve25519-dalek", "digest 0.10.7", "ff", @@ -2505,7 +2517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -2884,7 +2896,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", - "serdect", + "serdect 0.2.0", "signature", "spki", ] @@ -2956,7 +2968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", - "crypto-bigint", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff", "generic-array 0.14.7", @@ -2964,7 +2976,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", - "serdect", + "serdect 0.2.0", "subtle", "tap", "zeroize", @@ -2976,7 +2988,6 @@ version = "0.1.0" dependencies = [ "blake2", "ciphersuite 0.4.2", - "crypto-bigint", "dalek-ff-group", "ec-divisors", "ff-group-tests", @@ -2984,10 +2995,9 @@ dependencies = [ "generic-array 1.2.0", "hex", "hex-literal", + "prime-field", "rand_core 0.6.4", - "rustversion", "std-shims", - "subtle", "zeroize", ] @@ -4181,7 +4191,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -4672,7 +4682,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "serdect", + "serdect 0.2.0", "sha2 0.10.9", ] @@ -4752,7 +4762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -5934,16 +5944,11 @@ name = "minimal-ed448" version = "0.4.2" dependencies = [ "ciphersuite 0.4.2", - "crypto-bigint", - "ff", "ff-group-tests", - "generic-array 1.2.0", - "group", "hex", + "prime-field", "rand_core 0.6.4", - "rustversion", "sha3", - "subtle", "zeroize", ] @@ -6099,7 +6104,7 @@ name = "monero-generators" version = "0.4.0" source = "git+https://github.com/monero-oxide/monero-oxide?rev=6966575e05fe09b77674c46984b21686ed9304ff#6966575e05fe09b77674c46984b21686ed9304ff" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.5.5", "curve25519-dalek", "dalek-ff-group", "group", @@ -6610,7 +6615,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.106", @@ -7215,6 +7220,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prime-field" +version = "0.1.0" +dependencies = [ + "crypto-bigint 0.6.1", + "ff", + "ff-group-tests", + "paste", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -9450,7 +9468,7 @@ dependencies = [ "der", "generic-array 0.14.7", "pkcs8", - "serdect", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -9520,8 +9538,8 @@ dependencies = [ "hex", "hex-literal", "k256", + "prime-field", "rand_core 0.6.4", - "rustversion", "std-shims", ] @@ -10159,6 +10177,7 @@ dependencies = [ "flexible-transcript", "minimal-ed448", "multiexp", + "prime-field", "schnorr-signatures", "secq256k1", ] @@ -10799,6 +10818,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -13283,7 +13312,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 83adf1a4..adb79596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ "crypto/multiexp", "crypto/schnorr", + "crypto/prime-field", "crypto/evrf/secq256k1", "crypto/evrf/embedwards25519", diff --git a/crypto/ed448/Cargo.toml b/crypto/ed448/Cargo.toml index 1000ba31..7e6ef2a5 100644 --- a/crypto/ed448/Cargo.toml +++ b/crypto/ed448/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ed448" authors = ["Luke Parker "] keywords = ["ed448", "ff", "group"] edition = "2021" -rust-version = "1.83" +rust-version = "1.86" [package.metadata.docs.rs] all-features = true @@ -17,22 +17,13 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] -rustversion = "1" - -rand_core = { version = "0.6", default-features = false } - -zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } -subtle = { version = "^2.4", default-features = false } +zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } sha3 = { version = "0.10", default-features = false } -ff = { version = "0.13", default-features = false, features = ["bits"] } -group = { version = "0.13", default-features = false } +prime-field = { path = "../prime-field", default-features = false } ciphersuite = { path = "../ciphersuite", default-features = false } -generic-array = { version = "1", default-features = false } -crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] } - [dev-dependencies] hex = { version = "0.4", default-features = false, features = ["std"] } @@ -41,6 +32,6 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] } ff-group-tests = { path = "../ff-group-tests" } [features] -alloc = ["zeroize/alloc", "ciphersuite/alloc"] -std = ["alloc", "rand_core/std", "zeroize/std", "subtle/std", "sha3/std", "ff/std", "ciphersuite/std"] +alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"] +std = ["alloc", "zeroize/std", "sha3/std", "prime-field/std", "ciphersuite/std"] default = ["std"] diff --git a/crypto/ed448/src/backend.rs b/crypto/ed448/src/backend.rs deleted file mode 100644 index f68f9898..00000000 --- a/crypto/ed448/src/backend.rs +++ /dev/null @@ -1,311 +0,0 @@ -use zeroize::Zeroize; - -// Use black_box when possible -#[rustversion::since(1.66)] -mod black_box { - pub(crate) fn black_box(val: T) -> T { - #[allow(clippy::incompatible_msrv)] - core::hint::black_box(val) - } -} -#[rustversion::before(1.66)] -mod black_box { - pub(crate) fn black_box(val: T) -> T { - val - } -} -use black_box::black_box; - -pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 { - let bit_ref = black_box(bit_ref); - - let mut bit = black_box(*bit_ref); - #[allow(clippy::cast_lossless)] - let res = black_box(bit as u8); - bit.zeroize(); - debug_assert!((res | 1) == 1); - - bit_ref.zeroize(); - res -} - -macro_rules! math_op { - ( - $Value: ident, - $Other: ident, - $Op: ident, - $op_fn: ident, - $Assign: ident, - $assign_fn: ident, - $function: expr - ) => { - impl $Op<$Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: $Other) -> Self::Output { - $Value($function(self.0, other.0)) - } - } - impl $Assign<$Other> for $Value { - fn $assign_fn(&mut self, other: $Other) { - self.0 = $function(self.0, other.0); - } - } - impl<'a> $Op<&'a $Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: &'a $Other) -> Self::Output { - $Value($function(self.0, other.0)) - } - } - impl<'a> $Assign<&'a $Other> for $Value { - fn $assign_fn(&mut self, other: &'a $Other) { - self.0 = $function(self.0, other.0); - } - } - }; -} - -macro_rules! from_wrapper { - ($wrapper: ident, $inner: ident, $uint: ident) => { - impl From<$uint> for $wrapper { - fn from(a: $uint) -> $wrapper { - $wrapper(Residue::new(&$inner::from(a))) - } - } - }; -} - -macro_rules! field { - ( - $FieldName: ident, - $ResidueType: ident, - - $MODULUS_STR: ident, - $MODULUS: ident, - $WIDE_MODULUS: ident, - - $NUM_BITS: literal, - - $MULTIPLICATIVE_GENERATOR: literal, - $DELTA: expr, - ) => { - use core::{ - ops::{Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign}, - iter::{Sum, Product}, - }; - - use subtle::{Choice, CtOption, ConstantTimeEq, ConstantTimeLess, ConditionallySelectable}; - use rand_core::RngCore; - - use generic_array::{typenum::U57, GenericArray}; - use crypto_bigint::{Integer, NonZero, Encoding, impl_modulus}; - - use ff::{Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic}; - - use $crate::backend::u8_from_bool; - - fn reduce(x: U896) -> U448 { - U448::from_le_slice(&x.rem(&NonZero::new($WIDE_MODULUS).unwrap()).to_le_bytes()[.. 56]) - } - - impl ConstantTimeEq for $FieldName { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } - } - - impl ConditionallySelectable for $FieldName { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - $FieldName(Residue::conditional_select(&a.0, &b.0, choice)) - } - } - - math_op!($FieldName, $FieldName, Add, add, AddAssign, add_assign, |x: $ResidueType, y| x - .add(&y)); - math_op!($FieldName, $FieldName, Sub, sub, SubAssign, sub_assign, |x: $ResidueType, y| x - .sub(&y)); - math_op!($FieldName, $FieldName, Mul, mul, MulAssign, mul_assign, |x: $ResidueType, y| x - .mul(&y)); - - from_wrapper!($FieldName, U448, u8); - from_wrapper!($FieldName, U448, u16); - from_wrapper!($FieldName, U448, u32); - from_wrapper!($FieldName, U448, u64); - from_wrapper!($FieldName, U448, u128); - - impl Neg for $FieldName { - type Output = $FieldName; - fn neg(self) -> $FieldName { - $FieldName(self.0.neg()) - } - } - - impl<'a> Neg for &'a $FieldName { - type Output = $FieldName; - fn neg(self) -> Self::Output { - (*self).neg() - } - } - - impl $FieldName { - /// Perform an exponentiation. - pub fn pow(&self, other: $FieldName) -> $FieldName { - let mut table = [$FieldName(Residue::ONE); 16]; - table[1] = *self; - for i in 2 .. 16 { - table[i] = table[i - 1] * self; - } - - let mut res = $FieldName(Residue::ONE); - 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(&mut bit); - bits |= bit; - bit.zeroize(); - - if ((i + 1) % 4) == 0 { - if i != 3 { - for _ in 0 .. 4 { - res *= res; - } - } - - let mut scale_by = $FieldName(Residue::ONE); - #[allow(clippy::needless_range_loop)] - for i in 0 .. 16 { - #[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16 - { - scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8))); - } - } - res *= scale_by; - bits = 0; - } - } - res - } - } - - impl Field for $FieldName { - const ZERO: Self = $FieldName(Residue::ZERO); - const ONE: Self = $FieldName(Residue::ONE); - - fn random(mut rng: impl RngCore) -> Self { - let mut bytes = [0; 112]; - rng.fill_bytes(&mut bytes); - $FieldName(Residue::new(&reduce(U896::from_le_slice(bytes.as_ref())))) - } - - fn square(&self) -> Self { - *self * self - } - fn double(&self) -> Self { - *self + self - } - - fn invert(&self) -> CtOption { - const NEG_2: $FieldName = - $FieldName($ResidueType::sub(&$ResidueType::ZERO, &$ResidueType::new(&U448::from_u8(2)))); - CtOption::new(self.pow(NEG_2), !self.is_zero()) - } - - fn sqrt(&self) -> CtOption { - const MOD_1_4: $FieldName = $FieldName($ResidueType::new( - &$MODULUS.saturating_add(&U448::ONE).wrapping_div(&U448::from_u8(4)), - )); - - let res = self.pow(MOD_1_4); - CtOption::new(res, res.square().ct_eq(self)) - } - - fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - sqrt_ratio_generic(num, div) - } - } - - impl PrimeField for $FieldName { - type Repr = GenericArray; - - const MODULUS: &'static str = $MODULUS_STR; - - const NUM_BITS: u32 = $NUM_BITS; - const CAPACITY: u32 = $NUM_BITS - 1; - - const TWO_INV: Self = $FieldName($ResidueType::new(&U448::from_u8(2)).invert().0); - - const MULTIPLICATIVE_GENERATOR: Self = - $FieldName(Residue::new(&U448::from_u8($MULTIPLICATIVE_GENERATOR))); - // True for both the Ed448 Scalar field and FieldElement field - const S: u32 = 1; - - // Both fields have their root of unity as -1 - const ROOT_OF_UNITY: Self = - $FieldName($ResidueType::sub(&$ResidueType::ZERO, &$ResidueType::new(&U448::ONE))); - const ROOT_OF_UNITY_INV: Self = $FieldName(Self::ROOT_OF_UNITY.0.invert().0); - - const DELTA: Self = $FieldName(Residue::new(&U448::from_le_hex($DELTA))); - - fn from_repr(bytes: Self::Repr) -> CtOption { - let res = U448::from_le_slice(&bytes[.. 56]); - CtOption::new($FieldName(Residue::new(&res)), res.ct_lt(&$MODULUS) & bytes[56].ct_eq(&0)) - } - fn to_repr(&self) -> Self::Repr { - let mut repr = GenericArray::::default(); - repr[.. 56].copy_from_slice(&self.0.retrieve().to_le_bytes()); - repr - } - - fn is_odd(&self) -> Choice { - self.0.retrieve().is_odd() - } - } - - impl PrimeFieldBits for $FieldName { - type ReprBits = [u8; 56]; - - fn to_le_bits(&self) -> FieldBits { - let mut repr = [0; 56]; - repr.copy_from_slice(&self.to_repr()[.. 56]); - repr.into() - } - - fn char_le_bits() -> FieldBits { - let mut repr = [0; 56]; - repr.copy_from_slice(&MODULUS.to_le_bytes()[.. 56]); - repr.into() - } - } - - impl Sum<$FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - let mut res = $FieldName::ZERO; - for item in iter { - res += item; - } - res - } - } - - impl<'a> Sum<&'a $FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - iter.cloned().sum() - } - } - - impl Product<$FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - let mut res = $FieldName::ONE; - for item in iter { - res *= item; - } - res - } - } - - impl<'a> Product<&'a $FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - iter.cloned().product() - } - } - }; -} diff --git a/crypto/ed448/src/ciphersuite.rs b/crypto/ed448/src/ciphersuite.rs index c677bad4..6b930ee3 100644 --- a/crypto/ed448/src/ciphersuite.rs +++ b/crypto/ed448/src/ciphersuite.rs @@ -8,10 +8,12 @@ use sha3::{ Shake256, }; -use group::Group; -use crate::{Scalar, Point}; +use ciphersuite::{ + group::{ff::FromUniformBytes, Group}, + Ciphersuite, +}; -use ciphersuite::Ciphersuite; +use crate::{Scalar, Point}; /// Shake256, fixed to a 114-byte output, as used by Ed448. #[derive(Clone, Default)] @@ -69,13 +71,16 @@ impl Ciphersuite for Ed448 { } fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { - Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap()) + let digest = Self::H::digest([dst, data].concat()); + let mut wide_scalar = [0; 114]; + wide_scalar.copy_from_slice(digest.as_ref()); + Scalar::from_uniform_bytes(&wide_scalar) } } #[test] fn test_ed448() { - use ff::PrimeField; + use ciphersuite::group::ff::PrimeField; ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng); @@ -94,7 +99,7 @@ fn test_ed448() { .unwrap() ) .to_repr() - .to_vec(), + .as_ref(), hex::decode( "\ 67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28\ @@ -102,5 +107,6 @@ e7011f3193b9020e73c183a98cc3a519160ed759376dd92c94831622\ 00" ) .unwrap() + .as_slice() ); } diff --git a/crypto/ed448/src/field.rs b/crypto/ed448/src/field.rs deleted file mode 100644 index f14141c7..00000000 --- a/crypto/ed448/src/field.rs +++ /dev/null @@ -1,53 +0,0 @@ -use zeroize::{DefaultIsZeroes, Zeroize}; - -use crypto_bigint::{ - U448, U896, - modular::constant_mod::{ResidueParams, Residue}, -}; - -const MODULUS_STR: &str = concat!( - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", -); - -impl_modulus!(FieldModulus, U448, MODULUS_STR); -pub(crate) type ResidueType = Residue; - -/// Ed448 field element. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -pub struct FieldElement(pub(crate) ResidueType); - -impl DefaultIsZeroes for FieldElement {} - -// 2**448 - 2**224 - 1 -pub(crate) const MODULUS: U448 = U448::from_be_hex(MODULUS_STR); - -const WIDE_MODULUS: U896 = U896::from_be_hex(concat!( - "00000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" -)); - -pub(crate) const Q_4: FieldElement = FieldElement(ResidueType::new( - &MODULUS.saturating_add(&U448::ONE).wrapping_div(&U448::from_u8(4)), -)); - -field!( - FieldElement, - ResidueType, - MODULUS_STR, - MODULUS, - WIDE_MODULUS, - 448, - 7, - concat!( - "31000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - ), -); - -#[test] -fn test_field() { - ff_group_tests::prime_field::test_prime_field_bits::<_, FieldElement>(&mut rand_core::OsRng); -} diff --git a/crypto/ed448/src/lib.rs b/crypto/ed448/src/lib.rs index 2fbfeb60..05f3fc12 100644 --- a/crypto/ed448/src/lib.rs +++ b/crypto/ed448/src/lib.rs @@ -1,19 +1,47 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![no_std] -#![allow(clippy::redundant_closure_call)] -#[macro_use] -mod backend; +prime_field::odd_prime_field!( + FieldElement, + // 2**448 - 2**224 - 1 + concat!( + "00", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + "07", + false +); -mod scalar; -pub use scalar::Scalar; - -mod field; -pub use field::FieldElement; +prime_field::odd_prime_field!( + Scalar, + concat!( + "00", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", + ), + "02", + false +); mod point; pub use point::Point; mod ciphersuite; pub use crate::ciphersuite::Ed448; + +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 +} diff --git a/crypto/ed448/src/point.rs b/crypto/ed448/src/point.rs index d9b47b5e..95552663 100644 --- a/crypto/ed448/src/point.rs +++ b/crypto/ed448/src/point.rs @@ -3,49 +3,46 @@ use core::{ iter::Sum, }; -use rand_core::RngCore; +use prime_field::{ + subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable}, + zeroize::Zeroize, + rand_core::RngCore, + crypto_bigint::U512, +}; -use zeroize::Zeroize; -use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable}; - -use crypto_bigint::{U448, modular::constant_mod::Residue}; - -use group::{ +use ciphersuite::group::{ ff::{Field, PrimeField, PrimeFieldBits}, Group, GroupEncoding, prime::PrimeGroup, }; -use crate::{ - backend::u8_from_bool, - scalar::Scalar, - field::{ResidueType, FieldElement, Q_4}, -}; +use crate::{u8_from_bool, Scalar, FieldElement}; -const D: FieldElement = - FieldElement(ResidueType::sub(&ResidueType::ZERO, &Residue::new(&U448::from_u16(39081)))); - -const G_Y: FieldElement = FieldElement(Residue::new(&U448::from_be_hex(concat!( +const G_Y: FieldElement = FieldElement::from(&U512::from_be_hex(concat!( + "0000000000000000", "693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e", "05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14", -)))); +))); -const G_X: FieldElement = FieldElement(Residue::new(&U448::from_be_hex(concat!( +const G_X: FieldElement = FieldElement::from(&U512::from_be_hex(concat!( + "0000000000000000", "4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324", "a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e", -)))); +))); fn recover_x(y: FieldElement) -> CtOption { + #[allow(non_snake_case)] + let D = ->::from(39081u16); + let ysq = y.square(); #[allow(non_snake_case)] let D_ysq = D * ysq; (D_ysq - FieldElement::ONE).invert().and_then(|inverted| { - let temp = (ysq - FieldElement::ONE) * inverted; - let mut x = temp.pow(Q_4); - x.conditional_negate(x.is_odd()); - - let xsq = x.square(); - CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::ONE + (xsq * D_ysq)))) + let xsq = (ysq - FieldElement::ONE) * inverted; + xsq.sqrt().and_then(|mut x| { + x.conditional_negate(x.is_odd()); + CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::ONE + (xsq * D_ysq)))) + }) }) } @@ -104,6 +101,9 @@ impl ConditionallySelectable for Point { impl Add for Point { type Output = Point; fn add(self, other: Self) -> Self { + #[allow(non_snake_case)] + let D = ->::from(39081u16); + // 12 muls, 7 additions, 4 negations let xcp = self.x * other.x; let ycp = self.y * other.y; @@ -302,7 +302,7 @@ impl GroupEncoding for Point { fn from_bytes(bytes: &Self::Repr) -> CtOption { // Extract and clear the sign bit - let sign = Choice::from(bytes[56] >> 7); + let sign = Choice::from(bytes.as_ref()[56] >> 7); let mut bytes = *bytes; let mut_ref: &mut [u8] = bytes.as_mut(); mut_ref[56] &= !(1 << 7); @@ -350,63 +350,48 @@ fn generator() { #[test] fn torsion() { - use generic_array::GenericArray; - // Uses the originally suggested generator which had torsion - let old_y = FieldElement::from_repr(*GenericArray::from_slice( - &hex::decode( - "\ + const TORSIONED_Y: &str = "\ 12796c1532041525945f322e414d434467cfd5c57c9a9af2473b2775\ 8c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa51\ -00", - ) - .unwrap(), - )) - .unwrap(); +00"; + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&hex::decode(TORSIONED_Y).unwrap()); + + let old_y = FieldElement::from_repr(repr).unwrap(); let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::ONE }; assert!(bool::from(!old.is_torsion_free())); + + assert!(bool::from(Point::from_bytes(&old.to_bytes()).is_none())); } #[test] fn vector() { - use generic_array::GenericArray; - - assert_eq!( - Point::generator().double(), - Point::from_bytes(GenericArray::from_slice( - &hex::decode( - "\ + const TWO_G: &str = "\ ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a\ 726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae\ -80" - ) - .unwrap() - )) - .unwrap() - ); +80"; + let mut two_g = ::Repr::default(); + two_g.as_mut().copy_from_slice(&hex::decode(TWO_G).unwrap()); + assert_eq!(Point::generator().double(), Point::from_bytes(&two_g).unwrap()); - assert_eq!( - Point::generator() * - Scalar::from_repr(*GenericArray::from_slice( - &hex::decode( - "\ + const SCALAR: &str = "\ 6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158\ a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\ -00" - ) - .unwrap() - )) - .unwrap(), - Point::from_bytes(GenericArray::from_slice( - &hex::decode( - "\ +00"; + let mut scalar = ::Repr::default(); + scalar.as_mut().copy_from_slice(&hex::decode(SCALAR).unwrap()); + + const SCALED_G: &str = "\ 3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba2\ 65632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b0\ -00" - ) - .unwrap() - )) - .unwrap() +00"; + let mut scaled_g = ::Repr::default(); + scaled_g.as_mut().copy_from_slice(&hex::decode(SCALED_G).unwrap()); + + assert_eq!( + Point::generator() * Scalar::from_repr(scalar).unwrap(), + Point::from_bytes(&scaled_g).unwrap() ); } diff --git a/crypto/ed448/src/scalar.rs b/crypto/ed448/src/scalar.rs deleted file mode 100644 index ec8d0ad4..00000000 --- a/crypto/ed448/src/scalar.rs +++ /dev/null @@ -1,69 +0,0 @@ -use zeroize::{DefaultIsZeroes, Zeroize}; - -use crypto_bigint::{ - U448, U896, U1024, - modular::constant_mod::{ResidueParams, Residue}, -}; - -const MODULUS_STR: &str = concat!( - "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", -); - -impl_modulus!(ScalarModulus, U448, MODULUS_STR); -type ResidueType = Residue; - -/// Ed448 Scalar field element. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -pub struct Scalar(pub(crate) ResidueType); - -impl DefaultIsZeroes for Scalar {} - -// 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885 -pub(crate) const MODULUS: U448 = U448::from_be_hex(MODULUS_STR); - -const WIDE_MODULUS: U896 = U896::from_be_hex(concat!( - "00000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", -)); - -const WIDE_REDUCTION_MODULUS: NonZero = NonZero::from_uint(U1024::from_be_hex(concat!( - "00000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", -))); - -field!( - Scalar, - ResidueType, - MODULUS_STR, - MODULUS, - WIDE_MODULUS, - 446, - 2, - concat!( - "04000000000000000000000000000000000000000000000000000000", - "00000000000000000000000000000000000000000000000000000000", - ), -); - -impl Scalar { - /// Perform a wide reduction to obtain a non-biased Scalar. - pub fn wide_reduce(bytes: [u8; 114]) -> Scalar { - let mut bytes_128 = [0; 128]; - bytes_128[.. 114].copy_from_slice(&bytes); - let wide = U1024::from_le_slice(&bytes_128); - Scalar(Residue::new(&U448::from_le_slice( - &wide.rem(&WIDE_REDUCTION_MODULUS).to_le_bytes()[.. 56], - ))) - } -} - -#[test] -fn test_scalar() { - ff_group_tests::prime_field::test_prime_field_bits::<_, Scalar>(&mut rand_core::OsRng); -} diff --git a/crypto/evrf/embedwards25519/Cargo.toml b/crypto/evrf/embedwards25519/Cargo.toml index de142f4f..55a979cf 100644 --- a/crypto/evrf/embedwards25519/Cargo.toml +++ b/crypto/evrf/embedwards25519/Cargo.toml @@ -7,26 +7,22 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/embedw authors = ["Luke Parker "] keywords = ["curve25519", "ed25519", "ristretto255", "group"] edition = "2021" -rust-version = "1.83" +rust-version = "1.86" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -rustversion = "1" hex-literal = { version = "0.4", default-features = false } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true } -rand_core = { version = "0.6", default-features = false } - zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } -subtle = { version = "^2.4", default-features = false } generic-array = { version = "1", default-features = false } -crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] } +prime-field = { path = "../../prime-field", default-features = false } dalek-ff-group = { path = "../../dalek-ff-group", version = "0.4", default-features = false } blake2 = { version = "0.10", default-features = false } @@ -42,6 +38,6 @@ rand_core = { version = "0.6", features = ["std"] } ff-group-tests = { path = "../../ff-group-tests" } [features] -alloc = ["std-shims", "zeroize/alloc", "ciphersuite/alloc"] -std = ["std-shims/std", "rand_core/std", "zeroize/std", "subtle/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"] +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"] default = ["std"] diff --git a/crypto/evrf/embedwards25519/src/backend.rs b/crypto/evrf/embedwards25519/src/backend.rs deleted file mode 100644 index 2a5f962a..00000000 --- a/crypto/evrf/embedwards25519/src/backend.rs +++ /dev/null @@ -1,299 +0,0 @@ -use zeroize::Zeroize; - -// Use black_box when possible -#[rustversion::since(1.66)] -use core::hint::black_box; -#[rustversion::before(1.66)] -fn black_box(val: T) -> T { - val -} - -pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 { - let bit_ref = black_box(bit_ref); - - let mut bit = black_box(*bit_ref); - let res = black_box(bit as u8); - bit.zeroize(); - debug_assert!((res | 1) == 1); - - bit_ref.zeroize(); - res -} - -macro_rules! math_op { - ( - $Value: ident, - $Other: ident, - $Op: ident, - $op_fn: ident, - $Assign: ident, - $assign_fn: ident, - $function: expr - ) => { - impl $Op<$Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: $Other) -> Self::Output { - Self($function(self.0, other.0)) - } - } - impl $Assign<$Other> for $Value { - fn $assign_fn(&mut self, other: $Other) { - self.0 = $function(self.0, other.0); - } - } - impl<'a> $Op<&'a $Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: &'a $Other) -> Self::Output { - Self($function(self.0, other.0)) - } - } - impl<'a> $Assign<&'a $Other> for $Value { - fn $assign_fn(&mut self, other: &'a $Other) { - self.0 = $function(self.0, other.0); - } - } - }; -} - -macro_rules! from_wrapper { - ($wrapper: ident, $inner: ident, $uint: ident) => { - impl From<$uint> for $wrapper { - fn from(a: $uint) -> $wrapper { - Self(Residue::new(&$inner::from(a))) - } - } - }; -} - -macro_rules! field { - ( - $FieldName: ident, - $ResidueType: ident, - - $MODULUS_STR: ident, - $MODULUS: ident, - $WIDE_MODULUS: ident, - - $NUM_BITS: literal, - $MULTIPLICATIVE_GENERATOR: literal, - $S: literal, - $ROOT_OF_UNITY: literal, - $DELTA: literal, - ) => { - use core::{ - ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign}, - iter::{Sum, Product}, - }; - - use subtle::{Choice, CtOption, ConstantTimeEq, ConstantTimeLess, ConditionallySelectable}; - use rand_core::RngCore; - - use crypto_bigint::{Integer, NonZero, Encoding, impl_modulus}; - - use ciphersuite::group::ff::{ - Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes, helpers::sqrt_ratio_generic, - }; - - use $crate::backend::u8_from_bool; - - fn reduce(x: U512) -> U256 { - U256::from_le_slice(&x.rem(&NonZero::new($WIDE_MODULUS).unwrap()).to_le_bytes()[.. 32]) - } - - impl ConstantTimeEq for $FieldName { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } - } - - impl ConditionallySelectable for $FieldName { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - $FieldName(Residue::conditional_select(&a.0, &b.0, choice)) - } - } - - math_op!($FieldName, $FieldName, Add, add, AddAssign, add_assign, |x: $ResidueType, y| x - .add(&y)); - math_op!($FieldName, $FieldName, Sub, sub, SubAssign, sub_assign, |x: $ResidueType, y| x - .sub(&y)); - math_op!($FieldName, $FieldName, Mul, mul, MulAssign, mul_assign, |x: $ResidueType, y| x - .mul(&y)); - - from_wrapper!($FieldName, U256, u8); - from_wrapper!($FieldName, U256, u16); - from_wrapper!($FieldName, U256, u32); - from_wrapper!($FieldName, U256, u64); - from_wrapper!($FieldName, U256, u128); - - impl Neg for $FieldName { - type Output = $FieldName; - fn neg(self) -> $FieldName { - Self(self.0.neg()) - } - } - - impl<'a> Neg for &'a $FieldName { - type Output = $FieldName; - fn neg(self) -> Self::Output { - (*self).neg() - } - } - - impl $FieldName { - /// Perform an exponentation. - pub fn pow(&self, other: $FieldName) -> $FieldName { - let mut table = [Self(Residue::ONE); 16]; - table[1] = *self; - for i in 2 .. 16 { - table[i] = table[i - 1] * self; - } - - let mut res = Self(Residue::ONE); - 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; - } - } - - let mut factor = table[0]; - for (j, candidate) in table[1 ..].iter().enumerate() { - let j = j + 1; - factor = Self::conditional_select(&factor, &candidate, usize::from(bits).ct_eq(&j)); - } - res *= factor; - bits = 0; - } - } - res - } - } - - impl Field for $FieldName { - const ZERO: Self = Self(Residue::ZERO); - const ONE: Self = Self(Residue::ONE); - - fn random(mut rng: impl RngCore) -> Self { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - $FieldName(Residue::new(&reduce(U512::from_le_slice(bytes.as_ref())))) - } - - fn square(&self) -> Self { - Self(self.0.square()) - } - fn double(&self) -> Self { - *self + self - } - - fn invert(&self) -> CtOption { - let res = self.0.invert(); - CtOption::new(Self(res.0), res.1.into()) - } - - fn sqrt(&self) -> CtOption { - // (p + 1) // 4, as valid since p % 4 == 3 - let mod_plus_one_div_four = $MODULUS.saturating_add(&U256::ONE).wrapping_div(&(4u8.into())); - let res = self.pow(Self($ResidueType::new_checked(&mod_plus_one_div_four).unwrap())); - CtOption::new(res, res.square().ct_eq(self)) - } - - fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - sqrt_ratio_generic(num, div) - } - } - - impl PrimeField for $FieldName { - type Repr = [u8; 32]; - - const MODULUS: &'static str = $MODULUS_STR; - - const NUM_BITS: u32 = $NUM_BITS; - const CAPACITY: u32 = $NUM_BITS - 1; - - const TWO_INV: Self = $FieldName($ResidueType::new(&U256::from_u8(2)).invert().0); - - const MULTIPLICATIVE_GENERATOR: Self = - Self(Residue::new(&U256::from_u8($MULTIPLICATIVE_GENERATOR))); - const S: u32 = $S; - - const ROOT_OF_UNITY: Self = $FieldName(Residue::new(&U256::from_be_hex($ROOT_OF_UNITY))); - const ROOT_OF_UNITY_INV: Self = Self(Self::ROOT_OF_UNITY.0.invert().0); - - const DELTA: Self = $FieldName(Residue::new(&U256::from_be_hex($DELTA))); - - fn from_repr(bytes: Self::Repr) -> CtOption { - let res = U256::from_le_slice(&bytes); - CtOption::new($FieldName(Residue::new(&res)), res.ct_lt(&$MODULUS)) - } - fn to_repr(&self) -> Self::Repr { - let mut repr = [0; 32]; - repr.copy_from_slice(&self.0.retrieve().to_le_bytes()); - repr - } - - fn is_odd(&self) -> Choice { - self.0.retrieve().is_odd() - } - } - - impl PrimeFieldBits for $FieldName { - type ReprBits = [u8; 32]; - - fn to_le_bits(&self) -> FieldBits { - self.to_repr().into() - } - - fn char_le_bits() -> FieldBits { - let mut repr = [0; 32]; - repr.copy_from_slice(&MODULUS.to_le_bytes()); - repr.into() - } - } - - impl FromUniformBytes<64> for $FieldName { - fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { - $FieldName(Residue::new(&reduce(U512::from_le_slice(bytes)))) - } - } - - impl Sum<$FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - let mut res = $FieldName::ZERO; - for item in iter { - res += item; - } - res - } - } - - impl<'a> Sum<&'a $FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - iter.cloned().sum() - } - } - - impl Product<$FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - let mut res = $FieldName::ONE; - for item in iter { - res *= item; - } - res - } - } - - impl<'a> Product<&'a $FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - iter.cloned().product() - } - } - }; -} diff --git a/crypto/evrf/embedwards25519/src/lib.rs b/crypto/evrf/embedwards25519/src/lib.rs index da4789b4..a252167c 100644 --- a/crypto/evrf/embedwards25519/src/lib.rs +++ b/crypto/evrf/embedwards25519/src/lib.rs @@ -8,19 +8,38 @@ use std_shims::prelude::*; use std_shims::io::{self, Read}; use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}; -use ciphersuite::group::{ff::PrimeField, Group}; +use ciphersuite::group::{ + ff::{PrimeField, FromUniformBytes}, + Group, +}; -#[macro_use] -mod backend; - -mod scalar; -pub use scalar::Scalar; +prime_field::odd_prime_field!( + Scalar, + "0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b", + "0a", + false +); pub use dalek_ff_group::Scalar as FieldElement; mod point; pub use point::Point; +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 Embedwards25519. /// /// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition @@ -41,7 +60,9 @@ impl ciphersuite::Ciphersuite for Embedwards25519 { fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { use blake2::Digest; - Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap()) + >::from_uniform_bytes( + &Self::H::digest([dst, data].concat()).into(), + ) } // We override the provided impl, which compares against the reserialization, because diff --git a/crypto/evrf/embedwards25519/src/point.rs b/crypto/evrf/embedwards25519/src/point.rs index fb7fc9a3..4c2fb140 100644 --- a/crypto/evrf/embedwards25519/src/point.rs +++ b/crypto/evrf/embedwards25519/src/point.rs @@ -3,10 +3,11 @@ use core::{ iter::Sum, }; -use rand_core::RngCore; - -use zeroize::Zeroize; -use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable}; +use prime_field::{ + subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable}, + zeroize::Zeroize, + rand_core::RngCore, +}; use ciphersuite::group::{ ff::{Field, PrimeField, PrimeFieldBits}, @@ -14,7 +15,7 @@ use ciphersuite::group::{ prime::PrimeGroup, }; -use crate::{backend::u8_from_bool, Scalar, FieldElement}; +use crate::{u8_from_bool, Scalar, FieldElement}; #[allow(non_snake_case)] fn B() -> FieldElement { diff --git a/crypto/evrf/embedwards25519/src/scalar.rs b/crypto/evrf/embedwards25519/src/scalar.rs deleted file mode 100644 index f2d6e61f..00000000 --- a/crypto/evrf/embedwards25519/src/scalar.rs +++ /dev/null @@ -1,52 +0,0 @@ -use zeroize::{DefaultIsZeroes, Zeroize}; - -use crypto_bigint::{ - U256, U512, - modular::constant_mod::{ResidueParams, Residue}, -}; - -const MODULUS_STR: &str = "0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b"; - -impl_modulus!(EmbedwardsQ, U256, MODULUS_STR); -type ResidueType = Residue; - -/// The Scalar field of Embedwards25519. -/// -/// This is equivalent to the field secp256k1 is defined over. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -#[repr(C)] -pub struct Scalar(pub(crate) ResidueType); - -impl DefaultIsZeroes for Scalar {} - -pub(crate) const MODULUS: U256 = U256::from_be_hex(MODULUS_STR); - -const WIDE_MODULUS: U512 = U512::from_be_hex(concat!( - "0000000000000000000000000000000000000000000000000000000000000000", - "0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b", -)); - -field!( - Scalar, - ResidueType, - MODULUS_STR, - MODULUS, - WIDE_MODULUS, - 252, - 10, - 1, - "0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96a", - "0000000000000000000000000000000000000000000000000000000000000064", -); - -impl Scalar { - /// Perform a wide reduction, presumably to obtain a non-biased Scalar field element. - pub fn wide_reduce(bytes: [u8; 64]) -> Scalar { - Scalar(Residue::new(&reduce(U512::from_le_slice(bytes.as_ref())))) - } -} - -#[test] -fn test_scalar_field() { - ff_group_tests::prime_field::test_prime_field_bits::<_, Scalar>(&mut rand_core::OsRng); -} diff --git a/crypto/evrf/secq256k1/Cargo.toml b/crypto/evrf/secq256k1/Cargo.toml index 174dd1c0..09c38411 100644 --- a/crypto/evrf/secq256k1/Cargo.toml +++ b/crypto/evrf/secq256k1/Cargo.toml @@ -7,19 +7,19 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/secq25 authors = ["Luke Parker "] keywords = ["secp256k1", "secq256k1", "group"] edition = "2021" -rust-version = "1.85" +rust-version = "1.86" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -rustversion = "1" hex-literal = { version = "0.4", default-features = false } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true } k256 = { version = "0.13", default-features = false, features = ["arithmetic"] } +prime-field = { path = "../../prime-field", default-features = false } blake2 = { version = "0.10", default-features = false } ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false } @@ -34,6 +34,6 @@ rand_core = { version = "0.6", features = ["std"] } ff-group-tests = { path = "../../ff-group-tests" } [features] -alloc = ["std-shims", "k256/alloc", "ciphersuite/alloc"] -std = ["std-shims/std", "k256/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"] +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"] default = ["std"] diff --git a/crypto/evrf/secq256k1/src/backend.rs b/crypto/evrf/secq256k1/src/backend.rs deleted file mode 100644 index 5e3fdbcb..00000000 --- a/crypto/evrf/secq256k1/src/backend.rs +++ /dev/null @@ -1,301 +0,0 @@ -use k256::elliptic_curve::zeroize::Zeroize; - -// Use black_box when possible -#[rustversion::since(1.66)] -use core::hint::black_box; -#[rustversion::before(1.66)] -fn black_box(val: T) -> T { - val -} - -pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 { - let bit_ref = black_box(bit_ref); - - let mut bit = black_box(*bit_ref); - let res = black_box(bit as u8); - bit.zeroize(); - debug_assert!((res | 1) == 1); - - bit_ref.zeroize(); - res -} - -macro_rules! math_op { - ( - $Value: ident, - $Other: ident, - $Op: ident, - $op_fn: ident, - $Assign: ident, - $assign_fn: ident, - $function: expr - ) => { - impl $Op<$Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: $Other) -> Self::Output { - Self($function(self.0, other.0)) - } - } - impl $Assign<$Other> for $Value { - fn $assign_fn(&mut self, other: $Other) { - self.0 = $function(self.0, other.0); - } - } - impl<'a> $Op<&'a $Other> for $Value { - type Output = $Value; - fn $op_fn(self, other: &'a $Other) -> Self::Output { - Self($function(self.0, other.0)) - } - } - impl<'a> $Assign<&'a $Other> for $Value { - fn $assign_fn(&mut self, other: &'a $Other) { - self.0 = $function(self.0, other.0); - } - } - }; -} - -macro_rules! from_wrapper { - ($wrapper: ident, $inner: ident, $uint: ident) => { - impl From<$uint> for $wrapper { - fn from(a: $uint) -> $wrapper { - Self(Residue::new(&$inner::from(a))) - } - } - }; -} - -macro_rules! field { - ( - $FieldName: ident, - $ResidueType: ident, - - $MODULUS_STR: ident, - $MODULUS: ident, - $WIDE_MODULUS: ident, - - $NUM_BITS: literal, - $MULTIPLICATIVE_GENERATOR: literal, - $S: literal, - $ROOT_OF_UNITY: literal, - $DELTA: literal, - ) => { - use core::{ - ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign}, - iter::{Sum, Product}, - }; - - use k256::elliptic_curve::{ - subtle::{Choice, CtOption, ConstantTimeEq, ConstantTimeLess, ConditionallySelectable}, - rand_core::RngCore, - bigint::{Integer, NonZero, Encoding, impl_modulus}, - group::ff::{ - Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes, helpers::sqrt_ratio_generic, - }, - }; - - use $crate::backend::u8_from_bool; - - fn reduce(x: U512) -> U256 { - U256::from_le_slice(&x.rem(&NonZero::new($WIDE_MODULUS).unwrap()).to_le_bytes()[.. 32]) - } - - impl ConstantTimeEq for $FieldName { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } - } - - impl ConditionallySelectable for $FieldName { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - $FieldName(Residue::conditional_select(&a.0, &b.0, choice)) - } - } - - math_op!($FieldName, $FieldName, Add, add, AddAssign, add_assign, |x: $ResidueType, y| x - .add(&y)); - math_op!($FieldName, $FieldName, Sub, sub, SubAssign, sub_assign, |x: $ResidueType, y| x - .sub(&y)); - math_op!($FieldName, $FieldName, Mul, mul, MulAssign, mul_assign, |x: $ResidueType, y| x - .mul(&y)); - - from_wrapper!($FieldName, U256, u8); - from_wrapper!($FieldName, U256, u16); - from_wrapper!($FieldName, U256, u32); - from_wrapper!($FieldName, U256, u64); - from_wrapper!($FieldName, U256, u128); - - impl Neg for $FieldName { - type Output = $FieldName; - fn neg(self) -> $FieldName { - Self(self.0.neg()) - } - } - - impl<'a> Neg for &'a $FieldName { - type Output = $FieldName; - fn neg(self) -> Self::Output { - (*self).neg() - } - } - - impl $FieldName { - /// Perform an exponentation. - pub fn pow(&self, other: $FieldName) -> $FieldName { - let mut table = [Self(Residue::ONE); 16]; - table[1] = *self; - for i in 2 .. 16 { - table[i] = table[i - 1] * self; - } - - let mut res = Self(Residue::ONE); - 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; - } - } - - let mut factor = table[0]; - for (j, candidate) in table[1 ..].iter().enumerate() { - let j = j + 1; - factor = Self::conditional_select(&factor, &candidate, usize::from(bits).ct_eq(&j)); - } - res *= factor; - bits = 0; - } - } - res - } - } - - impl Field for $FieldName { - const ZERO: Self = Self(Residue::ZERO); - const ONE: Self = Self(Residue::ONE); - - fn random(mut rng: impl RngCore) -> Self { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - $FieldName(Residue::new(&reduce(U512::from_be_slice(bytes.as_ref())))) - } - - fn square(&self) -> Self { - Self(self.0.square()) - } - fn double(&self) -> Self { - *self + self - } - - fn invert(&self) -> CtOption { - let res = self.0.invert(); - CtOption::new(Self(res.0), res.1.into()) - } - - fn sqrt(&self) -> CtOption { - // (p + 1) // 4, as valid since p % 4 == 3 - let mod_plus_one_div_four = $MODULUS.saturating_add(&U256::ONE).wrapping_div(&(4u8.into())); - let res = self.pow(Self($ResidueType::new_checked(&mod_plus_one_div_four).unwrap())); - CtOption::new(res, res.square().ct_eq(self)) - } - - fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - sqrt_ratio_generic(num, div) - } - } - - impl PrimeField for $FieldName { - type Repr = [u8; 32]; - - const MODULUS: &'static str = $MODULUS_STR; - - const NUM_BITS: u32 = $NUM_BITS; - const CAPACITY: u32 = $NUM_BITS - 1; - - const TWO_INV: Self = $FieldName($ResidueType::new(&U256::from_u8(2)).invert().0); - - const MULTIPLICATIVE_GENERATOR: Self = - Self(Residue::new(&U256::from_u8($MULTIPLICATIVE_GENERATOR))); - const S: u32 = $S; - - const ROOT_OF_UNITY: Self = $FieldName(Residue::new(&U256::from_be_hex($ROOT_OF_UNITY))); - const ROOT_OF_UNITY_INV: Self = Self(Self::ROOT_OF_UNITY.0.invert().0); - - const DELTA: Self = $FieldName(Residue::new(&U256::from_be_hex($DELTA))); - - fn from_repr(bytes: Self::Repr) -> CtOption { - let res = U256::from_be_slice(&bytes); - CtOption::new($FieldName(Residue::new(&res)), res.ct_lt(&$MODULUS)) - } - fn to_repr(&self) -> Self::Repr { - let mut repr = [0; 32]; - repr.copy_from_slice(&self.0.retrieve().to_be_bytes()); - repr - } - - fn is_odd(&self) -> Choice { - self.0.retrieve().is_odd() - } - } - - impl PrimeFieldBits for $FieldName { - type ReprBits = [u8; 32]; - - fn to_le_bits(&self) -> FieldBits { - let mut repr = [0; 32]; - repr.copy_from_slice(&self.0.retrieve().to_le_bytes()); - repr.into() - } - - fn char_le_bits() -> FieldBits { - let mut repr = [0; 32]; - repr.copy_from_slice(&MODULUS.to_le_bytes()); - repr.into() - } - } - - impl FromUniformBytes<64> for $FieldName { - fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { - $FieldName(Residue::new(&reduce(U512::from_le_slice(bytes)))) - } - } - - impl Sum<$FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - let mut res = $FieldName::ZERO; - for item in iter { - res += item; - } - res - } - } - - impl<'a> Sum<&'a $FieldName> for $FieldName { - fn sum>(iter: I) -> $FieldName { - iter.cloned().sum() - } - } - - impl Product<$FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - let mut res = $FieldName::ONE; - for item in iter { - res *= item; - } - res - } - } - - impl<'a> Product<&'a $FieldName> for $FieldName { - fn product>(iter: I) -> $FieldName { - iter.cloned().product() - } - } - }; -} diff --git a/crypto/evrf/secq256k1/src/lib.rs b/crypto/evrf/secq256k1/src/lib.rs index 747ef11b..2e9cd052 100644 --- a/crypto/evrf/secq256k1/src/lib.rs +++ b/crypto/evrf/secq256k1/src/lib.rs @@ -10,20 +10,39 @@ use std_shims::io::{self, Read}; use k256::elliptic_curve::{ zeroize::Zeroize, generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}, - group::{ff::PrimeField, Group}, + group::{ + ff::{PrimeField, FromUniformBytes}, + Group, + }, }; -#[macro_use] -mod backend; - -mod scalar; -pub use scalar::Scalar; +prime_field::odd_prime_field!( + Scalar, + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "03", + true +); pub use k256::Scalar as FieldElement; mod point; pub use point::Point; +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 @@ -47,7 +66,9 @@ impl ciphersuite::Ciphersuite for Secq256k1 { fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { use blake2::Digest; - Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap()) + >::from_uniform_bytes( + &Self::H::digest([dst, data].concat()).into(), + ) } // We override the provided impl, which compares against the reserialization, because diff --git a/crypto/evrf/secq256k1/src/point.rs b/crypto/evrf/secq256k1/src/point.rs index 1b4c6bd8..25502f19 100644 --- a/crypto/evrf/secq256k1/src/point.rs +++ b/crypto/evrf/secq256k1/src/point.rs @@ -15,7 +15,7 @@ use k256::elliptic_curve::{ }, }; -use crate::{backend::u8_from_bool, Scalar, FieldElement}; +use crate::{u8_from_bool, Scalar, FieldElement}; fn recover_y(x: FieldElement) -> CtOption { // x**3 + B since a = 0 diff --git a/crypto/evrf/secq256k1/src/scalar.rs b/crypto/evrf/secq256k1/src/scalar.rs deleted file mode 100644 index a420bbc9..00000000 --- a/crypto/evrf/secq256k1/src/scalar.rs +++ /dev/null @@ -1,53 +0,0 @@ -use k256::elliptic_curve::{ - zeroize::{DefaultIsZeroes, Zeroize}, - bigint::{ - U256, U512, - modular::constant_mod::{ResidueParams, Residue}, - }, -}; - -const MODULUS_STR: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"; - -impl_modulus!(SecQ, U256, MODULUS_STR); -type ResidueType = Residue; - -/// The Scalar field of secq256k1. -/// -/// This is equivalent to the field secp256k1 is defined over. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -#[repr(C)] -pub struct Scalar(pub(crate) ResidueType); - -impl DefaultIsZeroes for Scalar {} - -pub(crate) const MODULUS: U256 = U256::from_be_hex(MODULUS_STR); - -const WIDE_MODULUS: U512 = U512::from_be_hex(concat!( - "0000000000000000000000000000000000000000000000000000000000000000", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", -)); - -field!( - Scalar, - ResidueType, - MODULUS_STR, - MODULUS, - WIDE_MODULUS, - 256, - 3, - 1, - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", - "0000000000000000000000000000000000000000000000000000000000000009", -); - -impl Scalar { - /// Perform a wide reduction, presumably to obtain a non-biased Scalar field element. - pub fn wide_reduce(bytes: [u8; 64]) -> Scalar { - Scalar(Residue::new(&reduce(U512::from_le_slice(bytes.as_ref())))) - } -} - -#[test] -fn test_scalar_field() { - ff_group_tests::prime_field::test_prime_field_bits::<_, Scalar>(&mut rand_core::OsRng); -} diff --git a/crypto/frost/src/curve/ed448.rs b/crypto/frost/src/curve/ed448.rs index 4aa5e7ae..34836cd5 100644 --- a/crypto/frost/src/curve/ed448.rs +++ b/crypto/frost/src/curve/ed448.rs @@ -2,7 +2,10 @@ use digest::Digest; use minimal_ed448::{Scalar, Point}; pub use minimal_ed448::Ed448; -pub use ciphersuite::{group::GroupEncoding, Ciphersuite}; +pub use ciphersuite::{ + group::{ff::FromUniformBytes, GroupEncoding}, + Ciphersuite, +}; use crate::{curve::Curve, algorithm::Hram}; @@ -18,17 +21,18 @@ pub(crate) struct Ietf8032Ed448Hram; impl Ietf8032Ed448Hram { #[allow(non_snake_case)] pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar { - Scalar::wide_reduce( - ::H::digest( - [ - &[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(), - context, - &[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(), - ] - .concat(), + Scalar::from_uniform_bytes( + &<[u8; 114]>::try_from( + ::H::digest( + [ + &[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(), + context, + &[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(), + ] + .concat(), + ) + .as_slice(), ) - .as_ref() - .try_into() .unwrap(), ) } diff --git a/crypto/prime-field/Cargo.toml b/crypto/prime-field/Cargo.toml new file mode 100644 index 00000000..66fe5e60 --- /dev/null +++ b/crypto/prime-field/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "prime-field" +version = "0.1.0" +description = "A library to declare an ff::PrimeField via crypto-bigint" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/crypto/prime-field" +authors = ["Luke Parker "] +keywords = ["ff", "field", "prime", "crypto-bigint"] +edition = "2021" +rust-version = "1.86" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +paste = { version = "1", default-features = false } +zeroize = { version = "^1.5", default-features = false } +subtle = { version = "^2.4", default-features = false } +rand_core = { version = "0.6", default-features = false } + +crypto-bigint = { version = "0.6", default-features = false, features = ["zeroize"] } + +ff = { version = "0.13", default-features = false, features = ["bits"] } + +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"] +default = ["std"] diff --git a/crypto/prime-field/LICENSE b/crypto/prime-field/LICENSE new file mode 100644 index 00000000..32ff304a --- /dev/null +++ b/crypto/prime-field/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2025 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crypto/prime-field/README.md b/crypto/prime-field/README.md new file mode 100644 index 00000000..2af2fe09 --- /dev/null +++ b/crypto/prime-field/README.md @@ -0,0 +1,3 @@ +# Prime Field + +A comprehensive macro to declare a prime field. diff --git a/crypto/prime-field/src/lib.rs b/crypto/prime-field/src/lib.rs new file mode 100644 index 00000000..48432f88 --- /dev/null +++ b/crypto/prime-field/src/lib.rs @@ -0,0 +1,521 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![no_std] + +pub use subtle; +pub use zeroize; +pub use rand_core; +pub use crypto_bigint; +pub use ff; + +#[doc(hidden)] +pub mod __prime_field_private { + pub use paste; + pub use ff_group_tests; + + use crypto_bigint::{Word, Uint, modular::ConstMontyParams}; + + /// Remove the "0x"-prefix from a hex string. + /// + /// May panic if the string isn't valid hex. + pub const fn hex_str_without_prefix(hex: &str) -> &str { + if hex.len() < 2 { + return hex; + } + + if hex.as_bytes()[1] == b'x' { + assert!(hex.as_bytes()[0] == b'0', "invalid hex string for modulus"); + hex.split_at(2).1 + } else { + hex + } + } + + pub const fn uint_to_u64_words( + value: Uint, + ) -> [u64; WORDS] { + let mut res = [0u64; WORDS]; + let mut i = 0; + while i < Uint::::LIMBS { + let word: Word = value.as_limbs()[i].0; + let bits = i * (Word::BITS as usize); + let j = bits / (u64::BITS as usize); + res[j] |= word << (bits % (u64::BITS as usize)); + if (j + 1) < WORDS { + if let Some(remaining_bits) = + ((bits % (u64::BITS as usize)) + (Word::BITS as usize)).checked_sub(u64::BITS as usize) + { + if remaining_bits != 0 { + res[j + 1] |= word >> ((Word::BITS as usize) - remaining_bits); + } + } + } + i += 1; + } + res + } + + pub const fn u64_words_to_uint( + words: [u64; WORDS], + ) -> Uint { + let mut reconstruction = Uint::::ZERO; + let mut i = 0; + while i < WORDS { + reconstruction = reconstruction + .bitor(&Uint::::from_u64(words[i]).shl_vartime((i * (u64::BITS as usize)) as u32)); + i += 1; + } + reconstruction + } + + #[allow(non_snake_case)] + pub const fn calculate_S>() -> u32 { + let mut i = 0; + loop { + let bit = P::MODULUS.as_ref().wrapping_sub(&Uint::::ONE).bit_vartime(i); + if !bit { + i += 1; + continue; + } + break; + } + i + } +} + +#[macro_export] +macro_rules! odd_prime_field { + ( + $name: ident, + $modulus_as_be_hex: expr, + $multiplicative_generator_as_be_hex: expr, + $big_endian: literal + ) => { + prime_field::__prime_field_private::paste::paste! { + mod [<$name __prime_field_private>] { + use core::{ + ops::*, + iter::{Sum, Product}, + }; + use prime_field::{ + subtle::{ + Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable, + }, + zeroize::Zeroize, + rand_core::RngCore, + crypto_bigint::{ + Limb, Encoding, Integer, Uint, + modular::{ConstMontyParams, ConstMontyForm}, + impl_modulus, + }, + ff::*, + __prime_field_private::*, + }; + + const MODULUS_WITHOUT_PREFIX: &str = hex_str_without_prefix($modulus_as_be_hex); + const MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX: &str = + hex_str_without_prefix($multiplicative_generator_as_be_hex); + + const MODULUS_BYTES: usize = MODULUS_WITHOUT_PREFIX.len() / 2; + type UnderlyingUint = Uint<{ MODULUS_BYTES.div_ceil(Limb::BYTES) }>; + + const PADDED_MODULUS_WITHOUT_PREFIX_BYTES: [u8; 2 * UnderlyingUint::BYTES] = { + let mut res = [b'0'; 2 * UnderlyingUint::BYTES]; + let start = (2 * UnderlyingUint::BYTES) - MODULUS_WITHOUT_PREFIX.len(); + let mut i = start; + while i < (2 * UnderlyingUint::BYTES) { + res[i] = MODULUS_WITHOUT_PREFIX.as_bytes()[i - start]; + i += 1; + } + res + }; + const PADDED_MODULUS_WITHOUT_PREFIX: &str = { + match core::str::from_utf8(&PADDED_MODULUS_WITHOUT_PREFIX_BYTES) { + Ok(res) => res, + Err(_) => panic!("couldn't successfully pad modulus"), + } + }; + + const PADDED_MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX_BYTES: + [u8; 2 * UnderlyingUint::BYTES] = { + let mut res = [b'0'; 2 * UnderlyingUint::BYTES]; + let start = (2 * UnderlyingUint::BYTES) - MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX.len(); + let mut i = start; + while i < (2 * UnderlyingUint::BYTES) { + res[i] = MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX.as_bytes()[i - start]; + i += 1; + } + res + }; + const PADDED_MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX: &str = { + match core::str::from_utf8(&PADDED_MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX_BYTES) { + Ok(res) => res, + Err(_) => panic!("couldn't successfully pad multiplicative generator"), + } + }; + + impl_modulus!(Params, UnderlyingUint, PADDED_MODULUS_WITHOUT_PREFIX); + type Underlying = ConstMontyForm; + + const MODULUS: &UnderlyingUint = Params::MODULUS.as_ref(); + const MODULUS_MINUS_ONE: UnderlyingUint = MODULUS.wrapping_sub(&UnderlyingUint::ONE); + const MODULUS_MINUS_TWO: UnderlyingUint = MODULUS.wrapping_sub(&UnderlyingUint::from_u8(2)); + const T: UnderlyingUint = MODULUS_MINUS_ONE.shr_vartime($name::S); + + /// A field automatically generated with `short-weierstrass`. + #[derive(Clone, Copy, Eq, Debug)] + pub struct $name(Underlying); + + impl Default for $name { + fn default() -> Self { + Self::ZERO + } + } + + impl $name { + /// Create a `$name` from the `Uint` type underlying it. + pub const fn from(value: &UnderlyingUint) -> Self { + $name(Underlying::new(value)) + } + } + impl From for $name { + fn from(value: u8) -> Self { + Self::from(&UnderlyingUint::from(value)) + } + } + impl From for $name { + fn from(value: u16) -> Self { + Self::from(&UnderlyingUint::from(value)) + } + } + impl From for $name { + fn from(value: u32) -> Self { + Self::from(&UnderlyingUint::from(value)) + } + } + impl From for $name { + fn from(value: u64) -> Self { + Self::from(&UnderlyingUint::from(value)) + } + } + + impl ConstantTimeEq for $name { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } + } + impl PartialEq for $name { + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } + } + + impl ConditionallySelectable for $name { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self(<_>::conditional_select(&a.0, &b.0, choice)) + } + } + impl ConditionallyNegatable for $name { + fn conditional_negate(&mut self, negate: Choice) { + self.0.conditional_negate(negate) + } + } + + impl Zeroize for $name { + fn zeroize(&mut self) { + self.0.zeroize(); + } + } + + impl Neg for $name { + type Output = Self; + fn neg(self) -> Self { + Self(-self.0) + } + } + + impl Add for $name { + type Output = Self; + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } + } + impl Sub for $name { + type Output = Self; + fn sub(self, other: Self) -> Self { + Self(self.0 - other.0) + } + } + impl Mul for $name { + type Output = Self; + fn mul(self, other: Self) -> Self { + Self(self.0 * other.0) + } + } + impl AddAssign for $name { + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + } + } + impl SubAssign for $name { + fn sub_assign(&mut self, other: Self) { + self.0 -= other.0; + } + } + impl MulAssign for $name { + fn mul_assign(&mut self, other: Self) { + self.0 *= other.0; + } + } + impl Sum for $name { + fn sum>(iter: I) -> Self { + let mut res = Self::ZERO; + for item in iter { + res += item; + } + res + } + } + impl Product for $name { + fn product>(iter: I) -> Self { + let mut res = Self::ONE; + for item in iter { + res *= item; + } + res + } + } + impl<'a> Add<&'a Self> for $name { + type Output = Self; + fn add(self, other: &'a Self) -> Self { + Self(self.0 + other.0) + } + } + impl<'a> Sub<&'a Self> for $name { + type Output = Self; + fn sub(self, other: &'a Self) -> Self { + Self(self.0 - other.0) + } + } + impl<'a> Mul<&'a Self> for $name { + type Output = Self; + fn mul(self, other: &'a Self) -> Self { + Self(self.0 * other.0) + } + } + impl<'a> Sum<&'a Self> for $name { + fn sum>(iter: I) -> Self { + let mut res = Self::ZERO; + for item in iter { + res += item; + } + res + } + } + impl<'a> Product<&'a Self> for $name { + fn product>(iter: I) -> Self { + let mut res = Self::ONE; + for item in iter { + res *= item; + } + res + } + } + impl<'a> AddAssign<&'a Self> for $name { + fn add_assign(&mut self, other: &'a Self) { + self.0 += other.0; + } + } + impl<'a> SubAssign<&'a Self> for $name { + fn sub_assign(&mut self, other: &'a Self) { + self.0 -= other.0; + } + } + impl<'a> MulAssign<&'a Self> for $name { + fn mul_assign(&mut self, other: &'a Self) { + self.0 *= other.0; + } + } + + impl Field for $name { + const ZERO: Self = Self(Underlying::ZERO); + const ONE: Self = Self(Underlying::ONE); + fn random(mut rng: impl RngCore) -> Self { + let mut bytes = [0; 2 * MODULUS_BYTES]; + rng.fill_bytes(&mut bytes); + Self::from_uniform_bytes(&bytes) + } + fn square(&self) -> Self { + Self(self.0.square()) + } + fn double(&self) -> Self { + Self(self.0.double()) + } + fn invert(&self) -> CtOption { + CtOption::from(self.0.inv()).map(Self) + } + fn sqrt(&self) -> CtOption { + const THREE_MOD_FOUR: bool = (MODULUS.as_words()[0] % 4) == 3; + const ONE_MOD_EIGHT: bool = (MODULUS.as_words()[0] % 8) == 1; + const FIVE_MOD_EIGHT: bool = (MODULUS.as_words()[0] % 8) == 5; + + let sqrt = if THREE_MOD_FOUR { + const SQRT_EXP: UnderlyingUint = + MODULUS.shr_vartime(2).wrapping_add(&UnderlyingUint::ONE); + Self(self.0.pow(&SQRT_EXP)) + } else if ONE_MOD_EIGHT { + const TM1D2: UnderlyingUint = (T.wrapping_sub(&UnderlyingUint::ONE)).shr_vartime(1); + const TM1D2_WORDS_LEN: usize = UnderlyingUint::BITS.div_ceil(u64::BITS) as usize; + const TM1D2_WORDS: [u64; TM1D2_WORDS_LEN] = uint_to_u64_words(TM1D2); + + const TM1D2_RECONSTRUCTION: UnderlyingUint = u64_words_to_uint(TM1D2_WORDS); + const RECONSTRUCTION_EQUALS_VALUE: bool = { + let mut i = 0; + let mut res = true; + while i < TM1D2_WORDS_LEN { + res &= TM1D2_RECONSTRUCTION.as_words()[i] == TM1D2.as_words()[i]; + i += 1; + } + res + }; + const _ASSERT_RECONSTRUCTION_EQUALS_VALUE: + [(); 0 - ((!RECONSTRUCTION_EQUALS_VALUE) as usize)] = [(); _]; + + helpers::sqrt_tonelli_shanks::(self, TM1D2_WORDS).unwrap_or(Self::ZERO) + } else { + const SQRT_EXP: UnderlyingUint = MODULUS.shr_vartime(3); + let upsilon = self.double().0.pow(&SQRT_EXP); + let i = (upsilon.square() * &self.0).double(); + Self(upsilon * self.0 * (i - Self::ONE.0)) + }; + + let sqrt = <_>::conditional_select(&sqrt, &-sqrt, sqrt.0.retrieve().is_odd()); + CtOption::new(sqrt, sqrt.square().ct_eq(self)) + } + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + helpers::sqrt_ratio_generic(num, div) + } + } + + #[derive(Clone, Copy)] + pub struct Repr([u8; MODULUS_BYTES]); + impl Default for Repr { + fn default() -> Self { + Self([0; _]) + } + } + impl AsRef<[u8]> for Repr { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } + } + impl AsMut<[u8]> for Repr { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } + } + + impl PrimeField for $name { + type Repr = Repr; + const MODULUS: &str = $modulus_as_be_hex; + const NUM_BITS: u32 = MODULUS.bits(); + const CAPACITY: u32 = Self::NUM_BITS - 1; + const TWO_INV: Self = + Self(Underlying::new(&UnderlyingUint::from_u8(2)).pow(&MODULUS_MINUS_TWO)); + const MULTIPLICATIVE_GENERATOR: Self = Self( + Underlying::new( + &UnderlyingUint::from_be_hex(PADDED_MULTIPLICATIVE_GENERATOR_WITHOUT_PREFIX) + ) + ); + const S: u32 = calculate_S::<_, Params>(); + const ROOT_OF_UNITY: Self = Self(Self::MULTIPLICATIVE_GENERATOR.0.pow(&T)); + const ROOT_OF_UNITY_INV: Self = Self(Self::ROOT_OF_UNITY.0.pow(&MODULUS_MINUS_TWO)); + const DELTA: Self = { + let two_to_the_s = UnderlyingUint::ONE.shl_vartime(Self::S); + Self(Self::MULTIPLICATIVE_GENERATOR.0.pow(&two_to_the_s)) + }; + + fn to_repr(&self) -> Self::Repr { + let mut res = Repr([0; _]); + if $big_endian { + res.0.copy_from_slice( + &self.0.retrieve().to_be_bytes()[(UnderlyingUint::BYTES - MODULUS_BYTES) ..] + ); + } else { + res.0.copy_from_slice(&self.0.retrieve().to_le_bytes()[.. MODULUS_BYTES]); + } + res + } + fn from_repr(repr: Self::Repr) -> CtOption { + let mut expanded_repr = [0; UnderlyingUint::BYTES]; + let result = Self(if $big_endian { + expanded_repr[(UnderlyingUint::BYTES - MODULUS_BYTES) .. ].copy_from_slice(&repr.0); + Underlying::new(&UnderlyingUint::from_be_bytes(expanded_repr)) + } else { + expanded_repr[.. MODULUS_BYTES].copy_from_slice(&repr.0); + Underlying::new(&UnderlyingUint::from_le_bytes(expanded_repr)) + }); + CtOption::new(result, result.to_repr().0.ct_eq(&repr.0)) + } + fn is_odd(&self) -> Choice { + self.0.retrieve().is_odd() + } + } + + impl PrimeFieldBits for $name { + type ReprBits = [u8; UnderlyingUint::BYTES]; + fn to_le_bits(&self) -> FieldBits { + self.0.retrieve().to_le_bytes().into() + } + fn char_le_bits() -> FieldBits { + MODULUS.to_le_bytes().into() + } + } + + impl FromUniformBytes<{ 2 * MODULUS_BYTES }> for $name { + fn from_uniform_bytes(bytes: &[u8; 2 * MODULUS_BYTES]) -> Self { + let mut expanded_wide_repr = [0; 2 * UnderlyingUint::BYTES]; + expanded_wide_repr[.. (2 * MODULUS_BYTES)].copy_from_slice(bytes); + let bytes = expanded_wide_repr; + + let lo = + Underlying::new(&UnderlyingUint::from_le_slice(&bytes[.. UnderlyingUint::BYTES])); + let hi = + Underlying::new(&UnderlyingUint::from_le_slice(&bytes[UnderlyingUint::BYTES ..])); + const HI: Underlying = { + let mut res = Underlying::new(&UnderlyingUint::ONE); + let mut i = 0; + while i < UnderlyingUint::BITS { + res = res.double(); + i += 1; + } + res + }; + Self(lo + (hi * HI)) + } + } + + const BITS_PLUS_SECURITY_LEVEL: usize = + (MODULUS.bits() + MODULUS.bits().div_ceil(2)) as usize; + const BITS_PLUS_SECURITY_LEVEL_BYTES: usize = BITS_PLUS_SECURITY_LEVEL.div_ceil(8); + impl FromUniformBytes<{ BITS_PLUS_SECURITY_LEVEL_BYTES }> for $name { + fn from_uniform_bytes(bytes: &[u8; BITS_PLUS_SECURITY_LEVEL_BYTES]) -> Self { + let mut larger = [0; 2 * MODULUS_BYTES]; + larger[.. BITS_PLUS_SECURITY_LEVEL_BYTES].copy_from_slice(bytes); + Self::from_uniform_bytes(&larger) + } + } + + #[cfg(feature = "std")] + #[test] + fn test() { + use prime_field::__prime_field_private::ff_group_tests; + use ff_group_tests::prime_field::test_prime_field_bits; + test_prime_field_bits::<_, $name>(&mut prime_field::rand_core::OsRng); + } + } + + pub use [<$name __prime_field_private>]::$name; + } + }; +} diff --git a/crypto/prime-field/tests/5mod8.rs b/crypto/prime-field/tests/5mod8.rs new file mode 100644 index 00000000..62debcc6 --- /dev/null +++ b/crypto/prime-field/tests/5mod8.rs @@ -0,0 +1,6 @@ +prime_field::odd_prime_field!( + Scalar, + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + "02", + false +); diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 26a11b95..06e591ac 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -309,12 +309,12 @@ fn embedded_curve_keys(network: Network) -> EmbeddedCurveKeys { embedwards25519: { let key = Zeroizing::new(::F::random(&mut rng)); let pub_key = Embedwards25519::generator() * key.deref(); - (Zeroizing::new(key.to_repr().as_slice().to_vec()), pub_key.to_bytes().to_vec()) + (Zeroizing::new(key.to_repr().as_ref().to_vec()), pub_key.to_bytes().to_vec()) }, secq256k1: { let key = Zeroizing::new(::F::random(&mut rng)); let pub_key = Secq256k1::generator() * key.deref(); - (Zeroizing::new(key.to_repr().as_slice().to_vec()), pub_key.to_bytes().to_vec()) + (Zeroizing::new(key.to_repr().as_ref().to_vec()), pub_key.to_bytes().to_vec()) }, } } diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index feff6515..7d5b41bf 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -28,6 +28,7 @@ multiexp = { path = "../../crypto/multiexp", default-features = false, features schnorr-signatures = { path = "../../crypto/schnorr", default-features = false } +prime-field = { path = "../../crypto/prime-field", default-features = false, features = ["alloc"] } secq256k1 = { path = "../../crypto/evrf/secq256k1", default-features = false } embedwards25519 = { path = "../../crypto/evrf/embedwards25519", default-features = false } diff --git a/tests/no-std/src/lib.rs b/tests/no-std/src/lib.rs index ca84e139..81c7c189 100644 --- a/tests/no-std/src/lib.rs +++ b/tests/no-std/src/lib.rs @@ -11,6 +11,7 @@ pub use multiexp; pub use schnorr_signatures; +pub use prime_field; pub use secq256k1; pub use embedwards25519;