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(target_arch = "aarch64")]
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(target_arch = "aarch64"))]
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 Flat::from_raw(lhs.into_raw() + rhs.into_raw())
448 }
449
450 #[inline(always)]
451 fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
452 let lhs = lhs.into_raw();
453 let rhs = rhs.into_raw();
454
455 #[cfg(target_arch = "aarch64")]
456 {
457 PackedFlat::from_raw(neon::add_packed_128(lhs, rhs))
458 }
459
460 #[cfg(not(target_arch = "aarch64"))]
461 {
462 PackedFlat::from_raw(lhs + rhs)
463 }
464 }
465
466 #[inline(always)]
467 fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
468 let lhs = lhs.into_raw();
469 let rhs = rhs.into_raw();
470
471 #[cfg(target_arch = "aarch64")]
472 {
473 Flat::from_raw(neon::mul_flat_128(lhs, rhs))
474 }
475
476 #[cfg(not(target_arch = "aarch64"))]
477 {
478 let a_tower = Self::from_hardware(Flat::from_raw(lhs));
479 let b_tower = Self::from_hardware(Flat::from_raw(rhs));
480
481 (a_tower * b_tower).to_hardware()
482 }
483 }
484
485 #[inline(always)]
486 fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
487 let lhs = lhs.into_raw();
488 let rhs = rhs.into_raw();
489
490 #[cfg(target_arch = "aarch64")]
491 {
492 let mut res = [Block128::ZERO; PACKED_WIDTH_128];
493 for ((out, l), r) in res.iter_mut().zip(lhs.0.iter()).zip(rhs.0.iter()) {
494 *out = neon::mul_flat_128(*l, *r);
495 }
496
497 PackedFlat::from_raw(PackedBlock128(res))
498 }
499
500 #[cfg(not(target_arch = "aarch64"))]
501 {
502 let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
503 let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
504 let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
505
506 Self::unpack(lhs, &mut l);
507 Self::unpack(rhs, &mut r);
508
509 for i in 0..<Self as PackableField>::WIDTH {
510 res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
511 }
512
513 PackedFlat::from_raw(Self::pack(&res))
514 }
515 }
516
517 #[inline(always)]
518 fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
519 let broadcasted = PackedBlock128::broadcast(rhs.into_raw());
520 Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
521 }
522
523 #[inline(always)]
524 fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
525 let mask = constants::FLAT_TO_TOWER_BIT_MASKS_128[bit_idx];
526
527 let mut v = value.into_raw().0 & mask;
531 v ^= v >> 64;
532 v ^= v >> 32;
533 v ^= v >> 16;
534 v ^= v >> 8;
535 v ^= v >> 4;
536 v ^= v >> 2;
537 v ^= v >> 1;
538
539 (v & 1) as u8
540 }
541}
542
543#[cfg(not(feature = "table-math"))]
552impl FlatPromote<Block8> for Block128 {
553 #[inline(always)]
554 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
555 let val = val.into_raw();
556 Flat::from_raw(Block128(lift_ct::<8>(
557 val.0 as u64,
558 &constants::LIFT_BASIS_8.0,
559 )))
560 }
561
562 fn promote_flat_batch(input: &[Flat<Block8>], output: &mut [Flat<Self>]) {
563 let n = input.len().min(output.len());
564
565 #[cfg(target_arch = "aarch64")]
566 {
567 let full = n / 16;
568 for chunk in 0..full {
569 let i = chunk * 16;
570 unsafe {
571 neon::promote_batch_8_to_128(
572 input.as_ptr().add(i).cast::<u8>(),
573 output.as_mut_ptr().add(i).cast::<u128>(),
574 );
575 }
576 }
577
578 let tail = full * 16;
579 for i in tail..n {
580 output[i] = Self::promote_flat(input[i]);
581 }
582 }
583
584 #[cfg(not(target_arch = "aarch64"))]
585 {
586 for i in 0..n {
587 output[i] = Self::promote_flat(input[i]);
588 }
589 }
590 }
591}
592
593#[cfg(not(feature = "table-math"))]
594impl FlatPromote<Block16> for Block128 {
595 #[inline(always)]
596 fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
597 Flat::from_raw(Block128(lift_ct::<16>(
598 val.into_raw().0 as u64,
599 &constants::LIFT_BASIS_16.0,
600 )))
601 }
602
603 fn promote_flat_batch(input: &[Flat<Block16>], output: &mut [Flat<Self>]) {
604 let n = input.len().min(output.len());
605
606 #[cfg(target_arch = "aarch64")]
607 {
608 let full = n / 16;
609 for chunk in 0..full {
610 let i = chunk * 16;
611 unsafe {
612 neon::promote_batch_16_to_128(
613 input.as_ptr().add(i).cast::<u8>(),
614 output.as_mut_ptr().add(i).cast::<u128>(),
615 );
616 }
617 }
618
619 let tail = full * 16;
620 for i in tail..n {
621 output[i] = Self::promote_flat(input[i]);
622 }
623 }
624
625 #[cfg(not(target_arch = "aarch64"))]
626 {
627 for i in 0..n {
628 output[i] = Self::promote_flat(input[i]);
629 }
630 }
631 }
632}
633
634#[cfg(not(feature = "table-math"))]
635impl FlatPromote<Block32> for Block128 {
636 #[inline(always)]
637 fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
638 Flat::from_raw(Block128(lift_ct::<32>(
639 val.into_raw().0 as u64,
640 &constants::LIFT_BASIS_32.0,
641 )))
642 }
643
644 fn promote_flat_batch(input: &[Flat<Block32>], output: &mut [Flat<Self>]) {
645 let n = input.len().min(output.len());
646
647 #[cfg(target_arch = "aarch64")]
648 {
649 let full = n / 16;
650 for chunk in 0..full {
651 let i = chunk * 16;
652 unsafe {
653 neon::promote_batch_32_to_128(
654 input.as_ptr().add(i).cast::<u8>(),
655 output.as_mut_ptr().add(i).cast::<u128>(),
656 );
657 }
658 }
659
660 let tail = full * 16;
661 for i in tail..n {
662 output[i] = Self::promote_flat(input[i]);
663 }
664 }
665
666 #[cfg(not(target_arch = "aarch64"))]
667 {
668 for i in 0..n {
669 output[i] = Self::promote_flat(input[i]);
670 }
671 }
672 }
673}
674
675#[cfg(not(feature = "table-math"))]
676impl FlatPromote<Block64> for Block128 {
677 #[inline(always)]
678 fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
679 Flat::from_raw(Block128(lift_ct::<64>(
680 val.into_raw().0,
681 &constants::LIFT_BASIS_64.0,
682 )))
683 }
684}
685
686#[cfg(feature = "table-math")]
688impl FlatPromote<Block8> for Block128 {
689 #[inline(always)]
690 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
691 let idx = val.into_raw().0 as usize;
692 Flat::from_raw(Block128(unsafe {
693 *constants::LIFT_TABLE_8_TO_128.get_unchecked(idx)
694 }))
695 }
696}
697
698#[cfg(feature = "table-math")]
699impl FlatPromote<Block16> for Block128 {
700 #[inline(always)]
701 fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
702 let v = val.into_raw().0;
703 let res = unsafe {
704 *constants::PROMOTE_16_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
705 ^ *constants::PROMOTE_16_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
706 };
707
708 Flat::from_raw(Block128(res))
709 }
710}
711
712#[cfg(feature = "table-math")]
713impl FlatPromote<Block32> for Block128 {
714 #[inline(always)]
715 fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
716 let v = val.into_raw().0;
717 let res = unsafe {
718 *constants::PROMOTE_32_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
719 ^ *constants::PROMOTE_32_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
720 ^ *constants::PROMOTE_32_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
721 ^ *constants::PROMOTE_32_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
722 };
723
724 Flat::from_raw(Block128(res))
725 }
726}
727
728#[cfg(feature = "table-math")]
729impl FlatPromote<Block64> for Block128 {
730 #[inline(always)]
731 fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
732 let v = val.into_raw().0;
733 let res = unsafe {
734 *constants::PROMOTE_64_BYTE_0_TO_128.get_unchecked((v & 0xFF) as usize)
735 ^ *constants::PROMOTE_64_BYTE_1_TO_128.get_unchecked(((v >> 8) & 0xFF) as usize)
736 ^ *constants::PROMOTE_64_BYTE_2_TO_128.get_unchecked(((v >> 16) & 0xFF) as usize)
737 ^ *constants::PROMOTE_64_BYTE_3_TO_128.get_unchecked(((v >> 24) & 0xFF) as usize)
738 ^ *constants::PROMOTE_64_BYTE_4_TO_128.get_unchecked(((v >> 32) & 0xFF) as usize)
739 ^ *constants::PROMOTE_64_BYTE_5_TO_128.get_unchecked(((v >> 40) & 0xFF) as usize)
740 ^ *constants::PROMOTE_64_BYTE_6_TO_128.get_unchecked(((v >> 48) & 0xFF) as usize)
741 ^ *constants::PROMOTE_64_BYTE_7_TO_128.get_unchecked(((v >> 56) & 0xFF) as usize)
742 };
743
744 Flat::from_raw(Block128(res))
745 }
746}
747
748impl_binary_field_extras!(
753 Block128,
754 Block64,
755 map_ct_128_split,
756 TRACE_MASK_128,
757 SOLVE_QUADRATIC_BASIS_128
758);
759
760#[cfg(feature = "table-math")]
765#[inline(always)]
766pub fn apply_matrix_128(val: Block128, table: &[u128; 4096]) -> Block128 {
767 let mut res = 0u128;
768 let v = val.0;
769
770 for i in 0..16 {
774 let byte = (v >> (i * 8)) & 0xFF;
775 let idx = (i * 256) + (byte as usize);
776 res ^= unsafe { *table.get_unchecked(idx) };
777 }
778
779 Block128(res)
780}
781
782#[inline(always)]
783fn map_ct_128_split(x: u128, basis: &[u128; 128]) -> u128 {
784 let mut acc_lo = 0u64;
785 let mut acc_hi = 0u64;
786 let mut i = 0usize;
787
788 while i < 128 {
789 let bit = ((x >> i) & 1) as u64;
790 let mask = 0u64.wrapping_sub(bit);
791
792 let b = basis[i];
793 acc_lo ^= (b as u64) & mask;
794 acc_hi ^= ((b >> 64) as u64) & mask;
795
796 i += 1;
797 }
798
799 (acc_lo as u128) | ((acc_hi as u128) << 64)
800}
801
802#[cfg(not(feature = "table-math"))]
803#[inline(always)]
804fn lift_ct<const N: usize>(x: u64, basis: &'static [u128; N]) -> u128 {
805 let mut acc = 0u128;
806 let mut i = 0usize;
807
808 while i < N {
809 let bit = (x >> i) & 1;
810 let mask = 0u128.wrapping_sub(bit as u128);
811 acc ^= basis[i] & mask;
812 i += 1;
813 }
814
815 acc
816}
817
818#[cfg(target_arch = "aarch64")]
823mod neon {
824 use super::*;
825 use core::arch::aarch64::*;
826 use core::mem::transmute;
827
828 const _: () = assert!(constants::POLY_128 == 0x87, "verus twins hardcode R = 0x87");
829
830 #[inline(always)]
831 pub fn add_packed_128(lhs: PackedBlock128, rhs: PackedBlock128) -> PackedBlock128 {
832 unsafe {
833 let l: [uint8x16_t; 4] = transmute(lhs.0);
836 let r: [uint8x16_t; 4] = transmute(rhs.0);
837
838 let res = [
839 veorq_u8(l[0], r[0]),
840 veorq_u8(l[1], r[1]),
841 veorq_u8(l[2], r[2]),
842 veorq_u8(l[3], r[3]),
843 ];
844
845 transmute(res)
846 }
847 }
848
849 #[inline(always)]
850 pub fn mul_flat_128(a: Block128, b: Block128) -> Block128 {
851 unsafe {
852 let a_vec: uint64x2_t = transmute(a.0);
854 let b_vec: uint64x2_t = transmute(b.0);
855
856 let a0 = vgetq_lane_u64(a_vec, 0);
857 let a1 = vgetq_lane_u64(a_vec, 1);
858 let b0 = vgetq_lane_u64(b_vec, 0);
859 let b1 = vgetq_lane_u64(b_vec, 1);
860
861 let d0 = vmull_p64(a0, b0);
864 let d2 = vmull_p64(a1, b1);
865 let d1 = vmull_p64(a0 ^ a1, b0 ^ b1);
866
867 let d0_v: uint8x16_t = transmute(d0);
871 let d1_v: uint8x16_t = transmute(d1);
872 let d2_v: uint8x16_t = transmute(d2);
873
874 let mid_v = veorq_u8(d1_v, veorq_u8(d0_v, d2_v));
875
876 let d0_u64: uint64x2_t = transmute(d0);
878 let mid_u64: uint64x2_t = transmute(mid_v);
879 let d2_u64: uint64x2_t = transmute(d2);
880
881 let c0 = vgetq_lane_u64(d0_u64, 0);
882 let c1 = vgetq_lane_u64(d0_u64, 1) ^ vgetq_lane_u64(mid_u64, 0);
883 let c2 = vgetq_lane_u64(d2_u64, 0) ^ vgetq_lane_u64(mid_u64, 1);
884 let c3 = vgetq_lane_u64(d2_u64, 1);
885
886 let r_val = constants::POLY_128 as u64;
889
890 let p0 = vmull_p64(c2, r_val);
893 let p1 = vmull_p64(c3, r_val);
894
895 let p0_u64: uint64x2_t = transmute(p0);
896 let p1_u64: uint64x2_t = transmute(p1);
897
898 let folded_0 = vgetq_lane_u64(p0_u64, 0);
899 let folded_1 = vgetq_lane_u64(p0_u64, 1) ^ vgetq_lane_u64(p1_u64, 0);
900 let carry = vgetq_lane_u64(p1_u64, 1);
901
902 let final_0 = c0 ^ folded_0;
903 let final_1 = c1 ^ folded_1;
904
905 let carry_mul = vmull_p64(carry, r_val);
907
908 let carry_res_vec: uint64x2_t = transmute(carry_mul);
911 let carry_res = vgetq_lane_u64(carry_res_vec, 0);
912
913 let res_lo = final_0 ^ carry_res;
914 let res_hi = final_1;
915
916 Block128((res_lo as u128) | ((res_hi as u128) << 64))
917 }
918 }
919
920 #[cfg(not(feature = "table-math"))]
923 #[inline(always)]
924 pub unsafe fn promote_batch_8_to_128(input: *const u8, output: *mut u128) {
925 unsafe {
926 let vals = vld1q_u8(input);
927
928 let mask_0f = vdupq_n_u8(0x0F);
929 let lo_nib = vandq_u8(vals, mask_0f);
930 let hi_nib = vshrq_n_u8::<4>(vals);
931
932 let mut out = [vdupq_n_u8(0); 16];
933
934 macro_rules! lookup {
935 ($j:expr, $lo:ident, $hi:ident, $dst:ident) => {{
936 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_8_0_TO_128[$j].as_ptr());
937 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_8_1_TO_128[$j].as_ptr());
938
939 $dst[$j] = veorq_u8(vqtbl1q_u8(t0, $lo), vqtbl1q_u8(t1, $hi));
940 }};
941 }
942
943 lookup!(0, lo_nib, hi_nib, out);
944 lookup!(1, lo_nib, hi_nib, out);
945 lookup!(2, lo_nib, hi_nib, out);
946 lookup!(3, lo_nib, hi_nib, out);
947 lookup!(4, lo_nib, hi_nib, out);
948 lookup!(5, lo_nib, hi_nib, out);
949 lookup!(6, lo_nib, hi_nib, out);
950 lookup!(7, lo_nib, hi_nib, out);
951 lookup!(8, lo_nib, hi_nib, out);
952 lookup!(9, lo_nib, hi_nib, out);
953 lookup!(10, lo_nib, hi_nib, out);
954 lookup!(11, lo_nib, hi_nib, out);
955 lookup!(12, lo_nib, hi_nib, out);
956 lookup!(13, lo_nib, hi_nib, out);
957 lookup!(14, lo_nib, hi_nib, out);
958 lookup!(15, lo_nib, hi_nib, out);
959
960 let elems = transpose_16x16(&out);
961 for (i, elem) in elems.iter().enumerate() {
962 vst1q_u8(output.add(i).cast::<u8>(), *elem);
963 }
964 }
965 }
966
967 #[cfg(not(feature = "table-math"))]
970 #[inline(always)]
971 pub unsafe fn promote_batch_16_to_128(input: *const u8, output: *mut u128) {
972 unsafe {
973 let raw0 = vld1q_u8(input);
974 let raw1 = vld1q_u8(input.add(16));
975
976 let lo_bytes = vuzp1q_u8(raw0, raw1);
977 let hi_bytes = vuzp2q_u8(raw0, raw1);
978
979 let mask_0f = vdupq_n_u8(0x0F);
980 let n0 = vandq_u8(lo_bytes, mask_0f);
981 let n1 = vshrq_n_u8::<4>(lo_bytes);
982 let n2 = vandq_u8(hi_bytes, mask_0f);
983 let n3 = vshrq_n_u8::<4>(hi_bytes);
984
985 let mut out = [vdupq_n_u8(0); 16];
986
987 macro_rules! lookup {
988 ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident, $dst:ident) => {{
989 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_16_0_TO_128[$j].as_ptr());
990 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_16_1_TO_128[$j].as_ptr());
991 let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_16_2_TO_128[$j].as_ptr());
992 let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_16_3_TO_128[$j].as_ptr());
993
994 $dst[$j] = veorq_u8(
995 veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
996 veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
997 );
998 }};
999 }
1000
1001 lookup!(0, n0, n1, n2, n3, out);
1002 lookup!(1, n0, n1, n2, n3, out);
1003 lookup!(2, n0, n1, n2, n3, out);
1004 lookup!(3, n0, n1, n2, n3, out);
1005 lookup!(4, n0, n1, n2, n3, out);
1006 lookup!(5, n0, n1, n2, n3, out);
1007 lookup!(6, n0, n1, n2, n3, out);
1008 lookup!(7, n0, n1, n2, n3, out);
1009 lookup!(8, n0, n1, n2, n3, out);
1010 lookup!(9, n0, n1, n2, n3, out);
1011 lookup!(10, n0, n1, n2, n3, out);
1012 lookup!(11, n0, n1, n2, n3, out);
1013 lookup!(12, n0, n1, n2, n3, out);
1014 lookup!(13, n0, n1, n2, n3, out);
1015 lookup!(14, n0, n1, n2, n3, out);
1016 lookup!(15, n0, n1, n2, n3, out);
1017
1018 let elems = transpose_16x16(&out);
1019 for (i, elem) in elems.iter().enumerate() {
1020 vst1q_u8(output.add(i).cast::<u8>(), *elem);
1021 }
1022 }
1023 }
1024
1025 #[cfg(not(feature = "table-math"))]
1028 #[inline(always)]
1029 pub unsafe fn promote_batch_32_to_128(input: *const u8, output: *mut u128) {
1030 unsafe {
1031 let raw0 = vld1q_u8(input);
1032 let raw1 = vld1q_u8(input.add(16));
1033 let raw2 = vld1q_u8(input.add(32));
1034 let raw3 = vld1q_u8(input.add(48));
1035
1036 let a02 = vuzp1q_u8(raw0, raw1);
1037 let a13 = vuzp2q_u8(raw0, raw1);
1038 let b02 = vuzp1q_u8(raw2, raw3);
1039 let b13 = vuzp2q_u8(raw2, raw3);
1040
1041 let byte0 = vuzp1q_u8(a02, b02);
1042 let byte2 = vuzp2q_u8(a02, b02);
1043 let byte1 = vuzp1q_u8(a13, b13);
1044 let byte3 = vuzp2q_u8(a13, b13);
1045
1046 let mask_0f = vdupq_n_u8(0x0F);
1047 let n0 = vandq_u8(byte0, mask_0f);
1048 let n1 = vshrq_n_u8::<4>(byte0);
1049 let n2 = vandq_u8(byte1, mask_0f);
1050 let n3 = vshrq_n_u8::<4>(byte1);
1051 let n4 = vandq_u8(byte2, mask_0f);
1052 let n5 = vshrq_n_u8::<4>(byte2);
1053 let n6 = vandq_u8(byte3, mask_0f);
1054 let n7 = vshrq_n_u8::<4>(byte3);
1055
1056 let mut out = [vdupq_n_u8(0); 16];
1057
1058 macro_rules! lookup {
1059 ($j:expr, $n0:ident, $n1:ident, $n2:ident, $n3:ident,
1060 $n4:ident, $n5:ident, $n6:ident, $n7:ident, $dst:ident) => {{
1061 let t0 = vld1q_u8(constants::NIBBLE_PROMOTE_32_0_TO_128[$j].as_ptr());
1062 let t1 = vld1q_u8(constants::NIBBLE_PROMOTE_32_1_TO_128[$j].as_ptr());
1063 let t2 = vld1q_u8(constants::NIBBLE_PROMOTE_32_2_TO_128[$j].as_ptr());
1064 let t3 = vld1q_u8(constants::NIBBLE_PROMOTE_32_3_TO_128[$j].as_ptr());
1065 let t4 = vld1q_u8(constants::NIBBLE_PROMOTE_32_4_TO_128[$j].as_ptr());
1066 let t5 = vld1q_u8(constants::NIBBLE_PROMOTE_32_5_TO_128[$j].as_ptr());
1067 let t6 = vld1q_u8(constants::NIBBLE_PROMOTE_32_6_TO_128[$j].as_ptr());
1068 let t7 = vld1q_u8(constants::NIBBLE_PROMOTE_32_7_TO_128[$j].as_ptr());
1069
1070 $dst[$j] = veorq_u8(
1071 veorq_u8(
1072 veorq_u8(vqtbl1q_u8(t0, $n0), vqtbl1q_u8(t1, $n1)),
1073 veorq_u8(vqtbl1q_u8(t2, $n2), vqtbl1q_u8(t3, $n3)),
1074 ),
1075 veorq_u8(
1076 veorq_u8(vqtbl1q_u8(t4, $n4), vqtbl1q_u8(t5, $n5)),
1077 veorq_u8(vqtbl1q_u8(t6, $n6), vqtbl1q_u8(t7, $n7)),
1078 ),
1079 );
1080 }};
1081 }
1082
1083 lookup!(0, n0, n1, n2, n3, n4, n5, n6, n7, out);
1084 lookup!(1, n0, n1, n2, n3, n4, n5, n6, n7, out);
1085 lookup!(2, n0, n1, n2, n3, n4, n5, n6, n7, out);
1086 lookup!(3, n0, n1, n2, n3, n4, n5, n6, n7, out);
1087 lookup!(4, n0, n1, n2, n3, n4, n5, n6, n7, out);
1088 lookup!(5, n0, n1, n2, n3, n4, n5, n6, n7, out);
1089 lookup!(6, n0, n1, n2, n3, n4, n5, n6, n7, out);
1090 lookup!(7, n0, n1, n2, n3, n4, n5, n6, n7, out);
1091 lookup!(8, n0, n1, n2, n3, n4, n5, n6, n7, out);
1092 lookup!(9, n0, n1, n2, n3, n4, n5, n6, n7, out);
1093 lookup!(10, n0, n1, n2, n3, n4, n5, n6, n7, out);
1094 lookup!(11, n0, n1, n2, n3, n4, n5, n6, n7, out);
1095 lookup!(12, n0, n1, n2, n3, n4, n5, n6, n7, out);
1096 lookup!(13, n0, n1, n2, n3, n4, n5, n6, n7, out);
1097 lookup!(14, n0, n1, n2, n3, n4, n5, n6, n7, out);
1098 lookup!(15, n0, n1, n2, n3, n4, n5, n6, n7, out);
1099
1100 let elems = transpose_16x16(&out);
1101 for (i, elem) in elems.iter().enumerate() {
1102 vst1q_u8(output.add(i).cast::<u8>(), *elem);
1103 }
1104 }
1105 }
1106
1107 #[cfg(not(feature = "table-math"))]
1109 #[inline(always)]
1110 unsafe fn transpose_16x16(r: &[uint8x16_t; 16]) -> [uint8x16_t; 16] {
1111 #[inline(always)]
1114 const fn u8_to_u16(v: uint8x16_t) -> uint16x8_t {
1115 unsafe { transmute::<uint8x16_t, uint16x8_t>(v) }
1116 }
1117
1118 #[inline(always)]
1119 const fn u16_to_u32(v: uint16x8_t) -> uint32x4_t {
1120 unsafe { transmute::<uint16x8_t, uint32x4_t>(v) }
1121 }
1122
1123 #[inline(always)]
1124 const fn u32_to_u64(v: uint32x4_t) -> uint64x2_t {
1125 unsafe { transmute::<uint32x4_t, uint64x2_t>(v) }
1126 }
1127
1128 #[inline(always)]
1129 const fn u64_to_u8(v: uint64x2_t) -> uint8x16_t {
1130 unsafe { transmute::<uint64x2_t, uint8x16_t>(v) }
1131 }
1132
1133 unsafe {
1134 let a0 = vtrn1q_u8(r[0], r[1]);
1137 let a1 = vtrn2q_u8(r[0], r[1]);
1138 let a2 = vtrn1q_u8(r[2], r[3]);
1139 let a3 = vtrn2q_u8(r[2], r[3]);
1140 let a4 = vtrn1q_u8(r[4], r[5]);
1141 let a5 = vtrn2q_u8(r[4], r[5]);
1142 let a6 = vtrn1q_u8(r[6], r[7]);
1143 let a7 = vtrn2q_u8(r[6], r[7]);
1144 let a8 = vtrn1q_u8(r[8], r[9]);
1145 let a9 = vtrn2q_u8(r[8], r[9]);
1146 let a10 = vtrn1q_u8(r[10], r[11]);
1147 let a11 = vtrn2q_u8(r[10], r[11]);
1148 let a12 = vtrn1q_u8(r[12], r[13]);
1149 let a13 = vtrn2q_u8(r[12], r[13]);
1150 let a14 = vtrn1q_u8(r[14], r[15]);
1151 let a15 = vtrn2q_u8(r[14], r[15]);
1152
1153 let b0 = vtrn1q_u16(u8_to_u16(a0), u8_to_u16(a2));
1156 let b2 = vtrn2q_u16(u8_to_u16(a0), u8_to_u16(a2));
1157 let b1 = vtrn1q_u16(u8_to_u16(a1), u8_to_u16(a3));
1158 let b3 = vtrn2q_u16(u8_to_u16(a1), u8_to_u16(a3));
1159 let b4 = vtrn1q_u16(u8_to_u16(a4), u8_to_u16(a6));
1160 let b6 = vtrn2q_u16(u8_to_u16(a4), u8_to_u16(a6));
1161 let b5 = vtrn1q_u16(u8_to_u16(a5), u8_to_u16(a7));
1162 let b7 = vtrn2q_u16(u8_to_u16(a5), u8_to_u16(a7));
1163 let b8 = vtrn1q_u16(u8_to_u16(a8), u8_to_u16(a10));
1164 let b10 = vtrn2q_u16(u8_to_u16(a8), u8_to_u16(a10));
1165 let b9 = vtrn1q_u16(u8_to_u16(a9), u8_to_u16(a11));
1166 let b11 = vtrn2q_u16(u8_to_u16(a9), u8_to_u16(a11));
1167 let b12 = vtrn1q_u16(u8_to_u16(a12), u8_to_u16(a14));
1168 let b14 = vtrn2q_u16(u8_to_u16(a12), u8_to_u16(a14));
1169 let b13 = vtrn1q_u16(u8_to_u16(a13), u8_to_u16(a15));
1170 let b15 = vtrn2q_u16(u8_to_u16(a13), u8_to_u16(a15));
1171
1172 let c0 = vtrn1q_u32(u16_to_u32(b0), u16_to_u32(b4));
1175 let c4 = vtrn2q_u32(u16_to_u32(b0), u16_to_u32(b4));
1176 let c1 = vtrn1q_u32(u16_to_u32(b1), u16_to_u32(b5));
1177 let c5 = vtrn2q_u32(u16_to_u32(b1), u16_to_u32(b5));
1178 let c2 = vtrn1q_u32(u16_to_u32(b2), u16_to_u32(b6));
1179 let c6 = vtrn2q_u32(u16_to_u32(b2), u16_to_u32(b6));
1180 let c3 = vtrn1q_u32(u16_to_u32(b3), u16_to_u32(b7));
1181 let c7 = vtrn2q_u32(u16_to_u32(b3), u16_to_u32(b7));
1182 let c8 = vtrn1q_u32(u16_to_u32(b8), u16_to_u32(b12));
1183 let c12 = vtrn2q_u32(u16_to_u32(b8), u16_to_u32(b12));
1184 let c9 = vtrn1q_u32(u16_to_u32(b9), u16_to_u32(b13));
1185 let c13 = vtrn2q_u32(u16_to_u32(b9), u16_to_u32(b13));
1186 let c10 = vtrn1q_u32(u16_to_u32(b10), u16_to_u32(b14));
1187 let c14 = vtrn2q_u32(u16_to_u32(b10), u16_to_u32(b14));
1188 let c11 = vtrn1q_u32(u16_to_u32(b11), u16_to_u32(b15));
1189 let c15 = vtrn2q_u32(u16_to_u32(b11), u16_to_u32(b15));
1190
1191 [
1194 u64_to_u8(vtrn1q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1195 u64_to_u8(vtrn1q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1196 u64_to_u8(vtrn1q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1197 u64_to_u8(vtrn1q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1198 u64_to_u8(vtrn1q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1199 u64_to_u8(vtrn1q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1200 u64_to_u8(vtrn1q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1201 u64_to_u8(vtrn1q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1202 u64_to_u8(vtrn2q_u64(u32_to_u64(c0), u32_to_u64(c8))),
1203 u64_to_u8(vtrn2q_u64(u32_to_u64(c1), u32_to_u64(c9))),
1204 u64_to_u8(vtrn2q_u64(u32_to_u64(c2), u32_to_u64(c10))),
1205 u64_to_u8(vtrn2q_u64(u32_to_u64(c3), u32_to_u64(c11))),
1206 u64_to_u8(vtrn2q_u64(u32_to_u64(c4), u32_to_u64(c12))),
1207 u64_to_u8(vtrn2q_u64(u32_to_u64(c5), u32_to_u64(c13))),
1208 u64_to_u8(vtrn2q_u64(u32_to_u64(c6), u32_to_u64(c14))),
1209 u64_to_u8(vtrn2q_u64(u32_to_u64(c7), u32_to_u64(c15))),
1210 ]
1211 }
1212 }
1213}
1214
1215#[cfg(test)]
1220mod tests {
1221 use super::*;
1222 use proptest::prelude::*;
1223 use rand::{RngExt, rng};
1224
1225 #[test]
1230 fn tower_constants() {
1231 let tau128 = Block128::EXTENSION_TAU;
1234 let (lo128, hi128) = tau128.split();
1235 assert_eq!(lo128, Block64::ZERO);
1236 assert_eq!(hi128, Block64::TAU);
1237 }
1238
1239 #[test]
1240 fn add_truth() {
1241 let zero = Block128::ZERO;
1242 let one = Block128::ONE;
1243
1244 assert_eq!(zero + zero, zero);
1245 assert_eq!(zero + one, one);
1246 assert_eq!(one + zero, one);
1247 assert_eq!(one + one, zero);
1248 }
1249
1250 #[test]
1251 fn mul_truth() {
1252 let zero = Block128::ZERO;
1253 let one = Block128::ONE;
1254
1255 assert_eq!(zero * zero, zero);
1256 assert_eq!(zero * one, zero);
1257 assert_eq!(one * one, one);
1258 }
1259
1260 #[test]
1261 fn add() {
1262 assert_eq!(Block128(5) + Block128(3), Block128(6));
1265 }
1266
1267 #[test]
1268 fn mul_simple() {
1269 assert_eq!(Block128(2) * Block128(2), Block128(4));
1272 }
1273
1274 #[test]
1275 fn mul_overflow() {
1276 assert_eq!(Block128(0x57) * Block128(0x83), Block128(0xC1));
1280 }
1281
1282 #[test]
1283 fn karatsuba_correctness() {
1284 let x = Block128::new(Block64::ZERO, Block64::ONE);
1296 let squared = x * x;
1297
1298 let (res_lo, res_hi) = squared.split();
1300
1301 assert_eq!(res_hi, Block64::ONE, "X^2 should contain X component");
1302 assert_eq!(
1303 res_lo,
1304 Block64(0x2000_0000_0000_0000),
1305 "X^2 should contain tau component (0x2000_0000_0000_0000)"
1306 );
1307 }
1308
1309 #[test]
1310 fn security_zeroize() {
1311 let mut secret_val = Block128::from(0xDEAD_BEEF_CAFE_BABE_u128);
1313 assert_ne!(secret_val, Block128::ZERO);
1314
1315 secret_val.zeroize();
1317
1318 assert_eq!(secret_val, Block128::ZERO, "Memory was not wiped!");
1320
1321 assert_eq!(secret_val.0, 0u128, "Underlying memory leak detected");
1323 }
1324
1325 #[test]
1326 fn invert_zero() {
1327 assert_eq!(
1329 Block128::ZERO.invert(),
1330 Block128::ZERO,
1331 "invert(0) must return 0"
1332 );
1333 }
1334
1335 #[test]
1336 fn inversion_random() {
1337 let mut rng = rng();
1338 for _i in 0..1000 {
1339 let val = Block128(rng.random());
1340
1341 if val != Block128::ZERO {
1342 let inv = val.invert();
1343 let identity = val * inv;
1344
1345 assert_eq!(
1346 identity,
1347 Block128::ONE,
1348 "Inversion identity failed: a * a^-1 != 1"
1349 );
1350 }
1351 }
1352 }
1353
1354 #[test]
1355 fn tower_embedding() {
1356 let mut rng = rng();
1357 for _ in 0..100 {
1358 let a = Block64(rng.random());
1359 let b = Block64(rng.random());
1360
1361 let a_lifted: Block128 = a.into();
1363 let (lo, hi) = a_lifted.split();
1364
1365 assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
1366 assert_eq!(
1367 hi,
1368 Block64::ZERO,
1369 "Embedding structure failed: high part must be zero"
1370 );
1371
1372 let sum_sub = a + b;
1374 let sum_lifted: Block128 = sum_sub.into();
1375 let sum_in_super = Block128::from(a) + Block128::from(b);
1376
1377 assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
1378
1379 let prod_sub = a * b;
1384 let prod_lifted: Block128 = prod_sub.into();
1385 let prod_in_super = Block128::from(a) * Block128::from(b);
1386
1387 assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
1388 }
1389 }
1390
1391 #[test]
1396 fn isomorphism_roundtrip() {
1397 let mut rng = rng();
1398 for _ in 0..1000 {
1399 let val = Block128(rng.random::<u128>());
1400 assert_eq!(val.to_hardware().to_tower(), val);
1401 }
1402 }
1403
1404 #[test]
1405 fn flat_mul_homomorphism() {
1406 let mut rng = rng();
1407 for _ in 0..1000 {
1408 let a = Block128(rng.random());
1409 let b = Block128(rng.random());
1410
1411 let expected_flat = (a * b).to_hardware();
1412 let actual_flat = a.to_hardware() * b.to_hardware();
1413
1414 assert_eq!(
1415 actual_flat, expected_flat,
1416 "Block128 flat multiplication mismatch: (a*b)^H != a^H * b^H"
1417 );
1418 }
1419 }
1420
1421 #[test]
1422 fn packed_consistency() {
1423 let mut rng = rng();
1424 for _ in 0..100 {
1425 let mut a_vals = [Block128::ZERO; 4];
1426 let mut b_vals = [Block128::ZERO; 4];
1427
1428 for i in 0..4 {
1429 a_vals[i] = Block128(rng.random::<u128>());
1430 b_vals[i] = Block128(rng.random::<u128>());
1431 }
1432
1433 let a_flat_vals = a_vals.map(|x| x.to_hardware());
1434 let b_flat_vals = b_vals.map(|x| x.to_hardware());
1435 let a_packed = Flat::<Block128>::pack(&a_flat_vals);
1436 let b_packed = Flat::<Block128>::pack(&b_flat_vals);
1437
1438 let add_res = Block128::add_hardware_packed(a_packed, b_packed);
1440
1441 let mut add_out = [Block128::ZERO.to_hardware(); 4];
1442 Flat::<Block128>::unpack(add_res, &mut add_out);
1443
1444 for i in 0..4 {
1445 assert_eq!(
1446 add_out[i],
1447 (a_vals[i] + b_vals[i]).to_hardware(),
1448 "Block128 SIMD add mismatch at index {}",
1449 i
1450 );
1451 }
1452
1453 let mul_res = Block128::mul_hardware_packed(a_packed, b_packed);
1455
1456 let mut mul_out = [Block128::ZERO.to_hardware(); 4];
1457 Flat::<Block128>::unpack(mul_res, &mut mul_out);
1458
1459 for i in 0..4 {
1460 let expected_flat = (a_vals[i] * b_vals[i]).to_hardware();
1461 assert_eq!(
1462 mul_out[i], expected_flat,
1463 "Block128 SIMD mul mismatch at index {}",
1464 i
1465 );
1466 }
1467 }
1468 }
1469
1470 #[test]
1475 fn pack_unpack_roundtrip() {
1476 let mut rng = rng();
1477 let mut data = [Block128::ZERO; PACKED_WIDTH_128];
1478 for v in data.iter_mut() {
1479 *v = Block128(rng.random());
1480 }
1481
1482 let packed = Block128::pack(&data);
1483 let mut unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1484 Block128::unpack(packed, &mut unpacked);
1485 assert_eq!(data, unpacked);
1486 }
1487
1488 #[test]
1489 fn packed_add_consistency() {
1490 let mut rng = rng();
1491 let mut a_vals = [Block128::ZERO; PACKED_WIDTH_128];
1492 let mut b_vals = [Block128::ZERO; PACKED_WIDTH_128];
1493
1494 for i in 0..PACKED_WIDTH_128 {
1495 a_vals[i] = Block128(rng.random());
1496 b_vals[i] = Block128(rng.random());
1497 }
1498
1499 let res_packed = Block128::pack(&a_vals) + Block128::pack(&b_vals);
1500 let mut res_unpacked = [Block128::ZERO; PACKED_WIDTH_128];
1501 Block128::unpack(res_packed, &mut res_unpacked);
1502
1503 for i in 0..PACKED_WIDTH_128 {
1504 assert_eq!(res_unpacked[i], a_vals[i] + b_vals[i]);
1505 }
1506 }
1507
1508 #[test]
1509 fn packed_mul_consistency() {
1510 let mut rng = rng();
1511
1512 for _ in 0..1000 {
1513 let mut a_arr = [Block128::ZERO; PACKED_WIDTH_128];
1516 let mut b_arr = [Block128::ZERO; PACKED_WIDTH_128];
1517
1518 for i in 0..PACKED_WIDTH_128 {
1519 let val_a: u128 = rng.random();
1521 let val_b: u128 = rng.random();
1522 a_arr[i] = Block128(val_a);
1523 b_arr[i] = Block128(val_b);
1524 }
1525
1526 let a_packed = PackedBlock128(a_arr);
1527 let b_packed = PackedBlock128(b_arr);
1528
1529 let c_packed = a_packed * b_packed;
1531
1532 let mut c_expected = [Block128::ZERO; PACKED_WIDTH_128];
1534 for i in 0..PACKED_WIDTH_128 {
1535 c_expected[i] = a_arr[i] * b_arr[i];
1536 }
1537
1538 assert_eq!(c_packed.0, c_expected, "SIMD multiplication mismatch!");
1539 }
1540 }
1541
1542 #[inline(always)]
1547 fn promote_block8_tables(val: Block8) -> Block128 {
1548 let idx_flat = val.0 as usize;
1550 let tower_byte = unsafe { *constants::FLAT_TO_TOWER_8.get_unchecked(idx_flat) };
1551 let idx_tower = tower_byte as usize;
1552
1553 Block128(unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx_tower) })
1554 }
1555
1556 #[inline(always)]
1557 fn promote_block16_tables(val: Block16) -> Block128 {
1558 let v_flat = val.0;
1559
1560 let mut v_tower = 0u16;
1561 for i in 0..2 {
1562 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1563 let idx = (i * 256) + byte;
1564 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_16.get_unchecked(idx) };
1565 }
1566
1567 let mut res = 0u128;
1568 for i in 0..2 {
1569 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1570 let idx = (i * 256) + byte;
1571 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1572 }
1573
1574 Block128(res)
1575 }
1576
1577 #[inline(always)]
1578 fn promote_block32_tables(val: Block32) -> Block128 {
1579 let v_flat = val.0;
1580
1581 let mut v_tower = 0u32;
1582 for i in 0..4 {
1583 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1584 let idx = (i * 256) + byte;
1585 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_32.get_unchecked(idx) };
1586 }
1587
1588 let mut res = 0u128;
1589 for i in 0..4 {
1590 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1591 let idx = (i * 256) + byte;
1592 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1593 }
1594
1595 Block128(res)
1596 }
1597
1598 #[inline(always)]
1599 fn promote_block64_tables(val: Block64) -> Block128 {
1600 let v_flat = val.0;
1601
1602 let mut v_tower = 0u64;
1603 for i in 0..8 {
1604 let byte = ((v_flat >> (i * 8)) & 0xFF) as usize;
1605 let idx = (i * 256) + byte;
1606 v_tower ^= unsafe { *constants::FLAT_TO_TOWER_64.get_unchecked(idx) };
1607 }
1608
1609 let mut res = 0u128;
1610 for i in 0..8 {
1611 let byte = ((v_tower >> (i * 8)) & 0xFF) as usize;
1612 let idx = (i * 256) + byte;
1613 res ^= unsafe { *constants::TOWER_TO_FLAT_128.get_unchecked(idx) };
1614 }
1615
1616 Block128(res)
1617 }
1618
1619 #[test]
1620 fn lift_from_partial_hardware_matches_tables_block8_exhaustive() {
1621 for x in 0u16..=u8::MAX as u16 {
1622 let v = Block8(x as u8);
1623 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1624 let expected = promote_block8_tables(v);
1625
1626 assert_eq!(got, expected);
1627 }
1628 }
1629
1630 #[test]
1631 fn lift_from_partial_hardware_matches_tables_block16_exhaustive() {
1632 for x in 0..=u16::MAX {
1633 let v = Block16(x);
1634 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1635 let expected = promote_block16_tables(v);
1636
1637 assert_eq!(got, expected);
1638 }
1639 }
1640
1641 #[test]
1642 fn lift_from_partial_hardware_matches_tables_block32_random() {
1643 let mut rng = rng();
1644 for _ in 0..10_000 {
1645 let v = Block32(rng.random::<u32>());
1646 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1647 let expected = promote_block32_tables(v);
1648
1649 assert_eq!(got, expected);
1650 }
1651 }
1652
1653 #[test]
1654 fn lift_from_partial_hardware_matches_tables_block64_random() {
1655 let mut rng = rng();
1656 for _ in 0..10_000 {
1657 let v = Block64(rng.random::<u64>());
1658 let got = Block128::promote_flat(Flat::from_raw(v)).into_raw();
1659 let expected = promote_block64_tables(v);
1660
1661 assert_eq!(got, expected);
1662 }
1663 }
1664
1665 #[test]
1670 fn promote_flat_batch_matches_scalar_block8() {
1671 let mut rng = rng();
1672 let input: Vec<Flat<Block8>> = (0..64)
1673 .map(|_| Block8(rng.random::<u8>()).to_hardware())
1674 .collect();
1675
1676 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 64];
1677 Block128::promote_flat_batch(&input, &mut batch_out);
1678
1679 for (i, &v) in input.iter().enumerate() {
1680 let scalar = Block128::promote_flat(v);
1681 assert_eq!(batch_out[i], scalar, "batch/scalar mismatch at index {}", i);
1682 }
1683 }
1684
1685 #[test]
1686 fn promote_flat_batch_matches_scalar_block16() {
1687 let mut rng = rng();
1688 let input: Vec<Flat<Block16>> = (0..32)
1689 .map(|_| Block16(rng.random::<u16>()).to_hardware())
1690 .collect();
1691
1692 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 32];
1693 Block128::promote_flat_batch(&input, &mut batch_out);
1694
1695 for (i, &v) in input.iter().enumerate() {
1696 assert_eq!(
1697 batch_out[i],
1698 Block128::promote_flat(v),
1699 "batch/scalar mismatch at index {}",
1700 i
1701 );
1702 }
1703 }
1704
1705 #[test]
1706 fn promote_flat_batch_matches_scalar_block32() {
1707 let mut rng = rng();
1708 let input: Vec<Flat<Block32>> = (0..16)
1709 .map(|_| Block32(rng.random::<u32>()).to_hardware())
1710 .collect();
1711
1712 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 16];
1713 Block128::promote_flat_batch(&input, &mut batch_out);
1714
1715 for (i, &v) in input.iter().enumerate() {
1716 assert_eq!(
1717 batch_out[i],
1718 Block128::promote_flat(v),
1719 "batch/scalar mismatch at index {}",
1720 i
1721 );
1722 }
1723 }
1724
1725 #[test]
1726 fn promote_flat_batch_matches_scalar_block64() {
1727 let mut rng = rng();
1728 let input: Vec<Flat<Block64>> = (0..8)
1729 .map(|_| Block64(rng.random::<u64>()).to_hardware())
1730 .collect();
1731
1732 let mut batch_out = vec![Flat::from_raw(Block128::ZERO); 8];
1733 Block128::promote_flat_batch(&input, &mut batch_out);
1734
1735 for (i, &v) in input.iter().enumerate() {
1736 assert_eq!(
1737 batch_out[i],
1738 Block128::promote_flat(v),
1739 "batch/scalar mismatch at index {}",
1740 i
1741 );
1742 }
1743 }
1744
1745 #[test]
1746 fn promote_flat_batch_partial_slice() {
1747 let input: Vec<Flat<Block8>> = (0..16).map(|i| Block8(i as u8).to_hardware()).collect();
1748
1749 let mut out_short = vec![Flat::from_raw(Block128::ZERO); 5];
1751 Block128::promote_flat_batch(&input, &mut out_short);
1752
1753 for i in 0..5 {
1754 assert_eq!(out_short[i], Block128::promote_flat(input[i]));
1755 }
1756
1757 let short_input = &input[..3];
1759 let mut out_long = vec![Flat::from_raw(Block128::ZERO); 10];
1760
1761 Block128::promote_flat_batch(short_input, &mut out_long);
1762
1763 for i in 0..3 {
1764 assert_eq!(out_long[i], Block128::promote_flat(short_input[i]));
1765 }
1766
1767 for val in &out_long[3..10] {
1769 assert_eq!(*val, Flat::from_raw(Block128::ZERO));
1770 }
1771 }
1772
1773 #[test]
1774 fn promote_edge_zero() {
1775 let zero = Flat::from_raw(Block8(0));
1776 let promoted = Block128::promote_flat(zero);
1777
1778 assert_eq!(
1779 promoted,
1780 Flat::from_raw(Block128::ZERO),
1781 "promote(0) must be 0"
1782 );
1783
1784 let input = vec![zero; 16];
1787 let mut output = vec![Flat::from_raw(Block128(0xDEAD)); 16];
1788
1789 Block128::promote_flat_batch(&input, &mut output);
1790
1791 for o in &output {
1792 assert_eq!(*o, Flat::from_raw(Block128::ZERO));
1793 }
1794 }
1795
1796 #[test]
1797 fn promote_edge_one() {
1798 let one_flat8 = Block8::ONE.to_hardware();
1799 let one_flat128 = Block128::ONE.to_hardware();
1800
1801 assert_eq!(
1802 Block128::promote_flat(one_flat8),
1803 one_flat128,
1804 "promote(1) must equal 1 in target field"
1805 );
1806 }
1807
1808 #[test]
1809 fn promote_edge_max_block8() {
1810 let max = Flat::from_raw(Block8(0xFF));
1811 let promoted = Block128::promote_flat(max);
1812
1813 assert_ne!(promoted, Flat::from_raw(Block128::ZERO));
1815
1816 let tower_8 = max.to_tower();
1819 let tower_128 = Block128::from(tower_8);
1820
1821 assert_eq!(promoted.to_tower(), tower_128);
1822 }
1823
1824 #[test]
1825 fn promote_edge_single_bits() {
1826 for bit in 0..8 {
1827 let val = Flat::from_raw(Block8(1u8 << bit));
1828 let promoted = Block128::promote_flat(val);
1829
1830 assert_ne!(
1832 promoted,
1833 Flat::from_raw(Block128::ZERO),
1834 "single-bit {} promoted to zero",
1835 bit
1836 );
1837
1838 let tower_8 = val.to_tower();
1840 let tower_128 = Block128::from(tower_8);
1841
1842 assert_eq!(
1843 promoted.to_tower(),
1844 tower_128,
1845 "tower roundtrip failed for bit {}",
1846 bit
1847 );
1848 }
1849 }
1850
1851 #[test]
1852 fn promote_edge_alternating_packed() {
1853 let input: Vec<Flat<Block8>> = (0..16)
1854 .map(|i| {
1855 if i % 2 == 0 {
1856 Flat::from_raw(Block8(0x00))
1857 } else {
1858 Flat::from_raw(Block8(0xFF))
1859 }
1860 })
1861 .collect();
1862
1863 let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1864 Block128::promote_flat_batch(&input, &mut output);
1865
1866 for (i, &v) in input.iter().enumerate() {
1867 assert_eq!(
1868 output[i],
1869 Block128::promote_flat(v),
1870 "alternating mismatch at {}",
1871 i
1872 );
1873 }
1874 }
1875
1876 #[test]
1877 fn promote_edge_all_same_packed() {
1878 let val = Flat::from_raw(Block8(0x42));
1879 let expected = Block128::promote_flat(val);
1880
1881 let input = vec![val; 16];
1882 let mut output = vec![Flat::from_raw(Block128::ZERO); 16];
1883
1884 Block128::promote_flat_batch(&input, &mut output);
1885
1886 for (i, o) in output.iter().enumerate() {
1887 assert_eq!(*o, expected, "all-same mismatch at {}", i);
1888 }
1889 }
1890
1891 #[test]
1892 fn promote_tower_roundtrip_block8() {
1893 for x in 0u16..=u8::MAX as u16 {
1894 let b8 = Block8(x as u8);
1895 let promoted = Block128::promote_flat(b8.to_hardware());
1896 let tower_128 = promoted.to_tower();
1897 let embedded = Block128::from(b8);
1898
1899 assert_eq!(
1900 tower_128, embedded,
1901 "tower roundtrip failed for Block8({})",
1902 x
1903 );
1904 }
1905 }
1906
1907 #[test]
1908 fn promote_tower_roundtrip_block16() {
1909 let mut rng = rng();
1910 for _ in 0..10_000 {
1911 let v = Block16(rng.random::<u16>());
1912 let promoted = Block128::promote_flat(v.to_hardware());
1913 let tower_128 = promoted.to_tower();
1914 let embedded = Block128::from(v);
1915
1916 assert_eq!(
1917 tower_128, embedded,
1918 "tower roundtrip failed for Block16({})",
1919 v.0
1920 );
1921 }
1922 }
1923
1924 #[test]
1925 fn promote_tower_roundtrip_block32() {
1926 let mut rng = rng();
1927 for _ in 0..10_000 {
1928 let v = Block32(rng.random::<u32>());
1929 let promoted = Block128::promote_flat(v.to_hardware());
1930 let tower_128 = promoted.to_tower();
1931 let embedded = Block128::from(v);
1932
1933 assert_eq!(
1934 tower_128, embedded,
1935 "tower roundtrip failed for Block32({})",
1936 v.0
1937 );
1938 }
1939 }
1940
1941 #[test]
1942 fn promote_tower_roundtrip_block64() {
1943 let mut rng = rng();
1944 for _ in 0..10_000 {
1945 let v = Block64(rng.random::<u64>());
1946 let promoted = Block128::promote_flat(v.to_hardware());
1947 let tower_128 = promoted.to_tower();
1948 let embedded = Block128::from(v);
1949
1950 assert_eq!(
1951 tower_128, embedded,
1952 "tower roundtrip failed for Block64({})",
1953 v.0
1954 );
1955 }
1956 }
1957
1958 #[test]
1959 fn promote_algebraic_homomorphism_add_block8() {
1960 let mut rng = rng();
1961 for _ in 0..1000 {
1962 let a = Block8(rng.random::<u8>());
1963 let b = Block8(rng.random::<u8>());
1964
1965 let promote_a = Block128::promote_flat(a.to_hardware());
1966 let promote_b = Block128::promote_flat(b.to_hardware());
1967 let promote_sum = Block128::promote_flat((a + b).to_hardware());
1968
1969 assert_eq!(
1970 promote_a + promote_b,
1971 promote_sum,
1972 "add homomorphism: promote(a)+promote(b) != promote(a+b)"
1973 );
1974 }
1975 }
1976
1977 #[test]
1978 fn promote_algebraic_homomorphism_mul_block8() {
1979 let mut rng = rng();
1980 for _ in 0..1000 {
1981 let a = Block8(rng.random::<u8>());
1982 let b = Block8(rng.random::<u8>());
1983
1984 let promote_a = Block128::promote_flat(a.to_hardware());
1985 let promote_b = Block128::promote_flat(b.to_hardware());
1986 let promote_prod = Block128::promote_flat((a * b).to_hardware());
1987
1988 assert_eq!(
1991 promote_a * promote_b,
1992 promote_prod,
1993 "mul homomorphism: promote(a)*promote(b) != promote(a*b)"
1994 );
1995 }
1996 }
1997
1998 #[test]
1999 fn promote_algebraic_homomorphism_add_block16() {
2000 let mut rng = rng();
2001 for _ in 0..1000 {
2002 let a = Block16(rng.random::<u16>());
2003 let b = Block16(rng.random::<u16>());
2004
2005 let pa = Block128::promote_flat(a.to_hardware());
2006 let pb = Block128::promote_flat(b.to_hardware());
2007 let p_sum = Block128::promote_flat((a + b).to_hardware());
2008
2009 assert_eq!(pa + pb, p_sum, "Block16 add homomorphism failed");
2010 }
2011 }
2012
2013 #[test]
2014 fn promote_algebraic_homomorphism_mul_block16() {
2015 let mut rng = rng();
2016 for _ in 0..1000 {
2017 let a = Block16(rng.random::<u16>());
2018 let b = Block16(rng.random::<u16>());
2019
2020 let pa = Block128::promote_flat(a.to_hardware());
2021 let pb = Block128::promote_flat(b.to_hardware());
2022 let p_prod = Block128::promote_flat((a * b).to_hardware());
2023
2024 assert_eq!(pa * pb, p_prod, "Block16 mul homomorphism failed");
2025 }
2026 }
2027
2028 #[test]
2029 fn promote_algebraic_homomorphism_add_block32() {
2030 let mut rng = rng();
2031 for _ in 0..1000 {
2032 let a = Block32(rng.random::<u32>());
2033 let b = Block32(rng.random::<u32>());
2034
2035 let pa = Block128::promote_flat(a.to_hardware());
2036 let pb = Block128::promote_flat(b.to_hardware());
2037 let p_sum = Block128::promote_flat((a + b).to_hardware());
2038
2039 assert_eq!(pa + pb, p_sum, "Block32 add homomorphism failed");
2040 }
2041 }
2042
2043 #[test]
2044 fn promote_algebraic_homomorphism_mul_block32() {
2045 let mut rng = rng();
2046 for _ in 0..1000 {
2047 let a = Block32(rng.random::<u32>());
2048 let b = Block32(rng.random::<u32>());
2049
2050 let pa = Block128::promote_flat(a.to_hardware());
2051 let pb = Block128::promote_flat(b.to_hardware());
2052 let p_prod = Block128::promote_flat((a * b).to_hardware());
2053
2054 assert_eq!(pa * pb, p_prod, "Block32 mul homomorphism failed");
2055 }
2056 }
2057
2058 #[test]
2059 fn promote_algebraic_homomorphism_add_block64() {
2060 let mut rng = rng();
2061 for _ in 0..1000 {
2062 let a = Block64(rng.random::<u64>());
2063 let b = Block64(rng.random::<u64>());
2064
2065 let pa = Block128::promote_flat(a.to_hardware());
2066 let pb = Block128::promote_flat(b.to_hardware());
2067 let p_sum = Block128::promote_flat((a + b).to_hardware());
2068
2069 assert_eq!(pa + pb, p_sum, "Block64 add homomorphism failed");
2070 }
2071 }
2072
2073 #[test]
2074 fn promote_algebraic_homomorphism_mul_block64() {
2075 let mut rng = rng();
2076 for _ in 0..1000 {
2077 let a = Block64(rng.random::<u64>());
2078 let b = Block64(rng.random::<u64>());
2079
2080 let pa = Block128::promote_flat(a.to_hardware());
2081 let pb = Block128::promote_flat(b.to_hardware());
2082 let p_prod = Block128::promote_flat((a * b).to_hardware());
2083
2084 assert_eq!(pa * pb, p_prod, "Block64 mul homomorphism failed");
2085 }
2086 }
2087
2088 #[test]
2089 fn promote_generator_preserves_order() {
2090 let g = Block8(3);
2092 let g_promoted = Block128::promote_flat(g.to_hardware());
2093
2094 let mut acc8 = Block8::ONE;
2097 for _ in 0..255 {
2098 acc8 *= g;
2099 }
2100
2101 assert_eq!(acc8, Block8::ONE, "Block8 Fermat: g^255 must be 1");
2102
2103 let mut acc128 = Flat::from_raw(Block128::ONE);
2106 for _ in 0..255 {
2107 acc128 *= g_promoted;
2108 }
2109
2110 assert_eq!(
2111 acc128,
2112 Flat::from_raw(Block128::ONE),
2113 "promoted element lost multiplicative order"
2114 );
2115 }
2116
2117 proptest! {
2118 #[test]
2119 fn parity_masks_match_from_hardware(x_flat in any::<u128>()) {
2120 let tower = Block128::from_hardware(Flat::from_raw(Block128(x_flat))).0;
2121
2122 for k in 0..128 {
2123 let bit = ((tower >> k) & 1) as u8;
2124 let via_api = Flat::from_raw(Block128(x_flat)).tower_bit(k);
2125
2126 prop_assert_eq!(
2127 via_api, bit,
2128 "Block128 tower_bit_from_hardware mismatch at x_flat={:#034x}, bit_idx={}",
2129 x_flat, k
2130 );
2131 }
2132 }
2133 }
2134}