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

@@ -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
);