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

@@ -7,26 +7,22 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/embedw
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
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"]

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 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())
<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

View File

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

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>"]
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"]

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::{
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())
<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

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> {
// 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);
}