Add prime-field crate

prime-field introduces a macro to generate a prime field, in its entitrety,
de-duplicating code across minimal-ed448, embedwards25519, and secq256k1.
This commit is contained in:
Luke Parker
2025-08-28 03:36:15 -04:00
parent 85949f4b04
commit 220bcbc592
29 changed files with 833 additions and 1301 deletions

View File

@@ -35,6 +35,9 @@ jobs:
-p ciphersuite-kp256 \ -p ciphersuite-kp256 \
-p multiexp \ -p multiexp \
-p schnorr-signatures \ -p schnorr-signatures \
-p prime-field \
-p secq256k1 \
-p embedwards25519 \
-p dkg \ -p dkg \
-p dkg-recovery \ -p dkg-recovery \
-p dkg-dealer \ -p dkg-dealer \

75
Cargo.lock generated
View File

@@ -2298,7 +2298,19 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
"rand_core 0.6.4", "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", "subtle",
"zeroize", "zeroize",
] ]
@@ -2419,7 +2431,7 @@ name = "dalek-ff-group"
version = "0.4.4" version = "0.4.4"
dependencies = [ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"crypto-bigint", "crypto-bigint 0.5.5",
"curve25519-dalek", "curve25519-dalek",
"digest 0.10.7", "digest 0.10.7",
"ff", "ff",
@@ -2505,7 +2517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"syn 1.0.109", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -2884,7 +2896,7 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
"elliptic-curve", "elliptic-curve",
"rfc6979", "rfc6979",
"serdect", "serdect 0.2.0",
"signature", "signature",
"spki", "spki",
] ]
@@ -2956,7 +2968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"crypto-bigint", "crypto-bigint 0.5.5",
"digest 0.10.7", "digest 0.10.7",
"ff", "ff",
"generic-array 0.14.7", "generic-array 0.14.7",
@@ -2964,7 +2976,7 @@ dependencies = [
"pkcs8", "pkcs8",
"rand_core 0.6.4", "rand_core 0.6.4",
"sec1", "sec1",
"serdect", "serdect 0.2.0",
"subtle", "subtle",
"tap", "tap",
"zeroize", "zeroize",
@@ -2976,7 +2988,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"crypto-bigint",
"dalek-ff-group", "dalek-ff-group",
"ec-divisors", "ec-divisors",
"ff-group-tests", "ff-group-tests",
@@ -2984,10 +2995,9 @@ dependencies = [
"generic-array 1.2.0", "generic-array 1.2.0",
"hex", "hex",
"hex-literal", "hex-literal",
"prime-field",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustversion",
"std-shims", "std-shims",
"subtle",
"zeroize", "zeroize",
] ]
@@ -4181,7 +4191,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"socket2 0.4.10", "socket2 0.5.8",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -4672,7 +4682,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"once_cell", "once_cell",
"serdect", "serdect 0.2.0",
"sha2 0.10.9", "sha2 0.10.9",
] ]
@@ -4752,7 +4762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.5", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -5934,16 +5944,11 @@ name = "minimal-ed448"
version = "0.4.2" version = "0.4.2"
dependencies = [ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"crypto-bigint",
"ff",
"ff-group-tests", "ff-group-tests",
"generic-array 1.2.0",
"group",
"hex", "hex",
"prime-field",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustversion",
"sha3", "sha3",
"subtle",
"zeroize", "zeroize",
] ]
@@ -6099,7 +6104,7 @@ name = "monero-generators"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/monero-oxide/monero-oxide?rev=6966575e05fe09b77674c46984b21686ed9304ff#6966575e05fe09b77674c46984b21686ed9304ff" source = "git+https://github.com/monero-oxide/monero-oxide?rev=6966575e05fe09b77674c46984b21686ed9304ff#6966575e05fe09b77674c46984b21686ed9304ff"
dependencies = [ dependencies = [
"crypto-bigint", "crypto-bigint 0.5.5",
"curve25519-dalek", "curve25519-dalek",
"dalek-ff-group", "dalek-ff-group",
"group", "group",
@@ -6610,7 +6615,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [ dependencies = [
"proc-macro-crate 1.3.1", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.106", "syn 2.0.106",
@@ -7215,6 +7220,19 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "primeorder" name = "primeorder"
version = "0.13.6" version = "0.13.6"
@@ -9450,7 +9468,7 @@ dependencies = [
"der", "der",
"generic-array 0.14.7", "generic-array 0.14.7",
"pkcs8", "pkcs8",
"serdect", "serdect 0.2.0",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -9520,8 +9538,8 @@ dependencies = [
"hex", "hex",
"hex-literal", "hex-literal",
"k256", "k256",
"prime-field",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustversion",
"std-shims", "std-shims",
] ]
@@ -10159,6 +10177,7 @@ dependencies = [
"flexible-transcript", "flexible-transcript",
"minimal-ed448", "minimal-ed448",
"multiexp", "multiexp",
"prime-field",
"schnorr-signatures", "schnorr-signatures",
"secq256k1", "secq256k1",
] ]
@@ -10799,6 +10818,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serdect"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
dependencies = [
"base16ct",
"serde",
]
[[package]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.9.8" version = "0.9.8"
@@ -13283,7 +13312,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]

View File

@@ -39,6 +39,7 @@ members = [
"crypto/multiexp", "crypto/multiexp",
"crypto/schnorr", "crypto/schnorr",
"crypto/prime-field",
"crypto/evrf/secq256k1", "crypto/evrf/secq256k1",
"crypto/evrf/embedwards25519", "crypto/evrf/embedwards25519",

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ed448"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["ed448", "ff", "group"] keywords = ["ed448", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.83" rust-version = "1.86"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -17,22 +17,13 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
rustversion = "1" zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
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 }
sha3 = { version = "0.10", default-features = false } sha3 = { version = "0.10", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] } prime-field = { path = "../prime-field", default-features = false }
group = { version = "0.13", default-features = false }
ciphersuite = { path = "../ciphersuite", 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] [dev-dependencies]
hex = { version = "0.4", default-features = false, features = ["std"] } 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" } ff-group-tests = { path = "../ff-group-tests" }
[features] [features]
alloc = ["zeroize/alloc", "ciphersuite/alloc"] alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"]
std = ["alloc", "rand_core/std", "zeroize/std", "subtle/std", "sha3/std", "ff/std", "ciphersuite/std"] std = ["alloc", "zeroize/std", "sha3/std", "prime-field/std", "ciphersuite/std"]
default = ["std"] default = ["std"]

View File

@@ -1,311 +0,0 @@
use zeroize::Zeroize;
// Use black_box when possible
#[rustversion::since(1.66)]
mod black_box {
pub(crate) fn black_box<T>(val: T) -> T {
#[allow(clippy::incompatible_msrv)]
core::hint::black_box(val)
}
}
#[rustversion::before(1.66)]
mod black_box {
pub(crate) fn black_box<T>(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<Self> {
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<Self> {
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<u8, U57>;
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<Self> {
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::<u8, U57>::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<Self::ReprBits> {
let mut repr = [0; 56];
repr.copy_from_slice(&self.to_repr()[.. 56]);
repr.into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
let mut repr = [0; 56];
repr.copy_from_slice(&MODULUS.to_le_bytes()[.. 56]);
repr.into()
}
}
impl Sum<$FieldName> for $FieldName {
fn sum<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a $FieldName> for $FieldName {
fn sum<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().sum()
}
}
impl Product<$FieldName> for $FieldName {
fn product<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a $FieldName> for $FieldName {
fn product<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().product()
}
}
};
}

View File

@@ -8,10 +8,12 @@ use sha3::{
Shake256, Shake256,
}; };
use group::Group; use ciphersuite::{
use crate::{Scalar, Point}; group::{ff::FromUniformBytes, Group},
Ciphersuite,
};
use ciphersuite::Ciphersuite; use crate::{Scalar, Point};
/// Shake256, fixed to a 114-byte output, as used by Ed448. /// Shake256, fixed to a 114-byte output, as used by Ed448.
#[derive(Clone, Default)] #[derive(Clone, Default)]
@@ -69,13 +71,16 @@ impl Ciphersuite for Ed448 {
} }
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { 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] #[test]
fn test_ed448() { fn test_ed448() {
use ff::PrimeField; use ciphersuite::group::ff::PrimeField;
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
@@ -94,7 +99,7 @@ fn test_ed448() {
.unwrap() .unwrap()
) )
.to_repr() .to_repr()
.to_vec(), .as_ref(),
hex::decode( hex::decode(
"\ "\
67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28\ 67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28\
@@ -102,5 +107,6 @@ e7011f3193b9020e73c183a98cc3a519160ed759376dd92c94831622\
00" 00"
) )
.unwrap() .unwrap()
.as_slice()
); );
} }

