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