1use crate::algebra::impl_binary_field_extras;
20use crate::towers::bit::Bit;
21use crate::towers::block8::Block8;
22use crate::{
23 BinaryFieldExtras, CanonicalDeserialize, CanonicalSerialize, Flat, FlatPromote, HardwareField,
24 PackableField, PackedFlat, TowerField, constants,
25};
26use core::ops::{Add, AddAssign, BitXor, BitXorAssign, Mul, MulAssign, Sub, SubAssign};
27use serde::{Deserialize, Serialize};
28use zeroize::Zeroize;
29
30#[cfg(not(feature = "table-math"))]
31#[repr(align(64))]
32struct CtConvertBasisU16<const N: usize>([u16; N]);
33
34#[cfg(not(feature = "table-math"))]
35static TOWER_TO_FLAT_BASIS_16: CtConvertBasisU16<16> =
36 CtConvertBasisU16(constants::RAW_TOWER_TO_FLAT_16);
37
38#[cfg(not(feature = "table-math"))]
39static FLAT_TO_TOWER_BASIS_16: CtConvertBasisU16<16> =
40 CtConvertBasisU16(constants::RAW_FLAT_TO_TOWER_16);
41
42#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
43#[repr(transparent)]
44pub struct Block16(pub u16);
45
46impl Block16 {
47 pub const TAU: Self = Block16(0x2000);
48
49 pub fn new(lo: Block8, hi: Block8) -> Self {
50 Self((hi.0 as u16) << 8 | (lo.0 as u16))
51 }
52
53 #[inline(always)]
54 pub fn split(self) -> (Block8, Block8) {
55 (Block8(self.0 as u8), Block8((self.0 >> 8) as u8))
56 }
57}
58
59impl TowerField for Block16 {
60 const BITS: usize = 16;
61 const ZERO: Self = Block16(0);
62 const ONE: Self = Block16(1);
63
64 const EXTENSION_TAU: Self = Self::TAU;
65
66 fn invert(&self) -> Self {
67 let (l, h) = self.split();
68
69 let h2 = h * h;
71 let l2 = l * l;
72 let hl = h * l;
73 let norm = (h2 * Block8::EXTENSION_TAU) + hl + l2;
74
75 let norm_inv = norm.invert();
76
77 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; 2];
86 buf.copy_from_slice(&bytes[0..2]);
87
88 Self(u16::from_le_bytes(buf))
89 }
90}
91
92impl Add for Block16 {
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 Block16 {
101 type Output = Self;
102
103 fn sub(self, rhs: Self) -> Self {
104 Self(self.0.bitxor(rhs.0))
105 }
106}
107
108impl Mul for Block16 {
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;
117 let v1 = a1 * b1;
118 let v_sum = (a0 + a1) * (b0 + b1);
119
120 let c_hi = v0 + v_sum;
123
124 let c_lo = v0 + (v1 * Block8::EXTENSION_TAU);
126
127 Self::new(c_lo, c_hi)
128 }
129}
130
131impl AddAssign for Block16 {
132 fn add_assign(&mut self, rhs: Self) {
133 self.0.bitxor_assign(rhs.0);
134 }
135}
136
137impl SubAssign for Block16 {
138 fn sub_assign(&mut self, rhs: Self) {
139 self.0.bitxor_assign(rhs.0);
140 }
141}
142
143impl MulAssign for Block16 {
144 fn mul_assign(&mut self, rhs: Self) {
145 *self = *self * rhs;
146 }
147}
148
149impl CanonicalSerialize for Block16 {
150 fn serialized_size(&self) -> usize {
151 2
152 }
153
154 fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
155 if writer.len() < 2 {
156 return Err(());
157 }
158
159 writer[..2].copy_from_slice(&self.0.to_le_bytes());
160
161 Ok(())
162 }
163}
164
165impl CanonicalDeserialize for Block16 {
166 fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
167 if bytes.len() < 2 {
168 return Err(());
169 }
170
171 let mut buf = [0u8; 2];
172 buf.copy_from_slice(&bytes[0..2]);
173
174 Ok(Self(u16::from_le_bytes(buf)))
175 }
176}
177
178impl From<u8> for Block16 {
179 fn from(val: u8) -> Self {
180 Self(val as u16)
181 }
182}
183
184impl From<u16> for Block16 {
185 #[inline]
186 fn from(val: u16) -> Self {
187 Self(val)
188 }
189}
190
191impl From<u32> for Block16 {
192 #[inline]
193 fn from(val: u32) -> Self {
194 Self(val as u16)
195 }
196}
197
198impl From<u64> for Block16 {
199 #[inline]
200 fn from(val: u64) -> Self {
201 Self(val as u16)
202 }
203}
204
205impl From<u128> for Block16 {
206 #[inline]
207 fn from(val: u128) -> Self {
208 Self(val as u16)
209 }
210}
211
212impl From<Bit> for Block16 {
217 #[inline(always)]
218 fn from(val: Bit) -> Self {
219 Self(val.get() as u16)
220 }
221}
222
223impl From<Block8> for Block16 {
224 #[inline(always)]
225 fn from(val: Block8) -> Self {
226 Self(val.0 as u16)
227 }
228}
229
230pub const PACKED_WIDTH_16: usize = 8;
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
238#[repr(C, align(16))]
239pub struct PackedBlock16(pub [Block16; PACKED_WIDTH_16]);
240
241impl PackedBlock16 {
242 #[inline(always)]
243 pub fn zero() -> Self {
244 Self([Block16::ZERO; PACKED_WIDTH_16])
245 }
246}
247
248impl PackableField for Block16 {
249 type Packed = PackedBlock16;
250
251 const WIDTH: usize = PACKED_WIDTH_16;
252
253 #[inline(always)]
254 fn pack(chunk: &[Self]) -> Self::Packed {
255 assert!(
256 chunk.len() >= PACKED_WIDTH_16,
257 "PackableField::pack: input slice too short",
258 );
259
260 let mut arr = [Self::ZERO; PACKED_WIDTH_16];
261 arr.copy_from_slice(&chunk[..PACKED_WIDTH_16]);
262
263 PackedBlock16(arr)
264 }
265
266 #[inline(always)]
267 fn unpack(packed: Self::Packed, output: &mut [Self]) {
268 assert!(
269 output.len() >= PACKED_WIDTH_16,
270 "PackableField::unpack: output slice too short",
271 );
272
273 output[..PACKED_WIDTH_16].copy_from_slice(&packed.0);
274 }
275}
276
277impl Add for PackedBlock16 {
278 type Output = Self;
279
280 #[inline(always)]
281 fn add(self, rhs: Self) -> Self {
282 let mut res = [Block16::ZERO; PACKED_WIDTH_16];
283 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
284 *out = *l + *r;
285 }
286
287 Self(res)
288 }
289}
290
291impl AddAssign for PackedBlock16 {
292 #[inline(always)]
293 fn add_assign(&mut self, rhs: Self) {
294 for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
295 *l += *r;
296 }
297 }
298}
299
300impl Sub for PackedBlock16 {
301 type Output = Self;
302
303 #[inline(always)]
304 fn sub(self, rhs: Self) -> Self {
305 self.add(rhs)
306 }
307}
308
309impl SubAssign for PackedBlock16 {
310 #[inline(always)]
311 fn sub_assign(&mut self, rhs: Self) {
312 self.add_assign(rhs);
313 }
314}
315
316impl Mul for PackedBlock16 {
317 type Output = Self;
318
319 #[inline(always)]
320 fn mul(self, rhs: Self) -> Self {
321 #[cfg(target_arch = "aarch64")]
322 {
323 let mut res = [Block16::ZERO; PACKED_WIDTH_16];
324 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
325 *out = mul_iso_16(*l, *r);
326 }
327
328 Self(res)
329 }
330
331 #[cfg(not(target_arch = "aarch64"))]
332 {
333 let mut res = [Block16::ZERO; PACKED_WIDTH_16];
334 for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
335 *out = *l * *r;
336 }
337
338 Self(res)
339 }
340 }
341}
342
343impl MulAssign for PackedBlock16 {
344 #[inline(always)]
345 fn mul_assign(&mut self, rhs: Self) {
346 *self = *self * rhs;
347 }
348}
349
350impl Mul<Block16> for PackedBlock16 {
351 type Output = Self;
352
353 #[inline(always)]
354 fn mul(self, rhs: Block16) -> Self {
355 let mut res = [Block16::ZERO; PACKED_WIDTH_16];
356 for (out, v) in res.iter_mut().zip(self.0.iter()) {
357 *out = *v * rhs;
358 }
359
360 Self(res)
361 }
362}
363
364impl HardwareField for Block16 {
369 #[inline(always)]
370 fn to_hardware(self) -> Flat<Self> {
371 #[cfg(feature = "table-math")]
372 {
373 Flat::from_raw(apply_matrix_16(self, &constants::TOWER_TO_FLAT_16))
374 }
375
376 #[cfg(not(feature = "table-math"))]
377 {
378 Flat::from_raw(Block16(map_ct_16(self.0, &TOWER_TO_FLAT_BASIS_16.0)))
379 }
380 }
381
382 #[inline(always)]
383 fn from_hardware(value: Flat<Self>) -> Self {
384 let value = value.into_raw();
385
386 #[cfg(feature = "table-math")]
387 {
388 apply_matrix_16(value, &constants::FLAT_TO_TOWER_16)
389 }
390
391 #[cfg(not(feature = "table-math"))]
392 {
393 Block16(map_ct_16(value.0, &FLAT_TO_TOWER_BASIS_16.0))
394 }
395 }
396
397 #[inline(always)]
398 fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
399 Flat::from_raw(lhs.into_raw() + rhs.into_raw())
400 }
401
402 #[inline(always)]
403 fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
404 let lhs = lhs.into_raw();
405 let rhs = rhs.into_raw();
406
407 #[cfg(target_arch = "aarch64")]
408 {
409 PackedFlat::from_raw(neon::add_packed_16(lhs, rhs))
410 }
411
412 #[cfg(not(target_arch = "aarch64"))]
413 {
414 PackedFlat::from_raw(lhs + rhs)
415 }
416 }
417
418 #[inline(always)]
419 fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
420 let lhs = lhs.into_raw();
421 let rhs = rhs.into_raw();
422
423 #[cfg(target_arch = "aarch64")]
424 {
425 Flat::from_raw(neon::mul_flat_16(lhs, rhs))
426 }
427
428 #[cfg(not(target_arch = "aarch64"))]
429 {
430 let a_tower = Self::from_hardware(Flat::from_raw(lhs));
431 let b_tower = Self::from_hardware(Flat::from_raw(rhs));
432
433 (a_tower * b_tower).to_hardware()
434 }
435 }
436
437 #[inline(always)]
438 fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
439 let lhs = lhs.into_raw();
440 let rhs = rhs.into_raw();
441
442 #[cfg(target_arch = "aarch64")]
443 {
444 PackedFlat::from_raw(neon::mul_flat_packed_16(lhs, rhs))
445 }
446
447 #[cfg(not(target_arch = "aarch64"))]
448 {
449 let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
450 let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
451 let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
452
453 Self::unpack(lhs, &mut l);
454 Self::unpack(rhs, &mut r);
455
456 for i in 0..<Self as PackableField>::WIDTH {
457 res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
458 }
459
460 PackedFlat::from_raw(Self::pack(&res))
461 }
462 }
463
464 #[inline(always)]
465 fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
466 #[cfg(target_arch = "aarch64")]
467 {
468 PackedFlat::from_raw(neon::mul_flat_scalar_packed_16(
469 lhs.into_raw(),
470 rhs.into_raw(),
471 ))
472 }
473
474 #[cfg(not(target_arch = "aarch64"))]
475 {
476 let broadcasted = PackedBlock16([rhs.into_raw(); PACKED_WIDTH_16]);
477 Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
478 }
479 }
480
481 #[inline(always)]
482 fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
483 let mask = constants::FLAT_TO_TOWER_BIT_MASKS_16[bit_idx];
484
485 let mut v = value.into_raw().0 & mask;
489 v ^= v >> 8;
490 v ^= v >> 4;
491 v ^= v >> 2;
492 v ^= v >> 1;
493
494 (v & 1) as u8
495 }
496}
497
498impl FlatPromote<Block8> for Block16 {
499 #[inline(always)]
500 fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
501 let val = val.into_raw();
502
503 #[cfg(not(feature = "table-math"))]
504 {
505 let mut acc = 0u16;
506 for i in 0..8 {
507 let bit = (val.0 >> i) & 1;
508 let mask = 0u16.wrapping_sub(bit as u16);
509 acc ^= constants::LIFT_BASIS_8_TO_16[i] & mask;
510 }
511
512 Flat::from_raw(Block16(acc))
513 }
514
515 #[cfg(feature = "table-math")]
516 {
517 Flat::from_raw(Block16(constants::LIFT_TABLE_8_TO_16[val.0 as usize]))
518 }
519 }
520}
521
522impl_binary_field_extras!(
527 Block16,
528 Block8,
529 map_ct_16,
530 TRACE_MASK_16,
531 SOLVE_QUADRATIC_BASIS_16
532);
533
534#[cfg(target_arch = "aarch64")]
539#[inline(always)]
540pub fn mul_iso_16(a: Block16, b: Block16) -> Block16 {
541 let a_f = a.to_hardware();
542 let b_f = b.to_hardware();
543 let c_f = Flat::from_raw(neon::mul_flat_16(a_f.into_raw(), b_f.into_raw()));
544
545 c_f.to_tower()
546}
547
548#[cfg(feature = "table-math")]
549#[inline(always)]
550pub fn apply_matrix_16(val: Block16, table: &[u16; 512]) -> Block16 {
551 let v = val.0;
552 let mut res = 0u16;
553
554 for i in 0..2 {
556 let idx = (i * 256) + ((v >> (i * 8)) & 0xFF) as usize;
557 res ^= unsafe { *table.get_unchecked(idx) };
558 }
559
560 Block16(res)
561}
562
563#[inline(always)]
564fn map_ct_16(x: u16, basis: &[u16; 16]) -> u16 {
565 let mut acc = 0u16;
566 let mut i = 0usize;
567
568 while i < 16 {
569 let bit = (x >> i) & 1;
570 let mask = 0u16.wrapping_sub(bit);
571
572 acc ^= basis[i] & mask;
573 i += 1;
574 }
575
576 acc
577}
578
579#[cfg(target_arch = "aarch64")]
584mod neon {
585 use super::*;
586 use core::arch::aarch64::*;
587 use core::mem::transmute;
588
589 const _: () = assert!(constants::POLY_16 == 0x2b, "packed fold hardcodes R = 0x2b");
591
592 #[inline(always)]
593 pub fn add_packed_16(lhs: PackedBlock16, rhs: PackedBlock16) -> PackedBlock16 {
594 unsafe {
595 let res = veorq_u8(
596 transmute::<[Block16; 8], uint8x16_t>(lhs.0),
597 transmute::<[Block16; 8], uint8x16_t>(rhs.0),
598 );
599 transmute(res)
600 }
601 }
602
603 #[inline(always)]
604 pub fn mul_flat_16(a: Block16, b: Block16) -> Block16 {
605 unsafe {
606 let prod = vmull_p64(a.0 as u64, b.0 as u64);
611 let prod_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(prod), 0);
612
613 let l = (prod_val & 0xFFFF) as u16;
614 let h = (prod_val >> 16) as u16; let r_val = constants::POLY_16 as u64;
618
619 let h_red = vmull_p64(h as u64, r_val);
621 let h_red_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(h_red), 0);
622
623 let folded = (h_red_val & 0xFFFF) as u16;
629 let carry = (h_red_val >> 16) as u16;
630
631 let mut res = l ^ folded;
632
633 let c_red = vmull_p64(carry as u64, r_val);
636 let c_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(c_red), 0);
637
638 res ^= c_val as u16;
639
640 Block16(res)
641 }
642 }
643
644 #[inline(always)]
647 pub fn mul_flat_packed_16(lhs: PackedBlock16, rhs: PackedBlock16) -> PackedBlock16 {
648 unsafe {
649 let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
650 let b = transmute::<[Block16; 8], uint16x8_t>(rhs.0);
651
652 let a_lo = vmovn_u16(a);
653 let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
654 let b_lo = vmovn_u16(b);
655 let b_hi = vmovn_u16(vshrq_n_u16(b, 8));
656
657 let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
658 transmute::<uint8x8_t, poly8x8_t>(a_lo),
659 transmute::<uint8x8_t, poly8x8_t>(b_lo),
660 ));
661
662 let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
663 transmute::<uint8x8_t, poly8x8_t>(a_hi),
664 transmute::<uint8x8_t, poly8x8_t>(b_hi),
665 ));
666
667 let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
668 transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
669 transmute::<uint8x8_t, poly8x8_t>(veor_u8(b_lo, b_hi)),
670 ));
671
672 PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
673 ll, mm, hh,
674 )))
675 }
676 }
677
678 #[inline(always)]
681 pub fn mul_flat_scalar_packed_16(lhs: PackedBlock16, scalar: Block16) -> PackedBlock16 {
682 unsafe {
683 let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
684
685 let s_lo = (scalar.0 & 0xff) as u8;
686 let s_hi = (scalar.0 >> 8) as u8;
687
688 let b_lo = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo));
689 let b_hi = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_hi));
690 let b_mid = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo ^ s_hi));
691
692 let a_lo = vmovn_u16(a);
693 let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
694
695 let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
696 transmute::<uint8x8_t, poly8x8_t>(a_lo),
697 b_lo,
698 ));
699
700 let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
701 transmute::<uint8x8_t, poly8x8_t>(a_hi),
702 b_hi,
703 ));
704
705 let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
706 transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
707 b_mid,
708 ));
709
710 PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
711 ll, mm, hh,
712 )))
713 }
714 }
715
716 #[inline(always)]
719 fn reduce_packed_16(ll: uint16x8_t, mm: uint16x8_t, hh: uint16x8_t) -> uint16x8_t {
720 unsafe {
721 let mid = veorq_u16(veorq_u16(mm, ll), hh);
722 let l = veorq_u16(ll, vshlq_n_u16(mid, 8));
723 let h = veorq_u16(hh, vshrq_n_u16(mid, 8));
724
725 let h_fold = veorq_u16(
726 veorq_u16(vshlq_n_u16(h, 5), vshlq_n_u16(h, 3)),
727 veorq_u16(vshlq_n_u16(h, 1), h),
728 );
729
730 let carry = veorq_u16(
732 veorq_u16(vshrq_n_u16(h, 11), vshrq_n_u16(h, 13)),
733 vshrq_n_u16(h, 15),
734 );
735
736 let carry_fold = veorq_u16(
737 veorq_u16(vshlq_n_u16(carry, 5), vshlq_n_u16(carry, 3)),
738 veorq_u16(vshlq_n_u16(carry, 1), carry),
739 );
740
741 veorq_u16(veorq_u16(l, h_fold), carry_fold)
742 }
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749 use rand::{RngExt, rng};
750
751 #[cfg(target_arch = "aarch64")]
752 use proptest::prelude::*;
753
754 #[test]
759 fn tower_constants() {
760 let tau16 = Block16::EXTENSION_TAU;
763 let (lo16, hi16) = tau16.split();
764 assert_eq!(lo16, Block8::ZERO);
765 assert_eq!(hi16, Block8(0x20));
766 }
767
768 #[test]
769 fn add_truth() {
770 let zero = Block16::ZERO;
771 let one = Block16::ONE;
772
773 assert_eq!(zero + zero, zero);
774 assert_eq!(zero + one, one);
775 assert_eq!(one + zero, one);
776 assert_eq!(one + one, zero);
777 }
778
779 #[test]
780 fn mul_truth() {
781 let zero = Block16::ZERO;
782 let one = Block16::ONE;
783
784 assert_eq!(zero * zero, zero);
785 assert_eq!(zero * one, zero);
786 assert_eq!(one * one, one);
787 }
788
789 #[test]
790 fn add() {
791 assert_eq!(Block16(5) + Block16(3), Block16(6));
794 }
795
796 #[test]
797 fn mul_simple() {
798 assert_eq!(Block16(2) * Block16(2), Block16(4));
801 }
802
803 #[test]
804 fn mul_overflow() {
805 assert_eq!(Block16(0x57) * Block16(0x83), Block16(0xC1));
809 }
810
811 #[test]
812 fn karatsuba_correctness() {
813 let x = Block16::new(Block8::ZERO, Block8::ONE);
824 let squared = x * x;
825
826 let (res_lo, res_hi) = squared.split();
828
829 assert_eq!(res_hi, Block8::ONE, "X^2 should contain X component");
830 assert_eq!(
831 res_lo,
832 Block8(0x20),
833 "X^2 should contain tau component (0x20)"
834 );
835 }
836
837 #[test]
838 fn security_zeroize() {
839 let mut secret_val = Block16::from(0xDEAD_u16);
840 assert_ne!(secret_val, Block16::ZERO);
841
842 secret_val.zeroize();
843
844 assert_eq!(secret_val, Block16::ZERO);
845 assert_eq!(secret_val.0, 0, "Block16 memory leak detected");
846 }
847
848 #[test]
849 fn invert_zero() {
850 assert_eq!(
853 Block16::ZERO.invert(),
854 Block16::ZERO,
855 "invert(0) must return 0"
856 );
857 }
858
859 #[test]
860 fn inversion_random() {
861 let mut rng = rng();
862
863 for _ in 0..1000 {
865 let val_u16: u16 = rng.random();
866 let val = Block16(val_u16);
867
868 if val != Block16::ZERO {
869 let inv = val.invert();
870 let res = val * inv;
871
872 assert_eq!(
873 res,
874 Block16::ONE,
875 "Inversion identity failed: a * a^-1 != 1"
876 );
877 }
878 }
879 }
880
881 #[test]
882 fn tower_embedding() {
883 let mut rng = rng();
884 for _ in 0..100 {
885 let a = Block8(rng.random());
886 let b = Block8(rng.random());
887
888 let a_lifted: Block16 = a.into();
893 let (lo, hi) = a_lifted.split();
894
895 assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
896 assert_eq!(
897 hi,
898 Block8::ZERO,
899 "Embedding structure failed: high part must be zero"
900 );
901
902 let sum_sub = a + b;
905 let sum_lifted: Block16 = sum_sub.into();
906 let sum_manual = Block16::from(a) + Block16::from(b);
907
908 assert_eq!(sum_lifted, sum_manual, "Homomorphism failed: add");
909
910 let prod_sub = a * b;
915 let prod_lifted: Block16 = prod_sub.into();
916 let prod_manual = Block16::from(a) * Block16::from(b);
917
918 assert_eq!(prod_lifted, prod_manual, "Homomorphism failed: mul");
919 }
920 }
921
922 #[test]
927 fn isomorphism_roundtrip() {
928 let mut rng = rng();
929 for _ in 0..1000 {
930 let val = Block16(rng.random::<u16>());
931 assert_eq!(
932 val.to_hardware().to_tower(),
933 val,
934 "Block16 isomorphism roundtrip failed"
935 );
936 }
937 }
938
939 #[test]
940 fn flat_mul_homomorphism() {
941 let mut rng = rng();
942 for _ in 0..1000 {
943 let a = Block16(rng.random::<u16>());
944 let b = Block16(rng.random::<u16>());
945
946 let expected_flat = (a * b).to_hardware();
947 let actual_flat = a.to_hardware() * b.to_hardware();
948
949 assert_eq!(
950 actual_flat, expected_flat,
951 "Block16 flat multiplication mismatch"
952 );
953 }
954 }
955
956 #[test]
957 fn packed_consistency() {
958 let mut rng = rng();
959 for _ in 0..100 {
960 let mut a_vals = [Block16::ZERO; 8];
961 let mut b_vals = [Block16::ZERO; 8];
962
963 for i in 0..8 {
964 a_vals[i] = Block16(rng.random::<u16>());
965 b_vals[i] = Block16(rng.random::<u16>());
966 }
967
968 let a_flat_vals = a_vals.map(|x| x.to_hardware());
969 let b_flat_vals = b_vals.map(|x| x.to_hardware());
970 let a_packed = Flat::<Block16>::pack(&a_flat_vals);
971 let b_packed = Flat::<Block16>::pack(&b_flat_vals);
972
973 let add_res = Block16::add_hardware_packed(a_packed, b_packed);
975
976 let mut add_out = [Block16::ZERO.to_hardware(); 8];
977 Flat::<Block16>::unpack(add_res, &mut add_out);
978
979 for i in 0..8 {
980 assert_eq!(
981 add_out[i],
982 (a_vals[i] + b_vals[i]).to_hardware(),
983 "Block16 packed add mismatch"
984 );
985 }
986
987 let mul_res = Block16::mul_hardware_packed(a_packed, b_packed);
989
990 let mut mul_out = [Block16::ZERO.to_hardware(); 8];
991 Flat::<Block16>::unpack(mul_res, &mut mul_out);
992
993 for i in 0..8 {
994 assert_eq!(
995 mul_out[i],
996 (a_vals[i] * b_vals[i]).to_hardware(),
997 "Block16 packed mul mismatch"
998 );
999 }
1000 }
1001 }
1002
1003 #[test]
1008 fn pack_unpack_roundtrip() {
1009 let mut rng = rng();
1010 let mut data = [Block16::ZERO; PACKED_WIDTH_16];
1011
1012 for v in data.iter_mut() {
1013 *v = Block16(rng.random());
1014 }
1015
1016 let packed = Block16::pack(&data);
1017 let mut unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1018 Block16::unpack(packed, &mut unpacked);
1019
1020 assert_eq!(data, unpacked, "Block16 pack/unpack roundtrip failed");
1021 }
1022
1023 #[test]
1024 fn packed_add_consistency() {
1025 let mut rng = rng();
1026 let mut a_vals = [Block16::ZERO; PACKED_WIDTH_16];
1027 let mut b_vals = [Block16::ZERO; PACKED_WIDTH_16];
1028
1029 for i in 0..PACKED_WIDTH_16 {
1030 a_vals[i] = Block16(rng.random());
1031 b_vals[i] = Block16(rng.random());
1032 }
1033
1034 let res_packed = Block16::pack(&a_vals) + Block16::pack(&b_vals);
1035 let mut res_unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1036 Block16::unpack(res_packed, &mut res_unpacked);
1037
1038 for i in 0..PACKED_WIDTH_16 {
1039 assert_eq!(
1040 res_unpacked[i],
1041 a_vals[i] + b_vals[i],
1042 "Block16 packed add mismatch"
1043 );
1044 }
1045 }
1046
1047 #[test]
1048 fn packed_mul_consistency() {
1049 let mut rng = rng();
1050
1051 for _ in 0..1000 {
1052 let mut a_arr = [Block16::ZERO; PACKED_WIDTH_16];
1053 let mut b_arr = [Block16::ZERO; PACKED_WIDTH_16];
1054
1055 for i in 0..PACKED_WIDTH_16 {
1056 let val_a_u16: u16 = rng.random();
1057 let val_b_u16: u16 = rng.random();
1058
1059 a_arr[i] = Block16(val_a_u16);
1060 b_arr[i] = Block16(val_b_u16);
1061 }
1062
1063 let a_packed = PackedBlock16(a_arr);
1064 let b_packed = PackedBlock16(b_arr);
1065 let c_packed = a_packed * b_packed;
1066
1067 let mut c_expected = [Block16::ZERO; PACKED_WIDTH_16];
1068 for i in 0..PACKED_WIDTH_16 {
1069 c_expected[i] = a_arr[i] * b_arr[i];
1070 }
1071
1072 assert_eq!(c_packed.0, c_expected, "SIMD Block16 mismatch!");
1073 }
1074 }
1075
1076 #[test]
1077 fn parity_masks_match_from_hardware() {
1078 for x_flat in 0u16..=u16::MAX {
1081 let tower = Block16::from_hardware(Flat::from_raw(Block16(x_flat))).0;
1082
1083 for k in 0..16 {
1084 let bit = ((tower >> k) & 1) as u8;
1085 let via_api = Flat::from_raw(Block16(x_flat)).tower_bit(k);
1086
1087 assert_eq!(
1088 via_api, bit,
1089 "Block16 tower_bit_from_hardware mismatch at x_flat={x_flat:#06x}, bit_idx={k}"
1090 );
1091 }
1092 }
1093 }
1094
1095 #[cfg(target_arch = "aarch64")]
1098 proptest! {
1099 #![proptest_config(ProptestConfig::with_cases(65536))]
1100
1101 #[test]
1102 fn neon_packed_eq_scalar(a in any::<[u16; 8]>(), b in any::<[u16; 8]>()) {
1103 let pp = neon::mul_flat_packed_16(
1104 PackedBlock16(a.map(Block16)),
1105 PackedBlock16(b.map(Block16)),
1106 );
1107
1108 let want: [Block16; 8] =
1109 core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(b[i])));
1110
1111 prop_assert_eq!(pp.0, want);
1112 }
1113
1114 #[test]
1115 fn neon_scalar_packed_eq_scalar(a in any::<[u16; 8]>(), s in any::<u16>()) {
1116 let sp = neon::mul_flat_scalar_packed_16(PackedBlock16(a.map(Block16)), Block16(s));
1117
1118 let want: [Block16; 8] =
1119 core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(s)));
1120
1121 prop_assert_eq!(sp.0, want);
1122 }
1123 }
1124}