View File

@@ -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<FieldModulus, { FieldModulus::LIMBS }>;
/// 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);
}

View File

@@ -1,19 +1,47 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![no_std] #![no_std]
#![allow(clippy::redundant_closure_call)]
#[macro_use] prime_field::odd_prime_field!(
mod backend; FieldElement,
// 2**448 - 2**224 - 1
concat!(
"00",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
),
"07",
false
);
mod scalar; prime_field::odd_prime_field!(
pub use scalar::Scalar; Scalar,
concat!(
mod field; "00",
pub use field::FieldElement; "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
),
"02",
false
);
mod point; mod point;
pub use point::Point; pub use point::Point;
mod ciphersuite; mod ciphersuite;
pub use crate::ciphersuite::Ed448; 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
}

View File

@@ -3,50 +3,47 @@ use core::{
iter::Sum, 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 ciphersuite::group::{
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
use crypto_bigint::{U448, modular::constant_mod::Residue};
use group::{
ff::{Field, PrimeField, PrimeFieldBits}, ff::{Field, PrimeField, PrimeFieldBits},
Group, GroupEncoding, Group, GroupEncoding,
prime::PrimeGroup, prime::PrimeGroup,
}; };
use crate::{ use crate::{u8_from_bool, Scalar, FieldElement};
backend::u8_from_bool,
scalar::Scalar,
field::{ResidueType, FieldElement, Q_4},
};
const D: FieldElement = const G_Y: FieldElement = FieldElement::from(&U512::from_be_hex(concat!(
FieldElement(ResidueType::sub(&ResidueType::ZERO, &Residue::new(&U448::from_u16(39081)))); "0000000000000000",
const G_Y: FieldElement = FieldElement(Residue::new(&U448::from_be_hex(concat!(
"693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e", "693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e",
"05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14", "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", "4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324",
"a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e", "a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e",
)))); )));
fn recover_x(y: FieldElement) -> CtOption<FieldElement> { fn recover_x(y: FieldElement) -> CtOption<FieldElement> {
#[allow(non_snake_case)]
let D = -<FieldElement as From<u16>>::from(39081u16);
let ysq = y.square(); let ysq = y.square();
#[allow(non_snake_case)] #[allow(non_snake_case)]
let D_ysq = D * ysq; let D_ysq = D * ysq;
(D_ysq - FieldElement::ONE).invert().and_then(|inverted| { (D_ysq - FieldElement::ONE).invert().and_then(|inverted| {
let temp = (ysq - FieldElement::ONE) * inverted; let xsq = (ysq - FieldElement::ONE) * inverted;
let mut x = temp.pow(Q_4); xsq.sqrt().and_then(|mut x| {
x.conditional_negate(x.is_odd()); x.conditional_negate(x.is_odd());
let xsq = x.square();
CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::ONE + (xsq * D_ysq)))) CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::ONE + (xsq * D_ysq))))
}) })
})
} }
/// Ed448 point. /// Ed448 point.
@@ -104,6 +101,9 @@ impl ConditionallySelectable for Point {
impl Add for Point { impl Add for Point {
type Output = Point; type Output = Point;
fn add(self, other: Self) -> Self { fn add(self, other: Self) -> Self {
#[allow(non_snake_case)]
let D = -<FieldElement as From<u16>>::from(39081u16);
// 12 muls, 7 additions, 4 negations // 12 muls, 7 additions, 4 negations
let xcp = self.x * other.x; let xcp = self.x * other.x;
let ycp = self.y * other.y; let ycp = self.y * other.y;
@@ -302,7 +302,7 @@ impl GroupEncoding for Point {
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> { fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
// Extract and clear the sign bit // 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 bytes = *bytes;
let mut_ref: &mut [u8] = bytes.as_mut(); let mut_ref: &mut [u8] = bytes.as_mut();
mut_ref[56] &= !(1 << 7); mut_ref[56] &= !(1 << 7);
@@ -350,63 +350,48 @@ fn generator() {
#[test] #[test]
fn torsion() { fn torsion() {
use generic_array::GenericArray;
// Uses the originally suggested generator which had torsion // Uses the originally suggested generator which had torsion
let old_y = FieldElement::from_repr(*GenericArray::from_slice( const TORSIONED_Y: &str = "\
&hex::decode(
"\
12796c1532041525945f322e414d434467cfd5c57c9a9af2473b2775\ 12796c1532041525945f322e414d434467cfd5c57c9a9af2473b2775\
8c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa51\ 8c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa51\
00", 00";
) let mut repr = <FieldElement as PrimeField>::Repr::default();
.unwrap(), repr.as_mut().copy_from_slice(&hex::decode(TORSIONED_Y).unwrap());
))
.unwrap(); let old_y = FieldElement::from_repr(repr).unwrap();
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::ONE }; 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(!old.is_torsion_free()));
assert!(bool::from(Point::from_bytes(&old.to_bytes()).is_none()));
} }
#[test] #[test]
fn vector() { fn vector() {
use generic_array::GenericArray; const TWO_G: &str = "\
assert_eq!(
Point::generator().double(),
Point::from_bytes(GenericArray::from_slice(
&hex::decode(
"\
ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a\ ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a\
726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae\ 726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae\
80" 80";
) let mut two_g = <Point as GroupEncoding>::Repr::default();
.unwrap() two_g.as_mut().copy_from_slice(&hex::decode(TWO_G).unwrap());
)) assert_eq!(Point::generator().double(), Point::from_bytes(&two_g).unwrap());
.unwrap()
);
assert_eq!( const SCALAR: &str = "\
Point::generator() *
Scalar::from_repr(*GenericArray::from_slice(
&hex::decode(
"\
6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158\ 6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158\
a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\ a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\
00" 00";
) let mut scalar = <Scalar as PrimeField>::Repr::default();
.unwrap() scalar.as_mut().copy_from_slice(&hex::decode(SCALAR).unwrap());
))
.unwrap(), const SCALED_G: &str = "\
Point::from_bytes(GenericArray::from_slice(
&hex::decode(
"\
3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba2\ 3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba2\
65632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b0\ 65632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b0\
00" 00";
) let mut scaled_g = <Point as GroupEncoding>::Repr::default();
.unwrap() scaled_g.as_mut().copy_from_slice(&hex::decode(SCALED_G).unwrap());
))
.unwrap() assert_eq!(
Point::generator() * Scalar::from_repr(scalar).unwrap(),
Point::from_bytes(&scaled_g).unwrap()
); );
} }

View File

@@ -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<ScalarModulus, { ScalarModulus::LIMBS }>;
/// 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<U1024> = 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);
}

View File

@@ -7,26 +7,22 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/embedw
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["curve25519", "ed25519", "ristretto255", "group"] keywords = ["curve25519", "ed25519", "ristretto255", "group"]
edition = "2021" edition = "2021"
rust-version = "1.83" rust-version = "1.86"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
rustversion = "1"
hex-literal = { version = "0.4", default-features = false } hex-literal = { version = "0.4", default-features = false }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true } 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"] } zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
subtle = { version = "^2.4", default-features = false }
generic-array = { version = "1", 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 } dalek-ff-group = { path = "../../dalek-ff-group", version = "0.4", default-features = false }
blake2 = { version = "0.10", 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" } ff-group-tests = { path = "../../ff-group-tests" }
[features] [features]
alloc = ["std-shims", "zeroize/alloc", "ciphersuite/alloc"] alloc = ["std-shims", "zeroize/alloc", "prime-field/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"] std = ["std-shims/std", "zeroize/std", "prime-field/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"] default = ["std"]

View File

@@ -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<T>(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<Self> {
let res = self.0.invert();
CtOption::new(Self(res.0), res.1.into())
}
fn sqrt(&self) -> CtOption<Self> {
// (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<Self> {
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::ReprBits> {
self.to_repr().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
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<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a $FieldName> for $FieldName {
fn sum<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().sum()
}
}
impl Product<$FieldName> for $FieldName {
fn product<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a $FieldName> for $FieldName {
fn product<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().product()
}
}
};
}

View File

@@ -8,19 +8,38 @@ use std_shims::prelude::*;
use std_shims::io::{self, Read}; use std_shims::io::{self, Read};
use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}; 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] prime_field::odd_prime_field!(
mod backend; Scalar,
"0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b",
mod scalar; "0a",
pub use scalar::Scalar; false
);
pub use dalek_ff_group::Scalar as FieldElement; pub use dalek_ff_group::Scalar as FieldElement;
mod point; mod point;
pub use point::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. /// Ciphersuite for Embedwards25519.
/// ///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition /// 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 { fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest; use blake2::Digest;
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap()) <Scalar as FromUniformBytes<64>>::from_uniform_bytes(
&Self::H::digest([dst, data].concat()).into(),
)
} }
// We override the provided impl, which compares against the reserialization, because // We override the provided impl, which compares against the reserialization, because

