Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7b49928
placeholder for SmallFp
z-tech Sep 4, 2025
daa99f7
migrate smallfp implementation from blendy
benbencik Sep 24, 2025
0fadbb9
add tests
benbencik Sep 24, 2025
3d6636e
add benchmarks
benbencik Sep 25, 2025
f7de469
add square root precomputation
benbencik Oct 2, 2025
4d835f4
split sampling method into cases
benbencik Oct 3, 2025
4ba1448
fix mul overflow issue
benbencik Oct 3, 2025
d1937cc
extend testing suite (some tests failing)
benbencik Oct 3, 2025
a1a3343
rewrite sampling function
benbencik Oct 3, 2025
9cc57f5
fix overflowing bug in multiplication
benbencik Oct 3, 2025
be2664d
fix the computation for bit size
benbencik Oct 3, 2025
e39da81
consider (de)serialization of small elements
benbencik Oct 4, 2025
3c7daf9
rewrite computation for two adic root of unity
benbencik Oct 4, 2025
8a907d8
use safe mul to avoid overflows in compile-time
benbencik Oct 4, 2025
ea4cce2
update the smallfp tests
benbencik Oct 4, 2025
db896d3
Merge branch 'smallfp-test' into small_fp
benbencik Oct 4, 2025
2945217
rewrite mul_assing to handle overflows correctly
benbencik Oct 5, 2025
582ac3d
move tests and benches to test-curves
benbencik Oct 8, 2025
b91c704
update doccomments
benbencik Oct 8, 2025
f2f3fb1
use the provided bench templates for fields
benbencik Oct 9, 2025
f8c81e8
Merge branch 'master' into small_fp
benbencik Oct 9, 2025
176fc97
add info about small fields to readme
benbencik Oct 9, 2025
0cad96a
add pending PR 1044
benbencik Oct 9, 2025
742bb39
fix markdown linter error
benbencik Oct 9, 2025
4ccddce
add mont multiplication fastpath
benbencik Oct 10, 2025
42d99e9
clean unused type
benbencik Oct 12, 2025
6976823
specify mont mul impl at compile time
benbencik Oct 12, 2025
7a7f18b
add inlinging for arithmetic ops
benbencik Oct 12, 2025
f59fa7d
replace modulo operation in addition
benbencik Oct 12, 2025
676a7c3
Merge branch 'master' into small_fp
z-tech Oct 13, 2025
d663208
remove branching from mont multiplicaiton
benbencik Oct 13, 2025
2a6e5e3
Merge branch 'master' into small_fp
z-tech Oct 14, 2025
8acdef2
update utils helper functions
benbencik Oct 14, 2025
391f0ba
specify supported moduli
benbencik Oct 14, 2025
234bd8f
reduce duplicity in tests
benbencik Oct 15, 2025
2c27ed4
delete smallfp macro and tests
benbencik Oct 15, 2025
9692b88
remove smallfp tests
benbencik Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- (`ark-poly`) Add fast polynomial division
- (`ark-ec`) Improve GLV scalar multiplication performance by skipping leading zeroes.
- (`ark-poly`) Make `SparsePolynomial.coeffs` field public
- [\#1044](https://github.com/arkworks-rs/algebra/pull/1044) Add implementation for small field with native integer types

### Breaking changes

Expand Down
12 changes: 9 additions & 3 deletions ff/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "ark-ff"
description = "A library for finite fields"
keywords = ["cryptography", "finite-fields" ]
keywords = ["cryptography", "finite-fields"]
documentation = "https://docs.rs/ark-ff/"
version.workspace = true
authors.workspace = true
Expand All @@ -28,15 +28,21 @@ num-bigint.workspace = true
digest = { workspace = true, features = ["alloc"] }

[dev-dependencies]
ark-test-curves = { workspace = true, features = ["bls12_381_curve", "mnt6_753", "secp256k1"] }
blake2.workspace = true
ark-test-curves = { workspace = true, features = [
"bls12_381_curve",
"mnt6_753",
"secp256k1",
] }
ark-algebra-test-templates = { path = "../test-templates" }

sha3.workspace = true
sha2.workspace = true
libtest-mimic.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_derive.workspace = true
hex.workspace = true
criterion.workspace = true

[features]
default = []
Expand Down
27 changes: 27 additions & 0 deletions ff/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ The above two models serve as abstractions for constructing the extension fields
- [`Fp6_3over2`](https://github.com/arkworks-rs/algebra/blob/master/ff/src/fields/models/fp6_3over2.rs#L64) - Extension tower, similar to the above except that the towering order is reversed: it's a cubic extension on a quadratic extension field, i.e. `BaseField = Fp2`, but `BasePrimeField = Fp`. Only this latter one is exported by default as `Fp6`.
- [`Fp12_2over3over2`](https://github.com/arkworks-rs/algebra/blob/master/ff/src/fields/models/fp12_2over3over2.rs#L66) - Extension tower: quadratic extension of `Fp6_3over2`, i.e. `BaseField = Fp6`.

## Instantiation

You can instantiate fields in two ways:

```rust
use ark_ff::ark_ff_macros::SmallFpConfig;
use ark_ff::fields::{Fp64, MontBackend, MontConfig};
use ark_ff::{BigInt, SmallFp, SmallFpConfig, SqrtPrecomputation};

// Standard (big integer) field
#[derive(MontConfig)]
#[modulus = "18446744069414584321"]
#[generator = "7"]
pub struct F64Config;
pub type F64 = Fp64<MontBackend<F64Config, 1>>;

// Small field (native integer backend):
#[derive(SmallFpConfig)]
#[modulus = "18446744069414584321"]
#[generator = "7"]
#[backend = "montgomery"] // or "standard"
pub struct SmallF64ConfigMont;
pub type SmallF64Mont = SmallFp<SmallF64ConfigMont>;
```

The standard field implementation can represent arbitrarily large fields, while the small field implementation supports native integer types from `u8` to `u128` for faster arithmetic. The small field implementation requires that the modulus fits into u128.

## Usage

There are two important traits when working with finite fields: [`Field`],
Expand Down
3 changes: 3 additions & 0 deletions ff/src/fields/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
pub mod fp;
pub use self::fp::*;

pub mod small_fp;
pub use self::small_fp::*;

pub mod fp2;
pub use self::fp2::*;

Expand Down
186 changes: 186 additions & 0 deletions ff/src/fields/models/small_fp/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use crate::fields::models::small_fp::small_fp_backend::{SmallFp, SmallFpConfig};
use crate::{Field, LegendreSymbol, One, PrimeField, SqrtPrecomputation, Zero};
use ark_serialize::{buffer_byte_size, CanonicalDeserialize, EmptyFlags, Flags};
use core::iter;

impl<P: SmallFpConfig> Field for SmallFp<P> {
type BasePrimeField = Self;

const SQRT_PRECOMP: Option<SqrtPrecomputation<Self>> = P::SQRT_PRECOMP;
const ONE: Self = P::ONE;
const NEG_ONE: Self = P::NEG_ONE;

fn extension_degree() -> u64 {
1
}

fn from_base_prime_field(elem: Self::BasePrimeField) -> Self {
elem
}

fn to_base_prime_field_elements(&self) -> impl Iterator<Item = Self> {
iter::once(*self)
}

fn from_base_prime_field_elems(
elems: impl IntoIterator<Item = Self::BasePrimeField>,
) -> Option<Self> {
let mut iter = elems.into_iter();
let first = iter.next()?;
if iter.next().is_some() {
None
} else {
Some(first)
}
}

#[inline]
fn characteristic() -> &'static [u64] {
&Self::MODULUS.as_ref()
}

#[inline]
fn sum_of_products<const T: usize>(a: &[Self; T], b: &[Self; T]) -> Self {
P::sum_of_products(a, b)
}

#[inline]
fn from_random_bytes_with_flags<F: Flags>(bytes: &[u8]) -> Option<(Self, F)> {
if F::BIT_SIZE > 8 {
None
} else {
let shave_bits = Self::num_bits_to_shave();
let mut result_bytes: crate::const_helpers::SerBuffer<2> =
crate::const_helpers::SerBuffer::zeroed();
// Copy the input into a temporary buffer.
result_bytes.copy_from_u8_slice(bytes);
// This mask retains everything in the last limb
// that is below `P::MODULUS_BIT_SIZE`.
let last_limb_mask =
(u64::MAX.checked_shr(shave_bits as u32).unwrap_or(0)).to_le_bytes();
let mut last_bytes_mask = [0u8; 9];
last_bytes_mask[..8].copy_from_slice(&last_limb_mask);

// Length of the buffer containing the field element and the flag.
let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE);
// Location of the flag is the last byte of the serialized
// form of the field element.
let flag_location = output_byte_size - 1;

// At which byte is the flag located in the last limb?
let flag_location_in_last_limb =
flag_location.saturating_sub(8 * (P::NUM_BIG_INT_LIMBS - 1));

// Take all but the last 9 bytes.
let last_bytes = result_bytes.last_n_plus_1_bytes_mut();

// The mask only has the last `F::BIT_SIZE` bits set
let flags_mask = u8::MAX.checked_shl(8 - (F::BIT_SIZE as u32)).unwrap_or(0);

// Mask away the remaining bytes, and try to reconstruct the
// flag
let mut flags: u8 = 0;
for (i, (b, m)) in last_bytes.zip(&last_bytes_mask).enumerate() {
if i == flag_location_in_last_limb {
flags = *b & flags_mask
}
*b &= m;
}
Self::deserialize_compressed(&result_bytes.as_slice()[..(P::NUM_BIG_INT_LIMBS * 8)])
.ok()
.and_then(|f| F::from_u8(flags).map(|flag| (f, flag)))
}
}

#[inline]
fn square(&self) -> Self {
let mut temp = *self;
temp.square_in_place();
temp
}

fn square_in_place(&mut self) -> &mut Self {
P::square_in_place(self);
self
}

#[inline]
fn inverse(&self) -> Option<Self> {
P::inverse(self)
}

fn inverse_in_place(&mut self) -> Option<&mut Self> {
self.inverse().map(|inverse| {
*self = inverse;
self
})
}

/// The Frobenius map has no effect in a prime field.
#[inline]
fn frobenius_map_in_place(&mut self, _: usize) {}

#[inline]
fn legendre(&self) -> LegendreSymbol {
// s = self^((MODULUS - 1) // 2)
let s = self.pow(Self::MODULUS_MINUS_ONE_DIV_TWO);
if s.is_zero() {
LegendreSymbol::Zero
} else if s.is_one() {
LegendreSymbol::QuadraticResidue
} else {
LegendreSymbol::QuadraticNonResidue
}
}

fn mul_by_base_prime_field(&self, elem: &Self::BasePrimeField) -> Self {
*self * elem
}

fn from_random_bytes(bytes: &[u8]) -> Option<Self> {
Self::from_random_bytes_with_flags::<EmptyFlags>(bytes).map(|f| f.0)
}

fn sqrt(&self) -> Option<Self> {
match Self::SQRT_PRECOMP {
Some(tv) => tv.sqrt(self),
None => ark_std::unimplemented!(),
}
}

fn sqrt_in_place(&mut self) -> Option<&mut Self> {
(*self).sqrt().map(|sqrt| {
*self = sqrt;
self
})
}

fn frobenius_map(&self, power: usize) -> Self {
let mut this = *self;
this.frobenius_map_in_place(power);
this
}

fn pow<S: AsRef<[u64]>>(&self, exp: S) -> Self {
let mut res = Self::one();

for i in crate::BitIteratorBE::without_leading_zeros(exp) {
res.square_in_place();

if i {
res *= self;
}
}
res
}

fn pow_with_table<S: AsRef<[u64]>>(powers_of_2: &[Self], exp: S) -> Option<Self> {
let mut res = Self::one();
for (pow, bit) in crate::BitIteratorLE::without_trailing_zeros(exp).enumerate() {
if bit {
res *= powers_of_2.get(pow)?;
}
}
Some(res)
}
}
Loading
Loading