Skip to main content

p3_binary_field/
subfield.rs

1//! `GF(4)` as a small subfield of the tower's `GF(2^128)`.
2//!
3//! Each level above `GF(4)` is two coefficients over the level below, in its low and high halves.
4//! Unrolled down to `GF(4)`, the 2-bit chunks of a `GF(2^128)` element are its coordinates over
5//! `GF(4)`. The chunk at bits `2i` and `2i + 1` is `b0 + b1·X_0`, with `b0` in the lower bit.
6//!
7//! `GF(4)` itself is the chunk at bit 0 with every other chunk zero.
8//! A `GF(4)` scalar lies in every level, so it multiplies both halves at each split separately.
9//! Hence it multiplies each chunk on its own, with nothing carried between chunks.
10
11use core::ops::{Add, Mul, Sub};
12
13use p3_field::op_assign_macros::{impl_add_base_field, impl_sub_base_field};
14use p3_field::{Algebra, HasSubfield};
15
16use crate::tower::TowerLevel;
17use crate::{BinaryField2, BinaryField128};
18
19/// The coefficient of `1` in every 2-bit chunk.
20const LOW_BITS: u128 = 0x5555_5555_5555_5555_5555_5555_5555_5555;
21
22/// The coefficient of `X_0` in every 2-bit chunk.
23const HIGH_BITS: u128 = !LOW_BITS;
24
25/// Multiply every 2-bit chunk by the generator `X_0` of `GF(4)`.
26///
27/// `X_0² = X_0 + 1`, so `X_0·(b0 + b1·X_0) = b1 + (b0 + b1)·X_0`.
28#[inline]
29const fn mul_x0(bits: u128) -> u128 {
30    let b1 = bits & HIGH_BITS;
31    (b1 >> 1) ^ ((bits & LOW_BITS) << 1) ^ b1
32}
33
34impl From<BinaryField2> for BinaryField128 {
35    /// `GF(4)` occupies the low two bits; every other coefficient vanishes.
36    #[inline]
37    fn from(x: BinaryField2) -> Self {
38        Self::from_repr(u128::from(x.to_repr()))
39    }
40}
41
42impl_add_base_field!(BinaryField128, BinaryField2);
43impl_sub_base_field!(BinaryField128, BinaryField2);
44
45impl Mul<BinaryField2> for BinaryField128 {
46    type Output = Self;
47
48    /// `x·(c0 + c1·X_0) = c0·x + c1·(X_0·x)`.
49    ///
50    /// Each coefficient of the scalar selects its term through an all-ones or all-zeros mask,
51    /// so there is no product and no branch on the scalar.
52    #[inline]
53    fn mul(self, rhs: BinaryField2) -> Self {
54        let bits = self.to_repr();
55        let c = rhs.to_repr();
56        let c0 = u128::from(c & 1).wrapping_neg();
57        let c1 = u128::from((c >> 1) & 1).wrapping_neg();
58        Self::from_repr((c0 & bits) ^ (c1 & mul_x0(bits)))
59    }
60}
61
62impl Mul<BinaryField128> for BinaryField2 {
63    type Output = BinaryField128;
64
65    #[inline]
66    fn mul(self, rhs: BinaryField128) -> BinaryField128 {
67        rhs * self
68    }
69}
70
71impl Algebra<BinaryField2> for BinaryField128 {}
72
73impl HasSubfield<BinaryField2> for BinaryField128 {
74    /// An element lies in `GF(4)` exactly when no bit above the lowest two is set.
75    #[inline]
76    fn as_subfield(&self) -> Option<BinaryField2> {
77        let bits = self.to_repr();
78        (bits & !3 == 0).then(|| BinaryField2::from_repr(bits as u8))
79    }
80
81    /// The bits above the lowest two, OR-ed across the slice, vanish exactly when every element
82    /// lies in `GF(4)`.
83    ///
84    /// The fold has no early exit, so the loop has no data-dependent branch.
85    #[inline]
86    fn all_in_subfield(values: &[Self]) -> bool {
87        values.iter().fold(0, |acc, v| acc | v.to_repr()) & !3 == 0
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use alloc::vec::Vec;
94
95    use p3_field::{HasSubfield, PrimeCharacteristicRing};
96    use p3_field_testing::test_has_subfield;
97    use proptest::prelude::*;
98
99    use crate::tower::TowerLevel;
100    use crate::{BinaryField2, BinaryField8, BinaryField128};
101
102    /// Every element of `GF(4)`, in bit-pattern order.
103    fn gf4() -> impl Iterator<Item = BinaryField2> {
104        (0..4).map(BinaryField2::from_repr)
105    }
106
107    #[test]
108    fn the_embedding_is_a_ring_homomorphism_on_every_pair() {
109        assert_eq!(
110            BinaryField128::from(BinaryField2::ZERO),
111            BinaryField128::ZERO
112        );
113        assert_eq!(BinaryField128::from(BinaryField2::ONE), BinaryField128::ONE);
114
115        for a in gf4() {
116            for b in gf4() {
117                let (x, y) = (BinaryField128::from(a), BinaryField128::from(b));
118                assert_eq!(BinaryField128::from(a + b), x + y, "{a} + {b}");
119                assert_eq!(BinaryField128::from(a * b), x * y, "{a} * {b}");
120
121                // `reference_mul` recurses to the bottom of the tower without dispatching, so
122                // this ties the embedding to the recursive definition, not to a fast path.
123                assert_eq!(BinaryField128::from(a * b), x.reference_mul(y), "{a} * {b}");
124            }
125        }
126    }
127
128    /// `GF(4)` has the same bit patterns inside `GF(2^8)`, so embedding it directly agrees with
129    /// embedding it through the byte level.
130    #[test]
131    fn the_embedding_factors_through_the_byte_level() {
132        let byte = |a: BinaryField2| BinaryField8::from_repr(a.to_repr());
133
134        for a in gf4() {
135            assert_eq!(BinaryField128::from(a), BinaryField128::from(byte(a)));
136
137            for b in gf4() {
138                assert_eq!(byte(a * b), byte(a) * byte(b), "{a} * {b}");
139            }
140        }
141    }
142
143    #[test]
144    fn every_subfield_element_narrows_back_to_itself() {
145        for a in gf4() {
146            assert_eq!(BinaryField128::from(a).as_subfield(), Some(a));
147        }
148    }
149
150    #[test]
151    fn the_has_subfield_contract_holds() {
152        test_has_subfield::<BinaryField128, BinaryField2>();
153    }
154
155    /// A single bit above the lowest two keeps an element out, whichever bit it is.
156    ///
157    /// Among subfield elements, that element alone must spoil the slice, wherever it sits.
158    #[test]
159    fn every_bit_above_the_lowest_two_lies_outside_the_subfield() {
160        let members: Vec<BinaryField128> = gf4().map(BinaryField128::from).collect();
161        for s in 0..4u128 {
162            for k in 2..128 {
163                let x = BinaryField128::from_repr(s | (1u128 << k));
164                assert_eq!(x.as_subfield(), None, "{s} with bit {k} set");
165
166                let mut values = members.clone();
167                values.insert(k % (members.len() + 1), x);
168                assert!(
169                    !BinaryField128::all_in_subfield(&values),
170                    "{s} with bit {k} set among the subfield elements"
171                );
172            }
173        }
174    }
175
176    proptest! {
177        #![proptest_config(ProptestConfig::with_cases(1000))]
178
179        #[test]
180        fn the_masked_product_agrees_with_the_tower_product(bits: u128) {
181            let x = BinaryField128::from_repr(bits);
182            for s in gf4() {
183                let image = BinaryField128::from(s);
184                prop_assert_eq!(x * s, x * image, "scalar {}", s);
185                prop_assert_eq!(s * x, image * x, "scalar {}", s);
186            }
187        }
188
189        /// The shift reaches every magnitude, including the patterns below four.
190        #[test]
191        fn narrowing_accepts_exactly_the_low_two_bits(bits: u128, shift in 0u32..128) {
192            let bits = bits >> shift;
193            let x = BinaryField128::from_repr(bits);
194            match x.as_subfield() {
195                Some(s) => {
196                    prop_assert!(bits < 4);
197                    prop_assert_eq!(BinaryField128::from(s), x);
198                }
199                None => prop_assert!(bits >= 4),
200            }
201        }
202
203        /// Mostly subfield elements, so that whole slices lie in the subfield often.
204        /// The others are shifted to every magnitude, so some sit just above the subfield.
205        #[test]
206        fn all_in_subfield_agrees_with_narrowing_each_element(
207            bits in prop::collection::vec(
208                prop_oneof![
209                    3 => 0u128..4,
210                    1 => (any::<u128>(), 0u32..128).prop_map(|(bits, shift)| bits >> shift),
211                ],
212                0..12,
213            ),
214        ) {
215            let values: Vec<BinaryField128> =
216                bits.into_iter().map(BinaryField128::from_repr).collect();
217            prop_assert_eq!(
218                BinaryField128::all_in_subfield(&values),
219                values.iter().all(|v| v.as_subfield().is_some())
220            );
221        }
222    }
223}