Skip to main content

ps_ecc/polynomial/methods/
div_rem.rs

1use crate::{
2    error::PolynomialDivError,
3    finite_field::{inv, mul, sub},
4    Polynomial,
5};
6
7impl Polynomial {
8    /// Divides this polynomial by a divisor, returning `(quotient, remainder)`.
9    ///
10    /// The divisor is interpreted as coefficients in ascending degree order.
11    /// Trailing zeros are ignored when determining the divisor's degree.
12    ///
13    /// # Errors
14    ///
15    /// Returns `ZeroDivisor` if the divisor is the zero polynomial.
16    pub fn div_rem(&self, divisor: impl AsRef<[u8]>) -> Result<(Self, Self), PolynomialDivError> {
17        let divisor = divisor.as_ref();
18
19        let Some(divisor_degree) = divisor.iter().rposition(|&c| c != 0) else {
20            return Err(PolynomialDivError::ZeroDivisor);
21        };
22
23        let divisor_lc_inv = inv(divisor[divisor_degree])?.get();
24
25        let Some(dividend_degree) = self.coefficients().iter().rposition(|&c| c != 0) else {
26            return Ok((Self::default(), Self::default()));
27        };
28
29        if dividend_degree < divisor_degree {
30            return Ok((Self::default(), *self));
31        }
32
33        let quot_degree = dividend_degree - divisor_degree;
34
35        #[allow(clippy::cast_possible_truncation)]
36        let mut quot = Self {
37            degree: quot_degree as u8,
38            ..Self::default()
39        };
40
41        let mut rem = *self;
42        let mut deg = dividend_degree;
43
44        while deg >= divisor_degree {
45            let lead_coef = rem.coefficients[deg];
46
47            if lead_coef == 0 {
48                if deg == 0 {
49                    break;
50                }
51                deg -= 1;
52                continue;
53            }
54
55            let ratio = mul(lead_coef, divisor_lc_inv);
56            let shift = deg - divisor_degree;
57
58            quot.coefficients[shift] = ratio;
59            rem.coefficients[deg] = 0;
60
61            for (i, &divisor_coef) in divisor[..divisor_degree].iter().enumerate() {
62                rem.coefficients[shift + i] =
63                    sub(rem.coefficients[shift + i], mul(ratio, divisor_coef));
64            }
65
66            if deg == 0 {
67                break;
68            }
69            deg -= 1;
70        }
71
72        quot.trim_degree();
73        rem.trim_degree();
74
75        Ok((quot, rem))
76    }
77}
78
79#[cfg(test)]
80#[allow(clippy::expect_used)]
81mod tests {
82    use std::ops::Mul;
83
84    use crate::{error::PolynomialDivError, Polynomial};
85
86    #[test]
87    fn div_rem_basic() {
88        // (x^2 + 1) / (x + 1)
89        // In GF(256), x + 1 divides x^2 + 1 with quotient x + 1 and remainder 0
90        // because (x + 1)^2 = x^2 + 2x + 1 = x^2 + 1 (since 2 = 0 in GF(2))
91        let a = Polynomial::try_from(&[1u8, 0, 1][..]).expect("valid polynomial");
92
93        let (quot, rem) = a.div_rem([1, 1]).expect("division succeeds");
94
95        assert_eq!(quot.coefficients(), &[1, 1]);
96        assert_eq!(rem.coefficients(), &[0]);
97    }
98
99    #[test]
100    fn div_rem_by_one() {
101        let a = Polynomial::try_from(&[5u8, 10, 15][..]).expect("valid polynomial");
102
103        let (quot, rem) = a.div_rem([1]).expect("division succeeds");
104
105        assert_eq!(quot.coefficients(), &[5, 10, 15]);
106        assert_eq!(rem.coefficients(), &[0]);
107    }
108
109    #[test]
110    fn div_rem_by_zero() {
111        let a = Polynomial::try_from(&[1u8, 2, 3][..]).expect("valid polynomial");
112
113        let result = a.div_rem([0]);
114
115        assert_eq!(result, Err(PolynomialDivError::ZeroDivisor));
116    }
117
118    #[test]
119    fn div_rem_by_empty_slice() {
120        let a = Polynomial::try_from(&[1u8, 2, 3][..]).expect("valid polynomial");
121
122        let result = a.div_rem([]);
123
124        assert_eq!(result, Err(PolynomialDivError::ZeroDivisor));
125    }
126
127    #[test]
128    fn div_rem_zero_by_nonzero() {
129        let zero = Polynomial::default();
130
131        let (quot, rem) = zero.div_rem([1, 2]).expect("division succeeds");
132
133        assert_eq!(quot.degree(), 0);
134        assert_eq!(quot.coefficients(), &[0]);
135        assert_eq!(rem.coefficients(), &[0]);
136    }
137
138    #[test]
139    fn div_rem_smaller_by_larger() {
140        // Dividend degree < divisor degree => quotient is 0, remainder is dividend
141        let a = Polynomial::try_from(&[1u8, 2][..]).expect("valid polynomial");
142
143        let (quot, rem) = a.div_rem([1, 2, 3]).expect("division succeeds");
144
145        assert_eq!(quot.degree(), 0);
146        assert_eq!(quot.coefficients(), &[0]);
147        assert_eq!(rem.coefficients(), &[1, 2]);
148    }
149
150    #[test]
151    fn div_rem_mul_roundtrip() {
152        // (a * b) / b = (a, 0) when b is not zero
153        let a = Polynomial::try_from(&[3u8, 5, 7][..]).expect("valid polynomial");
154        let b: &[u8] = &[2, 4];
155
156        let product = (&a).mul(b).expect("multiplication succeeds");
157        let (quot, rem) = product.div_rem(b).expect("division succeeds");
158
159        assert_eq!(quot, a);
160        assert_eq!(rem.coefficients(), &[0]);
161    }
162
163    #[test]
164    fn div_rem_with_remainder() {
165        // (x^2 + x + 1) / (x + 1) should have a remainder
166        let a = Polynomial::try_from(&[1u8, 1, 1][..]).expect("valid polynomial");
167        let b: &[u8] = &[1, 1];
168
169        let (quot, rem) = a.div_rem(b).expect("division succeeds");
170
171        // Verify: quot * b + rem = a
172        let product = quot.mul(b).expect("multiplication succeeds");
173        let reconstructed = (product + rem).expect("addition succeeds");
174
175        assert_eq!(reconstructed, a);
176    }
177
178    #[test]
179    fn div_rem_by_constant() {
180        // Dividing by a constant should scale all coefficients, remainder 0
181        let a = Polynomial::try_from(&[6u8, 12, 18][..]).expect("valid polynomial");
182
183        let (quot, rem) = a.div_rem([2]).expect("division succeeds");
184
185        assert_eq!(rem.coefficients(), &[0]);
186
187        // In GF(256), division by 2 is multiplication by inverse of 2
188        // We verify by checking that quot * 2 = a
189        let check = quot.mul([2u8]).expect("multiplication succeeds");
190
191        assert_eq!(check.coefficients(), &[6, 12, 18]);
192    }
193
194    #[test]
195    fn div_rem_with_polynomial() {
196        // Verify we can pass &Polynomial directly
197        let a = Polynomial::try_from(&[1u8, 0, 1][..]).expect("valid polynomial");
198        let b = Polynomial::try_from(&[1u8, 1][..]).expect("valid polynomial");
199
200        let (quot, rem) = a.div_rem(b).expect("division succeeds");
201
202        assert_eq!(quot.coefficients(), &[1, 1]);
203        assert_eq!(rem.coefficients(), &[0]);
204    }
205
206    #[test]
207    fn div_rem_trailing_zeros_ignored() {
208        // Trailing zeros in divisor should be ignored
209        let a = Polynomial::try_from(&[1u8, 0, 1][..]).expect("valid polynomial");
210
211        let (quot1, rem1) = a.div_rem([1, 1]).expect("division succeeds");
212        let (quot2, rem2) = a.div_rem([1, 1, 0, 0, 0]).expect("division succeeds");
213
214        assert_eq!(quot1, quot2);
215        assert_eq!(rem1, rem2);
216    }
217
218    #[test]
219    fn div_rem_same_degree() {
220        // Dividend and divisor have the same degree
221        let a = Polynomial::try_from(&[3u8, 5, 7][..]).expect("valid polynomial");
222        let b = Polynomial::try_from(&[2u8, 4, 6][..]).expect("valid polynomial");
223
224        let (quot, rem) = a.div_rem(b).expect("division succeeds");
225
226        // Quotient should be a constant (degree 0)
227        assert_eq!(quot.degree(), 0);
228
229        // Verify: quot * b + rem = a
230        let product = quot.mul(&b).expect("multiplication succeeds");
231        let reconstructed = (product + rem).expect("addition succeeds");
232
233        assert_eq!(reconstructed, a);
234    }
235
236    #[test]
237    fn div_rem_self_division() {
238        // a / a = (1, 0)
239        let a = Polynomial::try_from(&[3u8, 5, 7, 11][..]).expect("valid polynomial");
240
241        let (quot, rem) = a.div_rem(a).expect("division succeeds");
242
243        assert_eq!(quot.coefficients(), &[1]);
244        assert_eq!(rem.coefficients(), &[0]);
245    }
246
247    #[test]
248    fn div_rem_divisor_with_internal_zeros() {
249        // Divisor like x^3 + 1 (coefficients [1, 0, 0, 1])
250        let a = Polynomial::try_from(&[1u8, 2, 3, 4, 5][..]).expect("valid polynomial");
251        let b: &[u8] = &[1, 0, 0, 1];
252
253        let (quot, rem) = a.div_rem(b).expect("division succeeds");
254
255        // Verify: quot * b + rem = a
256        let product = quot.mul(b).expect("multiplication succeeds");
257        let reconstructed = (product + rem).expect("addition succeeds");
258
259        assert_eq!(reconstructed, a);
260    }
261
262    #[test]
263    fn div_rem_high_degree() {
264        // Test with higher degree polynomials
265        let mut a_coeffs = [0u8; 100];
266        let mut b_coeffs = [0u8; 30];
267
268        for (i, c) in a_coeffs.iter_mut().enumerate() {
269            *c = u8::try_from(i + 1).expect("index fits in u8");
270        }
271        for (i, c) in b_coeffs.iter_mut().enumerate() {
272            *c = u8::try_from(i + 5).expect("index fits in u8");
273        }
274
275        let a = Polynomial::try_from(&a_coeffs[..]).expect("valid polynomial");
276        let b = Polynomial::try_from(&b_coeffs[..]).expect("valid polynomial");
277
278        let (quot, rem) = a.div_rem(b).expect("division succeeds");
279
280        // Verify: quot * b + rem = a
281        let product = quot.mul(&b).expect("multiplication succeeds");
282        let reconstructed = (product + rem).expect("addition succeeds");
283
284        assert_eq!(reconstructed, a);
285    }
286}