1use 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 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 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
207impl 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
232pub 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(pmull)]
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(pmull))]
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
367impl 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(pmull)]
426 {
427 Flat::from_raw(neon::mul_flat_32(lhs, rhs))
428 }
429
430 #[cfg(not(pmull))]
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(pmull)]
445 {
446 PackedFlat::from_raw(neon::mul_flat_packed_32(lhs, rhs))
447 }
448
449 #[cfg(not(pmull))]
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 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
513impl_binary_field_extras!(
518 Block32,
519 Block16,
520 map_ct_32,
521 TRACE_MASK_32,
522 SOLVE_QUADRATIC_BASIS_32
523);
524
525#[cfg(pmull)]
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 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#[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 #[cfg(pmull)]
596 #[inline(always)]
597 pub fn mul_flat_packed_32(lhs: PackedBlock32, rhs: PackedBlock32) -> PackedBlock32 {
598 let r0 = mul_flat_32(lhs.0[0], rhs.0[0]);
599 let r1 = mul_flat_32(lhs.0[1], rhs.0[1]);
600 let r2 = mul_flat_32(lhs.0[2], rhs.0[2]);
601 let r3 = mul_flat_32(lhs.0[3], rhs.0[3]);
602
603 PackedBlock32([r0, r1, r2, r3])
604 }
605
606 #[cfg(pmull)]
607 #[inline(always)]
608 pub fn mul_flat_32(a: Block32, b: Block32) -> Block32 {
609 unsafe {
610 let prod = vmull_p64(a.0 as u64, b.0 as u64);
613
614 let prod_u64: uint64x2_t = transmute(prod);
617 let prod_val = vgetq_lane_u64(prod_u64, 0);
618
619 let l = (prod_val & 0xFFFFFFFF) as u32;
620 let h = (prod_val >> 32) as u32;
621
622 let r_val = constants::POLY_32 as u64;
624
625 let h_red = vmull_p64(h as u64, r_val);
627 let h_red_vec: uint64x2_t = transmute(h_red);
628 let h_red_val = vgetq_lane_u64(h_red_vec, 0);
629
630 let folded = (h_red_val & 0xFFFFFFFF) as u32;
631 let carry = (h_red_val >> 32) as u32;
632
633 let mut res = l ^ folded;
634
635 let carry_red = vmull_p64(carry as u64, r_val);
637 let carry_res_vec: uint64x2_t = transmute(carry_red);
638 let carry_val = vgetq_lane_u64(carry_res_vec, 0);
639
640 res ^= carry_val as u32;
641
642 Block32(res)
643 }
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use proptest::prelude::*;
651 use rand::{RngExt, rng};
652
653 #[test]
658 fn tower_constants() {
659 let tau32 = Block32::EXTENSION_TAU;
662 let (lo32, hi32) = tau32.split();
663 assert_eq!(lo32, Block16::ZERO);
664 assert_eq!(hi32, Block16::TAU);
665 }
666
667 #[test]
668 fn add_truth() {
669 let zero = Block32::ZERO;
670 let one = Block32::ONE;
671
672 assert_eq!(zero + zero, zero);
673 assert_eq!(zero + one, one);
674 assert_eq!(one + zero, one);
675 assert_eq!(one + one, zero);
676 }
677
678 #[test]
679 fn mul_truth() {
680 let zero = Block32::ZERO;
681 let one = Block32::ONE;
682
683 assert_eq!(zero * zero, zero);
684 assert_eq!(zero * one, zero);
685 assert_eq!(one * one, one);
686 }
687
688 #[test]
689 fn add() {
690 assert_eq!(Block32(5) + Block32(3), Block32(6));
693 }
694
695 #[test]
696 fn mul_simple() {
697 assert_eq!(Block32(2) * Block32(2), Block32(4));
700 }
701
702 #[test]
703 fn mul_overflow() {
704 assert_eq!(Block32(0x57) * Block32(0x83), Block32(0xC1));
708 }
709
710 #[test]
711 fn karatsuba_correctness() {
712 let x = Block32::new(Block16::ZERO, Block16::ONE);
723 let squared = x * x;
724
725 let (res_lo, res_hi) = squared.split();
727
728 assert_eq!(res_hi, Block16::ONE, "X^2 should contain X component");
729 assert_eq!(
730 res_lo,
731 Block16(0x2000),
732 "X^2 should contain tau component (0x2000)"
733 );
734 }
735
736 #[test]
737 fn security_zeroize() {
738 let mut secret_val = Block32::from(0xDEAD_BEEF_u32);
739 assert_ne!(secret_val, Block32::ZERO);
740
741 secret_val.zeroize();
742
743 assert_eq!(secret_val, Block32::ZERO);
744 assert_eq!(secret_val.0, 0, "Block32 memory leak detected");
745 }
746
747 #[test]
748 fn invert_zero() {
749 assert_eq!(
752 Block32::ZERO.invert(),
753 Block32::ZERO,
754 "invert(0) must return 0"
755 );
756 }
757
758 #[test]
759 fn inversion_random() {
760 let mut rng = rng();
761 for _ in 0..1000 {
762 let val = Block32(rng.random());
763
764 if val != Block32::ZERO {
765 let inv = val.invert();
766 let res = val * inv;
767
768 assert_eq!(
769 res,
770 Block32::ONE,
771 "Inversion identity failed: a * a^-1 != 1"
772 );
773 }
774 }
775 }
776
777 #[test]
778 fn tower_embedding() {
779 let mut rng = rng();
780 for _ in 0..100 {
781 let a_u16: u16 = rng.random();
782 let b_u16: u16 = rng.random();
783 let a = Block16(a_u16);
784 let b = Block16(b_u16);
785
786 let a_lifted: Block32 = a.into();
788 let (lo, hi) = a_lifted.split();
789
790 assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
791 assert_eq!(
792 hi,
793 Block16::ZERO,
794 "Embedding structure failed: high part must be zero"
795 );
796
797 let sum_sub = a + b;
799 let sum_lifted: Block32 = sum_sub.into();
800 let sum_manual = Block32::from(a) + Block32::from(b);
801
802 assert_eq!(sum_lifted, sum_manual, "Homomorphism failed: add");
803
804 let prod_sub = a * b;
806 let prod_lifted: Block32 = prod_sub.into();
807 let prod_manual = Block32::from(a) * Block32::from(b);
808
809 assert_eq!(prod_lifted, prod_manual, "Homomorphism failed: mul");
810 }
811 }
812
813 #[test]
818 fn isomorphism_roundtrip() {
819 let mut rng = rng();
820 for _ in 0..1000 {
821 let val = Block32(rng.random::<u32>());
822 assert_eq!(
823 val.to_hardware().to_tower(),
824 val,
825 "Block32 isomorphism roundtrip failed"
826 );
827 }
828 }
829
830 #[test]
831 fn flat_mul_homomorphism() {
832 let mut rng = rng();
833 for _ in 0..1000 {
834 let a = Block32(rng.random::<u32>());
835 let b = Block32(rng.random::<u32>());
836 assert_eq!(a.to_hardware() * b.to_hardware(), (a * b).to_hardware());
837 }
838 }
839
840 #[test]
841 fn packed_consistency() {
842 let mut rng = rng();
843 let mut a_vals = [Block32::ZERO; 4];
844 let mut b_vals = [Block32::ZERO; 4];
845
846 for i in 0..4 {
847 a_vals[i] = Block32(rng.random::<u32>());
848 b_vals[i] = Block32(rng.random::<u32>());
849 }
850
851 let a_flat_vals = a_vals.map(|x| x.to_hardware());
853 let b_flat_vals = b_vals.map(|x| x.to_hardware());
854 let add_res = Block32::add_hardware_packed(
855 Flat::<Block32>::pack(&a_flat_vals),
856 Flat::<Block32>::pack(&b_flat_vals),
857 );
858
859 let mut add_out = [Block32::ZERO.to_hardware(); 4];
860 Flat::<Block32>::unpack(add_res, &mut add_out);
861
862 for i in 0..4 {
863 assert_eq!(add_out[i], (a_vals[i] + b_vals[i]).to_hardware());
864 }
865
866 let mul_res = Block32::mul_hardware_packed(
868 Flat::<Block32>::pack(&a_flat_vals),
869 Flat::<Block32>::pack(&b_flat_vals),
870 );
871
872 let mut mul_out = [Block32::ZERO.to_hardware(); 4];
873 Flat::<Block32>::unpack(mul_res, &mut mul_out);
874
875 for i in 0..4 {
876 assert_eq!(mul_out[i], (a_vals[i] * b_vals[i]).to_hardware());
877 }
878 }
879
880 #[test]
885 fn pack_unpack_roundtrip() {
886 let mut rng = rng();
887 let mut data = [Block32::ZERO; PACKED_WIDTH_32];
888
889 for v in data.iter_mut() {
890 *v = Block32(rng.random());
891 }
892
893 let packed = Block32::pack(&data);
894 let mut unpacked = [Block32::ZERO; PACKED_WIDTH_32];
895 Block32::unpack(packed, &mut unpacked);
896
897 assert_eq!(data, unpacked);
898 }
899
900 #[test]
901 fn packed_add_consistency() {
902 let mut rng = rng();
903 let a_vals = [
904 Block32(rng.random()),
905 Block32(rng.random()),
906 Block32(rng.random()),
907 Block32(rng.random()),
908 ];
909 let b_vals = [
910 Block32(rng.random()),
911 Block32(rng.random()),
912 Block32(rng.random()),
913 Block32(rng.random()),
914 ];
915
916 let res_packed = Block32::pack(&a_vals) + Block32::pack(&b_vals);
917 let mut res_unpacked = [Block32::ZERO; PACKED_WIDTH_32];
918 Block32::unpack(res_packed, &mut res_unpacked);
919
920 for i in 0..PACKED_WIDTH_32 {
921 assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
922 }
923 }
924
925 #[test]
926 fn packed_mul_consistency() {
927 let mut rng = rng();
928
929 for _ in 0..1000 {
930 let mut a_arr = [Block32::ZERO; PACKED_WIDTH_32];
931 let mut b_arr = [Block32::ZERO; PACKED_WIDTH_32];
932
933 for i in 0..PACKED_WIDTH_32 {
934 let val_a: u32 = rng.random();
935 let val_b: u32 = rng.random();
936 a_arr[i] = Block32(val_a);
937 b_arr[i] = Block32(val_b);
938 }
939
940 let a_packed = PackedBlock32(a_arr);
941 let b_packed = PackedBlock32(b_arr);
942
943 let c_packed = a_packed * b_packed;
945
946 let mut c_expected = [Block32::ZERO; PACKED_WIDTH_32];
948 for i in 0..PACKED_WIDTH_32 {
949 c_expected[i] = a_arr[i] * b_arr[i];
950 }
951
952 assert_eq!(c_packed.0, c_expected, "SIMD Block32 mismatch!");
953 }
954 }
955
956 proptest! {
957 #[test]
958 fn parity_masks_match_from_hardware(x_flat in any::<u32>()) {
959 let tower = Block32::from_hardware(Flat::from_raw(Block32(x_flat))).0;
960
961 for k in 0..32 {
962 let bit = ((tower >> k) & 1) as u8;
963 let via_api = Flat::from_raw(Block32(x_flat)).tower_bit(k);
964
965 prop_assert_eq!(
966 via_api, bit,
967 "Block32 tower_bit_from_hardware mismatch at x_flat={:#010x}, bit_idx={}",
968 x_flat, k
969 );
970 }
971 }
972 }
973}