Skip to main content

hekate_math/towers/
block8.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>.
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^8), the AES field modulo x^8 + x^4 + x^3 + x + 1
19//! in both bases, and the aarch64 NEON kernels.
20
21use crate::constants::FLAT_TO_TOWER_BIT_MASKS_8;
22use crate::towers::bit::Bit;
23use crate::{
24    CanonicalDeserialize, CanonicalSerialize, Flat, HardwareField, PackableField, PackedFlat,
25    TowerField, constants,
26};
27use core::ops::{Add, AddAssign, BitXor, Mul, MulAssign, Sub, SubAssign};
28use serde::{Deserialize, Serialize};
29use zeroize::Zeroize;
30
31#[cfg(not(feature = "table-math"))]
32#[repr(align(64))]
33struct CtConvertBasisU8<const N: usize>([u8; N]);
34
35#[cfg(not(feature = "table-math"))]
36static TOWER_TO_FLAT_BASIS_8: CtConvertBasisU8<8> =
37    CtConvertBasisU8(constants::RAW_TOWER_TO_FLAT_8);
38
39#[cfg(not(feature = "table-math"))]
40static FLAT_TO_TOWER_BASIS_8: CtConvertBasisU8<8> =
41    CtConvertBasisU8(constants::RAW_FLAT_TO_TOWER_8);
42
43// ============================================================
44// Precomputed Lookup Tables for GF(2^8) arithmetic.
45// Polynomial: x^8 + x^4 + x^3 + x + 1 (0x11B) [AES Standard]
46// Generator: 3 (x + 1)
47// ============================================================
48
49/// Exponentiation Table: g^i
50/// Maps index i -> value inside the field.
51/// Range: [0..255].
52/// `EXP_TABLE[0]` == 1 and `EXP_TABLE[255]` == 1.
53#[cfg(feature = "table-math")]
54const EXP_TABLE: [u8; 256] = generate_exp_table();
55
56/// Logarithm Table: log_g(x)
57/// Maps value x -> power i such that g^i = x.
58/// Range: LOG_TABLE[1..=255] contain values 0..254.
59/// `LOG_TABLE[0]` is 0 (undefined).
60#[cfg(feature = "table-math")]
61const LOG_TABLE: [u8; 256] = generate_log_table();
62
63#[cfg(feature = "table-math")]
64const TAU_TABLE: [u8; 256] = generate_tau_table();
65
66/// GF(2^8) in the tower basis: bit i is the coefficient of x^i
67/// modulo x^8 + x^4 + x^3 + x + 1, as in AES (FIPS-197 §4.2).
68/// The flat basis has the same modulus, and φ(a) = a^32.
69#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
70#[repr(transparent)]
71pub struct Block8(pub u8);
72
73impl Block8 {
74    /// Builds the element from its tower-basis byte `val`.
75    pub const fn new(val: u8) -> Self {
76        Self(val)
77    }
78
79    /// Returns `self^2`; squaring is additive in characteristic 2.
80    #[inline(always)]
81    pub fn square(self) -> Self {
82        #[cfg(feature = "table-math")]
83        {
84            if self.0 == 0 {
85                return Self::ZERO;
86            }
87
88            let k = (LOG_TABLE[self.0 as usize] as usize) << 1;
89            let idx = if k >= 255 { k - 255 } else { k };
90
91            Self(EXP_TABLE[idx])
92        }
93
94        #[cfg(not(feature = "table-math"))]
95        {
96            // Carryless square (bit spread), then fold the
97            // high half twice by 0x1b (= x^4 + x^3 + x + 1).
98            let mut s = self.0 as u16;
99            s = (s | (s << 4)) & 0x0f0f;
100            s = (s | (s << 2)) & 0x3333;
101            s = (s | (s << 1)) & 0x5555;
102
103            let hi = s >> 8;
104            let s = (s & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4));
105
106            let hi = s >> 8;
107
108            Block8(((s & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4))) as u8)
109        }
110    }
111
112    #[inline(always)]
113    pub(crate) fn mul_tau(self) -> Self {
114        #[cfg(feature = "table-math")]
115        {
116            Block8(TAU_TABLE[self.0 as usize])
117        }
118
119        #[cfg(not(feature = "table-math"))]
120        {
121            Block8(mul_tau_fold(self.0))
122        }
123    }
124}
125
126impl TowerField for Block8 {
127    const BITS: usize = 8;
128    const ZERO: Self = Block8(0);
129    const ONE: Self = Block8(1);
130
131    const EXTENSION_TAU: Self = Block8(0x20);
132
133    fn invert(&self) -> Self {
134        #[cfg(feature = "table-math")]
135        {
136            if self.0 == 0 {
137                return Self::ZERO;
138            }
139
140            let i = LOG_TABLE[self.0 as usize] as usize;
141            Block8(EXP_TABLE[255 - i])
142        }
143
144        #[cfg(not(feature = "table-math"))]
145        {
146            // Fermat's Little Theorem:
147            // a^-1 = a^254 in GF(2^8)
148            // Constant-time, no branching.
149            let x = *self;
150            let x2 = x * x;
151            let x4 = x2 * x2;
152            let x8 = x4 * x4;
153            let x16 = x8 * x8;
154            let x32 = x16 * x16;
155            let x64 = x32 * x32;
156            let x128 = x64 * x64;
157
158            // 254 = 128 + 64 + 32 + 16 + 8 + 4 + 2
159            x128 * x64 * x32 * x16 * x8 * x4 * x2
160        }
161    }
162
163    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
164        Self(bytes[0])
165    }
166}
167
168/// Add (XOR)
169impl Add for Block8 {
170    type Output = Self;
171
172    fn add(self, rhs: Self) -> Self::Output {
173        Self(self.0.bitxor(rhs.0))
174    }
175}
176
177impl Sub for Block8 {
178    type Output = Self;
179
180    fn sub(self, rhs: Self) -> Self::Output {
181        self.add(rhs)
182    }
183}
184
185/// Mul (Galois Field Multiplication)
186impl Mul for Block8 {
187    type Output = Self;
188
189    #[inline]
190    fn mul(self, rhs: Self) -> Self::Output {
191        #[cfg(feature = "table-math")]
192        {
193            // Handle zero explicitly (log(0) is undefined)
194            if self.0 == 0 || rhs.0 == 0 {
195                return Self::ZERO;
196            }
197
198            // Lookup Logarithms
199            // Math:
200            // a * b = g^(log(a) + log(b))
201            let i = LOG_TABLE[self.0 as usize] as usize;
202            let j = LOG_TABLE[rhs.0 as usize] as usize;
203
204            // Add exponents modulo 255
205            // Since max(i) = 254, max(i+j) = 508.
206            // Check if sum >= 255 and subtract.
207            let k = i + j;
208            let idx = if k >= 255 { k - 255 } else { k };
209
210            // Lookup Exponent result
211            Self(EXP_TABLE[idx])
212        }
213
214        #[cfg(not(feature = "table-math"))]
215        {
216            #[cfg(target_arch = "aarch64")]
217            {
218                neon::mul_8(self, rhs)
219            }
220
221            #[cfg(not(target_arch = "aarch64"))]
222            {
223                let mut a = self.0;
224                let mut b = rhs.0;
225                let mut res = 0u8;
226
227                // Constant-time shift-and-add
228                // over GF(2^8) with poly 0x11B.
229                for _ in 0..8 {
230                    let bit = b & 1;
231                    let mask = 0u8.wrapping_sub(bit);
232                    res ^= a & mask;
233
234                    let high_bit = a >> 7;
235                    let overflow_mask = 0u8.wrapping_sub(high_bit);
236                    a = (a << 1) ^ (0x1B & overflow_mask);
237
238                    b >>= 1;
239                }
240
241                Self(res)
242            }
243        }
244    }
245}
246
247impl AddAssign for Block8 {
248    fn add_assign(&mut self, rhs: Self) {
249        *self = *self + rhs;
250    }
251}
252
253impl SubAssign for Block8 {
254    fn sub_assign(&mut self, rhs: Self) {
255        *self = *self - rhs;
256    }
257}
258
259impl MulAssign for Block8 {
260    fn mul_assign(&mut self, rhs: Self) {
261        *self = *self * rhs;
262    }
263}
264
265impl CanonicalSerialize for Block8 {
266    #[inline]
267    fn serialized_size(&self) -> usize {
268        1
269    }
270
271    #[inline]
272    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
273        if writer.is_empty() {
274            return Err(());
275        }
276
277        writer[0] = self.0;
278
279        Ok(())
280    }
281}
282
283impl CanonicalDeserialize for Block8 {
284    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
285        if bytes.is_empty() {
286            return Err(());
287        }
288
289        Ok(Self(bytes[0]))
290    }
291}
292
293impl From<u8> for Block8 {
294    #[inline]
295    fn from(val: u8) -> Self {
296        Self::new(val)
297    }
298}
299
300impl From<u32> for Block8 {
301    #[inline]
302    fn from(val: u32) -> Self {
303        Self(val as u8)
304    }
305}
306
307impl From<u64> for Block8 {
308    #[inline]
309    fn from(val: u64) -> Self {
310        Self(val as u8)
311    }
312}
313
314impl From<u128> for Block8 {
315    #[inline]
316    fn from(val: u128) -> Self {
317        Self(val as u8)
318    }
319}
320
321// ========================================
322// FIELD LIFTING
323// ========================================
324
325impl From<Bit> for Block8 {
326    #[inline(always)]
327    fn from(val: Bit) -> Self {
328        Self(val.get())
329    }
330}
331
332// ===================================
333// PACKED BLOCK 8 (Width = 16)
334// ===================================
335
336/// Lanes in [`PackedBlock8`].
337// 128 bits / 8 = 16 elements
338pub const PACKED_WIDTH_8: usize = 16;
339
340/// 16 [`Block8`] lanes; operators act lane by lane.
341#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
342#[repr(C, align(16))]
343pub struct PackedBlock8(pub [Block8; PACKED_WIDTH_8]);
344
345impl PackedBlock8 {
346    /// Returns the packed value with every lane zero.
347    #[inline(always)]
348    pub fn zero() -> Self {
349        Self([Block8::ZERO; PACKED_WIDTH_8])
350    }
351}
352
353impl PackableField for Block8 {
354    type Packed = PackedBlock8;
355
356    const WIDTH: usize = PACKED_WIDTH_8;
357
358    #[inline(always)]
359    fn pack(chunk: &[Self]) -> Self::Packed {
360        assert!(
361            chunk.len() >= PACKED_WIDTH_8,
362            "PackableField::pack: input slice too short",
363        );
364
365        let mut arr = [Self::ZERO; PACKED_WIDTH_8];
366        arr.copy_from_slice(&chunk[..PACKED_WIDTH_8]);
367
368        PackedBlock8(arr)
369    }
370
371    #[inline(always)]
372    fn unpack(packed: Self::Packed, output: &mut [Self]) {
373        assert!(
374            output.len() >= PACKED_WIDTH_8,
375            "PackableField::unpack: output slice too short",
376        );
377
378        output[..PACKED_WIDTH_8].copy_from_slice(&packed.0);
379    }
380}
381
382impl Add for PackedBlock8 {
383    type Output = Self;
384
385    #[inline(always)]
386    fn add(self, rhs: Self) -> Self {
387        let mut res = [Block8::ZERO; PACKED_WIDTH_8];
388        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
389            *out = *l + *r;
390        }
391
392        Self(res)
393    }
394}
395
396impl AddAssign for PackedBlock8 {
397    #[inline(always)]
398    fn add_assign(&mut self, rhs: Self) {
399        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
400            *l += *r;
401        }
402    }
403}
404
405impl Sub for PackedBlock8 {
406    type Output = Self;
407
408    #[inline(always)]
409    fn sub(self, rhs: Self) -> Self {
410        self.add(rhs)
411    }
412}
413
414impl SubAssign for PackedBlock8 {
415    #[inline(always)]
416    fn sub_assign(&mut self, rhs: Self) {
417        self.add_assign(rhs);
418    }
419}
420
421impl Mul for PackedBlock8 {
422    type Output = Self;
423
424    #[inline(always)]
425    fn mul(self, rhs: Self) -> Self {
426        #[cfg(target_arch = "aarch64")]
427        {
428            let mut res = [Block8::ZERO; PACKED_WIDTH_8];
429            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
430                *out = mul_iso_8(*l, *r);
431            }
432
433            Self(res)
434        }
435
436        #[cfg(not(target_arch = "aarch64"))]
437        {
438            let mut res = [Block8::ZERO; PACKED_WIDTH_8];
439            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
440                *out = *l * *r;
441            }
442
443            Self(res)
444        }
445    }
446}
447
448impl MulAssign for PackedBlock8 {
449    #[inline(always)]
450    fn mul_assign(&mut self, rhs: Self) {
451        *self = *self * rhs;
452    }
453}
454
455impl Mul<Block8> for PackedBlock8 {
456    type Output = Self;
457
458    #[inline(always)]
459    fn mul(self, rhs: Block8) -> Self {
460        let mut res = [Block8::ZERO; PACKED_WIDTH_8];
461        for (out, v) in res.iter_mut().zip(self.0.iter()) {
462            *out = *v * rhs;
463        }
464
465        Self(res)
466    }
467}
468
469// ===================================
470// Hardware Field
471// ===================================
472
473impl HardwareField for Block8 {
474    #[inline(always)]
475    fn to_hardware(self) -> Flat<Self> {
476        #[cfg(feature = "table-math")]
477        {
478            Flat::from_raw(apply_matrix_8(self, &constants::TOWER_TO_FLAT_8))
479        }
480
481        #[cfg(not(feature = "table-math"))]
482        {
483            Flat::from_raw(Block8(map_ct_8(self.0, &TOWER_TO_FLAT_BASIS_8.0)))
484        }
485    }
486
487    #[inline(always)]
488    fn from_hardware(value: Flat<Self>) -> Self {
489        let value = value.into_raw();
490        #[cfg(feature = "table-math")]
491        {
492            apply_matrix_8(value, &constants::FLAT_TO_TOWER_8)
493        }
494
495        #[cfg(not(feature = "table-math"))]
496        {
497            Block8(map_ct_8(value.0, &FLAT_TO_TOWER_BASIS_8.0))
498        }
499    }
500
501    #[inline(always)]
502    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
503        Flat::from_raw(lhs.into_raw() + rhs.into_raw())
504    }
505
506    #[inline(always)]
507    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
508        let lhs = lhs.into_raw();
509        let rhs = rhs.into_raw();
510        #[cfg(target_arch = "aarch64")]
511        {
512            PackedFlat::from_raw(neon::add_packed_8(lhs, rhs))
513        }
514
515        #[cfg(not(target_arch = "aarch64"))]
516        {
517            PackedFlat::from_raw(lhs + rhs)
518        }
519    }
520
521    #[inline(always)]
522    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
523        let lhs = lhs.into_raw();
524        let rhs = rhs.into_raw();
525        #[cfg(target_arch = "aarch64")]
526        {
527            Flat::from_raw(neon::mul_8(lhs, rhs))
528        }
529
530        #[cfg(not(target_arch = "aarch64"))]
531        {
532            let a_tower = Self::from_hardware(Flat::from_raw(lhs));
533            let b_tower = Self::from_hardware(Flat::from_raw(rhs));
534
535            (a_tower * b_tower).to_hardware()
536        }
537    }
538
539    #[inline(always)]
540    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
541        let lhs = lhs.into_raw();
542        let rhs = rhs.into_raw();
543
544        #[cfg(target_arch = "aarch64")]
545        {
546            PackedFlat::from_raw(neon::mul_flat_packed_8(lhs, rhs))
547        }
548
549        #[cfg(not(target_arch = "aarch64"))]
550        {
551            let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
552            let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
553            let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
554
555            Self::unpack(lhs, &mut l);
556            Self::unpack(rhs, &mut r);
557
558            for i in 0..<Self as PackableField>::WIDTH {
559                res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
560            }
561
562            PackedFlat::from_raw(Self::pack(&res))
563        }
564    }
565
566    #[inline(always)]
567    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
568        let broadcasted = PackedBlock8([rhs.into_raw(); PACKED_WIDTH_8]);
569        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
570    }
571
572    #[inline(always)]
573    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
574        let mask = FLAT_TO_TOWER_BIT_MASKS_8[bit_idx];
575
576        // Parity of (x & mask) without popcount
577        let mut v = value.into_raw().0 & mask;
578        v ^= v >> 4;
579        v ^= v >> 2;
580        v ^= v >> 1;
581
582        v & 1
583    }
584}
585
586// ===========================================
587// UTILS
588// ===========================================
589
590#[cfg(target_arch = "aarch64")]
591#[inline(always)]
592fn mul_iso_8(a: Block8, b: Block8) -> Block8 {
593    let a_f = a.to_hardware();
594    let b_f = b.to_hardware();
595    let c_f = Flat::from_raw(neon::mul_8(a_f.into_raw(), b_f.into_raw()));
596
597    c_f.to_tower()
598}
599
600#[cfg(feature = "table-math")]
601#[inline(always)]
602fn apply_matrix_8(val: Block8, table: &[u8; 256]) -> Block8 {
603    let idx = val.0 as usize;
604
605    // SAFETY:
606    // idx = val.0 < 256 = table.len().
607    Block8(unsafe { *table.get_unchecked(idx) })
608}
609
610#[cfg(not(feature = "table-math"))]
611#[inline(always)]
612fn map_ct_8(x: u8, basis: &[u8; 8]) -> u8 {
613    let mut acc = 0u8;
614    let mut i = 0usize;
615
616    while i < 8 {
617        let bit = (x >> i) & 1;
618        let mask = 0u8.wrapping_sub(bit);
619        acc ^= basis[i] & mask;
620        i += 1;
621    }
622
623    acc
624}
625
626const fn mul_tau_fold(a: u8) -> u8 {
627    let p = (a as u16) << 5;
628
629    let hi = p >> 8;
630    let s = (p & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4));
631
632    let hi = s >> 8;
633
634    ((s & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4))) as u8
635}
636
637#[cfg(feature = "table-math")]
638const fn generate_exp_table() -> [u8; 256] {
639    let mut table = [0u8; 256];
640    let mut val: u8 = 1;
641
642    // Iterate i from 0 to 255 (inclusive).
643    // This fills table[0]..table[255].
644    // At i=0, table[0] = 1.
645    // At i=255, val cycles back to 1, so table[255] = 1.
646    // This allows safe access to table[255]
647    // during inversion logic (255 - i).
648    let mut i = 0;
649    while i < 256 {
650        table[i] = val;
651
652        // Multiply val by GENERATOR (3) in GF(2^8)
653        // val * 3 = val * (x + 1) = (val << 1) ^ val
654
655        let high_bit = val & 0x80;
656        let mut shifted = val << 1;
657
658        // AES Polynomial 0x11B.
659        // If high bit was set, XOR with
660        // the lower 8 bits (0x1B).
661        if high_bit != 0 {
662            shifted ^= 0x1B;
663        }
664
665        val = shifted ^ val;
666        i += 1;
667    }
668
669    table
670}
671
672#[cfg(feature = "table-math")]
673const fn generate_log_table() -> [u8; 256] {
674    let mut table = [0u8; 256];
675
676    // For Log table, iterate 0..254.
677    // Valid log values are in range [0, 254].
678    // log(1) is 0. log(g^254) is 254.
679    //
680    // Note:
681    // Don't map index 255 here, as log(1)
682    // is strictly 0 for canonical form.
683
684    let mut val: u8 = 1;
685    let mut i = 0;
686
687    while i < 255 {
688        table[val as usize] = i as u8;
689
690        let high_bit = val & 0x80;
691        let mut shifted = val << 1;
692
693        if high_bit != 0 {
694            shifted ^= 0x1B;
695        }
696
697        val = shifted ^ val;
698
699        i += 1;
700    }
701
702    // table[0] remains 0 (log(0) is undefined).
703
704    table
705}
706
707#[cfg(feature = "table-math")]
708const fn generate_tau_table() -> [u8; 256] {
709    let mut table = [0u8; 256];
710    let mut i = 0;
711
712    while i < 256 {
713        table[i] = mul_tau_fold(i as u8);
714        i += 1;
715    }
716
717    table
718}
719
720#[cfg(feature = "table-math")]
721const fn gf8_mul_const(mut a: u8, mut b: u8) -> u8 {
722    let mut res = 0u8;
723    let mut i = 0;
724
725    while i < 8 {
726        if b & 1 == 1 {
727            res ^= a;
728        }
729
730        let carry = a & 0x80;
731
732        a <<= 1;
733
734        if carry != 0 {
735            a ^= 0x1B;
736        }
737
738        b >>= 1;
739        i += 1;
740    }
741
742    res
743}
744
745#[cfg(feature = "table-math")]
746const _: () = {
747    assert!(EXP_TABLE[0] == 1, "EXP_TABLE[0] is not g^0");
748
749    let mut x: usize = 0;
750    while x < 256 {
751        assert!(
752            TAU_TABLE[x] == gf8_mul_const(x as u8, <Block8 as TowerField>::EXTENSION_TAU.0),
753            "TAU_TABLE is not multiplication by EXTENSION_TAU"
754        );
755
756        if x < 255 {
757            assert!(
758                EXP_TABLE[x + 1] == gf8_mul_const(EXP_TABLE[x], 3),
759                "EXP_TABLE is not the power chain of the generator 3"
760            );
761        }
762
763        if x != 0 {
764            let i = LOG_TABLE[x] as usize;
765
766            assert!(
767                EXP_TABLE[i] as usize == x,
768                "EXP_TABLE does not invert LOG_TABLE"
769            );
770            assert!(
771                gf8_mul_const(x as u8, EXP_TABLE[255 - i]) == 1,
772                "EXP_TABLE[255 - LOG_TABLE[x]] is not the inverse of x"
773            );
774        }
775
776        x += 1;
777    }
778};
779
780// ===========================================
781// 8-BIT SIMD INSTRUCTIONS
782// ===========================================
783
784#[cfg(target_arch = "aarch64")]
785mod neon {
786    use super::*;
787    use core::arch::aarch64::*;
788    use core::mem::transmute;
789
790    const _: () = assert!(
791        constants::POLY_8 == 0x1b,
792        "reduction tables and verus twins hardcode R = 0x1b"
793    );
794
795    #[inline(always)]
796    pub fn add_packed_8(lhs: PackedBlock8, rhs: PackedBlock8) -> PackedBlock8 {
797        unsafe {
798            let res = veorq_u8(
799                transmute::<[Block8; 16], uint8x16_t>(lhs.0),
800                transmute::<[Block8; 16], uint8x16_t>(rhs.0),
801            );
802            transmute(res)
803        }
804    }
805
806    #[inline(always)]
807    pub fn mul_8(a: Block8, b: Block8) -> Block8 {
808        unsafe {
809            // Load 8-bit scalars
810            // into NEON vectors.
811            let a_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(a.0));
812            let b_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(b.0));
813
814            // Multiply:
815            // 8-bit x 8-bit -> 16-bit
816            let prod = vmull_p8(a_poly, b_poly);
817
818            // Extract the 16-bit result
819            let prod_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(prod), 0);
820
821            let l = (prod_u16 & 0xFF) as u8;
822            let h = (prod_u16 >> 8) as u8;
823
824            // P(x) = x^8 + 0x1B
825            let r_val = constants::POLY_8; // u8
826
827            // Fold high bits (h * 0x1B)
828            let h_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(h));
829            let r_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(r_val));
830            let h_red = vmull_p8(h_poly, r_poly);
831
832            let h_red_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(h_red), 0);
833
834            let folded = (h_red_u16 & 0xFF) as u8;
835            let carry = (h_red_u16 >> 8) as u8;
836
837            let mut res = l ^ folded;
838
839            // Unconditional carry reduction:
840            // If carry is 0, c_poly is 0,
841            // c_red is 0, and XOR does nothing.
842            let c_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(carry));
843            let c_red = vmull_p8(c_poly, r_poly);
844            let c_red_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(c_red), 0);
845
846            res ^= (c_red_u16 & 0xFF) as u8;
847
848            Block8(res)
849        }
850    }
851
852    /// Vectorized multiplication for Block8 (16 elements at once).
853    /// Uses vmull_p8 for multiplication and vqtbl1q_u8 for reduction.
854    #[inline(always)]
855    pub fn mul_flat_packed_8(lhs: PackedBlock8, rhs: PackedBlock8) -> PackedBlock8 {
856        unsafe {
857            let a: uint8x16_t = transmute(lhs.0);
858            let b: uint8x16_t = transmute(rhs.0);
859
860            // Split into low/high 64-bit halves
861            let a_lo = vget_low_u8(a);
862            let a_hi = vget_high_u8(a);
863            let b_lo = vget_low_u8(b);
864            let b_hi = vget_high_u8(b);
865
866            // Multiply 8x8 -> 16 bits
867            // (poly16x8_t, which is 128-bit wide)
868            let res_lo = vmull_p8(
869                transmute::<uint8x8_t, poly8x8_t>(a_lo),
870                transmute::<uint8x8_t, poly8x8_t>(b_lo),
871            );
872            let res_hi = vmull_p8(
873                transmute::<uint8x8_t, poly8x8_t>(a_hi),
874                transmute::<uint8x8_t, poly8x8_t>(b_hi),
875            );
876
877            // Reduction using Table Lookup
878            // Load the tables once.
879            let tbl_lo = vld1q_u8(
880                [
881                    0x00, 0x1b, 0x36, 0x2d, 0x6c, 0x77, 0x5a, 0x41, 0xd8, 0xc3, 0xee, 0xf5, 0xb4,
882                    0xaf, 0x82, 0x99,
883                ]
884                .as_ptr(),
885            );
886
887            let tbl_hi = vld1q_u8(
888                [
889                    0x00, 0xab, 0x4d, 0xe6, 0x9a, 0x31, 0xd7, 0x7c, 0x2f, 0x84, 0x62, 0xc9, 0xb5,
890                    0x1e, 0xf8, 0x53,
891                ]
892                .as_ptr(),
893            );
894
895            // Helper to reduce a 128-bit vector
896            // of 16-bit polys down to a 64-bit
897            // vector of 8-bit results.
898            let reduce_tbl = |val_poly: poly16x8_t| -> uint8x8_t {
899                let val: uint16x8_t = transmute(val_poly);
900
901                // vmovn_u16 narrows 128-bit (u16x8) to 64-bit (u8x8)
902                let data = vmovn_u16(val);
903                let carry_u16 = vshrq_n_u16(val, 8);
904                let carry = vmovn_u16(carry_u16);
905
906                // Operations on 64-bit vectors
907                let mask_lo = vdup_n_u8(0x0F);
908                let h_lo = vand_u8(carry, mask_lo);
909                let h_hi = vshr_n_u8(carry, 4);
910
911                // Lookup:
912                // Table is 128-bit (q),
913                // Index is 64-bit.
914                // Result is 64-bit.
915                let r_lo = vqtbl1_u8(tbl_lo, h_lo);
916                let r_hi = vqtbl1_u8(tbl_hi, h_hi);
917
918                // XOR everything together
919                veor_u8(data, veor_u8(r_lo, r_hi))
920            };
921
922            let final_lo = reduce_tbl(res_lo);
923            let final_hi = reduce_tbl(res_hi);
924
925            // Combine two 64-bit results
926            // back into one 128-bit vector.
927            let res = vcombine_u8(final_lo, final_hi);
928
929            PackedBlock8(transmute::<uint8x16_t, [Block8; 16]>(res))
930        }
931    }
932}
933
934#[cfg(test)]
935mod tests {
936    use super::*;
937    use rand::{RngExt, rng};
938
939    // ==================================
940    // BASIC
941    // ==================================
942
943    #[test]
944    fn tower_constants() {
945        // Check that tau is propagated correctly
946        // For Block8 we set 0x20
947        assert_eq!(Block8::EXTENSION_TAU, Block8(0x20));
948    }
949
950    #[test]
951    fn add_truth() {
952        let zero = Block8::ZERO;
953        let one = Block8::ONE;
954
955        assert_eq!(zero + zero, zero);
956        assert_eq!(zero + one, one);
957        assert_eq!(one + zero, one);
958        assert_eq!(one + one, zero);
959    }
960
961    #[test]
962    fn mul_truth() {
963        let zero = Block8::ZERO;
964        let one = Block8::ONE;
965
966        assert_eq!(zero * zero, zero);
967        assert_eq!(zero * one, zero);
968        assert_eq!(one * one, one);
969    }
970
971    #[test]
972    fn add() {
973        // 5 ^ 3 = 6
974        // 101 ^ 011 = 110
975        assert_eq!(Block8(5) + Block8(3), Block8(6));
976    }
977
978    #[test]
979    fn mul_simple() {
980        // Check for prime numbers (without overflow)
981        // x^1 * x^1 = x^2 (2 * 2 = 4)
982        assert_eq!(Block8(2) * Block8(2), Block8(4));
983    }
984
985    #[test]
986    fn mul_overflow() {
987        // Reduction verification (AES test vectors)
988        // Example from the AES specification:
989        // 0x57 * 0x83 = 0xC1
990        assert_eq!(Block8(0x57) * Block8(0x83), Block8(0xC1));
991    }
992
993    #[test]
994    fn square_exhaustive() {
995        for i in 0u16..=255 {
996            let x = Block8(i as u8);
997            assert_eq!(x.square(), x * x, "Block8 square mismatch at {i:#04x}");
998        }
999    }
1000
1001    #[test]
1002    fn mul_tau_exhaustive() {
1003        for i in 0u16..=255 {
1004            let x = Block8(i as u8);
1005            assert_eq!(
1006                x.mul_tau(),
1007                x * Block8::EXTENSION_TAU,
1008                "Block8 mul_tau mismatch at {i:#04x}"
1009            );
1010        }
1011    }
1012
1013    #[test]
1014    fn security_zeroize() {
1015        let mut secret_val = Block8::from(0xFF_u32);
1016        assert_ne!(secret_val, Block8::ZERO);
1017
1018        secret_val.zeroize();
1019
1020        assert_eq!(secret_val, Block8::ZERO);
1021        assert_eq!(secret_val.0, 0, "Block8 memory leak detected");
1022    }
1023
1024    #[test]
1025    fn inversion_exhaustive() {
1026        // Iterate over all possible field elements (0..255)
1027        for i in 0u8..=255 {
1028            let val = Block8(i);
1029
1030            if val == Block8::ZERO {
1031                // Case 1:
1032                // Zero inversion safety check
1033                assert_eq!(val.invert(), Block8::ZERO, "invert(0) must return 0");
1034            } else {
1035                // Case 2:
1036                // Algebraic correctness a * a^-1 = 1
1037                let inv = val.invert();
1038                let product = val * inv;
1039
1040                assert_eq!(
1041                    product,
1042                    Block8::ONE,
1043                    "Inversion identity failed: a * a^-1 != 1"
1044                );
1045            }
1046        }
1047    }
1048
1049    // ==================================
1050    // HARDWARE
1051    // ==================================
1052
1053    #[test]
1054    fn isomorphism_roundtrip() {
1055        let mut rng = rng();
1056        for _ in 0..1000 {
1057            let val = Block8::from(rng.random::<u8>());
1058
1059            // Roundtrip:
1060            // Tower -> Flat -> Tower must be identity
1061            assert_eq!(
1062                val.to_hardware().to_tower(),
1063                val,
1064                "Block8 isomorphism roundtrip failed"
1065            );
1066        }
1067    }
1068
1069    #[test]
1070    fn isomorphism_is_frobenius() {
1071        for i in 0u16..=255 {
1072            let x = Block8(i as u8);
1073
1074            let mut x8 = x;
1075            for _ in 0..3 {
1076                x8 = x8.square();
1077            }
1078
1079            let mut x32 = x8;
1080            for _ in 0..2 {
1081                x32 = x32.square();
1082            }
1083
1084            assert_eq!(
1085                x.to_hardware().into_raw(),
1086                x32,
1087                "Block8 to_hardware is not a^32 at {i:#04x}"
1088            );
1089            assert_eq!(
1090                Flat::from_raw(x).to_tower(),
1091                x8,
1092                "Block8 from_hardware is not a^8 at {i:#04x}"
1093            );
1094        }
1095    }
1096
1097    #[test]
1098    fn parity_masks_match_from_hardware() {
1099        // Exhaustive for Block8:
1100        // 256 values * 8 bits.
1101        for x in 0u16..=255 {
1102            let x_flat = x as u8;
1103            let tower = Block8::from_hardware(Flat::from_raw(Block8(x_flat))).0;
1104
1105            for (k, &mask) in FLAT_TO_TOWER_BIT_MASKS_8.iter().enumerate() {
1106                let parity = ((x_flat & mask).count_ones() & 1) as u8;
1107                let bit = (tower >> k) & 1;
1108                assert_eq!(
1109                    parity, bit,
1110                    "Block8 mask mismatch at x={x_flat:#04x}, k={k}"
1111                );
1112
1113                let via_api = Flat::from_raw(Block8(x_flat)).tower_bit(k);
1114                assert_eq!(via_api, bit, "Block8 tower_bit_from_hardware mismatch");
1115            }
1116        }
1117    }
1118
1119    #[test]
1120    fn flat_mul_homomorphism() {
1121        let mut rng = rng();
1122        for _ in 0..1000 {
1123            let a = Block8::from(rng.random::<u8>());
1124            let b = Block8::from(rng.random::<u8>());
1125
1126            let expected_flat = (a * b).to_hardware();
1127            let actual_flat = a.to_hardware() * b.to_hardware();
1128
1129            // Check if multiplication in Flat basis matches Tower
1130            assert_eq!(
1131                actual_flat, expected_flat,
1132                "Block8 flat multiplication mismatch"
1133            );
1134        }
1135    }
1136
1137    #[test]
1138    fn packed_consistency() {
1139        let mut rng = rng();
1140        for _ in 0..100 {
1141            let mut a_vals = [Block8::ZERO; 16];
1142            let mut b_vals = [Block8::ZERO; 16];
1143
1144            for i in 0..16 {
1145                a_vals[i] = Block8::from(rng.random::<u8>());
1146                b_vals[i] = Block8::from(rng.random::<u8>());
1147            }
1148
1149            let a_flat_vals = a_vals.map(|x| x.to_hardware());
1150            let b_flat_vals = b_vals.map(|x| x.to_hardware());
1151            let a_packed = Flat::<Block8>::pack(&a_flat_vals);
1152            let b_packed = Flat::<Block8>::pack(&b_flat_vals);
1153
1154            // Test SIMD Add (XOR)
1155            let add_res = Block8::add_hardware_packed(a_packed, b_packed);
1156
1157            let mut add_out = [Block8::ZERO.to_hardware(); 16];
1158            Flat::<Block8>::unpack(add_res, &mut add_out);
1159
1160            for i in 0..16 {
1161                assert_eq!(
1162                    add_out[i],
1163                    (a_vals[i] + b_vals[i]).to_hardware(),
1164                    "Block8 packed add mismatch"
1165                );
1166            }
1167
1168            // Test SIMD Mul (Flat basis)
1169            let mul_res = Block8::mul_hardware_packed(a_packed, b_packed);
1170
1171            let mut mul_out = [Block8::ZERO.to_hardware(); 16];
1172            Flat::<Block8>::unpack(mul_res, &mut mul_out);
1173
1174            for i in 0..16 {
1175                assert_eq!(
1176                    mul_out[i],
1177                    (a_vals[i] * b_vals[i]).to_hardware(),
1178                    "Block8 packed mul mismatch"
1179                );
1180            }
1181        }
1182    }
1183
1184    // ==================================
1185    // PACKED
1186    // ==================================
1187
1188    #[test]
1189    fn pack_unpack_roundtrip() {
1190        let mut rng = rng();
1191        let mut data = [Block8::ZERO; PACKED_WIDTH_8];
1192
1193        for v in data.iter_mut() {
1194            *v = Block8(rng.random());
1195        }
1196
1197        let packed = Block8::pack(&data);
1198        let mut unpacked = [Block8::ZERO; PACKED_WIDTH_8];
1199        Block8::unpack(packed, &mut unpacked);
1200
1201        assert_eq!(data, unpacked, "Block8 pack/unpack roundtrip failed");
1202    }
1203
1204    #[test]
1205    fn packed_add_consistency() {
1206        let mut rng = rng();
1207        let mut a_vals = [Block8::ZERO; PACKED_WIDTH_8];
1208        let mut b_vals = [Block8::ZERO; PACKED_WIDTH_8];
1209
1210        for i in 0..PACKED_WIDTH_8 {
1211            a_vals[i] = Block8(rng.random());
1212            b_vals[i] = Block8(rng.random());
1213        }
1214
1215        let a_packed = Block8::pack(&a_vals);
1216        let b_packed = Block8::pack(&b_vals);
1217        let res_packed = a_packed + b_packed;
1218
1219        let mut res_unpacked = [Block8::ZERO; PACKED_WIDTH_8];
1220        Block8::unpack(res_packed, &mut res_unpacked);
1221
1222        for i in 0..PACKED_WIDTH_8 {
1223            assert_eq!(
1224                res_unpacked[i],
1225                a_vals[i] + b_vals[i],
1226                "Block8 packed add mismatch at index {}",
1227                i
1228            );
1229        }
1230    }
1231
1232    #[test]
1233    fn packed_mul_consistency() {
1234        let mut rng = rng();
1235
1236        for _ in 0..1000 {
1237            let mut a_arr = [Block8::ZERO; PACKED_WIDTH_8];
1238            let mut b_arr = [Block8::ZERO; PACKED_WIDTH_8];
1239
1240            for i in 0..PACKED_WIDTH_8 {
1241                let val_a: u8 = rng.random();
1242                let val_b: u8 = rng.random();
1243                a_arr[i] = Block8(val_a);
1244                b_arr[i] = Block8(val_b);
1245            }
1246
1247            let a_packed = PackedBlock8(a_arr);
1248            let b_packed = PackedBlock8(b_arr);
1249            let c_packed = a_packed * b_packed;
1250
1251            let mut c_expected = [Block8::ZERO; PACKED_WIDTH_8];
1252            for i in 0..PACKED_WIDTH_8 {
1253                c_expected[i] = a_arr[i] * b_arr[i];
1254            }
1255
1256            assert_eq!(c_packed.0, c_expected, "SIMD Block8 mismatch!");
1257        }
1258    }
1259}