Skip to main content

hekate_math/towers/
block64.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 64 (GF(2^64))
19use crate::algebra::impl_binary_field_extras;
20use crate::constants::FLAT_TO_TOWER_BIT_MASKS_64;
21use crate::towers::bit::Bit;
22use crate::towers::block8::Block8;
23use crate::towers::block16::Block16;
24use crate::towers::block32::Block32;
25use crate::{
26    BinaryFieldExtras, CanonicalDeserialize, CanonicalSerialize, Flat, FlatPromote, HardwareField,
27    PackableField, PackedFlat, TowerField, constants,
28};
29use core::ops::{Add, AddAssign, BitXor, BitXorAssign, Mul, MulAssign, Sub, SubAssign};
30use serde::{Deserialize, Serialize};
31use zeroize::Zeroize;
32
33#[cfg(not(feature = "table-math"))]
34#[repr(align(64))]
35struct CtConvertBasisU64<const N: usize>([u64; N]);
36
37#[cfg(not(feature = "table-math"))]
38static TOWER_TO_FLAT_BASIS_64: CtConvertBasisU64<64> =
39    CtConvertBasisU64(constants::RAW_TOWER_TO_FLAT_64);
40
41#[cfg(not(feature = "table-math"))]
42static FLAT_TO_TOWER_BASIS_64: CtConvertBasisU64<64> =
43    CtConvertBasisU64(constants::RAW_FLAT_TO_TOWER_64);
44
45#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
46#[repr(transparent)]
47pub struct Block64(pub u64);
48
49impl Block64 {
50    // 0x2000_0000 << 32 = 0x2000_0000_0000_0000
51    pub const TAU: Self = Block64(0x2000_0000_0000_0000);
52
53    pub fn new(lo: Block32, hi: Block32) -> Self {
54        Self((hi.0 as u64) << 32 | (lo.0 as u64))
55    }
56
57    #[inline(always)]
58    pub fn split(self) -> (Block32, Block32) {
59        (Block32(self.0 as u32), Block32((self.0 >> 32) as u32))
60    }
61}
62
63impl TowerField for Block64 {
64    const BITS: usize = 64;
65    const ZERO: Self = Block64(0);
66    const ONE: Self = Block64(1);
67
68    const EXTENSION_TAU: Self = Self::TAU;
69
70    fn invert(&self) -> Self {
71        let (l, h) = self.split();
72        let h2 = h * h;
73        let l2 = l * l;
74        let hl = h * l;
75        let norm = (h2 * Block32::TAU) + hl + l2;
76
77        let norm_inv = norm.invert();
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; 8];
86        buf.copy_from_slice(&bytes[0..8]);
87
88        Self(u64::from_le_bytes(buf))
89    }
90}
91
92impl Add for Block64 {
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 Block64 {
101    type Output = Self;
102
103    fn sub(self, rhs: Self) -> Self {
104        self.add(rhs)
105    }
106}
107
108impl Mul for Block64 {
109    type Output = Self;
110
111    fn mul(self, rhs: Self) -> Self {
112        let (a0, a1) = self.split();
113        let (b0, b1) = rhs.split();
114
115        let v0 = a0 * b0;
116        let v1 = a1 * b1;
117        let v_sum = (a0 + a1) * (b0 + b1);
118
119        let c_hi = v0 + v_sum;
120        let c_lo = v0 + (v1 * Block32::TAU);
121
122        Self::new(c_lo, c_hi)
123    }
124}
125
126impl AddAssign for Block64 {
127    fn add_assign(&mut self, rhs: Self) {
128        self.0.bitxor_assign(rhs.0);
129    }
130}
131
132impl SubAssign for Block64 {
133    fn sub_assign(&mut self, rhs: Self) {
134        self.0.bitxor_assign(rhs.0);
135    }
136}
137
138impl MulAssign for Block64 {
139    fn mul_assign(&mut self, rhs: Self) {
140        *self = *self * rhs;
141    }
142}
143
144impl CanonicalSerialize for Block64 {
145    fn serialized_size(&self) -> usize {
146        8
147    }
148
149    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
150        if writer.len() < 8 {
151            return Err(());
152        }
153
154        writer[..8].copy_from_slice(&self.0.to_le_bytes());
155
156        Ok(())
157    }
158}
159
160impl CanonicalDeserialize for Block64 {
161    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
162        if bytes.len() < 8 {
163            return Err(());
164        }
165
166        let mut buf = [0u8; 8];
167        buf.copy_from_slice(&bytes[0..8]);
168
169        Ok(Self(u64::from_le_bytes(buf)))
170    }
171}
172
173impl From<u8> for Block64 {
174    #[inline(always)]
175    fn from(val: u8) -> Self {
176        Self(val as u64)
177    }
178}
179
180impl From<u32> for Block64 {
181    #[inline(always)]
182    fn from(val: u32) -> Self {
183        Self::from(val as u64)
184    }
185}
186
187impl From<u64> for Block64 {
188    #[inline(always)]
189    fn from(val: u64) -> Self {
190        Self(val)
191    }
192}
193
194impl From<u128> for Block64 {
195    #[inline(always)]
196    fn from(val: u128) -> Self {
197        Self(val as u64)
198    }
199}
200
201// ========================================
202// FIELD LIFTING
203// ========================================
204
205impl From<Bit> for Block64 {
206    #[inline(always)]
207    fn from(val: Bit) -> Self {
208        Self(val.get() as u64)
209    }
210}
211
212impl From<Block8> for Block64 {
213    #[inline(always)]
214    fn from(val: Block8) -> Self {
215        Self(val.0 as u64)
216    }
217}
218
219impl From<Block16> for Block64 {
220    #[inline(always)]
221    fn from(val: Block16) -> Self {
222        Self(val.0 as u64)
223    }
224}
225
226impl From<Block32> for Block64 {
227    #[inline(always)]
228    fn from(val: Block32) -> Self {
229        Self(val.0 as u64)
230    }
231}
232
233// ===================================
234// PACKED BLOCK 64 (Width = 2)
235// ===================================
236
237pub const PACKED_WIDTH_64: usize = 2;
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
240#[repr(C, align(16))] // 128-bit alignment
241pub struct PackedBlock64(pub [Block64; PACKED_WIDTH_64]);
242
243impl PackedBlock64 {
244    #[inline(always)]
245    pub fn zero() -> Self {
246        Self([Block64::ZERO; PACKED_WIDTH_64])
247    }
248}
249
250impl PackableField for Block64 {
251    type Packed = PackedBlock64;
252
253    const WIDTH: usize = PACKED_WIDTH_64;
254
255    #[inline(always)]
256    fn pack(chunk: &[Self]) -> Self::Packed {
257        assert!(
258            chunk.len() >= PACKED_WIDTH_64,
259            "PackableField::pack: input slice too short",
260        );
261
262        let mut arr = [Self::ZERO; PACKED_WIDTH_64];
263        arr.copy_from_slice(&chunk[..PACKED_WIDTH_64]);
264
265        PackedBlock64(arr)
266    }
267
268    #[inline(always)]
269    fn unpack(packed: Self::Packed, output: &mut [Self]) {
270        assert!(
271            output.len() >= PACKED_WIDTH_64,
272            "PackableField::unpack: output slice too short",
273        );
274
275        output[..PACKED_WIDTH_64].copy_from_slice(&packed.0);
276    }
277}
278
279impl Add for PackedBlock64 {
280    type Output = Self;
281
282    #[inline(always)]
283    fn add(self, rhs: Self) -> Self {
284        let mut res = [Block64::ZERO; PACKED_WIDTH_64];
285        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
286            *out = *l + *r;
287        }
288
289        Self(res)
290    }
291}
292
293impl AddAssign for PackedBlock64 {
294    #[inline(always)]
295    fn add_assign(&mut self, rhs: Self) {
296        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
297            *l += *r;
298        }
299    }
300}
301
302impl Sub for PackedBlock64 {
303    type Output = Self;
304
305    #[inline(always)]
306    fn sub(self, rhs: Self) -> Self {
307        self.add(rhs)
308    }
309}
310
311impl SubAssign for PackedBlock64 {
312    #[inline(always)]
313    fn sub_assign(&mut self, rhs: Self) {
314        self.add_assign(rhs);
315    }
316}
317
318impl Mul for PackedBlock64 {
319    type Output = Self;
320
321    #[inline(always)]
322    fn mul(self, rhs: Self) -> Self {
323        #[cfg(pmull)]
324        {
325            let a0 = mul_iso_64(self.0[0], rhs.0[0]);
326            let a1 = mul_iso_64(self.0[1], rhs.0[1]);
327
328            Self([a0, a1])
329        }
330
331        #[cfg(not(pmull))]
332        {
333            let mut res = [Block64::ZERO; PACKED_WIDTH_64];
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 PackedBlock64 {
344    #[inline(always)]
345    fn mul_assign(&mut self, rhs: Self) {
346        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
347            *l *= *r;
348        }
349    }
350}
351
352impl Mul<Block64> for PackedBlock64 {
353    type Output = Self;
354
355    #[inline(always)]
356    fn mul(self, rhs: Block64) -> Self {
357        let mut res = [Block64::ZERO; PACKED_WIDTH_64];
358        for (out, v) in res.iter_mut().zip(self.0.iter()) {
359            *out = *v * rhs;
360        }
361
362        Self(res)
363    }
364}
365
366// ===================================
367// Hardware Field
368// ===================================
369
370impl HardwareField for Block64 {
371    #[inline(always)]
372    fn to_hardware(self) -> Flat<Self> {
373        #[cfg(feature = "table-math")]
374        {
375            Flat::from_raw(apply_matrix_64(self, &constants::TOWER_TO_FLAT_64))
376        }
377
378        #[cfg(not(feature = "table-math"))]
379        {
380            Flat::from_raw(Block64(map_ct_64(self.0, &TOWER_TO_FLAT_BASIS_64.0)))
381        }
382    }
383
384    #[inline(always)]
385    fn from_hardware(value: Flat<Self>) -> Self {
386        let value = value.into_raw();
387
388        #[cfg(feature = "table-math")]
389        {
390            apply_matrix_64(value, &constants::FLAT_TO_TOWER_64)
391        }
392
393        #[cfg(not(feature = "table-math"))]
394        {
395            Block64(map_ct_64(value.0, &FLAT_TO_TOWER_BASIS_64.0))
396        }
397    }
398
399    #[inline(always)]
400    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
401        Flat::from_raw(lhs.into_raw() + rhs.into_raw())
402    }
403
404    #[inline(always)]
405    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
406        let lhs = lhs.into_raw();
407        let rhs = rhs.into_raw();
408
409        #[cfg(target_arch = "aarch64")]
410        {
411            PackedFlat::from_raw(neon::add_packed_64(lhs, rhs))
412        }
413
414        #[cfg(not(target_arch = "aarch64"))]
415        {
416            PackedFlat::from_raw(lhs + rhs)
417        }
418    }
419
420    #[inline(always)]
421    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
422        let lhs = lhs.into_raw();
423        let rhs = rhs.into_raw();
424
425        #[cfg(pmull)]
426        {
427            Flat::from_raw(neon::mul_flat_64(lhs, rhs))
428        }
429
430        #[cfg(not(pmull))]
431        {
432            let a_tower = Self::from_hardware(Flat::from_raw(lhs));
433            let b_tower = Self::from_hardware(Flat::from_raw(rhs));
434
435            (a_tower * b_tower).to_hardware()
436        }
437    }
438
439    #[inline(always)]
440    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
441        let lhs = lhs.into_raw();
442        let rhs = rhs.into_raw();
443
444        #[cfg(pmull)]
445        {
446            PackedFlat::from_raw(neon::mul_flat_packed_64(lhs, rhs))
447        }
448
449        #[cfg(not(pmull))]
450        {
451            let mut l = [Self::ZERO; <Self as PackableField>::WIDTH];
452            let mut r = [Self::ZERO; <Self as PackableField>::WIDTH];
453            let mut res = [Self::ZERO; <Self as PackableField>::WIDTH];
454
455            Self::unpack(lhs, &mut l);
456            Self::unpack(rhs, &mut r);
457
458            for i in 0..<Self as PackableField>::WIDTH {
459                res[i] = Self::mul_hardware(Flat::from_raw(l[i]), Flat::from_raw(r[i])).into_raw();
460            }
461
462            PackedFlat::from_raw(Self::pack(&res))
463        }
464    }
465
466    #[inline(always)]
467    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
468        let broadcasted = PackedBlock64([rhs.into_raw(); PACKED_WIDTH_64]);
469        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
470    }
471
472    #[inline(always)]
473    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
474        let mask = FLAT_TO_TOWER_BIT_MASKS_64[bit_idx];
475
476        // Parity of (x & mask) without popcount
477        // Folds 64 bits down to 4,
478        // then uses a lookup table.
479        let mut v = value.into_raw().0 & mask;
480        v ^= v >> 32;
481        v ^= v >> 16;
482        v ^= v >> 8;
483        v ^= v >> 4;
484
485        let idx = (v & 0xF) as u8;
486
487        // Nibble parity lookup encoded
488        // in a 16-bit constant (0x6996).
489        ((0x6996u16 >> idx) & 1) as u8
490    }
491}
492
493impl FlatPromote<Block8> for Block64 {
494    #[inline(always)]
495    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
496        let val = val.into_raw();
497
498        #[cfg(not(feature = "table-math"))]
499        {
500            let mut acc = 0u64;
501            for i in 0..8 {
502                let bit = (val.0 >> i) & 1;
503                let mask = 0u64.wrapping_sub(bit as u64);
504                acc ^= constants::LIFT_BASIS_8_TO_64[i] & mask;
505            }
506
507            Flat::from_raw(Block64(acc))
508        }
509
510        #[cfg(feature = "table-math")]
511        {
512            Flat::from_raw(Block64(constants::LIFT_TABLE_8_TO_64[val.0 as usize]))
513        }
514    }
515}
516
517// ===========================================
518// Binary Field Extras
519// ===========================================
520
521impl_binary_field_extras!(
522    Block64,
523    Block32,
524    map_ct_64,
525    TRACE_MASK_64,
526    SOLVE_QUADRATIC_BASIS_64
527);
528
529// ===========================================
530// UTILS
531// ===========================================
532
533#[cfg(pmull)]
534#[inline(always)]
535pub fn mul_iso_64(a: Block64, b: Block64) -> Block64 {
536    let a_flat = a.to_hardware();
537    let b_flat = b.to_hardware();
538
539    let c_flat = Flat::from_raw(neon::mul_flat_64(a_flat.into_raw(), b_flat.into_raw()));
540
541    c_flat.to_tower()
542}
543
544#[cfg(feature = "table-math")]
545#[inline(always)]
546pub fn apply_matrix_64(val: Block64, table: &[u64; 2048]) -> Block64 {
547    let mut res = 0u64;
548    let v = val.0;
549
550    // 8 lookups (8-bit window)
551    for i in 0..8 {
552        let byte = (v >> (i * 8)) & 0xFF;
553        let idx = (i * 256) + (byte as usize);
554        res ^= unsafe { *table.get_unchecked(idx) };
555    }
556
557    Block64(res)
558}
559
560#[inline(always)]
561fn map_ct_64(x: u64, basis: &[u64; 64]) -> u64 {
562    let mut acc = 0u64;
563    let mut i = 0usize;
564
565    while i < 64 {
566        let bit = (x >> i) & 1;
567        let mask = 0u64.wrapping_sub(bit);
568        acc ^= basis[i] & mask;
569        i += 1;
570    }
571
572    acc
573}
574
575// ===========================================
576// SIMD INSTRUCTIONS
577// ===========================================
578
579#[cfg(target_arch = "aarch64")]
580mod neon {
581    use super::*;
582    use core::arch::aarch64::*;
583    use core::mem::transmute;
584
585    const _: () = assert!(constants::POLY_64 == 0x1b, "verus twins hardcode R = 0x1b");
586
587    #[inline(always)]
588    pub fn add_packed_64(lhs: PackedBlock64, rhs: PackedBlock64) -> PackedBlock64 {
589        unsafe {
590            let l: uint8x16_t = transmute::<[Block64; PACKED_WIDTH_64], uint8x16_t>(lhs.0);
591            let r: uint8x16_t = transmute::<[Block64; PACKED_WIDTH_64], uint8x16_t>(rhs.0);
592            let res = veorq_u8(l, r);
593            let out: [Block64; PACKED_WIDTH_64] =
594                transmute::<uint8x16_t, [Block64; PACKED_WIDTH_64]>(res);
595
596            PackedBlock64(out)
597        }
598    }
599
600    #[cfg(pmull)]
601    #[inline(always)]
602    pub fn mul_flat_packed_64(lhs: PackedBlock64, rhs: PackedBlock64) -> PackedBlock64 {
603        unsafe {
604            let ap: poly64x2_t = transmute(lhs.0);
605            let bp: poly64x2_t = transmute(rhs.0);
606
607            // vdupq_n_p64, not [c; 2]: the repeat
608            // form lowers to a memcpy libcall.
609            let rv: poly64x2_t = vdupq_n_p64(constants::POLY_64);
610
611            // Both lanes per stage: PMULL/PMULL2 pairs,
612            // uzp1/uzp2 regroup [lo, hi] products by lane.
613            let p0: uint64x2_t =
614                transmute(vmull_p64(vgetq_lane_p64::<0>(ap), vgetq_lane_p64::<0>(bp)));
615            let p1: uint64x2_t = transmute(vmull_high_p64(ap, bp));
616
617            let los = vuzp1q_u64(p0, p1);
618            let his = vuzp2q_u64(p0, p1);
619            let hisp: poly64x2_t = transmute(his);
620
621            let hr0: uint64x2_t = transmute(vmull_p64(
622                vgetq_lane_p64::<0>(hisp),
623                vgetq_lane_p64::<0>(rv),
624            ));
625            let hr1: uint64x2_t = transmute(vmull_high_p64(hisp, rv));
626
627            let folded = vuzp1q_u64(hr0, hr1);
628            let carries = vuzp2q_u64(hr0, hr1);
629            let cp: poly64x2_t = transmute(carries);
630
631            let cr0: uint64x2_t =
632                transmute(vmull_p64(vgetq_lane_p64::<0>(cp), vgetq_lane_p64::<0>(rv)));
633            let cr1: uint64x2_t = transmute(vmull_high_p64(cp, rv));
634            let carry_red = vuzp1q_u64(cr0, cr1);
635
636            let res = veorq_u64(veorq_u64(los, folded), carry_red);
637
638            PackedBlock64(transmute::<uint64x2_t, [Block64; 2]>(res))
639        }
640    }
641
642    #[cfg(pmull)]
643    #[inline(always)]
644    pub fn mul_flat_64(a: Block64, b: Block64) -> Block64 {
645        unsafe {
646            // vdupq_n_p64, not [c; 2]: the repeat
647            // form lowers to a memcpy libcall.
648            let rv: poly64x2_t = vdupq_n_p64(constants::POLY_64);
649
650            // Reduction P(x) = x^64 + R(x), R(x) = 0x1b
651            let prod: uint8x16_t = transmute(vmull_p64(a.0, b.0));
652            let h_red: uint8x16_t = transmute(vmull_high_p64(
653                transmute::<uint8x16_t, poly64x2_t>(prod),
654                rv,
655            ));
656
657            // carry = hi lane of h_red < 2^3;
658            // deg(carry·R) < 7: no third fold.
659            let c_red: uint8x16_t = transmute(vmull_high_p64(
660                transmute::<uint8x16_t, poly64x2_t>(h_red),
661                rv,
662            ));
663
664            let res = veorq_u8(veorq_u8(prod, h_red), c_red);
665
666            Block64(vgetq_lane_u64(vreinterpretq_u64_u8(res), 0))
667        }
668    }
669}
670
671// ==================================
672// BLOCK 64 TESTS
673// ==================================
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678    use proptest::prelude::*;
679    use rand::{RngExt, rng};
680
681    // ==================================
682    // BASIC
683    // ==================================
684
685    #[test]
686    fn tower_constants() {
687        // Check that tau is propagated correctly
688        // For Block64, tau must be (0, 1) from Block32.
689        let tau64 = Block64::EXTENSION_TAU;
690        let (lo64, hi64) = tau64.split();
691        assert_eq!(lo64, Block32::ZERO);
692        assert_eq!(hi64, Block32::TAU);
693    }
694
695    #[test]
696    fn add_truth() {
697        let zero = Block64::ZERO;
698        let one = Block64::ONE;
699
700        assert_eq!(zero + zero, zero);
701        assert_eq!(zero + one, one);
702        assert_eq!(one + zero, one);
703        assert_eq!(one + one, zero);
704    }
705
706    #[test]
707    fn mul_truth() {
708        let zero = Block64::ZERO;
709        let one = Block64::ONE;
710
711        assert_eq!(zero * zero, zero);
712        assert_eq!(zero * one, zero);
713        assert_eq!(one * one, one);
714    }
715
716    #[test]
717    fn add() {
718        // 5 ^ 3 = 6
719        // 101 ^ 011 = 110
720        assert_eq!(Block64(5) + Block64(3), Block64(6));
721    }
722
723    #[test]
724    fn mul_simple() {
725        // Check for prime numbers (without overflow)
726        // x^1 * x^1 = x^2 (2 * 2 = 4)
727        assert_eq!(Block64(2) * Block64(2), Block64(4));
728    }
729
730    #[test]
731    fn mul_overflow() {
732        // Reduction verification (AES test vectors)
733        // Example from the AES specification:
734        // 0x57 * 0x83 = 0xC1
735        assert_eq!(Block64(0x57) * Block64(0x83), Block64(0xC1));
736    }
737
738    #[test]
739    fn karatsuba_correctness() {
740        // Let's check using Block64 as an example
741        // Let A = X (hi=1, lo=0)
742        // Let B = X (hi=1, lo=0)
743        // A * B = X^2
744        // According to the rule:
745        // X^2 = X + tau
746        // Where tau for Block32 = 0x2000_0000.
747        // So the result should be:
748        // hi=1 (X), lo=0x20 (tau)
749
750        // Construct X manually
751        let x = Block64::new(Block32::ZERO, Block32::ONE);
752        let squared = x * x;
753
754        // Verify result via splitting
755        let (res_lo, res_hi) = squared.split();
756
757        assert_eq!(res_hi, Block32::ONE, "X^2 should contain X component");
758        assert_eq!(
759            res_lo,
760            Block32(0x2000_0000),
761            "X^2 should contain tau component (0x2000_0000)"
762        );
763    }
764
765    #[test]
766    fn security_zeroize() {
767        let mut secret_val = Block64::from(0xDEAD_BEEF_CAFE_BABE_u64);
768        assert_ne!(secret_val, Block64::ZERO);
769
770        secret_val.zeroize();
771
772        assert_eq!(secret_val, Block64::ZERO);
773        assert_eq!(secret_val.0, 0, "Block64 memory leak detected");
774    }
775
776    #[test]
777    fn invert_zero() {
778        // Zero check
779        assert_eq!(
780            Block64::ZERO.invert(),
781            Block64::ZERO,
782            "invert(0) must return 0"
783        );
784    }
785
786    #[test]
787    fn inversion_random() {
788        let mut rng = rng();
789        for _ in 0..1000 {
790            let val = Block64(rng.random());
791            if val != Block64::ZERO {
792                let inv = val.invert();
793                assert_eq!(
794                    val * inv,
795                    Block64::ONE,
796                    "Inversion identity failed: a * a^-1 != 1"
797                );
798            }
799        }
800    }
801
802    #[test]
803    fn tower_embedding() {
804        let mut rng = rng();
805        for _ in 0..100 {
806            let a = Block32(rng.random());
807            let b = Block32(rng.random());
808
809            // 1. Structure check
810            let a_lifted: Block64 = a.into();
811            let (lo, hi) = a_lifted.split();
812
813            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
814            assert_eq!(
815                hi,
816                Block32::ZERO,
817                "Embedding structure failed: high part must be zero"
818            );
819
820            // 2. Addition Homomorphism
821            let sum_sub = a + b;
822            let sum_lifted: Block64 = sum_sub.into();
823            let sum_in_super = Block64::from(a) + Block64::from(b);
824
825            assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
826
827            // 3. Multiplication Homomorphism
828            let prod_sub = a * b;
829            let prod_lifted: Block64 = prod_sub.into();
830            let prod_in_super = Block64::from(a) * Block64::from(b);
831
832            assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
833        }
834    }
835
836    // ==================================
837    // HARDWARE
838    // ==================================
839
840    #[test]
841    fn isomorphism_roundtrip() {
842        let mut rng = rng();
843        for _ in 0..1000 {
844            let val = Block64(rng.random::<u64>());
845            assert_eq!(val.to_hardware().to_tower(), val);
846        }
847    }
848
849    #[test]
850    fn flat_mul_homomorphism() {
851        let mut rng = rng();
852        for _ in 0..1000 {
853            let a = Block64(rng.random());
854            let b = Block64(rng.random());
855
856            let expected_flat = (a * b).to_hardware();
857            let actual_flat = a.to_hardware() * b.to_hardware();
858
859            assert_eq!(
860                actual_flat, expected_flat,
861                "Block64 flat multiplication mismatch: (a*b)^H != a^H * b^H"
862            );
863        }
864    }
865
866    #[test]
867    fn packed_consistency() {
868        let mut rng = rng();
869        for _ in 0..100 {
870            let a_vals = [Block64(rng.random()), Block64(rng.random())];
871            let b_vals = [Block64(rng.random()), Block64(rng.random())];
872
873            let a_flat_vals = a_vals.map(|x| x.to_hardware());
874            let b_flat_vals = b_vals.map(|x| x.to_hardware());
875            let a_packed = Flat::<Block64>::pack(&a_flat_vals);
876            let b_packed = Flat::<Block64>::pack(&b_flat_vals);
877
878            // 1. Test SIMD Add (XOR)
879            let add_res = Block64::add_hardware_packed(a_packed, b_packed);
880
881            let mut add_out = [Block64::ZERO.to_hardware(); 2];
882            Flat::<Block64>::unpack(add_res, &mut add_out);
883
884            assert_eq!(add_out[0], (a_vals[0] + b_vals[0]).to_hardware());
885            assert_eq!(add_out[1], (a_vals[1] + b_vals[1]).to_hardware());
886
887            // 2. Test SIMD Mul (Isomorphic/Flat basis)
888            let mul_res = Block64::mul_hardware_packed(a_packed, b_packed);
889
890            let mut mul_out = [Block64::ZERO.to_hardware(); 2];
891            Flat::<Block64>::unpack(mul_res, &mut mul_out);
892
893            assert_eq!(
894                mul_out[0],
895                (a_vals[0] * b_vals[0]).to_hardware(),
896                "Block64 SIMD mul mismatch at index 0"
897            );
898            assert_eq!(
899                mul_out[1],
900                (a_vals[1] * b_vals[1]).to_hardware(),
901                "Block64 SIMD mul mismatch at index 1"
902            );
903        }
904    }
905
906    // ==================================
907    // PACKED
908    // ==================================
909
910    #[test]
911    fn pack_unpack_roundtrip() {
912        let mut rng = rng();
913        let data = [Block64(rng.random()), Block64(rng.random())];
914
915        let packed = Block64::pack(&data);
916        let mut unpacked = [Block64::ZERO; 2];
917
918        Block64::unpack(packed, &mut unpacked);
919        assert_eq!(data, unpacked);
920    }
921
922    #[test]
923    fn packed_add_consistency() {
924        let mut rng = rng();
925        let a_vals = [Block64(rng.random()), Block64(rng.random())];
926        let b_vals = [Block64(rng.random()), Block64(rng.random())];
927
928        let res_packed = Block64::pack(&a_vals) + Block64::pack(&b_vals);
929        let mut res_unpacked = [Block64::ZERO; 2];
930        Block64::unpack(res_packed, &mut res_unpacked);
931
932        assert_eq!(res_unpacked[0], a_vals[0] + b_vals[0]);
933        assert_eq!(res_unpacked[1], a_vals[1] + b_vals[1]);
934    }
935
936    #[test]
937    fn packed_mul_consistency() {
938        let mut rng = rng();
939
940        for _ in 0..1000 {
941            let mut a_arr = [Block64::ZERO; PACKED_WIDTH_64];
942            let mut b_arr = [Block64::ZERO; PACKED_WIDTH_64];
943
944            for i in 0..PACKED_WIDTH_64 {
945                let val_a: u64 = rng.random();
946                let val_b: u64 = rng.random();
947                a_arr[i] = Block64(val_a);
948                b_arr[i] = Block64(val_b);
949            }
950
951            let a_packed = PackedBlock64(a_arr);
952            let b_packed = PackedBlock64(b_arr);
953
954            // Perform SIMD multiplication
955            let c_packed = a_packed * b_packed;
956
957            // Verify against Scalar
958            let mut c_expected = [Block64::ZERO; PACKED_WIDTH_64];
959            for i in 0..PACKED_WIDTH_64 {
960                c_expected[i] = a_arr[i] * b_arr[i];
961            }
962
963            assert_eq!(c_packed.0, c_expected, "SIMD Block64 mismatch!");
964        }
965    }
966
967    proptest! {
968        #[test]
969        fn parity_masks_match_from_hardware(x_flat in any::<u64>()) {
970            let tower = Block64::from_hardware(Flat::from_raw(Block64(x_flat))).0;
971
972            for (k, &mask) in FLAT_TO_TOWER_BIT_MASKS_64.iter().enumerate() {
973                // Ensure the static masks
974                // themselves are correct.
975                let parity = ((x_flat & mask).count_ones() & 1) as u8;
976                let bit = ((tower >> k) & 1) as u8;
977                prop_assert_eq!(parity, bit, "Block64 static mask mismatch at k={}", k);
978
979                // Ensure XOR-tree implementation matches.
980                let via_api = Flat::from_raw(Block64(x_flat)).tower_bit(k);
981                prop_assert_eq!(
982                    via_api, bit,
983                    "Block64 tower_bit_from_hardware mismatch at x_flat={:#018x}, bit_idx={}",
984                    x_flat, k
985                );
986            }
987        }
988    }
989}