Skip to content

Commit

Permalink
Use generic implementations for prime field operations. (#1099)
Browse files Browse the repository at this point in the history
This is a refactor of the FieldParameters struct
to use generic datatypes providing implementations for
primes that fit in one primitive word.

Tests script and documentation parameters were updated to
support the new structure.

Performance for FieldPrio2 operation is twice faster.
  • Loading branch information
armfazh authored Aug 27, 2024
1 parent 99eee6e commit c842f2d
Show file tree
Hide file tree
Showing 7 changed files with 753 additions and 866 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ num-bigint = { version = "0.4.6", optional = true, features = ["rand", "serde"]
num-integer = { version = "0.1.46", optional = true }
num-iter = { version = "0.1.45", optional = true }
num-rational = { version = "0.4.2", optional = true, features = ["serde"] }
num-traits = { version = "0.2.19", optional = true }
num-traits = "0.2.19"
rand = "0.8"
rand_core = "0.6.4"
rayon = { version = "1.10.0", optional = true }
Expand Down Expand Up @@ -52,7 +52,7 @@ statrs = "0.17.1"

[features]
default = ["crypto-dependencies"]
experimental = ["bitvec", "fiat-crypto", "fixed", "num-bigint", "num-rational", "num-traits", "num-integer", "num-iter"]
experimental = ["bitvec", "fiat-crypto", "fixed", "num-bigint", "num-rational", "num-integer", "num-iter"]
multithreaded = ["rayon"]
crypto-dependencies = ["aes", "ctr", "hmac", "sha2"]
test-util = ["hex", "serde_json", "zipf"]
Expand Down
28 changes: 18 additions & 10 deletions documentation/field_parameters.sage
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,27 @@ class Field:
for i in range(min(self.num_roots, 20) + 1)
]

def log2_base(self):
"""
Returns log2(r), where r is the base used for multiprecision arithmetic.
"""
return log(self.r, 2)

def log2_radix(self):
"""
Returns log2(R), where R is the machine word-friendly modulus
used in the Montgomery representation.
"""
return log(self.R, 2)


FIELDS = [
Field(
"FieldPrio2, u128",
"FieldPrio2, u32",
2 ^ 20 * 4095 + 1,
3925978153,
2 ^ 64,
2 ^ 128,
),
Field(
"Field64, u128",
2 ^ 32 * 4294967295 + 1,
pow(7, 4294967295, 2 ^ 32 * 4294967295 + 1),
2 ^ 64,
2 ^ 128,
2 ^ 32,
2 ^ 32,
),
Field(
"Field64, u64",
Expand All @@ -140,4 +146,6 @@ for field in FIELDS:
print(f"bit_mask: {field.bit_mask()}")
print("roots:")
pprint.pprint(field.roots())
print(f"log2_base: {field.log2_base()}")
print(f"log2_radix: {field.log2_radix()}")
print()
55 changes: 27 additions & 28 deletions src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
use crate::{
codec::{CodecError, Decode, Encode},
fp::{FP128, FP32},
fp64::FP64,
fp::{FieldOps, FieldParameters, FP128, FP32, FP64},
prng::{Prng, PrngError},
};
use rand::{
Expand Down Expand Up @@ -465,12 +464,12 @@ macro_rules! make_field {

int &= mask;

if int >= $fp.p {
if int >= $fp::PRIME {
return Err(FieldError::ModulusOverflow);
}
// FieldParameters::montgomery() will return a value that has been fully reduced
// mod p, satisfying the invariant on Self.
Ok(Self($fp.montgomery(int)))
Ok(Self($fp::montgomery(int)))
}
}

Expand All @@ -481,8 +480,8 @@ macro_rules! make_field {
// https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq

// Check the invariant that the integer representation is fully reduced.
debug_assert!(self.0 < $fp.p);
debug_assert!(rhs.0 < $fp.p);
debug_assert!(self.0 < $fp::PRIME);
debug_assert!(rhs.0 < $fp::PRIME);

self.0 == rhs.0
}
Expand All @@ -507,7 +506,7 @@ macro_rules! make_field {
// https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq

// Check the invariant that the integer representation is fully reduced.
debug_assert!(self.0 < $fp.p);
debug_assert!(self.0 < $fp::PRIME);

self.0.hash(state);
}
Expand All @@ -520,7 +519,7 @@ macro_rules! make_field {
fn add(self, rhs: Self) -> Self {
// FieldParameters::add() returns a value that has been fully reduced
// mod p, satisfying the invariant on Self.
Self($fp.add(self.0, rhs.0))
Self($fp::add(self.0, rhs.0))
}
}

Expand All @@ -542,7 +541,7 @@ macro_rules! make_field {
fn sub(self, rhs: Self) -> Self {
// We know that self.0 and rhs.0 are both less than p, thus FieldParameters::sub()
// returns a value less than p, satisfying the invariant on Self.
Self($fp.sub(self.0, rhs.0))
Self($fp::sub(self.0, rhs.0))
}
}

Expand All @@ -564,7 +563,7 @@ macro_rules! make_field {
fn mul(self, rhs: Self) -> Self {
// FieldParameters::mul() always returns a value less than p, so the invariant on
// Self is satisfied.
Self($fp.mul(self.0, rhs.0))
Self($fp::mul(self.0, rhs.0))
}
}

Expand Down Expand Up @@ -607,7 +606,7 @@ macro_rules! make_field {
fn neg(self) -> Self {
// FieldParameters::neg() will return a value less than p because self.0 is less
// than p, and neg() dispatches to sub().
Self($fp.neg(self.0))
Self($fp::neg(self.0))
}
}

Expand All @@ -622,19 +621,19 @@ macro_rules! make_field {
fn from(x: $int_conversion) -> Self {
// FieldParameters::montgomery() will return a value that has been fully reduced
// mod p, satisfying the invariant on Self.
Self($fp.montgomery($int_internal::try_from(x).unwrap()))
Self($fp::montgomery($int_internal::try_from(x).unwrap()))
}
}

impl From<$elem> for $int_conversion {
fn from(x: $elem) -> Self {
$int_conversion::try_from($fp.residue(x.0)).unwrap()
$int_conversion::try_from($fp::residue(x.0)).unwrap()
}
}

impl PartialEq<$int_conversion> for $elem {
fn eq(&self, rhs: &$int_conversion) -> bool {
$fp.residue(self.0) == $int_internal::try_from(*rhs).unwrap()
$fp::residue(self.0) == $int_internal::try_from(*rhs).unwrap()
}
}

Expand All @@ -648,7 +647,7 @@ macro_rules! make_field {

impl From<$elem> for [u8; $elem::ENCODED_SIZE] {
fn from(elem: $elem) -> Self {
let int = $fp.residue(elem.0);
let int = $fp::residue(elem.0);
let mut slice = [0; $elem::ENCODED_SIZE];
for i in 0..$elem::ENCODED_SIZE {
slice[i] = ((int >> (i << 3)) & 0xff) as u8;
Expand All @@ -665,13 +664,13 @@ macro_rules! make_field {

impl Display for $elem {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", $fp.residue(self.0))
write!(f, "{}", $fp::residue(self.0))
}
}

impl Debug for $elem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", $fp.residue(self.0))
write!(f, "{}", $fp::residue(self.0))
}
}

Expand Down Expand Up @@ -719,19 +718,19 @@ macro_rules! make_field {
fn inv(&self) -> Self {
// FieldParameters::inv() ultimately relies on mul(), and will always return a
// value less than p.
Self($fp.inv(self.0))
Self($fp::inv(self.0))
}

fn try_from_random(bytes: &[u8]) -> Result<Self, FieldError> {
$elem::try_from_bytes(bytes, $fp.bit_mask)
$elem::try_from_bytes(bytes, $fp::BIT_MASK)
}

fn zero() -> Self {
Self(0)
}

fn one() -> Self {
Self($fp.roots[0])
Self($fp::ROOTS[0])
}
}

Expand All @@ -741,26 +740,26 @@ macro_rules! make_field {
fn pow(&self, exp: Self::Integer) -> Self {
// FieldParameters::pow() relies on mul(), and will always return a value less
// than p.
Self($fp.pow(self.0, $int_internal::try_from(exp).unwrap()))
Self($fp::pow(self.0, $int_internal::try_from(exp).unwrap()))
}

fn modulus() -> Self::Integer {
$fp.p as $int_conversion
$fp::PRIME as $int_conversion
}
}

impl FftFriendlyFieldElement for $elem {
fn generator() -> Self {
Self($fp.g)
Self($fp::G)
}

fn generator_order() -> Self::Integer {
1 << (Self::Integer::try_from($fp.num_roots).unwrap())
1 << (Self::Integer::try_from($fp::NUM_ROOTS).unwrap())
}

fn root(l: usize) -> Option<Self> {
if l < min($fp.roots.len(), $fp.num_roots+1) {
Some(Self($fp.roots[l]))
if l < min($fp::ROOTS.len(), $fp::NUM_ROOTS+1) {
Some(Self($fp::ROOTS[l]))
} else {
None
}
Expand Down Expand Up @@ -815,9 +814,9 @@ impl Integer for u128 {
}

make_field!(
/// Same as Field32, but encoded in little endian for compatibility with Prio v2.
/// `GF(4293918721)`, a 32-bit field.
FieldPrio2,
u128,
u32,
u32,
FP32,
4,
Expand Down
Loading

0 comments on commit c842f2d

Please sign in to comment.