Skip to main content

hekate_math/towers/
block16.rs

1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate-math project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! GF(2^16) as `Block8[X] / (X^2 + X + τ)`, its flat basis
19//! modulo x^16 + x^5 + x^3 + x + 1, and the aarch64 NEON kernels.
20
21use 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/// GF(2^16) in the tower basis: `lo + hi·X`
45/// over [`Block8`], `lo` in the low 8 bits.
46#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
47#[repr(transparent)]
48pub struct Block16(pub u16);
49
50impl Block16 {
51    /// τ of the next level, `Block16[X] / (X^2 + X + τ)`;
52    /// equals [`TowerField::EXTENSION_TAU`].
53    pub const TAU: Self = Block16(0x2000);
54
55    /// Builds `lo + hi·X` from its halves.
56    pub fn new(lo: Block8, hi: Block8) -> Self {
57        Self((hi.0 as u16) << 8 | (lo.0 as u16))
58    }
59
60    /// Returns the halves `(lo, hi)` of `lo + hi·X`.
61    #[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        // Norm = h^2 * tau + h*l + l^2
86        let norm = h.square().mul_tau() + h * l + l.square();
87        let norm_inv = norm.invert();
88
89        // Res = (h*norm_inv) X + (h+l)*norm_inv
90        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
217// ========================================
218// FIELD LIFTING
219// ========================================
220
221impl 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
235// ===================================
236// PACKED BLOCK 16 (Width = 8)
237// ===================================
238
239/// Lanes in [`PackedBlock16`].
240// 128 bits / 16 = 8 elements
241pub const PACKED_WIDTH_16: usize = 8;
242
243/// Eight [`Block16`] lanes; operators act lane by lane.
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
245#[repr(C, align(16))]
246pub struct PackedBlock16(pub [Block16; PACKED_WIDTH_16]);
247
248impl PackedBlock16 {
249    /// Returns the packed value with every lane zero.
250    #[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
367// ===================================
368// Hardware Field
369// ===================================
370
371impl 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        // Parity of (x & mask) without
489        // popcount. Folds 16 bits down
490        // to 1 using a binary XOR tree.
491        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
521// ===================================
522// Binary Field Extras
523// ===================================
524
525impl_binary_field_extras!(Block16, map_ct_16, TRACE_MASK_16, SOLVE_QUADRATIC_BASIS_16);
526
527// ===========================================
528// UTILS
529// ===========================================
530
531/// Returns `a * b` through the flat basis, φ⁻¹(φ(a) · φ(b)), with
532/// the PMULL kernel; aarch64 with the `aes` target feature only.
533#[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    // 2 lookups (8-bit window)
550    for i in 0..2 {
551        let idx = (i * 256) + ((v >> (i * 8)) & 0xFF) as usize;
552
553        // SAFETY:
554        // idx = i * 256 + byte < 2 * 256 = table.len().
555        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// ===========================================
595// SIMD INSTRUCTIONS
596// ===========================================
597
598#[cfg(target_arch = "aarch64")]
599mod neon {
600    use super::*;
601    use core::arch::aarch64::*;
602    use core::mem::transmute;
603
604    // Shifts 5,3,1,0 in reduce_packed_16 encode R = POLY_16 (0x2b)
605    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            // Note: Using 64-bit PMULL for 16-bit blocks
623            // is optimal on Apple Silicon. The pipeline
624            // parallelism of scalar `vmull_p64` outperforms
625            // complex SIMD Karatsuba.
626            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; // The rest fits in u16 for 16x16
631
632            // P(x) = x^16 + R
633            let r_val = constants::POLY_16 as u64;
634
635            // h * R
636            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            // Result of h*R fits in 32 bits max (16+16).
640            // It's x^16 * H = H * R.
641            // res = L ^ (H*R)
642            // Since H*R > 16 bits, we have carry.
643
644            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            // Unconditional reduction
650            // ensures constant-time.
651            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    /// 8-wide GF(2^16) flat multiply, bit-identical
661    /// to eight scalar mul_flat_16 calls.
662    #[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    /// Hoists the scalar twiddle's lane-uniform byte split
695    /// out of the eight lanes; otherwise as mul_flat_packed_16.
696    #[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    // Per lane == gf_mul(·,·,16):
733    // verus/flat/packed.rs::packed_16_lane_correct.
734    #[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            // h <= deg 14, so h*R spills <= 4 bits past bit 15.
747            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    // ==================================
771    // BASIC
772    // ==================================
773
774    #[test]
775    fn tower_constants() {
776        // Check that tau is propagated correctly
777        // For Block16, tau must be (0, 1) from Block8.
778        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        // 5 ^ 3 = 6
808        // 101 ^ 011 = 110
809        assert_eq!(Block16(5) + Block16(3), Block16(6));
810    }
811
812    #[test]
813    fn mul_simple() {
814        // Check for prime numbers (without overflow)
815        // x^1 * x^1 = x^2 (2 * 2 = 4)
816        assert_eq!(Block16(2) * Block16(2), Block16(4));
817    }
818
819    #[test]
820    fn mul_overflow() {
821        // Reduction verification (AES test vectors)
822        // Example from the AES specification:
823        // 0x57 * 0x83 = 0xC1
824        assert_eq!(Block16(0x57) * Block16(0x83), Block16(0xC1));
825    }
826
827    #[test]
828    fn karatsuba_correctness() {
829        // Let A = X (hi=1, lo=0)
830        // Let B = X (hi=1, lo=0)
831        // A * B = X^2
832        // According to the rule:
833        // X^2 = X + tau
834        // Where tau for Block8 = 0x20.
835        // So the result should be:
836        // hi=1 (X), lo=0x20 (tau)
837
838        // Construct X manually
839        let x = Block16::new(Block8::ZERO, Block8::ONE);
840        let squared = x * x;
841
842        // Verify result via splitting
843        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        // Critical safety check:
879        // Inverting zero must return 0 by convention.
880        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        // Test a significant number of random elements
892        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            // 1. Structure check:
917            // Lifting puts value in low part,
918            // zero in high part Subfield element
919            // 'a' inside extension must look like (a, 0)
920            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            // 2. Addition Homomorphism:
931            // lift(a + b) == lift(a) + lift(b)
932            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            // 3. Multiplication Homomorphism:
939            // lift(a * b) == lift(a) * lift(b)
940            // Operations in the subfield must
941            // match operations in the superfield.
942            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    // ==================================
951    // HARDWARE
952    // ==================================
953
954    #[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            // Test SIMD Add
1002            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            // Test SIMD Mul
1016            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    // ==================================
1032    // PACKED
1033    // ==================================
1034
1035    #[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        // Exhaustive for Block16:
1119        // 65536 values * 16 bits.
1120        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    // The NEON vmull_p8 path vs
1136    // the scalar vmull_p64 path.
1137    #[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}