Skip to main content

hekate_math/towers/
block128.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^128) as `Block64[X] / (X^2 + X + τ)`, its flat basis
19//! modulo x^128 + x^7 + x^2 + x + 1, and the aarch64 NEON kernels.
20
21use crate::algebra::impl_binary_field_extras;
22use crate::towers::bit::Bit;
23use crate::towers::block8::Block8;
24use crate::towers::block16::Block16;
25use crate::towers::block32::Block32;
26use crate::towers::block64::Block64;
27use crate::{
28    BinaryFieldExtras, CanonicalDeserialize, CanonicalSerialize, Flat, FlatPromote, HardwareField,
29    PackableField, PackedFlat, TowerField, constants,
30};
31use core::ops::{Add, AddAssign, BitXor, BitXorAssign, Mul, MulAssign, Sub, SubAssign};
32use serde::{Deserialize, Serialize};
33use zeroize::Zeroize;
34
35#[cfg(not(feature = "table-math"))]
36#[repr(align(64))]
37struct CtConvertBasisU128<const N: usize>([u128; N]);
38
39#[cfg(not(feature = "table-math"))]
40static TOWER_TO_FLAT_BASIS_128: CtConvertBasisU128<128> =
41    CtConvertBasisU128(constants::RAW_TOWER_TO_FLAT_128);
42
43#[cfg(not(feature = "table-math"))]
44static FLAT_TO_TOWER_BASIS_128: CtConvertBasisU128<128> =
45    CtConvertBasisU128(constants::RAW_FLAT_TO_TOWER_128);
46
47/// GF(2^128) in the tower basis: `lo + hi·X`
48/// over [`Block64`], `lo` in the low 64 bits.
49#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
50#[repr(transparent)]
51pub struct Block128(pub u128);
52
53impl Block128 {
54    // 0x2000_0000_0000_0000 << 64
55    const TAU: Self = Block128(0x2000_0000_0000_0000_0000_0000_0000_0000);
56
57    /// Builds `lo + hi·X` from its halves.
58    pub fn new(lo: Block64, hi: Block64) -> Self {
59        Self((hi.0 as u128) << 64 | (lo.0 as u128))
60    }
61
62    /// Returns the halves `(lo, hi)` of `lo + hi·X`.
63    #[inline(always)]
64    pub fn split(self) -> (Block64, Block64) {
65        (Block64(self.0 as u64), Block64((self.0 >> 64) as u64))
66    }
67
68    #[inline(always)]
69    pub(crate) fn mul_tau(self) -> Self {
70        let (a0, a1) = self.split();
71        let t = a1.mul_tau();
72
73        Self::new(t.mul_tau(), (a0 + a1).mul_tau())
74    }
75}
76
77impl TowerField for Block128 {
78    const BITS: usize = 128;
79    const ZERO: Self = Block128(0);
80    const ONE: Self = Block128(1);
81
82    const EXTENSION_TAU: Self = Self::TAU;
83
84    fn invert(&self) -> Self {
85        let (l, h) = self.split();
86
87        let norm = h.square().mul_tau() + h * l + l.square();
88        let norm_inv = norm.invert();
89
90        let res_hi = h * norm_inv;
91        let res_lo = (h + l) * norm_inv;
92
93        Self::new(res_lo, res_hi)
94    }
95
96    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
97        let mut buf = [0u8; 16];
98        buf.copy_from_slice(&bytes[0..16]);
99
100        Self(u128::from_le_bytes(buf))
101    }
102}
103
104impl Add for Block128 {
105    type Output = Self;
106
107    fn add(self, rhs: Self) -> Self {
108        Self(self.0.bitxor(rhs.0))
109    }
110}
111
112impl Sub for Block128 {
113    type Output = Self;
114
115    fn sub(self, rhs: Self) -> Self {
116        self.add(rhs)
117    }
118}
119
120impl Mul for Block128 {
121    type Output = Self;
122
123    #[inline]
124    fn mul(self, rhs: Self) -> Self {
125        #[cfg(all(pmull, feature = "table-math"))]
126        {
127            mul_iso_128(self, rhs)
128        }
129
130        #[cfg(not(all(pmull, feature = "table-math")))]
131        {
132            let (a0, a1) = self.split();
133            let (b0, b1) = rhs.split();
134
135            let v0 = a0 * b0;
136            let v1 = a1 * b1;
137            let vs = (a0 + a1) * (b0 + b1);
138
139            Self::new(v0 + v1.mul_tau(), v0 + vs)
140        }
141    }
142}
143
144impl AddAssign for Block128 {
145    fn add_assign(&mut self, rhs: Self) {
146        self.0.bitxor_assign(rhs.0);
147    }
148}
149
150impl SubAssign for Block128 {
151    fn sub_assign(&mut self, rhs: Self) {
152        self.0.bitxor_assign(rhs.0);
153    }
154}
155
156impl MulAssign for Block128 {
157    fn mul_assign(&mut self, rhs: Self) {
158        *self = *self * rhs;
159    }
160}
161
162impl CanonicalSerialize for Block128 {
163    fn serialized_size(&self) -> usize {
164        16
165    }
166
167    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
168        if writer.len() < 16 {
169            return Err(());
170        }
171
172        writer[..16].copy_from_slice(&self.0.to_le_bytes());
173
174        Ok(())
175    }
176}
177
178impl CanonicalDeserialize for Block128 {
179    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
180        if bytes.len() < 16 {
181            return Err(());
182        }
183
184        let mut buf = [0u8; 16];
185        buf.copy_from_slice(&bytes[0..16]);
186
187        Ok(Self(u128::from_le_bytes(buf)))
188    }
189}
190
191impl From<u8> for Block128 {
192    fn from(val: u8) -> Self {
193        Self(val as u128)
194    }
195}
196
197impl From<u32> for Block128 {
198    #[inline]
199    fn from(val: u32) -> Self {
200        Self(val as u128)
201    }
202}
203
204impl From<u64> for Block128 {
205    #[inline]
206    fn from(val: u64) -> Self {
207        Self::from(val as u128)
208    }
209}
210
211impl From<u128> for Block128 {
212    #[inline]
213    fn from(val: u128) -> Self {
214        Self(val)
215    }
216}
217
218// ========================================
219// FIELD LIFTING
220// ========================================
221
222impl From<Bit> for Block128 {
223    #[inline(always)]
224    fn from(val: Bit) -> Self {
225        Self(val.get() as u128)
226    }
227}
228
229impl From<Block8> for Block128 {
230    #[inline(always)]
231    fn from(val: Block8) -> Self {
232        Self(val.0 as u128)
233    }
234}
235
236impl From<Block16> for Block128 {
237    #[inline(always)]
238    fn from(val: Block16) -> Self {
239        Self(val.0 as u128)
240    }
241}
242
243impl From<Block32> for Block128 {
244    #[inline(always)]
245    fn from(val: Block32) -> Self {
246        Self(val.0 as u128)
247    }
248}
249
250impl From<Block64> for Block128 {
251    #[inline(always)]
252    fn from(val: Block64) -> Self {
253        Self(val.0 as u128)
254    }
255}
256
257// ===================================
258// PACKED BLOCK 128 (Width = 4)
259// ===================================
260
261/// Lanes in [`PackedBlock128`].
262pub const PACKED_WIDTH_128: usize = 4;
263
264/// Four [`Block128`] lanes; operators act lane by lane.
265/// 32-byte alignment matches the AVX2 load width.
266#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
267#[repr(C, align(32))]
268pub struct PackedBlock128(pub [Block128; PACKED_WIDTH_128]);
269
270impl PackedBlock128 {
271    /// Returns the packed value with every lane zero.
272    #[inline(always)]
273    pub fn zero() -> Self {
274        Self([Block128::ZERO; PACKED_WIDTH_128])
275    }
276
277    /// Returns `val` in every lane.
278    #[inline(always)]
279    pub fn broadcast(val: Block128) -> Self {
280        Self([val; PACKED_WIDTH_128])
281    }
282}
283
284impl PackableField for Block128 {
285    type Packed = PackedBlock128;
286
287    const WIDTH: usize = PACKED_WIDTH_128;
288
289    #[inline(always)]
290    fn pack(chunk: &[Self]) -> Self::Packed {
291        assert!(
292            chunk.len() >= PACKED_WIDTH_128,
293            "PackableField::pack: input slice too short",
294        );
295
296        let mut arr = [Self::ZERO; PACKED_WIDTH_128];
297        arr.copy_from_slice(&chunk[..PACKED_WIDTH_128]);
298
299        PackedBlock128(arr)
300    }
301
302    #[inline(always)]
303    fn unpack(packed: Self::Packed, output: &mut [Self]) {
304        assert!(
305            output.len() >= PACKED_WIDTH_128,
306            "PackableField::unpack: output slice too short",
307        );
308
309        output[..PACKED_WIDTH_128].copy_from_slice(&packed.0);
310    }
311}
312
313// 1. ADDITION (XOR)
314// This is perfectly parallel. Compiler will
315// vectorize this automatically using `vpxor`.
316
317impl Add for PackedBlock128 {
318    type Output = Self;
319
320    #[inline(always)]
321    fn add(self, rhs: Self) -> Self {
322        let mut res = [Block128::ZERO; PACKED_WIDTH_128];
323        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
324            *out = *l + *r;
325        }
326
327        Self(res)
328    }
329}
330
331impl AddAssign for PackedBlock128 {
332    #[inline(always)]
333    fn add_assign(&mut self, rhs: Self) {
334        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
335            *l += *r;
336        }
337    }
338}
339
340// 2. SUBTRACTION (Same as Add for Char 2)
341
342impl Sub for PackedBlock128 {
343    type Output = Self;
344
345    #[inline(always)]
346    fn sub(self, rhs: Self) -> Self {
347        self.add(rhs)
348    }
349}
350
351impl SubAssign for PackedBlock128 {
352    #[inline(always)]
353    fn sub_assign(&mut self, rhs: Self) {
354        self.add_assign(rhs);
355    }
356}
357
358// 3. MULTIPLICATION (Hardware Accelerated)
359
360impl Mul for PackedBlock128 {
361    type Output = Self;
362
363    #[inline(always)]
364    fn mul(self, rhs: Self) -> Self {
365        let mut res = [Block128::ZERO; PACKED_WIDTH_128];
366        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
367            *out = *l * *r;
368        }
369
370        Self(res)
371    }
372}
373
374impl MulAssign for PackedBlock128 {
375    #[inline(always)]
376    fn mul_assign(&mut self, rhs: Self) {
377        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
378            *l *= *r;
379        }
380    }
381}
382
383// 4. SCALAR MULTIPLICATION (Vector * Scalar)
384// Used for broadcasting coefficients.
385
386impl Mul<Block128> for PackedBlock128 {
387    type Output = Self;
388
389    #[inline(always)]
390    fn mul(self, rhs: Block128) -> Self {
391        let mut res = [Block128::ZERO; PACKED_WIDTH_128];
392        for (out, v) in res.iter_mut().zip(self.0.iter()) {
393            *out = *v * rhs;
394        }
395
396        Self(res)
397    }
398}
399
400impl MulAssign<Block128> for PackedBlock128 {
401    #[inline(always)]
402    fn mul_assign(&mut self, rhs: Block128) {
403        for v in self.0.iter_mut() {
404            *v *= rhs;
405        }
406    }
407}
408
409// ===================================
410// Block128 Hardware Field
411// ===================================
412
413impl HardwareField for Block128 {
414    #[inline(always)]
415    fn to_hardware(self) -> Flat<Self> {
416        #[cfg(feature = "table-math")]
417        {
418            Flat::from_raw(apply_matrix_128(self, &constants::TOWER_TO_FLAT_128))
419        }
420
421        #[cfg(not(feature = "table-math"))]
422        {
423            Flat::from_raw(Block128(map_ct_128_split(
424                self.0,
425                &TOWER_TO_FLAT_BASIS_128.0,
426            )))
427        }
428    }
429
430    #[inline(always)]
431    fn from_hardware(value: Flat<Self>) -> Self {
432        let value = value.into_raw();
433
434        #[cfg(feature = "table-math")]
435        {
436            apply_matrix_128(value, &constants::FLAT_TO_TOWER_128)
437        }
438
439        #[cfg(not(feature = "table-math"))]
440        {
441            Block128(map_ct_128_split(value.0, &FLAT_TO_TOWER_BASIS_128.0))
442        }
443    }
444
445    #[inline(always)]
446    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
447        let lhs = lhs.into_raw();
448        let rhs = rhs.into_raw();
449
450        #[cfg(target_arch = "aarch64")]
451        {
452            Flat::from_raw(neon::add_flat_128(lhs, rhs))
453        }
454
455        #[cfg(not(target_arch = "aarch64"))]
456        {
457            Flat::from_raw(lhs + rhs)
458        }
459    }
460
461    #[inline(always)]
462    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
463        let lhs = lhs.into_raw();
464        let rhs = rhs.into_raw();
465
466        #[cfg(target_arch = "aarch64")]
467        {
468            PackedFlat::from_raw(neon::add_packed_128(lhs, rhs))
469        }
470
471        #[cfg(not(target_arch = "aarch64"))]
472        {
473            PackedFlat::from_raw(lhs + rhs)
474        }
475    }
476
477    #[inline(always)]
478    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
479        let lhs = lhs.into_raw();
480        let rhs = rhs.into_raw();
481
482        #[cfg(pmull)]
483        {
484            Flat::from_raw(neon::mul_flat_128(lhs, rhs))
485        }
486
487        #[cfg(not(pmull))]
488        {
489            let a_tower = Self::from_hardware(Flat::from_raw(lhs));
490            let b_tower = Self::from_hardware(Flat::from_raw(rhs));
491
492            (a_tower * b_tower).to_hardware()
493        }
494    }
495
496    #[inline(always)]
497    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
498        let lhs = lhs.into_raw();
499        let rhs = rhs.into_raw();
500
501        #[cfg(pmull)]
502        {
503            let mut res = [Block128::ZERO; PACKED_WIDTH_128];
504            for ((out, l), r) in res.iter_mut().zip(lhs.0.iter()).zip(rhs.0.iter()) {
505                *out = neon::mul_flat_128(*l, *r);
506            }
507
508            PackedFlat::from_raw(PackedBlock128(res))
509        }
510
511        #[cfg(not(pmull))]
512        {
513            let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
514            let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
515            let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
516
517            Self::unpack(lhs, &mut l);
518            Self::unpack(rhs, &mut r);
519
520            for i in 0..<Self as PackableField>::WIDTH {
521                res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
522            }
523
524            PackedFlat::from_raw(Self::pack(&res))
525        }
526    }
527
528    #[inline(always)]
529    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
530        let broadcasted = PackedBlock128::broadcast(rhs.into_raw());
531        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
532    }
533
534    #[inline(always)]
535    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
536        let mask = constants::FLAT_TO_TOWER_BIT_MASKS_128[bit_idx];
537
538        // Parity of (x & mask) without popcount.
539        // Folds 128 bits down to 1
540        // using a binary XOR tree.
541        let mut v = value.into_raw().0 & mask;
542        v ^= v >> 64;
543        v ^= v >> 32;
544        v ^= v >> 16;
545        v ^= v >> 8;
546        v ^= v >> 4;
547        v ^= v >> 2;
548        v ^= v >> 1;
549
550        (v & 1) as u8
551    }
552}
553
554// ========================================
555// FIELD LIFTING (FlatPromote)
556// ========================================
557//
558// SECURITY:
559// Default implementation is constant-time (CT):
560// no secret-dependent memory access.
561
562#[cfg(not(feature = "table-math"))]
563impl FlatPromote<Block8> for Block128 {
564    #[inline(always)]
565    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
566        let val = val.into_raw();
567        Flat::from_raw(Block128(lift_ct::<8>(
568            val.0 as u64,
569            &constants::LIFT_BASIS_8.0,
570        )))
571    }
572
573    #[cfg(target_arch = "aarch64")]
574    fn promote_flat_batch(input: &[Flat<Block8>], output: &mut [Flat<Self>]) {
575        promote_batch_neon(input, output, neon::promote_batch_8_to_128);
576    }
577}
578
579#[cfg(not(feature = "table-math"))]
580impl FlatPromote<Block16> for Block128 {
581    #[inline(always)]
582    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
583        Flat::from_raw(Block128(lift_ct::<16>(
584            val.into_raw().0 as u64,
585            &constants::LIFT_BASIS_16.0,
586        )))
587    }
588
589    #[cfg(target_arch = "aarch64")]
590    fn promote_flat_batch(input: &[Flat<Block16>], output: &mut [Flat<Self>]) {
591        promote_batch_neon(input, output, neon::promote_batch_16_to_128);
592    }
593}
594
595#[cfg(not(feature = "table-math"))]
596impl FlatPromote<Block32> for Block128 {
597    #[inline(always)]
598    fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
599        Flat::from_raw(Block128(lift_ct::<32>(
600            val.into_raw().0 as u64,
601            &constants::LIFT_BASIS_32.0,
602        )))
603    }
604
605    #[cfg(target_arch = "aarch64")]
606    fn promote_flat_batch(input: &[Flat<Block32>], output: &mut [Flat<Self>]) {
607        promote_batch_neon(input, output, neon::promote_batch_32_to_128);
608    }
609}
610
611#[cfg(not(feature = "table-math"))]
612impl FlatPromote<Block64> for Block128 {
613    #[inline(always)]
614    fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
615        Flat::from_raw(Block128(lift_ct::<64>(
616            val.into_raw().0,
617            &constants::LIFT_BASIS_64.0,
618        )))
619    }
620
621    #[cfg(target_arch = "aarch64")]
622    fn promote_flat_batch(input: &[Flat<Block64>], output: &mut [Flat<Self>]) {
623        promote_batch_neon(input, output, neon::promote_batch_64_to_128);
624    }
625}
626
627// Insecure (secret-dependent table indexing) lifting path
628#[cfg(feature = "table-math")]
629impl FlatPromote<Block8> for Block128 {
630    #[inline(always)]
631    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
632        let idx = val.into_raw().0 as usize;
633        Flat::from_raw(Block128(unsafe {
634            *constants::LIFT_TABLE_8_TO_128.get_unchecked(idx)
635        }))
636    }
637}
638
639#[cfg(feature = "table-math")]
640impl FlatPromote<Block16> for Block128 {
641    #[inline(always)]
642    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
643        let v = val.into_raw().0;
644        let res = unsafe {
645            *constants::PROMOTE_16_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
646                ^ *constants::PROMOTE_16_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
647        };
648
649        Flat::from_raw(Block128(res))
650    }
651}
652
653#[cfg(feature = "table-math")]
654impl FlatPromote<Block32> for Block128 {
655    #[inline(always)]
656    fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
657        let v = val.into_raw().0;
658        let res = unsafe {
659            *constants::PROMOTE_32_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
660                ^ *constants::PROMOTE_32_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
661                ^ *constants::PROMOTE_32_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
662                ^ *constants::PROMOTE_32_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
663        };
664
665        Flat::from_raw(Block128(res))
666    }
667}
668
669#[cfg(feature = "table-math")]
670impl FlatPromote<Block64> for Block128 {
671    #[inline(always)]
672    fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
673        let v = val.into_raw().0;
674        let res = unsafe {
675            *constants::PROMOTE_64_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
676                ^ *constants::PROMOTE_64_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
677                ^ *constants::PROMOTE_64_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
678                ^ *constants::PROMOTE_64_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
679                ^ *constants::PROMOTE_64_BYTE_4_TO_128.get_unchecked(((v >> 32) & 0xFF) as usize)
680                ^ *constants::PROMOTE_64_BYTE_5_TO_128.get_unchecked(((v >> 40) & 0xFF) as usize)
681                ^ *constants::PROMOTE_64_BYTE_6_TO_128.get_unchecked(((v >> 48) & 0xFF) as usize)
682                ^ *constants::PROMOTE_64_BYTE_7_TO_128.get_unchecked(((v >> 56) & 0xFF) as usize)
683        };
684
685        Flat::from_raw(Block128(res))
686    }
687}
688
689// ===========================================
690// Binary Field Extras
691// ===========================================
692
693impl_binary_field_extras!(
694    Block128,
695    map_ct_128_split,
696    TRACE_MASK_128,
697    SOLVE_QUADRATIC_BASIS_128
698);
699
700// ===========================================
701// UTILS
702// ===========================================
703
704/// Returns `a * b` through the flat basis, φ⁻¹(φ(a) · φ(b)), with
705/// the PMULL kernel; aarch64 with the `aes` target feature only.
706#[cfg(pmull)]
707#[inline(always)]
708pub fn mul_iso_128(a: Block128, b: Block128) -> Block128 {
709    let a_flat = a.to_hardware();
710    let b_flat = b.to_hardware();
711    let c_flat = Flat::from_raw(neon::mul_flat_128(a_flat.into_raw(), b_flat.into_raw()));
712
713    c_flat.to_tower()
714}
715
716#[cfg(feature = "table-math")]
717#[inline(always)]
718pub(crate) fn apply_matrix_128(val: Block128, table: &[u128; 4096]) -> Block128 {
719    let mut res = 0u128;
720    let v = val.0;
721
722    // [!] The Ghost isn't in the Shell @_@
723
724    // 16 lookups (8-bit window)
725    for i in 0..16 {
726        let byte = (v >> (i * 8)) & 0xFF;
727        let idx = (i * 256) + (byte as usize);
728
729        // SAFETY:
730        // idx = i * 256 + byte < 16 * 256 = table.len().
731        res ^= unsafe { *table.get_unchecked(idx) };
732    }
733
734    Block128(res)
735}
736
737#[inline(always)]
738fn map_ct_128_split(x: u128, basis: &[u128; 128]) -> u128 {
739    let lo = x as u64;
740    let hi = (x >> 64) as u64;
741
742    let mut acc_lo = 0u64;
743    let mut acc_hi = 0u64;
744    let mut i = 0usize;
745
746    while i < 64 {
747        let m_lo = 0u64.wrapping_sub((lo >> i) & 1);
748        let m_hi = 0u64.wrapping_sub((hi >> i) & 1);
749
750        let a = basis[i];
751        let b = basis[64 + i];
752
753        acc_lo ^= ((a as u64) & m_lo) ^ ((b as u64) & m_hi);
754        acc_hi ^= (((a >> 64) as u64) & m_lo) ^ (((b >> 64) as u64) & m_hi);
755
756        i += 1;
757    }
758
759    (acc_lo as u128) | ((acc_hi as u128) << 64)
760}
761
762#[cfg(not(feature = "table-math"))]
763#[inline(always)]
764fn lift_ct<const N: usize>(x: u64, basis: &'static [u128; N]) -> u128 {
765    let mut acc = 0u128;
766    let mut i = 0usize;
767
768    while i < N {
769        let bit = (x >> i) & 1;
770        let mask = 0u128.wrapping_sub(bit as u128);
771        acc ^= basis[i] & mask;
772        i += 1;
773    }
774
775    acc
776}
777
778#[cfg(all(target_arch = "aarch64", not(feature = "table-math")))]
779#[inline(always)]
780fn promote_batch_neon<Src>(
781    input: &[Flat<Src>],
782    output: &mut [Flat<Block128>],
783    kernel: unsafe fn(*const u8, *mut u128),
784) where
785    Src: HardwareField,
786    Block128: FlatPromote<Src>,
787{
788    let n = input.len().min(output.len());
789    let full = n / 16;
790
791    for chunk in 0..full {
792        let i = chunk * 16;
793
794        // SAFETY:
795        // i + 16 <= n <= both lengths; `kernel` reads 16 `Src`
796        // from `input[i..]` and writes 16 to `output[i..]`.
797        unsafe {
798            kernel(
799                input.as_ptr().add(i).cast::<u8>(),
800                output.as_mut_ptr().add(i).cast::<u128>(),
801            );
802        }
803    }
804
805    for i in full * 16..n {
806        output[i] = <Block128 as FlatPromote<Src>>::promote_flat(input[i]);
807    }
808}
809
810// ===========================================
811// 128-BIT SIMD INSTRUCTIONS
812// ===========================================
813
814#[cfg(target_arch = "aarch64")]
815mod neon {
816    use super::*;
817    use core::arch::aarch64::*;
818    use core::mem::transmute;
819
820    const _: () = assert!(constants::POLY_128 == 0x87, "verus twins hardcode R = 0x87");
821
822    #[inline(always)]
823    pub fn add_packed_128(lhs: PackedBlock128, rhs: PackedBlock128) -> PackedBlock128 {
824        unsafe {
825            // Block128 is packed into 4 elements
826            // (512 bits), work with 4 registers.
827            let l: [uint8x16_t; 4] = transmute(lhs.0);
828            let r: [uint8x16_t; 4] = transmute(rhs.0);
829
830            let res = [
831                veorq_u8(l[0], r[0]),
832                veorq_u8(l[1], r[1]),
833                veorq_u8(l[2], r[2]),
834                veorq_u8(l[3], r[3]),
835            ];
836
837            transmute(res)
838        }
839    }
840
841    #[inline(always)]
842    pub fn add_flat_128(a: Block128, b: Block128) -> Block128 {
843        unsafe {
844            // veor, not u128 ^:
845            // stays in the mul kernel's register file.
846            let l: uint8x16_t = transmute(a.0);
847            let r: uint8x16_t = transmute(b.0);
848
849            Block128(transmute::<uint8x16_t, u128>(veorq_u8(l, r)))
850        }
851    }
852
853    #[cfg(pmull)]
854    #[inline(always)]
855    pub fn mul_flat_128(a: Block128, b: Block128) -> Block128 {
856        unsafe {
857            let a8: uint8x16_t = transmute(a.0);
858            let b8: uint8x16_t = transmute(b.0);
859            let ap: poly64x2_t = transmute(a8);
860            let bp: poly64x2_t = transmute(b8);
861
862            // Schoolbook, not Karatsuba:
863            // issue width binds, not PMULL count.
864            let bs: poly64x2_t = transmute(vextq_u8::<8>(b8, b8));
865
866            let d0: uint8x16_t =
867                transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bp)));
868            let d2: uint8x16_t = transmute(vmull_high_p64(ap, bp));
869            let x0: uint8x16_t =
870                transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bs)));
871            let x1: uint8x16_t = transmute(vmull_high_p64(ap, bs));
872
873            let mid = veorq_u8(x0, x1);
874
875            // 256-bit product limbs:
876            // lo = [c0, c1], hi = [c2, c3].
877            let zero = vdupq_n_u8(0);
878            let lo = veorq_u8(d0, vextq_u8::<8>(zero, mid));
879            let hi = veorq_u8(d2, vextq_u8::<8>(mid, zero));
880
881            // Reduction P(x) = x^128 + R(x), R(x) = 0x87;
882            // vdupq_n_p64, not [c; 2]: the repeat
883            // form lowers to a memcpy libcall.
884            let rv: poly64x2_t = vdupq_n_p64(constants::POLY_128 as u64);
885            let hip: poly64x2_t = transmute(hi);
886
887            let p0: uint8x16_t =
888                transmute(vmull_p64(vgetq_lane_p64::<0>(hip), vgetq_lane_p64::<0>(rv)));
889            let p1: uint8x16_t = transmute(vmull_high_p64(hip, rv));
890            let p1p: poly64x2_t = transmute(p1);
891
892            let fold = veorq_u8(p0, vextq_u8::<8>(zero, p1));
893
894            // carry = hi lane of p1 < 2^7;
895            // deg(carry·R) < 14: fits the low lane, no third fold.
896            let carry_mul: uint8x16_t = transmute(vmull_high_p64(p1p, rv));
897
898            let res = veorq_u8(veorq_u8(lo, fold), carry_mul);
899
900            Block128(transmute::<uint8x16_t, u128>(res))
901        }
902    }
903
904    /// CT packed promote:
905    /// 16 × Block8 -> 16 × Block128 via nibble decomposition.
906    ///
907    /// # Safety
908    /// `input` must be readable for 16 `u8`,
909    /// `output` writable for 16 `u128`.
910    #[cfg(not(feature = "table-math"))]
911    #[inline(always)]
912    pub unsafe fn promote_batch_8_to_128(input: *const u8, output: *mut u128) {
913        unsafe {
914            let vals = vld1q_u8(input);
915
916            let mask_0f = vdupq_n_u8(0x0F);
917            let lo_nib = vandq_u8(vals, mask_0f);
918            let hi_nib = vshrq_n_u8::<4>(vals);
919
920            let mut out = [vdupq_n_u8(0); 16];
921
922            macro_rules! lookup {
923                ($j:expr, $lo:ident, $hi:ident, $dst:ident) => {{
924                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_8_0_TO_128[$j].as_ptr());
925                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_8_1_TO_128[$j].as_ptr());
926
927                    $dst[$j] = veorq_u8(vqtbl1q_u8(t0, $lo), vqtbl1q_u8(t1, $hi));
928                }};
929            }
930
931            lookup!(0, lo_nib, hi_nib, out);
932            lookup!(1, lo_nib, hi_nib, out);
933            lookup!(2, lo_nib, hi_nib, out);
934            lookup!(3, lo_nib, hi_nib, out);
935            lookup!(4, lo_nib, hi_nib, out);
936            lookup!(5, lo_nib, hi_nib, out);
937            lookup!(6, lo_nib, hi_nib, out);
938            lookup!(7, lo_nib, hi_nib, out);
939            lookup!(8, lo_nib, hi_nib, out);
940            lookup!(9, lo_nib, hi_nib, out);
941            lookup!(10, lo_nib, hi_nib, out);
942            lookup!(11, lo_nib, hi_nib, out);
943            lookup!(12, lo_nib, hi_nib, out);
944            lookup!(13, lo_nib, hi_nib, out);
945            lookup!(14, lo_nib, hi_nib, out);
946            lookup!(15, lo_nib, hi_nib, out);
947
948            let elems = transpose_16x16(&out);
949            for (i, elem) in elems.iter().enumerate() {
950                vst1q_u8(output.add(i).cast::<u8>(), *elem);
951            }
952        }
953    }
954
955    /// CT packed promote:
956    /// 16 × Block16 -> 16 × Block128 via nibble decomposition.
957    ///
958    /// # Safety
959    /// `input` must be readable for 16 `u16`,
960    /// `output` writable for 16 `u128`.
961    #[cfg(not(feature = "table-math"))]
962    #[inline(always)]
963    pub unsafe fn promote_batch_16_to_128(input: *const u8, output: *mut u128) {
964        unsafe {
965            let raw0 = vld1q_u8(input);
966            let raw1 = vld1q_u8(input.add(16));
967
968            let lo_bytes = vuzp1q_u8(raw0, raw1);
969            let hi_bytes = vuzp2q_u8(raw0, raw1);
970
971            let mask_0f = vdupq_n_u8(0x0F);
972            let n0 = vandq_u8(lo_bytes, mask_0f);
973            let n1 = vshrq_n_u8::<4>(lo_bytes);
974            let n2 = vandq_u8(hi_bytes, mask_0f);
975            let n3 = vshrq_n_u8::<4>(hi_bytes);
976
977            let mut out = [vdupq_n_u8(0); 16];
978
979            macro_rules! lookup {
980                ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident, $dst:ident) => {{
981                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_16_0_TO_128[$j].as_ptr());
982                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_16_1_TO_128[$j].as_ptr());
983                    let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_16_2_TO_128[$j].as_ptr());
984                    let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_16_3_TO_128[$j].as_ptr());
985
986                    $dst[$j] = veorq_u8(
987                        veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
988                        veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
989                    );
990                }};
991            }
992
993            lookup!(0, n0, n1, n2, n3, out);
994            lookup!(1, n0, n1, n2, n3, out);
995            lookup!(2, n0, n1, n2, n3, out);
996            lookup!(3, n0, n1, n2, n3, out);
997            lookup!(4, n0, n1, n2, n3, out);
998            lookup!(5, n0, n1, n2, n3, out);
999            lookup!(6, n0, n1, n2, n3, out);
1000            lookup!(7, n0, n1, n2, n3, out);
1001            lookup!(8, n0, n1, n2, n3, out);
1002            lookup!(9, n0, n1, n2, n3, out);
1003            lookup!(10, n0, n1, n2, n3, out);
1004            lookup!(11, n0, n1, n2, n3, out);
1005            lookup!(12, n0, n1, n2, n3, out);
1006            lookup!(13, n0, n1, n2, n3, out);
1007            lookup!(14, n0, n1, n2, n3, out);
1008            lookup!(15, n0, n1, n2, n3, out);
1009
1010            let elems = transpose_16x16(&out);
1011            for (i, elem) in elems.iter().enumerate() {
1012                vst1q_u8(output.add(i).cast::<u8>(), *elem);
1013            }
1014        }
1015    }
1016
1017    /// CT packed promote:
1018    /// 16 × Block32 -> 16 × Block128 via nibble decomposition.
1019    ///
1020    /// # Safety
1021    /// `input` must be readable for 16 `u32`,
1022    /// `output` writable for 16 `u128`.
1023    #[cfg(not(feature = "table-math"))]
1024    #[inline(always)]
1025    pub unsafe fn promote_batch_32_to_128(input: *const u8, output: *mut u128) {
1026        unsafe {
1027            let raw0 = vld1q_u8(input);
1028            let raw1 = vld1q_u8(input.add(16));
1029            let raw2 = vld1q_u8(input.add(32));
1030            let raw3 = vld1q_u8(input.add(48));
1031
1032            let a02 = vuzp1q_u8(raw0, raw1);
1033            let a13 = vuzp2q_u8(raw0, raw1);
1034            let b02 = vuzp1q_u8(raw2, raw3);
1035            let b13 = vuzp2q_u8(raw2, raw3);
1036
1037            let byte0 = vuzp1q_u8(a02, b02);
1038            let byte2 = vuzp2q_u8(a02, b02);
1039            let byte1 = vuzp1q_u8(a13, b13);
1040            let byte3 = vuzp2q_u8(a13, b13);
1041
1042            let mask_0f = vdupq_n_u8(0x0F);
1043            let n0 = vandq_u8(byte0, mask_0f);
1044            let n1 = vshrq_n_u8::<4>(byte0);
1045            let n2 = vandq_u8(byte1, mask_0f);
1046            let n3 = vshrq_n_u8::<4>(byte1);
1047            let n4 = vandq_u8(byte2, mask_0f);
1048            let n5 = vshrq_n_u8::<4>(byte2);
1049            let n6 = vandq_u8(byte3, mask_0f);
1050            let n7 = vshrq_n_u8::<4>(byte3);
1051
1052            let mut out = [vdupq_n_u8(0); 16];
1053
1054            macro_rules! lookup {
1055                ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident,
1056                 $n4:ident, $n5:ident, $n6:ident, $n7:ident, $dst:ident) => {{
1057                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_32_0_TO_128[$j].as_ptr());
1058                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_32_1_TO_128[$j].as_ptr());
1059                    let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_32_2_TO_128[$j].as_ptr());
1060                    let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_32_3_TO_128[$j].as_ptr());
1061                    let t4 = vld1q_u8(constants::NIBBLE_PROMOTE_32_4_TO_128[$j].as_ptr());
1062                    let t5 = vld1q_u8(constants::NIBBLE_PROMOTE_32_5_TO_128[$j].as_ptr());
1063                    let t6 = vld1q_u8(constants::NIBBLE_PROMOTE_32_6_TO_128[$j].as_ptr());
1064                    let t7 = vld1q_u8(constants::NIBBLE_PROMOTE_32_7_TO_128[$j].as_ptr());
1065
1066                    $dst[$j] = veorq_u8(
1067                        veorq_u8(
1068                            veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
1069                            veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1070                        ),
1071                        veorq_u8(
1072                            veorq_u8(vqtbl1q_u8(t4, $n4), vqtbl1q_u8(t5, $n5)),
1073                            veorq_u8(vqtbl1q_u8(t6, $n6), vqtbl1q_u8(t7, $n7)),
1074                        ),
1075                    );
1076                }};
1077            }
1078
1079            lookup!(0, n0, n1, n2, n3, n4, n5, n6, n7, out);
1080            lookup!(1, n0, n1, n2, n3, n4, n5, n6, n7, out);
1081            lookup!(2, n0, n1, n2, n3, n4, n5, n6, n7, out);
1082            lookup!(3, n0, n1, n2, n3, n4, n5, n6, n7, out);
1083            lookup!(4, n0, n1, n2, n3, n4, n5, n6, n7, out);
1084            lookup!(5, n0, n1, n2, n3, n4, n5, n6, n7, out);
1085            lookup!(6, n0, n1, n2, n3, n4, n5, n6, n7, out);
1086            lookup!(7, n0, n1, n2, n3, n4, n5, n6, n7, out);
1087            lookup!(8, n0, n1, n2, n3, n4, n5, n6, n7, out);
1088            lookup!(9, n0, n1, n2, n3, n4, n5, n6, n7, out);
1089            lookup!(10, n0, n1, n2, n3, n4, n5, n6, n7, out);
1090            lookup!(11, n0, n1, n2, n3, n4, n5, n6, n7, out);
1091            lookup!(12, n0, n1, n2, n3, n4, n5, n6, n7, out);
1092            lookup!(13, n0, n1, n2, n3, n4, n5, n6, n7, out);
1093            lookup!(14, n0, n1, n2, n3, n4, n5, n6, n7, out);
1094            lookup!(15, n0, n1, n2, n3, n4, n5, n6, n7, out);
1095
1096            let elems = transpose_16x16(&out);
1097            for (i, elem) in elems.iter().enumerate() {
1098                vst1q_u8(output.add(i).cast::<u8>(), *elem);
1099            }
1100        }
1101    }
1102
1103    /// CT packed promote:
1104    /// 16 × Block64 -> 16 × Block128 via nibble decomposition.
1105    ///
1106    /// # Safety
1107    /// `input` must be readable for 16 `u64`,
1108    /// `output` writable for 16 `u128`.
1109    #[cfg(not(feature = "table-math"))]
1110    #[inline(always)]
1111    pub unsafe fn promote_batch_64_to_128(input: *const u8, output: *mut u128) {
1112        unsafe {
1113            let raw0 = vld1q_u8(input);
1114            let raw1 = vld1q_u8(input.add(16));
1115            let raw2 = vld1q_u8(input.add(32));
1116            let raw3 = vld1q_u8(input.add(48));
1117            let raw4 = vld1q_u8(input.add(64));
1118            let raw5 = vld1q_u8(input.add(80));
1119            let raw6 = vld1q_u8(input.add(96));
1120            let raw7 = vld1q_u8(input.add(112));
1121
1122            let e01 = vuzp1q_u8(raw0, raw1);
1123            let o01 = vuzp2q_u8(raw0, raw1);
1124            let e23 = vuzp1q_u8(raw2, raw3);
1125            let o23 = vuzp2q_u8(raw2, raw3);
1126            let e45 = vuzp1q_u8(raw4, raw5);
1127            let o45 = vuzp2q_u8(raw4, raw5);
1128            let e67 = vuzp1q_u8(raw6, raw7);
1129            let o67 = vuzp2q_u8(raw6, raw7);
1130
1131            let e0_lo = vuzp1q_u8(e01, e23);
1132            let e2_lo = vuzp2q_u8(e01, e23);
1133            let o1_lo = vuzp1q_u8(o01, o23);
1134            let o3_lo = vuzp2q_u8(o01, o23);
1135            let e0_hi = vuzp1q_u8(e45, e67);
1136            let e2_hi = vuzp2q_u8(e45, e67);
1137            let o1_hi = vuzp1q_u8(o45, o67);
1138            let o3_hi = vuzp2q_u8(o45, o67);
1139
1140            let byte0 = vuzp1q_u8(e0_lo, e0_hi);
1141            let byte4 = vuzp2q_u8(e0_lo, e0_hi);
1142            let byte1 = vuzp1q_u8(o1_lo, o1_hi);
1143            let byte5 = vuzp2q_u8(o1_lo, o1_hi);
1144            let byte2 = vuzp1q_u8(e2_lo, e2_hi);
1145            let byte6 = vuzp2q_u8(e2_lo, e2_hi);
1146            let byte3 = vuzp1q_u8(o3_lo, o3_hi);
1147            let byte7 = vuzp2q_u8(o3_lo, o3_hi);
1148
1149            let mask_0f = vdupq_n_u8(0x0F);
1150            let nib = [
1151                vandq_u8(byte0, mask_0f),
1152                vshrq_n_u8::<4>(byte0),
1153                vandq_u8(byte1, mask_0f),
1154                vshrq_n_u8::<4>(byte1),
1155                vandq_u8(byte2, mask_0f),
1156                vshrq_n_u8::<4>(byte2),
1157                vandq_u8(byte3, mask_0f),
1158                vshrq_n_u8::<4>(byte3),
1159                vandq_u8(byte4, mask_0f),
1160                vshrq_n_u8::<4>(byte4),
1161                vandq_u8(byte5, mask_0f),
1162                vshrq_n_u8::<4>(byte5),
1163                vandq_u8(byte6, mask_0f),
1164                vshrq_n_u8::<4>(byte6),
1165                vandq_u8(byte7, mask_0f),
1166                vshrq_n_u8::<4>(byte7),
1167            ];
1168
1169            let mut out = [vdupq_n_u8(0); 16];
1170
1171            macro_rules! lookup {
1172                ($j:expr, $nib:ident, $dst:ident) => {{
1173                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_64_0_TO_128[$j].as_ptr());
1174                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_64_1_TO_128[$j].as_ptr());
1175                    let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_64_2_TO_128[$j].as_ptr());
1176                    let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_64_3_TO_128[$j].as_ptr());
1177                    let t4 = vld1q_u8(constants::NIBBLE_PROMOTE_64_4_TO_128[$j].as_ptr());
1178                    let t5 = vld1q_u8(constants::NIBBLE_PROMOTE_64_5_TO_128[$j].as_ptr());
1179                    let t6 = vld1q_u8(constants::NIBBLE_PROMOTE_64_6_TO_128[$j].as_ptr());
1180                    let t7 = vld1q_u8(constants::NIBBLE_PROMOTE_64_7_TO_128[$j].as_ptr());
1181                    let t8 = vld1q_u8(constants::NIBBLE_PROMOTE_64_8_TO_128[$j].as_ptr());
1182                    let t9 = vld1q_u8(constants::NIBBLE_PROMOTE_64_9_TO_128[$j].as_ptr());
1183                    let t10 = vld1q_u8(constants::NIBBLE_PROMOTE_64_10_TO_128[$j].as_ptr());
1184                    let t11 = vld1q_u8(constants::NIBBLE_PROMOTE_64_11_TO_128[$j].as_ptr());
1185                    let t12 = vld1q_u8(constants::NIBBLE_PROMOTE_64_12_TO_128[$j].as_ptr());
1186                    let t13 = vld1q_u8(constants::NIBBLE_PROMOTE_64_13_TO_128[$j].as_ptr());
1187                    let t14 = vld1q_u8(constants::NIBBLE_PROMOTE_64_14_TO_128[$j].as_ptr());
1188                    let t15 = vld1q_u8(constants::NIBBLE_PROMOTE_64_15_TO_128[$j].as_ptr());
1189
1190                    $dst[$j] = veorq_u8(
1191                        veorq_u8(
1192                            veorq_u8(
1193                                veorq_u8(vqtbl1q_u8(t0, $nib[0]), vqtbl1q_u8(t1, $nib[1])),
1194                                veorq_u8(vqtbl1q_u8(t2, $nib[2]), vqtbl1q_u8(t3, $nib[3])),
1195                            ),
1196                            veorq_u8(
1197                                veorq_u8(vqtbl1q_u8(t4, $nib[4]), vqtbl1q_u8(t5, $nib[5])),
1198                                veorq_u8(vqtbl1q_u8(t6, $nib[6]), vqtbl1q_u8(t7, $nib[7])),
1199                            ),
1200                        ),
1201                        veorq_u8(
1202                            veorq_u8(
1203                                veorq_u8(vqtbl1q_u8(t8, $nib[8]), vqtbl1q_u8(t9, $nib[9])),
1204                                veorq_u8(vqtbl1q_u8(t10, $nib[10]), vqtbl1q_u8(t11, $nib[11])),
1205                            ),
1206                            veorq_u8(
1207                                veorq_u8(vqtbl1q_u8(t12, $nib[12]), vqtbl1q_u8(t13, $nib[13])),
1208                                veorq_u8(vqtbl1q_u8(t14, $nib[14]), vqtbl1q_u8(t15, $nib[15])),
1209                            ),
1210                        ),
1211                    );
1212                }};
1213            }
1214
1215            lookup!(0, nib, out);
1216            lookup!(1, nib, out);
1217            lookup!(2, nib, out);
1218            lookup!(3, nib, out);
1219            lookup!(4, nib, out);
1220            lookup!(5, nib, out);
1221            lookup!(6, nib, out);
1222            lookup!(7, nib, out);
1223            lookup!(8, nib, out);
1224            lookup!(9, nib, out);
1225            lookup!(10, nib, out);
1226            lookup!(11, nib, out);
1227            lookup!(12, nib, out);
1228            lookup!(13, nib, out);
1229            lookup!(14, nib, out);
1230            lookup!(15, nib, out);
1231
1232            let elems = transpose_16x16(&out);
1233            for (i, elem) in elems.iter().enumerate() {
1234                vst1q_u8(output.add(i).cast::<u8>(), *elem);
1235            }
1236        }
1237    }
1238
1239    /// 16×16 byte matrix transpose via TRN cascade.
1240    #[cfg(not(feature = "table-math"))]
1241    #[inline(always)]
1242    unsafe fn transpose_16x16(r: &[uint8x16_t; 16]) -> [uint8x16_t; 16] {
1243        // Shorthand reinterpret casts
1244        // between NEON register types.
1245        #[inline(always)]
1246        const fn u8_to_u16(v: uint8x16_t) -> uint16x8_t {
1247            unsafe { transmute::<uint8x16_t, uint16x8_t>(v) }
1248        }
1249
1250        #[inline(always)]
1251        const fn u16_to_u32(v: uint16x8_t) -> uint32x4_t {
1252            unsafe { transmute::<uint16x8_t, uint32x4_t>(v) }
1253        }
1254
1255        #[inline(always)]
1256        const fn u32_to_u64(v: uint32x4_t) -> uint64x2_t {
1257            unsafe { transmute::<uint32x4_t, uint64x2_t>(v) }
1258        }
1259
1260        #[inline(always)]
1261        const fn u64_to_u8(v: uint64x2_t) -> uint8x16_t {
1262            unsafe { transmute::<uint64x2_t, uint8x16_t>(v) }
1263        }
1264
1265        unsafe {
1266            // Phase 1:
1267            // TRN u8, transpose 2×2 byte blocks
1268            let a0 = vtrn1q_u8(r[0], r[1]);
1269            let a1 = vtrn2q_u8(r[0], r[1]);
1270            let a2 = vtrn1q_u8(r[2], r[3]);
1271            let a3 = vtrn2q_u8(r[2], r[3]);
1272            let a4 = vtrn1q_u8(r[4], r[5]);
1273            let a5 = vtrn2q_u8(r[4], r[5]);
1274            let a6 = vtrn1q_u8(r[6], r[7]);
1275            let a7 = vtrn2q_u8(r[6], r[7]);
1276            let a8 = vtrn1q_u8(r[8], r[9]);
1277            let a9 = vtrn2q_u8(r[8], r[9]);
1278            let a10 = vtrn1q_u8(r[10], r[11]);
1279            let a11 = vtrn2q_u8(r[10], r[11]);
1280            let a12 = vtrn1q_u8(r[12], r[13]);
1281            let a13 = vtrn2q_u8(r[12], r[13]);
1282            let a14 = vtrn1q_u8(r[14], r[15]);
1283            let a15 = vtrn2q_u8(r[14], r[15]);
1284
1285            // Phase 2:
1286            // TRN u16, transpose 4×4 blocks
1287            let b0 = vtrn1q_u16(u8_to_u16(a0), u8_to_u16(a2));
1288            let b2 = vtrn2q_u16(u8_to_u16(a0), u8_to_u16(a2));
1289            let b1 = vtrn1q_u16(u8_to_u16(a1), u8_to_u16(a3));
1290            let b3 = vtrn2q_u16(u8_to_u16(a1), u8_to_u16(a3));
1291            let b4 = vtrn1q_u16(u8_to_u16(a4), u8_to_u16(a6));
1292            let b6 = vtrn2q_u16(u8_to_u16(a4), u8_to_u16(a6));
1293            let b5 = vtrn1q_u16(u8_to_u16(a5), u8_to_u16(a7));
1294            let b7 = vtrn2q_u16(u8_to_u16(a5), u8_to_u16(a7));
1295            let b8 = vtrn1q_u16(u8_to_u16(a8), u8_to_u16(a10));
1296            let b10 = vtrn2q_u16(u8_to_u16(a8), u8_to_u16(a10));
1297            let b9 = vtrn1q_u16(u8_to_u16(a9), u8_to_u16(a11));
1298            let b11 = vtrn2q_u16(u8_to_u16(a9), u8_to_u16(a11));
1299            let b12 = vtrn1q_u16(u8_to_u16(a12), u8_to_u16(a14));
1300            let b14 = vtrn2q_u16(u8_to_u16(a12), u8_to_u16(a14));
1301            let b13 = vtrn1q_u16(u8_to_u16(a13), u8_to_u16(a15));
1302            let b15 = vtrn2q_u16(u8_to_u16(a13), u8_to_u16(a15));
1303
1304            // Phase 3:
1305            // TRN u32, transpose 8×8 blocks
1306            let c0 = vtrn1q_u32(u16_to_u32(b0), u16_to_u32(b4));
1307            let c4 = vtrn2q_u32(u16_to_u32(b0), u16_to_u32(b4));
1308            let c1 = vtrn1q_u32(u16_to_u32(b1), u16_to_u32(b5));
1309            let c5 = vtrn2q_u32(u16_to_u32(b1), u16_to_u32(b5));
1310            let c2 = vtrn1q_u32(u16_to_u32(b2), u16_to_u32(b6));
1311            let c6 = vtrn2q_u32(u16_to_u32(b2), u16_to_u32(b6));
1312            let c3 = vtrn1q_u32(u16_to_u32(b3), u16_to_u32(b7));
1313            let c7 = vtrn2q_u32(u16_to_u32(b3), u16_to_u32(b7));
1314            let c8 = vtrn1q_u32(u16_to_u32(b8), u16_to_u32(b12));
1315            let c12 = vtrn2q_u32(u16_to_u32(b8), u16_to_u32(b12));
1316            let c9 = vtrn1q_u32(u16_to_u32(b9), u16_to_u32(b13));
1317            let c13 = vtrn2q_u32(u16_to_u32(b9), u16_to_u32(b13));
1318            let c10 = vtrn1q_u32(u16_to_u32(b10), u16_to_u32(b14));
1319            let c14 = vtrn2q_u32(u16_to_u32(b10), u16_to_u32(b14));
1320            let c11 = vtrn1q_u32(u16_to_u32(b11), u16_to_u32(b15));
1321            let c15 = vtrn2q_u32(u16_to_u32(b11), u16_to_u32(b15));
1322
1323            // Phase 4:
1324            // TRN u64, full 16×16 transpose
1325            [
1326                u64_to_u8(vtrn1q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1327                u64_to_u8(vtrn1q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1328                u64_to_u8(vtrn1q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1329                u64_to_u8(vtrn1q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1330                u64_to_u8(vtrn1q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1331                u64_to_u8(vtrn1q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1332                u64_to_u8(vtrn1q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1333                u64_to_u8(vtrn1q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1334                u64_to_u8(vtrn2q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1335                u64_to_u8(vtrn2q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1336                u64_to_u8(vtrn2q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1337                u64_to_u8(vtrn2q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1338                u64_to_u8(vtrn2q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1339                u64_to_u8(vtrn2q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1340                u64_to_u8(vtrn2q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1341                u64_to_u8(vtrn2q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1342            ]
1343        }
1344    }
1345}
1346
1347// ==================================
1348// BLOCK 128 TESTS
1349// ==================================
1350
1351#[cfg(test)]
1352mod tests {
1353    use super::*;
1354    use proptest::prelude::*;
1355    use rand::{RngExt, rng};
1356
1357    // ==================================
1358    // BASIC
1359    // ==================================
1360
1361    #[test]
1362    fn tower_constants() {
1363        // Check that tau is propagated correctly
1364        // For Block128, tau must be (0, 1) from Block64.
1365        let tau128 = Block128::EXTENSION_TAU;
1366        let (lo128, hi128) = tau128.split();
1367        assert_eq!(lo128, Block64::ZERO);
1368        assert_eq!(hi128, Block64::TAU);
1369    }
1370
1371    #[test]
1372    fn add_truth() {
1373        let zero = Block128::ZERO;
1374        let one = Block128::ONE;
1375
1376        assert_eq!(zero + zero, zero);
1377        assert_eq!(zero + one, one);
1378        assert_eq!(one + zero, one);
1379        assert_eq!(one + one, zero);
1380    }
1381
1382    #[test]
1383    fn mul_truth() {
1384        let zero = Block128::ZERO;
1385        let one = Block128::ONE;
1386
1387        assert_eq!(zero * zero, zero);
1388        assert_eq!(zero * one, zero);
1389        assert_eq!(one * one, one);
1390    }
1391
1392    #[test]
1393    fn add() {
1394        // 5 ^ 3 = 6
1395        // 101 ^ 011 = 110
1396        assert_eq!(Block128(5) + Block128(3), Block128(6));
1397    }
1398
1399    #[test]
1400    fn mul_simple() {
1401        // Check for prime numbers (without overflow)
1402        // x^1 * x^1 = x^2 (2 * 2 = 4)
1403        assert_eq!(Block128(2) * Block128(2), Block128(4));
1404    }
1405
1406    #[test]
1407    fn mul_overflow() {
1408        // Reduction verification (AES test vectors)
1409        // Example from the AES specification:
1410        // 0x57 * 0x83 = 0xC1
1411        assert_eq!(Block128(0x57) * Block128(0x83), Block128(0xC1));
1412    }
1413
1414    #[test]
1415    fn karatsuba_correctness() {
1416        // Let's check using Block128 as an example
1417        // Let A = X (hi=1, lo=0)
1418        // Let B = X (hi=1, lo=0)
1419        // A * B = X^2
1420        // According to the rule:
1421        // X^2 = X + tau
1422        // Where tau for Block64 = 0x2000_0000_0000_0000.
1423        // So the result should be:
1424        // hi=1 (X), lo=0x20 (tau)
1425
1426        // Construct X manually
1427        let x = Block128::new(Block64::ZERO, Block64::ONE);
1428        let squared = x * x;
1429
1430        // Verify result via splitting
1431        let (res_lo, res_hi) = squared.split();
1432
1433        assert_eq!(res_hi, Block64::ONE, "X^2 should contain X component");
1434        assert_eq!(
1435            res_lo,
1436            Block64(0x2000_0000_0000_0000),
1437            "X^2 should contain tau component (0x2000_0000_0000_0000)"
1438        );
1439    }
1440
1441    #[test]
1442    fn mul_tau_matches_mul() {
1443        let mut rng = rng();
1444        let edges = [
1445            Block128::ZERO,
1446            Block128::ONE,
1447            Block128::TAU,
1448            Block128(u128::MAX),
1449        ];
1450
1451        let random = (0..1000).map(|_| Block128(rng.random()));
1452
1453        for x in edges.into_iter().chain(random) {
1454            assert_eq!(
1455                x.mul_tau(),
1456                x * Block128::EXTENSION_TAU,
1457                "Block128 mul_tau mismatch at {:#034x}",
1458                x.0
1459            );
1460        }
1461    }
1462
1463    #[test]
1464    fn security_zeroize() {
1465        // Setup sensitive data
1466        let mut secret_val = Block128::from(0xDEAD_BEEF_CAFE_BABE_u128);
1467        assert_ne!(secret_val, Block128::ZERO);
1468
1469        // Nuke it
1470        secret_val.zeroize();
1471
1472        // Verify absolute zero
1473        assert_eq!(secret_val, Block128::ZERO, "Memory was not wiped!");
1474
1475        // Check internal bytes just to be sure
1476        assert_eq!(secret_val.0, 0u128, "Underlying memory leak detected");
1477    }
1478
1479    #[test]
1480    fn invert_zero() {
1481        // Ensure strictly that 0 cannot be inverted.
1482        assert_eq!(
1483            Block128::ZERO.invert(),
1484            Block128::ZERO,
1485            "invert(0) must return 0"
1486        );
1487    }
1488
1489    #[test]
1490    fn inversion_random() {
1491        let mut rng = rng();
1492        for _i in 0..1000 {
1493            let val = Block128(rng.random());
1494
1495            if val != Block128::ZERO {
1496                let inv = val.invert();
1497                let identity = val * inv;
1498
1499                assert_eq!(
1500                    identity,
1501                    Block128::ONE,
1502                    "Inversion identity failed: a * a^-1 != 1"
1503                );
1504            }
1505        }
1506    }
1507
1508    #[test]
1509    fn tower_embedding() {
1510        let mut rng = rng();
1511        for _ in 0..100 {
1512            let a = Block64(rng.random());
1513            let b = Block64(rng.random());
1514
1515            // 1. Structure check: Lifting Block64 -> Block128
1516            let a_lifted: Block128 = a.into();
1517            let (lo, hi) = a_lifted.split();
1518
1519            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
1520            assert_eq!(
1521                hi,
1522                Block64::ZERO,
1523                "Embedding structure failed: high part must be zero"
1524            );
1525
1526            // 2. Addition Homomorphism
1527            let sum_sub = a + b;
1528            let sum_lifted: Block128 = sum_sub.into();
1529            let sum_in_super = Block128::from(a) + Block128::from(b);
1530
1531            assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
1532
1533            // 3. Multiplication Homomorphism
1534            // If I multiply two small numbers inside the big field,
1535            // the result must be the same as multiplying them in the small field
1536            // and then converting.
1537            let prod_sub = a * b;
1538            let prod_lifted: Block128 = prod_sub.into();
1539            let prod_in_super = Block128::from(a) * Block128::from(b);
1540
1541            assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
1542        }
1543    }
1544
1545    // ==================================
1546    // HARDWARE
1547    // ==================================
1548
1549    #[test]
1550    fn isomorphism_roundtrip() {
1551        let mut rng = rng();
1552        for _ in 0..1000 {
1553            let val = Block128(rng.random::<u128>());
1554            assert_eq!(val.to_hardware().to_tower(), val);
1555        }
1556    }
1557
1558    #[cfg(not(feature = "table-math"))]
1559    #[test]
1560    fn map_ct_128_split_is_column_map() {
1561        let mut rng = rng();
1562
1563        for basis in [&TOWER_TO_FLAT_BASIS_128.0, &FLAT_TO_TOWER_BASIS_128.0] {
1564            for (i, &col) in basis.iter().enumerate() {
1565                assert_eq!(map_ct_128_split(1u128 << i, basis), col, "unit vector {i}");
1566            }
1567
1568            for _ in 0..1000 {
1569                let x = rng.random::<u128>();
1570                let expected = basis
1571                    .iter()
1572                    .enumerate()
1573                    .filter(|&(i, _)| (x >> i) & 1 == 1)
1574                    .fold(0, |acc, (_, &col)| acc ^ col);
1575
1576                assert_eq!(map_ct_128_split(x, basis), expected, "x = {x:#x}");
1577            }
1578        }
1579    }
1580
1581    #[test]
1582    fn flat_mul_homomorphism() {
1583        let mut rng = rng();
1584        for _ in 0..1000 {
1585            let a = Block128(rng.random());
1586            let b = Block128(rng.random());
1587
1588            let expected_flat = (a * b).to_hardware();
1589            let actual_flat = a.to_hardware() * b.to_hardware();
1590
1591            assert_eq!(
1592                actual_flat, expected_flat,
1593                "Block128 flat multiplication mismatch: (a*b)^H != a^H * b^H"
1594            );
1595        }
1596    }
1597
1598    #[test]
1599    fn packed_consistency() {
1600        let mut rng = rng();
1601        for _ in 0..100 {
1602            let mut a_vals = [Block128::ZERO; 4];
1603            let mut b_vals = [Block128::ZERO; 4];
1604
1605            for i in 0..4 {
1606                a_vals[i] = Block128(rng.random::<u128>());
1607                b_vals[i] = Block128(rng.random::<u128>());
1608            }
1609
1610            let a_flat_vals = a_vals.map(|x| x.to_hardware());
1611            let b_flat_vals = b_vals.map(|x| x.to_hardware());
1612            let a_packed = Flat::<Block128>::pack(&a_flat_vals);
1613            let b_packed = Flat::<Block128>::pack(&b_flat_vals);
1614
1615            // 1. Test SIMD Add (Check 512-bit / 4-register XOR)
1616            let add_res = Block128::add_hardware_packed(a_packed, b_packed);
1617
1618            let mut add_out = [Block128::ZERO.to_hardware(); 4];
1619            Flat::<Block128>::unpack(add_res, &mut add_out);
1620
1621            for i in 0..4 {
1622                assert_eq!(
1623                    add_out[i],
1624                    (a_vals[i] + b_vals[i]).to_hardware(),
1625                    "Block128 SIMD add mismatch at index {}",
1626                    i
1627                );
1628            }
1629
1630            // 2. Test SIMD Mul (Flat basis)
1631            let mul_res = Block128::mul_hardware_packed(a_packed, b_packed);
1632
1633            let mut mul_out = [Block128::ZERO.to_hardware(); 4];
1634            Flat::<Block128>::unpack(mul_res, &mut mul_out);
1635
1636            for i in 0..4 {
1637                let expected_flat = (a_vals[i] * b_vals[i]).to_hardware();
1638                assert_eq!(
1639                    mul_out[i], expected_flat,
1640                    "Block128 SIMD mul mismatch at index {}",
1641                    i
1642                );
1643            }
1644        }
1645    }
1646
1647    // ==================================
1648    // PACKED
1649    // ==================================
1650
1651    #[test]
1652    fn pack_unpack_roundtrip() {
1653        let mut rng = rng();
1654        let mut data = [Block128::ZERO; PACKED_WIDTH_128];
1655        for v in data.iter_mut() {
1656            *v = Block128(rng.random());
1657        }
1658
1659        let packed = Block128::pack(&data);
1660        let mut unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1661        Block128::unpack(packed, &mut unpacked);
1662        assert_eq!(data, unpacked);
1663    }
1664
1665    #[test]
1666    fn packed_add_consistency() {
1667        let mut rng = rng();
1668        let mut a_vals = [Block128::ZERO; PACKED_WIDTH_128];
1669        let mut b_vals = [Block128::ZERO; PACKED_WIDTH_128];
1670
1671        for i in 0..PACKED_WIDTH_128 {
1672            a_vals[i] = Block128(rng.random());
1673            b_vals[i] = Block128(rng.random());
1674        }
1675
1676        let res_packed = Block128::pack(&a_vals) + Block128::pack(&b_vals);
1677        let mut res_unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1678        Block128::unpack(res_packed, &mut res_unpacked);
1679
1680        for i in 0..PACKED_WIDTH_128 {
1681            assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
1682        }
1683    }
1684
1685    #[test]
1686    fn packed_mul_consistency() {
1687        let mut rng = rng();
1688
1689        for _ in 0..1000 {
1690            // Check 1000 random cases.
1691            // Generate random inputs
1692            let mut a_arr = [Block128::ZERO; PACKED_WIDTH_128];
1693            let mut b_arr = [Block128::ZERO; PACKED_WIDTH_128];
1694
1695            for i in 0..PACKED_WIDTH_128 {
1696                // Generate random u128
1697                let val_a: u128 = rng.random();
1698                let val_b: u128 = rng.random();
1699                a_arr[i] = Block128(val_a);
1700                b_arr[i] = Block128(val_b);
1701            }
1702
1703            let a_packed = PackedBlock128(a_arr);
1704            let b_packed = PackedBlock128(b_arr);
1705
1706            // Perform SIMD multiplication
1707            let c_packed = a_packed * b_packed;
1708
1709            // Verify against Scalar multiplication
1710            let mut c_expected = [Block128::ZERO; PACKED_WIDTH_128];
1711            for i in 0..PACKED_WIDTH_128 {
1712                c_expected[i] = a_arr[i] * b_arr[i];
1713            }
1714
1715            assert_eq!(c_packed.0, c_expected, "SIMD multiplication mismatch!");
1716        }
1717    }
1718
1719    // ==================================
1720    // CT LIFTING BASIS
1721    // ==================================
1722
1723    #[inline(always)]
1724    fn promote_block8_tables(val: Block8) -> Block128 {
1725        // Current (table) lifting: flat/hardware byte -> tower byte -> Block128 flat.
1726        let idx_flat = val.0 as usize;
1727        let tower_byte = unsafe { *constants::FLAT_TO_TOWER_8.get_unchecked(idx_flat) };
1728        let idx_tower = tower_byte as usize;
1729
1730        Block128(unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx_tower) })
1731    }
1732
1733    #[inline(always)]
1734    fn promote_block16_tables(val: Block16) -> Block128 {
1735        let v_flat = val.0;
1736
1737        let mut v_tower = 0u16;
1738        for i in 0..2 {
1739            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1740            let idx = (i * 256) + byte;
1741            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_16.get_unchecked(idx) };
1742        }
1743
1744        let mut res = 0u128;
1745        for i in 0..2 {
1746            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1747            let idx = (i * 256) + byte;
1748            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1749        }
1750
1751        Block128(res)
1752    }
1753
1754    #[inline(always)]
1755    fn promote_block32_tables(val: Block32) -> Block128 {
1756        let v_flat = val.0;
1757
1758        let mut v_tower = 0u32;
1759        for i in 0..4 {
1760            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1761            let idx = (i * 256) + byte;
1762            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_32.get_unchecked(idx) };
1763        }
1764
1765        let mut res = 0u128;
1766        for i in 0..4 {
1767            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1768            let idx = (i * 256) + byte;
1769            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1770        }
1771
1772        Block128(res)
1773    }
1774
1775    #[inline(always)]
1776    fn promote_block64_tables(val: Block64) -> Block128 {
1777        let v_flat = val.0;
1778
1779        let mut v_tower = 0u64;
1780        for i in 0..8 {
1781            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1782            let idx = (i * 256) + byte;
1783            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_64.get_unchecked(idx) };
1784        }
1785
1786        let mut res = 0u128;
1787        for i in 0..8 {
1788            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1789            let idx = (i * 256) + byte;
1790            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1791        }
1792
1793        Block128(res)
1794    }
1795
1796    #[test]
1797    fn lift_from_partial_hardware_matches_tables_block8_exhaustive() {
1798        for x in 0u16..=u8::MAX as u16 {
1799            let v = Block8(x as u8);
1800            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1801            let expected = promote_block8_tables(v);
1802
1803            assert_eq!(got, expected);
1804        }
1805    }
1806
1807    #[test]
1808    fn lift_from_partial_hardware_matches_tables_block16_exhaustive() {
1809        for x in 0..=u16::MAX {
1810            let v = Block16(x);
1811            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1812            let expected = promote_block16_tables(v);
1813
1814            assert_eq!(got, expected);
1815        }
1816    }
1817
1818    #[test]
1819    fn lift_from_partial_hardware_matches_tables_block32_random() {
1820        let mut rng = rng();
1821        for _ in 0..10_000 {
1822            let v = Block32(rng.random::<u32>());
1823            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1824            let expected = promote_block32_tables(v);
1825
1826            assert_eq!(got, expected);
1827        }
1828    }
1829
1830    #[test]
1831    fn lift_from_partial_hardware_matches_tables_block64_random() {
1832        let mut rng = rng();
1833        for _ in 0..10_000 {
1834            let v = Block64(rng.random::<u64>());
1835            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1836            let expected = promote_block64_tables(v);
1837
1838            assert_eq!(got, expected);
1839        }
1840    }
1841
1842    // ==================================
1843    // PROMOTE BATCH + EDGE CASES
1844    // ==================================
1845
1846    #[test]
1847    fn promote_flat_batch_matches_scalar_block8() {
1848        let mut rng = rng();
1849        let input: Vec<Flat<Block8>> = (0..64)
1850            .map(|_| Block8(rng.random::<u8>()).to_hardware())
1851            .collect();
1852
1853        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 64];
1854        Block128::promote_flat_batch(&input, &mut batch_out);
1855
1856        for (i, &v) in input.iter().enumerate() {
1857            let scalar = Block128::promote_flat(v);
1858            assert_eq!(batch_out[i], scalar, "batch/scalar mismatch at index {}", i);
1859        }
1860    }
1861
1862    #[test]
1863    fn promote_flat_batch_matches_scalar_block16() {
1864        let mut rng = rng();
1865        let input: Vec<Flat<Block16>> = (0..32)
1866            .map(|_| Block16(rng.random::<u16>()).to_hardware())
1867            .collect();
1868
1869        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 32];
1870        Block128::promote_flat_batch(&input, &mut batch_out);
1871
1872        for (i, &v) in input.iter().enumerate() {
1873            assert_eq!(
1874                batch_out[i],
1875                Block128::promote_flat(v),
1876                "batch/scalar mismatch at index {}",
1877                i
1878            );
1879        }
1880    }
1881
1882    #[test]
1883    fn promote_flat_batch_matches_scalar_block32() {
1884        let mut rng = rng();
1885        let input: Vec<Flat<Block32>> = (0..16)
1886            .map(|_| Block32(rng.random::<u32>()).to_hardware())
1887            .collect();
1888
1889        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 16];
1890        Block128::promote_flat_batch(&input, &mut batch_out);
1891
1892        for (i, &v) in input.iter().enumerate() {
1893            assert_eq!(
1894                batch_out[i],
1895                Block128::promote_flat(v),
1896                "batch/scalar mismatch at index {}",
1897                i
1898            );
1899        }
1900    }
1901
1902    #[test]
1903    fn promote_flat_batch_matches_scalar_block64() {
1904        let mut rng = rng();
1905        let input: Vec<Flat<Block64>> = (0..40)
1906            .map(|_| Block64(rng.random::<u64>()).to_hardware())
1907            .collect();
1908
1909        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 40];
1910        Block128::promote_flat_batch(&input, &mut batch_out);
1911
1912        for (i, &v) in input.iter().enumerate() {
1913            assert_eq!(
1914                batch_out[i],
1915                Block128::promote_flat(v),
1916                "batch/scalar mismatch at index {}",
1917                i
1918            );
1919        }
1920    }
1921
1922    #[test]
1923    fn promote_flat_batch_partial_slice() {
1924        let input: Vec<Flat<Block8>> = (0..16).map(|i| Block8(i as u8).to_hardware()).collect();
1925
1926        // Output shorter than input
1927        let mut out_short = vec![Flat::from_raw(Block128::ZERO); 5];
1928        Block128::promote_flat_batch(&input, &mut out_short);
1929
1930        for i in 0..5 {
1931            assert_eq!(out_short[i], Block128::promote_flat(input[i]));
1932        }
1933
1934        // Input shorter than output
1935        let short_input = &input[..3];
1936        let mut out_long = vec![Flat::from_raw(Block128::ZERO); 10];
1937
1938        Block128::promote_flat_batch(short_input, &mut out_long);
1939
1940        for i in 0..3 {
1941            assert_eq!(out_long[i], Block128::promote_flat(short_input[i]));
1942        }
1943
1944        // Elements beyond input length untouched
1945        for val in &out_long[3..10] {
1946            assert_eq!(*val, Flat::from_raw(Block128::ZERO));
1947        }
1948    }
1949
1950    #[test]
1951    fn promote_edge_zero() {
1952        let zero = Flat::from_raw(Block8(0));
1953        let promoted = Block128::promote_flat(zero);
1954
1955        assert_eq!(
1956            promoted,
1957            Flat::from_raw(Block128::ZERO),
1958            "promote(0) must be 0"
1959        );
1960
1961        // Batch:
1962        // all-zero input
1963        let input = vec![zero; 16];
1964        let mut output = vec![Flat::from_raw(Block128(0xDEAD)); 16];
1965
1966        Block128::promote_flat_batch(&input, &mut output);
1967
1968        for o in &output {
1969            assert_eq!(*o, Flat::from_raw(Block128::ZERO));
1970        }
1971    }
1972
1973    #[test]
1974    fn promote_edge_one() {
1975        let one_flat8 = Block8::ONE.to_hardware();
1976        let one_flat128 = Block128::ONE.to_hardware();
1977
1978        assert_eq!(
1979            Block128::promote_flat(one_flat8),
1980            one_flat128,
1981            "promote(1) must equal 1 in target field"
1982        );
1983    }
1984
1985    #[test]
1986    fn promote_edge_max_block8() {
1987        let max = Flat::from_raw(Block8(0xFF));
1988        let promoted = Block128::promote_flat(max);
1989
1990        // Must not be zero
1991        assert_ne!(promoted, Flat::from_raw(Block128::ZERO));
1992
1993        // Roundtrip through tower
1994        // must preserve embedding.
1995        let tower_8 = max.to_tower();
1996        let tower_128 = Block128::from(tower_8);
1997
1998        assert_eq!(promoted.to_tower(), tower_128);
1999    }
2000
2001    #[test]
2002    fn promote_edge_single_bits() {
2003        for bit in 0..8 {
2004            let val = Flat::from_raw(Block8(1u8 << bit));
2005            let promoted = Block128::promote_flat(val);
2006
2007            // Must not be zero
2008            assert_ne!(
2009                promoted,
2010                Flat::from_raw(Block128::ZERO),
2011                "single-bit {} promoted to zero",
2012                bit
2013            );
2014
2015            // Tower roundtrip
2016            let tower_8 = val.to_tower();
2017            let tower_128 = Block128::from(tower_8);
2018
2019            assert_eq!(
2020                promoted.to_tower(),
2021                tower_128,
2022                "tower roundtrip failed for bit {}",
2023                bit
2024            );
2025        }
2026    }
2027
2028    #[test]
2029    fn promote_edge_alternating_packed() {
2030        let input: Vec<Flat<Block8>> = (0..16)
2031            .map(|i| {
2032                if i % 2 == 0 {
2033                    Flat::from_raw(Block8(0x00))
2034                } else {
2035                    Flat::from_raw(Block8(0xFF))
2036                }
2037            })
2038            .collect();
2039
2040        let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
2041        Block128::promote_flat_batch(&input, &mut output);
2042
2043        for (i, &v) in input.iter().enumerate() {
2044            assert_eq!(
2045                output[i],
2046                Block128::promote_flat(v),
2047                "alternating mismatch at {}",
2048                i
2049            );
2050        }
2051    }
2052
2053    #[test]
2054    fn promote_edge_all_same_packed() {
2055        let val = Flat::from_raw(Block8(0x42));
2056        let expected = Block128::promote_flat(val);
2057
2058        let input = vec![val; 16];
2059        let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
2060
2061        Block128::promote_flat_batch(&input, &mut output);
2062
2063        for (i, o) in output.iter().enumerate() {
2064            assert_eq!(*o, expected, "all-same mismatch at {}", i);
2065        }
2066    }
2067
2068    #[test]
2069    fn promote_tower_roundtrip_block8() {
2070        for x in 0u16..=u8::MAX as u16 {
2071            let b8 = Block8(x as u8);
2072            let promoted = Block128::promote_flat(b8.to_hardware());
2073            let tower_128 = promoted.to_tower();
2074            let embedded = Block128::from(b8);
2075
2076            assert_eq!(
2077                tower_128, embedded,
2078                "tower roundtrip failed for Block8({})",
2079                x
2080            );
2081        }
2082    }
2083
2084    #[test]
2085    fn promote_tower_roundtrip_block16() {
2086        let mut rng = rng();
2087        for _ in 0..10_000 {
2088            let v = Block16(rng.random::<u16>());
2089            let promoted = Block128::promote_flat(v.to_hardware());
2090            let tower_128 = promoted.to_tower();
2091            let embedded = Block128::from(v);
2092
2093            assert_eq!(
2094                tower_128, embedded,
2095                "tower roundtrip failed for Block16({})",
2096                v.0
2097            );
2098        }
2099    }
2100
2101    #[test]
2102    fn promote_tower_roundtrip_block32() {
2103        let mut rng = rng();
2104        for _ in 0..10_000 {
2105            let v = Block32(rng.random::<u32>());
2106            let promoted = Block128::promote_flat(v.to_hardware());
2107            let tower_128 = promoted.to_tower();
2108            let embedded = Block128::from(v);
2109
2110            assert_eq!(
2111                tower_128, embedded,
2112                "tower roundtrip failed for Block32({})",
2113                v.0
2114            );
2115        }
2116    }
2117
2118    #[test]
2119    fn promote_tower_roundtrip_block64() {
2120        let mut rng = rng();
2121        for _ in 0..10_000 {
2122            let v = Block64(rng.random::<u64>());
2123            let promoted = Block128::promote_flat(v.to_hardware());
2124            let tower_128 = promoted.to_tower();
2125            let embedded = Block128::from(v);
2126
2127            assert_eq!(
2128                tower_128, embedded,
2129                "tower roundtrip failed for Block64({})",
2130                v.0
2131            );
2132        }
2133    }
2134
2135    #[test]
2136    fn promote_algebraic_homomorphism_add_block8() {
2137        let mut rng = rng();
2138        for _ in 0..1000 {
2139            let a = Block8(rng.random::<u8>());
2140            let b = Block8(rng.random::<u8>());
2141
2142            let promote_a = Block128::promote_flat(a.to_hardware());
2143            let promote_b = Block128::promote_flat(b.to_hardware());
2144            let promote_sum = Block128::promote_flat((a + b).to_hardware());
2145
2146            assert_eq!(
2147                promote_a + promote_b,
2148                promote_sum,
2149                "add homomorphism: promote(a)+promote(b) != promote(a+b)"
2150            );
2151        }
2152    }
2153
2154    #[test]
2155    fn promote_algebraic_homomorphism_mul_block8() {
2156        let mut rng = rng();
2157        for _ in 0..1000 {
2158            let a = Block8(rng.random::<u8>());
2159            let b = Block8(rng.random::<u8>());
2160
2161            let promote_a = Block128::promote_flat(a.to_hardware());
2162            let promote_b = Block128::promote_flat(b.to_hardware());
2163            let promote_prod = Block128::promote_flat((a * b).to_hardware());
2164
2165            // Subfield elements: promote then multiply
2166            // must equal multiply then promote.
2167            assert_eq!(
2168                promote_a * promote_b,
2169                promote_prod,
2170                "mul homomorphism: promote(a)*promote(b) != promote(a*b)"
2171            );
2172        }
2173    }
2174
2175    #[test]
2176    fn promote_algebraic_homomorphism_add_block16() {
2177        let mut rng = rng();
2178        for _ in 0..1000 {
2179            let a = Block16(rng.random::<u16>());
2180            let b = Block16(rng.random::<u16>());
2181
2182            let pa = Block128::promote_flat(a.to_hardware());
2183            let pb = Block128::promote_flat(b.to_hardware());
2184            let p_sum = Block128::promote_flat((a + b).to_hardware());
2185
2186            assert_eq!(pa + pb, p_sum, "Block16 add homomorphism failed");
2187        }
2188    }
2189
2190    #[test]
2191    fn promote_algebraic_homomorphism_mul_block16() {
2192        let mut rng = rng();
2193        for _ in 0..1000 {
2194            let a = Block16(rng.random::<u16>());
2195            let b = Block16(rng.random::<u16>());
2196
2197            let pa = Block128::promote_flat(a.to_hardware());
2198            let pb = Block128::promote_flat(b.to_hardware());
2199            let p_prod = Block128::promote_flat((a * b).to_hardware());
2200
2201            assert_eq!(pa * pb, p_prod, "Block16 mul homomorphism failed");
2202        }
2203    }
2204
2205    #[test]
2206    fn promote_algebraic_homomorphism_add_block32() {
2207        let mut rng = rng();
2208        for _ in 0..1000 {
2209            let a = Block32(rng.random::<u32>());
2210            let b = Block32(rng.random::<u32>());
2211
2212            let pa = Block128::promote_flat(a.to_hardware());
2213            let pb = Block128::promote_flat(b.to_hardware());
2214            let p_sum = Block128::promote_flat((a + b).to_hardware());
2215
2216            assert_eq!(pa + pb, p_sum, "Block32 add homomorphism failed");
2217        }
2218    }
2219
2220    #[test]
2221    fn promote_algebraic_homomorphism_mul_block32() {
2222        let mut rng = rng();
2223        for _ in 0..1000 {
2224            let a = Block32(rng.random::<u32>());
2225            let b = Block32(rng.random::<u32>());
2226
2227            let pa = Block128::promote_flat(a.to_hardware());
2228            let pb = Block128::promote_flat(b.to_hardware());
2229            let p_prod = Block128::promote_flat((a * b).to_hardware());
2230
2231            assert_eq!(pa * pb, p_prod, "Block32 mul homomorphism failed");
2232        }
2233    }
2234
2235    #[test]
2236    fn promote_algebraic_homomorphism_add_block64() {
2237        let mut rng = rng();
2238        for _ in 0..1000 {
2239            let a = Block64(rng.random::<u64>());
2240            let b = Block64(rng.random::<u64>());
2241
2242            let pa = Block128::promote_flat(a.to_hardware());
2243            let pb = Block128::promote_flat(b.to_hardware());
2244            let p_sum = Block128::promote_flat((a + b).to_hardware());
2245
2246            assert_eq!(pa + pb, p_sum, "Block64 add homomorphism failed");
2247        }
2248    }
2249
2250    #[test]
2251    fn promote_algebraic_homomorphism_mul_block64() {
2252        let mut rng = rng();
2253        for _ in 0..1000 {
2254            let a = Block64(rng.random::<u64>());
2255            let b = Block64(rng.random::<u64>());
2256
2257            let pa = Block128::promote_flat(a.to_hardware());
2258            let pb = Block128::promote_flat(b.to_hardware());
2259            let p_prod = Block128::promote_flat((a * b).to_hardware());
2260
2261            assert_eq!(pa * pb, p_prod, "Block64 mul homomorphism failed");
2262        }
2263    }
2264
2265    #[test]
2266    fn promote_generator_preserves_order() {
2267        // Block8 generator is 3 with order 255
2268        let g = Block8(3);
2269        let g_promoted = Block128::promote_flat(g.to_hardware());
2270
2271        // Fermat:
2272        // g^255 = 1 in GF(2^8)
2273        let mut acc8 = Block8::ONE;
2274        for _ in 0..255 {
2275            acc8 *= g;
2276        }
2277
2278        assert_eq!(acc8, Block8::ONE, "Block8 Fermat: g^255 must be 1");
2279
2280        // Promoted element must
2281        // also satisfy g^255 = 1.
2282        let mut acc128 = Flat::from_raw(Block128::ONE);
2283        for _ in 0..255 {
2284            acc128 *= g_promoted;
2285        }
2286
2287        assert_eq!(
2288            acc128,
2289            Flat::from_raw(Block128::ONE),
2290            "promoted element lost multiplicative order"
2291        );
2292    }
2293
2294    proptest! {
2295        #[test]
2296        fn parity_masks_match_from_hardware(x_flat in any::<u128>()) {
2297            let tower = Block128::from_hardware(Flat::from_raw(Block128(x_flat))).0;
2298
2299            for k in 0..128 {
2300                let bit = ((tower >> k) & 1) as u8;
2301                let via_api = Flat::from_raw(Block128(x_flat)).tower_bit(k);
2302
2303                prop_assert_eq!(
2304                    via_api, bit,
2305                    "Block128 tower_bit_from_hardware mismatch at x_flat={:#034x}, bit_idx={}",
2306                    x_flat, k
2307                );
2308            }
2309        }
2310    }
2311}