1use crate::algebra::impl_binary_field_extras;
20use crate::towers::bit::Bit;
21use crate::towers::block8::Block8;
22use crate::towers::block16::Block16;
23use crate::towers::block32::Block32;
24use crate::towers::block64::Block64;
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 CtConvertBasisU128<const N: usize>([u128; N]);
36
37#[cfg(not(feature = "table-math"))]
38static TOWER_TO_FLAT_BASIS_128: CtConvertBasisU128<128> =
39 CtConvertBasisU128(constants::RAW_TOWER_TO_FLAT_128);
40
41#[cfg(not(feature = "table-math"))]
42static FLAT_TO_TOWER_BASIS_128: CtConvertBasisU128<128> =
43 CtConvertBasisU128(constants::RAW_FLAT_TO_TOWER_128);
44
45#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
46#[repr(transparent)]
47pub struct Block128(pub u128);
48
49impl Block128 {
50 const TAU: Self = Block128(0x2000_0000_0000_0000_0000_0000_0000_0000);
52
53 pub fn new(lo: Block64, hi: Block64) -> Self {
54 Self((hi.0 as u128) << 64 | (lo.0 as u128))
55 }
56
57 #[inline(always)]
58 pub fn split(self) -> (Block64, Block64) {
59 (Block64(self.0 as u64), Block64((self.0 >> 64) as u64))
60 }
61}
62
63impl TowerField for Block128 {
64 const BITS: usize = 128;
65 const ZERO: Self = Block128(0);
66 const ONE: Self = Block128(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 * Block64::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; 16];
86 buf.copy_from_slice(&bytes[0..16]);
87
88 Self(u128::from_le_bytes(buf))
89 }
90}
91
92impl Add for Block128 {
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 Block128 {
101 type Output = Self;
102
103 fn sub(self, rhs: Self) -> Self {
104 self.add(rhs)
105 }
106}
107
108impl Mul for Block128 {
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 * Block64::TAU);
121
122 Self::new(c_lo, c_hi)
123 }
124}
125
126impl AddAssign for Block128 {
127 fn add_assign(&mut self, rhs: Self) {
128 self.0.bitxor_assign(rhs.0);
129 }
130}
131
132impl SubAssign for Block128 {
133 fn sub_assign(&mut self, rhs: Self) {
134 self.0.bitxor_assign(rhs.0);
135 }
136}
137
138impl MulAssign for Block128 {
139 fn mul_assign(&mut self, rhs: Self) {
140 *self = *self * rhs;
141 }
142}
143
144impl CanonicalSerialize for Block128 {
145 fn serialized_size(&self) -> usize {
146 16
147 }
148
149 fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
150 if writer.len() < 16 {
151 return Err(());
152 }
153
154 writer[..16].copy_from_slice(&self.0.to_le_bytes());
155
156 Ok(())
157 }
158}
159
160impl CanonicalDeserialize for Block128 {
161 fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
162 if bytes.len() < 16 {
163 return Err(());
164 }
165
166 let mut buf = [0u8; 16];
167 buf.copy_from_slice(&bytes[0..16]);
168
169 Ok(Self(u128::from_le_bytes(buf)))
170 }
171}
172
173impl From<u8> for Block128 {
174 fn from(val: u8) -> Self {
175 Self(val as u128)
176 }
177}
178
179impl From<u32> for Block128 {
180 #[inline]
181 fn from(val: u32) -> Self {
182 Self(val as u128)
183 }
184}
185
186impl From<u64> for Block128 {
187 #[inline]
188 fn from(val: u64) -> Self {
189 Self::from(val as u128)
190 }
191}
192
193impl From<u128> for Block128 {
194 #[inline]
195 fn from(val: u128) -> Self {
196 Self(val)
197 }
198}
199
200impl From<Bit> for Block128 {
205 #[inline(always)]
206 fn from(val: Bit) -> Self {
207 Self(val.get() as u128)
208 }
209}
210
211impl From<Block8> for Block128 {
212 #[inline(always)]
213 fn from(val: Block8) -> Self {
214 Self(val.0 as u128)
215 }
216}
217
218impl From<Block16> for Block128 {
219 #[inline(always)]
220 fn from(val: Block16) -> Self {
221 Self(val.0 as u128)
222 }
223}
224
225impl From<Block32> for Block128 {
226 #[inline(always)]
227 fn from(val: Block32) -> Self {
228 Self(val.0 as u128)
229 }
230}
231
232impl From<Block64> for Block128 {
233 #[inline(always)]
234 fn from(val: Block64) -> Self {
235 Self(val.0 as u128)
236 }
237}
238
239pub const PACKED_WIDTH_128: usize = 4;
244
245#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
249#[repr(C, align(32))]
250pub struct PackedBlock128(pub [Block128; PACKED_WIDTH_128]);
251
252impl PackedBlock128 {
253 #[inline(always)]
255 pub fn zero() -> Self {
256 Self([Block128::ZERO; PACKED_WIDTH_128])
257 }
258
259 #[inline(always)]
261 pub fn broadcast(val: Block128) -> Self {
262 Self([val; PACKED_WIDTH_128])
263 }
264}
265
266impl PackableField for Block128 {
267 type Packed = PackedBlock128;
268
269 const WIDTH: usize = PACKED_WIDTH_128;
270
271 #[inline(always)]
272 fn pack(chunk: &[Self]) -> Self::Packed {
273 assert!(
274 chunk.len() >= PACKED_WIDTH_128,
275 "PackableField::pack: input slice too short",
276 );
277
278 let mut arr = [Self::ZERO; PACKED_WIDTH_128];
279 arr.copy_from_slice(&chunk[..PACKED_WIDTH_128]);
280
281 PackedBlock128(arr)
282 }
283
284 #[inline(always)]
285 fn unpack(packed: Self::Packed, output: &mut [Self]) {
286 assert!(
287 output.len() >= PACKED_WIDTH_128,
288 "PackableField::unpack: output slice too short",
289 );
290
291 output[..PACKED_WIDTH_128].copy_from_slice(&packed.0);
292 }
293}
294
295impl Add for PackedBlock128 {
300 type Output = Self;
301
302 #[inline(always)]
303 fn add(self, rhs: Self) -> Self {
304 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
305 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
306 *out = *l + *r;
307 }
308
309 Self(res)
310 }
311}
312
313impl AddAssign for PackedBlock128 {
314 #[inline(always)]
315 fn add_assign(&mut self, rhs: Self) {
316 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
317 *l += *r;
318 }
319 }
320}
321
322impl Sub for PackedBlock128 {
325 type Output = Self;
326
327 #[inline(always)]
328 fn sub(self, rhs: Self) -> Self {
329 self.add(rhs)
330 }
331}
332
333impl SubAssign for PackedBlock128 {
334 #[inline(always)]
335 fn sub_assign(&mut self, rhs: Self) {
336 self.add_assign(rhs);
337 }
338}
339
340impl Mul for PackedBlock128 {
343 type Output = Self;
344
345 #[inline(always)]
346 fn mul(self, rhs: Self) -> Self {
347 #[cfg(pmull)]
348 {
349 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
350 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
351 let a_flat = l.to_hardware();
352 let b_flat = r.to_hardware();
353 let c_flat =
354 Flat::from_raw(neon::mul_flat_128(a_flat.into_raw(), b_flat.into_raw()));
355
356 *out = c_flat.to_tower();
357 }
358
359 Self(res)
360 }
361
362 #[cfg(not(pmull))]
363 {
364 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
365 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
366 *out = *l * *r;
367 }
368
369 Self(res)
370 }
371 }
372}
373
374impl MulAssign for PackedBlock128 {
375 #[inline(always)]
376 fn mul_assign(&mut self, rhs: Self) {
377 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
378 *l *= *r;
379 }
380 }
381}
382
383impl Mul<Block128> for PackedBlock128 {
387 type Output = Self;
388
389 #[inline(always)]
390 fn mul(self, rhs: Block128) -> Self {
391 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
392 for (out, v) in res.iter_mut().zip(self.0.iter()) {
393 *out = *v * rhs;
394 }
395
396 Self(res)
397 }
398}
399
400impl MulAssign<Block128> for PackedBlock128 {
401 #[inline(always)]
402 fn mul_assign(&mut self, rhs: Block128) {
403 for v in self.0.iter_mut() {
404 *v *= rhs;
405 }
406 }
407}
408
409impl HardwareField for Block128 {
414 #[inline(always)]
415 fn to_hardware(self) -> Flat<Self> {
416 #[cfg(feature = "table-math")]
417 {
418 Flat::from_raw(apply_matrix_128(self, &constants::TOWER_TO_FLAT_128))
419 }
420
421 #[cfg(not(feature = "table-math"))]
422 {
423 Flat::from_raw(Block128(map_ct_128_split(
424 self.0,
425 &TOWER_TO_FLAT_BASIS_128.0,
426 )))
427 }
428 }
429
430 #[inline(always)]
431 fn from_hardware(value: Flat<Self>) -> Self {
432 let value = value.into_raw();
433
434 #[cfg(feature = "table-math")]
435 {
436 apply_matrix_128(value, &constants::FLAT_TO_TOWER_128)
437 }
438
439 #[cfg(not(feature = "table-math"))]
440 {
441 Block128(map_ct_128_split(value.0, &FLAT_TO_TOWER_BASIS_128.0))
442 }
443 }
444
445 #[inline(always)]
446 fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
447 let lhs = lhs.into_raw();
448 let rhs = rhs.into_raw();
449
450 #[cfg(target_arch = "aarch64")]
451 {
452 Flat::from_raw(neon::add_flat_128(lhs, rhs))
453 }
454
455 #[cfg(not(target_arch = "aarch64"))]
456 {
457 Flat::from_raw(lhs + rhs)
458 }
459 }
460
461 #[inline(always)]
462 fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
463 let lhs = lhs.into_raw();
464 let rhs = rhs.into_raw();
465
466 #[cfg(target_arch = "aarch64")]
467 {
468 PackedFlat::from_raw(neon::add_packed_128(lhs, rhs))
469 }
470
471 #[cfg(not(target_arch = "aarch64"))]
472 {
473 PackedFlat::from_raw(lhs + rhs)
474 }
475 }
476
477 #[inline(always)]
478 fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
479 let lhs = lhs.into_raw();
480 let rhs = rhs.into_raw();
481
482 #[cfg(pmull)]
483 {
484 Flat::from_raw(neon::mul_flat_128(lhs, rhs))
485 }
486
487 #[cfg(not(pmull))]
488 {
489 let a_tower = Self::from_hardware(Flat::from_raw(lhs));
490 let b_tower = Self::from_hardware(Flat::from_raw(rhs));
491
492 (a_tower * b_tower).to_hardware()
493 }
494 }
495
496 #[inline(always)]
497 fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
498 let lhs = lhs.into_raw();
499 let rhs = rhs.into_raw();
500
501 #[cfg(pmull)]
502 {
503 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
504 for ((out, l), r) in res.iter_mut().zip(lhs.0.iter()).zip(rhs.0.iter()) {
505 *out = neon::mul_flat_128(*l, *r);
506 }
507
508 PackedFlat::from_raw(PackedBlock128(res))
509 }
510
511 #[cfg(not(pmull))]
512 {
513 let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
514 let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
515 let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
516
517 Self::unpack(lhs, &mut l);
518 Self::unpack(rhs, &mut r);
519
520 for i in 0..<Self as PackableField>::WIDTH {
521 res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
522 }
523
524 PackedFlat::from_raw(Self::pack(&res))
525 }
526 }
527
528 #[inline(always)]
529 fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
530 let broadcasted = PackedBlock128::broadcast(rhs.into_raw());
531 Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
532 }
533
534 #[inline(always)]
535 fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
536 let mask = constants::FLAT_TO_TOWER_BIT_MASKS_128[bit_idx];
537
538 let mut v = value.into_raw().0 & mask;
542 v ^= v >> 64;
543 v ^= v >> 32;
544 v ^= v >> 16;
545 v ^= v >> 8;
546 v ^= v >> 4;
547 v ^= v >> 2;
548 v ^= v >> 1;
549
550 (v & 1) as u8
551 }
552}
553
554#[cfg(not(feature = "table-math"))]
563impl FlatPromote<Block8> for Block128 {
564 #[inline(always)]
565 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
566 let val = val.into_raw();
567 Flat::from_raw(Block128(lift_ct::<8>(
568 val.0 as u64,
569 &constants::LIFT_BASIS_8.0,
570 )))
571 }
572
573 fn promote_flat_batch(input: &[Flat<Block8>], output: &mut [Flat<Self>]) {
574 let n = input.len().min(output.len());
575
576 #[cfg(target_arch = "aarch64")]
577 {
578 let full = n / 16;
579 for chunk in 0..full {
580 let i = chunk * 16;
581 unsafe {
582 neon::promote_batch_8_to_128(
583 input.as_ptr().add(i).cast::<u8>(),
584 output.as_mut_ptr().add(i).cast::<u128>(),
585 );
586 }
587 }
588
589 let tail = full * 16;
590 for i in tail..n {
591 output[i] = Self::promote_flat(input[i]);
592 }
593 }
594
595 #[cfg(not(target_arch = "aarch64"))]
596 {
597 for i in 0..n {
598 output[i] = Self::promote_flat(input[i]);
599 }
600 }
601 }
602}
603
604#[cfg(not(feature = "table-math"))]
605impl FlatPromote<Block16> for Block128 {
606 #[inline(always)]
607 fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
608 Flat::from_raw(Block128(lift_ct::<16>(
609 val.into_raw().0 as u64,
610 &constants::LIFT_BASIS_16.0,
611 )))
612 }
613
614 fn promote_flat_batch(input: &[Flat<Block16>], output: &mut [Flat<Self>]) {
615 let n = input.len().min(output.len());
616
617 #[cfg(target_arch = "aarch64")]
618 {
619 let full = n / 16;
620 for chunk in 0..full {
621 let i = chunk * 16;
622 unsafe {
623 neon::promote_batch_16_to_128(
624 input.as_ptr().add(i).cast::<u8>(),
625 output.as_mut_ptr().add(i).cast::<u128>(),
626 );
627 }
628 }
629
630 let tail = full * 16;
631 for i in tail..n {
632 output[i] = Self::promote_flat(input[i]);
633 }
634 }
635
636 #[cfg(not(target_arch = "aarch64"))]
637 {
638 for i in 0..n {
639 output[i] = Self::promote_flat(input[i]);
640 }
641 }
642 }
643}
644
645#[cfg(not(feature = "table-math"))]
646impl FlatPromote<Block32> for Block128 {
647 #[inline(always)]
648 fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
649 Flat::from_raw(Block128(lift_ct::<32>(
650 val.into_raw().0 as u64,
651 &constants::LIFT_BASIS_32.0,
652 )))
653 }
654
655 fn promote_flat_batch(input: &[Flat<Block32>], output: &mut [Flat<Self>]) {
656 let n = input.len().min(output.len());
657
658 #[cfg(target_arch = "aarch64")]
659 {
660 let full = n / 16;
661 for chunk in 0..full {
662 let i = chunk * 16;
663 unsafe {
664 neon::promote_batch_32_to_128(
665 input.as_ptr().add(i).cast::<u8>(),
666 output.as_mut_ptr().add(i).cast::<u128>(),
667 );
668 }
669 }
670
671 let tail = full * 16;
672 for i in tail..n {
673 output[i] = Self::promote_flat(input[i]);
674 }
675 }
676
677 #[cfg(not(target_arch = "aarch64"))]
678 {
679 for i in 0..n {
680 output[i] = Self::promote_flat(input[i]);
681 }
682 }
683 }
684}
685
686#[cfg(not(feature = "table-math"))]
687impl FlatPromote<Block64> for Block128 {
688 #[inline(always)]
689 fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
690 Flat::from_raw(Block128(lift_ct::<64>(
691 val.into_raw().0,
692 &constants::LIFT_BASIS_64.0,
693 )))
694 }
695}
696
697#[cfg(feature = "table-math")]
699impl FlatPromote<Block8> for Block128 {
700 #[inline(always)]
701 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
702 let idx = val.into_raw().0 as usize;
703 Flat::from_raw(Block128(unsafe {
704 *constants::LIFT_TABLE_8_TO_128.get_unchecked(idx)
705 }))
706 }
707}
708
709#[cfg(feature = "table-math")]
710impl FlatPromote<Block16> for Block128 {
711 #[inline(always)]
712 fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
713 let v = val.into_raw().0;
714 let res = unsafe {
715 *constants::PROMOTE_16_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
716 ^ *constants::PROMOTE_16_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
717 };
718
719 Flat::from_raw(Block128(res))
720 }
721}
722
723#[cfg(feature = "table-math")]
724impl FlatPromote<Block32> for Block128 {
725 #[inline(always)]
726 fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
727 let v = val.into_raw().0;
728 let res = unsafe {
729 *constants::PROMOTE_32_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
730 ^ *constants::PROMOTE_32_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
731 ^ *constants::PROMOTE_32_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
732 ^ *constants::PROMOTE_32_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
733 };
734
735 Flat::from_raw(Block128(res))
736 }
737}
738
739#[cfg(feature = "table-math")]
740impl FlatPromote<Block64> for Block128 {
741 #[inline(always)]
742 fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
743 let v = val.into_raw().0;
744 let res = unsafe {
745 *constants::PROMOTE_64_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
746 ^ *constants::PROMOTE_64_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
747 ^ *constants::PROMOTE_64_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
748 ^ *constants::PROMOTE_64_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
749 ^ *constants::PROMOTE_64_BYTE_4_TO_128.get_unchecked(((v >> 32) & 0xFF) as usize)
750 ^ *constants::PROMOTE_64_BYTE_5_TO_128.get_unchecked(((v >> 40) & 0xFF) as usize)
751 ^ *constants::PROMOTE_64_BYTE_6_TO_128.get_unchecked(((v >> 48) & 0xFF) as usize)
752 ^ *constants::PROMOTE_64_BYTE_7_TO_128.get_unchecked(((v >> 56) & 0xFF) as usize)
753 };
754
755 Flat::from_raw(Block128(res))
756 }
757}
758
759impl_binary_field_extras!(
764 Block128,
765 Block64,
766 map_ct_128_split,
767 TRACE_MASK_128,
768 SOLVE_QUADRATIC_BASIS_128
769);
770
771#[cfg(feature = "table-math")]
776#[inline(always)]
777pub fn apply_matrix_128(val: Block128, table: &[u128; 4096]) -> Block128 {
778 let mut res = 0u128;
779 let v = val.0;
780
781 for i in 0..16 {
785 let byte = (v >> (i * 8)) & 0xFF;
786 let idx = (i * 256) + (byte as usize);
787 res ^= unsafe { *table.get_unchecked(idx) };
788 }
789
790 Block128(res)
791}
792
793#[inline(always)]
794fn map_ct_128_split(x: u128, basis: &[u128; 128]) -> u128 {
795 let mut acc_lo = 0u64;
796 let mut acc_hi = 0u64;
797 let mut i = 0usize;
798
799 while i < 128 {
800 let bit = ((x >> i) & 1) as u64;
801 let mask = 0u64.wrapping_sub(bit);
802
803 let b = basis[i];
804 acc_lo ^= (b as u64) & mask;
805 acc_hi ^= ((b >> 64) as u64) & mask;
806
807 i += 1;
808 }
809
810 (acc_lo as u128) | ((acc_hi as u128) << 64)
811}
812
813#[cfg(not(feature = "table-math"))]
814#[inline(always)]
815fn lift_ct<const N: usize>(x: u64, basis: &'static [u128; N]) -> u128 {
816 let mut acc = 0u128;
817 let mut i = 0usize;
818
819 while i < N {
820 let bit = (x >> i) & 1;
821 let mask = 0u128.wrapping_sub(bit as u128);
822 acc ^= basis[i] & mask;
823 i += 1;
824 }
825
826 acc
827}
828
829#[cfg(target_arch = "aarch64")]
834mod neon {
835 use super::*;
836 use core::arch::aarch64::*;
837 use core::mem::transmute;
838
839 const _: () = assert!(constants::POLY_128 == 0x87, "verus twins hardcode R = 0x87");
840
841 #[inline(always)]
842 pub fn add_packed_128(lhs: PackedBlock128, rhs: PackedBlock128) -> PackedBlock128 {
843 unsafe {
844 let l: [uint8x16_t; 4] = transmute(lhs.0);
847 let r: [uint8x16_t; 4] = transmute(rhs.0);
848
849 let res = [
850 veorq_u8(l[0], r[0]),
851 veorq_u8(l[1], r[1]),
852 veorq_u8(l[2], r[2]),
853 veorq_u8(l[3], r[3]),
854 ];
855
856 transmute(res)
857 }
858 }
859
860 #[inline(always)]
861 pub fn add_flat_128(a: Block128, b: Block128) -> Block128 {
862 unsafe {
863 let l: uint8x16_t = transmute(a.0);
866 let r: uint8x16_t = transmute(b.0);
867
868 Block128(transmute::<uint8x16_t, u128>(veorq_u8(l, r)))
869 }
870 }
871
872 #[cfg(pmull)]
873 #[inline(always)]
874 pub fn mul_flat_128(a: Block128, b: Block128) -> Block128 {
875 unsafe {
876 let a8: uint8x16_t = transmute(a.0);
877 let b8: uint8x16_t = transmute(b.0);
878 let ap: poly64x2_t = transmute(a8);
879 let bp: poly64x2_t = transmute(b8);
880
881 let bs: poly64x2_t = transmute(vextq_u8::<8>(b8, b8));
884
885 let d0: uint8x16_t =
886 transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bp)));
887 let d2: uint8x16_t = transmute(vmull_high_p64(ap, bp));
888 let x0: uint8x16_t =
889 transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bs)));
890 let x1: uint8x16_t = transmute(vmull_high_p64(ap, bs));
891
892 let mid = veorq_u8(x0, x1);
893
894 let zero = vdupq_n_u8(0);
897 let lo = veorq_u8(d0, vextq_u8::<8>(zero, mid));
898 let hi = veorq_u8(d2, vextq_u8::<8>(mid, zero));
899
900 let rv: poly64x2_t = vdupq_n_p64(constants::POLY_128 as u64);
904 let hip: poly64x2_t = transmute(hi);
905
906 let p0: uint8x16_t =
907 transmute(vmull_p64(vgetq_lane_p64::<0>(hip), vgetq_lane_p64::<0>(rv)));
908 let p1: uint8x16_t = transmute(vmull_high_p64(hip, rv));
909 let p1p: poly64x2_t = transmute(p1);
910
911 let fold = veorq_u8(p0, vextq_u8::<8>(zero, p1));
912
913 let carry_mul: uint8x16_t = transmute(vmull_high_p64(p1p, rv));
916
917 let res = veorq_u8(veorq_u8(lo, fold), carry_mul);
918
919 Block128(transmute::<uint8x16_t, u128>(res))
920 }
921 }
922
923 #[cfg(not(feature = "table-math"))]
926 #[inline(always)]
927 pub unsafe fn promote_batch_8_to_128(input: *const u8, output: *mut u128) {
928 unsafe {
929 let vals = vld1q_u8(input);
930
931 let mask_0f = vdupq_n_u8(0x0F);
932 let lo_nib = vandq_u8(vals, mask_0f);
933 let hi_nib = vshrq_n_u8::<4>(vals);
934
935 let mut out = [vdupq_n_u8(0); 16];
936
937 macro_rules! lookup {
938 ($j:expr, $lo:ident, $hi:ident, $dst:ident) => {{
939 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_8_0_TO_128[$j].as_ptr());
940 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_8_1_TO_128[$j].as_ptr());
941
942 $dst[$j] = veorq_u8(vqtbl1q_u8(t0, $lo), vqtbl1q_u8(t1, $hi));
943 }};
944 }
945
946 lookup!(0, lo_nib, hi_nib, out);
947 lookup!(1, lo_nib, hi_nib, out);
948 lookup!(2, lo_nib, hi_nib, out);
949 lookup!(3, lo_nib, hi_nib, out);
950 lookup!(4, lo_nib, hi_nib, out);
951 lookup!(5, lo_nib, hi_nib, out);
952 lookup!(6, lo_nib, hi_nib, out);
953 lookup!(7, lo_nib, hi_nib, out);
954 lookup!(8, lo_nib, hi_nib, out);
955 lookup!(9, lo_nib, hi_nib, out);
956 lookup!(10, lo_nib, hi_nib, out);
957 lookup!(11, lo_nib, hi_nib, out);
958 lookup!(12, lo_nib, hi_nib, out);
959 lookup!(13, lo_nib, hi_nib, out);
960 lookup!(14, lo_nib, hi_nib, out);
961 lookup!(15, lo_nib, hi_nib, out);
962
963 let elems = transpose_16x16(&out);
964 for (i, elem) in elems.iter().enumerate() {
965 vst1q_u8(output.add(i).cast::<u8>(), *elem);
966 }
967 }
968 }
969
970 #[cfg(not(feature = "table-math"))]
973 #[inline(always)]
974 pub unsafe fn promote_batch_16_to_128(input: *const u8, output: *mut u128) {
975 unsafe {
976 let raw0 = vld1q_u8(input);
977 let raw1 = vld1q_u8(input.add(16));
978
979 let lo_bytes = vuzp1q_u8(raw0, raw1);
980 let hi_bytes = vuzp2q_u8(raw0, raw1);
981
982 let mask_0f = vdupq_n_u8(0x0F);
983 let n0 = vandq_u8(lo_bytes, mask_0f);
984 let n1 = vshrq_n_u8::<4>(lo_bytes);
985 let n2 = vandq_u8(hi_bytes, mask_0f);
986 let n3 = vshrq_n_u8::<4>(hi_bytes);
987
988 let mut out = [vdupq_n_u8(0); 16];
989
990 macro_rules! lookup {
991 ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident, $dst:ident) => {{
992 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_16_0_TO_128[$j].as_ptr());
993 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_16_1_TO_128[$j].as_ptr());
994 let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_16_2_TO_128[$j].as_ptr());
995 let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_16_3_TO_128[$j].as_ptr());
996
997 $dst[$j] = veorq_u8(
998 veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
999 veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1000 );
1001 }};
1002 }
1003
1004 lookup!(0, n0, n1, n2, n3, out);
1005 lookup!(1, n0, n1, n2, n3, out);
1006 lookup!(2, n0, n1, n2, n3, out);
1007 lookup!(3, n0, n1, n2, n3, out);
1008 lookup!(4, n0, n1, n2, n3, out);
1009 lookup!(5, n0, n1, n2, n3, out);
1010 lookup!(6, n0, n1, n2, n3, out);
1011 lookup!(7, n0, n1, n2, n3, out);
1012 lookup!(8, n0, n1, n2, n3, out);
1013 lookup!(9, n0, n1, n2, n3, out);
1014 lookup!(10, n0, n1, n2, n3, out);
1015 lookup!(11, n0, n1, n2, n3, out);
1016 lookup!(12, n0, n1, n2, n3, out);
1017 lookup!(13, n0, n1, n2, n3, out);
1018 lookup!(14, n0, n1, n2, n3, out);
1019 lookup!(15, n0, n1, n2, n3, out);
1020
1021 let elems = transpose_16x16(&out);
1022 for (i, elem) in elems.iter().enumerate() {
1023 vst1q_u8(output.add(i).cast::<u8>(), *elem);
1024 }
1025 }
1026 }
1027
1028 #[cfg(not(feature = "table-math"))]
1031 #[inline(always)]
1032 pub unsafe fn promote_batch_32_to_128(input: *const u8, output: *mut u128) {
1033 unsafe {
1034 let raw0 = vld1q_u8(input);
1035 let raw1 = vld1q_u8(input.add(16));
1036 let raw2 = vld1q_u8(input.add(32));
1037 let raw3 = vld1q_u8(input.add(48));
1038
1039 let a02 = vuzp1q_u8(raw0, raw1);
1040 let a13 = vuzp2q_u8(raw0, raw1);
1041 let b02 = vuzp1q_u8(raw2, raw3);
1042 let b13 = vuzp2q_u8(raw2, raw3);
1043
1044 let byte0 = vuzp1q_u8(a02, b02);
1045 let byte2 = vuzp2q_u8(a02, b02);
1046 let byte1 = vuzp1q_u8(a13, b13);
1047 let byte3 = vuzp2q_u8(a13, b13);
1048
1049 let mask_0f = vdupq_n_u8(0x0F);
1050 let n0 = vandq_u8(byte0, mask_0f);
1051 let n1 = vshrq_n_u8::<4>(byte0);
1052 let n2 = vandq_u8(byte1, mask_0f);
1053 let n3 = vshrq_n_u8::<4>(byte1);
1054 let n4 = vandq_u8(byte2, mask_0f);
1055 let n5 = vshrq_n_u8::<4>(byte2);
1056 let n6 = vandq_u8(byte3, mask_0f);
1057 let n7 = vshrq_n_u8::<4>(byte3);
1058
1059 let mut out = [vdupq_n_u8(0); 16];
1060
1061 macro_rules! lookup {
1062 ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident,
1063 $n4:ident, $n5:ident, $n6:ident, $n7:ident, $dst:ident) => {{
1064 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_32_0_TO_128[$j].as_ptr());
1065 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_32_1_TO_128[$j].as_ptr());
1066 let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_32_2_TO_128[$j].as_ptr());
1067 let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_32_3_TO_128[$j].as_ptr());
1068 let t4 = vld1q_u8(constants::NIBBLE_PROMOTE_32_4_TO_128[$j].as_ptr());
1069 let t5 = vld1q_u8(constants::NIBBLE_PROMOTE_32_5_TO_128[$j].as_ptr());
1070 let t6 = vld1q_u8(constants::NIBBLE_PROMOTE_32_6_TO_128[$j].as_ptr());
1071 let t7 = vld1q_u8(constants::NIBBLE_PROMOTE_32_7_TO_128[$j].as_ptr());
1072
1073 $dst[$j] = veorq_u8(
1074 veorq_u8(
1075 veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
1076 veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1077 ),
1078 veorq_u8(
1079 veorq_u8(vqtbl1q_u8(t4, $n4), vqtbl1q_u8(t5, $n5)),
1080 veorq_u8(vqtbl1q_u8(t6, $n6), vqtbl1q_u8(t7, $n7)),
1081 ),
1082 );
1083 }};
1084 }
1085
1086 lookup!(0, n0, n1, n2, n3, n4, n5, n6, n7, out);
1087 lookup!(1, n0, n1, n2, n3, n4, n5, n6, n7, out);
1088 lookup!(2, n0, n1, n2, n3, n4, n5, n6, n7, out);
1089 lookup!(3, n0, n1, n2, n3, n4, n5, n6, n7, out);
1090 lookup!(4, n0, n1, n2, n3, n4, n5, n6, n7, out);
1091 lookup!(5, n0, n1, n2, n3, n4, n5, n6, n7, out);
1092 lookup!(6, n0, n1, n2, n3, n4, n5, n6, n7, out);
1093 lookup!(7, n0, n1, n2, n3, n4, n5, n6, n7, out);
1094 lookup!(8, n0, n1, n2, n3, n4, n5, n6, n7, out);
1095 lookup!(9, n0, n1, n2, n3, n4, n5, n6, n7, out);
1096 lookup!(10, n0, n1, n2, n3, n4, n5, n6, n7, out);
1097 lookup!(11, n0, n1, n2, n3, n4, n5, n6, n7, out);
1098 lookup!(12, n0, n1, n2, n3, n4, n5, n6, n7, out);
1099 lookup!(13, n0, n1, n2, n3, n4, n5, n6, n7, out);
1100 lookup!(14, n0, n1, n2, n3, n4, n5, n6, n7, out);
1101 lookup!(15, n0, n1, n2, n3, n4, n5, n6, n7, out);
1102
1103 let elems = transpose_16x16(&out);
1104 for (i, elem) in elems.iter().enumerate() {
1105 vst1q_u8(output.add(i).cast::<u8>(), *elem);
1106 }
1107 }
1108 }
1109
1110 #[cfg(not(feature = "table-math"))]
1112 #[inline(always)]
1113 unsafe fn transpose_16x16(r: &[uint8x16_t; 16]) -> [uint8x16_t; 16] {
1114 #[inline(always)]
1117 const fn u8_to_u16(v: uint8x16_t) -> uint16x8_t {
1118 unsafe { transmute::<uint8x16_t, uint16x8_t>(v) }
1119 }
1120
1121 #[inline(always)]
1122 const fn u16_to_u32(v: uint16x8_t) -> uint32x4_t {
1123 unsafe { transmute::<uint16x8_t, uint32x4_t>(v) }
1124 }
1125
1126 #[inline(always)]
1127 const fn u32_to_u64(v: uint32x4_t) -> uint64x2_t {
1128 unsafe { transmute::<uint32x4_t, uint64x2_t>(v) }
1129 }
1130
1131 #[inline(always)]
1132 const fn u64_to_u8(v: uint64x2_t) -> uint8x16_t {
1133 unsafe { transmute::<uint64x2_t, uint8x16_t>(v) }
1134 }
1135
1136 unsafe {
1137 let a0 = vtrn1q_u8(r[0], r[1]);
1140 let a1 = vtrn2q_u8(r[0], r[1]);
1141 let a2 = vtrn1q_u8(r[2], r[3]);
1142 let a3 = vtrn2q_u8(r[2], r[3]);
1143 let a4 = vtrn1q_u8(r[4], r[5]);
1144 let a5 = vtrn2q_u8(r[4], r[5]);
1145 let a6 = vtrn1q_u8(r[6], r[7]);
1146 let a7 = vtrn2q_u8(r[6], r[7]);
1147 let a8 = vtrn1q_u8(r[8], r[9]);
1148 let a9 = vtrn2q_u8(r[8], r[9]);
1149 let a10 = vtrn1q_u8(r[10], r[11]);
1150 let a11 = vtrn2q_u8(r[10], r[11]);
1151 let a12 = vtrn1q_u8(r[12], r[13]);
1152 let a13 = vtrn2q_u8(r[12], r[13]);
1153 let a14 = vtrn1q_u8(r[14], r[15]);
1154 let a15 = vtrn2q_u8(r[14], r[15]);
1155
1156 let b0 = vtrn1q_u16(u8_to_u16(a0), u8_to_u16(a2));
1159 let b2 = vtrn2q_u16(u8_to_u16(a0), u8_to_u16(a2));
1160 let b1 = vtrn1q_u16(u8_to_u16(a1), u8_to_u16(a3));
1161 let b3 = vtrn2q_u16(u8_to_u16(a1), u8_to_u16(a3));
1162 let b4 = vtrn1q_u16(u8_to_u16(a4), u8_to_u16(a6));
1163 let b6 = vtrn2q_u16(u8_to_u16(a4), u8_to_u16(a6));
1164 let b5 = vtrn1q_u16(u8_to_u16(a5), u8_to_u16(a7));
1165 let b7 = vtrn2q_u16(u8_to_u16(a5), u8_to_u16(a7));
1166 let b8 = vtrn1q_u16(u8_to_u16(a8), u8_to_u16(a10));
1167 let b10 = vtrn2q_u16(u8_to_u16(a8), u8_to_u16(a10));
1168 let b9 = vtrn1q_u16(u8_to_u16(a9), u8_to_u16(a11));
1169 let b11 = vtrn2q_u16(u8_to_u16(a9), u8_to_u16(a11));
1170 let b12 = vtrn1q_u16(u8_to_u16(a12), u8_to_u16(a14));
1171 let b14 = vtrn2q_u16(u8_to_u16(a12), u8_to_u16(a14));
1172 let b13 = vtrn1q_u16(u8_to_u16(a13), u8_to_u16(a15));
1173 let b15 = vtrn2q_u16(u8_to_u16(a13), u8_to_u16(a15));
1174
1175 let c0 = vtrn1q_u32(u16_to_u32(b0), u16_to_u32(b4));
1178 let c4 = vtrn2q_u32(u16_to_u32(b0), u16_to_u32(b4));
1179 let c1 = vtrn1q_u32(u16_to_u32(b1), u16_to_u32(b5));
1180 let c5 = vtrn2q_u32(u16_to_u32(b1), u16_to_u32(b5));
1181 let c2 = vtrn1q_u32(u16_to_u32(b2), u16_to_u32(b6));
1182 let c6 = vtrn2q_u32(u16_to_u32(b2), u16_to_u32(b6));
1183 let c3 = vtrn1q_u32(u16_to_u32(b3), u16_to_u32(b7));
1184 let c7 = vtrn2q_u32(u16_to_u32(b3), u16_to_u32(b7));
1185 let c8 = vtrn1q_u32(u16_to_u32(b8), u16_to_u32(b12));
1186 let c12 = vtrn2q_u32(u16_to_u32(b8), u16_to_u32(b12));
1187 let c9 = vtrn1q_u32(u16_to_u32(b9), u16_to_u32(b13));
1188 let c13 = vtrn2q_u32(u16_to_u32(b9), u16_to_u32(b13));
1189 let c10 = vtrn1q_u32(u16_to_u32(b10), u16_to_u32(b14));
1190 let c14 = vtrn2q_u32(u16_to_u32(b10), u16_to_u32(b14));
1191 let c11 = vtrn1q_u32(u16_to_u32(b11), u16_to_u32(b15));
1192 let c15 = vtrn2q_u32(u16_to_u32(b11), u16_to_u32(b15));
1193
1194 [
1197 u64_to_u8(vtrn1q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1198 u64_to_u8(vtrn1q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1199 u64_to_u8(vtrn1q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1200 u64_to_u8(vtrn1q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1201 u64_to_u8(vtrn1q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1202 u64_to_u8(vtrn1q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1203 u64_to_u8(vtrn1q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1204 u64_to_u8(vtrn1q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1205 u64_to_u8(vtrn2q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1206 u64_to_u8(vtrn2q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1207 u64_to_u8(vtrn2q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1208 u64_to_u8(vtrn2q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1209 u64_to_u8(vtrn2q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1210 u64_to_u8(vtrn2q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1211 u64_to_u8(vtrn2q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1212 u64_to_u8(vtrn2q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1213 ]
1214 }
1215 }
1216}
1217
1218#[cfg(test)]
1223mod tests {
1224 use super::*;
1225 use proptest::prelude::*;
1226 use rand::{RngExt, rng};
1227
1228 #[test]
1233 fn tower_constants() {
1234 let tau128 = Block128::EXTENSION_TAU;
1237 let (lo128, hi128) = tau128.split();
1238 assert_eq!(lo128, Block64::ZERO);
1239 assert_eq!(hi128, Block64::TAU);
1240 }
1241
1242 #[test]
1243 fn add_truth() {
1244 let zero = Block128::ZERO;
1245 let one = Block128::ONE;
1246
1247 assert_eq!(zero + zero, zero);
1248 assert_eq!(zero + one, one);
1249 assert_eq!(one + zero, one);
1250 assert_eq!(one + one, zero);
1251 }
1252
1253 #[test]
1254 fn mul_truth() {
1255 let zero = Block128::ZERO;
1256 let one = Block128::ONE;
1257
1258 assert_eq!(zero * zero, zero);
1259 assert_eq!(zero * one, zero);
1260 assert_eq!(one * one, one);
1261 }
1262
1263 #[test]
1264 fn add() {
1265 assert_eq!(Block128(5) + Block128(3), Block128(6));
1268 }
1269
1270 #[test]
1271 fn mul_simple() {
1272 assert_eq!(Block128(2) * Block128(2), Block128(4));
1275 }
1276
1277 #[test]
1278 fn mul_overflow() {
1279 assert_eq!(Block128(0x57) * Block128(0x83), Block128(0xC1));
1283 }
1284
1285 #[test]
1286 fn karatsuba_correctness() {
1287 let x = Block128::new(Block64::ZERO, Block64::ONE);
1299 let squared = x * x;
1300
1301 let (res_lo, res_hi) = squared.split();
1303
1304 assert_eq!(res_hi, Block64::ONE, "X^2 should contain X component");
1305 assert_eq!(
1306 res_lo,
1307 Block64(0x2000_0000_0000_0000),
1308 "X^2 should contain tau component (0x2000_0000_0000_0000)"
1309 );
1310 }
1311
1312 #[test]
1313 fn security_zeroize() {
1314 let mut secret_val = Block128::from(0xDEAD_BEEF_CAFE_BABE_u128);
1316 assert_ne!(secret_val, Block128::ZERO);
1317
1318 secret_val.zeroize();
1320
1321 assert_eq!(secret_val, Block128::ZERO, "Memory was not wiped!");
1323
1324 assert_eq!(secret_val.0, 0u128, "Underlying memory leak detected");
1326 }
1327
1328 #[test]
1329 fn invert_zero() {
1330 assert_eq!(
1332 Block128::ZERO.invert(),
1333 Block128::ZERO,
1334 "invert(0) must return 0"
1335 );
1336 }
1337
1338 #[test]
1339 fn inversion_random() {
1340 let mut rng = rng();
1341 for _i in 0..1000 {
1342 let val = Block128(rng.random());
1343
1344 if val != Block128::ZERO {
1345 let inv = val.invert();
1346 let identity = val * inv;
1347
1348 assert_eq!(
1349 identity,
1350 Block128::ONE,
1351 "Inversion identity failed: a * a^-1 != 1"
1352 );
1353 }
1354 }
1355 }
1356
1357 #[test]
1358 fn tower_embedding() {
1359 let mut rng = rng();
1360 for _ in 0..100 {
1361 let a = Block64(rng.random());
1362 let b = Block64(rng.random());
1363
1364 let a_lifted: Block128 = a.into();
1366 let (lo, hi) = a_lifted.split();
1367
1368 assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
1369 assert_eq!(
1370 hi,
1371 Block64::ZERO,
1372 "Embedding structure failed: high part must be zero"
1373 );
1374
1375 let sum_sub = a + b;
1377 let sum_lifted: Block128 = sum_sub.into();
1378 let sum_in_super = Block128::from(a) + Block128::from(b);
1379
1380 assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
1381
1382 let prod_sub = a * b;
1387 let prod_lifted: Block128 = prod_sub.into();
1388 let prod_in_super = Block128::from(a) * Block128::from(b);
1389
1390 assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
1391 }
1392 }
1393
1394 #[test]
1399 fn isomorphism_roundtrip() {
1400 let mut rng = rng();
1401 for _ in 0..1000 {
1402 let val = Block128(rng.random::<u128>());
1403 assert_eq!(val.to_hardware().to_tower(), val);
1404 }
1405 }
1406
1407 #[test]
1408 fn flat_mul_homomorphism() {
1409 let mut rng = rng();
1410 for _ in 0..1000 {
1411 let a = Block128(rng.random());
1412 let b = Block128(rng.random());
1413
1414 let expected_flat = (a * b).to_hardware();
1415 let actual_flat = a.to_hardware() * b.to_hardware();
1416
1417 assert_eq!(
1418 actual_flat, expected_flat,
1419 "Block128 flat multiplication mismatch: (a*b)^H != a^H * b^H"
1420 );
1421 }
1422 }
1423
1424 #[test]
1425 fn packed_consistency() {
1426 let mut rng = rng();
1427 for _ in 0..100 {
1428 let mut a_vals = [Block128::ZERO; 4];
1429 let mut b_vals = [Block128::ZERO; 4];
1430
1431 for i in 0..4 {
1432 a_vals[i] = Block128(rng.random::<u128>());
1433 b_vals[i] = Block128(rng.random::<u128>());
1434 }
1435
1436 let a_flat_vals = a_vals.map(|x| x.to_hardware());
1437 let b_flat_vals = b_vals.map(|x| x.to_hardware());
1438 let a_packed = Flat::<Block128>::pack(&a_flat_vals);
1439 let b_packed = Flat::<Block128>::pack(&b_flat_vals);
1440
1441 let add_res = Block128::add_hardware_packed(a_packed, b_packed);
1443
1444 let mut add_out = [Block128::ZERO.to_hardware(); 4];
1445 Flat::<Block128>::unpack(add_res, &mut add_out);
1446
1447 for i in 0..4 {
1448 assert_eq!(
1449 add_out[i],
1450 (a_vals[i] + b_vals[i]).to_hardware(),
1451 "Block128 SIMD add mismatch at index {}",
1452 i
1453 );
1454 }
1455
1456 let mul_res = Block128::mul_hardware_packed(a_packed, b_packed);
1458
1459 let mut mul_out = [Block128::ZERO.to_hardware(); 4];
1460 Flat::<Block128>::unpack(mul_res, &mut mul_out);
1461
1462 for i in 0..4 {
1463 let expected_flat = (a_vals[i] * b_vals[i]).to_hardware();
1464 assert_eq!(
1465 mul_out[i], expected_flat,
1466 "Block128 SIMD mul mismatch at index {}",
1467 i
1468 );
1469 }
1470 }
1471 }
1472
1473 #[test]
1478 fn pack_unpack_roundtrip() {
1479 let mut rng = rng();
1480 let mut data = [Block128::ZERO; PACKED_WIDTH_128];
1481 for v in data.iter_mut() {
1482 *v = Block128(rng.random());
1483 }
1484
1485 let packed = Block128::pack(&data);
1486 let mut unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1487 Block128::unpack(packed, &mut unpacked);
1488 assert_eq!(data, unpacked);
1489 }
1490
1491 #[test]
1492 fn packed_add_consistency() {
1493 let mut rng = rng();
1494 let mut a_vals = [Block128::ZERO; PACKED_WIDTH_128];
1495 let mut b_vals = [Block128::ZERO; PACKED_WIDTH_128];
1496
1497 for i in 0..PACKED_WIDTH_128 {
1498 a_vals[i] = Block128(rng.random());
1499 b_vals[i] = Block128(rng.random());
1500 }
1501
1502 let res_packed = Block128::pack(&a_vals) + Block128::pack(&b_vals);
1503 let mut res_unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1504 Block128::unpack(res_packed, &mut res_unpacked);
1505
1506 for i in 0..PACKED_WIDTH_128 {
1507 assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
1508 }
1509 }
1510
1511 #[test]
1512 fn packed_mul_consistency() {
1513 let mut rng = rng();
1514
1515 for _ in 0..1000 {
1516 let mut a_arr = [Block128::ZERO; PACKED_WIDTH_128];
1519 let mut b_arr = [Block128::ZERO; PACKED_WIDTH_128];
1520
1521 for i in 0..PACKED_WIDTH_128 {
1522 let val_a: u128 = rng.random();
1524 let val_b: u128 = rng.random();
1525 a_arr[i] = Block128(val_a);
1526 b_arr[i] = Block128(val_b);
1527 }
1528
1529 let a_packed = PackedBlock128(a_arr);
1530 let b_packed = PackedBlock128(b_arr);
1531
1532 let c_packed = a_packed * b_packed;
1534
1535 let mut c_expected = [Block128::ZERO; PACKED_WIDTH_128];
1537 for i in 0..PACKED_WIDTH_128 {
1538 c_expected[i] = a_arr[i] * b_arr[i];
1539 }
1540
1541 assert_eq!(c_packed.0, c_expected, "SIMD multiplication mismatch!");
1542 }
1543 }
1544
1545 #[inline(always)]
1550 fn promote_block8_tables(val: Block8) -> Block128 {
1551 let idx_flat = val.0 as usize;
1553 let tower_byte = unsafe { *constants::FLAT_TO_TOWER_8.get_unchecked(idx_flat) };
1554 let idx_tower = tower_byte as usize;
1555
1556 Block128(unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx_tower) })
1557 }
1558
1559 #[inline(always)]
1560 fn promote_block16_tables(val: Block16) -> Block128 {
1561 let v_flat = val.0;
1562
1563 let mut v_tower = 0u16;
1564 for i in 0..2 {
1565 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1566 let idx = (i * 256) + byte;
1567 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_16.get_unchecked(idx) };
1568 }
1569
1570 let mut res = 0u128;
1571 for i in 0..2 {
1572 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1573 let idx = (i * 256) + byte;
1574 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1575 }
1576
1577 Block128(res)
1578 }
1579
1580 #[inline(always)]
1581 fn promote_block32_tables(val: Block32) -> Block128 {
1582 let v_flat = val.0;
1583
1584 let mut v_tower = 0u32;
1585 for i in 0..4 {
1586 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1587 let idx = (i * 256) + byte;
1588 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_32.get_unchecked(idx) };
1589 }
1590
1591 let mut res = 0u128;
1592 for i in 0..4 {
1593 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1594 let idx = (i * 256) + byte;
1595 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1596 }
1597
1598 Block128(res)
1599 }
1600
1601 #[inline(always)]
1602 fn promote_block64_tables(val: Block64) -> Block128 {
1603 let v_flat = val.0;
1604
1605 let mut v_tower = 0u64;
1606 for i in 0..8 {
1607 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1608 let idx = (i * 256) + byte;
1609 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_64.get_unchecked(idx) };
1610 }
1611
1612 let mut res = 0u128;
1613 for i in 0..8 {
1614 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1615 let idx = (i * 256) + byte;
1616 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1617 }
1618
1619 Block128(res)
1620 }
1621
1622 #[test]
1623 fn lift_from_partial_hardware_matches_tables_block8_exhaustive() {
1624 for x in 0u16..=u8::MAX as u16 {
1625 let v = Block8(x as u8);
1626 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1627 let expected = promote_block8_tables(v);
1628
1629 assert_eq!(got, expected);
1630 }
1631 }
1632
1633 #[test]
1634 fn lift_from_partial_hardware_matches_tables_block16_exhaustive() {
1635 for x in 0..=u16::MAX {
1636 let v = Block16(x);
1637 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1638 let expected = promote_block16_tables(v);
1639
1640 assert_eq!(got, expected);
1641 }
1642 }
1643
1644 #[test]
1645 fn lift_from_partial_hardware_matches_tables_block32_random() {
1646 let mut rng = rng();
1647 for _ in 0..10_000 {
1648 let v = Block32(rng.random::<u32>());
1649 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1650 let expected = promote_block32_tables(v);
1651
1652 assert_eq!(got, expected);
1653 }
1654 }
1655
1656 #[test]
1657 fn lift_from_partial_hardware_matches_tables_block64_random() {
1658 let mut rng = rng();
1659 for _ in 0..10_000 {
1660 let v = Block64(rng.random::<u64>());
1661 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1662 let expected = promote_block64_tables(v);
1663
1664 assert_eq!(got, expected);
1665 }
1666 }
1667
1668 #[test]
1673 fn promote_flat_batch_matches_scalar_block8() {
1674 let mut rng = rng();
1675 let input: Vec<Flat<Block8>> = (0..64)
1676 .map(|_| Block8(rng.random::<u8>()).to_hardware())
1677 .collect();
1678
1679 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 64];
1680 Block128::promote_flat_batch(&input, &mut batch_out);
1681
1682 for (i, &v) in input.iter().enumerate() {
1683 let scalar = Block128::promote_flat(v);
1684 assert_eq!(batch_out[i], scalar, "batch/scalar mismatch at index {}", i);
1685 }
1686 }
1687
1688 #[test]
1689 fn promote_flat_batch_matches_scalar_block16() {
1690 let mut rng = rng();
1691 let input: Vec<Flat<Block16>> = (0..32)
1692 .map(|_| Block16(rng.random::<u16>()).to_hardware())
1693 .collect();
1694
1695 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 32];
1696 Block128::promote_flat_batch(&input, &mut batch_out);
1697
1698 for (i, &v) in input.iter().enumerate() {
1699 assert_eq!(
1700 batch_out[i],
1701 Block128::promote_flat(v),
1702 "batch/scalar mismatch at index {}",
1703 i
1704 );
1705 }
1706 }
1707
1708 #[test]
1709 fn promote_flat_batch_matches_scalar_block32() {
1710 let mut rng = rng();
1711 let input: Vec<Flat<Block32>> = (0..16)
1712 .map(|_| Block32(rng.random::<u32>()).to_hardware())
1713 .collect();
1714
1715 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 16];
1716 Block128::promote_flat_batch(&input, &mut batch_out);
1717
1718 for (i, &v) in input.iter().enumerate() {
1719 assert_eq!(
1720 batch_out[i],
1721 Block128::promote_flat(v),
1722 "batch/scalar mismatch at index {}",
1723 i
1724 );
1725 }
1726 }
1727
1728 #[test]
1729 fn promote_flat_batch_matches_scalar_block64() {
1730 let mut rng = rng();
1731 let input: Vec<Flat<Block64>> = (0..8)
1732 .map(|_| Block64(rng.random::<u64>()).to_hardware())
1733 .collect();
1734
1735 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 8];
1736 Block128::promote_flat_batch(&input, &mut batch_out);
1737
1738 for (i, &v) in input.iter().enumerate() {
1739 assert_eq!(
1740 batch_out[i],
1741 Block128::promote_flat(v),
1742 "batch/scalar mismatch at index {}",
1743 i
1744 );
1745 }
1746 }
1747
1748 #[test]
1749 fn promote_flat_batch_partial_slice() {
1750 let input: Vec<Flat<Block8>> = (0..16).map(|i| Block8(i as u8).to_hardware()).collect();
1751
1752 let mut out_short = vec![Flat::from_raw(Block128::ZERO); 5];
1754 Block128::promote_flat_batch(&input, &mut out_short);
1755
1756 for i in 0..5 {
1757 assert_eq!(out_short[i], Block128::promote_flat(input[i]));
1758 }
1759
1760 let short_input = &input[..3];
1762 let mut out_long = vec![Flat::from_raw(Block128::ZERO); 10];
1763
1764 Block128::promote_flat_batch(short_input, &mut out_long);
1765
1766 for i in 0..3 {
1767 assert_eq!(out_long[i], Block128::promote_flat(short_input[i]));
1768 }
1769
1770 for val in &out_long[3..10] {
1772 assert_eq!(*val, Flat::from_raw(Block128::ZERO));
1773 }
1774 }
1775
1776 #[test]
1777 fn promote_edge_zero() {
1778 let zero = Flat::from_raw(Block8(0));
1779 let promoted = Block128::promote_flat(zero);
1780
1781 assert_eq!(
1782 promoted,
1783 Flat::from_raw(Block128::ZERO),
1784 "promote(0) must be 0"
1785 );
1786
1787 let input = vec![zero; 16];
1790 let mut output = vec![Flat::from_raw(Block128(0xDEAD)); 16];
1791
1792 Block128::promote_flat_batch(&input, &mut output);
1793
1794 for o in &output {
1795 assert_eq!(*o, Flat::from_raw(Block128::ZERO));
1796 }
1797 }
1798
1799 #[test]
1800 fn promote_edge_one() {
1801 let one_flat8 = Block8::ONE.to_hardware();
1802 let one_flat128 = Block128::ONE.to_hardware();
1803
1804 assert_eq!(
1805 Block128::promote_flat(one_flat8),
1806 one_flat128,
1807 "promote(1) must equal 1 in target field"
1808 );
1809 }
1810
1811 #[test]
1812 fn promote_edge_max_block8() {
1813 let max = Flat::from_raw(Block8(0xFF));
1814 let promoted = Block128::promote_flat(max);
1815
1816 assert_ne!(promoted, Flat::from_raw(Block128::ZERO));
1818
1819 let tower_8 = max.to_tower();
1822 let tower_128 = Block128::from(tower_8);
1823
1824 assert_eq!(promoted.to_tower(), tower_128);
1825 }
1826
1827 #[test]
1828 fn promote_edge_single_bits() {
1829 for bit in 0..8 {
1830 let val = Flat::from_raw(Block8(1u8 << bit));
1831 let promoted = Block128::promote_flat(val);
1832
1833 assert_ne!(
1835 promoted,
1836 Flat::from_raw(Block128::ZERO),
1837 "single-bit {} promoted to zero",
1838 bit
1839 );
1840
1841 let tower_8 = val.to_tower();
1843 let tower_128 = Block128::from(tower_8);
1844
1845 assert_eq!(
1846 promoted.to_tower(),
1847 tower_128,
1848 "tower roundtrip failed for bit {}",
1849 bit
1850 );
1851 }
1852 }
1853
1854 #[test]
1855 fn promote_edge_alternating_packed() {
1856 let input: Vec<Flat<Block8>> = (0..16)
1857 .map(|i| {
1858 if i % 2 == 0 {
1859 Flat::from_raw(Block8(0x00))
1860 } else {
1861 Flat::from_raw(Block8(0xFF))
1862 }
1863 })
1864 .collect();
1865
1866 let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1867 Block128::promote_flat_batch(&input, &mut output);
1868
1869 for (i, &v) in input.iter().enumerate() {
1870 assert_eq!(
1871 output[i],
1872 Block128::promote_flat(v),
1873 "alternating mismatch at {}",
1874 i
1875 );
1876 }
1877 }
1878
1879 #[test]
1880 fn promote_edge_all_same_packed() {
1881 let val = Flat::from_raw(Block8(0x42));
1882 let expected = Block128::promote_flat(val);
1883
1884 let input = vec![val; 16];
1885 let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1886
1887 Block128::promote_flat_batch(&input, &mut output);
1888
1889 for (i, o) in output.iter().enumerate() {
1890 assert_eq!(*o, expected, "all-same mismatch at {}", i);
1891 }
1892 }
1893
1894 #[test]
1895 fn promote_tower_roundtrip_block8() {
1896 for x in 0u16..=u8::MAX as u16 {
1897 let b8 = Block8(x as u8);
1898 let promoted = Block128::promote_flat(b8.to_hardware());
1899 let tower_128 = promoted.to_tower();
1900 let embedded = Block128::from(b8);
1901
1902 assert_eq!(
1903 tower_128, embedded,
1904 "tower roundtrip failed for Block8({})",
1905 x
1906 );
1907 }
1908 }
1909
1910 #[test]
1911 fn promote_tower_roundtrip_block16() {
1912 let mut rng = rng();
1913 for _ in 0..10_000 {
1914 let v = Block16(rng.random::<u16>());
1915 let promoted = Block128::promote_flat(v.to_hardware());
1916 let tower_128 = promoted.to_tower();
1917 let embedded = Block128::from(v);
1918
1919 assert_eq!(
1920 tower_128, embedded,
1921 "tower roundtrip failed for Block16({})",
1922 v.0
1923 );
1924 }
1925 }
1926
1927 #[test]
1928 fn promote_tower_roundtrip_block32() {
1929 let mut rng = rng();
1930 for _ in 0..10_000 {
1931 let v = Block32(rng.random::<u32>());
1932 let promoted = Block128::promote_flat(v.to_hardware());
1933 let tower_128 = promoted.to_tower();
1934 let embedded = Block128::from(v);
1935
1936 assert_eq!(
1937 tower_128, embedded,
1938 "tower roundtrip failed for Block32({})",
1939 v.0
1940 );
1941 }
1942 }
1943
1944 #[test]
1945 fn promote_tower_roundtrip_block64() {
1946 let mut rng = rng();
1947 for _ in 0..10_000 {
1948 let v = Block64(rng.random::<u64>());
1949 let promoted = Block128::promote_flat(v.to_hardware());
1950 let tower_128 = promoted.to_tower();
1951 let embedded = Block128::from(v);
1952
1953 assert_eq!(
1954 tower_128, embedded,
1955 "tower roundtrip failed for Block64({})",
1956 v.0
1957 );
1958 }
1959 }
1960
1961 #[test]
1962 fn promote_algebraic_homomorphism_add_block8() {
1963 let mut rng = rng();
1964 for _ in 0..1000 {
1965 let a = Block8(rng.random::<u8>());
1966 let b = Block8(rng.random::<u8>());
1967
1968 let promote_a = Block128::promote_flat(a.to_hardware());
1969 let promote_b = Block128::promote_flat(b.to_hardware());
1970 let promote_sum = Block128::promote_flat((a + b).to_hardware());
1971
1972 assert_eq!(
1973 promote_a + promote_b,
1974 promote_sum,
1975 "add homomorphism: promote(a)+promote(b) != promote(a+b)"
1976 );
1977 }
1978 }
1979
1980 #[test]
1981 fn promote_algebraic_homomorphism_mul_block8() {
1982 let mut rng = rng();
1983 for _ in 0..1000 {
1984 let a = Block8(rng.random::<u8>());
1985 let b = Block8(rng.random::<u8>());
1986
1987 let promote_a = Block128::promote_flat(a.to_hardware());
1988 let promote_b = Block128::promote_flat(b.to_hardware());
1989 let promote_prod = Block128::promote_flat((a * b).to_hardware());
1990
1991 assert_eq!(
1994 promote_a * promote_b,
1995 promote_prod,
1996 "mul homomorphism: promote(a)*promote(b) != promote(a*b)"
1997 );
1998 }
1999 }
2000
2001 #[test]
2002 fn promote_algebraic_homomorphism_add_block16() {
2003 let mut rng = rng();
2004 for _ in 0..1000 {
2005 let a = Block16(rng.random::<u16>());
2006 let b = Block16(rng.random::<u16>());
2007
2008 let pa = Block128::promote_flat(a.to_hardware());
2009 let pb = Block128::promote_flat(b.to_hardware());
2010 let p_sum = Block128::promote_flat((a + b).to_hardware());
2011
2012 assert_eq!(pa + pb, p_sum, "Block16 add homomorphism failed");
2013 }
2014 }
2015
2016 #[test]
2017 fn promote_algebraic_homomorphism_mul_block16() {
2018 let mut rng = rng();
2019 for _ in 0..1000 {
2020 let a = Block16(rng.random::<u16>());
2021 let b = Block16(rng.random::<u16>());
2022
2023 let pa = Block128::promote_flat(a.to_hardware());
2024 let pb = Block128::promote_flat(b.to_hardware());
2025 let p_prod = Block128::promote_flat((a * b).to_hardware());
2026
2027 assert_eq!(pa * pb, p_prod, "Block16 mul homomorphism failed");
2028 }
2029 }
2030
2031 #[test]
2032 fn promote_algebraic_homomorphism_add_block32() {
2033 let mut rng = rng();
2034 for _ in 0..1000 {
2035 let a = Block32(rng.random::<u32>());
2036 let b = Block32(rng.random::<u32>());
2037
2038 let pa = Block128::promote_flat(a.to_hardware());
2039 let pb = Block128::promote_flat(b.to_hardware());
2040 let p_sum = Block128::promote_flat((a + b).to_hardware());
2041
2042 assert_eq!(pa + pb, p_sum, "Block32 add homomorphism failed");
2043 }
2044 }
2045
2046 #[test]
2047 fn promote_algebraic_homomorphism_mul_block32() {
2048 let mut rng = rng();
2049 for _ in 0..1000 {
2050 let a = Block32(rng.random::<u32>());
2051 let b = Block32(rng.random::<u32>());
2052
2053 let pa = Block128::promote_flat(a.to_hardware());
2054 let pb = Block128::promote_flat(b.to_hardware());
2055 let p_prod = Block128::promote_flat((a * b).to_hardware());
2056
2057 assert_eq!(pa * pb, p_prod, "Block32 mul homomorphism failed");
2058 }
2059 }
2060
2061 #[test]
2062 fn promote_algebraic_homomorphism_add_block64() {
2063 let mut rng = rng();
2064 for _ in 0..1000 {
2065 let a = Block64(rng.random::<u64>());
2066 let b = Block64(rng.random::<u64>());
2067
2068 let pa = Block128::promote_flat(a.to_hardware());
2069 let pb = Block128::promote_flat(b.to_hardware());
2070 let p_sum = Block128::promote_flat((a + b).to_hardware());
2071
2072 assert_eq!(pa + pb, p_sum, "Block64 add homomorphism failed");
2073 }
2074 }
2075
2076 #[test]
2077 fn promote_algebraic_homomorphism_mul_block64() {
2078 let mut rng = rng();
2079 for _ in 0..1000 {
2080 let a = Block64(rng.random::<u64>());
2081 let b = Block64(rng.random::<u64>());
2082
2083 let pa = Block128::promote_flat(a.to_hardware());
2084 let pb = Block128::promote_flat(b.to_hardware());
2085 let p_prod = Block128::promote_flat((a * b).to_hardware());
2086
2087 assert_eq!(pa * pb, p_prod, "Block64 mul homomorphism failed");
2088 }
2089 }
2090
2091 #[test]
2092 fn promote_generator_preserves_order() {
2093 let g = Block8(3);
2095 let g_promoted = Block128::promote_flat(g.to_hardware());
2096
2097 let mut acc8 = Block8::ONE;
2100 for _ in 0..255 {
2101 acc8 *= g;
2102 }
2103
2104 assert_eq!(acc8, Block8::ONE, "Block8 Fermat: g^255 must be 1");
2105
2106 let mut acc128 = Flat::from_raw(Block128::ONE);
2109 for _ in 0..255 {
2110 acc128 *= g_promoted;
2111 }
2112
2113 assert_eq!(
2114 acc128,
2115 Flat::from_raw(Block128::ONE),
2116 "promoted element lost multiplicative order"
2117 );
2118 }
2119
2120 proptest! {
2121 #[test]
2122 fn parity_masks_match_from_hardware(x_flat in any::<u128>()) {
2123 let tower = Block128::from_hardware(Flat::from_raw(Block128(x_flat))).0;
2124
2125 for k in 0..128 {
2126 let bit = ((tower >> k) & 1) as u8;
2127 let via_api = Flat::from_raw(Block128(x_flat)).tower_bit(k);
2128
2129 prop_assert_eq!(
2130 via_api, bit,
2131 "Block128 tower_bit_from_hardware mismatch at x_flat={:#034x}, bit_idx={}",
2132 x_flat, k
2133 );
2134 }
2135 }
2136 }
2137}