Skip to main content

ps_ecc/polynomial/methods/
mul_xor_assign.rs

1use crate::{finite_field::mul, Polynomial, PolynomialMulError};
2
3impl Polynomial {
4    /// Fused multiply-XOR: `self ^= a * b`
5    ///
6    /// Computes the product of polynomials `a` and `b`, `XORing` the result into `self`.
7    /// This avoids allocating an intermediate polynomial for the product.
8    ///
9    /// In GF(2^8), XOR is equivalent to both addition and subtraction, so this
10    /// computes `self = self + a * b` or equivalently `self = self - a * b`.
11    /// # Errors
12    /// Returns [`PolynomialMulError::DegreeOverflow`] if the product degree,
13    /// `a.degree() + b.degree()`, exceeds [`Polynomial::MAX_DEGREE`].
14    /// `self` is unmodified on error.
15    #[inline]
16    pub fn mul_xor_assign(&mut self, a: &Self, b: &Self) -> Result<(), PolynomialMulError> {
17        let a_deg = a.degree as usize;
18        let b_deg = b.degree as usize;
19
20        // Early exit if either operand is zero polynomial
21        if a.coefficients[a_deg] == 0 || b.coefficients[b_deg] == 0 {
22            return Ok(());
23        }
24
25        let product_deg = a_deg + b_deg;
26
27        if product_deg > usize::from(Self::MAX_DEGREE) {
28            return Err(PolynomialMulError::DegreeOverflow);
29        }
30
31        // Accumulate a * b into self via XOR
32        for i in 0..=a_deg {
33            let a_coef = a.coefficients[i];
34
35            if a_coef == 0 {
36                continue;
37            }
38
39            for j in 0..=b_deg {
40                let b_coef = b.coefficients[j];
41
42                if b_coef == 0 {
43                    continue;
44                }
45
46                self.coefficients[i + j] ^= mul(a_coef, b_coef);
47            }
48        }
49
50        // Update degree: max of original self degree and product degree
51        #[allow(clippy::cast_possible_truncation)] // product_deg <= MAX_DEGREE
52        if product_deg > self.degree as usize {
53            self.degree = product_deg as u8;
54        }
55
56        self.trim_degree();
57
58        Ok(())
59    }
60}
61
62#[cfg(test)]
63#[allow(clippy::expect_used)]
64mod tests {
65    use crate::{Polynomial, PolynomialMulError};
66
67    #[test]
68    fn mul_xor_assign_max_product_degree() {
69        // 127 + 127 = 254 = MAX_DEGREE, the largest representable product.
70        let mut dest = Polynomial::default();
71        let mut a = Polynomial::default();
72        let mut b = Polynomial::default();
73
74        a.set(127, 1);
75        b.set(127, 1);
76
77        dest.mul_xor_assign(&a, &b).expect("product degree fits");
78
79        assert_eq!(dest.degree(), 254);
80    }
81
82    #[test]
83    fn mul_xor_assign_rejects_degree_overflow() {
84        // 128 + 127 = 255 > MAX_DEGREE; previously wrote past the
85        // 255-element coefficient array.
86        let mut dest = Polynomial::try_from(&[7u8][..]).expect("valid polynomial");
87        let mut a = Polynomial::default();
88        let mut b = Polynomial::default();
89
90        a.set(128, 1);
91        b.set(127, 1);
92
93        let original = dest;
94        let result = dest.mul_xor_assign(&a, &b);
95
96        assert_eq!(result, Err(PolynomialMulError::DegreeOverflow));
97        assert_eq!(dest, original);
98    }
99
100    #[test]
101    fn mul_xor_assign_basic() {
102        // dest = [1], a = [1, 1], b = [1, 1]
103        // a * b = [1, 0, 1] (since 1*1 ^ 1*1 = 0 for middle term in GF(2))
104        // dest ^= a * b => [1] ^ [1, 0, 1] = [0, 0, 1]
105        let mut dest = Polynomial::try_from(&[1u8][..]).expect("valid polynomial");
106        let a = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial");
107        let b = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial");
108
109        dest.mul_xor_assign(&a, &b).expect("product degree fits");
110
111        assert_eq!(dest.coefficients(), &[0, 0, 1]);
112    }
113
114    #[test]
115    fn mul_xor_assign_with_zero_a() {
116        let mut dest = Polynomial::try_from(&[5u8, 3][..]).expect("valid polynomial");
117        let a = Polynomial::default(); // zero polynomial
118        let b = Polynomial::try_from(&[1u8, 2, 3][..]).expect("valid polynomial");
119
120        dest.mul_xor_assign(&a, &b).expect("product degree fits");
121
122        // dest unchanged since a is zero
123        assert_eq!(dest.coefficients(), &[5, 3]);
124    }
125
126    #[test]
127    fn mul_xor_assign_with_zero_b() {
128        let mut dest = Polynomial::try_from(&[5u8, 3][..]).expect("valid polynomial");
129        let a = Polynomial::try_from(&[1u8, 2, 3][..]).expect("valid polynomial");
130        let b = Polynomial::default(); // zero polynomial
131
132        dest.mul_xor_assign(&a, &b).expect("product degree fits");
133
134        // dest unchanged since b is zero
135        assert_eq!(dest.coefficients(), &[5, 3]);
136    }
137
138    #[test]
139    fn mul_xor_assign_accumulates() {
140        // Start with non-zero dest and verify XOR accumulation
141        let mut dest = Polynomial::try_from(&[1u8, 0, 1][..]).expect("valid polynomial");
142        let a = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial");
143        let b = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial");
144
145        // a * b = [1, 0, 1]
146        // dest ^= [1, 0, 1] => [0, 0, 0] = 0
147        dest.mul_xor_assign(&a, &b).expect("product degree fits");
148
149        assert_eq!(dest.coefficients(), &[0]);
150    }
151
152    #[test]
153    fn mul_xor_assign_extends_degree() {
154        // dest has degree 0, product has higher degree
155        let mut dest = Polynomial::try_from(&[5u8][..]).expect("valid polynomial");
156        let a = Polynomial::try_from(&[1u8, 0, 1][..]).expect("valid polynomial"); // x^2 + 1
157        let b = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial"); // x + 1
158
159        // a * b = x^3 + x^2 + x + 1 = [1, 1, 1, 1]
160        // dest ^= [1, 1, 1, 1] => [5^1, 1, 1, 1] = [4, 1, 1, 1]
161        dest.mul_xor_assign(&a, &b).expect("product degree fits");
162
163        assert_eq!(dest.coefficients(), &[4, 1, 1, 1]);
164    }
165}