Skip to content

Commit

Permalink
Implement small prime-power fields
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeyBF committed Dec 15, 2023
1 parent a125c24 commit 9853e63
Show file tree
Hide file tree
Showing 10 changed files with 1,440 additions and 293 deletions.
2 changes: 2 additions & 0 deletions ext/crates/fp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ cfg-if = "1.0.0"
itertools = { version = "0.10.0", default-features = false }
serde = "1.0.0"
serde_json = "1.0.0"
dashmap = "5"

maybe-rayon = { path = "../maybe-rayon" }
once_cell = "1.19.0"

[dev-dependencies]
criterion = { version = "0.3.5", features = ["html_reports"] }
Expand Down
128 changes: 128 additions & 0 deletions ext/crates/fp/src/field/fp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::{constants::BITS_PER_LIMB, limb::Limb, prime::Prime};

use super::{limb::LimbMethods, Field, FieldElement};

/// A prime field. This is just a wrapper around a prime.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Fp<P>(pub(crate) P);

impl<P: Prime> Field for Fp<P> {
type Characteristic = P;

fn characteristic(self) -> Self::Characteristic {
self.0
}

fn degree(self) -> u32 {
1
}

fn zero(self) -> Self::Element {
0
}

fn one(self) -> Self::Element {
1
}

fn add(self, a: Self::Element, b: Self::Element) -> Self::Element {
self.0.sum(a, b)
}

fn mul(self, a: Self::Element, b: Self::Element) -> Self::Element {
self.0.product(a, b)
}

fn inv(self, a: Self::Element) -> Option<Self::Element> {
if a == 0 {
None
} else {
// By Fermat's little theorem, `a^(p-1) = 1`. Therefore, `a^(-1) = a^(p-2)`.
Some(self.0.pow_mod(a, self.0.as_u32() - 2))
}
}

fn neg(self, a: Self::Element) -> Self::Element {
if a > 0 {
self.0.as_u32() - a
} else {
0
}
}

fn frobenius(self, a: Self::Element) -> Self::Element {
a
}
}

impl<P: Prime> LimbMethods for Fp<P> {
type Element = u32;

fn encode(self, element: Self::Element) -> Limb {
element as Limb
}

fn decode(self, element: Limb) -> Self::Element {
(element % self.0.as_u32() as Limb) as u32
}

fn bit_length(self) -> usize {
let p = self.characteristic().as_u32() as u64;
match p {
2 => 1,
_ => (BITS_PER_LIMB as u32 - (p * (p - 1)).leading_zeros()) as usize,
}
}

fn fma_limb(self, limb_a: Limb, limb_b: Limb, coeff: Self::Element) -> Limb {
if self.characteristic() == 2 {
limb_a ^ (coeff as Limb * limb_b)
} else {
limb_a + (coeff as Limb) * limb_b
}
}

/// Contributed by Robert Burklund.
fn reduce(self, limb: Limb) -> Limb {
match self.characteristic().as_u32() {
2 => limb,
3 => {
// Set top bit to 1 in every limb
const TOP_BIT: Limb = (!0 / 7) << (2 - BITS_PER_LIMB % 3);
let mut limb_2 = ((limb & TOP_BIT) >> 2) + (limb & (!TOP_BIT));
let mut limb_3s = limb_2 & (limb_2 >> 1);
limb_3s |= limb_3s << 1;
limb_2 ^= limb_3s;
limb_2
}
5 => {
// Set bottom bit to 1 in every limb
const BOTTOM_BIT: Limb = (!0 / 31) >> (BITS_PER_LIMB % 5);
const BOTTOM_TWO_BITS: Limb = BOTTOM_BIT | (BOTTOM_BIT << 1);
const BOTTOM_THREE_BITS: Limb = BOTTOM_BIT | (BOTTOM_TWO_BITS << 1);
let a = (limb >> 2) & BOTTOM_THREE_BITS;
let b = limb & BOTTOM_TWO_BITS;
let m = (BOTTOM_BIT << 3) - a + b;
let mut c = (m >> 3) & BOTTOM_BIT;
c |= c << 1;
let d = m & BOTTOM_THREE_BITS;
d + c - BOTTOM_TWO_BITS
}
_ => self.pack(self.unpack(limb)),
}
}
}