View File

@@ -3,10 +3,11 @@ use core::{
iter::Sum, iter::Sum,
}; };
use rand_core::RngCore; use prime_field::{
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable},
use zeroize::Zeroize; zeroize::Zeroize,
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable}; rand_core::RngCore,
};
use ciphersuite::group::{ use ciphersuite::group::{
ff::{Field, PrimeField, PrimeFieldBits}, ff::{Field, PrimeField, PrimeFieldBits},
@@ -14,7 +15,7 @@ use ciphersuite::group::{
prime::PrimeGroup, prime::PrimeGroup,
}; };
use crate::{backend::u8_from_bool, Scalar, FieldElement}; use crate::{u8_from_bool, Scalar, FieldElement};
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn B() -> FieldElement { fn B() -> FieldElement {

View File

@@ -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<EmbedwardsQ, { EmbedwardsQ::LIMBS }>;
/// 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);
}

View File

@@ -7,19 +7,19 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/secq25
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["secp256k1", "secq256k1", "group"] keywords = ["secp256k1", "secq256k1", "group"]
edition = "2021" edition = "2021"
rust-version = "1.85" rust-version = "1.86"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
rustversion = "1"
hex-literal = { version = "0.4", default-features = false } hex-literal = { version = "0.4", default-features = false }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true }
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] } k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
prime-field = { path = "../../prime-field", default-features = false }
blake2 = { version = "0.10", default-features = false } blake2 = { version = "0.10", default-features = false }
ciphersuite = { path = "../../ciphersuite", version = "0.4", 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" } ff-group-tests = { path = "../../ff-group-tests" }
[features] [features]
alloc = ["std-shims", "k256/alloc", "ciphersuite/alloc"] alloc = ["std-shims", "k256/alloc", "prime-field/alloc", "ciphersuite/alloc"]
std = ["std-shims/std", "k256/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"] std = ["std-shims/std", "k256/std", "prime-field/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"] default = ["std"]

View File

@@ -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<T>(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<Self> {
let res = self.0.invert();
CtOption::new(Self(res.0), res.1.into())
}
fn sqrt(&self) -> CtOption<Self> {
// (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<Self> {
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<Self::ReprBits> {
let mut repr = [0; 32];
repr.copy_from_slice(&self.0.retrieve().to_le_bytes());
repr.into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
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<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a $FieldName> for $FieldName {
fn sum<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().sum()
}
}
impl Product<$FieldName> for $FieldName {
fn product<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a $FieldName> for $FieldName {
fn product<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().product()
}
}
};
}

View File

@@ -10,20 +10,39 @@ use std_shims::io::{self, Read};
use k256::elliptic_curve::{ use k256::elliptic_curve::{
zeroize::Zeroize, zeroize::Zeroize,
generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}, generic_array::typenum::{Sum, Diff, Quot, U, U1, U2},
group::{ff::PrimeField, Group}, group::{
ff::{PrimeField, FromUniformBytes},
Group,
},
}; };
#[macro_use] prime_field::odd_prime_field!(
mod backend; Scalar,
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
mod scalar; "03",
pub use scalar::Scalar; true
);
pub use k256::Scalar as FieldElement; pub use k256::Scalar as FieldElement;
mod point; mod point;
pub use point::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. /// Ciphersuite for Secq256k1.
/// ///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition /// 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 { fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest; use blake2::Digest;
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap()) <Scalar as FromUniformBytes<64>>::from_uniform_bytes(
&Self::H::digest([dst, data].concat()).into(),
)
} }
// We override the provided impl, which compares against the reserialization, because // We override the provided impl, which compares against the reserialization, because

