1use crate::algebra::impl_binary_field_extras;
20use crate::constants::FLAT_TO_TOWER_BIT_MASKS_64;
21use crate::towers::bit::Bit;
22use crate::towers::block8::Block8;
23use crate::towers::block16::Block16;
24use crate::towers::block32::Block32;
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 CtConvertBasisU64<const N: usize>([u64; N]);
36
37#[cfg(not(feature = "table-math"))]
38static TOWER_TO_FLAT_BASIS_64: CtConvertBasisU64<64> =
39 CtConvertBasisU64(constants::RAW_TOWER_TO_FLAT_64);
40
41#[cfg(not(feature = "table-math"))]
42static FLAT_TO_TOWER_BASIS_64: CtConvertBasisU64<64> =
43 CtConvertBasisU64(constants::RAW_FLAT_TO_TOWER_64);
44
45#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
46#[repr(transparent)]
47pub struct Block64(pub u64);
48
49impl Block64 {
50 pub const TAU: Self = Block64(0x2000_0000_0000_0000);
52
53 pub fn new(lo: Block32, hi: Block32) -> Self {
54 Self((hi.0 as u64) << 32 | (lo.0 as u64))
55 }
56
57 #[inline(always)]
58 pub fn split(self) -> (Block32, Block32) {
59 (Block32(self.0 as u32), Block32((self.0 >> 32) as u32))
60 }
61}
62
63impl TowerField for Block64 {
64 const BITS: usize = 64;
65 const ZERO: Self = Block64(0);
66 const ONE: Self = Block64(1);
67
68 const EXTENSION_TAU: Self = Self::TAU;
69
70 fn invert(&self) -> Self {
71 let (l, h) = self.split();
72 let h2 = h * h;
73 let l2 = l * l;
74 let hl = h * l;
75 let norm = (h2 * Block32::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; 8];
86 buf.copy_from_slice(&bytes[0..8]);
87
88 Self(u64::from_le_bytes(buf))
89 }
90}
91
92impl Add for Block64 {
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 Block64 {
101 type Output = Self;
102
103 fn sub(self, rhs: Self) -> Self {
104 self.add(rhs)
105 }
106}
107
108impl Mul for Block64 {
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 * Block32::TAU);
121
122 Self::new(c_lo, c_hi)
123 }
124}
125
126impl AddAssign for Block64 {
127 fn add_assign(&mut self, rhs: Self) {
128 self.0.bitxor_assign(rhs.0);
129 }
130}
131
132impl SubAssign for Block64 {
133 fn sub_assign(&mut self, rhs: Self) {
134 self.0.bitxor_assign(rhs.0);
135 }
136}
137
138impl MulAssign for Block64 {
139 fn mul_assign(&mut self, rhs: Self) {
140 *self = *self * rhs;
141 }
142}
143
144impl CanonicalSerialize for Block64 {
145 fn serialized_size(&self) -> usize {
146 8
147 }
148
149 fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
150 if writer.len() < 8 {
151 return Err(());
152 }
153
154 writer[..8].copy_from_slice(&self.0.to_le_bytes());
155
156 Ok(())
157 }
158}
159
160impl CanonicalDeserialize for Block64 {
161 fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
162 if bytes.len() < 8 {
163 return Err(());
164 }
165
166 let mut buf = [0u8; 8];
167 buf.copy_from_slice(&bytes[0..8]);
168
169 Ok(Self(u64::from_le_bytes(buf)))
170 }
171}
172
173impl From<u8> for Block64 {
174 #[inline(always)]
175 fn from(val: u8) -> Self {
176 Self(val as u64)
177 }
178}
179
180impl From<u32> for Block64 {
181 #[inline(always)]
182 fn from(val: u32) -> Self {
183 Self::from(val as u64)
184 }
185}
186
187impl From<u64> for Block64 {
188 #[inline(always)]
189 fn from(val: u64) -> Self {
190 Self(val)
191 }
192}
193
194impl From<u128> for Block64 {
195 #[inline(always)]
196 fn from(val: u128) -> Self {
197 Self(val as u64)
198 }
199}
200
201impl From<Bit> for Block64 {
206 #[inline(always)]
207 fn from(val: Bit) -> Self {
208 Self(val.get() as u64)
209 }
210}
211
212impl From<Block8> for Block64 {
213 #[inline(always)]
214 fn from(val: Block8) -> Self {
215 Self(val.0 as u64)
216 }
217}
218
219impl From<Block16> for Block64 {
220 #[inline(always)]
221 fn from(val: Block16) -> Self {
222 Self(val.0 as u64)
223 }
224}
225
226impl From<Block32> for Block64 {
227 #[inline(always)]
228 fn from(val: Block32) -> Self {
229 Self(val.0 as u64)
230 }
231}
232
233pub const PACKED_WIDTH_64: usize = 2;
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
240#[repr(C, align(16))] pub struct PackedBlock64(pub [Block64; PACKED_WIDTH_64]);
242
243impl PackedBlock64 {
244 #[inline(always)]
245 pub fn zero() -> Self {
246 Self([Block64::ZERO; PACKED_WIDTH_64])
247 }
248}
249
250impl PackableField for Block64 {
251 type Packed = PackedBlock64;
252
253 const WIDTH: usize = PACKED_WIDTH_64;
254
255 #[inline(always)]
256 fn pack(chunk: &[Self]) -> Self::Packed {
257 assert!(
258 chunk.len() >= PACKED_WIDTH_64,
259 "PackableField::pack: input slice too short",
260 );
261
262 let mut arr = [Self::ZERO; PACKED_WIDTH_64];
263 arr.copy_from_slice(&chunk[..PACKED_WIDTH_64]);
264
265 PackedBlock64(arr)
266 }
267
268 #[inline(always)]
269 fn unpack(packed: Self::Packed, output: &mut [Self]) {
270 assert!(
271 output.len() >= PACKED_WIDTH_64,
272 "PackableField::unpack: output slice too short",
273 );
274
275 output[..PACKED_WIDTH_64].copy_from_slice(&packed.0);
276 }
277}
278
279impl Add for PackedBlock64 {
280 type Output = Self;
281
282 #[inline(always)]
283 fn add(self, rhs: Self) -> Self {
284 let mut res = [Block64::ZERO; PACKED_WIDTH_64];
285 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
286 *out = *l + *r;
287 }
288
289 Self(res)
290 }
291}
292
293impl AddAssign for PackedBlock64 {
294 #[inline(always)]
295 fn add_assign(&mut self, rhs: Self) {
296 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
297 *l += *r;
298 }
299 }
300}
301
302impl Sub for PackedBlock64 {
303 type Output = Self;
304
305 #[inline(always)]
306 fn sub(self, rhs: Self) -> Self {
307 self.add(rhs)
308 }
309}
310
311impl SubAssign for PackedBlock64 {
312 #[inline(always)]
313 fn sub_assign(&mut self, rhs: Self) {
314 self.add_assign(rhs);
315 }
316}
317
318impl Mul for PackedBlock64 {
319 type Output = Self;
320
321 #[inline(always)]
322 fn mul(self, rhs: Self) -> Self {
323 #[cfg(target_arch = "aarch64")]
324 {
325 let a0 = mul_iso_64(self.0[0], rhs.0[0]);
326 let a1 = mul_iso_64(self.0[1], rhs.0[1]);
327
328 Self([a0, a1])
329 }
330
331 #[cfg(not(target_arch = "aarch64"))]
332 {
333 let mut res = [Block64::ZERO; PACKED_WIDTH_64];
334 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
335 *out = *l * *r;
336 }
337
338 Self(res)
339 }
340 }
341}
342
343impl MulAssign for PackedBlock64 {
344 #[inline(always)]
345 fn mul_assign(&mut self, rhs: Self) {
346 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
347 *l *= *r;
348 }
349 }
350}
351
352impl Mul<Block64> for PackedBlock64 {
353 type Output = Self;
354
355 #[inline(always)]
356 fn mul(self, rhs: Block64) -> Self {
357 let mut res = [Block64::ZERO; PACKED_WIDTH_64];
358 for (out, v) in res.iter_mut().zip(self.0.iter()) {
359 *out = *v * rhs;
360 }
361
362 Self(res)
363 }
364}
365
366impl HardwareField for Block64 {
371 #[inline(always)]
372 fn to_hardware(self) -> Flat<Self> {
373 #[cfg(feature = "table-math")]
374 {
375 Flat::from_raw(apply_matrix_64(self, &constants::TOWER_TO_FLAT_64))
376 }
377
378 #[cfg(not(feature = "table-math"))]
379 {
380 Flat::from_raw(Block64(map_ct_64(self.0, &TOWER_TO_FLAT_BASIS_64.0)))
381 }
382 }
383
384 #[inline(always)]
385 fn from_hardware(value: Flat<Self>) -> Self {
386 let value = value.into_raw();
387
388 #[cfg(feature = "table-math")]
389 {
390 apply_matrix_64(value, &constants::FLAT_TO_TOWER_64)
391 }
392
393 #[cfg(not(feature = "table-math"))]
394 {
395 Block64(map_ct_64(value.0, &FLAT_TO_TOWER_BASIS_64.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_64(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_64(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_64(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 = PackedBlock64([rhs.into_raw(); PACKED_WIDTH_64]);
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 = FLAT_TO_TOWER_BIT_MASKS_64[bit_idx];
475
476 let mut v = value.into_raw().0 & mask;
480 v ^= v >> 32;
481 v ^= v >> 16;
482 v ^= v >> 8;
483 v ^= v >> 4;
484
485 let idx = (v & 0xF) as u8;
486
487 ((0x6996u16 >> idx) & 1) as u8
490 }
491}
492
493impl FlatPromote<Block8> for Block64 {
494 #[inline(always)]
495 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
496 let val = val.into_raw();
497
498 #[cfg(not(feature = "table-math"))]
499 {
500 let mut acc = 0u64;
501 for i in 0..8 {
502 let bit = (val.0 >> i) & 1;
503 let mask = 0u64.wrapping_sub(bit as u64);
504 acc ^= constants::LIFT_BASIS_8_TO_64[i] & mask;
505 }
506
507 Flat::from_raw(Block64(acc))
508 }
509
510 #[cfg(feature = "table-math")]
511 {
512 Flat::from_raw(Block64(constants::LIFT_TABLE_8_TO_64[val.0 as usize]))
513 }
514 }
515}
516
517impl_binary_field_extras!(
522 Block64,
523 Block32,
524 map_ct_64,
525 TRACE_MASK_64,
526 SOLVE_QUADRATIC_BASIS_64
527);
528
529#[cfg(target_arch = "aarch64")]
534#[inline(always)]
535pub fn mul_iso_64(a: Block64, b: Block64) -> Block64 {
536 let a_flat = a.to_hardware();
537 let b_flat = b.to_hardware();
538
539 let c_flat = Flat::from_raw(neon::mul_flat_64(a_flat.into_raw(), b_flat.into_raw()));
540
541 c_flat.to_tower()
542}
543
544#[cfg(feature = "table-math")]
545#[inline(always)]
546pub fn apply_matrix_64(val: Block64, table: &[u64; 2048]) -> Block64 {
547 let mut res = 0u64;
548 let v = val.0;
549
550 for i in 0..8 {
552 let byte = (v >> (i * 8)) & 0xFF;
553 let idx = (i * 256) + (byte as usize);
554 res ^= unsafe { *table.get_unchecked(idx) };
555 }
556
557 Block64(res)
558}
559
560#[inline(always)]
561fn map_ct_64(x: u64, basis: &[u64; 64]) -> u64 {
562 let mut acc = 0u64;
563 let mut i = 0usize;
564
565 while i < 64 {
566 let bit = (x >> i) & 1;
567 let mask = 0u64.wrapping_sub(bit);
568 acc ^= basis[i] & mask;
569 i += 1;
570 }
571
572 acc
573}
574
575#[cfg(target_arch = "aarch64")]
580mod neon {
581 use super::*;
582 use core::arch::aarch64::*;
583 use core::mem::transmute;
584
585 const _: () = assert!(constants::POLY_64 == 0x1b, "verus twins hardcode R = 0x1b");
586
587 #[inline(always)]
588 pub fn add_packed_64(lhs: PackedBlock64, rhs: PackedBlock64) -> PackedBlock64 {
589 unsafe {
590 let l: uint8x16_t = transmute::<[Block64; PACKED_WIDTH_64], uint8x16_t>(lhs.0);
591 let r: uint8x16_t = transmute::<[Block64; PACKED_WIDTH_64], uint8x16_t>(rhs.0);
592 let res = veorq_u8(l, r);
593 let out: [Block64; PACKED_WIDTH_64] =
594 transmute::<uint8x16_t, [Block64; PACKED_WIDTH_64]>(res);
595
596 PackedBlock64(out)
597 }
598 }
599
600 #[inline(always)]
601 pub fn mul_flat_packed_64(lhs: PackedBlock64, rhs: PackedBlock64) -> PackedBlock64 {
602 unsafe {
603 let a: uint64x2_t = transmute(lhs.0);
604 let b: uint64x2_t = transmute(rhs.0);
605
606 let a_lo = vget_low_u64(a);
607 let b_lo = vget_low_u64(b);
608
609 let p0: uint64x2_t =
610 transmute(vmull_p64(vget_lane_u64(a_lo, 0), vget_lane_u64(b_lo, 0)));
611
612 let a_hi = vget_high_u64(a);
613 let b_hi = vget_high_u64(b);
614 let p1: uint64x2_t =
615 transmute(vmull_p64(vget_lane_u64(a_hi, 0), vget_lane_u64(b_hi, 0)));
616
617 let r0 = reduce_64(p0);
618 let r1 = reduce_64(p1);
619
620 PackedBlock64([r0, r1])
621 }
622 }
623
624 #[inline(always)]
625 fn reduce_64(prod: uint64x2_t) -> Block64 {
626 unsafe {
627 let l = vgetq_lane_u64(prod, 0);
628 let h = vgetq_lane_u64(prod, 1);
629
630 let r_val = constants::POLY_64;
631
632 let h_red: uint64x2_t = transmute(vmull_p64(h, r_val));
633
634 let folded = vgetq_lane_u64(h_red, 0);
635 let carry = vgetq_lane_u64(h_red, 1);
636
637 let mut res = l ^ folded;
638
639 let carry_red: uint64x2_t = transmute(vmull_p64(carry, r_val));
640 res ^= vgetq_lane_u64(carry_red, 0);
641
642 Block64(res)
643 }
644 }
645
646 #[inline(always)]
647 pub fn mul_flat_64(a: Block64, b: Block64) -> Block64 {
648 unsafe {
649 let prod = vmull_p64(a.0, b.0);
651 let prod_u64: uint64x2_t = transmute(prod);
652
653 let l = vgetq_lane_u64(prod_u64, 0);
654 let h = vgetq_lane_u64(prod_u64, 1);
655
656 let r_val = constants::POLY_64; let h_red = vmull_p64(h, r_val);
661 let h_red_u64: uint64x2_t = transmute(h_red);
662
663 let folded = vgetq_lane_u64(h_red_u64, 0);
664 let carry = vgetq_lane_u64(h_red_u64, 1);
665
666 let mut res = l ^ folded;
667
668 let carry_red = vmull_p64(carry, r_val);
670 let carry_res_vec: uint64x2_t = transmute(carry_red);
671 let carry_val = vgetq_lane_u64(carry_res_vec, 0);
672
673 res ^= carry_val;
674
675 Block64(res)
676 }
677 }
678}
679
680#[cfg(test)]
685mod tests {
686 use super::*;
687 use proptest::prelude::*;
688 use rand::{RngExt, rng};
689
690 #[test]
695 fn tower_constants() {
696 let tau64 = Block64::EXTENSION_TAU;
699 let (lo64, hi64) = tau64.split();
700 assert_eq!(lo64, Block32::ZERO);
701 assert_eq!(hi64, Block32::TAU);
702 }
703
704 #[test]
705 fn add_truth() {
706 let zero = Block64::ZERO;
707 let one = Block64::ONE;
708
709 assert_eq!(zero + zero, zero);
710 assert_eq!(zero + one, one);
711 assert_eq!(one + zero, one);
712 assert_eq!(one + one, zero);
713 }
714
715 #[test]
716 fn mul_truth() {
717 let zero = Block64::ZERO;
718 let one = Block64::ONE;
719
720 assert_eq!(zero * zero, zero);
721 assert_eq!(zero * one, zero);
722 assert_eq!(one * one, one);
723 }
724
725 #[test]
726 fn add() {
727 assert_eq!(Block64(5) + Block64(3), Block64(6));
730 }
731
732 #[test]
733 fn mul_simple() {
734 assert_eq!(Block64(2) * Block64(2), Block64(4));
737 }
738
739 #[test]
740 fn mul_overflow() {
741 assert_eq!(Block64(0x57) * Block64(0x83), Block64(0xC1));
745 }
746
747 #[test]
748 fn karatsuba_correctness() {
749 let x = Block64::new(Block32::ZERO, Block32::ONE);
761 let squared = x * x;
762
763 let (res_lo, res_hi) = squared.split();
765
766 assert_eq!(res_hi, Block32::ONE, "X^2 should contain X component");
767 assert_eq!(
768 res_lo,
769 Block32(0x2000_0000),
770 "X^2 should contain tau component (0x2000_0000)"
771 );
772 }
773
774 #[test]
775 fn security_zeroize() {
776 let mut secret_val = Block64::from(0xDEAD_BEEF_CAFE_BABE_u64);
777 assert_ne!(secret_val, Block64::ZERO);
778
779 secret_val.zeroize();
780
781 assert_eq!(secret_val, Block64::ZERO);
782 assert_eq!(secret_val.0, 0, "Block64 memory leak detected");
783 }
784
785 #[test]
786 fn invert_zero() {
787 assert_eq!(
789 Block64::ZERO.invert(),
790 Block64::ZERO,
791 "invert(0) must return 0"
792 );
793 }
794
795 #[test]
796 fn inversion_random() {
797 let mut rng = rng();
798 for _ in 0..1000 {
799 let val = Block64(rng.random());
800 if val != Block64::ZERO {
801 let inv = val.invert();
802 assert_eq!(
803 val * inv,
804 Block64::ONE,
805 "Inversion identity failed: a * a^-1 != 1"
806 );
807 }
808 }
809 }
810
811 #[test]
812 fn tower_embedding() {
813 let mut rng = rng();
814 for _ in 0..100 {
815 let a = Block32(rng.random());
816 let b = Block32(rng.random());
817
818 let a_lifted: Block64 = a.into();
820 let (lo, hi) = a_lifted.split();
821
822 assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
823 assert_eq!(
824 hi,
825 Block32::ZERO,
826 "Embedding structure failed: high part must be zero"
827 );
828
829 let sum_sub = a + b;
831 let sum_lifted: Block64 = sum_sub.into();
832 let sum_in_super = Block64::from(a) + Block64::from(b);
833
834 assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
835
836 let prod_sub = a * b;
838 let prod_lifted: Block64 = prod_sub.into();
839 let prod_in_super = Block64::from(a) * Block64::from(b);
840
841 assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
842 }
843 }
844
845 #[test]
850 fn isomorphism_roundtrip() {
851 let mut rng = rng();
852 for _ in 0..1000 {
853 let val = Block64(rng.random::<u64>());
854 assert_eq!(val.to_hardware().to_tower(), val);
855 }
856 }
857
858 #[test]
859 fn flat_mul_homomorphism() {
860 let mut rng = rng();
861 for _ in 0..1000 {
862 let a = Block64(rng.random());
863 let b = Block64(rng.random());
864
865 let expected_flat = (a * b).to_hardware();
866 let actual_flat = a.to_hardware() * b.to_hardware();
867
868 assert_eq!(
869 actual_flat, expected_flat,
870 "Block64 flat multiplication mismatch: (a*b)^H != a^H * b^H"
871 );
872 }
873 }
874
875 #[test]
876 fn packed_consistency() {
877 let mut rng = rng();
878 for _ in 0..100 {
879 let a_vals = [Block64(rng.random()), Block64(rng.random())];
880 let b_vals = [Block64(rng.random()), Block64(rng.random())];
881
882 let a_flat_vals = a_vals.map(|x| x.to_hardware());
883 let b_flat_vals = b_vals.map(|x| x.to_hardware());
884 let a_packed = Flat::<Block64>::pack(&a_flat_vals);
885 let b_packed = Flat::<Block64>::pack(&b_flat_vals);
886
887 let add_res = Block64::add_hardware_packed(a_packed, b_packed);
889
890 let mut add_out = [Block64::ZERO.to_hardware(); 2];
891 Flat::<Block64>::unpack(add_res, &mut add_out);
892
893 assert_eq!(add_out[0], (a_vals[0] + b_vals[0]).to_hardware());
894 assert_eq!(add_out[1], (a_vals[1] + b_vals[1]).to_hardware());
895
896 let mul_res = Block64::mul_hardware_packed(a_packed, b_packed);
898
899 let mut mul_out = [Block64::ZERO.to_hardware(); 2];
900 Flat::<Block64>::unpack(mul_res, &mut mul_out);
901
902 assert_eq!(
903 mul_out[0],
904 (a_vals[0] * b_vals[0]).to_hardware(),
905 "Block64 SIMD mul mismatch at index 0"
906 );
907 assert_eq!(
908 mul_out[1],
909 (a_vals[1] * b_vals[1]).to_hardware(),
910 "Block64 SIMD mul mismatch at index 1"
911 );
912 }
913 }
914
915 #[test]
920 fn pack_unpack_roundtrip() {
921 let mut rng = rng();
922 let data = [Block64(rng.random()), Block64(rng.random())];
923
924 let packed = Block64::pack(&data);
925 let mut unpacked = [Block64::ZERO; 2];
926
927 Block64::unpack(packed, &mut unpacked);
928 assert_eq!(data, unpacked);
929 }
930
931 #[test]
932 fn packed_add_consistency() {
933 let mut rng = rng();
934 let a_vals = [Block64(rng.random()), Block64(rng.random())];
935 let b_vals = [Block64(rng.random()), Block64(rng.random())];
936
937 let res_packed = Block64::pack(&a_vals) + Block64::pack(&b_vals);
938 let mut res_unpacked = [Block64::ZERO; 2];
939 Block64::unpack(res_packed, &mut res_unpacked);
940
941 assert_eq!(res_unpacked[0], a_vals[0] + b_vals[0]);
942 assert_eq!(res_unpacked[1], a_vals[1] + b_vals[1]);
943 }
944
945 #[test]
946 fn packed_mul_consistency() {
947 let mut rng = rng();
948
949 for _ in 0..1000 {
950 let mut a_arr = [Block64::ZERO; PACKED_WIDTH_64];
951 let mut b_arr = [Block64::ZERO; PACKED_WIDTH_64];
952
953 for i in 0..PACKED_WIDTH_64 {
954 let val_a: u64 = rng.random();
955 let val_b: u64 = rng.random();
956 a_arr[i] = Block64(val_a);
957 b_arr[i] = Block64(val_b);
958 }
959
960 let a_packed = PackedBlock64(a_arr);
961 let b_packed = PackedBlock64(b_arr);
962
963 let c_packed = a_packed * b_packed;
965
966 let mut c_expected = [Block64::ZERO; PACKED_WIDTH_64];
968 for i in 0..PACKED_WIDTH_64 {
969 c_expected[i] = a_arr[i] * b_arr[i];
970 }
971
972 assert_eq!(c_packed.0, c_expected, "SIMD Block64 mismatch!");
973 }
974 }
975
976 proptest! {
977 #[test]
978 fn parity_masks_match_from_hardware(x_flat in any::<u64>()) {
979 let tower = Block64::from_hardware(Flat::from_raw(Block64(x_flat))).0;
980
981 for (k, &mask) in FLAT_TO_TOWER_BIT_MASKS_64.iter().enumerate() {
982 let parity = ((x_flat & mask).count_ones() & 1) as u8;
985 let bit = ((tower >> k) & 1) as u8;
986 prop_assert_eq!(parity, bit, "Block64 static mask mismatch at k={}", k);
987
988 let via_api = Flat::from_raw(Block64(x_flat)).tower_bit(k);
990 prop_assert_eq!(
991 via_api, bit,
992 "Block64 tower_bit_from_hardware mismatch at x_flat={:#018x}, bit_idx={}",
993 x_flat, k
994 );
995 }
996 }
997 }
998}