1use 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#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
48#[repr(transparent)]
49pub struct Block32(pub u32);
50
51impl Block32 {
52 pub const TAU: Self = Block32(0x2000_0000);
55
56 pub fn new(lo: Block16, hi: Block16) -> Self {
58 Self((hi.0 as u32) << 16 | (lo.0 as u32))
59 }
60
61 #[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
216impl 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
241pub const PACKED_WIDTH_32: usize = 4;
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
250#[repr(C, align(16))]
251pub struct PackedBlock32(pub [Block32; PACKED_WIDTH_32]);
252
253impl PackedBlock32 {
254 #[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
374impl 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 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
541impl_binary_field_extras!(Block32, map_ct_32, TRACE_MASK_32, SOLVE_QUADRATIC_BASIS_32);
546
547#[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 for i in 0..4 {
571 let byte = (v >> (i * 8)) & 0xFF;
572 let idx = (i * 256) + (byte as usize);
573
574 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#[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 let prod = vmull_p64(a.0 as u64, b.0 as u64);
658
659 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 let r_val = constants::POLY_32 as u64;
669
670 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 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 #[test]
703 fn tower_constants() {
704 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 assert_eq!(Block32(5) + Block32(3), Block32(6));
738 }
739
740 #[test]
741 fn mul_simple() {
742 assert_eq!(Block32(2) * Block32(2), Block32(4));
745 }
746
747 #[test]
748 fn mul_overflow() {
749 assert_eq!(Block32(0x57) * Block32(0x83), Block32(0xC1));
753 }
754
755 #[test]
756 fn karatsuba_correctness() {
757 let x = Block32::new(Block16::ZERO, Block16::ONE);
768 let squared = x * x;
769
770 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 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 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 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 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 #[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 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 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 #[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 let c_packed = a_packed * b_packed;
1030
1031 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}