impl<P> std::ops::Deref for Fp<P> {
type Target = P;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl FieldElement for u32 {
fn is_zero(&self) -> bool {
*self == 0
}
}
99 changes: 99 additions & 0 deletions ext/crates/fp/src/field/largefq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use dashmap::DashMap as HashMap;
use once_cell::sync::Lazy;

use crate::{
limb::Limb,
matrix::Matrix,
prime::{Prime, ValidPrime},
vector::inner::FqVectorP,
};

use super::{limb::LimbMethods, Field, FieldElement, Fp};

static MULT_MATRICES: Lazy<HashMap<(ValidPrime, u32), Matrix>> = Lazy::new(HashMap::new);
static FROB_MATRICES: Lazy<HashMap<(ValidPrime, u32), Matrix>> = Lazy::new(HashMap::new);

/// A field of order `q = p^d`, where `q >= 2^16` and `d > 1`. Fields of that size are too large to
/// cache their Zech logarithms, so we represent their elements as polynomials over the prime field.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct LargeFq<P> {
p: P,
d: u32,
}

impl<P: Prime> Field for LargeFq<P> {
type Characteristic = P;

fn characteristic(self) -> Self::Characteristic {
self.p
}

fn degree(self) -> u32 {
self.d
}

fn zero(self) -> Self::Element {
LargeFqElement(FqVectorP::new(Fp(self.p), self.d as usize))
}

fn one(self) -> Self::Element {
todo!()
}

fn add(self, a: Self::Element, b: Self::Element) -> Self::Element {
todo!()
}

fn mul(self, a: Self::Element, b: Self::Element) -> Self::Element {
todo!()
}

fn neg(self, a: Self::Element) -> Self::Element {
todo!()
}

fn inv(self, a: Self::Element) -> Option<Self::Element> {
todo!()
}

fn frobenius(self, a: Self::Element) -> Self::Element {
todo!()
}
}

impl<P: Prime> LimbMethods for LargeFq<P> {
type Element = LargeFqElement<P>;

fn encode(self, element: Self::Element) -> Limb {
todo!()
}

fn decode(self, element: Limb) -> Self::Element {
todo!()
}

fn bit_length(self) -> usize {
todo!()
}

fn fma_limb(self, limb_a: Limb, limb_b: Limb, coeff: Self::Element) -> Limb {
todo!()
}

fn reduce(self, limb: Limb) -> Limb {
limb
}
}

/// A field element as a polynomial over the prime field. This is used when the order of the
/// field is large, since otherwise caching Zech logarithms uses too much memory.
///
/// This is backed by an `FpVectorP` consisting of the coefficients of the polynomial.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LargeFqElement<P: Prime>(pub(super) FqVectorP<Fp<P>>);

impl<P: Prime> FieldElement for LargeFqElement<P> {
fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
128 changes: 12 additions & 116 deletions ext/crates/fp/src/field/limb.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// According to
// https://doc.rust-lang.org/stable/rustc/lints/listing/warn-by-default.html#private-interfaces:
//
// "Having something private in primary interface guarantees that the item will be unusable from
// outer modules due to type privacy."
//
// In our case, this is a feature. We want to be able to use the `LimbMethods` trait in this crate
// and we also want it to be inaccessible from outside the crate.
#![allow(private_interfaces)]

use std::ops::Range;

use crate::{
constants::BITS_PER_LIMB,
limb::{Limb, LimbBitIndexPair},
prime::Prime,
};

use super::{
element::{FieldElement, MultiplicativeFieldElement, PolynomialFieldElement},
Field, Fp, LargeFq, SmallFq,
};
use super::FieldElement;

/// Methods that lets us interact with the underlying `Limb` type.
///
Expand Down Expand Up @@ -123,7 +129,7 @@ pub trait LimbMethods: Clone + Copy + Sized {
}
}

