Skip to main content

p3_field/extension/
quintic_extension.rs

1//! Degree-5 extension field using the trinomial `X^5 + X^2 - 1`.
2//!
3//! This extension requires that `X^5 + X^2 - 1` is irreducible over the base field.
4//! Currently used for KoalaBear where irreducibility has been verified.
5//!
6//! Reduction identity: `X^5 = 1 - X^2`
7
8use alloc::format;
9use alloc::string::ToString;
10use alloc::vec::Vec;
11use core::array;
12use core::fmt::{self, Display, Formatter};
13use core::iter::{Product, Sum};
14use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
15
16use itertools::Itertools;
17use num_bigint::BigUint;
18use p3_util::{as_base_slice, as_base_slice_mut, reconstitute_from_base};
19
20use super::packed_quintic_extension::PackedQuinticTrinomialExtensionField;
21use super::{ExtField, HasFrobenius, HasTwoAdicQuinticExtension};
22use crate::extension::{ExtensionAlgebra, QuinticTrinomial, QuinticTrinomialExtendable};
23use crate::field::Field;
24use crate::{
25    Algebra, ExtensionField, PackedFieldExtension, PrimeCharacteristicRing, RawDataSerializable,
26    TwoAdicField, field_to_array,
27};
28
29/// A degree-5 extension field using the trinomial `X^5 + X^2 - 1`.
30///
31/// Elements are represented as `a_0 + a_1*X + a_2*X^2 + a_3*X^3 + a_4*X^4`.
32///
33/// Type alias for the unified [`ExtField`] with `Shape = QuinticTrinomial`.
34pub type QuinticTrinomialExtensionField<F, A = F> = ExtField<F, 5, QuinticTrinomial, A>;
35
36impl<F: Copy> QuinticTrinomialExtensionField<F, F> {
37    /// Convert a `[[F; 5]; N]` array to an array of extension field elements.
38    ///
39    /// # Panics
40    /// Panics if `N == 0`.
41    #[inline]
42    pub const fn new_array<const N: usize>(input: [[F; 5]; N]) -> [Self; N] {
43        const { assert!(N > 0) }
44        let mut output = [Self::new(input[0]); N];
45        let mut i = 1;
46        while i < N {
47            output[i] = Self::new(input[i]);
48            i += 1;
49        }
50        output
51    }
52}
53
54impl<F: QuinticTrinomialExtendable> ExtensionField<F> for QuinticTrinomialExtensionField<F>
55where
56    PackedQuinticTrinomialExtensionField<F, F::Packing>: PackedFieldExtension<F, Self>,
57{
58    type ExtensionPacking = PackedQuinticTrinomialExtensionField<F, F::Packing>;
59
60    #[inline]
61    fn is_in_basefield(&self) -> bool {
62        self.value[1..].iter().all(F::is_zero)
63    }
64
65    #[inline]
66    fn as_base(&self) -> Option<F> {
67        <Self as ExtensionField<F>>::is_in_basefield(self).then(|| self.value[0])
68    }
69}
70
71impl<F: QuinticTrinomialExtendable> HasFrobenius<F> for QuinticTrinomialExtensionField<F> {
72    #[inline]
73    fn frobenius(&self) -> Self {
74        let a = &self.value;
75        let fc = &F::FROBENIUS_COEFFS;
76
77        // φ(a) = a_0 + Σ_{k=1}^{4} a_k * X^{k*p} where X^{k*p} = fc[k-1]
78        let a_tail = &[a[1], a[2], a[3], a[4]];
79        let c0 = a[0] + F::dot_product::<4>(a_tail, &[fc[0][0], fc[1][0], fc[2][0], fc[3][0]]);
80        let c1 = F::dot_product::<4>(a_tail, &[fc[0][1], fc[1][1], fc[2][1], fc[3][1]]);
81        let c2 = F::dot_product::<4>(a_tail, &[fc[0][2], fc[1][2], fc[2][2], fc[3][2]]);
82        let c3 = F::dot_product::<4>(a_tail, &[fc[0][3], fc[1][3], fc[2][3], fc[3][3]]);
83        let c4 = F::dot_product::<4>(a_tail, &[fc[0][4], fc[1][4], fc[2][4], fc[3][4]]);
84
85        Self::new([c0, c1, c2, c3, c4])
86    }
87
88    /// Apply Frobenius `count` times: `x → x^{p^count}`.
89    #[inline]
90    fn repeated_frobenius(&self, count: usize) -> Self {
91        match count % 5 {
92            0 => *self,
93            _ => {
94                let mut result = *self;
95                for _ in 0..(count % 5) {
96                    result = result.frobenius();
97                }
98                result
99            }
100        }
101    }
102
103    /// Compute pseudo-inverse using Frobenius automorphism.
104    ///
105    /// Returns `0` if `self == 0`, and `1/self` otherwise.
106    ///
107    /// Uses the identity: `a^{-1} = ProdConj(a) * Norm(a)^{-1}` where
108    /// - `ProdConj(a) = a^{p^4 + p^3 + p^2 + p}`,
109    /// - `Norm(a) = a * ProdConj(a)` is in the base field.
110    #[inline]
111    fn pseudo_inv(&self) -> Self {
112        if self.is_zero() {
113            return Self::ZERO;
114        }
115
116        // Compute ProdConj(a) = a^{p^4 + p^3 + p^2 + p} efficiently:
117        // a^{p + p^2} = (a * a^p)^p, then a^{p + p^2 + p^3 + p^4} = (a^{p+p^2}) * (a^{p+p^2})^{p^2}
118        let a_exp_p = self.frobenius();
119        let a_exp_p_plus_p2 = (*self * a_exp_p).frobenius();
120        let prod_conj = a_exp_p_plus_p2 * a_exp_p_plus_p2.repeated_frobenius(2);
121
122        // Norm(a) = a * ProdConj(a) lies in the base field.
123        // Compute only the constant coefficient.
124        let norm = self.compute_norm_with_prod_conj(&prod_conj);
125        debug_assert_eq!(Self::from(norm), *self * prod_conj);
126
127        prod_conj * norm.inverse()
128    }
129}
130
131impl<F: QuinticTrinomialExtendable> QuinticTrinomialExtensionField<F> {
132    /// Compute the norm given pre-computed product of conjugates.
133    ///
134    /// The norm `Norm(a) = a * prod_conj` lies in the base field.
135    /// This computes only the constant coefficient for efficiency.
136    #[inline]
137    fn compute_norm_with_prod_conj(&self, prod_conj: &Self) -> F {
138        let a = &self.value;
139        let b = &prod_conj.value;
140
141        // For trinomial X^5 + X^2 - 1, the constant term of a*b is:
142        // c_0 + c_5 - c_8 where c_k = Σ_{i+j=k} a_i*b_j
143        let c0 = a[0] * b[0];
144        let c5 = F::dot_product::<4>(&[a[1], a[2], a[3], a[4]], &[b[4], b[3], b[2], b[1]]);
145        let c8 = a[4] * b[4];
146
147        c0 + c5 - c8
148    }
149}
150
151impl<F, A> PrimeCharacteristicRing for QuinticTrinomialExtensionField<F, A>
152where
153    F: QuinticTrinomialExtendable,
154    A: ExtensionAlgebra<F, 5, QuinticTrinomial> + Copy,
155{
156    type PrimeSubfield = <A as PrimeCharacteristicRing>::PrimeSubfield;
157
158    const ZERO: Self = Self::new([A::ZERO; 5]);
159    const ONE: Self = Self::new(field_to_array(A::ONE));
160    const TWO: Self = Self::new(field_to_array(A::TWO));
161    const NEG_ONE: Self = Self::new(field_to_array(A::NEG_ONE));
162
163    #[inline]
164    fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
165        <A as PrimeCharacteristicRing>::from_prime_subfield(f).into()
166    }
167
168    #[inline]
169    fn halve(&self) -> Self {
170        Self::new(array::from_fn(|i| self.value[i].halve()))
171    }
172
173    #[inline(always)]
174    fn square(&self) -> Self {
175        let mut res = Self::default();
176        <A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_square(&self.value, &mut res.value);
177        res
178    }
179
180    #[inline]
181    fn mul_2exp_u64(&self, exp: u64) -> Self {
182        Self::new(array::from_fn(|i| self.value[i].mul_2exp_u64(exp)))
183    }
184
185    #[inline]
186    fn div_2exp_u64(&self, exp: u64) -> Self {
187        Self::new(array::from_fn(|i| self.value[i].div_2exp_u64(exp)))
188    }
189
190    #[inline]
191    fn zero_vec(len: usize) -> Vec<Self> {
192        // SAFETY: `Self` is `repr(transparent)` over `[A; 5]`.
193        unsafe { reconstitute_from_base(A::zero_vec(len * 5)) }
194    }
195}
196
197impl<F: QuinticTrinomialExtendable> Algebra<F> for QuinticTrinomialExtensionField<F> {}
198
199impl<F: QuinticTrinomialExtendable> RawDataSerializable for QuinticTrinomialExtensionField<F> {
200    const NUM_BYTES: usize = F::NUM_BYTES * 5;
201
202    #[inline]
203    fn into_bytes(self) -> impl IntoIterator<Item = u8> {
204        self.value.into_iter().flat_map(|x| x.into_bytes())
205    }
206
207    #[inline]
208    fn into_byte_stream(input: impl IntoIterator<Item = Self>) -> impl IntoIterator<Item = u8> {
209        F::into_byte_stream(input.into_iter().flat_map(|x| x.value))
210    }
211
212    #[inline]
213    fn into_u32_stream(input: impl IntoIterator<Item = Self>) -> impl IntoIterator<Item = u32> {
214        F::into_u32_stream(input.into_iter().flat_map(|x| x.value))
215    }
216
217    #[inline]
218    fn into_u64_stream(input: impl IntoIterator<Item = Self>) -> impl IntoIterator<Item = u64> {
219        F::into_u64_stream(input.into_iter().flat_map(|x| x.value))
220    }
221
222    #[inline]
223    fn into_parallel_byte_streams<const N: usize>(
224        input: impl IntoIterator<Item = [Self; N]>,
225    ) -> impl IntoIterator<Item = [u8; N]> {
226        F::into_parallel_byte_streams(
227            input
228                .into_iter()
229                .flat_map(|x| (0..5).map(move |i| array::from_fn(|j| x[j].value[i]))),
230        )
231    }
232
233    #[inline]
234    fn into_parallel_u32_streams<const N: usize>(
235        input: impl IntoIterator<Item = [Self; N]>,
236    ) -> impl IntoIterator<Item = [u32; N]> {
237        F::into_parallel_u32_streams(
238            input
239                .into_iter()
240                .flat_map(|x| (0..5).map(move |i| array::from_fn(|j| x[j].value[i]))),
241        )
242    }
243
244    #[inline]
245    fn into_parallel_u64_streams<const N: usize>(
246        input: impl IntoIterator<Item = [Self; N]>,
247    ) -> impl IntoIterator<Item = [u64; N]> {
248        F::into_parallel_u64_streams(
249            input
250                .into_iter()
251                .flat_map(|x| (0..5).map(move |i| array::from_fn(|j| x[j].value[i]))),
252        )
253    }
254}
255
256impl<F: QuinticTrinomialExtendable> crate::AlgebraIdentity<F>
257    for QuinticTrinomialExtensionField<F>
258{
259    fn algebra_id() -> Vec<u8> {
260        b"p3-power-basis-v1:X^5+X^2-1".to_vec()
261    }
262}
263
264impl<F: QuinticTrinomialExtendable> Field for QuinticTrinomialExtensionField<F> {
265    type Packing = Self;
266
267    const GENERATOR: Self = Self::new(F::EXT_GENERATOR);
268
269    fn try_inverse(&self) -> Option<Self> {
270        if self.is_zero() {
271            return None;
272        }
273        Some(self.pseudo_inv())
274    }
275
276    #[inline]
277    fn add_slices(slice_1: &mut [Self], slice_2: &[Self]) {
278        // SAFETY: `Self` is `repr(transparent)` over `[F; 5]`.
279        // Addition is F-linear, so we can operate on base field slices.
280        unsafe {
281            let base_slice_1 = as_base_slice_mut(slice_1);
282            let base_slice_2 = as_base_slice(slice_2);
283            F::add_slices(base_slice_1, base_slice_2);
284        }
285    }
286
287    #[inline]
288    fn order() -> BigUint {
289        F::order().pow(5)
290    }
291}
292
293impl<F: QuinticTrinomialExtendable> Display for QuinticTrinomialExtensionField<F> {
294    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
295        if self.is_zero() {
296            write!(f, "0")
297        } else {
298            let str = self
299                .value
300                .iter()
301                .enumerate()
302                .filter(|(_, x)| !x.is_zero())
303                .map(|(i, x)| match (i, x.is_one()) {
304                    (0, _) => format!("{x}"),
305                    (1, true) => "X".to_string(),
306                    (1, false) => format!("{x} X"),
307                    (_, true) => format!("X^{i}"),
308                    (_, false) => format!("{x} X^{i}"),
309                })
310                .join(" + ");
311            write!(f, "{str}")
312        }
313    }
314}
315
316impl<F, A> Neg for QuinticTrinomialExtensionField<F, A>
317where
318    F: QuinticTrinomialExtendable,
319    A: Algebra<F>,
320{
321    type Output = Self;
322
323    #[inline]
324    fn neg(self) -> Self {
325        Self::new(self.value.map(A::neg))
326    }
327}
328
329impl<F, A> Add for QuinticTrinomialExtensionField<F, A>
330where
331    F: QuinticTrinomialExtendable,
332    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
333{
334    type Output = Self;
335
336    #[inline]
337    fn add(self, rhs: Self) -> Self {
338        Self::new(<A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_add(
339            &self.value,
340            &rhs.value,
341        ))
342    }
343}
344
345impl<F, A> Add<A> for QuinticTrinomialExtensionField<F, A>
346where
347    F: QuinticTrinomialExtendable,
348    A: Algebra<F>,
349{
350    type Output = Self;
351
352    #[inline]
353    fn add(mut self, rhs: A) -> Self {
354        self.value[0] += rhs;
355        self
356    }
357}
358
359impl<F, A> AddAssign for QuinticTrinomialExtensionField<F, A>
360where
361    F: QuinticTrinomialExtendable,
362    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
363{
364    #[inline]
365    fn add_assign(&mut self, rhs: Self) {
366        self.value =
367            <A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_add(&self.value, &rhs.value);
368    }
369}
370
371impl<F, A> AddAssign<A> for QuinticTrinomialExtensionField<F, A>
372where
373    F: QuinticTrinomialExtendable,
374    A: Algebra<F>,
375{
376    #[inline]
377    fn add_assign(&mut self, rhs: A) {
378        self.value[0] += rhs;
379    }
380}
381
382impl<F, A> Sum for QuinticTrinomialExtensionField<F, A>
383where
384    F: QuinticTrinomialExtendable,
385    A: ExtensionAlgebra<F, 5, QuinticTrinomial> + Copy,
386{
387    #[inline]
388    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
389        iter.reduce(|acc, x| acc + x).unwrap_or(Self::ZERO)
390    }
391}
392
393impl<F, A> Sub for QuinticTrinomialExtensionField<F, A>
394where
395    F: QuinticTrinomialExtendable,
396    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
397{
398    type Output = Self;
399
400    #[inline]
401    fn sub(self, rhs: Self) -> Self {
402        Self::new(<A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_sub(
403            &self.value,
404            &rhs.value,
405        ))
406    }
407}
408
409impl<F, A> Sub<A> for QuinticTrinomialExtensionField<F, A>
410where
411    F: QuinticTrinomialExtendable,
412    A: Algebra<F>,
413{
414    type Output = Self;
415
416    #[inline]
417    fn sub(self, rhs: A) -> Self {
418        let mut res = self.value;
419        res[0] -= rhs;
420        Self::new(res)
421    }
422}
423
424impl<F, A> SubAssign for QuinticTrinomialExtensionField<F, A>
425where
426    F: QuinticTrinomialExtendable,
427    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
428{
429    #[inline]
430    fn sub_assign(&mut self, rhs: Self) {
431        self.value =
432            <A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_sub(&self.value, &rhs.value);
433    }
434}
435
436impl<F, A> SubAssign<A> for QuinticTrinomialExtensionField<F, A>
437where
438    F: QuinticTrinomialExtendable,
439    A: Algebra<F>,
440{
441    #[inline]
442    fn sub_assign(&mut self, rhs: A) {
443        self.value[0] -= rhs;
444    }
445}
446
447impl<F, A> Mul for QuinticTrinomialExtensionField<F, A>
448where
449    F: QuinticTrinomialExtendable,
450    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
451{
452    type Output = Self;
453
454    #[inline]
455    fn mul(self, rhs: Self) -> Self {
456        let mut res = Self::default();
457        <A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_mul(
458            &self.value,
459            &rhs.value,
460            &mut res.value,
461        );
462        res
463    }
464}
465
466impl<F, A> Mul<A> for QuinticTrinomialExtensionField<F, A>
467where
468    F: QuinticTrinomialExtendable,
469    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
470{
471    type Output = Self;
472
473    #[inline]
474    fn mul(self, rhs: A) -> Self {
475        Self::new(<A as ExtensionAlgebra<F, 5, QuinticTrinomial>>::ext_base_mul(self.value, rhs))
476    }
477}
478
479impl<F, A> MulAssign for QuinticTrinomialExtensionField<F, A>
480where
481    F: QuinticTrinomialExtendable,
482    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
483{
484    #[inline]
485    fn mul_assign(&mut self, rhs: Self) {
486        *self = self.clone() * rhs;
487    }
488}
489
490impl<F, A> MulAssign<A> for QuinticTrinomialExtensionField<F, A>
491where
492    F: QuinticTrinomialExtendable,
493    A: ExtensionAlgebra<F, 5, QuinticTrinomial>,
494{
495    #[inline]
496    fn mul_assign(&mut self, rhs: A) {
497        *self = self.clone() * rhs;
498    }
499}
500
501impl<F, A> Product for QuinticTrinomialExtensionField<F, A>
502where
503    F: QuinticTrinomialExtendable,
504    A: ExtensionAlgebra<F, 5, QuinticTrinomial> + Copy,
505{
506    #[inline]
507    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
508        iter.reduce(|acc, x| acc * x).unwrap_or(Self::ONE)
509    }
510}
511
512impl<F> Div for QuinticTrinomialExtensionField<F>
513where
514    F: QuinticTrinomialExtendable,
515{
516    type Output = Self;
517
518    #[allow(clippy::suspicious_arithmetic_impl)]
519    #[inline]
520    fn div(self, rhs: Self) -> Self::Output {
521        self * rhs.inverse()
522    }
523}
524
525impl<F> DivAssign for QuinticTrinomialExtensionField<F>
526where
527    F: QuinticTrinomialExtendable,
528{
529    #[inline]
530    fn div_assign(&mut self, rhs: Self) {
531        *self = *self / rhs;
532    }
533}
534
535impl<F: QuinticTrinomialExtendable + HasTwoAdicQuinticExtension> TwoAdicField
536    for QuinticTrinomialExtensionField<F>
537{
538    const TWO_ADICITY: usize = F::EXT_TWO_ADICITY;
539
540    #[inline]
541    fn two_adic_generator(bits: usize) -> Self {
542        Self::new(F::ext_two_adic_generator(bits))
543    }
544}
545
546/// Multiply two elements in the quintic trinomial extension field (X^5 + X^2 - 1).
547#[inline]
548pub fn trinomial_quintic_mul<R: PrimeCharacteristicRing>(a: &[R; 5], b: &[R; 5], res: &mut [R; 5]) {
549    // Compute convolution coefficients c_k = Σ_{i+j=k} a_i * b_j using dot products
550    let c0 = a[0].dup() * b[0].dup();
551    let c1 = R::dot_product::<2>(&[a[0].dup(), a[1].dup()], &[b[1].dup(), b[0].dup()]);
552    let c2 = R::dot_product::<3>(
553        &[a[0].dup(), a[1].dup(), a[2].dup()],
554        &[b[2].dup(), b[1].dup(), b[0].dup()],
555    );
556    let c3 = R::dot_product::<4>(
557        &[a[0].dup(), a[1].dup(), a[2].dup(), a[3].dup()],
558        &[b[3].dup(), b[2].dup(), b[1].dup(), b[0].dup()],
559    );
560    let c4 = R::dot_product::<5>(
561        a,
562        &[b[4].dup(), b[3].dup(), b[2].dup(), b[1].dup(), b[0].dup()],
563    );
564
565    // High-degree coefficients
566    let c5 = R::dot_product::<4>(
567        &[a[1].dup(), a[2].dup(), a[3].dup(), a[4].dup()],
568        &[b[4].dup(), b[3].dup(), b[2].dup(), b[1].dup()],
569    );
570    let c6 = R::dot_product::<3>(
571        &[a[2].dup(), a[3].dup(), a[4].dup()],
572        &[b[4].dup(), b[3].dup(), b[2].dup()],
573    );
574    let c7 = R::dot_product::<2>(&[a[3].dup(), a[4].dup()], &[b[4].dup(), b[3].dup()]);
575    let c8 = a[4].dup() * b[4].dup();
576
577    // Pre-compute c5 - c8 to save an operation
578    let c5_minus_c8 = c5 - c8.dup();
579
580    // Apply reduction: X^5 = 1 - X^2, X^6 = X - X^3, X^7 = X^2 - X^4, X^8 = X^3 + X^2 - 1
581    res[0] = c0 + c5_minus_c8.dup();
582    res[1] = c1 + c6.dup();
583    res[2] = c2 - c5_minus_c8 + c7.dup();
584    res[3] = c3 - c6 + c8;
585    res[4] = c4 - c7;
586}
587
588/// Square an element in the quintic extension field.
589#[inline]
590pub fn quintic_square<R: PrimeCharacteristicRing>(a: &[R; 5], res: &mut [R; 5]) {
591    // Precompute doubled coefficients for cross terms
592    let a0_2 = a[0].double();
593    let a1_2 = a[1].double();
594    let a2_2 = a[2].double();
595    let a3_2 = a[3].double();
596
597    // Compute convolution coefficients using dot products
598    // c0 = a0^2
599    let c0 = a[0].square();
600    // c1 = 2*a0*a1
601    let c1 = a0_2.dup() * a[1].dup();
602    // c2 = 2*a0*a2 + a1^2
603    let c2 = R::dot_product::<2>(&[a0_2.dup(), a[1].dup()], &[a[2].dup(), a[1].dup()]);
604    // c3 = 2*a0*a3 + 2*a1*a2
605    let c3 = R::dot_product::<2>(&[a0_2.dup(), a1_2.dup()], &[a[3].dup(), a[2].dup()]);
606    // c4 = 2*a0*a4 + 2*a1*a3 + a2^2
607    let c4 = R::dot_product::<3>(
608        &[a0_2, a1_2.dup(), a[2].dup()],
609        &[a[4].dup(), a[3].dup(), a[2].dup()],
610    );
611    // c5 = 2*a1*a4 + 2*a2*a3
612    let c5 = R::dot_product::<2>(&[a1_2, a2_2.dup()], &[a[4].dup(), a[3].dup()]);
613    // c6 = 2*a2*a4 + a3^2
614    let c6 = R::dot_product::<2>(&[a2_2, a[3].dup()], &[a[4].dup(), a[3].dup()]);
615    // c7 = 2*a3*a4
616    let c7 = a3_2 * a[4].dup();
617    // c8 = a4^2
618    let c8 = a[4].square();
619
620    // Pre-compute c5 - c8 to save an operation
621    let c5_minus_c8 = c5 - c8.dup();
622
623    // Apply reduction: X^5 = 1 - X^2, X^6 = X - X^3, X^7 = X^2 - X^4, X^8 = X^3 + X^2 - 1
624    res[0] = c0 + c5_minus_c8.dup();
625    res[1] = c1 + c6.dup();
626    res[2] = c2 - c5_minus_c8 + c7.dup();
627    res[3] = c3 - c6 + c8;
628    res[4] = c4 - c7;
629}