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(pmull)]
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(pmull))]
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        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    fn promote_flat_batch(input: &[Flat<Block8>], output: &mut [Flat<Self>]) {
574        let n = input.len().min(output.len());
575
576        #[cfg(target_arch = "aarch64")]
577        {
578            let full = n / 16;
579            for chunk in 0..full {
580                let i = chunk * 16;
581                unsafe {
582                    neon::promote_batch_8_to_128(
583                        input.as_ptr().add(i).cast::<u8>(),
584                        output.as_mut_ptr().add(i).cast::<u128>(),
585                    );
586                }
587            }
588
589            let tail = full * 16;
590            for i in tail..n {
591                output[i] = Self::promote_flat(input[i]);
592            }
593        }
594
595        #[cfg(not(target_arch = "aarch64"))]
596        {
597            for i in 0..n {
598                output[i] = Self::promote_flat(input[i]);
599            }
600        }
601    }
602}
603
604#[cfg(not(feature = "table-math"))]
605impl FlatPromote<Block16> for Block128 {
606    #[inline(always)]
607    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
608        Flat::from_raw(Block128(lift_ct::<16>(
609            val.into_raw().0 as u64,
610            &constants::LIFT_BASIS_16.0,
611        )))
612    }
613
614    fn promote_flat_batch(input: &[Flat<Block16>], output: &mut [Flat<Self>]) {
615        let n = input.len().min(output.len());
616
617        #[cfg(target_arch = "aarch64")]
618        {
619            let full = n / 16;
620            for chunk in 0..full {
621                let i = chunk * 16;
622                unsafe {
623                    neon::promote_batch_16_to_128(
624                        input.as_ptr().add(i).cast::<u8>(),
625                        output.as_mut_ptr().add(i).cast::<u128>(),
626                    );
627                }
628            }
629
630            let tail = full * 16;
631            for i in tail..n {
632                output[i] = Self::promote_flat(input[i]);
633            }
634        }
635
636        #[cfg(not(target_arch = "aarch64"))]
637        {
638            for i in 0..n {
639                output[i] = Self::promote_flat(input[i]);
640            }
641        }
642    }
643}
644
645#[cfg(not(feature = "table-math"))]
646impl FlatPromote<Block32> for Block128 {
647    #[inline(always)]
648    fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
649        Flat::from_raw(Block128(lift_ct::<32>(
650            val.into_raw().0 as u64,
651            &constants::LIFT_BASIS_32.0,
652        )))
653    }
654
655    fn promote_flat_batch(input: &[Flat<Block32>], output: &mut [Flat<Self>]) {
656        let n = input.len().min(output.len());
657
658        #[cfg(target_arch = "aarch64")]
659        {
660            let full = n / 16;
661            for chunk in 0..full {
662                let i = chunk * 16;
663                unsafe {
664                    neon::promote_batch_32_to_128(
665                        input.as_ptr().add(i).cast::<u8>(),
666                        output.as_mut_ptr().add(i).cast::<u128>(),
667                    );
668                }
669            }
670
671            let tail = full * 16;
672            for i in tail..n {
673                output[i] = Self::promote_flat(input[i]);
674            }
675        }
676
677        #[cfg(not(target_arch = "aarch64"))]
678        {
679            for i in 0..n {
680                output[i] = Self::promote_flat(input[i]);
681            }
682        }
683    }
684}
685
686#[cfg(not(feature = "table-math"))]
687impl FlatPromote<Block64> for Block128 {
688    #[inline(always)]
689    fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
690        Flat::from_raw(Block128(lift_ct::<64>(
691            val.into_raw().0,
692            &constants::LIFT_BASIS_64.0,
693        )))
694    }
695}
696
697// Insecure (secret-dependent table indexing) lifting path
698#[cfg(feature = "table-math")]
699impl FlatPromote<Block8> for Block128 {
700    #[inline(always)]
701    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
702        let idx = val.into_raw().0 as usize;
703        Flat::from_raw(Block128(unsafe {
704            *constants::LIFT_TABLE_8_TO_128.get_unchecked(idx)
705        }))
706    }
707}
708
709#[cfg(feature = "table-math")]
710impl FlatPromote<Block16> for Block128 {
711    #[inline(always)]
712    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
713        let v = val.into_raw().0;
714        let res = unsafe {
715            *constants::PROMOTE_16_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
716                ^ *constants::PROMOTE_16_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
717        };
718
719        Flat::from_raw(Block128(res))
720    }
721}
722
723#[cfg(feature = "table-math")]
724impl FlatPromote<Block32> for Block128 {
725    #[inline(always)]
726    fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
727        let v = val.into_raw().0;
728        let res = unsafe {
729            *constants::PROMOTE_32_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
730                ^ *constants::PROMOTE_32_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
731                ^ *constants::PROMOTE_32_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
732                ^ *constants::PROMOTE_32_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
733        };
734
735        Flat::from_raw(Block128(res))
736    }
737}
738
739#[cfg(feature = "table-math")]
740impl FlatPromote<Block64> for Block128 {
741    #[inline(always)]
742    fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
743        let v = val.into_raw().0;
744        let res = unsafe {
745            *constants::PROMOTE_64_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
746                ^ *constants::PROMOTE_64_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
747                ^ *constants::PROMOTE_64_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
748                ^ *constants::PROMOTE_64_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
749                ^ *constants::PROMOTE_64_BYTE_4_TO_128.get_unchecked(((v >> 32) & 0xFF) as usize)
750                ^ *constants::PROMOTE_64_BYTE_5_TO_128.get_unchecked(((v >> 40) & 0xFF) as usize)
751                ^ *constants::PROMOTE_64_BYTE_6_TO_128.get_unchecked(((v >> 48) & 0xFF) as usize)
752                ^ *constants::PROMOTE_64_BYTE_7_TO_128.get_unchecked(((v >> 56) & 0xFF) as usize)
753        };
754
755        Flat::from_raw(Block128(res))
756    }
757}
758
759// ===========================================
760// Binary Field Extras
761// ===========================================
762
763impl_binary_field_extras!(
764    Block128,
765    Block64,
766    map_ct_128_split,
767    TRACE_MASK_128,
768    SOLVE_QUADRATIC_BASIS_128
769);
770
771// ===========================================
772// UTILS
773// ===========================================
774
775#[cfg(feature = "table-math")]
776#[inline(always)]
777pub fn apply_matrix_128(val: Block128, table: &[u128; 4096]) -> Block128 {
778    let mut res = 0u128;
779    let v = val.0;
780
781    // [!] The Ghost isn't in the Shell @_@
782
783    // 16 lookups (8-bit window)
784    for i in 0..16 {
785        let byte = (v >> (i * 8)) & 0xFF;
786        let idx = (i * 256) + (byte as usize);
787        res ^= unsafe { *table.get_unchecked(idx) };
788    }
789
790    Block128(res)
791}
792
793#[inline(always)]
794fn map_ct_128_split(x: u128, basis: &[u128; 128]) -> u128 {
795    let mut acc_lo = 0u64;
796    let mut acc_hi = 0u64;
797    let mut i = 0usize;
798
799    while i < 128 {
800        let bit = ((x >> i) & 1) as u64;
801        let mask = 0u64.wrapping_sub(bit);
802
803        let b = basis[i];
804        acc_lo ^= (b as u64) & mask;
805        acc_hi ^= ((b >> 64) as u64) & mask;
806
807        i += 1;
808    }
809
810    (acc_lo as u128) | ((acc_hi as u128) << 64)
811}
812
813#[cfg(not(feature = "table-math"))]
814#[inline(always)]
815fn lift_ct<const N: usize>(x: u64, basis: &'static [u128; N]) -> u128 {
816    let mut acc = 0u128;
817    let mut i = 0usize;
818
819    while i < N {
820        let bit = (x >> i) & 1;
821        let mask = 0u128.wrapping_sub(bit as u128);
822        acc ^= basis[i] & mask;
823        i += 1;
824    }
825
826    acc
827}
828
829// ===========================================
830// 128-BIT SIMD INSTRUCTIONS
831// ===========================================
832
833#[cfg(target_arch = "aarch64")]
834mod neon {
835    use super::*;
836    use core::arch::aarch64::*;
837    use core::mem::transmute;
838
839    const _: () = assert!(constants::POLY_128 == 0x87, "verus twins hardcode R = 0x87");
840
841    #[inline(always)]
842    pub fn add_packed_128(lhs: PackedBlock128, rhs: PackedBlock128) -> PackedBlock128 {
843        unsafe {
844            // Block128 is packed into 4 elements
845            // (512 bits), work with 4 registers.
846            let l: [uint8x16_t; 4] = transmute(lhs.0);
847            let r: [uint8x16_t; 4] = transmute(rhs.0);
848
849            let res = [
850                veorq_u8(l[0], r[0]),
851                veorq_u8(l[1], r[1]),
852                veorq_u8(l[2], r[2]),
853                veorq_u8(l[3], r[3]),
854            ];
855
856            transmute(res)
857        }
858    }
859
860    #[inline(always)]
861    pub fn add_flat_128(a: Block128, b: Block128) -> Block128 {
862        unsafe {
863            // veor, not u128 ^:
864            // stays in the mul kernel's register file.
865            let l: uint8x16_t = transmute(a.0);
866            let r: uint8x16_t = transmute(b.0);
867
868            Block128(transmute::<uint8x16_t, u128>(veorq_u8(l, r)))
869        }
870    }
871
872    #[cfg(pmull)]
873    #[inline(always)]
874    pub fn mul_flat_128(a: Block128, b: Block128) -> Block128 {
875        unsafe {
876            let a8: uint8x16_t = transmute(a.0);
877            let b8: uint8x16_t = transmute(b.0);
878            let ap: poly64x2_t = transmute(a8);
879            let bp: poly64x2_t = transmute(b8);
880
881            // Schoolbook, not Karatsuba:
882            // issue width binds, not PMULL count.
883            let bs: poly64x2_t = transmute(vextq_u8::<8>(b8, b8));
884
885            let d0: uint8x16_t =
886                transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bp)));
887            let d2: uint8x16_t = transmute(vmull_high_p64(ap, bp));
888            let x0: uint8x16_t =
889                transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bs)));
890            let x1: uint8x16_t = transmute(vmull_high_p64(ap, bs));
891
892            let mid = veorq_u8(x0, x1);
893
894            // 256-bit product limbs:
895            // lo = [c0, c1], hi = [c2, c3].
896            let zero = vdupq_n_u8(0);
897            let lo = veorq_u8(d0, vextq_u8::<8>(zero, mid));
898            let hi = veorq_u8(d2, vextq_u8::<8>(mid, zero));
899
900            // Reduction P(x) = x^128 + R(x), R(x) = 0x87;
901            // vdupq_n_p64, not [c; 2]: the repeat
902            // form lowers to a memcpy libcall.
903            let rv: poly64x2_t = vdupq_n_p64(constants::POLY_128 as u64);
904            let hip: poly64x2_t = transmute(hi);
905
906            let p0: uint8x16_t =
907                transmute(vmull_p64(vgetq_lane_p64::<0>(hip), vgetq_lane_p64::<0>(rv)));
908            let p1: uint8x16_t = transmute(vmull_high_p64(hip, rv));
909            let p1p: poly64x2_t = transmute(p1);
910
911            let fold = veorq_u8(p0, vextq_u8::<8>(zero, p1));
912
913            // carry = hi lane of p1 < 2^7;
914            // deg(carry·R) < 14: fits the low lane, no third fold.
915            let carry_mul: uint8x16_t = transmute(vmull_high_p64(p1p, rv));
916
917            let res = veorq_u8(veorq_u8(lo, fold), carry_mul);
918
919            Block128(transmute::<uint8x16_t, u128>(res))
920        }
921    }
922
923    /// CT packed promote:
924    /// 16 × Block8 → 16 × Block128 via nibble decomposition.
925    #[cfg(not(feature = "table-math"))]
926    #[inline(always)]
927    pub unsafe fn promote_batch_8_to_128(input: *const u8, output: *mut u128) {
928        unsafe {
929            let vals = vld1q_u8(input);
930
931            let mask_0f = vdupq_n_u8(0x0F);
932            let lo_nib = vandq_u8(vals, mask_0f);
933            let hi_nib = vshrq_n_u8::<4>(vals);
934
935            let mut out = [vdupq_n_u8(0); 16];
936
937            macro_rules! lookup {
938                ($j:expr, $lo:ident, $hi:ident, $dst:ident) => {{
939                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_8_0_TO_128[$j].as_ptr());
940                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_8_1_TO_128[$j].as_ptr());
941
942                    $dst[$j] = veorq_u8(vqtbl1q_u8(t0, $lo), vqtbl1q_u8(t1, $hi));
943                }};
944            }
945
946            lookup!(0, lo_nib, hi_nib, out);
947            lookup!(1, lo_nib, hi_nib, out);
948            lookup!(2, lo_nib, hi_nib, out);
949            lookup!(3, lo_nib, hi_nib, out);
950            lookup!(4, lo_nib, hi_nib, out);
951            lookup!(5, lo_nib, hi_nib, out);
952            lookup!(6, lo_nib, hi_nib, out);
953            lookup!(7, lo_nib, hi_nib, out);
954            lookup!(8, lo_nib, hi_nib, out);
955            lookup!(9, lo_nib, hi_nib, out);
956            lookup!(10, lo_nib, hi_nib, out);
957            lookup!(11, lo_nib, hi_nib, out);
958            lookup!(12, lo_nib, hi_nib, out);
959            lookup!(13, lo_nib, hi_nib, out);
960            lookup!(14, lo_nib, hi_nib, out);
961            lookup!(15, lo_nib, hi_nib, out);
962
963            let elems = transpose_16x16(&out);
964            for (i, elem) in elems.iter().enumerate() {
965                vst1q_u8(output.add(i).cast::<u8>(), *elem);
966            }
967        }
968    }
969
970    /// CT packed promote:
971    /// 16 × Block16 → 16 × Block128 via nibble decomposition.
972    #[cfg(not(feature = "table-math"))]
973    #[inline(always)]
974    pub unsafe fn promote_batch_16_to_128(input: *const u8, output: *mut u128) {
975        unsafe {
976            let raw0 = vld1q_u8(input);
977            let raw1 = vld1q_u8(input.add(16));
978
979            let lo_bytes = vuzp1q_u8(raw0, raw1);
980            let hi_bytes = vuzp2q_u8(raw0, raw1);
981
982            let mask_0f = vdupq_n_u8(0x0F);
983            let n0 = vandq_u8(lo_bytes, mask_0f);
984            let n1 = vshrq_n_u8::<4>(lo_bytes);
985            let n2 = vandq_u8(hi_bytes, mask_0f);
986            let n3 = vshrq_n_u8::<4>(hi_bytes);
987
988            let mut out = [vdupq_n_u8(0); 16];
989
990            macro_rules! lookup {
991                ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident, $dst:ident) => {{
992                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_16_0_TO_128[$j].as_ptr());
993                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_16_1_TO_128[$j].as_ptr());
994                    let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_16_2_TO_128[$j].as_ptr());
995                    let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_16_3_TO_128[$j].as_ptr());
996
997                    $dst[$j] = veorq_u8(
998                        veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
999                        veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1000                    );
1001                }};
1002            }
1003
1004            lookup!(0, n0, n1, n2, n3, out);
1005            lookup!(1, n0, n1, n2, n3, out);
1006            lookup!(2, n0, n1, n2, n3, out);
1007            lookup!(3, n0, n1, n2, n3, out);
1008            lookup!(4, n0, n1, n2, n3, out);
1009            lookup!(5, n0, n1, n2, n3, out);
1010            lookup!(6, n0, n1, n2, n3, out);
1011            lookup!(7, n0, n1, n2, n3, out);
1012            lookup!(8, n0, n1, n2, n3, out);
1013            lookup!(9, n0, n1, n2, n3, out);
1014            lookup!(10, n0, n1, n2, n3, out);
1015            lookup!(11, n0, n1, n2, n3, out);
1016            lookup!(12, n0, n1, n2, n3, out);
1017            lookup!(13, n0, n1, n2, n3, out);
1018            lookup!(14, n0, n1, n2, n3, out);
1019            lookup!(15, n0, n1, n2, n3, out);
1020
1021            let elems = transpose_16x16(&out);
1022            for (i, elem) in elems.iter().enumerate() {
1023                vst1q_u8(output.add(i).cast::<u8>(), *elem);
1024            }
1025        }
1026    }
1027
1028    /// CT packed promote:
1029    /// 16 × Block32 → 16 × Block128 via nibble decomposition.
1030    #[cfg(not(feature = "table-math"))]
1031    #[inline(always)]
1032    pub unsafe fn promote_batch_32_to_128(input: *const u8, output: *mut u128) {
1033        unsafe {
1034            let raw0 = vld1q_u8(input);
1035            let raw1 = vld1q_u8(input.add(16));
1036            let raw2 = vld1q_u8(input.add(32));
1037            let raw3 = vld1q_u8(input.add(48));
1038
1039            let a02 = vuzp1q_u8(raw0, raw1);
1040            let a13 = vuzp2q_u8(raw0, raw1);
1041            let b02 = vuzp1q_u8(raw2, raw3);
1042            let b13 = vuzp2q_u8(raw2, raw3);
1043
1044            let byte0 = vuzp1q_u8(a02, b02);
1045            let byte2 = vuzp2q_u8(a02, b02);
1046            let byte1 = vuzp1q_u8(a13, b13);
1047            let byte3 = vuzp2q_u8(a13, b13);
1048
1049            let mask_0f = vdupq_n_u8(0x0F);
1050            let n0 = vandq_u8(byte0, mask_0f);
1051            let n1 = vshrq_n_u8::<4>(byte0);
1052            let n2 = vandq_u8(byte1, mask_0f);
1053            let n3 = vshrq_n_u8::<4>(byte1);
1054            let n4 = vandq_u8(byte2, mask_0f);
1055            let n5 = vshrq_n_u8::<4>(byte2);
1056            let n6 = vandq_u8(byte3, mask_0f);
1057            let n7 = vshrq_n_u8::<4>(byte3);
1058
1059            let mut out = [vdupq_n_u8(0); 16];
1060
1061            macro_rules! lookup {
1062                ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident,
1063                 $n4:ident, $n5:ident, $n6:ident, $n7:ident, $dst:ident) => {{
1064                    let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_32_0_TO_128[$j].as_ptr());
1065                    let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_32_1_TO_128[$j].as_ptr());
1066                    let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_32_2_TO_128[$j].as_ptr());
1067                    let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_32_3_TO_128[$j].as_ptr());
1068                    let t4 = vld1q_u8(constants::NIBBLE_PROMOTE_32_4_TO_128[$j].as_ptr());
1069                    let t5 = vld1q_u8(constants::NIBBLE_PROMOTE_32_5_TO_128[$j].as_ptr());
1070                    let t6 = vld1q_u8(constants::NIBBLE_PROMOTE_32_6_TO_128[$j].as_ptr());
1071                    let t7 = vld1q_u8(constants::NIBBLE_PROMOTE_32_7_TO_128[$j].as_ptr());
1072
1073                    $dst[$j] = veorq_u8(
1074                        veorq_u8(
1075                            veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
1076                            veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1077                        ),
1078                        veorq_u8(
1079                            veorq_u8(vqtbl1q_u8(t4, $n4), vqtbl1q_u8(t5, $n5)),
1080                            veorq_u8(vqtbl1q_u8(t6, $n6), vqtbl1q_u8(t7, $n7)),
1081                        ),
1082                    );
1083                }};
1084            }
1085
1086            lookup!(0, n0, n1, n2, n3, n4, n5, n6, n7, out);
1087            lookup!(1, n0, n1, n2, n3, n4, n5, n6, n7, out);
1088            lookup!(2, n0, n1, n2, n3, n4, n5, n6, n7, out);
1089            lookup!(3, n0, n1, n2, n3, n4, n5, n6, n7, out);
1090            lookup!(4, n0, n1, n2, n3, n4, n5, n6, n7, out);
1091            lookup!(5, n0, n1, n2, n3, n4, n5, n6, n7, out);
1092            lookup!(6, n0, n1, n2, n3, n4, n5, n6, n7, out);
1093            lookup!(7, n0, n1, n2, n3, n4, n5, n6, n7, out);
1094            lookup!(8, n0, n1, n2, n3, n4, n5, n6, n7, out);
1095            lookup!(9, n0, n1, n2, n3, n4, n5, n6, n7, out);
1096            lookup!(10, n0, n1, n2, n3, n4, n5, n6, n7, out);
1097            lookup!(11, n0, n1, n2, n3, n4, n5, n6, n7, out);
1098            lookup!(12, n0, n1, n2, n3, n4, n5, n6, n7, out);
1099            lookup!(13, n0, n1, n2, n3, n4, n5, n6, n7, out);
1100            lookup!(14, n0, n1, n2, n3, n4, n5, n6, n7, out);
1101            lookup!(15, n0, n1, n2, n3, n4, n5, n6, n7, out);
1102
1103            let elems = transpose_16x16(&out);
1104            for (i, elem) in elems.iter().enumerate() {
1105                vst1q_u8(output.add(i).cast::<u8>(), *elem);
1106            }
1107        }
1108    }
1109
1110    /// 16×16 byte matrix transpose via TRN cascade.
1111    #[cfg(not(feature = "table-math"))]
1112    #[inline(always)]
1113    unsafe fn transpose_16x16(r: &[uint8x16_t; 16]) -> [uint8x16_t; 16] {
1114        // Shorthand reinterpret casts
1115        // between NEON register types.
1116        #[inline(always)]
1117        const fn u8_to_u16(v: uint8x16_t) -> uint16x8_t {
1118            unsafe { transmute::<uint8x16_t, uint16x8_t>(v) }
1119        }
1120
1121        #[inline(always)]
1122        const fn u16_to_u32(v: uint16x8_t) -> uint32x4_t {
1123            unsafe { transmute::<uint16x8_t, uint32x4_t>(v) }
1124        }
1125
1126        #[inline(always)]
1127        const fn u32_to_u64(v: uint32x4_t) -> uint64x2_t {
1128            unsafe { transmute::<uint32x4_t, uint64x2_t>(v) }
1129        }
1130
1131        #[inline(always)]
1132        const fn u64_to_u8(v: uint64x2_t) -> uint8x16_t {
1133            unsafe { transmute::<uint64x2_t, uint8x16_t>(v) }
1134        }
1135
1136        unsafe {
1137            // Phase 1:
1138            // TRN u8, transpose 2×2 byte blocks
1139            let a0 = vtrn1q_u8(r[0], r[1]);
1140            let a1 = vtrn2q_u8(r[0], r[1]);
1141            let a2 = vtrn1q_u8(r[2], r[3]);
1142            let a3 = vtrn2q_u8(r[2], r[3]);
1143            let a4 = vtrn1q_u8(r[4], r[5]);
1144            let a5 = vtrn2q_u8(r[4], r[5]);
1145            let a6 = vtrn1q_u8(r[6], r[7]);
1146            let a7 = vtrn2q_u8(r[6], r[7]);
1147            let a8 = vtrn1q_u8(r[8], r[9]);
1148            let a9 = vtrn2q_u8(r[8], r[9]);
1149            let a10 = vtrn1q_u8(r[10], r[11]);
1150            let a11 = vtrn2q_u8(r[10], r[11]);
1151            let a12 = vtrn1q_u8(r[12], r[13]);
1152            let a13 = vtrn2q_u8(r[12], r[13]);
1153            let a14 = vtrn1q_u8(r[14], r[15]);
1154            let a15 = vtrn2q_u8(r[14], r[15]);
1155
1156            // Phase 2:
1157            // TRN u16, transpose 4×4 blocks
1158            let b0 = vtrn1q_u16(u8_to_u16(a0), u8_to_u16(a2));
1159            let b2 = vtrn2q_u16(u8_to_u16(a0), u8_to_u16(a2));
1160            let b1 = vtrn1q_u16(u8_to_u16(a1), u8_to_u16(a3));
1161            let b3 = vtrn2q_u16(u8_to_u16(a1), u8_to_u16(a3));
1162            let b4 = vtrn1q_u16(u8_to_u16(a4), u8_to_u16(a6));
1163            let b6 = vtrn2q_u16(u8_to_u16(a4), u8_to_u16(a6));
1164            let b5 = vtrn1q_u16(u8_to_u16(a5), u8_to_u16(a7));
1165            let b7 = vtrn2q_u16(u8_to_u16(a5), u8_to_u16(a7));
1166            let b8 = vtrn1q_u16(u8_to_u16(a8), u8_to_u16(a10));
1167            let b10 = vtrn2q_u16(u8_to_u16(a8), u8_to_u16(a10));
1168            let b9 = vtrn1q_u16(u8_to_u16(a9), u8_to_u16(a11));
1169            let b11 = vtrn2q_u16(u8_to_u16(a9), u8_to_u16(a11));
1170            let b12 = vtrn1q_u16(u8_to_u16(a12), u8_to_u16(a14));
1171            let b14 = vtrn2q_u16(u8_to_u16(a12), u8_to_u16(a14));
1172            let b13 = vtrn1q_u16(u8_to_u16(a13), u8_to_u16(a15));
1173            let b15 = vtrn2q_u16(u8_to_u16(a13), u8_to_u16(a15));
1174
1175            // Phase 3:
1176            // TRN u32, transpose 8×8 blocks
1177            let c0 = vtrn1q_u32(u16_to_u32(b0), u16_to_u32(b4));
1178            let c4 = vtrn2q_u32(u16_to_u32(b0), u16_to_u32(b4));
1179            let c1 = vtrn1q_u32(u16_to_u32(b1), u16_to_u32(b5));
1180            let c5 = vtrn2q_u32(u16_to_u32(b1), u16_to_u32(b5));
1181            let c2 = vtrn1q_u32(u16_to_u32(b2), u16_to_u32(b6));
1182            let c6 = vtrn2q_u32(u16_to_u32(b2), u16_to_u32(b6));
1183            let c3 = vtrn1q_u32(u16_to_u32(b3), u16_to_u32(b7));
1184            let c7 = vtrn2q_u32(u16_to_u32(b3), u16_to_u32(b7));
1185            let c8 = vtrn1q_u32(u16_to_u32(b8), u16_to_u32(b12));
1186            let c12 = vtrn2q_u32(u16_to_u32(b8), u16_to_u32(b12));
1187            let c9 = vtrn1q_u32(u16_to_u32(b9), u16_to_u32(b13));
1188            let c13 = vtrn2q_u32(u16_to_u32(b9), u16_to_u32(b13));
1189            let c10 = vtrn1q_u32(u16_to_u32(b10), u16_to_u32(b14));
1190            let c14 = vtrn2q_u32(u16_to_u32(b10), u16_to_u32(b14));
1191            let c11 = vtrn1q_u32(u16_to_u32(b11), u16_to_u32(b15));
1192            let c15 = vtrn2q_u32(u16_to_u32(b11), u16_to_u32(b15));
1193
1194            // Phase 4:
1195            // TRN u64, full 16×16 transpose
1196            [
1197                u64_to_u8(vtrn1q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1198                u64_to_u8(vtrn1q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1199                u64_to_u8(vtrn1q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1200                u64_to_u8(vtrn1q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1201                u64_to_u8(vtrn1q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1202                u64_to_u8(vtrn1q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1203                u64_to_u8(vtrn1q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1204                u64_to_u8(vtrn1q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1205                u64_to_u8(vtrn2q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1206                u64_to_u8(vtrn2q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1207                u64_to_u8(vtrn2q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1208                u64_to_u8(vtrn2q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1209                u64_to_u8(vtrn2q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1210                u64_to_u8(vtrn2q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1211                u64_to_u8(vtrn2q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1212                u64_to_u8(vtrn2q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1213            ]
1214        }
1215    }
1216}
1217
1218// ==================================
1219// BLOCK 128 TESTS
1220// ==================================
1221
1222#[cfg(test)]
1223mod tests {
1224    use super::*;
1225    use proptest::prelude::*;
1226    use rand::{RngExt, rng};
1227
1228    // ==================================
1229    // BASIC
1230    // ==================================
1231
1232    #[test]
1233    fn tower_constants() {
1234        // Check that tau is propagated correctly
1235        // For Block128, tau must be (0, 1) from Block64.
1236        let tau128 = Block128::EXTENSION_TAU;
1237        let (lo128, hi128) = tau128.split();
1238        assert_eq!(lo128, Block64::ZERO);
1239        assert_eq!(hi128, Block64::TAU);
1240    }
1241
1242    #[test]
1243    fn add_truth() {
1244        let zero = Block128::ZERO;
1245        let one = Block128::ONE;
1246
1247        assert_eq!(zero + zero, zero);
1248        assert_eq!(zero + one, one);
1249        assert_eq!(one + zero, one);
1250        assert_eq!(one + one, zero);
1251    }
1252
1253    #[test]
1254    fn mul_truth() {
1255        let zero = Block128::ZERO;
1256        let one = Block128::ONE;
1257
1258        assert_eq!(zero * zero, zero);
1259        assert_eq!(zero * one, zero);
1260        assert_eq!(one * one, one);
1261    }
1262
1263    #[test]
1264    fn add() {
1265        // 5 ^ 3 = 6
1266        // 101 ^ 011 = 110
1267        assert_eq!(Block128(5) + Block128(3), Block128(6));
1268    }
1269
1270    #[test]
1271    fn mul_simple() {
1272        // Check for prime numbers (without overflow)
1273        // x^1 * x^1 = x^2 (2 * 2 = 4)
1274        assert_eq!(Block128(2) * Block128(2), Block128(4));
1275    }
1276
1277    #[test]
1278    fn mul_overflow() {
1279        // Reduction verification (AES test vectors)
1280        // Example from the AES specification:
1281        // 0x57 * 0x83 = 0xC1
1282        assert_eq!(Block128(0x57) * Block128(0x83), Block128(0xC1));
1283    }
1284
1285    #[test]
1286    fn karatsuba_correctness() {
1287        // Let's check using Block128 as an example
1288        // Let A = X (hi=1, lo=0)
1289        // Let B = X (hi=1, lo=0)
1290        // A * B = X^2
1291        // According to the rule:
1292        // X^2 = X + tau
1293        // Where tau for Block64 = 0x2000_0000_0000_0000.
1294        // So the result should be:
1295        // hi=1 (X), lo=0x20 (tau)
1296
1297        // Construct X manually
1298        let x = Block128::new(Block64::ZERO, Block64::ONE);
1299        let squared = x * x;
1300
1301        // Verify result via splitting
1302        let (res_lo, res_hi) = squared.split();
1303
1304        assert_eq!(res_hi, Block64::ONE, "X^2 should contain X component");
1305        assert_eq!(
1306            res_lo,
1307            Block64(0x2000_0000_0000_0000),
1308            "X^2 should contain tau component (0x2000_0000_0000_0000)"
1309        );
1310    }
1311
1312    #[test]
1313    fn security_zeroize() {
1314        // Setup sensitive data
1315        let mut secret_val = Block128::from(0xDEAD_BEEF_CAFE_BABE_u128);
1316        assert_ne!(secret_val, Block128::ZERO);
1317
1318        // Nuke it
1319        secret_val.zeroize();
1320
1321        // Verify absolute zero
1322        assert_eq!(secret_val, Block128::ZERO, "Memory was not wiped!");
1323
1324        // Check internal bytes just to be sure
1325        assert_eq!(secret_val.0, 0u128, "Underlying memory leak detected");
1326    }
1327
1328    #[test]
1329    fn invert_zero() {
1330        // Ensure strictly that 0 cannot be inverted.
1331        assert_eq!(
1332            Block128::ZERO.invert(),
1333            Block128::ZERO,
1334            "invert(0) must return 0"
1335        );
1336    }
1337
1338    #[test]
1339    fn inversion_random() {
1340        let mut rng = rng();
1341        for _i in 0..1000 {
1342            let val = Block128(rng.random());
1343
1344            if val != Block128::ZERO {
1345                let inv = val.invert();
1346                let identity = val * inv;
1347
1348                assert_eq!(
1349                    identity,
1350                    Block128::ONE,
1351                    "Inversion identity failed: a * a^-1 != 1"
1352                );
1353            }
1354        }
1355    }
1356
1357    #[test]
1358    fn tower_embedding() {
1359        let mut rng = rng();
1360        for _ in 0..100 {
1361            let a = Block64(rng.random());
1362            let b = Block64(rng.random());
1363
1364            // 1. Structure check: Lifting Block64 -> Block128
1365            let a_lifted: Block128 = a.into();
1366            let (lo, hi) = a_lifted.split();
1367
1368            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
1369            assert_eq!(
1370                hi,
1371                Block64::ZERO,
1372                "Embedding structure failed: high part must be zero"
1373            );
1374
1375            // 2. Addition Homomorphism
1376            let sum_sub = a + b;
1377            let sum_lifted: Block128 = sum_sub.into();
1378            let sum_in_super = Block128::from(a) + Block128::from(b);
1379
1380            assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
1381
1382            // 3. Multiplication Homomorphism
1383            // If I multiply two small numbers inside the big field,
1384            // the result must be the same as multiplying them in the small field
1385            // and then converting.
1386            let prod_sub = a * b;
1387            let prod_lifted: Block128 = prod_sub.into();
1388            let prod_in_super = Block128::from(a) * Block128::from(b);
1389
1390            assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
1391        }
1392    }
1393
1394    // ==================================
1395    // HARDWARE
1396    // ==================================
1397
1398    #[test]
1399    fn isomorphism_roundtrip() {
1400        let mut rng = rng();
1401        for _ in 0..1000 {
1402            let val = Block128(rng.random::<u128>());
1403            assert_eq!(val.to_hardware().to_tower(), val);
1404        }
1405    }
1406
1407    #[test]
1408    fn flat_mul_homomorphism() {
1409        let mut rng = rng();
1410        for _ in 0..1000 {
1411            let a = Block128(rng.random());
1412            let b = Block128(rng.random());
1413
1414            let expected_flat = (a * b).to_hardware();
1415            let actual_flat = a.to_hardware() * b.to_hardware();
1416
1417            assert_eq!(
1418                actual_flat, expected_flat,
1419                "Block128 flat multiplication mismatch: (a*b)^H != a^H * b^H"
1420            );
1421        }
1422    }
1423
1424    #[test]
1425    fn packed_consistency() {
1426        let mut rng = rng();
1427        for _ in 0..100 {
1428            let mut a_vals = [Block128::ZERO; 4];
1429            let mut b_vals = [Block128::ZERO; 4];
1430
1431            for i in 0..4 {
1432                a_vals[i] = Block128(rng.random::<u128>());
1433                b_vals[i] = Block128(rng.random::<u128>());
1434            }
1435
1436            let a_flat_vals = a_vals.map(|x| x.to_hardware());
1437            let b_flat_vals = b_vals.map(|x| x.to_hardware());
1438            let a_packed = Flat::<Block128>::pack(&a_flat_vals);
1439            let b_packed = Flat::<Block128>::pack(&b_flat_vals);
1440
1441            // 1. Test SIMD Add (Check 512-bit / 4-register XOR)
1442            let add_res = Block128::add_hardware_packed(a_packed, b_packed);
1443
1444            let mut add_out = [Block128::ZERO.to_hardware(); 4];
1445            Flat::<Block128>::unpack(add_res, &mut add_out);
1446
1447            for i in 0..4 {
1448                assert_eq!(
1449                    add_out[i],
1450                    (a_vals[i] + b_vals[i]).to_hardware(),
1451                    "Block128 SIMD add mismatch at index {}",
1452                    i
1453                );
1454            }
1455
1456            // 2. Test SIMD Mul (Flat basis)
1457            let mul_res = Block128::mul_hardware_packed(a_packed, b_packed);
1458
1459            let mut mul_out = [Block128::ZERO.to_hardware(); 4];
1460            Flat::<Block128>::unpack(mul_res, &mut mul_out);
1461
1462            for i in 0..4 {
1463                let expected_flat = (a_vals[i] * b_vals[i]).to_hardware();
1464                assert_eq!(
1465                    mul_out[i], expected_flat,
1466                    "Block128 SIMD mul mismatch at index {}",
1467                    i
1468                );
1469            }
1470        }
1471    }
1472
1473    // ==================================
1474    // PACKED
1475    // ==================================
1476
1477    #[test]
1478    fn pack_unpack_roundtrip() {
1479        let mut rng = rng();
1480        let mut data = [Block128::ZERO; PACKED_WIDTH_128];
1481        for v in data.iter_mut() {
1482            *v = Block128(rng.random());
1483        }
1484
1485        let packed = Block128::pack(&data);
1486        let mut unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1487        Block128::unpack(packed, &mut unpacked);
1488        assert_eq!(data, unpacked);
1489    }
1490
1491    #[test]
1492    fn packed_add_consistency() {
1493        let mut rng = rng();
1494        let mut a_vals = [Block128::ZERO; PACKED_WIDTH_128];
1495        let mut b_vals = [Block128::ZERO; PACKED_WIDTH_128];
1496
1497        for i in 0..PACKED_WIDTH_128 {
1498            a_vals[i] = Block128(rng.random());
1499            b_vals[i] = Block128(rng.random());
1500        }
1501
1502        let res_packed = Block128::pack(&a_vals) + Block128::pack(&b_vals);
1503        let mut res_unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1504        Block128::unpack(res_packed, &mut res_unpacked);
1505
1506        for i in 0..PACKED_WIDTH_128 {
1507            assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
1508        }
1509    }
1510
1511    #[test]
1512    fn packed_mul_consistency() {
1513        let mut rng = rng();
1514
1515        for _ in 0..1000 {
1516            // Check 1000 random cases.
1517            // Generate random inputs
1518            let mut a_arr = [Block128::ZERO; PACKED_WIDTH_128];
1519            let mut b_arr = [Block128::ZERO; PACKED_WIDTH_128];
1520
1521            for i in 0..PACKED_WIDTH_128 {
1522                // Generate random u128
1523                let val_a: u128 = rng.random();
1524                let val_b: u128 = rng.random();
1525                a_arr[i] = Block128(val_a);
1526                b_arr[i] = Block128(val_b);
1527            }
1528
1529            let a_packed = PackedBlock128(a_arr);
1530            let b_packed = PackedBlock128(b_arr);
1531
1532            // Perform SIMD multiplication
1533            let c_packed = a_packed * b_packed;
1534
1535            // Verify against Scalar multiplication
1536            let mut c_expected = [Block128::ZERO; PACKED_WIDTH_128];
1537            for i in 0..PACKED_WIDTH_128 {
1538                c_expected[i] = a_arr[i] * b_arr[i];
1539            }
1540
1541            assert_eq!(c_packed.0, c_expected, "SIMD multiplication mismatch!");
1542        }
1543    }
1544
1545    // ==================================
1546    // CT LIFTING BASIS
1547    // ==================================
1548
1549    #[inline(always)]
1550    fn promote_block8_tables(val: Block8) -> Block128 {
1551        // Current (table) lifting: flat/hardware byte -> tower byte -> Block128 flat.
1552        let idx_flat = val.0 as usize;
1553        let tower_byte = unsafe { *constants::FLAT_TO_TOWER_8.get_unchecked(idx_flat) };
1554        let idx_tower = tower_byte as usize;
1555
1556        Block128(unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx_tower) })
1557    }
1558
1559    #[inline(always)]
1560    fn promote_block16_tables(val: Block16) -> Block128 {
1561        let v_flat = val.0;
1562
1563        let mut v_tower = 0u16;
1564        for i in 0..2 {
1565            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1566            let idx = (i * 256) + byte;
1567            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_16.get_unchecked(idx) };
1568        }
1569
1570        let mut res = 0u128;
1571        for i in 0..2 {
1572            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1573            let idx = (i * 256) + byte;
1574            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1575        }
1576
1577        Block128(res)
1578    }
1579
1580    #[inline(always)]
1581    fn promote_block32_tables(val: Block32) -> Block128 {
1582        let v_flat = val.0;
1583
1584        let mut v_tower = 0u32;
1585        for i in 0..4 {
1586            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1587            let idx = (i * 256) + byte;
1588            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_32.get_unchecked(idx) };
1589        }
1590
1591        let mut res = 0u128;
1592        for i in 0..4 {
1593            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1594            let idx = (i * 256) + byte;
1595            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1596        }
1597
1598        Block128(res)
1599    }
1600
1601    #[inline(always)]
1602    fn promote_block64_tables(val: Block64) -> Block128 {
1603        let v_flat = val.0;
1604
1605        let mut v_tower = 0u64;
1606        for i in 0..8 {
1607            let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1608            let idx = (i * 256) + byte;
1609            v_tower ^= unsafe { *constants::FLAT_TO_TOWER_64.get_unchecked(idx) };
1610        }
1611
1612        let mut res = 0u128;
1613        for i in 0..8 {
1614            let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1615            let idx = (i * 256) + byte;
1616            res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1617        }
1618
1619        Block128(res)
1620    }
1621
1622    #[test]
1623    fn lift_from_partial_hardware_matches_tables_block8_exhaustive() {
1624        for x in 0u16..=u8::MAX as u16 {
1625            let v = Block8(x as u8);
1626            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1627            let expected = promote_block8_tables(v);
1628
1629            assert_eq!(got, expected);
1630        }
1631    }
1632
1633    #[test]
1634    fn lift_from_partial_hardware_matches_tables_block16_exhaustive() {
1635        for x in 0..=u16::MAX {
1636            let v = Block16(x);
1637            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1638            let expected = promote_block16_tables(v);
1639
1640            assert_eq!(got, expected);
1641        }
1642    }
1643
1644    #[test]
1645    fn lift_from_partial_hardware_matches_tables_block32_random() {
1646        let mut rng = rng();
1647        for _ in 0..10_000 {
1648            let v = Block32(rng.random::<u32>());
1649            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1650            let expected = promote_block32_tables(v);
1651
1652            assert_eq!(got, expected);
1653        }
1654    }
1655
1656    #[test]
1657    fn lift_from_partial_hardware_matches_tables_block64_random() {
1658        let mut rng = rng();
1659        for _ in 0..10_000 {
1660            let v = Block64(rng.random::<u64>());
1661            let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1662            let expected = promote_block64_tables(v);
1663
1664            assert_eq!(got, expected);
1665        }
1666    }
1667
1668    // ==================================
1669    // PROMOTE BATCH + EDGE CASES
1670    // ==================================
1671
1672    #[test]
1673    fn promote_flat_batch_matches_scalar_block8() {
1674        let mut rng = rng();
1675        let input: Vec<Flat<Block8>> = (0..64)
1676            .map(|_| Block8(rng.random::<u8>()).to_hardware())
1677            .collect();
1678
1679        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 64];
1680        Block128::promote_flat_batch(&input, &mut batch_out);
1681
1682        for (i, &v) in input.iter().enumerate() {
1683            let scalar = Block128::promote_flat(v);
1684            assert_eq!(batch_out[i], scalar, "batch/scalar mismatch at index {}", i);
1685        }
1686    }
1687
1688    #[test]
1689    fn promote_flat_batch_matches_scalar_block16() {
1690        let mut rng = rng();
1691        let input: Vec<Flat<Block16>> = (0..32)
1692            .map(|_| Block16(rng.random::<u16>()).to_hardware())
1693            .collect();
1694
1695        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 32];
1696        Block128::promote_flat_batch(&input, &mut batch_out);
1697
1698        for (i, &v) in input.iter().enumerate() {
1699            assert_eq!(
1700                batch_out[i],
1701                Block128::promote_flat(v),
1702                "batch/scalar mismatch at index {}",
1703                i
1704            );
1705        }
1706    }
1707
1708    #[test]
1709    fn promote_flat_batch_matches_scalar_block32() {
1710        let mut rng = rng();
1711        let input: Vec<Flat<Block32>> = (0..16)
1712            .map(|_| Block32(rng.random::<u32>()).to_hardware())
1713            .collect();
1714
1715        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 16];
1716        Block128::promote_flat_batch(&input, &mut batch_out);
1717
1718        for (i, &v) in input.iter().enumerate() {
1719            assert_eq!(
1720                batch_out[i],
1721                Block128::promote_flat(v),
1722                "batch/scalar mismatch at index {}",
1723                i
1724            );
1725        }
1726    }
1727
1728    #[test]
1729    fn promote_flat_batch_matches_scalar_block64() {
1730        let mut rng = rng();
1731        let input: Vec<Flat<Block64>> = (0..8)
1732            .map(|_| Block64(rng.random::<u64>()).to_hardware())
1733            .collect();
1734
1735        let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 8];
1736        Block128::promote_flat_batch(&input, &mut batch_out);
1737
1738        for (i, &v) in input.iter().enumerate() {
1739            assert_eq!(
1740                batch_out[i],
1741                Block128::promote_flat(v),
1742                "batch/scalar mismatch at index {}",
1743                i
1744            );
1745        }
1746    }
1747
1748    #[test]
1749    fn promote_flat_batch_partial_slice() {
1750        let input: Vec<Flat<Block8>> = (0..16).map(|i| Block8(i as u8).to_hardware()).collect();
1751
1752        // Output shorter than input
1753        let mut out_short = vec![Flat::from_raw(Block128::ZERO); 5];
1754        Block128::promote_flat_batch(&input, &mut out_short);
1755
1756        for i in 0..5 {
1757            assert_eq!(out_short[i], Block128::promote_flat(input[i]));
1758        }
1759
1760        // Input shorter than output
1761        let short_input = &input[..3];
1762        let mut out_long = vec![Flat::from_raw(Block128::ZERO); 10];
1763
1764        Block128::promote_flat_batch(short_input, &mut out_long);
1765
1766        for i in 0..3 {
1767            assert_eq!(out_long[i], Block128::promote_flat(short_input[i]));
1768        }
1769
1770        // Elements beyond input length untouched
1771        for val in &out_long[3..10] {
1772            assert_eq!(*val, Flat::from_raw(Block128::ZERO));
1773        }
1774    }
1775
1776    #[test]
1777    fn promote_edge_zero() {
1778        let zero = Flat::from_raw(Block8(0));
1779        let promoted = Block128::promote_flat(zero);
1780
1781        assert_eq!(
1782            promoted,
1783            Flat::from_raw(Block128::ZERO),
1784            "promote(0) must be 0"
1785        );
1786
1787        // Batch:
1788        // all-zero input
1789        let input = vec![zero; 16];
1790        let mut output = vec![Flat::from_raw(Block128(0xDEAD)); 16];
1791
1792        Block128::promote_flat_batch(&input, &mut output);
1793
1794        for o in &output {
1795            assert_eq!(*o, Flat::from_raw(Block128::ZERO));
1796        }
1797    }
1798
1799    #[test]
1800    fn promote_edge_one() {
1801        let one_flat8 = Block8::ONE.to_hardware();
1802        let one_flat128 = Block128::ONE.to_hardware();
1803
1804        assert_eq!(
1805            Block128::promote_flat(one_flat8),
1806            one_flat128,
1807            "promote(1) must equal 1 in target field"
1808        );
1809    }
1810
1811    #[test]
1812    fn promote_edge_max_block8() {
1813        let max = Flat::from_raw(Block8(0xFF));
1814        let promoted = Block128::promote_flat(max);
1815
1816        // Must not be zero
1817        assert_ne!(promoted, Flat::from_raw(Block128::ZERO));
1818
1819        // Roundtrip through tower
1820        // must preserve embedding.
1821        let tower_8 = max.to_tower();
1822        let tower_128 = Block128::from(tower_8);
1823
1824        assert_eq!(promoted.to_tower(), tower_128);
1825    }
1826
1827    #[test]
1828    fn promote_edge_single_bits() {
1829        for bit in 0..8 {
1830            let val = Flat::from_raw(Block8(1u8 << bit));
1831            let promoted = Block128::promote_flat(val);
1832
1833            // Must not be zero
1834            assert_ne!(
1835                promoted,
1836                Flat::from_raw(Block128::ZERO),
1837                "single-bit {} promoted to zero",
1838                bit
1839            );
1840
1841            // Tower roundtrip
1842            let tower_8 = val.to_tower();
1843            let tower_128 = Block128::from(tower_8);
1844
1845            assert_eq!(
1846                promoted.to_tower(),
1847                tower_128,
1848                "tower roundtrip failed for bit {}",
1849                bit
1850            );
1851        }
1852    }
1853
1854    #[test]
1855    fn promote_edge_alternating_packed() {
1856        let input: Vec<Flat<Block8>> = (0..16)
1857            .map(|i| {
1858                if i % 2 == 0 {
1859                    Flat::from_raw(Block8(0x00))
1860                } else {
1861                    Flat::from_raw(Block8(0xFF))
1862                }
1863            })
1864            .collect();
1865
1866        let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1867        Block128::promote_flat_batch(&input, &mut output);
1868
1869        for (i, &v) in input.iter().enumerate() {
1870            assert_eq!(
1871                output[i],
1872                Block128::promote_flat(v),
1873                "alternating mismatch at {}",
1874                i
1875            );
1876        }
1877    }
1878
1879    #[test]
1880    fn promote_edge_all_same_packed() {
1881        let val = Flat::from_raw(Block8(0x42));
1882        let expected = Block128::promote_flat(val);
1883
1884        let input = vec![val; 16];
1885        let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1886
1887        Block128::promote_flat_batch(&input, &mut output);
1888
1889        for (i, o) in output.iter().enumerate() {
1890            assert_eq!(*o, expected, "all-same mismatch at {}", i);
1891        }
1892    }
1893
1894    #[test]
1895    fn promote_tower_roundtrip_block8() {
1896        for x in 0u16..=u8::MAX as u16 {
1897            let b8 = Block8(x as u8);
1898            let promoted = Block128::promote_flat(b8.to_hardware());
1899            let tower_128 = promoted.to_tower();
1900            let embedded = Block128::from(b8);
1901
1902            assert_eq!(
1903                tower_128, embedded,
1904                "tower roundtrip failed for Block8({})",
1905                x
1906            );
1907        }
1908    }
1909
1910    #[test]
1911    fn promote_tower_roundtrip_block16() {
1912        let mut rng = rng();
1913        for _ in 0..10_000 {
1914            let v = Block16(rng.random::<u16>());
1915            let promoted = Block128::promote_flat(v.to_hardware());
1916            let tower_128 = promoted.to_tower();
1917            let embedded = Block128::from(v);
1918
1919            assert_eq!(
1920                tower_128, embedded,
1921                "tower roundtrip failed for Block16({})",
1922                v.0
1923            );
1924        }
1925    }
1926
1927    #[test]
1928    fn promote_tower_roundtrip_block32() {
1929        let mut rng = rng();
1930        for _ in 0..10_000 {
1931            let v = Block32(rng.random::<u32>());
1932            let promoted = Block128::promote_flat(v.to_hardware());
1933            let tower_128 = promoted.to_tower();
1934            let embedded = Block128::from(v);
1935
1936            assert_eq!(
1937                tower_128, embedded,
1938                "tower roundtrip failed for Block32({})",
1939                v.0
1940            );
1941        }
1942    }
1943
1944    #[test]
1945    fn promote_tower_roundtrip_block64() {
1946        let mut rng = rng();
1947        for _ in 0..10_000 {
1948            let v = Block64(rng.random::<u64>());
1949            let promoted = Block128::promote_flat(v.to_hardware());
1950            let tower_128 = promoted.to_tower();
1951            let embedded = Block128::from(v);
1952
1953            assert_eq!(
1954                tower_128, embedded,
1955                "tower roundtrip failed for Block64({})",
1956                v.0
1957            );
1958        }
1959    }
1960
1961    #[test]
1962    fn promote_algebraic_homomorphism_add_block8() {
1963        let mut rng = rng();
1964        for _ in 0..1000 {
1965            let a = Block8(rng.random::<u8>());
1966            let b = Block8(rng.random::<u8>());
1967
1968            let promote_a = Block128::promote_flat(a.to_hardware());
1969            let promote_b = Block128::promote_flat(b.to_hardware());
1970            let promote_sum = Block128::promote_flat((a + b).to_hardware());
1971
1972            assert_eq!(
1973                promote_a + promote_b,
1974                promote_sum,
1975                "add homomorphism: promote(a)+promote(b) != promote(a+b)"
1976            );
1977        }
1978    }
1979
1980    #[test]
1981    fn promote_algebraic_homomorphism_mul_block8() {
1982        let mut rng = rng();
1983        for _ in 0..1000 {
1984            let a = Block8(rng.random::<u8>());
1985            let b = Block8(rng.random::<u8>());
1986
1987            let promote_a = Block128::promote_flat(a.to_hardware());
1988            let promote_b = Block128::promote_flat(b.to_hardware());
1989            let promote_prod = Block128::promote_flat((a * b).to_hardware());
1990
1991            // Subfield elements: promote then multiply
1992            // must equal multiply then promote.
1993            assert_eq!(
1994                promote_a * promote_b,
1995                promote_prod,
1996                "mul homomorphism: promote(a)*promote(b) != promote(a*b)"
1997            );
1998        }
1999    }
2000
2001    #[test]
2002    fn promote_algebraic_homomorphism_add_block16() {
2003        let mut rng = rng();
2004        for _ in 0..1000 {
2005            let a = Block16(rng.random::<u16>());
2006            let b = Block16(rng.random::<u16>());
2007
2008            let pa = Block128::promote_flat(a.to_hardware());
2009            let pb = Block128::promote_flat(b.to_hardware());
2010            let p_sum = Block128::promote_flat((a + b).to_hardware());
2011
2012            assert_eq!(pa + pb, p_sum, "Block16 add homomorphism failed");
2013        }
2014    }
2015
2016    #[test]
2017    fn promote_algebraic_homomorphism_mul_block16() {
2018        let mut rng = rng();
2019        for _ in 0..1000 {
2020            let a = Block16(rng.random::<u16>());
2021            let b = Block16(rng.random::<u16>());
2022
2023            let pa = Block128::promote_flat(a.to_hardware());
2024            let pb = Block128::promote_flat(b.to_hardware());
2025            let p_prod = Block128::promote_flat((a * b).to_hardware());
2026
2027            assert_eq!(pa * pb, p_prod, "Block16 mul homomorphism failed");
2028        }
2029    }
2030
2031    #[test]
2032    fn promote_algebraic_homomorphism_add_block32() {
2033        let mut rng = rng();
2034        for _ in 0..1000 {
2035            let a = Block32(rng.random::<u32>());
2036            let b = Block32(rng.random::<u32>());
2037
2038            let pa = Block128::promote_flat(a.to_hardware());
2039            let pb = Block128::promote_flat(b.to_hardware());
2040            let p_sum = Block128::promote_flat((a + b).to_hardware());
2041
2042            assert_eq!(pa + pb, p_sum, "Block32 add homomorphism failed");
2043        }
2044    }
2045
2046    #[test]
2047    fn promote_algebraic_homomorphism_mul_block32() {
2048        let mut rng = rng();
2049        for _ in 0..1000 {
2050            let a = Block32(rng.random::<u32>());
2051            let b = Block32(rng.random::<u32>());
2052
2053            let pa = Block128::promote_flat(a.to_hardware());
2054            let pb = Block128::promote_flat(b.to_hardware());
2055            let p_prod = Block128::promote_flat((a * b).to_hardware());
2056
2057            assert_eq!(pa * pb, p_prod, "Block32 mul homomorphism failed");
2058        }
2059    }
2060
2061    #[test]
2062    fn promote_algebraic_homomorphism_add_block64() {
2063        let mut rng = rng();
2064        for _ in 0..1000 {
2065            let a = Block64(rng.random::<u64>());
2066            let b = Block64(rng.random::<u64>());
2067
2068            let pa = Block128::promote_flat(a.to_hardware());
2069            let pb = Block128::promote_flat(b.to_hardware());
2070            let p_sum = Block128::promote_flat((a + b).to_hardware());
2071
2072            assert_eq!(pa + pb, p_sum, "Block64 add homomorphism failed");
2073        }
2074    }
2075
2076    #[test]
2077    fn promote_algebraic_homomorphism_mul_block64() {
2078        let mut rng = rng();
2079        for _ in 0..1000 {
2080            let a = Block64(rng.random::<u64>());
2081            let b = Block64(rng.random::<u64>());
2082
2083            let pa = Block128::promote_flat(a.to_hardware());
2084            let pb = Block128::promote_flat(b.to_hardware());
2085            let p_prod = Block128::promote_flat((a * b).to_hardware());
2086
2087            assert_eq!(pa * pb, p_prod, "Block64 mul homomorphism failed");
2088        }
2089    }
2090
2091    #[test]
2092    fn promote_generator_preserves_order() {
2093        // Block8 generator is 3 with order 255
2094        let g = Block8(3);
2095        let g_promoted = Block128::promote_flat(g.to_hardware());
2096
2097        // Fermat:
2098        // g^255 = 1 in GF(2^8)
2099        let mut acc8 = Block8::ONE;
2100        for _ in 0..255 {
2101            acc8 *= g;
2102        }
2103
2104        assert_eq!(acc8, Block8::ONE, "Block8 Fermat: g^255 must be 1");
2105
2106        // Promoted element must
2107        // also satisfy g^255 = 1.
2108        let mut acc128 = Flat::from_raw(Block128::ONE);
2109        for _ in 0..255 {
2110            acc128 *= g_promoted;
2111        }
2112
2113        assert_eq!(
2114            acc128,
2115            Flat::from_raw(Block128::ONE),
2116            "promoted element lost multiplicative order"
2117        );
2118    }
2119
2120    proptest! {
2121        #[test]
2122        fn parity_masks_match_from_hardware(x_flat in any::<u128>()) {
2123            let tower = Block128::from_hardware(Flat::from_raw(Block128(x_flat))).0;
2124
2125            for k in 0..128 {
2126                let bit = ((tower >> k) & 1) as u8;
2127                let via_api = Flat::from_raw(Block128(x_flat)).tower_bit(k);
2128
2129                prop_assert_eq!(
2130                    via_api, bit,
2131                    "Block128 tower_bit_from_hardware mismatch at x_flat={:#034x}, bit_idx={}",
2132                    x_flat, k
2133                );
2134            }
2135        }
2136    }
2137}