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