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