Skip to main content

hekate_math/towers/
bit.rs

1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate-math project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>. All rights reserved.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! GF(2) at one byte per element and per packed lane, its flat
19//! basis equal to the tower basis, and the aarch64 NEON kernels.
20
21use crate::{
22    CanonicalDeserialize, CanonicalSerialize, Flat, HardwareField, PackableField, PackedFlat,
23    TowerField,
24};
25use core::ops::{Add, AddAssign, BitAnd, BitXor, Mul, MulAssign, Sub, SubAssign};
26use serde::{Deserialize, Serialize};
27use zeroize::Zeroize;
28
29// ==================================
30// BIT (GF(2))
31// ==================================
32
33/// GF(2) element; the inner byte is invariantly 0 or 1.
34#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Zeroize)]
35#[repr(transparent)]
36pub struct Bit(u8);
37
38impl Bit {
39    /// Builds the element from the low bit of `val`.
40    #[inline]
41    pub const fn new(val: u8) -> Self {
42        Self(val & 1)
43    }
44
45    /// Returns the element if `val` is 0 or 1, else `None`.
46    #[inline]
47    pub const fn try_new(val: u8) -> Option<Self> {
48        match val {
49            0 | 1 => Some(Self(val)),
50            _ => None,
51        }
52    }
53
54    /// Returns the value, always 0 or 1.
55    #[inline]
56    pub const fn get(self) -> u8 {
57        self.0
58    }
59}
60
61impl TowerField for Bit {
62    const BITS: usize = 1;
63    const ZERO: Self = Bit(0);
64    const ONE: Self = Bit(1);
65
66    // x^2 + x + 1 = 0 -> Irreducible over GF(2)
67    const EXTENSION_TAU: Self = Bit(1);
68
69    fn invert(&self) -> Self {
70        // In GF(2), the inverse of 1 is 1.
71        // By cryptographic convention, the
72        // inverse of 0 is defined as 0.
73        // Thus, inversion in GF(2) is
74        // just the identity function.
75        *self
76    }
77
78    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
79        // Take LSB of first byte
80        Self(bytes[0] & 1)
81    }
82}
83
84/// Add (XOR)
85/// 0+0=0, 0+1=1, 1+0=1, 1+1=0
86impl Add for Bit {
87    type Output = Self;
88
89    fn add(self, rhs: Self) -> Self::Output {
90        Self(self.0.bitxor(rhs.0))
91    }
92}
93
94/// Sub is the same as add
95impl Sub for Bit {
96    type Output = Self;
97
98    fn sub(self, rhs: Self) -> Self::Output {
99        self.add(rhs)
100    }
101}
102
103/// Mul (AND)
104/// 0*0=0, 0*1=0, 1*1=1
105impl Mul for Bit {
106    type Output = Self;
107
108    fn mul(self, rhs: Self) -> Self::Output {
109        Self(self.0.bitand(rhs.0))
110    }
111}
112
113impl AddAssign for Bit {
114    fn add_assign(&mut self, rhs: Self) {
115        *self = *self + rhs
116    }
117}
118
119impl SubAssign for Bit {
120    fn sub_assign(&mut self, rhs: Self) {
121        *self = *self - rhs
122    }
123}
124
125impl MulAssign for Bit {
126    fn mul_assign(&mut self, rhs: Self) {
127        *self = *self * rhs;
128    }
129}
130
131impl CanonicalSerialize for Bit {
132    #[inline]
133    fn serialized_size(&self) -> usize {
134        1
135    }
136
137    #[inline]
138    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
139        if writer.is_empty() {
140            return Err(());
141        }
142
143        writer[0] = self.0;
144
145        Ok(())
146    }
147}
148
149impl CanonicalDeserialize for Bit {
150    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
151        if bytes.is_empty() {
152            return Err(());
153        }
154
155        if bytes[0] > 1 {
156            return Err(());
157        }
158
159        Ok(Self(bytes[0]))
160    }
161}
162
163impl Serialize for Bit {
164    #[inline]
165    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166    where
167        S: serde::Serializer,
168    {
169        serializer.serialize_u8(self.0)
170    }
171}
172
173impl<'de> Deserialize<'de> for Bit {
174    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175    where
176        D: serde::Deserializer<'de>,
177    {
178        let raw = u8::deserialize(deserializer)?;
179        Self::try_new(raw).ok_or_else(|| serde::de::Error::custom("Bit must be 0 or 1"))
180    }
181}
182
183impl From<u8> for Bit {
184    #[inline]
185    fn from(val: u8) -> Self {
186        Self(val & 1)
187    }
188}
189
190impl From<u32> for Bit {
191    #[inline]
192    fn from(val: u32) -> Self {
193        Self((val & 1) as u8)
194    }
195}
196
197impl From<u64> for Bit {
198    #[inline]
199    fn from(val: u64) -> Self {
200        Self((val & 1) as u8)
201    }
202}
203
204impl From<u128> for Bit {
205    #[inline]
206    fn from(val: u128) -> Self {
207        Self((val & 1) as u8)
208    }
209}
210
211// ===================================
212// PACKED BIT (Width = 64)
213// ===================================
214
215/// Lanes in [`PackedBit`].
216// 64 bytes = 512 bits = 4 SIMD registers (128-bit each)
217pub const PACKED_WIDTH_BIT: usize = 64;
218
219/// 64 [`Bit`] lanes, one byte each; operators act lane by lane.
220#[repr(C, align(64))]
221pub struct PackedBit(pub [Bit; PACKED_WIDTH_BIT]);
222
223impl Clone for PackedBit {
224    #[inline(always)]
225    fn clone(&self) -> Self {
226        *self
227    }
228}
229
230impl Copy for PackedBit {}
231
232impl Default for PackedBit {
233    #[inline(always)]
234    fn default() -> Self {
235        Self::zero()
236    }
237}
238
239impl PartialEq for PackedBit {
240    fn eq(&self, other: &Self) -> bool {
241        // Bit(u8) is transparent, direct slice
242        // comparison works and is fast.
243        self.0[..] == other.0[..]
244    }
245}
246
247impl Eq for PackedBit {}
248
249impl core::fmt::Debug for PackedBit {
250    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251        write!(f, "PackedBit([size={}])", PACKED_WIDTH_BIT)
252    }
253}
254
255impl PackedBit {
256    /// Returns the packed value with every lane zero.
257    #[inline(always)]
258    pub fn zero() -> Self {
259        Self([Bit::ZERO; PACKED_WIDTH_BIT])
260    }
261}
262
263impl PackableField for Bit {
264    type Packed = PackedBit;
265
266    const WIDTH: usize = PACKED_WIDTH_BIT;
267
268    #[inline(always)]
269    fn pack(chunk: &[Self]) -> Self::Packed {
270        assert!(
271            chunk.len() >= PACKED_WIDTH_BIT,
272            "PackableField::pack: input slice too short",
273        );
274
275        let mut arr = [Self::ZERO; PACKED_WIDTH_BIT];
276        arr.copy_from_slice(&chunk[..PACKED_WIDTH_BIT]);
277
278        PackedBit(arr)
279    }
280
281    #[inline(always)]
282    fn unpack(packed: Self::Packed, output: &mut [Self]) {
283        assert!(
284            output.len() >= PACKED_WIDTH_BIT,
285            "PackableField::unpack: output slice too short",
286        );
287
288        output[..PACKED_WIDTH_BIT].copy_from_slice(&packed.0);
289    }
290}
291
292impl Add for PackedBit {
293    type Output = Self;
294
295    #[inline(always)]
296    fn add(self, rhs: Self) -> Self {
297        #[cfg(target_arch = "aarch64")]
298        {
299            neon::add_packed_bit(self, rhs)
300        }
301
302        #[cfg(not(target_arch = "aarch64"))]
303        {
304            let mut res = [Bit::ZERO; PACKED_WIDTH_BIT];
305            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
306                *out = *l + *r;
307            }
308
309            Self(res)
310        }
311    }
312}
313
314impl AddAssign for PackedBit {
315    #[inline(always)]
316    fn add_assign(&mut self, rhs: Self) {
317        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
318            *l += *r;
319        }
320    }
321}
322
323impl Sub for PackedBit {
324    type Output = Self;
325
326    #[inline(always)]
327    fn sub(self, rhs: Self) -> Self {
328        self.add(rhs)
329    }
330}
331
332impl SubAssign for PackedBit {
333    #[inline(always)]
334    fn sub_assign(&mut self, rhs: Self) {
335        self.add_assign(rhs)
336    }
337}
338
339impl Mul for PackedBit {
340    type Output = Self;
341
342    #[inline(always)]
343    fn mul(self, rhs: Self) -> Self {
344        #[cfg(target_arch = "aarch64")]
345        {
346            neon::mul_packed_bit(self, rhs)
347        }
348
349        #[cfg(not(target_arch = "aarch64"))]
350        {
351            let mut res = [Bit::ZERO; PACKED_WIDTH_BIT];
352            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
353                *out = *l * *r;
354            }
355
356            Self(res)
357        }
358    }
359}
360
361impl MulAssign for PackedBit {
362    #[inline(always)]
363    fn mul_assign(&mut self, rhs: Self) {
364        *self = *self * rhs;
365    }
366}
367
368impl Mul<Bit> for PackedBit {
369    type Output = Self;
370
371    #[inline(always)]
372    fn mul(self, rhs: Bit) -> Self {
373        let mut res = [Bit::ZERO; PACKED_WIDTH_BIT];
374        for (out, v) in res.iter_mut().zip(self.0.iter()) {
375            *out = *v * rhs;
376        }
377
378        Self(res)
379    }
380}
381
382// ===================================
383// Hardware Field
384// ===================================
385
386impl HardwareField for Bit {
387    #[inline(always)]
388    fn to_hardware(self) -> Flat<Self> {
389        Flat::from_raw(self)
390    }
391
392    #[inline(always)]
393    fn from_hardware(value: Flat<Self>) -> Self {
394        value.into_raw()
395    }
396
397    #[inline(always)]
398    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
399        let lhs = lhs.into_raw();
400        let rhs = rhs.into_raw();
401
402        // Hardware addition for bits is XOR
403        Flat::from_raw(Self(lhs.0 ^ rhs.0))
404    }
405
406    #[inline(always)]
407    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
408        PackedFlat::from_raw(lhs.into_raw() + rhs.into_raw())
409    }
410
411    #[inline(always)]
412    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
413        let lhs = lhs.into_raw();
414        let rhs = rhs.into_raw();
415
416        // Hardware multiplication for bits is AND
417        Flat::from_raw(Self(lhs.0 & rhs.0))
418    }
419
420    #[inline(always)]
421    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
422        PackedFlat::from_raw(lhs.into_raw() * rhs.into_raw())
423    }
424
425    #[inline(always)]
426    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
427        let broadcasted = PackedBit([rhs.into_raw(); PACKED_WIDTH_BIT]);
428        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
429    }
430
431    #[inline(always)]
432    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
433        assert_eq!(bit_idx, 0, "bit index out of bounds for Bit");
434
435        // In GF(2), Tower and Flat
436        // bases are identical.
437        value.into_raw().0
438    }
439}
440
441// ===========================================
442// SIMD INSTRUCTIONS
443// ===========================================
444
445#[cfg(target_arch = "aarch64")]
446mod neon {
447    use super::*;
448    use core::arch::aarch64::*;
449    use core::mem::transmute;
450
451    /// XOR for 64 bits (represented as bytes).
452    /// Uses 4 NEON registers.
453    #[inline(always)]
454    pub fn add_packed_bit(lhs: PackedBit, rhs: PackedBit) -> PackedBit {
455        unsafe {
456            // Cast [Bit; 64] -> [uint8x16_t; 4]
457            let l: [uint8x16_t; 4] = transmute::<[Bit; PACKED_WIDTH_BIT], [uint8x16_t; 4]>(lhs.0);
458            let r: [uint8x16_t; 4] = transmute::<[Bit; PACKED_WIDTH_BIT], [uint8x16_t; 4]>(rhs.0);
459
460            let res = [
461                veorq_u8(l[0], r[0]),
462                veorq_u8(l[1], r[1]),
463                veorq_u8(l[2], r[2]),
464                veorq_u8(l[3], r[3]),
465            ];
466
467            PackedBit(transmute::<[uint8x16_t; 4], [Bit; PACKED_WIDTH_BIT]>(res))
468        }
469    }
470
471    /// AND for 64 bits (represented as bytes).
472    /// Uses 4 NEON registers.
473    #[inline(always)]
474    pub fn mul_packed_bit(lhs: PackedBit, rhs: PackedBit) -> PackedBit {
475        unsafe {
476            let l: [uint8x16_t; 4] = transmute::<[Bit; PACKED_WIDTH_BIT], [uint8x16_t; 4]>(lhs.0);
477            let r: [uint8x16_t; 4] = transmute::<[Bit; PACKED_WIDTH_BIT], [uint8x16_t; 4]>(rhs.0);
478
479            let res = [
480                vandq_u8(l[0], r[0]),
481                vandq_u8(l[1], r[1]),
482                vandq_u8(l[2], r[2]),
483                vandq_u8(l[3], r[3]),
484            ];
485
486            PackedBit(transmute::<[uint8x16_t; 4], [Bit; PACKED_WIDTH_BIT]>(res))
487        }
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494    use rand::{RngExt, rng};
495
496    // ==================================
497    // BASIC
498    // ==================================
499
500    #[test]
501    fn add_truth() {
502        let zero = Bit::ZERO;
503        let one = Bit::ONE;
504
505        assert_eq!(zero + zero, zero);
506        assert_eq!(zero + one, one);
507        assert_eq!(one + zero, one);
508        assert_eq!(one + one, zero);
509    }
510
511    #[test]
512    fn mul_truth() {
513        let zero = Bit::ZERO;
514        let one = Bit::ONE;
515
516        assert_eq!(zero * zero, zero);
517        assert_eq!(zero * one, zero);
518        assert_eq!(one * one, one);
519    }
520
521    #[test]
522    fn invariant_constructors() {
523        assert_eq!(Bit::try_new(0), Some(Bit::ZERO));
524        assert_eq!(Bit::try_new(1), Some(Bit::ONE));
525
526        for v in 2u8..=255 {
527            assert_eq!(Bit::try_new(v), None, "try_new admitted {v}");
528        }
529
530        for v in 0u8..=255 {
531            assert!(Bit::new(v).get() <= 1, "new escaped GF(2) at {v}");
532        }
533    }
534
535    #[test]
536    fn security_zeroize() {
537        // Setup sensitive bit (1)
538        let mut secret_bit = Bit::ONE;
539        assert_eq!(secret_bit.0, 1);
540
541        // Nuke it
542        secret_bit.zeroize();
543
544        // Verify
545        assert_eq!(secret_bit, Bit::ZERO);
546        assert_eq!(secret_bit.0, 0, "Bit memory leak detected");
547    }
548
549    #[test]
550    fn invert_truth() {
551        // In GF(2):
552        // invert(1) = 1
553        // invert(0) = 0 (by convention)
554
555        let one = Bit::ONE;
556        let zero = Bit::ZERO;
557
558        assert_eq!(one.invert(), Bit::ONE, "Inversion of 1 must be 1");
559        assert_eq!(zero.invert(), Bit::ZERO, "Inversion of 0 must be 0");
560    }
561
562    // ==================================
563    // HARDWARE
564    // ==================================
565
566    #[test]
567    fn isomorphism_roundtrip() {
568        let mut rng = rng();
569        for _ in 0..100 {
570            // Generate random bit (0 or 1)
571            let val = Bit::new(rng.random::<u8>());
572
573            // Roundtrip: Tower -> Hardware -> Tower must be identity.
574            // For Bit, this is trivial (identity),
575            // but we verify the trait contract.
576            assert_eq!(
577                val.to_hardware().to_tower(),
578                val,
579                "Bit isomorphism roundtrip failed"
580            );
581        }
582    }
583
584    #[test]
585    fn flat_mul_homomorphism() {
586        let mut rng = rng();
587        for _ in 0..100 {
588            let a = Bit::new(rng.random::<u8>());
589            let b = Bit::new(rng.random::<u8>());
590
591            let expected_flat = (a * b).to_hardware();
592            let actual_flat = a.to_hardware() * b.to_hardware();
593
594            // Check if multiplication in Flat basis matches Tower
595            assert_eq!(
596                actual_flat, expected_flat,
597                "Bit flat multiplication mismatch"
598            );
599        }
600    }
601
602    #[test]
603    fn packed_consistency() {
604        let mut rng = rng();
605        for _ in 0..100 {
606            // PACKED_WIDTH_BIT = 64
607            let mut a_vals = [Bit::ZERO; 64];
608            let mut b_vals = [Bit::ZERO; 64];
609
610            for i in 0..64 {
611                a_vals[i] = Bit::new(rng.random::<u8>());
612                b_vals[i] = Bit::new(rng.random::<u8>());
613            }
614
615            let a_flat_vals = a_vals.map(|x| x.to_hardware());
616            let b_flat_vals = b_vals.map(|x| x.to_hardware());
617            let a_packed = Flat::<Bit>::pack(&a_flat_vals);
618            let b_packed = Flat::<Bit>::pack(&b_flat_vals);
619
620            // 1. Test SIMD Add (XOR)
621            let add_res = Bit::add_hardware_packed(a_packed, b_packed);
622
623            let mut add_out = [Bit::ZERO.to_hardware(); 64];
624            Flat::<Bit>::unpack(add_res, &mut add_out);
625
626            for i in 0..64 {
627                assert_eq!(
628                    add_out[i],
629                    (a_vals[i] + b_vals[i]).to_hardware(),
630                    "Bit packed add mismatch at index {}",
631                    i
632                );
633            }
634
635            // 2. Test SIMD Mul (AND)
636            let mul_res = Bit::mul_hardware_packed(a_packed, b_packed);
637
638            let mut mul_out = [Bit::ZERO.to_hardware(); 64];
639            Flat::<Bit>::unpack(mul_res, &mut mul_out);
640
641            for i in 0..64 {
642                assert_eq!(
643                    mul_out[i],
644                    (a_vals[i] * b_vals[i]).to_hardware(),
645                    "Bit packed mul mismatch at index {}",
646                    i
647                );
648            }
649        }
650    }
651
652    // ==================================
653    // PACKED
654    // ==================================
655
656    #[test]
657    fn pack_unpack_roundtrip() {
658        let mut rng = rng();
659        // Width is 64
660        let mut data = [Bit::ZERO; PACKED_WIDTH_BIT];
661
662        for v in data.iter_mut() {
663            *v = Bit::new(rng.random());
664        }
665
666        let packed = Bit::pack(&data);
667        let mut unpacked = [Bit::ZERO; PACKED_WIDTH_BIT];
668        Bit::unpack(packed, &mut unpacked);
669
670        assert_eq!(data, unpacked, "Bit pack/unpack roundtrip failed");
671    }
672
673    #[test]
674    fn packed_add_consistency() {
675        let mut rng = rng();
676        let mut a_vals = [Bit::ZERO; PACKED_WIDTH_BIT];
677        let mut b_vals = [Bit::ZERO; PACKED_WIDTH_BIT];
678
679        for i in 0..PACKED_WIDTH_BIT {
680            a_vals[i] = Bit::new(rng.random());
681            b_vals[i] = Bit::new(rng.random());
682        }
683
684        let a_packed = Bit::pack(&a_vals);
685        let b_packed = Bit::pack(&b_vals);
686
687        // Uses the SIMD add impl (which uses aarch64::add_packed_bit)
688        let res_packed = a_packed + b_packed;
689
690        let mut res_unpacked = [Bit::ZERO; PACKED_WIDTH_BIT];
691        Bit::unpack(res_packed, &mut res_unpacked);
692
693        for i in 0..PACKED_WIDTH_BIT {
694            assert_eq!(
695                res_unpacked[i],
696                a_vals[i] + b_vals[i], // Regular Bit add (XOR)
697                "Bit packed add mismatch"
698            );
699        }
700    }
701
702    #[test]
703    fn packed_mul_consistency() {
704        let mut rng = rng();
705
706        for _ in 0..100 {
707            let mut a_arr = [Bit::ZERO; PACKED_WIDTH_BIT];
708            let mut b_arr = [Bit::ZERO; PACKED_WIDTH_BIT];
709
710            for i in 0..PACKED_WIDTH_BIT {
711                a_arr[i] = Bit::new(rng.random());
712                b_arr[i] = Bit::new(rng.random());
713            }
714
715            let a_packed = PackedBit(a_arr); // Using constructor directly or pack
716            let b_packed = PackedBit(b_arr);
717
718            // Uses the SIMD mul impl (which uses aarch64::mul_packed_bit)
719            let c_packed = a_packed * b_packed;
720
721            let mut c_expected = [Bit::ZERO; PACKED_WIDTH_BIT];
722            for i in 0..PACKED_WIDTH_BIT {
723                c_expected[i] = a_arr[i] * b_arr[i]; // Regular Bit mul (AND)
724            }
725
726            assert_eq!(c_packed.0, c_expected, "Bit packed mul mismatch");
727        }
728    }
729}