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(target_arch = "aarch64")]
322        {
323            let mut res = [Block16::ZERO; PACKED_WIDTH_16];
324            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
325                *out = mul_iso_16(*l, *r);
326            }
327
328            Self(res)
329        }
330
331        #[cfg(not(target_arch = "aarch64"))]
332        {
333            let mut res = [Block16::ZERO; PACKED_WIDTH_16];
334            for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
335                *out = *l * *r;
336            }
337
338            Self(res)
339        }
340    }
341}
342
343impl MulAssign for PackedBlock16 {
344    #[inline(always)]
345    fn mul_assign(&mut self, rhs: Self) {
346        *self = *self * rhs;
347    }
348}
349
350impl Mul<Block16> for PackedBlock16 {
351    type Output = Self;
352
353    #[inline(always)]
354    fn mul(self, rhs: Block16) -> Self {
355        let mut res = [Block16::ZERO; PACKED_WIDTH_16];
356        for (out, v) in res.iter_mut().zip(self.0.iter()) {
357            *out = *v * rhs;
358        }
359
360        Self(res)
361    }
362}
363
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(target_arch = "aarch64")]
424        {
425            Flat::from_raw(neon::mul_flat_16(lhs, rhs))
426        }
427
428        #[cfg(not(target_arch = "aarch64"))]
429        {
430            let a_tower = Self::from_hardware(Flat::from_raw(lhs));
431            let b_tower = Self::from_hardware(Flat::from_raw(rhs));
432
433            (a_tower * b_tower).to_hardware()
434        }
435    }
436
437    #[inline(always)]
438    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
439        let lhs = lhs.into_raw();
440        let rhs = rhs.into_raw();
441
442        #[cfg(target_arch = "aarch64")]
443        {
444            PackedFlat::from_raw(neon::mul_flat_packed_16(lhs, rhs))
445        }
446
447        #[cfg(not(target_arch = "aarch64"))]
448        {
449            let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
450            let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
451            let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
452
453            Self::unpack(lhs, &mut l);
454            Self::unpack(rhs, &mut r);
455
456            for i in 0..<Self as PackableField>::WIDTH {
457                res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
458            }
459
460            PackedFlat::from_raw(Self::pack(&res))
461        }
462    }
463
464    #[inline(always)]
465    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
466        #[cfg(target_arch = "aarch64")]
467        {
468            PackedFlat::from_raw(neon::mul_flat_scalar_packed_16(
469                lhs.into_raw(),
470                rhs.into_raw(),
471            ))
472        }
473
474        #[cfg(not(target_arch = "aarch64"))]
475        {
476            let broadcasted = PackedBlock16([rhs.into_raw(); PACKED_WIDTH_16]);
477            Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
478        }
479    }
480
481    #[inline(always)]
482    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
483        let mask = constants::FLAT_TO_TOWER_BIT_MASKS_16[bit_idx];
484
485        // 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(target_arch = "aarch64")]
539#[inline(always)]
540pub fn mul_iso_16(a: Block16, b: Block16) -> Block16 {
541    let a_f = a.to_hardware();
542    let b_f = b.to_hardware();
543    let c_f = Flat::from_raw(neon::mul_flat_16(a_f.into_raw(), b_f.into_raw()));
544
545    c_f.to_tower()
546}
547
548#[cfg(feature = "table-math")]
549#[inline(always)]
550pub fn apply_matrix_16(val: Block16, table: &[u16; 512]) -> Block16 {
551    let v = val.0;
552    let mut res = 0u16;
553
554    // 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    #[inline(always)]
604    pub fn mul_flat_16(a: Block16, b: Block16) -> Block16 {
605        unsafe {
606            // Note: Using 64-bit PMULL for 16-bit blocks
607            // is optimal on Apple Silicon. The pipeline
608            // parallelism of scalar `vmull_p64` outperforms
609            // complex SIMD Karatsuba.
610            let prod = vmull_p64(a.0 as u64, b.0 as u64);
611            let prod_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(prod), 0);
612
613            let l = (prod_val & 0xFFFF) as u16;
614            let h = (prod_val >> 16) as u16; // The rest fits in u16 for 16x16
615
616            // P(x) = x^16 + R
617            let r_val = constants::POLY_16 as u64;
618
619            // h * R
620            let h_red = vmull_p64(h as u64, r_val);
621            let h_red_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(h_red), 0);
622
623            // Result of h*R fits in 32 bits max (16+16).
624            // It's x^16 * H = H * R.
625            // res = L ^ (H*R)
626            // Since H*R > 16 bits, we have carry.
627
628            let folded = (h_red_val & 0xFFFF) as u16;
629            let carry = (h_red_val >> 16) as u16;
630
631            let mut res = l ^ folded;
632
633            // Unconditional reduction
634            // ensures constant-time.
635            let c_red = vmull_p64(carry as u64, r_val);
636            let c_val = vgetq_lane_u64(transmute::<u128, uint64x2_t>(c_red), 0);
637
638            res ^= c_val as u16;
639
640            Block16(res)
641        }
642    }
643
644    /// 8-wide GF(2^16) flat multiply, bit-identical
645    /// to eight scalar mul_flat_16 calls.
646    #[inline(always)]
647    pub fn mul_flat_packed_16(lhs: PackedBlock16, rhs: PackedBlock16) -> PackedBlock16 {
648        unsafe {
649            let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
650            let b = transmute::<[Block16; 8], uint16x8_t>(rhs.0);
651
652            let a_lo = vmovn_u16(a);
653            let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
654            let b_lo = vmovn_u16(b);
655            let b_hi = vmovn_u16(vshrq_n_u16(b, 8));
656
657            let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
658                transmute::<uint8x8_t, poly8x8_t>(a_lo),
659                transmute::<uint8x8_t, poly8x8_t>(b_lo),
660            ));
661
662            let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
663                transmute::<uint8x8_t, poly8x8_t>(a_hi),
664                transmute::<uint8x8_t, poly8x8_t>(b_hi),
665            ));
666
667            let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
668                transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
669                transmute::<uint8x8_t, poly8x8_t>(veor_u8(b_lo, b_hi)),
670            ));
671
672            PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
673                ll, mm, hh,
674            )))
675        }
676    }
677
678    /// Hoists the scalar twiddle's lane-uniform byte split
679    /// out of the eight lanes; otherwise as mul_flat_packed_16.
680    #[inline(always)]
681    pub fn mul_flat_scalar_packed_16(lhs: PackedBlock16, scalar: Block16) -> PackedBlock16 {
682        unsafe {
683            let a = transmute::<[Block16; 8], uint16x8_t>(lhs.0);
684
685            let s_lo = (scalar.0 & 0xff) as u8;
686            let s_hi = (scalar.0 >> 8) as u8;
687
688            let b_lo = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo));
689            let b_hi = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_hi));
690            let b_mid = transmute::<uint8x8_t, poly8x8_t>(vdup_n_u8(s_lo ^ s_hi));
691
692            let a_lo = vmovn_u16(a);
693            let a_hi = vmovn_u16(vshrq_n_u16(a, 8));
694
695            let ll = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
696                transmute::<uint8x8_t, poly8x8_t>(a_lo),
697                b_lo,
698            ));
699
700            let hh = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
701                transmute::<uint8x8_t, poly8x8_t>(a_hi),
702                b_hi,
703            ));
704
705            let mm = transmute::<poly16x8_t, uint16x8_t>(vmull_p8(
706                transmute::<uint8x8_t, poly8x8_t>(veor_u8(a_lo, a_hi)),
707                b_mid,
708            ));
709
710            PackedBlock16(transmute::<uint16x8_t, [Block16; 8]>(reduce_packed_16(
711                ll, mm, hh,
712            )))
713        }
714    }
715
716    // Per lane == gf_mul(·,·,16):
717    // verus/neon/packed.rs::packed_16_lane_correct.
718    #[inline(always)]
719    fn reduce_packed_16(ll: uint16x8_t, mm: uint16x8_t, hh: uint16x8_t) -> uint16x8_t {
720        unsafe {
721            let mid = veorq_u16(veorq_u16(mm, ll), hh);
722            let l = veorq_u16(ll, vshlq_n_u16(mid, 8));
723            let h = veorq_u16(hh, vshrq_n_u16(mid, 8));
724
725            let h_fold = veorq_u16(
726                veorq_u16(vshlq_n_u16(h, 5), vshlq_n_u16(h, 3)),
727                veorq_u16(vshlq_n_u16(h, 1), h),
728            );
729
730            // h <= deg 14, so h*R spills <= 4 bits past bit 15.
731            let carry = veorq_u16(
732                veorq_u16(vshrq_n_u16(h, 11), vshrq_n_u16(h, 13)),
733                vshrq_n_u16(h, 15),
734            );
735
736            let carry_fold = veorq_u16(
737                veorq_u16(vshlq_n_u16(carry, 5), vshlq_n_u16(carry, 3)),
738                veorq_u16(vshlq_n_u16(carry, 1), carry),
739            );
740
741            veorq_u16(veorq_u16(l, h_fold), carry_fold)
742        }
743    }
744}
745
746#[cfg(test)]
747mod tests {
748    use super::*;
749    use rand::{RngExt, rng};
750
751    #[cfg(target_arch = "aarch64")]
752    use proptest::prelude::*;
753
754    // ==================================
755    // BASIC
756    // ==================================
757
758    #[test]
759    fn tower_constants() {
760        // Check that tau is propagated correctly
761        // For Block16, tau must be (0, 1) from Block8.
762        let tau16 = Block16::EXTENSION_TAU;
763        let (lo16, hi16) = tau16.split();
764        assert_eq!(lo16, Block8::ZERO);
765        assert_eq!(hi16, Block8(0x20));
766    }
767
768    #[test]
769    fn add_truth() {
770        let zero = Block16::ZERO;
771        let one = Block16::ONE;
772
773        assert_eq!(zero + zero, zero);
774        assert_eq!(zero + one, one);
775        assert_eq!(one + zero, one);
776        assert_eq!(one + one, zero);
777    }
778
779    #[test]
780    fn mul_truth() {
781        let zero = Block16::ZERO;
782        let one = Block16::ONE;
783
784        assert_eq!(zero * zero, zero);
785        assert_eq!(zero * one, zero);
786        assert_eq!(one * one, one);
787    }
788
789    #[test]
790    fn add() {
791        // 5 ^ 3 = 6
792        // 101 ^ 011 = 110
793        assert_eq!(Block16(5) + Block16(3), Block16(6));
794    }
795
796    #[test]
797    fn mul_simple() {
798        // Check for prime numbers (without overflow)
799        // x^1 * x^1 = x^2 (2 * 2 = 4)
800        assert_eq!(Block16(2) * Block16(2), Block16(4));
801    }
802
803    #[test]
804    fn mul_overflow() {
805        // Reduction verification (AES test vectors)
806        // Example from the AES specification:
807        // 0x57 * 0x83 = 0xC1
808        assert_eq!(Block16(0x57) * Block16(0x83), Block16(0xC1));
809    }
810
811    #[test]
812    fn karatsuba_correctness() {
813        // Let A = X (hi=1, lo=0)
814        // Let B = X (hi=1, lo=0)
815        // A * B = X^2
816        // According to the rule:
817        // X^2 = X + tau
818        // Where tau for Block8 = 0x20.
819        // So the result should be:
820        // hi=1 (X), lo=0x20 (tau)
821
822        // Construct X manually
823        let x = Block16::new(Block8::ZERO, Block8::ONE);
824        let squared = x * x;
825
826        // Verify result via splitting
827        let (res_lo, res_hi) = squared.split();
828
829        assert_eq!(res_hi, Block8::ONE, "X^2 should contain X component");
830        assert_eq!(
831            res_lo,
832            Block8(0x20),
833            "X^2 should contain tau component (0x20)"
834        );
835    }
836
837    #[test]
838    fn security_zeroize() {
839        let mut secret_val = Block16::from(0xDEAD_u16);
840        assert_ne!(secret_val, Block16::ZERO);
841
842        secret_val.zeroize();
843
844        assert_eq!(secret_val, Block16::ZERO);
845        assert_eq!(secret_val.0, 0, "Block16 memory leak detected");
846    }
847
848    #[test]
849    fn invert_zero() {
850        // Critical safety check:
851        // Inverting zero must return 0 by convention.
852        assert_eq!(
853            Block16::ZERO.invert(),
854            Block16::ZERO,
855            "invert(0) must return 0"
856        );
857    }
858
859    #[test]
860    fn inversion_random() {
861        let mut rng = rng();
862
863        // Test a significant number of random elements
864        for _ in 0..1000 {
865            let val_u16: u16 = rng.random();
866            let val = Block16(val_u16);
867
868            if val != Block16::ZERO {
869                let inv = val.invert();
870                let res = val * inv;
871
872                assert_eq!(
873                    res,
874                    Block16::ONE,
875                    "Inversion identity failed: a * a^-1 != 1"
876                );
877            }
878        }
879    }
880
881    #[test]
882    fn tower_embedding() {
883        let mut rng = rng();
884        for _ in 0..100 {
885            let a = Block8(rng.random());
886            let b = Block8(rng.random());
887
888            // 1. Structure check:
889            // Lifting puts value in low part,
890            // zero in high part Subfield element
891            // 'a' inside extension must look like (a, 0)
892            let a_lifted: Block16 = a.into();
893            let (lo, hi) = a_lifted.split();
894
895            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
896            assert_eq!(
897                hi,
898                Block8::ZERO,
899                "Embedding structure failed: high part must be zero"
900            );
901
902            // 2. Addition Homomorphism:
903            // lift(a + b) == lift(a) + lift(b)
904            let sum_sub = a + b;
905            let sum_lifted: Block16 = sum_sub.into();
906            let sum_manual = Block16::from(a) + Block16::from(b);
907
908            assert_eq!(sum_lifted, sum_manual, "Homomorphism failed: add");
909
910            // 3. Multiplication Homomorphism:
911            // lift(a * b) == lift(a) * lift(b)
912            // Operations in the subfield must
913            // match operations in the superfield.
914            let prod_sub = a * b;
915            let prod_lifted: Block16 = prod_sub.into();
916            let prod_manual = Block16::from(a) * Block16::from(b);
917
918            assert_eq!(prod_lifted, prod_manual, "Homomorphism failed: mul");
919        }
920    }
921
922    // ==================================
923    // HARDWARE
924    // ==================================
925
926    #[test]
927    fn isomorphism_roundtrip() {
928        let mut rng = rng();
929        for _ in 0..1000 {
930            let val = Block16(rng.random::<u16>());
931            assert_eq!(
932                val.to_hardware().to_tower(),
933                val,
934                "Block16 isomorphism roundtrip failed"
935            );
936        }
937    }
938
939    #[test]
940    fn flat_mul_homomorphism() {
941        let mut rng = rng();
942        for _ in 0..1000 {
943            let a = Block16(rng.random::<u16>());
944            let b = Block16(rng.random::<u16>());
945
946            let expected_flat = (a * b).to_hardware();
947            let actual_flat = a.to_hardware() * b.to_hardware();
948
949            assert_eq!(
950                actual_flat, expected_flat,
951                "Block16 flat multiplication mismatch"
952            );
953        }
954    }
955
956    #[test]
957    fn packed_consistency() {
958        let mut rng = rng();
959        for _ in 0..100 {
960            let mut a_vals = [Block16::ZERO; 8];
961            let mut b_vals = [Block16::ZERO; 8];
962
963            for i in 0..8 {
964                a_vals[i] = Block16(rng.random::<u16>());
965                b_vals[i] = Block16(rng.random::<u16>());
966            }
967
968            let a_flat_vals = a_vals.map(|x| x.to_hardware());
969            let b_flat_vals = b_vals.map(|x| x.to_hardware());
970            let a_packed = Flat::<Block16>::pack(&a_flat_vals);
971            let b_packed = Flat::<Block16>::pack(&b_flat_vals);
972
973            // Test SIMD Add
974            let add_res = Block16::add_hardware_packed(a_packed, b_packed);
975
976            let mut add_out = [Block16::ZERO.to_hardware(); 8];
977            Flat::<Block16>::unpack(add_res, &mut add_out);
978
979            for i in 0..8 {
980                assert_eq!(
981                    add_out[i],
982                    (a_vals[i] + b_vals[i]).to_hardware(),
983                    "Block16 packed add mismatch"
984                );
985            }
986
987            // Test SIMD Mul
988            let mul_res = Block16::mul_hardware_packed(a_packed, b_packed);
989
990            let mut mul_out = [Block16::ZERO.to_hardware(); 8];
991            Flat::<Block16>::unpack(mul_res, &mut mul_out);
992
993            for i in 0..8 {
994                assert_eq!(
995                    mul_out[i],
996                    (a_vals[i] * b_vals[i]).to_hardware(),
997                    "Block16 packed mul mismatch"
998                );
999            }
1000        }
1001    }
1002
1003    // ==================================
1004    // PACKED
1005    // ==================================
1006
1007    #[test]
1008    fn pack_unpack_roundtrip() {
1009        let mut rng = rng();
1010        let mut data = [Block16::ZERO; PACKED_WIDTH_16];
1011
1012        for v in data.iter_mut() {
1013            *v = Block16(rng.random());
1014        }
1015
1016        let packed = Block16::pack(&data);
1017        let mut unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1018        Block16::unpack(packed, &mut unpacked);
1019
1020        assert_eq!(data, unpacked, "Block16 pack/unpack roundtrip failed");
1021    }
1022
1023    #[test]
1024    fn packed_add_consistency() {
1025        let mut rng = rng();
1026        let mut a_vals = [Block16::ZERO; PACKED_WIDTH_16];
1027        let mut b_vals = [Block16::ZERO; PACKED_WIDTH_16];
1028
1029        for i in 0..PACKED_WIDTH_16 {
1030            a_vals[i] = Block16(rng.random());
1031            b_vals[i] = Block16(rng.random());
1032        }
1033
1034        let res_packed = Block16::pack(&a_vals) + Block16::pack(&b_vals);
1035        let mut res_unpacked = [Block16::ZERO; PACKED_WIDTH_16];
1036        Block16::unpack(res_packed, &mut res_unpacked);
1037
1038        for i in 0..PACKED_WIDTH_16 {
1039            assert_eq!(
1040                res_unpacked[i],
1041                a_vals[i] + b_vals[i],
1042                "Block16 packed add mismatch"
1043            );
1044        }
1045    }
1046
1047    #[test]
1048    fn packed_mul_consistency() {
1049        let mut rng = rng();
1050
1051        for _ in 0..1000 {
1052            let mut a_arr = [Block16::ZERO; PACKED_WIDTH_16];
1053            let mut b_arr = [Block16::ZERO; PACKED_WIDTH_16];
1054
1055            for i in 0..PACKED_WIDTH_16 {
1056                let val_a_u16: u16 = rng.random();
1057                let val_b_u16: u16 = rng.random();
1058
1059                a_arr[i] = Block16(val_a_u16);
1060                b_arr[i] = Block16(val_b_u16);
1061            }
1062
1063            let a_packed = PackedBlock16(a_arr);
1064            let b_packed = PackedBlock16(b_arr);
1065            let c_packed = a_packed * b_packed;
1066
1067            let mut c_expected = [Block16::ZERO; PACKED_WIDTH_16];
1068            for i in 0..PACKED_WIDTH_16 {
1069                c_expected[i] = a_arr[i] * b_arr[i];
1070            }
1071
1072            assert_eq!(c_packed.0, c_expected, "SIMD Block16 mismatch!");
1073        }
1074    }
1075
1076    #[test]
1077    fn parity_masks_match_from_hardware() {
1078        // Exhaustive for Block16:
1079        // 65536 values * 16 bits.
1080        for x_flat in 0u16..=u16::MAX {
1081            let tower = Block16::from_hardware(Flat::from_raw(Block16(x_flat))).0;
1082
1083            for k in 0..16 {
1084                let bit = ((tower >> k) & 1) as u8;
1085                let via_api = Flat::from_raw(Block16(x_flat)).tower_bit(k);
1086
1087                assert_eq!(
1088                    via_api, bit,
1089                    "Block16 tower_bit_from_hardware mismatch at x_flat={x_flat:#06x}, bit_idx={k}"
1090                );
1091            }
1092        }
1093    }
1094
1095    // The NEON vmull_p8 path vs
1096    // the scalar vmull_p64 path.
1097    #[cfg(target_arch = "aarch64")]
1098    proptest! {
1099        #![proptest_config(ProptestConfig::with_cases(65536))]
1100
1101        #[test]
1102        fn neon_packed_eq_scalar(a in any::<[u16; 8]>(), b in any::<[u16; 8]>()) {
1103            let pp = neon::mul_flat_packed_16(
1104                PackedBlock16(a.map(Block16)),
1105                PackedBlock16(b.map(Block16)),
1106            );
1107
1108            let want: [Block16; 8] =
1109                core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(b[i])));
1110
1111            prop_assert_eq!(pp.0, want);
1112        }
1113
1114        #[test]
1115        fn neon_scalar_packed_eq_scalar(a in any::<[u16; 8]>(), s in any::<u16>()) {
1116            let sp = neon::mul_flat_scalar_packed_16(PackedBlock16(a.map(Block16)), Block16(s));
1117
1118            let want: [Block16; 8] =
1119                core::array::from_fn(|i| neon::mul_flat_16(Block16(a[i]), Block16(s)));
1120
1121            prop_assert_eq!(sp.0, want);
1122        }
1123    }
1124}