Skip to main content

hekate_math/towers/
block32.rs

1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate-math project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! GF(2^32) as `Block16[X] / (X^2 + X + τ)`, its flat basis
19//! modulo x^32 + x^7 + x^3 + x^2 + 1, and the aarch64 NEON kernels.
20
21use crate::algebra::impl_binary_field_extras;
22use crate::towers::bit::Bit;
23use crate::towers::block8::Block8;
24use crate::towers::block16::Block16;
25use crate::{
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 CtConvertBasisU32<const N: usize>([u32; N]);
36
37#[cfg(not(feature = "table-math"))]
38static TOWER_TO_FLAT_BASIS_32: CtConvertBasisU32<32> =
39    CtConvertBasisU32(constants::RAW_TOWER_TO_FLAT_32);
40
41#[cfg(not(feature = "table-math"))]
42static FLAT_TO_TOWER_BASIS_32: CtConvertBasisU32<32> =
43    CtConvertBasisU32(constants::RAW_FLAT_TO_TOWER_32);
44
45/// GF(2^32) in the tower basis: `lo + hi·X`
46/// over [`Block16`], `lo` in the low 16 bits.
47#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
48#[repr(transparent)]
49pub struct Block32(pub u32);
50
51impl Block32 {
52    /// τ of the next level, `Block32[X] / (X^2 + X + τ)`;
53    /// equals [`TowerField::EXTENSION_TAU`].
54    pub const TAU: Self = Block32(0x2000_0000);
55
56    /// Builds `lo + hi·X` from its halves.
57    pub fn new(lo: Block16, hi: Block16) -> Self {
58        Self((hi.0 as u32) << 16 | (lo.0 as u32))
59    }
60
61    /// Returns the halves `(lo, hi)` of `lo + hi·X`.
62    #[inline(always)]
63    pub fn split(self) -> (Block16, Block16) {
64        (Block16(self.0 as u16), Block16((self.0 >> 16) as u16))
65    }
66
67    #[inline(always)]
68    pub(crate) fn mul_tau(self) -> Self {
69        let (a0, a1) = self.split();
70        let t = a1.mul_tau();
71
72        Self::new(t.mul_tau(), (a0 + a1).mul_tau())
73    }
74}
75
76impl TowerField for Block32 {
77    const BITS: usize = 32;
78    const ZERO: Self = Block32(0);
79    const ONE: Self = Block32(1);
80
81    const EXTENSION_TAU: Self = Self::TAU;
82
83    fn invert(&self) -> Self {
84        let (l, h) = self.split();
85
86        let norm = h.square().mul_tau() + h * l + l.square();
87        let norm_inv = norm.invert();
88
89        let res_hi = h * norm_inv;
90        let res_lo = (h + l) * norm_inv;
91
92        Self::new(res_lo, res_hi)
93    }
94
95    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
96        let mut buf = [0u8; 4];
97        buf.copy_from_slice(&bytes[0..4]);
98
99        Self(u32::from_le_bytes(buf))
100    }
101}
102
103impl Add for Block32 {
104    type Output = Self;
105
106    fn add(self, rhs: Self) -> Self {
107        Self(self.0.bitxor(rhs.0))
108    }
109}
110
111impl Sub for Block32 {
112    type Output = Self;
113
114    fn sub(self, rhs: Self) -> Self {
115        self.add(rhs)
116    }
117}
118
119impl Mul for Block32 {
120    type Output = Self;
121
122    #[inline]
123    fn mul(self, rhs: Self) -> Self {
124        let (a0, a1) = self.split();
125        let (b0, b1) = rhs.split();
126
127        let v0 = a0 * b0;
128        let v1 = a1 * b1;
129        let vs = (a0 + a1) * (b0 + b1);
130
131        Self::new(v0 + v1.mul_tau(), v0 + vs)
132    }
133}
134
135impl AddAssign for Block32 {
136    fn add_assign(&mut self, rhs: Self) {
137        self.0.bitxor_assign(rhs.0);
138    }
139}
140
141impl SubAssign for Block32 {
142    fn sub_assign(&mut self, rhs: Self) {
143        self.0.bitxor_assign(rhs.0);
144    }
145}
146
147impl MulAssign for Block32 {
148    fn mul_assign(&mut self, rhs: Self) {
149        *self = *self * rhs;
150    }
151}
152
153impl CanonicalSerialize for Block32 {
154    fn serialized_size(&self) -> usize {
155        4
156    }
157
158    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
159        if writer.len() < 4 {
160            return Err(());
161        }
162
163        writer[..4].copy_from_slice(&self.0.to_le_bytes());
164
165        Ok(())
166    }
167}
168
169impl CanonicalDeserialize for Block32 {
170    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
171        if bytes.len() < 4 {
172            return Err(());
173        }
174
175        let mut buf = [0u8; 4];
176        buf.copy_from_slice(&bytes[0..4]);
177
178        Ok(Self(u32::from_le_bytes(buf)))
179    }
180}
181
182impl From<u8> for Block32 {
183    fn from(val: u8) -> Self {
184        Self(val as u32)
185    }
186}
187
188impl From<u16> for Block32 {
189    #[inline]
190    fn from(val: u16) -> Self {
191        Self::from(val as u32)
192    }
193}
194
195impl From<u32> for Block32 {
196    #[inline]
197    fn from(val: u32) -> Self {
198        Self(val)
199    }
200}
201
202impl From<u64> for Block32 {
203    #[inline]
204    fn from(val: u64) -> Self {
205        Self(val as u32)
206    }
207}
208
209impl From<u128> for Block32 {
210    #[inline]
211    fn from(val: u128) -> Self {
212        Self(val as u32)
213    }
214}
215
216// ========================================
217// FIELD LIFTING
218// ========================================
219
220impl From<Bit> for Block32 {
221    #[inline(always)]
222    fn from(val: Bit) -> Self {
223        Self(val.get() as u32)
224    }
225}
226
227impl From<Block8> for Block32 {
228    #[inline(always)]
229    fn from(val: Block8) -> Self {
230        Self(val.0 as u32)
231    }
232}
233
234impl From<Block16> for Block32 {
235    #[inline(always)]
236    fn from(val: Block16) -> Self {
237        Self(val.0 as u32)
238    }
239}
240
241// ========================================
242// PACKED BLOCK 32 (Width = 4)
243// ========================================
244
245/// Lanes in [`PackedBlock32`].
246pub const PACKED_WIDTH_32: usize = 4;
247
248/// Four [`Block32`] lanes; operators act lane by lane.
249#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
250#[repr(C, align(16))]
251pub struct PackedBlock32(pub [Block32; PACKED_WIDTH_32]);
252
253impl PackedBlock32 {
254    /// Returns the packed value with every lane zero.
255    #[inline(always)]
256    pub fn zero() -> Self {
257        Self([Block32::ZERO; PACKED_WIDTH_32])
258    }
259}
260
261impl PackableField for Block32 {
262    type Packed = PackedBlock32;
263
264    const WIDTH: usize = PACKED_WIDTH_32;
265
266    #[inline(always)]
267    fn pack(chunk: &[Self]) -> Self::Packed {
268        assert!(
269            chunk.len() >= PACKED_WIDTH_32,
270            "PackableField::pack: input slice too short",
271        );
272
273        let mut arr = [Self::ZERO; PACKED_WIDTH_32];
274        arr.copy_from_slice(&chunk[..PACKED_WIDTH_32]);
275
276        PackedBlock32(arr)
277    }
278
279    #[inline(always)]
280    fn unpack(packed: Self::Packed, output: &mut [Self]) {
281        assert!(
282            output.len() >= PACKED_WIDTH_32,
283            "PackableField::unpack: output slice too short",
284        );
285
286        output[..PACKED_WIDTH_32].copy_from_slice(&packed.0);
287    }
288}
289
290impl Add for PackedBlock32 {
291    type Output = Self;
292
293    #[inline(always)]
294    fn add(self, rhs: Self) -> Self {
295        let mut res = [Block32::ZERO; PACKED_WIDTH_32];
296        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
297            *out = *l + *r;
298        }
299
300        Self(res)
301    }
302}
303
304impl AddAssign for PackedBlock32 {
305    #[inline(always)]
306    fn add_assign(&mut self, rhs: Self) {
307        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
308            *l += *r;
309        }
310    }
311}
312
313impl Sub for PackedBlock32 {
314    type Output = Self;
315
316    #[inline(always)]
317    fn sub(self, rhs: Self) -> Self {
318        self.add(rhs)
319    }
320}
321
322impl SubAssign for PackedBlock32 {
323    #[inline(always)]
324    fn sub_assign(&mut self, rhs: Self) {
325        self.add_assign(rhs);
326    }
327}
328
329impl Mul for PackedBlock32 {
330    type Output = Self;
331
332    #[inline(always)]
333    fn mul(self, rhs: Self) -> Self {
334        let mut res = [Block32::ZERO; PACKED_WIDTH_32];
335        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
336            #[cfg(all(pmull, feature = "table-math"))]
337            {
338                *out = mul_iso_32(*l, *r);
339            }
340
341            #[cfg(not(all(pmull, feature = "table-math")))]
342            {
343                *out = *l * *r;
344            }
345        }
346
347        Self(res)
348    }
349}
350
351impl MulAssign for PackedBlock32 {
352    #[inline(always)]
353    fn mul_assign(&mut self, rhs: Self) {
354        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
355            *l *= *r;
356        }
357    }
358}
359
360impl Mul<Block32> for PackedBlock32 {
361    type Output = Self;
362
363    #[inline(always)]
364    fn mul(self, rhs: Block32) -> Self {
365        let mut res = [Block32::ZERO; PACKED_WIDTH_32];
366        for (out, v) in res.iter_mut().zip(self.0.iter()) {
367            *out = *v * rhs;
368        }
369
370        Self(res)
371    }
372}
373
374// ===================================
375// Block32 Hardware Field
376// ===================================
377
378impl HardwareField for Block32 {
379    #[inline(always)]
380    fn to_hardware(self) -> Flat<Self> {
381        #[cfg(feature = "table-math")]
382        {
383            Flat::from_raw(apply_matrix_32(self, &constants::TOWER_TO_FLAT_32))
384        }
385
386        #[cfg(not(feature = "table-math"))]
387        {
388            Flat::from_raw(Block32(map_ct_32(self.0, &TOWER_TO_FLAT_BASIS_32.0)))
389        }
390    }
391
392    #[inline(always)]
393    fn from_hardware(value: Flat<Self>) -> Self {
394        let value = value.into_raw();
395        #[cfg(feature = "table-math")]
396        {
397            apply_matrix_32(value, &constants::FLAT_TO_TOWER_32)
398        }
399
400        #[cfg(not(feature = "table-math"))]
401        {
402            Block32(map_ct_32(value.0, &FLAT_TO_TOWER_BASIS_32.0))
403        }
404    }
405
406    #[inline(always)]
407    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
408        Flat::from_raw(lhs.into_raw() + rhs.into_raw())
409    }
410
411    #[inline(always)]
412    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
413        let lhs = lhs.into_raw();
414        let rhs = rhs.into_raw();
415
416        #[cfg(target_arch = "aarch64")]
417        {
418            PackedFlat::from_raw(neon::add_packed_32(lhs, rhs))
419        }
420
421        #[cfg(not(target_arch = "aarch64"))]
422        {
423            PackedFlat::from_raw(lhs + rhs)
424        }
425    }
426
427    #[inline(always)]
428    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
429        let lhs = lhs.into_raw();
430        let rhs = rhs.into_raw();
431
432        #[cfg(pmull)]
433        {
434            Flat::from_raw(neon::mul_flat_32(lhs, rhs))
435        }
436
437        #[cfg(not(pmull))]
438        {
439            let a_tower = Self::from_hardware(Flat::from_raw(lhs));
440            let b_tower = Self::from_hardware(Flat::from_raw(rhs));
441
442            (a_tower * b_tower).to_hardware()
443        }
444    }
445
446    #[inline(always)]
447    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
448        let lhs = lhs.into_raw();
449        let rhs = rhs.into_raw();
450
451        #[cfg(pmull)]
452        {
453            PackedFlat::from_raw(neon::mul_flat_packed_32(lhs, rhs))
454        }
455
456        #[cfg(not(pmull))]
457        {
458            let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
459            let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
460            let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
461
462            Self::unpack(lhs, &mut l);
463            Self::unpack(rhs, &mut r);
464
465            for i in 0..<Self as PackableField>::WIDTH {
466                res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
467            }
468
469            PackedFlat::from_raw(Self::pack(&res))
470        }
471    }
472
473    #[inline(always)]
474    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
475        let broadcasted = PackedBlock32([rhs.into_raw(); PACKED_WIDTH_32]);
476        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
477    }
478
479    #[inline(always)]
480    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
481        let mask = constants::FLAT_TO_TOWER_BIT_MASKS_32[bit_idx];
482
483        // Parity of (x & mask) without popcount.
484        // Folds 32 bits down to 1
485        // using a binary XOR tree.
486        let mut v = value.into_raw().0 & mask;
487        v ^= v >> 16;
488        v ^= v >> 8;
489        v ^= v >> 4;
490        v ^= v >> 2;
491        v ^= v >> 1;
492
493        (v & 1) as u8
494    }
495}
496
497impl FlatPromote<Block8> for Block32 {
498    #[inline(always)]
499    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
500        let val = val.into_raw();
501
502        #[cfg(not(feature = "table-math"))]
503        {
504            Flat::from_raw(Block32(lift_ct_32::<8>(
505                val.0 as u32,
506                &constants::LIFT_BASIS_8_TO_32,
507            )))
508        }
509
510        #[cfg(feature = "table-math")]
511        {
512            Flat::from_raw(Block32(constants::LIFT_TABLE_8_TO_32[val.0 as usize]))
513        }
514    }
515}
516
517impl FlatPromote<Block16> for Block32 {
518    #[inline(always)]
519    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
520        let val = val.into_raw();
521
522        #[cfg(not(feature = "table-math"))]
523        {
524            Flat::from_raw(Block32(lift_ct_32::<16>(
525                val.0 as u32,
526                &constants::LIFT_BASIS_16_TO_32,
527            )))
528        }
529
530        #[cfg(feature = "table-math")]
531        {
532            let v = val.0;
533            let res = constants::PROMOTE_16_BYTE_0_TO_32[(v & 0xFF) as usize]
534                ^ constants::PROMOTE_16_BYTE_1_TO_32[(v >> 8) as usize];
535
536            Flat::from_raw(Block32(res))
537        }
538    }
539}
540
541// ===========================================
542// Binary Field Extras
543// ===========================================
544
545impl_binary_field_extras!(Block32, map_ct_32, TRACE_MASK_32, SOLVE_QUADRATIC_BASIS_32);
546
547// ===========================================
548// UTILS
549// ===========================================
550
551/// Returns `a * b` through the flat basis, φ⁻¹(φ(a) · φ(b)), with
552/// the PMULL kernel; aarch64 with the `aes` target feature only.
553#[cfg(pmull)]
554#[inline(always)]
555pub fn mul_iso_32(a: Block32, b: Block32) -> Block32 {
556    let a_flat = a.to_hardware();
557    let b_flat = b.to_hardware();
558    let c_flat = Flat::from_raw(neon::mul_flat_32(a_flat.into_raw(), b_flat.into_raw()));
559
560    c_flat.to_tower()
561}
562
563#[cfg(feature = "table-math")]
564#[inline(always)]
565pub(crate) fn apply_matrix_32(val: Block32, table: &[u32; 1024]) -> Block32 {
566    let mut res = 0u32;
567    let v = val.0;
568
569    // 4 lookups
570    for i in 0..4 {
571        let byte = (v >> (i * 8)) & 0xFF;
572        let idx = (i * 256) + (byte as usize);
573
574        // SAFETY:
575        // idx = i * 256 + byte < 4 * 256 = table.len().
576        res ^= unsafe { *table.get_unchecked(idx) };
577    }
578
579    Block32(res)
580}
581
582#[cfg(not(feature = "table-math"))]
583#[inline(always)]
584fn lift_ct_32<const N: usize>(x: u32, basis: &[u32; N]) -> u32 {
585    let mut acc = 0u32;
586    let mut i = 0usize;
587
588    while i < N {
589        let bit = (x >> i) & 1;
590        let mask = 0u32.wrapping_sub(bit);
591
592        acc ^= basis[i] & mask;
593        i += 1;
594    }
595
596    acc
597}
598
599#[inline(always)]
600fn map_ct_32(x: u32, basis: &[u32; 32]) -> u32 {
601    let mut acc = 0u32;
602    let mut i = 0usize;
603
604    while i < 32 {
605        let bit = (x >> i) & 1;
606        let mask = 0u32.wrapping_sub(bit);
607
608        acc ^= basis[i] & mask;
609        i += 1;
610    }
611
612    acc
613}
614
615// ===========================================
616// 32-BIT SIMD INSTRUCTIONS
617// ===========================================
618
619#[cfg(target_arch = "aarch64")]
620mod neon {
621    use super::*;
622    use core::arch::aarch64::*;
623    use core::mem::transmute;
624
625    const _: () = assert!(constants::POLY_32 == 0x8d, "verus twins hardcode R = 0x8d");
626
627    #[inline(always)]
628    pub fn add_packed_32(lhs: PackedBlock32, rhs: PackedBlock32) -> PackedBlock32 {
629        unsafe {
630            let l: uint8x16_t = transmute::<[Block32; PACKED_WIDTH_32], uint8x16_t>(lhs.0);
631            let r: uint8x16_t = transmute::<[Block32; PACKED_WIDTH_32], uint8x16_t>(rhs.0);
632            let res = veorq_u8(l, r);
633            let out: [Block32; PACKED_WIDTH_32] =
634                transmute::<uint8x16_t, [Block32; PACKED_WIDTH_32]>(res);
635
636            PackedBlock32(out)
637        }
638    }
639
640    #[cfg(pmull)]
641    #[inline(always)]
642    pub fn mul_flat_packed_32(lhs: PackedBlock32, rhs: PackedBlock32) -> PackedBlock32 {
643        let r0 = mul_flat_32(lhs.0[0], rhs.0[0]);
644        let r1 = mul_flat_32(lhs.0[1], rhs.0[1]);
645        let r2 = mul_flat_32(lhs.0[2], rhs.0[2]);
646        let r3 = mul_flat_32(lhs.0[3], rhs.0[3]);
647
648        PackedBlock32([r0, r1, r2, r3])
649    }
650
651    #[cfg(pmull)]
652    #[inline(always)]
653    pub fn mul_flat_32(a: Block32, b: Block32) -> Block32 {
654        unsafe {
655            // 1. Multiply 32x32 -> 64
656            // Cast u32 to u64 for vmull
657            let prod = vmull_p64(a.0 as u64, b.0 as u64);
658
659            // The result is 128-bit type, but only care
660            // about low 64 bits because 32*32 fits in 64 bits.
661            let prod_u64: uint64x2_t = transmute(prod);
662            let prod_val = vgetq_lane_u64(prod_u64, 0);
663
664            let l = (prod_val & 0xFFFFFFFF) as u32;
665            let h = (prod_val >> 32) as u32;
666
667            // 2. Reduce mod P(x) = x^32 + R(x)
668            let r_val = constants::POLY_32 as u64;
669
670            // H * R
671            let h_red = vmull_p64(h as u64, r_val);
672            let h_red_vec: uint64x2_t = transmute(h_red);
673            let h_red_val = vgetq_lane_u64(h_red_vec, 0);
674
675            let folded = (h_red_val & 0xFFFFFFFF) as u32;
676            let carry = (h_red_val >> 32) as u32;
677
678            let mut res = l ^ folded;
679
680            // 3. Reduce carry
681            let carry_red = vmull_p64(carry as u64, r_val);
682            let carry_res_vec: uint64x2_t = transmute(carry_red);
683            let carry_val = vgetq_lane_u64(carry_res_vec, 0);
684
685            res ^= carry_val as u32;
686
687            Block32(res)
688        }
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695    use proptest::prelude::*;
696    use rand::{RngExt, rng};
697
698    // ==================================
699    // BASIC
700    // ==================================
701
702    #[test]
703    fn tower_constants() {
704        // Check that tau is propagated correctly
705        // For Block32, tau must be (0, 1) from Block16.
706        let tau32 = Block32::EXTENSION_TAU;
707        let (lo32, hi32) = tau32.split();
708        assert_eq!(lo32, Block16::ZERO);
709        assert_eq!(hi32, Block16::TAU);
710    }
711
712    #[test]
713    fn add_truth() {
714        let zero = Block32::ZERO;
715        let one = Block32::ONE;
716
717        assert_eq!(zero + zero, zero);
718        assert_eq!(zero + one, one);
719        assert_eq!(one + zero, one);
720        assert_eq!(one + one, zero);
721    }
722
723    #[test]
724    fn mul_truth() {
725        let zero = Block32::ZERO;
726        let one = Block32::ONE;
727
728        assert_eq!(zero * zero, zero);
729        assert_eq!(zero * one, zero);
730        assert_eq!(one * one, one);
731    }
732
733    #[test]
734    fn add() {
735        // 5 ^ 3 = 6
736        // 101 ^ 011 = 110
737        assert_eq!(Block32(5) + Block32(3), Block32(6));
738    }
739
740    #[test]
741    fn mul_simple() {
742        // Check for prime numbers (without overflow)
743        // x^1 * x^1 = x^2 (2 * 2 = 4)
744        assert_eq!(Block32(2) * Block32(2), Block32(4));
745    }
746
747    #[test]
748    fn mul_overflow() {
749        // Reduction verification (AES test vectors)
750        // Example from the AES specification:
751        // 0x57 * 0x83 = 0xC1
752        assert_eq!(Block32(0x57) * Block32(0x83), Block32(0xC1));
753    }
754
755    #[test]
756    fn karatsuba_correctness() {
757        // Let A = X (hi=1, lo=0)
758        // Let B = X (hi=1, lo=0)
759        // A * B = X^2
760        // According to the rule:
761        // X^2 = X + tau
762        // Where tau for Block16 = 0x2000.
763        // So the result should be:
764        // hi=1 (X), lo=0x20 (tau)
765
766        // Construct X manually
767        let x = Block32::new(Block16::ZERO, Block16::ONE);
768        let squared = x * x;
769
770        // Verify result via splitting
771        let (res_lo, res_hi) = squared.split();
772
773        assert_eq!(res_hi, Block16::ONE, "X^2 should contain X component");
774        assert_eq!(
775            res_lo,
776            Block16(0x2000),
777            "X^2 should contain tau component (0x2000)"
778        );
779    }
780
781    #[test]
782    fn mul_tau_matches_mul() {
783        let mut rng = rng();
784        let edges = [Block32::ZERO, Block32::ONE, Block32::TAU, Block32(u32::MAX)];
785        let random = (0..1000).map(|_| Block32(rng.random()));
786
787        for x in edges.into_iter().chain(random) {
788            assert_eq!(
789                x.mul_tau(),
790                x * Block32::EXTENSION_TAU,
791                "Block32 mul_tau mismatch at {:#010x}",
792                x.0
793            );
794        }
795    }
796
797    #[test]
798    fn security_zeroize() {
799        let mut secret_val = Block32::from(0xDEAD_BEEF_u32);
800        assert_ne!(secret_val, Block32::ZERO);
801
802        secret_val.zeroize();
803
804        assert_eq!(secret_val, Block32::ZERO);
805        assert_eq!(secret_val.0, 0, "Block32 memory leak detected");
806    }
807
808    #[test]
809    fn invert_zero() {
810        // Verify that inverting zero adheres
811        // to the API contract (returns 0).
812        assert_eq!(
813            Block32::ZERO.invert(),
814            Block32::ZERO,
815            "invert(0) must return 0"
816        );
817    }
818
819    #[test]
820    fn inversion_random() {
821        let mut rng = rng();
822        for _ in 0..1000 {
823            let val = Block32(rng.random());
824
825            if val != Block32::ZERO {
826                let inv = val.invert();
827                let res = val * inv;
828
829                assert_eq!(
830                    res,
831                    Block32::ONE,
832                    "Inversion identity failed: a * a^-1 != 1"
833                );
834            }
835        }
836    }
837
838    #[test]
839    fn tower_embedding() {
840        let mut rng = rng();
841        for _ in 0..100 {
842            let a_u16: u16 = rng.random();
843            let b_u16: u16 = rng.random();
844            let a = Block16(a_u16);
845            let b = Block16(b_u16);
846
847            // 1. Structure check
848            let a_lifted: Block32 = a.into();
849            let (lo, hi) = a_lifted.split();
850
851            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
852            assert_eq!(
853                hi,
854                Block16::ZERO,
855                "Embedding structure failed: high part must be zero"
856            );
857
858            // 2. Addition Homomorphism
859            let sum_sub = a + b;
860            let sum_lifted: Block32 = sum_sub.into();
861            let sum_manual = Block32::from(a) + Block32::from(b);
862
863            assert_eq!(sum_lifted, sum_manual, "Homomorphism failed: add");
864
865            // 3. Multiplication Homomorphism
866            let prod_sub = a * b;
867            let prod_lifted: Block32 = prod_sub.into();
868            let prod_manual = Block32::from(a) * Block32::from(b);
869
870            assert_eq!(prod_lifted, prod_manual, "Homomorphism failed: mul");
871        }
872    }
873
874    // ==================================
875    // HARDWARE
876    // ==================================
877
878    #[test]
879    fn isomorphism_roundtrip() {
880        let mut rng = rng();
881        for _ in 0..1000 {
882            let val = Block32(rng.random::<u32>());
883            assert_eq!(
884                val.to_hardware().to_tower(),
885                val,
886                "Block32 isomorphism roundtrip failed"
887            );
888        }
889    }
890
891    #[test]
892    fn flat_mul_homomorphism() {
893        let mut rng = rng();
894        for _ in 0..1000 {
895            let a = Block32(rng.random::<u32>());
896            let b = Block32(rng.random::<u32>());
897            assert_eq!(a.to_hardware() * b.to_hardware(), (a * b).to_hardware());
898        }
899    }
900
901    #[test]
902    fn promote_block8_matches_embedding() {
903        for v in 0u16..256 {
904            let x = Block8(v as u8);
905            assert_eq!(
906                <Block32 as FlatPromote<Block8>>::promote_flat(x.to_hardware()),
907                Block32::from(x).to_hardware(),
908                "Block8 -> Block32 promote mismatch at {v:#04x}"
909            );
910        }
911    }
912
913    #[test]
914    fn promote_block16_matches_embedding() {
915        for v in 0u32..0x1_0000 {
916            let x = Block16(v as u16);
917            assert_eq!(
918                <Block32 as FlatPromote<Block16>>::promote_flat(x.to_hardware()),
919                Block32::from(x).to_hardware(),
920                "Block16 -> Block32 promote mismatch at {v:#06x}"
921            );
922        }
923    }
924
925    #[test]
926    fn packed_consistency() {
927        let mut rng = rng();
928        let mut a_vals = [Block32::ZERO; 4];
929        let mut b_vals = [Block32::ZERO; 4];
930
931        for i in 0..4 {
932            a_vals[i] = Block32(rng.random::<u32>());
933            b_vals[i] = Block32(rng.random::<u32>());
934        }
935
936        // Add consistency
937        let a_flat_vals = a_vals.map(|x| x.to_hardware());
938        let b_flat_vals = b_vals.map(|x| x.to_hardware());
939        let add_res = Block32::add_hardware_packed(
940            Flat::<Block32>::pack(&a_flat_vals),
941            Flat::<Block32>::pack(&b_flat_vals),
942        );
943
944        let mut add_out = [Block32::ZERO.to_hardware(); 4];
945        Flat::<Block32>::unpack(add_res, &mut add_out);
946
947        for i in 0..4 {
948            assert_eq!(add_out[i], (a_vals[i] + b_vals[i]).to_hardware());
949        }
950
951        // Mul consistency (Flat basis)
952        let mul_res = Block32::mul_hardware_packed(
953            Flat::<Block32>::pack(&a_flat_vals),
954            Flat::<Block32>::pack(&b_flat_vals),
955        );
956
957        let mut mul_out = [Block32::ZERO.to_hardware(); 4];
958        Flat::<Block32>::unpack(mul_res, &mut mul_out);
959
960        for i in 0..4 {
961            assert_eq!(mul_out[i], (a_vals[i] * b_vals[i]).to_hardware());
962        }
963    }
964
965    // ==================================
966    // PACKED
967    // ==================================
968
969    #[test]
970    fn pack_unpack_roundtrip() {
971        let mut rng = rng();
972        let mut data = [Block32::ZERO; PACKED_WIDTH_32];
973
974        for v in data.iter_mut() {
975            *v = Block32(rng.random());
976        }
977
978        let packed = Block32::pack(&data);
979        let mut unpacked = [Block32::ZERO; PACKED_WIDTH_32];
980        Block32::unpack(packed, &mut unpacked);
981
982        assert_eq!(data, unpacked);
983    }
984
985    #[test]
986    fn packed_add_consistency() {
987        let mut rng = rng();
988        let a_vals = [
989            Block32(rng.random()),
990            Block32(rng.random()),
991            Block32(rng.random()),
992            Block32(rng.random()),
993        ];
994        let b_vals = [
995            Block32(rng.random()),
996            Block32(rng.random()),
997            Block32(rng.random()),
998            Block32(rng.random()),
999        ];
1000
1001        let res_packed = Block32::pack(&a_vals) + Block32::pack(&b_vals);
1002        let mut res_unpacked = [Block32::ZERO; PACKED_WIDTH_32];
1003        Block32::unpack(res_packed, &mut res_unpacked);
1004
1005        for i in 0..PACKED_WIDTH_32 {
1006            assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
1007        }
1008    }
1009
1010    #[test]
1011    fn packed_mul_consistency() {
1012        let mut rng = rng();
1013
1014        for _ in 0..1000 {
1015            let mut a_arr = [Block32::ZERO; PACKED_WIDTH_32];
1016            let mut b_arr = [Block32::ZERO; PACKED_WIDTH_32];
1017
1018            for i in 0..PACKED_WIDTH_32 {
1019                let val_a: u32 = rng.random();
1020                let val_b: u32 = rng.random();
1021                a_arr[i] = Block32(val_a);
1022                b_arr[i] = Block32(val_b);
1023            }
1024
1025            let a_packed = PackedBlock32(a_arr);
1026            let b_packed = PackedBlock32(b_arr);
1027
1028            // Perform SIMD multiplication
1029            let c_packed = a_packed * b_packed;
1030
1031            // Verify against Scalar
1032            let mut c_expected = [Block32::ZERO; PACKED_WIDTH_32];
1033            for i in 0..PACKED_WIDTH_32 {
1034                c_expected[i] = a_arr[i] * b_arr[i];
1035            }
1036
1037            assert_eq!(c_packed.0, c_expected, "SIMD Block32 mismatch!");
1038        }
1039    }
1040
1041    proptest! {
1042        #[test]
1043        fn parity_masks_match_from_hardware(x_flat in any::<u32>()) {
1044            let tower = Block32::from_hardware(Flat::from_raw(Block32(x_flat))).0;
1045
1046            for k in 0..32 {
1047                let bit = ((tower >> k) & 1) as u8;
1048                let via_api = Flat::from_raw(Block32(x_flat)).tower_bit(k);
1049
1050                prop_assert_eq!(
1051                    via_api, bit,
1052                    "Block32 tower_bit_from_hardware mismatch at x_flat={:#010x}, bit_idx={}",
1053                    x_flat, k
1054                );
1055            }
1056        }
1057    }
1058}