1use crate::constants::FLAT_TO_TOWER_BIT_MASKS_8;
20use crate::towers::bit::Bit;
21use crate::{
22 CanonicalDeserialize, CanonicalSerialize, Flat, HardwareField, PackableField, PackedFlat,
23 TowerField, constants,
24};
25use core::ops::{Add, AddAssign, BitXor, Mul, MulAssign, Sub, SubAssign};
26use serde::{Deserialize, Serialize};
27use zeroize::Zeroize;
28
29#[cfg(not(feature = "table-math"))]
30#[repr(align(64))]
31struct CtConvertBasisU8<const N: usize>([u8; N]);
32
33#[cfg(not(feature = "table-math"))]
34static TOWER_TO_FLAT_BASIS_8: CtConvertBasisU8<8> =
35 CtConvertBasisU8(constants::RAW_TOWER_TO_FLAT_8);
36
37#[cfg(not(feature = "table-math"))]
38static FLAT_TO_TOWER_BASIS_8: CtConvertBasisU8<8> =
39 CtConvertBasisU8(constants::RAW_FLAT_TO_TOWER_8);
40
41#[cfg(feature = "table-math")]
52const EXP_TABLE: [u8; 256] = generate_exp_table();
53
54#[cfg(feature = "table-math")]
59const LOG_TABLE: [u8; 256] = generate_log_table();
60
61#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
63#[repr(transparent)]
64pub struct Block8(pub u8);
65
66impl Block8 {
67 pub const fn new(val: u8) -> Self {
68 Self(val)
69 }
70
71 #[inline(always)]
72 pub fn square(self) -> Self {
73 let mut s = self.0 as u16;
76 s = (s | (s << 4)) & 0x0f0f;
77 s = (s | (s << 2)) & 0x3333;
78 s = (s | (s << 1)) & 0x5555;
79
80 let hi = s >> 8;
81 let s = (s & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4));
82
83 let hi = s >> 8;
84
85 Block8(((s & 0x00ff) ^ (hi ^ (hi << 1) ^ (hi << 3) ^ (hi << 4))) as u8)
86 }
87}
88
89impl TowerField for Block8 {
90 const BITS: usize = 8;
91 const ZERO: Self = Block8(0);
92 const ONE: Self = Block8(1);
93
94 const EXTENSION_TAU: Self = Block8(0x20);
95
96 fn invert(&self) -> Self {
97 #[cfg(feature = "table-math")]
98 {
99 if self.0 == 0 {
100 return Self::ZERO;
101 }
102
103 let i = LOG_TABLE[self.0 as usize] as usize;
104 Block8(EXP_TABLE[255 - i])
105 }
106
107 #[cfg(not(feature = "table-math"))]
108 {
109 let x = *self;
113 let x2 = x * x;
114 let x4 = x2 * x2;
115 let x8 = x4 * x4;
116 let x16 = x8 * x8;
117 let x32 = x16 * x16;
118 let x64 = x32 * x32;
119 let x128 = x64 * x64;
120
121 x128 * x64 * x32 * x16 * x8 * x4 * x2
123 }
124 }
125
126 fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
127 Self(bytes[0])
128 }
129}
130
131impl Add for Block8 {
133 type Output = Self;
134
135 fn add(self, rhs: Self) -> Self::Output {
136 Self(self.0.bitxor(rhs.0))
137 }
138}
139
140impl Sub for Block8 {
141 type Output = Self;
142
143 fn sub(self, rhs: Self) -> Self::Output {
144 self.add(rhs)
145 }
146}
147
148impl Mul for Block8 {
150 type Output = Self;
151
152 fn mul(self, rhs: Self) -> Self::Output {
153 #[cfg(feature = "table-math")]
154 {
155 if self.0 == 0 || rhs.0 == 0 {
157 return Self::ZERO;
158 }
159
160 let i = LOG_TABLE[self.0 as usize] as usize;
164 let j = LOG_TABLE[rhs.0 as usize] as usize;
165
166 let k = i + j;
170 let idx = if k >= 255 { k - 255 } else { k };
171
172 Self(EXP_TABLE[idx])
174 }
175
176 #[cfg(not(feature = "table-math"))]
177 {
178 #[cfg(target_arch = "aarch64")]
179 {
180 neon::mul_8(self, rhs)
181 }
182
183 #[cfg(not(target_arch = "aarch64"))]
184 {
185 let mut a = self.0;
186 let mut b = rhs.0;
187 let mut res = 0u8;
188
189 for _ in 0..8 {
192 let bit = b & 1;
193 let mask = 0u8.wrapping_sub(bit);
194 res ^= a & mask;
195
196 let high_bit = a >> 7;
197 let overflow_mask = 0u8.wrapping_sub(high_bit);
198 a = (a << 1) ^ (0x1B & overflow_mask);
199
200 b >>= 1;
201 }
202
203 Self(res)
204 }
205 }
206 }
207}
208
209impl AddAssign for Block8 {
210 fn add_assign(&mut self, rhs: Self) {
211 *self = *self + rhs;
212 }
213}
214
215impl SubAssign for Block8 {
216 fn sub_assign(&mut self, rhs: Self) {
217 *self = *self - rhs;
218 }
219}
220
221impl MulAssign for Block8 {
222 fn mul_assign(&mut self, rhs: Self) {
223 *self = *self * rhs;
224 }
225}
226
227impl CanonicalSerialize for Block8 {
228 #[inline]
229 fn serialized_size(&self) -> usize {
230 1
231 }
232
233 #[inline]
234 fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
235 if writer.is_empty() {
236 return Err(());
237 }
238
239 writer[0] = self.0;
240
241 Ok(())
242 }
243}
244
245impl CanonicalDeserialize for Block8 {
246 fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
247 if bytes.is_empty() {
248 return Err(());
249 }
250
251 Ok(Self(bytes[0]))
252 }
253}
254
255impl From<u8> for Block8 {
256 #[inline]
257 fn from(val: u8) -> Self {
258 Self::new(val)
259 }
260}
261
262impl From<u32> for Block8 {
263 #[inline]
264 fn from(val: u32) -> Self {
265 Self(val as u8)
266 }
267}
268
269impl From<u64> for Block8 {
270 #[inline]
271 fn from(val: u64) -> Self {
272 Self(val as u8)
273 }
274}
275
276impl From<u128> for Block8 {
277 #[inline]
278 fn from(val: u128) -> Self {
279 Self(val as u8)
280 }
281}
282
283impl From<Bit> for Block8 {
288 #[inline(always)]
289 fn from(val: Bit) -> Self {
290 Self(val.get())
291 }
292}
293
294pub const PACKED_WIDTH_8: usize = 16;
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
302#[repr(C, align(16))]
303pub struct PackedBlock8(pub [Block8; PACKED_WIDTH_8]);
304
305impl PackedBlock8 {
306 #[inline(always)]
307 pub fn zero() -> Self {
308 Self([Block8::ZERO; PACKED_WIDTH_8])
309 }
310}
311
312impl PackableField for Block8 {
313 type Packed = PackedBlock8;
314
315 const WIDTH: usize = PACKED_WIDTH_8;
316
317 #[inline(always)]
318 fn pack(chunk: &[Self]) -> Self::Packed {
319 assert!(
320 chunk.len() >= PACKED_WIDTH_8,
321 "PackableField::pack: input slice too short",
322 );
323
324 let mut arr = [Self::ZERO; PACKED_WIDTH_8];
325 arr.copy_from_slice(&chunk[..PACKED_WIDTH_8]);
326
327 PackedBlock8(arr)
328 }
329
330 #[inline(always)]
331 fn unpack(packed: Self::Packed, output: &mut [Self]) {
332 assert!(
333 output.len() >= PACKED_WIDTH_8,
334 "PackableField::unpack: output slice too short",
335 );
336
337 output[..PACKED_WIDTH_8].copy_from_slice(&packed.0);
338 }
339}
340
341impl Add for PackedBlock8 {
342 type Output = Self;
343
344 #[inline(always)]
345 fn add(self, rhs: Self) -> Self {
346 let mut res = [Block8::ZERO; PACKED_WIDTH_8];
347 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
348 *out = *l + *r;
349 }
350
351 Self(res)
352 }
353}
354
355impl AddAssign for PackedBlock8 {
356 #[inline(always)]
357 fn add_assign(&mut self, rhs: Self) {
358 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
359 *l += *r;
360 }
361 }
362}
363
364impl Sub for PackedBlock8 {
365 type Output = Self;
366
367 #[inline(always)]
368 fn sub(self, rhs: Self) -> Self {
369 self.add(rhs)
370 }
371}
372
373impl SubAssign for PackedBlock8 {
374 #[inline(always)]
375 fn sub_assign(&mut self, rhs: Self) {
376 self.add_assign(rhs);
377 }
378}
379
380impl Mul for PackedBlock8 {
381 type Output = Self;
382
383 #[inline(always)]
384 fn mul(self, rhs: Self) -> Self {
385 #[cfg(target_arch = "aarch64")]
386 {
387 let mut res = [Block8::ZERO; PACKED_WIDTH_8];
388 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
389 *out = mul_iso_8(*l, *r);
390 }
391
392 Self(res)
393 }
394
395 #[cfg(not(target_arch = "aarch64"))]
396 {
397 let mut res = [Block8::ZERO; PACKED_WIDTH_8];
398 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
399 *out = *l * *r;
400 }
401
402 Self(res)
403 }
404 }
405}
406
407impl MulAssign for PackedBlock8 {
408 #[inline(always)]
409 fn mul_assign(&mut self, rhs: Self) {
410 *self = *self * rhs;
411 }
412}
413
414impl Mul<Block8> for PackedBlock8 {
415 type Output = Self;
416
417 #[inline(always)]
418 fn mul(self, rhs: Block8) -> Self {
419 let mut res = [Block8::ZERO; PACKED_WIDTH_8];
420 for (out, v) in res.iter_mut().zip(self.0.iter()) {
421 *out = *v * rhs;
422 }
423
424 Self(res)
425 }
426}
427
428impl HardwareField for Block8 {
433 #[inline(always)]
434 fn to_hardware(self) -> Flat<Self> {
435 #[cfg(feature = "table-math")]
436 {
437 Flat::from_raw(apply_matrix_8(self, &constants::TOWER_TO_FLAT_8))
438 }
439
440 #[cfg(not(feature = "table-math"))]
441 {
442 Flat::from_raw(Block8(map_ct_8(self.0, &TOWER_TO_FLAT_BASIS_8.0)))
443 }
444 }
445
446 #[inline(always)]
447 fn from_hardware(value: Flat<Self>) -> Self {
448 let value = value.into_raw();
449 #[cfg(feature = "table-math")]
450 {
451 apply_matrix_8(value, &constants::FLAT_TO_TOWER_8)
452 }
453
454 #[cfg(not(feature = "table-math"))]
455 {
456 Block8(map_ct_8(value.0, &FLAT_TO_TOWER_BASIS_8.0))
457 }
458 }
459
460 #[inline(always)]
461 fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
462 Flat::from_raw(lhs.into_raw() + rhs.into_raw())
463 }
464
465 #[inline(always)]
466 fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
467 let lhs = lhs.into_raw();
468 let rhs = rhs.into_raw();
469 #[cfg(target_arch = "aarch64")]
470 {
471 PackedFlat::from_raw(neon::add_packed_8(lhs, rhs))
472 }
473
474 #[cfg(not(target_arch = "aarch64"))]
475 {
476 PackedFlat::from_raw(lhs + rhs)
477 }
478 }
479
480 #[inline(always)]
481 fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
482 let lhs = lhs.into_raw();
483 let rhs = rhs.into_raw();
484 #[cfg(target_arch = "aarch64")]
485 {
486 Flat::from_raw(neon::mul_8(lhs, rhs))
487 }
488
489 #[cfg(not(target_arch = "aarch64"))]
490 {
491 let a_tower = Self::from_hardware(Flat::from_raw(lhs));
492 let b_tower = Self::from_hardware(Flat::from_raw(rhs));
493
494 (a_tower * b_tower).to_hardware()
495 }
496 }
497
498 #[inline(always)]
499 fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
500 let lhs = lhs.into_raw();
501 let rhs = rhs.into_raw();
502
503 #[cfg(target_arch = "aarch64")]
504 {
505 PackedFlat::from_raw(neon::mul_flat_packed_8(lhs, rhs))
506 }
507
508 #[cfg(not(target_arch = "aarch64"))]
509 {
510 let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
511 let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
512 let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
513
514 Self::unpack(lhs, &mut l);
515 Self::unpack(rhs, &mut r);
516
517 for i in 0..<Self as PackableField>::WIDTH {
518 res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
519 }
520
521 PackedFlat::from_raw(Self::pack(&res))
522 }
523 }
524
525 #[inline(always)]
526 fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
527 let broadcasted = PackedBlock8([rhs.into_raw(); PACKED_WIDTH_8]);
528 Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
529 }
530
531 #[inline(always)]
532 fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
533 let mask = FLAT_TO_TOWER_BIT_MASKS_8[bit_idx];
534
535 let mut v = value.into_raw().0 & mask;
537 v ^= v >> 4;
538 v ^= v >> 2;
539 v ^= v >> 1;
540
541 v & 1
542 }
543}
544
545#[cfg(target_arch = "aarch64")]
550#[inline(always)]
551fn mul_iso_8(a: Block8, b: Block8) -> Block8 {
552 let a_f = a.to_hardware();
553 let b_f = b.to_hardware();
554 let c_f = Flat::from_raw(neon::mul_8(a_f.into_raw(), b_f.into_raw()));
555
556 c_f.to_tower()
557}
558
559#[cfg(feature = "table-math")]
560#[inline(always)]
561fn apply_matrix_8(val: Block8, table: &[u8; 256]) -> Block8 {
562 let idx = val.0 as usize;
563 Block8(unsafe { *table.get_unchecked(idx) })
564}
565
566#[cfg(not(feature = "table-math"))]
567#[inline(always)]
568fn map_ct_8(x: u8, basis: &[u8; 8]) -> u8 {
569 let mut acc = 0u8;
570 let mut i = 0usize;
571
572 while i < 8 {
573 let bit = (x >> i) & 1;
574 let mask = 0u8.wrapping_sub(bit);
575 acc ^= basis[i] & mask;
576 i += 1;
577 }
578
579 acc
580}
581
582#[cfg(feature = "table-math")]
583const fn generate_exp_table() -> [u8; 256] {
584 let mut table = [0u8; 256];
585 let mut val: u8 = 1;
586
587 let mut i = 0;
594 while i < 256 {
595 table[i] = val;
596
597 let high_bit = val & 0x80;
601 let mut shifted = val << 1;
602
603 if high_bit != 0 {
607 shifted ^= 0x1B;
608 }
609
610 val = shifted ^ val;
611 i += 1;
612 }
613
614 table
615}
616
617#[cfg(feature = "table-math")]
618const fn generate_log_table() -> [u8; 256] {
619 let mut table = [0u8; 256];
620
621 let mut val: u8 = 1;
630 let mut i = 0;
631
632 while i < 255 {
633 table[val as usize] = i as u8;
634
635 let high_bit = val & 0x80;
636 let mut shifted = val << 1;
637
638 if high_bit != 0 {
639 shifted ^= 0x1B;
640 }
641
642 val = shifted ^ val;
643
644 i += 1;
645 }
646
647 table
650}
651
652#[cfg(target_arch = "aarch64")]
657mod neon {
658 use super::*;
659 use core::arch::aarch64::*;
660 use core::mem::transmute;
661
662 const _: () = assert!(
663 constants::POLY_8 == 0x1b,
664 "reduction tables and verus twins hardcode R = 0x1b"
665 );
666
667 #[inline(always)]
668 pub fn add_packed_8(lhs: PackedBlock8, rhs: PackedBlock8) -> PackedBlock8 {
669 unsafe {
670 let res = veorq_u8(
671 transmute::<[Block8; 16], uint8x16_t>(lhs.0),
672 transmute::<[Block8; 16], uint8x16_t>(rhs.0),
673 );
674 transmute(res)
675 }
676 }
677
678 #[inline(always)]
679 pub fn mul_8(a: Block8, b: Block8) -> Block8 {
680 unsafe {
681 let a_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(a.0));
684 let b_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(b.0));
685
686 let prod = vmull_p8(a_poly, b_poly);
689
690 let prod_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(prod), 0);
692
693 let l = (prod_u16 & 0xFF) as u8;
694 let h = (prod_u16 >> 8) as u8;
695
696 let r_val = constants::POLY_8; let h_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(h));
701 let r_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(r_val));
702 let h_red = vmull_p8(h_poly, r_poly);
703
704 let h_red_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(h_red), 0);
705
706 let folded = (h_red_u16 & 0xFF) as u8;
707 let carry = (h_red_u16 >> 8) as u8;
708
709 let mut res = l ^ folded;
710
711 let c_poly = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(carry));
715 let c_red = vmull_p8(c_poly, r_poly);
716 let c_red_u16 = vgetq_lane_u16(transmute::<poly16x8_t, uint16x8_t>(c_red), 0);
717
718 res ^= (c_red_u16 & 0xFF) as u8;
719
720 Block8(res)
721 }
722 }
723
724 #[inline(always)]
727 pub fn mul_flat_packed_8(lhs: PackedBlock8, rhs: PackedBlock8) -> PackedBlock8 {
728 unsafe {
729 let a: uint8x16_t = transmute(lhs.0);
730 let b: uint8x16_t = transmute(rhs.0);
731
732 let a_lo = vget_low_u8(a);
734 let a_hi = vget_high_u8(a);
735 let b_lo = vget_low_u8(b);
736 let b_hi = vget_high_u8(b);
737
738 let res_lo = vmull_p8(
741 transmute::<uint8x8_t, poly8x8_t>(a_lo),
742 transmute::<uint8x8_t, poly8x8_t>(b_lo),
743 );
744 let res_hi = vmull_p8(
745 transmute::<uint8x8_t, poly8x8_t>(a_hi),
746 transmute::<uint8x8_t, poly8x8_t>(b_hi),
747 );
748
749 let tbl_lo = vld1q_u8(
752 [
753 0x00, 0x1b, 0x36, 0x2d, 0x6c, 0x77, 0x5a, 0x41, 0xd8, 0xc3, 0xee, 0xf5, 0xb4,
754 0xaf, 0x82, 0x99,
755 ]
756 .as_ptr(),
757 );
758
759 let tbl_hi = vld1q_u8(
760 [
761 0x00, 0xab, 0x4d, 0xe6, 0x9a, 0x31, 0xd7, 0x7c, 0x2f, 0x84, 0x62, 0xc9, 0xb5,
762 0x1e, 0xf8, 0x53,
763 ]
764 .as_ptr(),
765 );
766
767 let reduce_tbl = |val_poly: poly16x8_t| -> uint8x8_t {
771 let val: uint16x8_t = transmute(val_poly);
772
773 let data = vmovn_u16(val);
775 let carry_u16 = vshrq_n_u16(val, 8);
776 let carry = vmovn_u16(carry_u16);
777
778 let mask_lo = vdup_n_u8(0x0F);
780 let h_lo = vand_u8(carry, mask_lo);
781 let h_hi = vshr_n_u8(carry, 4);
782
783 let r_lo = vqtbl1_u8(tbl_lo, h_lo);
788 let r_hi = vqtbl1_u8(tbl_hi, h_hi);
789
790 veor_u8(data, veor_u8(r_lo, r_hi))
792 };
793
794 let final_lo = reduce_tbl(res_lo);
795 let final_hi = reduce_tbl(res_hi);
796
797 let res = vcombine_u8(final_lo, final_hi);
800
801 PackedBlock8(transmute::<uint8x16_t, [Block8; 16]>(res))
802 }
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809 use rand::{RngExt, rng};
810
811 #[test]
816 fn tower_constants() {
817 assert_eq!(Block8::EXTENSION_TAU, Block8(0x20));
820 }
821
822 #[test]
823 fn add_truth() {
824 let zero = Block8::ZERO;
825 let one = Block8::ONE;
826
827 assert_eq!(zero + zero, zero);
828 assert_eq!(zero + one, one);
829 assert_eq!(one + zero, one);
830 assert_eq!(one + one, zero);
831 }
832
833 #[test]
834 fn mul_truth() {
835 let zero = Block8::ZERO;
836 let one = Block8::ONE;
837
838 assert_eq!(zero * zero, zero);
839 assert_eq!(zero * one, zero);
840 assert_eq!(one * one, one);
841 }
842
843 #[test]
844 fn add() {
845 assert_eq!(Block8(5) + Block8(3), Block8(6));
848 }
849
850 #[test]
851 fn mul_simple() {
852 assert_eq!(Block8(2) * Block8(2), Block8(4));
855 }
856
857 #[test]
858 fn mul_overflow() {
859 assert_eq!(Block8(0x57) * Block8(0x83), Block8(0xC1));
863 }
864
865 #[test]
866 fn square_exhaustive() {
867 for i in 0u16..=255 {
868 let x = Block8(i as u8);
869 assert_eq!(x.square(), x * x, "Block8 square mismatch at {i:#04x}");
870 }
871 }
872
873 #[test]
874 fn security_zeroize() {
875 let mut secret_val = Block8::from(0xFF_u32);
876 assert_ne!(secret_val, Block8::ZERO);
877
878 secret_val.zeroize();
879
880 assert_eq!(secret_val, Block8::ZERO);
881 assert_eq!(secret_val.0, 0, "Block8 memory leak detected");
882 }
883
884 #[test]
885 fn inversion_exhaustive() {
886 for i in 0u8..=255 {
888 let val = Block8(i);
889
890 if val == Block8::ZERO {
891 assert_eq!(val.invert(), Block8::ZERO, "invert(0) must return 0");
894 } else {
895 let inv = val.invert();
898 let product = val * inv;
899
900 assert_eq!(
901 product,
902 Block8::ONE,
903 "Inversion identity failed: a * a^-1 != 1"
904 );
905 }
906 }
907 }
908
909 #[test]
914 fn isomorphism_roundtrip() {
915 let mut rng = rng();
916 for _ in 0..1000 {
917 let val = Block8::from(rng.random::<u8>());
918
919 assert_eq!(
922 val.to_hardware().to_tower(),
923 val,
924 "Block8 isomorphism roundtrip failed"
925 );
926 }
927 }
928
929 #[test]
930 fn parity_masks_match_from_hardware() {
931 for x in 0u16..=255 {
934 let x_flat = x as u8;
935 let tower = Block8::from_hardware(Flat::from_raw(Block8(x_flat))).0;
936
937 for (k, &mask) in FLAT_TO_TOWER_BIT_MASKS_8.iter().enumerate() {
938 let parity = ((x_flat & mask).count_ones() & 1) as u8;
939 let bit = (tower >> k) & 1;
940 assert_eq!(
941 parity, bit,
942 "Block8 mask mismatch at x={x_flat:#04x}, k={k}"
943 );
944
945 let via_api = Flat::from_raw(Block8(x_flat)).tower_bit(k);
946 assert_eq!(via_api, bit, "Block8 tower_bit_from_hardware mismatch");
947 }
948 }
949 }
950
951 #[test]
952 fn flat_mul_homomorphism() {
953 let mut rng = rng();
954 for _ in 0..1000 {
955 let a = Block8::from(rng.random::<u8>());
956 let b = Block8::from(rng.random::<u8>());
957
958 let expected_flat = (a * b).to_hardware();
959 let actual_flat = a.to_hardware() * b.to_hardware();
960
961 assert_eq!(
963 actual_flat, expected_flat,
964 "Block8 flat multiplication mismatch"
965 );
966 }
967 }
968
969 #[test]
970 fn packed_consistency() {
971 let mut rng = rng();
972 for _ in 0..100 {
973 let mut a_vals = [Block8::ZERO; 16];
974 let mut b_vals = [Block8::ZERO; 16];
975
976 for i in 0..16 {
977 a_vals[i] = Block8::from(rng.random::<u8>());
978 b_vals[i] = Block8::from(rng.random::<u8>());
979 }
980
981 let a_flat_vals = a_vals.map(|x| x.to_hardware());
982 let b_flat_vals = b_vals.map(|x| x.to_hardware());
983 let a_packed = Flat::<Block8>::pack(&a_flat_vals);
984 let b_packed = Flat::<Block8>::pack(&b_flat_vals);
985
986 let add_res = Block8::add_hardware_packed(a_packed, b_packed);
988
989 let mut add_out = [Block8::ZERO.to_hardware(); 16];
990 Flat::<Block8>::unpack(add_res, &mut add_out);
991
992 for i in 0..16 {
993 assert_eq!(
994 add_out[i],
995 (a_vals[i] + b_vals[i]).to_hardware(),
996 "Block8 packed add mismatch"
997 );
998 }
999
1000 let mul_res = Block8::mul_hardware_packed(a_packed, b_packed);
1002
1003 let mut mul_out = [Block8::ZERO.to_hardware(); 16];
1004 Flat::<Block8>::unpack(mul_res, &mut mul_out);
1005
1006 for i in 0..16 {
1007 assert_eq!(
1008 mul_out[i],
1009 (a_vals[i] * b_vals[i]).to_hardware(),
1010 "Block8 packed mul mismatch"
1011 );
1012 }
1013 }
1014 }
1015
1016 #[test]
1021 fn pack_unpack_roundtrip() {
1022 let mut rng = rng();
1023 let mut data = [Block8::ZERO; PACKED_WIDTH_8];
1024
1025 for v in data.iter_mut() {
1026 *v = Block8(rng.random());
1027 }
1028
1029 let packed = Block8::pack(&data);
1030 let mut unpacked = [Block8::ZERO; PACKED_WIDTH_8];
1031 Block8::unpack(packed, &mut unpacked);
1032
1033 assert_eq!(data, unpacked, "Block8 pack/unpack roundtrip failed");
1034 }
1035
1036 #[test]
1037 fn packed_add_consistency() {
1038 let mut rng = rng();
1039 let mut a_vals = [Block8::ZERO; PACKED_WIDTH_8];
1040 let mut b_vals = [Block8::ZERO; PACKED_WIDTH_8];
1041
1042 for i in 0..PACKED_WIDTH_8 {
1043 a_vals[i] = Block8(rng.random());
1044 b_vals[i] = Block8(rng.random());
1045 }
1046
1047 let a_packed = Block8::pack(&a_vals);
1048 let b_packed = Block8::pack(&b_vals);
1049 let res_packed = a_packed + b_packed;
1050
1051 let mut res_unpacked = [Block8::ZERO; PACKED_WIDTH_8];
1052 Block8::unpack(res_packed, &mut res_unpacked);
1053
1054 for i in 0..PACKED_WIDTH_8 {
1055 assert_eq!(
1056 res_unpacked[i],
1057 a_vals[i] + b_vals[i],
1058 "Block8 packed add mismatch at index {}",
1059 i
1060 );
1061 }
1062 }
1063
1064 #[test]
1065 fn packed_mul_consistency() {
1066 let mut rng = rng();
1067
1068 for _ in 0..1000 {
1069 let mut a_arr = [Block8::ZERO; PACKED_WIDTH_8];
1070 let mut b_arr = [Block8::ZERO; PACKED_WIDTH_8];
1071
1072 for i in 0..PACKED_WIDTH_8 {
1073 let val_a: u8 = rng.random();
1074 let val_b: u8 = rng.random();
1075 a_arr[i] = Block8(val_a);
1076 b_arr[i] = Block8(val_b);
1077 }
1078
1079 let a_packed = PackedBlock8(a_arr);
1080 let b_packed = PackedBlock8(b_arr);
1081 let c_packed = a_packed * b_packed;
1082
1083 let mut c_expected = [Block8::ZERO; PACKED_WIDTH_8];
1084 for i in 0..PACKED_WIDTH_8 {
1085 c_expected[i] = a_arr[i] * b_arr[i];
1086 }
1087
1088 assert_eq!(c_packed.0, c_expected, "SIMD Block8 mismatch!");
1089 }
1090 }
1091}