struct LimbIterator<F> {
pub(crate) struct LimbIterator<F> {
fq: F,
limb: Limb,
bit_length: usize,
Expand All @@ -142,113 +148,3 @@ impl<F: LimbMethods> Iterator for LimbIterator<F> {
Some(self.fq.decode(result))
}
}

impl<P: Prime> LimbMethods for Fp<P> {
type Element = u32;

fn encode(self, element: Self::Element) -> Limb {
element as Limb
}

fn decode(self, element: Limb) -> Self::Element {
(element % self.0.as_u32() as Limb) as u32
}

fn bit_length(self) -> usize {
let p = self.characteristic().as_u32() as u64;
match p {
2 => 1,
_ => (BITS_PER_LIMB as u32 - (p * (p - 1)).leading_zeros()) as usize,
}
}

fn fma_limb(self, limb_a: Limb, limb_b: Limb, coeff: Self::Element) -> Limb {
if self.characteristic() == 2 {
limb_a ^ (coeff as Limb * limb_b)
} else {
limb_a + (coeff as Limb) * limb_b
}
}

/// Contributed by Robert Burklund.
fn reduce(self, limb: Limb) -> Limb {
match self.characteristic().as_u32() {
2 => limb,
3 => {
// Set top bit to 1 in every limb
const TOP_BIT: Limb = (!0 / 7) << (2 - BITS_PER_LIMB % 3);
let mut limb_2 = ((limb & TOP_BIT) >> 2) + (limb & (!TOP_BIT));
let mut limb_3s = limb_2 & (limb_2 >> 1);
limb_3s |= limb_3s << 1;
limb_2 ^= limb_3s;
limb_2
}
5 => {
// Set bottom bit to 1 in every limb
const BOTTOM_BIT: Limb = (!0 / 31) >> (BITS_PER_LIMB % 5);
const BOTTOM_TWO_BITS: Limb = BOTTOM_BIT | (BOTTOM_BIT << 1);
const BOTTOM_THREE_BITS: Limb = BOTTOM_BIT | (BOTTOM_TWO_BITS << 1);
let a = (limb >> 2) & BOTTOM_THREE_BITS;
let b = limb & BOTTOM_TWO_BITS;
let m = (BOTTOM_BIT << 3) - a + b;
let mut c = (m >> 3) & BOTTOM_BIT;
c |= c << 1;
let d = m & BOTTOM_THREE_BITS;
d + c - BOTTOM_TWO_BITS
}
_ => self.pack(self.unpack(limb)),
}
}
}

impl LimbMethods for SmallFq {
type Element = MultiplicativeFieldElement;

fn encode(self, element: Self::Element) -> Limb {
element.0.map(|x| (x as Limb) << 1 | 1).unwrap_or(0)
}

fn decode(self, element: Limb) -> Self::Element {
if element & 1 == 0 {
MultiplicativeFieldElement(None)
} else {
MultiplicativeFieldElement(Some((element >> 1) as u32))
}
}

fn bit_length(self) -> usize {
todo!()
}

fn fma_limb(self, limb_a: Limb, limb_b: Limb, coeff: Self::Element) -> Limb {
todo!()
}

fn reduce(self, limb: Limb) -> Limb {
todo!()
}
}

impl LimbMethods for LargeFq {
type Element = PolynomialFieldElement<Self>;

fn encode(self, element: Self::Element) -> Limb {
todo!()
}

fn decode(self, element: Limb) -> Self::Element {
todo!()
}

fn bit_length(self) -> usize {
todo!()
}

fn fma_limb(self, limb_a: Limb, limb_b: Limb, coeff: Self::Element) -> Limb {
todo!()
}

fn reduce(self, limb: Limb) -> Limb {
todo!()
}
}
Loading

0 comments on commit 9853e63

Please sign in to comment.