Skip to main content

p3_binary_field/
poly64.rs

1//! `GF(2^64)` in the polynomial basis of `x^64 + x^4 + x^3 + x + 1`.
2
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5use core::iter::{Product, Sum};
6use core::mem::ManuallyDrop;
7use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
8use core::slice;
9
10use num_bigint::BigUint;
11use p3_field::op_assign_macros::{
12    impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
13    impl_sub_assign, impl_sub_base_field, ring_sum,
14};
15use p3_field::{Algebra, Field, Packable, PrimeCharacteristicRing, RawDataSerializable};
16use p3_maybe_rayon::prelude::*;
17use rand::Rng;
18use rand::distr::{Distribution, StandardUniform};
19use serde::{Deserialize, Serialize};
20
21use crate::cantor::CANTOR_BASIS_128;
22use crate::gf2::characteristic_two_methods;
23use crate::tower::TowerLevel;
24use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, Gf2, clmul};
25
26/// Elements one parallel task converts between the two 64-bit bases.
27const TABLE_CHUNK: usize = 1 << 16;
28
29/// The Cantor basis in this representation, carried over from the tower's own.
30///
31/// Both representations then span the same additive transform domain.
32const CANTOR_BASIS: [u64; 64] = {
33    let mut basis = [0u64; 64];
34    let mut i = 0;
35    while i < 64 {
36        basis[i] = clmul::tower_image_64(CANTOR_BASIS_128[i] as u64);
37        i += 1;
38    }
39    basis
40};
41
42/// The bit pattern of the multiplicative generator of the tower representation.
43///
44/// Its image here is the generator, so the change of basis carries one onto the other.
45const TOWER_GENERATOR: u64 = 0x1_0000_0004;
46
47/// The generator of this level over the one below, in this representation.
48///
49/// The tower carries that element as a single basis vector, at bit 32.
50const ALPHA: u64 = clmul::tower_image_64(1 << 32);
51
52/// The binary field `GF(2^64)` modulo `x^64 + x^4 + x^3 + x + 1`.
53///
54/// Bit `i` stores the coefficient of `x^i`, and every 64-bit pattern is a distinct element.
55///
56/// One carryless product covers a whole multiplication, against four at 128 bits.
57///
58/// A committed column here is also half the volume of a 128-bit one.
59///
60/// Multiplication, squaring, the square root and inversion here are all table-free.
61///
62/// The change of basis to and from the tower is table-driven, and so is not.
63#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
64#[serde(transparent)]
65#[repr(transparent)]
66#[must_use]
67pub struct Poly64(u64);
68
69impl Poly64 {
70    /// The number of bits of an element.
71    pub(crate) const BITS: usize = 64;
72
73    /// Converts a whole tower-basis table in its existing allocation.
74    ///
75    /// Every entry is the image of the corresponding tower element under the field isomorphism.
76    pub fn from_tower_vec(values: Vec<BinaryField64>) -> Vec<Self> {
77        let mut values = ManuallyDrop::new(values);
78        let (ptr, len, capacity) = (values.as_mut_ptr(), values.len(), values.capacity());
79
80        // Both fields are transparent over a 64-bit word.
81        // Reusing the allocation avoids copying the residual tables before sumcheck.
82        let words = unsafe { slice::from_raw_parts_mut(ptr.cast::<u64>(), len) };
83        words.par_chunks_mut(TABLE_CHUNK).for_each(|chunk| {
84            chunk
85                .iter_mut()
86                .for_each(|word| *word = clmul::tower_to_poly_64(*word));
87        });
88
89        // SAFETY: both element types have the size and alignment of `u64`.
90        // Every 64-bit pattern is canonical in both fields.
91        unsafe { Vec::from_raw_parts(ptr.cast::<Self>(), len, capacity) }
92    }
93
94    /// The element with the given polynomial-basis coordinates.
95    ///
96    /// Bit `i` is the coefficient of `x^i`, and every pattern is a valid element.
97    #[inline]
98    pub const fn new(coordinates: u64) -> Self {
99        Self(coordinates)
100    }
101
102    /// The polynomial-basis coordinates of this element.
103    #[must_use]
104    #[inline]
105    pub const fn to_bits(self) -> u64 {
106        self.0
107    }
108
109    /// Construct a field element from its little-endian byte representation.
110    ///
111    /// Every byte string of this length is a valid element.
112    #[inline]
113    pub const fn from_le_bytes(bytes: [u8; 8]) -> Self {
114        Self(u64::from_le_bytes(bytes))
115    }
116
117    /// The inverse of this element, with zero sent to zero.
118    ///
119    /// The chain runs whatever the operand is, so its cost says nothing about the value.
120    #[inline]
121    pub fn invert_or_zero(self) -> Self {
122        Self(clmul::poly_inverse_64(self.0))
123    }
124}
125
126impl Packable for Poly64 {}
127
128impl Display for Poly64 {
129    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130        Display::fmt(&self.0, f)
131    }
132}
133
134impl Debug for Poly64 {
135    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
136        Debug::fmt(&self.0, f)
137    }
138}
139
140impl Distribution<Poly64> for StandardUniform {
141    #[inline]
142    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Poly64 {
143        let mut bytes = [0u8; 8];
144        rng.fill_bytes(&mut bytes);
145        Poly64::from_le_bytes(bytes)
146    }
147}
148
149impl PrimeCharacteristicRing for Poly64 {
150    #[inline]
151    fn zero_vec(len: usize) -> Vec<Self> {
152        let mut values = ManuallyDrop::new(alloc::vec![0u64; len]);
153        // SAFETY: the transparent wrapper has exactly the integer's layout, and zero is
154        // canonical. The allocation retains its original size and alignment.
155        unsafe { Vec::from_raw_parts(values.as_mut_ptr().cast(), values.len(), values.capacity()) }
156    }
157
158    type PrimeSubfield = Gf2;
159
160    const ZERO: Self = Self(0);
161    const ONE: Self = Self(1);
162    // The characteristic is 2, so `TWO = ONE + ONE = ZERO`.
163    const TWO: Self = Self(0);
164    // The characteristic is 2, so `NEG_ONE = ONE`.
165    const NEG_ONE: Self = Self(1);
166
167    #[inline]
168    fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
169        Self::from_bool(f.is_one())
170    }
171
172    #[inline]
173    fn from_bool(b: bool) -> Self {
174        Self(u64::from(b))
175    }
176
177    characteristic_two_methods!();
178
179    #[inline]
180    fn square(&self) -> Self {
181        Self(clmul::poly_square_64(self.0))
182    }
183
184    #[inline]
185    fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
186        // Reduction is linear, so an entire sum pays for it only once.
187        Self(clmul::poly_dot_64(u.iter().zip(v).map(|(a, b)| (a.0, b.0))))
188    }
189}
190
191impl Field for Poly64 {
192    type Packing = Self;
193
194    const GENERATOR: Self = Self(clmul::tower_image_64(TOWER_GENERATOR));
195
196    #[inline]
197    fn try_inverse(&self) -> Option<Self> {
198        // The chain runs whatever the operand, so its cost says nothing about the value.
199        //
200        // Zero is outside the multiplicative group and the chain already sends it to itself.
201        let inverse = self.invert_or_zero();
202        (self.0 != 0).then_some(inverse)
203    }
204
205    #[inline]
206    fn try_sqrt(&self) -> Option<Self> {
207        // Separate even and odd coefficients to invert the squaring map directly.
208        Some(Self(clmul::poly_sqrt_64(self.0)))
209    }
210
211    #[inline]
212    fn order() -> BigUint {
213        BigUint::from(1u8) << Self::BITS
214    }
215
216    /// An element of `GF(2^n)` is exactly `n` bits wide.
217    #[inline]
218    fn bits() -> usize {
219        Self::BITS
220    }
221
222    /// The enumeration by bit pattern.
223    ///
224    /// The coordinates of the returned element over `GF(2)` are the bits of the index.
225    ///
226    /// A pointer is never wider than 64 bits, so every index is in range.
227    #[inline]
228    fn interpolation_node(i: usize) -> Self {
229        Self(i as u64)
230    }
231}
232
233impl RawDataSerializable for Poly64 {
234    const NUM_BYTES: usize = 8;
235
236    #[inline]
237    fn into_bytes(self) -> impl IntoIterator<Item = u8> {
238        self.0.to_le_bytes()
239    }
240}
241
242impl crate::tower::private::Sealed for Poly64 {}
243
244impl TowerLevel for Poly64 {
245    type Repr = u64;
246
247    const LOG_BITS: usize = 6;
248
249    #[inline]
250    fn from_repr(r: Self::Repr) -> Self {
251        Self(r)
252    }
253
254    #[inline]
255    fn to_repr(self) -> Self::Repr {
256        self.0
257    }
258
259    /// The tower scales by a basis vector, which costs it a shift.
260    ///
261    /// Here the same element is an arbitrary one, so it costs a full product.
262    #[inline]
263    fn mul_alpha(self) -> Self {
264        self * Self(ALPHA)
265    }
266
267    /// # Panics
268    /// Panics if the stream ends before a whole element has been read.
269    #[inline]
270    fn from_le_byte_iter(mut bytes: impl Iterator<Item = u8>) -> Self {
271        let mut buffer = [0u8; 8];
272        for byte in &mut buffer {
273            *byte = bytes
274                .next()
275                .expect("byte stream ended before a whole element was read");
276        }
277        Self::from_le_bytes(buffer)
278    }
279
280    /// # Panics
281    /// Panics if the index is at least the bit width of the field.
282    #[inline]
283    fn cantor_basis(i: usize) -> Self {
284        assert!(i < Self::BITS, "Cantor basis index out of range");
285        Self(CANTOR_BASIS[i])
286    }
287}
288
289impl From<BinaryField64> for Poly64 {
290    /// The same field element, seen in the polynomial basis.
291    #[inline]
292    fn from(x: BinaryField64) -> Self {
293        Self(clmul::tower_to_poly_64(x.to_repr()))
294    }
295}
296
297impl From<Poly64> for BinaryField64 {
298    /// The same field element, seen in the tower basis.
299    #[inline]
300    fn from(x: Poly64) -> Self {
301        Self::from_repr(clmul::poly_to_tower_64(x.0))
302    }
303}
304
305impl Add<BinaryField64> for Poly64 {
306    type Output = Self;
307
308    #[inline]
309    fn add(self, rhs: BinaryField64) -> Self {
310        self + Self::from(rhs)
311    }
312}
313
314impl Sub<BinaryField64> for Poly64 {
315    type Output = Self;
316
317    #[inline]
318    fn sub(self, rhs: BinaryField64) -> Self {
319        self - Self::from(rhs)
320    }
321}
322
323impl Mul<BinaryField64> for Poly64 {
324    type Output = Self;
325
326    #[inline]
327    fn mul(self, rhs: BinaryField64) -> Self {
328        self * Self::from(rhs)
329    }
330}
331
332impl Algebra<BinaryField64> for Poly64 {}
333
334macro_rules! impl_narrow_algebra {
335    ($($field:ty),* $(,)?) => {$(
336        impl From<$field> for Poly64 {
337            #[inline]
338            fn from(x: $field) -> Self {
339                Self::from(BinaryField64::from(x))
340            }
341        }
342
343        impl_add_base_field!(Poly64, $field);
344        impl_sub_base_field!(Poly64, $field);
345        impl_mul_base_field!(Poly64, $field);
346
347        impl Algebra<$field> for Poly64 {}
348    )*};
349}
350
351impl_narrow_algebra!(BinaryField8, BinaryField16, BinaryField32);
352
353impl Add for Poly64 {
354    type Output = Self;
355
356    #[inline]
357    #[allow(clippy::suspicious_arithmetic_impl)]
358    fn add(self, rhs: Self) -> Self {
359        // Addition in characteristic 2 is `XOR`.
360        Self(self.0 ^ rhs.0)
361    }
362}
363
364impl Sub for Poly64 {
365    type Output = Self;
366
367    #[inline]
368    #[allow(clippy::suspicious_arithmetic_impl)]
369    fn sub(self, rhs: Self) -> Self {
370        // Subtraction coincides with addition in characteristic 2.
371        self + rhs
372    }
373}
374
375impl Neg for Poly64 {
376    type Output = Self;
377
378    #[inline]
379    fn neg(self) -> Self {
380        // `-x = x` in characteristic 2.
381        self
382    }
383}
384
385impl Mul for Poly64 {
386    type Output = Self;
387
388    #[inline]
389    fn mul(self, rhs: Self) -> Self {
390        Self(clmul::poly_mul_64(self.0, rhs.0))
391    }
392}
393
394impl_add_assign!(Poly64);
395impl_sub_assign!(Poly64);
396impl_mul_methods!(Poly64);
397impl_div_methods!(Poly64, Poly64);
398ring_sum!(Poly64);
399
400impl From<Gf2> for Poly64 {
401    /// `GF(2)` is the prime subfield, embedded as `{ZERO, ONE}`.
402    #[inline]
403    fn from(x: Gf2) -> Self {
404        Self::from_prime_subfield(x)
405    }
406}
407
408impl_add_base_field!(Poly64, Gf2);
409impl_sub_base_field!(Poly64, Gf2);
410impl_mul_base_field!(Poly64, Gf2);
411
412impl Algebra<Gf2> for Poly64 {}
413
414#[cfg(test)]
415mod tests {
416    use alloc::vec::Vec;
417
418    use p3_field::{Algebra, Field, PrimeCharacteristicRing};
419    use proptest::prelude::*;
420
421    use super::Poly64;
422    use crate::tower::TowerLevel;
423    use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, Gf2};
424
425    #[test]
426    fn tower_table_conversion_and_narrow_algebras_match_the_tower() {
427        // The bulk conversion must be the scalar field isomorphism without another allocation.
428        let table: Vec<BinaryField64> = (0..257)
429            .map(|i| BinaryField64::from_repr(i * 0x9e37_79b9))
430            .collect();
431        let expected: Vec<Poly64> = table.iter().copied().map(Poly64::from).collect();
432        let allocation = table.as_ptr();
433        let converted = Poly64::from_tower_vec(table);
434        assert_eq!(converted, expected);
435        assert_eq!(converted.as_ptr().cast::<BinaryField64>(), allocation);
436
437        // Each supported subfield must use the same embedding as tower multiplication.
438        const fn assert_algebra<F: PrimeCharacteristicRing, A: Algebra<F>>() {}
439        assert_algebra::<BinaryField8, Poly64>();
440        assert_algebra::<BinaryField16, Poly64>();
441        assert_algebra::<BinaryField32, Poly64>();
442        assert_algebra::<BinaryField64, Poly64>();
443
444        let x = BinaryField64::from_repr(0xd6e8_feb8_6659_fd93);
445        let x_poly = Poly64::from(x);
446        let scalar = BinaryField32::from_repr(0xa5c3_19e7);
447        assert_eq!(
448            BinaryField64::from(x_poly * scalar),
449            x * BinaryField64::from(scalar)
450        );
451    }
452
453    #[test]
454    fn the_change_of_basis_fixes_the_constants() {
455        // Any field isomorphism fixes zero and one.
456        assert_eq!(Poly64::from(BinaryField64::ZERO), Poly64::ZERO);
457        assert_eq!(Poly64::from(BinaryField64::ONE), Poly64::ONE);
458    }
459
460    #[test]
461    fn the_generator_is_the_image_of_the_tower_generator() {
462        // Both representations are the same field, so one generator maps onto the other.
463        //
464        // This is what pins the transcribed bit pattern the constant is built from.
465        assert_eq!(Poly64::GENERATOR, Poly64::from(BinaryField64::GENERATOR));
466    }
467
468    #[test]
469    fn the_generator_has_the_full_order() {
470        // Invariant: 2^64 - 1 = 3 * 5 * 17 * 257 * 641 * 65537 * 6700417.
471        //
472        // A generator survives every proper-divisor power.
473        let order = u64::MAX;
474        for prime in [3u64, 5, 17, 257, 641, 65537, 6_700_417] {
475            assert_ne!(
476                Poly64::GENERATOR.exp_u64(order / prime),
477                Poly64::ONE,
478                "{prime}"
479            );
480        }
481        assert_eq!(Poly64::GENERATOR.exp_u64(order), Poly64::ONE);
482    }
483
484    #[test]
485    fn the_cantor_basis_satisfies_its_recurrence() {
486        // Invariant: v_0 = 1 and v_i^2 + v_i = v_{i-1}.
487        assert_eq!(Poly64::cantor_basis(0), Poly64::ONE);
488        for i in 1..Poly64::BITS {
489            let v = Poly64::cantor_basis(i);
490            assert_eq!(v.square() + v, Poly64::cantor_basis(i - 1), "vector {i}");
491        }
492
493        // And it is the image of the tower's own, so both span the same transform domain.
494        for i in 0..Poly64::BITS {
495            assert_eq!(
496                Poly64::cantor_basis(i),
497                Poly64::from(BinaryField64::cantor_basis(i)),
498                "vector {i}"
499            );
500        }
501    }
502
503    #[test]
504    #[should_panic = "Cantor basis index out of range"]
505    fn the_cantor_basis_rejects_an_index_beyond_the_field() {
506        let _vector = Poly64::cantor_basis(64);
507    }
508
509    #[test]
510    #[should_panic = "byte stream ended before a whole element was read"]
511    fn a_truncated_byte_stream_is_rejected() {
512        // Seven bytes is one short of an element.
513        let _element = Poly64::from_le_byte_iter([0u8; 7].into_iter());
514    }
515
516    #[test]
517    fn zero_vectors_preserve_layout_and_support_growth() {
518        for len in [0, 1, 33, 1024] {
519            let mut values = Poly64::zero_vec(len);
520            assert_eq!(values.len(), len);
521            assert!(values.iter().all(|x| *x == Poly64::ZERO));
522            values.push(Poly64::ONE);
523            values.reserve(100);
524            assert_eq!(values.pop(), Some(Poly64::ONE));
525        }
526    }
527
528    proptest! {
529        #![proptest_config(ProptestConfig::with_cases(2000))]
530
531        #[test]
532        fn the_change_of_basis_round_trips(bits: u64) {
533            let x = Poly64::from_repr(bits);
534            prop_assert_eq!(Poly64::from(BinaryField64::from(x)), x);
535        }
536
537        /// The change of basis must carry products, not merely sums.
538        #[test]
539        fn the_change_of_basis_is_a_ring_isomorphism(a: u64, b: u64) {
540            let (x, y) = (BinaryField64::from_repr(a), BinaryField64::from_repr(b));
541            prop_assert_eq!(Poly64::from(x + y), Poly64::from(x) + Poly64::from(y));
542            prop_assert_eq!(
543                Poly64::from(x.reference_mul(y)),
544                Poly64::from(x) * Poly64::from(y)
545            );
546        }
547
548        /// Scaling by the level's own generator must agree across the two representations.
549        #[test]
550        fn scaling_by_the_generator_agrees_with_the_tower(bits: u64) {
551            let x = BinaryField64::from_repr(bits);
552            prop_assert_eq!(Poly64::from(x).mul_alpha(), Poly64::from(x.mul_alpha()));
553        }
554
555        #[test]
556        fn the_prime_subfield_embeds_as_zero_and_one(bit: bool) {
557            prop_assert_eq!(Poly64::from(Gf2::from_bool(bit)), Poly64::from_bool(bit));
558        }
559
560        #[test]
561        fn the_byte_stream_reads_back_what_the_element_wrote(bits: u64) {
562            use p3_field::RawDataSerializable;
563            let x = Poly64::from_repr(bits);
564            prop_assert_eq!(Poly64::from_le_byte_iter(x.into_bytes().into_iter()), x);
565        }
566    }
567
568    #[test]
569    fn serde_accepts_every_bit_pattern() {
570        // No bit is masked off, so nothing is out of range.
571        for bits in [0u64, 1, u64::MAX, 1 << 63] {
572            let x = Poly64::from_repr(bits);
573            let encoded = serde_json::to_string(&x).unwrap();
574            assert_eq!(serde_json::from_str::<Poly64>(&encoded).unwrap(), x);
575        }
576    }
577}