View File

@@ -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<FieldElement> { fn recover_y(x: FieldElement) -> CtOption<FieldElement> {
// x**3 + B since a = 0 // x**3 + B since a = 0

View File

@@ -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<SecQ, { SecQ::LIMBS }>;
/// 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);
}

View File

@@ -2,7 +2,10 @@ use digest::Digest;
use minimal_ed448::{Scalar, Point}; use minimal_ed448::{Scalar, Point};
pub use minimal_ed448::Ed448; 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}; use crate::{curve::Curve, algorithm::Hram};
@@ -18,7 +21,8 @@ pub(crate) struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram { impl Ietf8032Ed448Hram {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar { pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
Scalar::wide_reduce( Scalar::from_uniform_bytes(
&<[u8; 114]>::try_from(
<Ed448 as Ciphersuite>::H::digest( <Ed448 as Ciphersuite>::H::digest(
[ [
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(), &[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(),
@@ -27,8 +31,8 @@ impl Ietf8032Ed448Hram {
] ]
.concat(), .concat(),
) )
.as_ref() .as_slice(),
.try_into() )
.unwrap(), .unwrap(),
) )
} }

View File

@@ -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 <lukeparker5132@gmail.com>"]
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"]

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
# Prime Field
A comprehensive macro to declare a prime field.

View File

@@ -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<const LIMBS: usize, const WORDS: usize>(
value: Uint<LIMBS>,
) -> [u64; WORDS] {
let mut res = [0u64; WORDS];
let mut i = 0;
while i < Uint::<LIMBS>::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<const LIMBS: usize, const WORDS: usize>(
words: [u64; WORDS],
) -> Uint<LIMBS> {
let mut reconstruction = Uint::<LIMBS>::ZERO;
let mut i = 0;
while i < WORDS {
reconstruction = reconstruction
.bitor(&Uint::<LIMBS>::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<const LIMBS: usize, P: ConstMontyParams<LIMBS>>() -> u32 {
let mut i = 0;
loop {
let bit = P::MODULUS.as_ref().wrapping_sub(&Uint::<LIMBS>::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<Params, { UnderlyingUint::LIMBS }>;
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<u8> for $name {
fn from(value: u8) -> Self {
Self::from(&UnderlyingUint::from(value))
}
}
impl From<u16> for $name {
fn from(value: u16) -> Self {
Self::from(&UnderlyingUint::from(value))
}
}
impl From<u32> for $name {
fn from(value: u32) -> Self {
Self::from(&UnderlyingUint::from(value))
}
}
impl From<u64> 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<I: Iterator<Item = Self>>(iter: I) -> Self {
let mut res = Self::ZERO;
for item in iter {
res += item;
}
res
}
}
impl Product for $name {
fn product<I: Iterator<Item = Self>>(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<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
let mut res = Self::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Product<&'a Self> for $name {
fn product<I: Iterator<Item = &'a Self>>(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<Self> {
CtOption::from(self.0.inv()).map(Self)
}
fn sqrt(&self) -> CtOption<Self> {
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, _>(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<Self> {
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::ReprBits> {
self.0.retrieve().to_le_bytes().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
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;
}
};
}

View File

@@ -0,0 +1,6 @@
prime_field::odd_prime_field!(
Scalar,
"0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed",
"02",
false
);

View File

@@ -309,12 +309,12 @@ fn embedded_curve_keys(network: Network) -> EmbeddedCurveKeys {
embedwards25519: { embedwards25519: {
let key = Zeroizing::new(<Embedwards25519 as Ciphersuite>::F::random(&mut rng)); let key = Zeroizing::new(<Embedwards25519 as Ciphersuite>::F::random(&mut rng));
let pub_key = Embedwards25519::generator() * key.deref(); 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: { secq256k1: {
let key = Zeroizing::new(<Secq256k1 as Ciphersuite>::F::random(&mut rng)); let key = Zeroizing::new(<Secq256k1 as Ciphersuite>::F::random(&mut rng));
let pub_key = Secq256k1::generator() * key.deref(); 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())
}, },
} }
} }

View File

@@ -28,6 +28,7 @@ multiexp = { path = "../../crypto/multiexp", default-features = false, features
schnorr-signatures = { path = "../../crypto/schnorr", default-features = false } 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 } secq256k1 = { path = "../../crypto/evrf/secq256k1", default-features = false }
embedwards25519 = { path = "../../crypto/evrf/embedwards25519", default-features = false } embedwards25519 = { path = "../../crypto/evrf/embedwards25519", default-features = false }

View File

@@ -11,6 +11,7 @@ pub use multiexp;
pub use schnorr_signatures; pub use schnorr_signatures;
pub use prime_field;
pub use secq256k1; pub use secq256k1;
pub use embedwards25519; pub use embedwards25519;