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