1use alloc::vec::Vec;
12use core::fmt::{self, Debug, Display, Formatter};
13use core::iter::{Product, Sum};
14use core::mem::ManuallyDrop;
15use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
16
17use num_bigint::BigUint;
18use p3_field::extension::HasFrobenius;
19use p3_field::op_assign_macros::{
20 impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_methods, impl_sub_assign,
21 impl_sub_base_field, ring_sum,
22};
23use p3_field::{
24 Algebra, AlgebraIdentity, BasedVectorSpace, ExtensionField, Field, Packable,
25 PackedFieldExtension, Powers, PrimeCharacteristicRing, RawDataSerializable,
26};
27use rand::Rng;
28use rand::distr::{Distribution, StandardUniform};
29use serde::{Deserialize, Serialize};
30
31use crate::gf2::characteristic_two_methods;
32use crate::{Gf2, Poly64};
33
34const DEGREE: usize = 3;
36
37#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
43#[serde(transparent)]
44#[repr(transparent)]
45#[must_use]
46pub struct Poly192([Poly64; DEGREE]);
47
48impl Poly192 {
49 pub(crate) const BITS: usize = DEGREE * 64;
51
52 #[inline]
54 pub const fn new(coefficients: [Poly64; DEGREE]) -> Self {
55 Self(coefficients)
56 }
57
58 #[inline]
60 pub const fn coefficients(self) -> [Poly64; DEGREE] {
61 self.0
62 }
63
64 #[inline]
66 const fn embed(value: Poly64) -> Self {
67 Self([value, Poly64::ZERO, Poly64::ZERO])
68 }
69
70 #[doc(hidden)]
74 #[inline]
75 pub fn composed_mul(self, rhs: Self) -> Self {
76 let ([a0, a1, a2], [b0, b1, b2]) = (self.0, rhs.0);
77
78 let c0 = a0 * b0;
80 let c1 = a1 * b1;
81 let c2 = a2 * b2;
82
83 let d01 = (a0 + a1) * (b0 + b1);
85 let d02 = (a0 + a2) * (b0 + b2);
86 let d12 = (a1 + a2) * (b1 + b2);
87
88 let p1 = d01 + c0 + c1;
90 let p2 = d02 + c0 + c1 + c2;
91 let p3 = d12 + c1 + c2;
92
93 Self([c0 + p3, p1 + p3 + c2, p2 + c2])
94 }
95
96 #[inline]
100 pub fn invert_or_zero(self) -> Self {
101 self.try_inverse().unwrap_or(Self::ZERO)
102 }
103}
104
105impl Packable for Poly192 {}
106
107impl Display for Poly192 {
108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 write!(f, "{} + {}*y + {}*y^2", self.0[0], self.0[1], self.0[2])
110 }
111}
112
113impl Debug for Poly192 {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 Debug::fmt(&self.0, f)
116 }
117}
118
119impl Distribution<Poly192> for StandardUniform {
120 #[inline]
121 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Poly192 {
122 Poly192(core::array::from_fn(|_| self.sample(rng)))
123 }
124}
125
126impl Add for Poly192 {
127 type Output = Self;
128
129 #[inline]
130 #[allow(clippy::suspicious_arithmetic_impl)]
131 fn add(self, rhs: Self) -> Self {
132 Self(core::array::from_fn(|i| self.0[i] + rhs.0[i]))
134 }
135}
136
137impl Sub for Poly192 {
138 type Output = Self;
139
140 #[inline]
141 #[allow(clippy::suspicious_arithmetic_impl)]
142 fn sub(self, rhs: Self) -> Self {
143 self + rhs
145 }
146}
147
148impl Neg for Poly192 {
149 type Output = Self;
150
151 #[inline]
152 fn neg(self) -> Self {
153 self
155 }
156}
157
158impl Mul for Poly192 {
159 type Output = Self;
160
161 #[inline]
180 fn mul(self, rhs: Self) -> Self {
181 #[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
182 {
183 let limbs = |x: Self| x.0.map(Poly64::to_bits);
184 Self(crate::clmul::poly_mul_192(limbs(self), limbs(rhs)).map(Poly64::new))
185 }
186 #[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
187 {
188 self.composed_mul(rhs)
189 }
190 }
191}
192
193impl Mul<Poly64> for Poly192 {
194 type Output = Self;
195
196 #[inline]
198 fn mul(self, rhs: Poly64) -> Self {
199 Self(self.0.map(|c| c * rhs))
200 }
201}
202
203impl Mul<Poly192> for Poly64 {
204 type Output = Poly192;
205
206 #[inline]
207 fn mul(self, rhs: Poly192) -> Poly192 {
208 rhs * self
209 }
210}
211
212impl PrimeCharacteristicRing for Poly192 {
213 #[inline]
214 fn zero_vec(len: usize) -> Vec<Self> {
215 let mut values = ManuallyDrop::new(alloc::vec![[0u64; DEGREE]; len]);
216 unsafe { Vec::from_raw_parts(values.as_mut_ptr().cast(), values.len(), values.capacity()) }
219 }
220
221 type PrimeSubfield = Gf2;
222
223 const ZERO: Self = Self::embed(Poly64::ZERO);
224 const ONE: Self = Self::embed(Poly64::ONE);
225 const TWO: Self = Self::embed(Poly64::ZERO);
227 const NEG_ONE: Self = Self::embed(Poly64::ONE);
229
230 #[inline]
231 fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
232 Self::embed(Poly64::from_prime_subfield(f))
233 }
234
235 #[inline]
242 fn square(&self) -> Self {
243 let [a0, a1, a2] = self.0;
244 let (s0, s1, s2) = (a0.square(), a1.square(), a2.square());
245 Self([s0, s2, s1 + s2])
246 }
247
248 characteristic_two_methods!();
249}
250
251impl Field for Poly192 {
252 type Packing = Self;
253
254 const GENERATOR: Self = Self([Poly64::new(5), Poly64::new(3), Poly64::new(1)]);
256
257 #[inline]
275 fn try_inverse(&self) -> Option<Self> {
276 let [a0, a1, a2] = self.0;
277
278 let diagonal = a0 + a2;
280
281 let c0 = diagonal.square() + a1.square() + a1 * a2;
283 let c1 = a1 * diagonal + (a1 + a2) * a2;
284 let c2 = a1.square() + diagonal * a2;
285
286 let norm = a0 * c0 + a2 * c1 + a1 * c2;
288
289 let candidate = Self([c0, c1, c2]) * norm.invert_or_zero();
291 (!norm.is_zero()).then_some(candidate)
292 }
293
294 #[inline]
302 fn try_sqrt(&self) -> Option<Self> {
303 let [b0, b1, b2] = self.0;
304 let root = |x: Poly64| {
305 x.try_sqrt()
306 .expect("every element of a binary field is a square")
307 };
308 Some(Self([root(b0), root(b1 + b2), root(b1)]))
309 }
310
311 #[inline]
312 fn order() -> BigUint {
313 BigUint::from(1u8) << Self::BITS
314 }
315
316 #[inline]
318 fn bits() -> usize {
319 Self::BITS
320 }
321
322 #[inline]
328 fn interpolation_node(i: usize) -> Self {
329 Self::embed(Poly64::interpolation_node(i))
330 }
331}
332
333impl RawDataSerializable for Poly192 {
334 const NUM_BYTES: usize = DEGREE * Poly64::NUM_BYTES;
335
336 #[inline]
337 fn into_bytes(self) -> impl IntoIterator<Item = u8> {
338 self.0.into_iter().flat_map(RawDataSerializable::into_bytes)
339 }
340}
341
342impl From<Poly64> for Poly192 {
343 #[inline]
345 fn from(x: Poly64) -> Self {
346 Self::embed(x)
347 }
348}
349
350impl From<Gf2> for Poly192 {
351 #[inline]
353 fn from(x: Gf2) -> Self {
354 Self::from_prime_subfield(x)
355 }
356}
357
358impl Mul<Gf2> for Poly192 {
359 type Output = Self;
360
361 #[inline]
362 fn mul(self, rhs: Gf2) -> Self {
363 if rhs.is_one() { self } else { Self::ZERO }
365 }
366}
367
368impl Mul<Poly192> for Gf2 {
369 type Output = Poly192;
370
371 #[inline]
372 fn mul(self, rhs: Poly192) -> Poly192 {
373 rhs * self
374 }
375}
376
377impl_add_assign!(Poly192);
378impl_sub_assign!(Poly192);
379impl_mul_methods!(Poly192);
380impl_div_methods!(Poly192, Poly192);
381impl_div_methods!(Poly192, Poly64);
382ring_sum!(Poly192);
383
384impl_add_base_field!(Poly192, Poly64);
385impl_sub_base_field!(Poly192, Poly64);
386impl_add_base_field!(Poly192, Gf2);
387impl_sub_base_field!(Poly192, Gf2);
388
389impl Algebra<Poly64> for Poly192 {}
390
391impl Algebra<Gf2> for Poly192 {}
392
393impl AlgebraIdentity<Poly64> for Poly192 {
394 fn algebra_id() -> Vec<u8> {
395 b"p3-binary-cubic-v1:y^3+y+1:64:192".to_vec()
396 }
397}
398
399impl BasedVectorSpace<Poly64> for Poly192 {
400 const DIMENSION: usize = DEGREE;
401
402 #[inline]
403 fn as_basis_coefficients_slice(&self) -> &[Poly64] {
404 &self.0
405 }
406
407 #[inline]
408 fn from_basis_coefficients_fn<F: FnMut(usize) -> Poly64>(f: F) -> Self {
409 Self(core::array::from_fn(f))
410 }
411
412 #[inline]
413 fn from_basis_coefficients_iter<I: ExactSizeIterator<Item = Poly64>>(iter: I) -> Option<Self> {
414 (iter.len() == DEGREE).then(|| {
415 let mut coefficients = [Poly64::ZERO; DEGREE];
417 for (slot, value) in coefficients.iter_mut().zip(iter) {
418 *slot = value;
419 }
420 Self(coefficients)
421 })
422 }
423
424 #[inline]
426 fn flatten_to_base(vec: Vec<Self>) -> Vec<Poly64> {
427 let mut out = Vec::with_capacity(vec.len() * DEGREE);
428 for element in vec {
429 out.extend_from_slice(&element.0);
430 }
431 out
432 }
433}
434
435impl ExtensionField<Poly64> for Poly192 {
436 type ExtensionPacking = Self;
437
438 #[inline]
439 fn is_in_basefield(&self) -> bool {
440 self.0[1].is_zero() && self.0[2].is_zero()
442 }
443
444 #[inline]
445 fn as_base(&self) -> Option<Poly64> {
446 <Self as ExtensionField<Poly64>>::is_in_basefield(self).then_some(self.0[0])
447 }
448}
449
450impl PackedFieldExtension<Poly64, Self> for Poly192 {
452 #[inline]
453 fn from_ext_fn(f: impl Fn(usize) -> Self) -> Self {
454 f(0)
455 }
456
457 #[inline]
458 fn from_ext_slice(slice: &[Self]) -> Self {
459 assert_eq!(slice.len(), 1);
460 slice[0]
461 }
462
463 #[inline]
464 fn extract(&self, lane: usize) -> Self {
465 assert_eq!(lane, 0, "lane index out of range");
466 *self
467 }
468
469 #[inline]
470 fn add_assign_lane(&mut self, lane: usize, value: Self) {
471 assert_eq!(lane, 0, "lane index out of range");
472 *self += value;
473 }
474
475 #[inline]
476 fn packed_ext_powers(base: Self) -> Powers<Self> {
477 base.powers()
478 }
479}
480
481impl HasFrobenius<Poly64> for Poly192 {
482 #[inline]
495 fn frobenius(&self) -> Self {
496 let [a0, a1, a2] = self.0;
497 Self([a0, a2, a1 + a2])
498 }
499
500 #[inline]
501 fn repeated_frobenius(&self, count: usize) -> Self {
502 match count % DEGREE {
504 0 => *self,
505 1 => self.frobenius(),
506 _ => self.frobenius().frobenius(),
507 }
508 }
509
510 #[inline]
511 fn pseudo_inv(&self) -> Self {
512 self.invert_or_zero()
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use p3_field::extension::HasFrobenius;
520 use p3_field::{BasedVectorSpace, ExtensionField, Field, PrimeCharacteristicRing};
521 use proptest::prelude::*;
522
523 use super::{DEGREE, Poly192};
524 use crate::Poly64;
525
526 fn element(a: [u64; 3]) -> Poly192 {
528 Poly192::new(a.map(Poly64::new))
529 }
530
531 fn basis_element(j: usize) -> Poly192 {
533 <Poly192 as BasedVectorSpace<Poly64>>::ith_basis_element(j).unwrap()
534 }
535
536 fn schoolbook(a: Poly192, b: Poly192) -> Poly192 {
542 let (x, y) = (a.coefficients(), b.coefficients());
543
544 let mut raw = [Poly64::ZERO; 5];
546 for (i, &xi) in x.iter().enumerate() {
547 for (j, &yj) in y.iter().enumerate() {
548 raw[i + j] += xi * yj;
549 }
550 }
551
552 raw[2] += raw[4];
557 raw[1] += raw[4];
558 raw[1] += raw[3];
559 raw[0] += raw[3];
560
561 Poly192::new([raw[0], raw[1], raw[2]])
562 }
563
564 fn inverse_by_elimination(a: Poly192) -> Option<Poly192> {
568 let columns: [Poly192; DEGREE] = core::array::from_fn(|j| a * basis_element(j));
570
571 let mut rows: [[Poly64; DEGREE + 1]; DEGREE] = core::array::from_fn(|i| {
573 core::array::from_fn(|j| {
574 if j < DEGREE {
575 columns[j].coefficients()[i]
576 } else {
577 Poly64::from_bool(i == 0)
578 }
579 })
580 });
581
582 for pivot in 0..DEGREE {
583 let found = (pivot..DEGREE).find(|&r| !rows[r][pivot].is_zero())?;
585 rows.swap(pivot, found);
586
587 let scale = rows[pivot][pivot].inverse();
589 for entry in &mut rows[pivot][pivot..] {
590 *entry *= scale;
591 }
592 let pivot_row = rows[pivot];
593 for (r, row) in rows.iter_mut().enumerate() {
594 if r == pivot || row[pivot].is_zero() {
595 continue;
596 }
597 let factor = row[pivot];
598 for (entry, &above) in row[pivot..].iter_mut().zip(&pivot_row[pivot..]) {
599 *entry += above * factor;
600 }
601 }
602 }
603
604 Some(Poly192::new(core::array::from_fn(|i| rows[i][DEGREE])))
605 }
606
607 #[test]
608 fn the_modulus_reduces_the_way_the_relation_says() {
609 let y = element([0, 1, 0]);
610
611 assert_eq!(y * y, element([0, 0, 1]));
613
614 assert_eq!(y * y * y, element([1, 1, 0]));
616
617 assert_eq!(y * y * y * y, element([0, 1, 1]));
619 }
620
621 #[test]
622 fn the_modulus_has_no_root_in_the_coefficient_field() {
623 for c in 0..=u8::MAX {
631 let x = Poly64::new(u64::from(c));
632 assert_ne!(x * x * x + x + Poly64::ONE, Poly64::ZERO, "{c}");
633 }
634 }
635
636 #[test]
637 fn the_frobenius_fixes_the_coefficient_field_and_has_order_three() {
638 for bits in [0u64, 1, 0x1b, u64::MAX] {
640 let embedded = Poly192::from(Poly64::new(bits));
641 assert_eq!(embedded.frobenius(), embedded, "{bits:#x}");
642 }
643
644 let y = element([0, 1, 0]);
646 assert_ne!(y.frobenius(), y);
647 assert_eq!(y.frobenius(), element([0, 0, 1]));
648 assert_eq!(y.frobenius().frobenius().frobenius(), y);
649 }
650
651 #[test]
652 fn zero_vectors_preserve_layout_and_support_growth() {
653 for len in [0, 1, 33, 1024] {
654 let mut values = Poly192::zero_vec(len);
655 assert_eq!(values.len(), len);
656 assert!(values.iter().all(|x| *x == Poly192::ZERO));
657 values.push(Poly192::ONE);
658 values.reserve(100);
659 assert_eq!(values.pop(), Some(Poly192::ONE));
660 }
661 }
662
663 proptest! {
664 #![proptest_config(ProptestConfig::with_cases(1000))]
665
666 #[test]
667 fn the_karatsuba_product_matches_the_schoolbook_one(a: [u64; 3], b: [u64; 3]) {
668 let (x, y) = (element(a), element(b));
669 prop_assert_eq!(x * y, schoolbook(x, y));
670 }
671
672 #[test]
673 fn the_square_matches_the_product_with_itself(a: [u64; 3]) {
674 let x = element(a);
675 prop_assert_eq!(x.square(), schoolbook(x, x));
676 }
677
678 #[test]
679 fn the_cofactor_inverse_matches_the_eliminated_one(a: [u64; 3]) {
680 let x = element(a);
681 prop_assert_eq!(x.try_inverse(), inverse_by_elimination(x));
682 }
683
684 #[test]
685 fn a_nonzero_element_times_its_inverse_is_one(a: [u64; 3]) {
686 let x = element(a);
687 match x.try_inverse() {
688 Some(inverse) => prop_assert_eq!(x * inverse, Poly192::ONE),
689 None => prop_assert_eq!(x, Poly192::ZERO),
690 }
691 }
692
693 #[test]
694 fn the_square_root_squares_back(a: [u64; 3]) {
695 let x = element(a);
696 let root = x.try_sqrt().expect("every element of a binary field is a square");
697 prop_assert_eq!(root.square(), x);
698 }
699
700 #[test]
702 fn the_frobenius_is_a_ring_homomorphism(a: [u64; 3], b: [u64; 3]) {
703 let (x, y) = (element(a), element(b));
704 prop_assert_eq!((x + y).frobenius(), x.frobenius() + y.frobenius());
705 prop_assert_eq!((x * y).frobenius(), x.frobenius() * y.frobenius());
706 }
707
708 #[test]
710 fn the_frobenius_is_the_power_map_it_claims_to_be(a: [u64; 3]) {
711 let x = element(a);
712 prop_assert_eq!(x.frobenius(), x.exp_power_of_2(64));
713 }
714
715 #[test]
717 fn scaling_by_a_coefficient_agrees_with_the_full_product(a: [u64; 3], s: u64) {
718 let (x, scalar) = (element(a), Poly64::new(s));
719 prop_assert_eq!(x * scalar, x * Poly192::from(scalar));
720 }
721
722 #[test]
723 fn the_coefficients_round_trip(a: [u64; 3]) {
724 let x = element(a);
725 let coefficients = BasedVectorSpace::<Poly64>::as_basis_coefficients_slice(&x);
726 prop_assert_eq!(
727 <Poly192 as BasedVectorSpace<Poly64>>::from_basis_coefficients_slice(coefficients),
728 Some(x)
729 );
730 prop_assert_eq!(
731 ExtensionField::<Poly64>::is_in_basefield(&x),
732 a[1] == 0 && a[2] == 0
733 );
734 }
735 }
736}