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>. All rights reserved.
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//! BLOCK 16 (GF(2^16))
19use 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        // Norm = h^2 * tau + h*l + l^2
70        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        // Res = (h*norm_inv) X + (h+l)*norm_inv
78        let res_hi = h * norm_inv;
79        let res_lo = (h + l) * norm_inv;
80
81        Self::new(res_lo, res_hi)
82    }
83
84    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
85        let mut buf = [0u8; 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        // Karatsuba
116        let v0 = a0 * b0;
117        let v1 = a1 * b1;
118        let v_sum = (a0 + a1) * (b0 + b1);
119
120        // Reconstruction with reduction X^2 = X + tau
121        // Hi
122        let c_hi = v0 + v_sum;
123
124        // Lo
125        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
212// ========================================
213// FIELD LIFTING
214// ========================================
215
216impl 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
230// ===================================
231// PACKED BLOCK 16 (Width = 8)
232// ===================================
233
234// 128 bits / 16 = 8 elements
235pub 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(pmull)]
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(pmull))]
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
364// ===================================
365// Hardware Field
366// ===================================
367
368impl 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(pmull)]
424        {
425            Flat::from_raw(neon::mul_flat_16(lhs, rhs))
426        }
427
428        #[cfg(not(pmull))]
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        // Parity of (x & mask) without
486        // popcount. Folds 16 bits down
487        // to 1 using a binary XOR tree.
488        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
522// ===================================
523// Binary Field Extras
524// ===================================
525
526impl_binary_field_extras!(
527    Block16,
528    Block8,
529    map_ct_16,
530    TRACE_MASK_16,
531    SOLVE_QUADRATIC_BASIS_16
532);
533
534// ===========================================
535// UTILS
536// ===========================================
537
538#[cfg(pmull)]
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    // 2 lookups (8-bit window)
555    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// ===========================================
580// SIMD INSTRUCTIONS
581// ===========================================
582
583#[cfg(target_arch = "aarch64")]
584mod neon {
585    use super::*;
586    use core::arch::aarch64::*;
587    use core::mem::transmute;
588
589    // Shifts 5,3,1,0 in reduce_packed_16 encode R = POLY_16 (0x2b)
590    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    #[cfg(pmull)]
604    #[inline(always)]
605    pub fn mul_flat_16(a: Block16, b: Block16) -> Block16 {
606        unsafe {
607            // Note: Using 64-bit PMULL for 16-bit blocks
608            // is optimal on Apple Silicon. The pipeline
609            // parallelism of scalar `vmull_p64` outperforms
610            // complex SIMD Karatsuba.
611            let prod = vmull_p64(a.0 as u64, b.0 as u64);
612            let prod_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(prod), 0);
613
614            let l = (prod_val & 0xFFFF) as u16;
615            let h = (prod_val >> 16) as u16; // The rest fits in u16 for 16x16
616
617            // P(x) = x^16 + R
618            let r_val = constants::POLY_16 as u64;
619
620            // h * R
621            let h_red = vmull_p64(h as u64, r_val);
622            let h_red_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(h_red), 0);
623
624            // Result of h*R fits in 32 bits max (16+16).
625            // It's x^16 * H = H * R.
626            // res = L ^ (H*R)
627            // Since H*R > 16 bits, we have carry.
628
629            let folded = (h_red_val & 0xFFFF) as u16;
630            let carry = (h_red_val >> 16) as u16;
631
632            let mut res = l ^ folded;
633
634            // Unconditional reduction
635            // ensures constant-time.
636            let c_red = vmull_p64(carry as u64, r_val);
637            let c_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(c_red), 0);
638
639            res ^= c_val as u16;
640
641            Block16(res)
642        }
643    }
644
645    /// 8-wide GF(2^16) flat multiply, bit-identical
646    /// to eight scalar mul_flat_16 calls.
647    #[inline(always)]
648    pub fn mul_flat_packed_16(lhs: PackedBlock16, rhs: PackedBlock16) -> PackedBlock16 {
649        unsafe {
650            let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
651            let b = transmute::<[Block16; 8], uint16x8_t>(rhs.0);
652
653            let a_lo = vmovn_u16(a);
654            let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
655            let b_lo = vmovn_u16(b);
656            let b_hi = vmovn_u16(vshrq_n_u16(b, 8));
657
658            let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
659                transmute::<uint8x8_t, poly8x8_t>(a_lo),
660                transmute::<uint8x8_t, poly8x8_t>(b_lo),
661            ));
662
663            let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
664                transmute::<uint8x8_t, poly8x8_t>(a_hi),
665                transmute::<uint8x8_t, poly8x8_t>(b_hi),
666            ));
667
668            let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
669                transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
670                transmute::<uint8x8_t, poly8x8_t>(veor_u8(b_lo, b_hi)),
671            ));
672
673            PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
674                ll, mm, hh,
675            )))
676        }
677    }
678
679    /// Hoists the scalar twiddle's lane-uniform byte split
680    /// out of the eight lanes; otherwise as mul_flat_packed_16.
681    #[inline(always)]
682    pub fn mul_flat_scalar_packed_16(lhs: PackedBlock16, scalar: Block16) -> PackedBlock16 {
683        unsafe {
684            let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
685
686            let s_lo = (scalar.0 & 0xff) as u8;
687            let s_hi = (scalar.0 >> 8) as u8;
688
689            let b_lo = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo));
690            let b_hi = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_hi));
691            let b_mid = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo ^ s_hi));
692
693            let a_lo = vmovn_u16(a);
694            let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
695
696            let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
697                transmute::<uint8x8_t, poly8x8_t>(a_lo),
698                b_lo,
699            ));
700
701            let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
702                transmute::<uint8x8_t, poly8x8_t>(a_hi),
703                b_hi,
704            ));
705
706            let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
707                transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
708                b_mid,
709            ));
710
711            PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
712                ll, mm, hh,
713            )))
714        }
715    }
716
717    // Per lane == gf_mul(·,·,16):
718    // verus/neon/packed.rs::packed_16_lane_correct.
719    #[inline(always)]
720    fn reduce_packed_16(ll: uint16x8_t, mm: uint16x8_t, hh: uint16x8_t) -> uint16x8_t {
721        unsafe {
722            let mid = veorq_u16(veorq_u16(mm, ll), hh);
723            let l = veorq_u16(ll, vshlq_n_u16(mid, 8));
724            let h = veorq_u16(hh, vshrq_n_u16(mid, 8));
725
726            let h_fold = veorq_u16(
727                veorq_u16(vshlq_n_u16(h, 5), vshlq_n_u16(h, 3)),
728                veorq_u16(vshlq_n_u16(h, 1), h),
729            );
730
731            // h <= deg 14, so h*R spills <= 4 bits past bit 15.
732            let carry = veorq_u16(
733                veorq_u16(vshrq_n_u16(h, 11), vshrq_n_u16(h, 13)),
734                vshrq_n_u16(h, 15),
735            );
736
737            let carry_fold = veorq_u16(
738                veorq_u16(vshlq_n_u16(carry, 5), vshlq_n_u16(carry, 3)),
739                veorq_u16(vshlq_n_u16(carry, 1), carry),
740            );
741
742            veorq_u16(veorq_u16(l, h_fold), carry_fold)
743        }
744    }
745}
746
747#[cfg(test)]
748mod tests {
749    use super::*;
750    use rand::{RngExt, rng};
751
752    #[cfg(pmull)]
753    use proptest::prelude::*;
754
755    // ==================================
756    // BASIC
757    // ==================================
758
759    #[test]
760    fn tower_constants() {
761        // Check that tau is propagated correctly
762        // For Block16, tau must be (0, 1) from Block8.
763        let tau16 = Block16::EXTENSION_TAU;
764        let (lo16, hi16) = tau16.split();
765        assert_eq!(lo16, Block8::ZERO);
766        assert_eq!(hi16, Block8(0x20));
767    }
768
769    #[test]
770    fn add_truth() {
771        let zero = Block16::ZERO;
772        let one = Block16::ONE;
773
774        assert_eq!(zero + zero, zero);
775        assert_eq!(zero + one, one);
776        assert_eq!(one + zero, one);
777        assert_eq!(one + one, zero);
778    }
779
780    #[test]
781    fn mul_truth() {
782        let zero = Block16::ZERO;
783        let one = Block16::ONE;
784
785        assert_eq!(zero * zero, zero);
786        assert_eq!(zero * one, zero);
787        assert_eq!(one * one, one);
788    }
789
790    #[test]
791    fn add() {
792        // 5 ^ 3 = 6
793        // 101 ^ 011 = 110
794        assert_eq!(Block16(5) + Block16(3), Block16(6));
795    }
796
797    #[test]
798    fn mul_simple() {
799        // Check for prime numbers (without overflow)
800        // x^1 * x^1 = x^2 (2 * 2 = 4)
801        assert_eq!(Block16(2) * Block16(2), Block16(4));
802    }
803
804    #[test]
805    fn mul_overflow() {
806        // Reduction verification (AES test vectors)
807        // Example from the AES specification:
808        // 0x57 * 0x83 = 0xC1
809        assert_eq!(Block16(0x57) * Block16(0x83), Block16(0xC1));
810    }
811
812    #[test]
813    fn karatsuba_correctness() {
814        // Let A = X (hi=1, lo=0)
815        // Let B = X (hi=1, lo=0)
816        // A * B = X^2
817        // According to the rule:
818        // X^2 = X + tau
819        // Where tau for Block8 = 0x20.
820        // So the result should be:
821        // hi=1 (X), lo=0x20 (tau)
822
823        // Construct X manually
824        let x = Block16::new(Block8::ZERO, Block8::ONE);
825        let squared = x * x;
826
827        // Verify result via splitting
828        let (res_lo, res_hi) = squared.split();
829
830        assert_eq!(res_hi, Block8::ONE, "X^2 should contain X component");
831        assert_eq!(
832            res_lo,
833            Block8(0x20),
834            "X^2 should contain tau component (0x20)"
835        );
836    }
837
838    #[test]
839    fn security_zeroize() {
840        let mut secret_val = Block16::from(0xDEAD_u16);
841        assert_ne!(secret_val, Block16::ZERO);
842
843        secret_val.zeroize();
844
845        assert_eq!(secret_val, Block16::ZERO);
846        assert_eq!(secret_val.0, 0, "Block16 memory leak detected");
847    }
848
849    #[test]
850    fn invert_zero() {
851        // Critical safety check:
852        // Inverting zero must return 0 by convention.
853        assert_eq!(
854            Block16::ZERO.invert(),
855            Block16::ZERO,
856            "invert(0) must return 0"
857        );
858    }
859
860    #[test]
861    fn inversion_random() {
862        let mut rng = rng();
863
864        // Test a significant number of random elements
865        for _ in 0..1000 {
866            let val_u16: u16 = rng.random();
867            let val = Block16(val_u16);
868
869            if val != Block16::ZERO {
870                let inv = val.invert();
871                let res = val * inv;
872
873                assert_eq!(
874                    res,
875                    Block16::ONE,
876                    "Inversion identity failed: a * a^-1 != 1"
877                );
878            }
879        }
880    }
881
882    #[test]
883    fn tower_embedding() {
884        let mut rng = rng();
885        for _ in 0..100 {
886            let a = Block8(rng.random());
887            let b = Block8(rng.random());
888
889            // 1. Structure check:
890            // Lifting puts value in low part,
891            // zero in high part Subfield element
892            // 'a' inside extension must look like (a, 0)
893            let a_lifted: Block16 = a.into();
894            let (lo, hi) = a_lifted.split();
895
896            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
897            assert_eq!(
898                hi,
899                Block8::ZERO,
900                "Embedding structure failed: high part must be zero"
901            );
902
903            // 2. Addition Homomorphism:
904            // lift(a + b) == lift(a) + lift(b)
905            let sum_sub = a + b;
906            let sum_lifted: Block16 = sum_sub.into();
907            let sum_manual = Block16::from(a) + Block16::from(b);
908
909            assert_eq!(sum_lifted, sum_manual, "Homomorphism failed: add");
910
911            // 3. Multiplication Homomorphism:
912            // lift(a * b) == lift(a) * lift(b)
913            // Operations in the subfield must
914            // match operations in the superfield.
915            let prod_sub = a * b;
916            let prod_lifted: Block16 = prod_sub.into();
917            let prod_manual = Block16::from(a) * Block16::from(b);
918
919            assert_eq!(prod_lifted, prod_manual, "Homomorphism failed: mul");
920        }
921    }
922
923    // ==================================
924    // HARDWARE
925    // ==================================
926
927    #[test]
928    fn isomorphism_roundtrip() {
929        let mut rng = rng();
930        for _ in 0..1000 {
931            let val = Block16(rng.random::<u16>());
932            assert_eq!(
933                val.to_hardware().to_tower(),
934                val,
935                "Block16 isomorphism roundtrip failed"
936            );
937        }
938    }
939
940    #[test]
941    fn flat_mul_homomorphism() {
942        let mut rng = rng();
943        for _ in 0..1000 {
944            let a = Block16(rng.random::<u16>());
945            let b = Block16(rng.random::<u16>());
946
947            let expected_flat = (a * b).to_hardware();
948            let actual_flat = a.to_hardware() * b.to_hardware();
949
950            assert_eq!(
951                actual_flat, expected_flat,
952                "Block16 flat multiplication mismatch"
953            );
954        }
955    }
956
957    #[test]
958    fn packed_consistency() {
959        let mut rng = rng();
960        for _ in 0..100 {
961            let mut a_vals = [Block16::ZERO; 8];
962            let mut b_vals = [Block16::ZERO; 8];
963
964            for i in 0..8 {
965                a_vals[i] = Block16(rng.random::<u16>());
966                b_vals[i] = Block16(rng.random::<u16>());
967            }
968
969            let a_flat_vals = a_vals.map(|x| x.to_hardware());
970            let b_flat_vals = b_vals.map(|x| x.to_hardware());
971            let a_packed = Flat::<Block16>::pack(&a_flat_vals);
972            let b_packed = Flat::<Block16>::pack(&b_flat_vals);
973
974            // Test SIMD Add
975            let add_res = Block16::add_hardware_packed(a_packed, b_packed);
976
977            let mut add_out = [Block16::ZERO.to_hardware(); 8];
978            Flat::<Block16>::unpack(add_res, &mut add_out);
979
980            for i in 0..8 {
981                assert_eq!(
982                    add_out[i],
983                    (a_vals[i] + b_vals[i]).to_hardware(),
984                    "Block16 packed add mismatch"
985                );
986            }
987
988            // Test SIMD Mul
989            let mul_res = Block16::mul_hardware_packed(a_packed, b_packed);
990
991            let mut mul_out = [Block16::ZERO.to_hardware(); 8];
992            Flat::<Block16>::unpack(mul_res, &mut mul_out);
993
994            for i in 0..8 {
995                assert_eq!(
996                    mul_out[i],
997                    (a_vals[i] * b_vals[i]).to_hardware(),
998                    "Block16 packed mul mismatch"
999                );
1000            }
1001        }
1002    }
1003
1004    // ==================================
1005    // PACKED
1006    // ==================================
1007
1008    #[test]
1009    fn pack_unpack_roundtrip() {
1010        let mut rng = rng();
1011        let mut data = [Block16::ZERO; PACKED_WIDTH_16];
1012
1013        for v in data.iter_mut() {
1014            *v = Block16(rng.random());
1015        }
1016
1017        let packed = Block16::pack(&data);
1018        let mut unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1019        Block16::unpack(packed, &mut unpacked);
1020
1021        assert_eq!(data, unpacked, "Block16 pack/unpack roundtrip failed");
1022    }
1023
1024    #[test]
1025    fn packed_add_consistency() {
1026        let mut rng = rng();
1027        let mut a_vals = [Block16::ZERO; PACKED_WIDTH_16];
1028        let mut b_vals = [Block16::ZERO; PACKED_WIDTH_16];
1029
1030        for i in 0..PACKED_WIDTH_16 {
1031            a_vals[i] = Block16(rng.random());
1032            b_vals[i] = Block16(rng.random());
1033        }
1034
1035        let res_packed = Block16::pack(&a_vals) + Block16::pack(&b_vals);
1036        let mut res_unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1037        Block16::unpack(res_packed, &mut res_unpacked);
1038
1039        for i in 0..PACKED_WIDTH_16 {
1040            assert_eq!(
1041                res_unpacked[i],
1042                a_vals[i] + b_vals[i],
1043                "Block16 packed add mismatch"
1044            );
1045        }
1046    }
1047
1048    #[test]
1049    fn packed_mul_consistency() {
1050        let mut rng = rng();
1051
1052        for _ in 0..1000 {
1053            let mut a_arr = [Block16::ZERO; PACKED_WIDTH_16];
1054            let mut b_arr = [Block16::ZERO; PACKED_WIDTH_16];
1055
1056            for i in 0..PACKED_WIDTH_16 {
1057                let val_a_u16: u16 = rng.random();
1058                let val_b_u16: u16 = rng.random();
1059
1060                a_arr[i] = Block16(val_a_u16);
1061                b_arr[i] = Block16(val_b_u16);
1062            }
1063
1064            let a_packed = PackedBlock16(a_arr);
1065            let b_packed = PackedBlock16(b_arr);
1066            let c_packed = a_packed * b_packed;
1067
1068            let mut c_expected = [Block16::ZERO; PACKED_WIDTH_16];
1069            for i in 0..PACKED_WIDTH_16 {
1070                c_expected[i] = a_arr[i] * b_arr[i];
1071            }
1072
1073            assert_eq!(c_packed.0, c_expected, "SIMD Block16 mismatch!");
1074        }
1075    }
1076
1077    #[test]
1078    fn parity_masks_match_from_hardware() {
1079        // Exhaustive for Block16:
1080        // 65536 values * 16 bits.
1081        for x_flat in 0u16..=u16::MAX {
1082            let tower = Block16::from_hardware(Flat::from_raw(Block16(x_flat))).0;
1083
1084            for k in 0..16 {
1085                let bit = ((tower >> k) & 1) as u8;
1086                let via_api = Flat::from_raw(Block16(x_flat)).tower_bit(k);
1087
1088                assert_eq!(
1089                    via_api, bit,
1090                    "Block16 tower_bit_from_hardware mismatch at x_flat={x_flat:#06x}, bit_idx={k}"
1091                );
1092            }
1093        }
1094    }
1095
1096    // The NEON vmull_p8 path vs
1097    // the scalar vmull_p64 path.
1098    #[cfg(pmull)]
1099    proptest! {
1100        #![proptest_config(ProptestConfig::with_cases(65536))]
1101
1102        #[test]
1103        fn neon_packed_eq_scalar(a in any::<[u16; 8]>(), b in any::<[u16; 8]>()) {
1104            let pp = neon::mul_flat_packed_16(
1105                PackedBlock16(a.map(Block16)),
1106                PackedBlock16(b.map(Block16)),
1107            );
1108
1109            let want: [Block16; 8] =
1110                core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(b[i])));
1111
1112            prop_assert_eq!(pp.0, want);
1113        }
1114
1115        #[test]
1116        fn neon_scalar_packed_eq_scalar(a in any::<[u16; 8]>(), s in any::<u16>()) {
1117            let sp = neon::mul_flat_scalar_packed_16(PackedBlock16(a.map(Block16)), Block16(s));
1118
1119            let want: [Block16; 8] =
1120                core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(s)));
1121
1122            prop_assert_eq!(sp.0, want);
1123        }
1124    }
1125}