Skip to main content

fp/
f2_ext.rs

1//! Generic finary fields $F_{2^m} = F_2\[x\] / (f(x))$
2
3use crate::field_ops::{FieldFromRepr, FieldOps, FieldRandom};
4use core::ops::{Add, Mul, Neg, Sub};
5use crypto_bigint::Uint;
6use std::marker::PhantomData;
7use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
8
9// ---------------------------------------------------------------------------
10// IrreduciblePoly — the only thing callers need to implement for a new field
11// ---------------------------------------------------------------------------
12
13/// An irreducible polynomial over $\mathbb{F}_2$.
14pub trait BinaryIrreducible<const LIMBS: usize>: 'static {
15    /// Full polynomial bitmask, including the leading term x^m
16    fn modulus() -> Uint<LIMBS>;
17
18    /// Degree m of the irreducible polynomial
19    fn degree() -> usize;
20}
21
22// ---------------------------------------------------------------------------
23// F2Ext — element of F_{2^M}
24// ---------------------------------------------------------------------------
25
26/// An extension of $\mathbb{F}_2$ given by a polynomial `P`.
27pub struct F2Ext<const LIMBS: usize, P>
28where
29    P: BinaryIrreducible<LIMBS>,
30{
31    /// The value of an element of $\mathbb{F}_{2^M}$
32    pub value: Uint<LIMBS>,
33    _phantom: PhantomData<P>,
34}
35
36// ---------------------------------------------------------------------------
37// Constructors
38// ---------------------------------------------------------------------------
39
40impl<const LIMBS: usize, P> F2Ext<LIMBS, P>
41where
42    P: BinaryIrreducible<LIMBS>,
43{
44    /// Make an element from the limbs
45    pub fn new(x: Uint<LIMBS>) -> Self {
46        Self {
47            value: reduce::<LIMBS, P>(x),
48            _phantom: PhantomData,
49        }
50    }
51
52    /// Make an element from a `u64`
53    pub fn from_u64(x: u64) -> Self {
54        Self::new(Uint::from(x))
55    }
56
57    /// Make an element from a `Uint<LIMBS>`
58    pub fn from_uint(x: Uint<LIMBS>) -> Self {
59        Self::new(x)
60    }
61
62    /// Get a `Unit<LIMBS>` from an element
63    pub fn as_uint(&self) -> Uint<LIMBS> {
64        self.value
65    }
66
67    /// Get the degree of the field extension
68    pub fn degree() -> usize {
69        P::degree()
70    }
71}
72
73// ---------------------------------------------------------------------------
74// Clone / Copy / PartialEq / Eq / Debug
75// (manual impls so we don't over-constrain the bounds the way #[derive] would)
76// ---------------------------------------------------------------------------
77
78impl<const LIMBS: usize, P> Clone for F2Ext<LIMBS, P>
79where
80    P: BinaryIrreducible<LIMBS>,
81{
82    fn clone(&self) -> Self {
83        Self {
84            value: self.value.clone(),
85            _phantom: PhantomData,
86        }
87    }
88}
89
90impl<const LIMBS: usize, P> Copy for F2Ext<LIMBS, P> where P: BinaryIrreducible<LIMBS> {}
91
92impl<const LIMBS: usize, P> PartialEq for F2Ext<LIMBS, P>
93where
94    P: BinaryIrreducible<LIMBS>,
95{
96    fn eq(&self, other: &Self) -> bool {
97        self.value == other.value
98    }
99}
100
101impl<const LIMBS: usize, P> Eq for F2Ext<LIMBS, P> where P: BinaryIrreducible<LIMBS> {}
102
103impl<const LIMBS: usize, P> core::fmt::Debug for F2Ext<LIMBS, P>
104where
105    P: BinaryIrreducible<LIMBS>,
106{
107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108        write!(f, "F2Ext({:?})", self.value)
109    }
110}
111
112// ---------------------------------------------------------------------------
113// Pretty Display — polynomial form over F_2
114// ---------------------------------------------------------------------------
115//
116// Shows the element as a sum of powers of x, e.g.  `x^7 + x^3 + x + 1`.
117// The zero element is printed as `0`.
118
119impl<const LIMBS: usize, P> core::fmt::Display for F2Ext<LIMBS, P>
120where
121    P: BinaryIrreducible<LIMBS>,
122{
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        let m = P::degree();
125        let mut terms = Vec::new();
126        let words = self.value.to_words();
127
128        // Collect set bits from high to low for descending-degree display.
129        for i in (0..m).rev() {
130            let word = words[i / 64];
131            let bit = (word >> (i % 64)) & 1;
132            if bit == 1 {
133                match i {
134                    0 => terms.push("1".to_string()),
135                    1 => terms.push("x".to_string()),
136                    _ => terms.push(format!("x^{i}")),
137                }
138            }
139        }
140
141        if terms.is_empty() {
142            write!(f, "0")
143        } else {
144            write!(f, "{}", terms.join(" + "))
145        }
146    }
147}
148
149
150// ---------------------------------------------------------------------------
151// CtOption functionalities
152// ---------------------------------------------------------------------------
153
154impl<const LIMBS: usize, P> Default for F2Ext<LIMBS, P>
155where
156    P: BinaryIrreducible<LIMBS>,
157{
158    fn default() -> Self {
159        Self::zero()
160    }
161}
162
163impl<const LIMBS: usize, P> ConditionallySelectable for F2Ext<LIMBS, P>
164where
165    P: BinaryIrreducible<LIMBS>,
166{
167    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
168        Self::new(Uint::<LIMBS>::conditional_select(
169            &a.value, &b.value, choice,
170        ))
171    }
172
173    fn conditional_assign(&mut self, other: &Self, choice: Choice) {
174        self.value.conditional_assign(&other.value, choice)
175    }
176
177    fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
178        Uint::<LIMBS>::conditional_swap(&mut a.value, &mut b.value, choice)
179    }
180}
181
182impl<const LIMBS: usize, P> ConstantTimeEq for F2Ext<LIMBS, P>
183where
184    P: BinaryIrreducible<LIMBS>,
185{
186    fn ct_eq(&self, other: &Self) -> Choice {
187        Uint::<LIMBS>::ct_eq(&self.value, &other.value)
188    }
189
190    fn ct_ne(&self, other: &Self) -> Choice {
191        Uint::<LIMBS>::ct_ne(&self.value, &other.value)
192    }
193}
194
195// ---------------------------------------------------------------------------
196// Private helper functions
197// ---------------------------------------------------------------------------
198
199fn reduce<const LIMBS: usize, P>(mut a: Uint<LIMBS>) -> Uint<LIMBS>
200where
201    P: BinaryIrreducible<LIMBS>,
202{
203    let modulus = P::modulus();
204    let m = P::degree();
205    let nbits = Uint::<LIMBS>::BITS as usize;
206
207    debug_assert!(m > 0);
208    debug_assert!(m < nbits);
209
210    // Fixed iteration count => no leakage from the current degree of `a`.
211    for i in (m..nbits).rev() {
212        let bit = a.bit(i as u32); // Choice: whether x^i is present
213        let shifted = modulus << (i - m);
214        let reduced = a ^ shifted;
215
216        // Constant-time:
217        // - keep `a` if bit == 0
218        // - replace by `reduced` if bit == 1
219        a = Uint::<LIMBS>::conditional_select(&a, &reduced, bit.into());
220    }
221    a
222}
223
224fn add_helper<const LIMBS: usize>(a: &Uint<LIMBS>, b: &Uint<LIMBS>) -> Uint<LIMBS> {
225    a ^ b
226}
227
228// This implements the add-and-shift with immediate modular reduction algorithm
229fn mul_helper<const LIMBS: usize, P>(a: &Uint<LIMBS>, b: &Uint<LIMBS>) -> Uint<LIMBS>
230where
231    P: BinaryIrreducible<LIMBS>,
232{
233    let m = P::degree();
234    let modulus = P::modulus();
235
236    let mut res = Uint::<LIMBS>::ZERO;
237    let mut cur = a.clone(); // current term appearing in the sum
238
239    for i in 0..m {
240        let bit = b.bit(i as u32);
241
242        // if bit(b, i) = 1 then res += cur (sum here is just xor-ing)
243        let res_xor = res ^ cur;
244        res = Uint::<LIMBS>::conditional_select(&res, &res_xor, bit.into());
245
246        // We now multiply cur by x and reduce modulo P
247
248        let top = cur.bit((m - 1) as u32);
249        // top represents the bit of cur at index (m-1), which is the highest coefficient of cur mod modulus
250
251        let shifted = cur << 1;
252        // if top = 0 then the x^(m-1) term in cur was 0,
253        // so the term x^m does not appear in shifted and we don't need
254        // to simplify shifted using the equality x^m = lower.
255        let reduced = shifted ^ modulus;
256        // if top = 1 then the term x^(m-1) appears in cur,
257        // so x^m appears in shifted, and we need to simplify this using the equality x^m = lower.
258        // It suffices to add modulus, because this adds lower and kills the leading term.
259        cur = Uint::<LIMBS>::conditional_select(&shifted, &reduced, top.into());
260    }
261
262    res
263}
264
265fn square_helper<const LIMBS: usize, P>(a: &Uint<LIMBS>) -> Uint<LIMBS>
266where
267    P: BinaryIrreducible<LIMBS>,
268{
269    let m = P::degree();
270    let modulus = P::modulus();
271
272    let mut res = Uint::<LIMBS>::ZERO;
273    let mut cur = Uint::<LIMBS>::ONE; // cur = x^(2i) mod P, initially i = 0
274
275    for i in 0..m {
276        let bit = a.bit(i as u32);
277
278        // if bit(a, i) = 1 then res += x^(2i) mod P
279        let res_xor = res ^ cur;
280        res = Uint::<LIMBS>::conditional_select(&res, &res_xor, bit.into());
281
282        // multiply cur by x^2 and reduce mod P (we do so in 2 steps)
283        let top1 = cur.bit((m - 1) as u32);
284        let shifted1 = cur << 1;
285        let reduced1 = shifted1 ^ modulus;
286        cur = Uint::<LIMBS>::conditional_select(&shifted1, &reduced1, top1.into());
287
288        let top2 = cur.bit((m - 1) as u32);
289        let shifted2 = cur << 1;
290        let reduced2 = shifted2 ^ modulus;
291        cur = Uint::<LIMBS>::conditional_select(&shifted2, &reduced2, top2.into());
292    }
293
294    res
295}
296
297fn pow_2k_helper<const LIMBS: usize, P>(a: &Uint<LIMBS>, k: &usize) -> Uint<LIMBS>
298where
299    P: BinaryIrreducible<LIMBS>,
300{
301    let mut res = a.clone();
302    for _ in 0..*k {
303        res = square_helper::<LIMBS, P>(&res);
304    }
305    res
306}
307
308fn itoh_tsujii<const LIMBS: usize, P>(a: &Uint<LIMBS>) -> Uint<LIMBS>
309where
310    P: BinaryIrreducible<LIMBS>,
311{
312    // the degree m is public so its bits are public too!
313    let m = P::degree();
314    if m == 1 {
315        return a.clone();
316    }
317
318    let mut beta = a.clone();
319    let mut r = 1usize;
320
321    let top = (m - 1).ilog2(); // number of bits of m-1
322
323    for i in (0..top).rev() {
324        let beta_frob = pow_2k_helper::<LIMBS, P>(&beta, &r); // computing beta_r^(2r)
325        beta = mul_helper::<LIMBS, P>(&beta, &beta_frob); // computing beta_(2r) = beta_r * beta_r^(2r)
326        r <<= 1; // doubling r
327
328        if (((m - 1) >> i) & 1) == 1 {
329            beta = mul_helper::<LIMBS, P>(&square_helper::<LIMBS, P>(&beta), a);
330            // the current bit being 1, we compute beta_(2r+1) = a * beta_(2r)^2
331            r += 1;
332        }
333    }
334    square_helper::<LIMBS, P>(&beta)
335}
336
337fn sqrt_helper<const LIMBS: usize, P>(a: &Uint<LIMBS>) -> Uint<LIMBS>
338where
339    P: BinaryIrreducible<LIMBS>,
340{
341    // In the binary field F_{2^m}, we have sqrt(a) = a^(2^(m-1)) for any a (even a = 0)
342
343    let m = P::degree();
344    pow_2k_helper::<LIMBS, P>(&a, &(m - 1))
345}
346
347// ===========================================================================
348// Operator overloads (delegate to the FieldOps methods below)
349// ===========================================================================
350
351impl<const LIMBS: usize, P> Add for F2Ext<LIMBS, P>
352where
353    P: BinaryIrreducible<LIMBS>,
354{
355    type Output = Self;
356    fn add(self, rhs: Self) -> Self {
357        FieldOps::add(&self, &rhs)
358    }
359}
360
361impl<const LIMBS: usize, P> Sub for F2Ext<LIMBS, P>
362where
363    P: BinaryIrreducible<LIMBS>,
364{
365    type Output = Self;
366    fn sub(self, rhs: Self) -> Self {
367        FieldOps::sub(&self, &rhs)
368    }
369}
370
371impl<const LIMBS: usize, P> Mul for F2Ext<LIMBS, P>
372where
373    P: BinaryIrreducible<LIMBS>,
374{
375    type Output = Self;
376    fn mul(self, rhs: Self) -> Self {
377        FieldOps::mul(&self, &rhs)
378    }
379}
380
381impl<const LIMBS: usize, P> Neg for F2Ext<LIMBS, P>
382where
383    P: BinaryIrreducible<LIMBS>,
384{
385    type Output = Self;
386    fn neg(self) -> Self {
387        FieldOps::negate(&self)
388    }
389}
390
391// ===========================================================================
392// FieldOps implementation
393// ===========================================================================
394
395impl<const LIMBS: usize, P> FieldOps for F2Ext<LIMBS, P>
396where
397    P: BinaryIrreducible<LIMBS>,
398{
399    // to be done: legendre, sqrt
400    fn zero() -> Self {
401        Self::from_uint(Uint::<LIMBS>::ZERO)
402    }
403
404    fn one() -> Self {
405        Self::from_uint(Uint::<LIMBS>::ONE)
406    }
407
408    fn from_u64(x: u64) -> Self { Self::from_u64(x)  }
409
410    fn is_zero(&self) -> Choice {
411        Self::ct_eq(self, &Self::zero())
412    }
413
414    fn is_one(&self) -> Choice {
415        Self::ct_eq(self, &Self::one())
416    }
417
418    fn negate(&self) -> Self {
419        // we have x = -x for any element x of a binary field
420        *self
421    }
422
423    fn add(&self, rhs: &Self) -> Self {
424        Self::new(add_helper(&self.value, &rhs.value))
425    }
426
427    fn sub(&self, rhs: &Self) -> Self {
428        Self::new(add_helper(&self.value, &rhs.value))
429    }
430
431    fn mul(&self, rhs: &Self) -> Self {
432        Self::new(mul_helper::<LIMBS, P>(&self.value, &rhs.value))
433    }
434
435    fn square(&self) -> Self {
436        Self::new(square_helper::<LIMBS, P>(&self.value))
437    }
438
439    fn double(&self) -> Self {
440        // 2 = 0 in binary fields!
441        Self::zero()
442    }
443
444    fn invert(&self) -> CtOption<Self> {
445        let is_invertible = !self.is_zero();
446        CtOption::new(
447            Self::new(itoh_tsujii::<LIMBS, P>(&self.value)),
448            is_invertible,
449        )
450    }
451
452    fn frobenius(&self) -> Self {
453        self.square()
454    }
455
456    fn trace(&self) -> Self {
457        let deg = P::degree();
458        let mut result = self.clone();
459        let mut conj = self.frobenius();
460        for _ in 1..deg {
461            result = <Self as FieldOps>::add(&result, &conj);
462            conj = conj.frobenius();
463        }
464        result
465    }
466
467    fn norm(&self) -> Self {
468        let deg = P::degree();
469        let mut result = self.clone();
470        let mut conj = self.frobenius();
471        for _ in 1..deg {
472            result = <Self as FieldOps>::mul(&result, &conj);
473            conj = conj.frobenius();
474        }
475        result
476    }
477
478    fn sqrt(&self) -> CtOption<Self> {
479        // In binary fields, everything is a square, so the Choice object always wraps 1u8
480        let sqrt = Self::new(sqrt_helper::<LIMBS, P>(&self.value));
481        CtOption::new(sqrt, Choice::from(1u8))
482    }
483
484    fn legendre(&self) -> i8 {
485        let is_zero = self.is_zero();
486        i8::conditional_select(&0i8, &1i8, is_zero)
487    }
488
489    fn characteristic() -> Vec<u64> {
490        vec![2u64]
491    }
492
493    fn degree() -> u32 {
494        P::degree() as u32
495    }
496
497}
498
499// ---------------------------------------------------------------------------
500// Cryptographically secure random sampling
501// ---------------------------------------------------------------------------
502
503impl<const LIMBS: usize, P> FieldRandom for F2Ext<LIMBS, P>
504where
505    P: BinaryIrreducible<LIMBS>,
506{
507    /// Sample a uniformly random element of F_{2^m} using a CSPRNG.
508    ///
509    /// Fills a `Uint<LIMBS>` with random bytes, masks to `m` bits,
510    /// and wraps via `F2Ext::new` (which reduces mod the irreducible).
511    fn random(rng: &mut (impl rand::CryptoRng + rand::Rng)) -> Self {
512
513        let m = P::degree();
514        let mut words = [0u64; LIMBS];
515        for w in words.iter_mut() {
516            *w = rng.next_u64();
517        }
518
519        // Mask to m bits so we stay in the valid range [0, 2^m).
520        let full_limbs = m / 64;
521        let leftover = m % 64;
522
523        // Zero out limbs beyond the ones we need.
524        for w in words.iter_mut().skip(full_limbs + if leftover > 0 { 1 } else { 0 }) {
525            *w = 0;
526        }
527        // Mask the partial top limb.
528        if leftover > 0 && full_limbs < LIMBS {
529            words[full_limbs] &= (1u64 << leftover) - 1;
530        }
531
532        Self::new(Uint::from_words(words))
533    }
534}
535
536
537impl<const LIMBS: usize, P> FieldFromRepr for F2Ext<LIMBS, P>
538where
539    P: BinaryIrreducible<LIMBS>,
540{
541    type Repr = Uint<LIMBS>;
542
543    fn from_repr(x: Self::Repr) -> Self {
544        Self::from_uint(x)
545    }
546}