Skip to main content

hekate_math/towers/
block256.rs

1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate-math project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! GF(2^256) as `Block128[X] / (X^2 + X + τ)`, its flat form as a pair
19//! of GF(2^128) flat elements, and scalar packed lanes (no SIMD kernel).
20
21use crate::{BinaryFieldExtras, Bit, Block8, Block16, Block32, Block64, Block128};
22use crate::{
23    CanonicalDeserialize, CanonicalSerialize, Flat, FlatPromote, HardwareField, PackableField,
24    PackedFlat, TowerField,
25};
26use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
27use serde::{Deserialize, Serialize};
28use zeroize::Zeroize;
29
30// Flat<Block256> = Flat<Block128>[y] / (y² + y + τ_flat).
31// τ_flat = to_hardware(Block128::EXTENSION_TAU).
32const TAU_FLAT: u128 = 0x66340c45203fe3685d08f8c248334a81;
33
34/// GF(2^256) in the tower basis: `lo + hi·X`
35/// over [`Block128`], stored as `[lo, hi]`.
36#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Zeroize)]
37#[repr(C, align(32))]
38pub struct Block256(pub [u128; 2]);
39
40impl Block256 {
41    const TAU: Self = Block256([0, 0x2000_0000_0000_0000_0000_0000_0000_0000]);
42
43    /// Builds `lo + hi·X` from its halves.
44    pub fn new(lo: Block128, hi: Block128) -> Self {
45        Self([lo.0, hi.0])
46    }
47
48    /// Returns the halves `(lo, hi)` of `lo + hi·X`.
49    #[inline(always)]
50    pub fn split(self) -> (Block128, Block128) {
51        (Block128(self.0[0]), Block128(self.0[1]))
52    }
53}
54
55impl TowerField for Block256 {
56    const BITS: usize = 256;
57    const ZERO: Self = Block256([0, 0]);
58    const ONE: Self = Block256([1, 0]);
59
60    const EXTENSION_TAU: Self = Self::TAU;
61
62    fn invert(&self) -> Self {
63        let (l, h) = self.split();
64
65        let norm = h.square().mul_tau() + h * l + l.square();
66        let norm_inv = norm.invert();
67
68        let res_hi = h * norm_inv;
69        let res_lo = (h + l) * norm_inv;
70
71        Self::new(res_lo, res_hi)
72    }
73
74    fn from_uniform_bytes(bytes: &[u8; 32]) -> Self {
75        let mut lo_buf = [0u8; 16];
76        let mut hi_buf = [0u8; 16];
77
78        lo_buf.copy_from_slice(&bytes[0..16]);
79        hi_buf.copy_from_slice(&bytes[16..32]);
80
81        Self([u128::from_le_bytes(lo_buf), u128::from_le_bytes(hi_buf)])
82    }
83}
84
85impl Add for Block256 {
86    type Output = Self;
87
88    fn add(self, rhs: Self) -> Self {
89        Self([self.0[0] ^ rhs.0[0], self.0[1] ^ rhs.0[1]])
90    }
91}
92
93impl Sub for Block256 {
94    type Output = Self;
95
96    fn sub(self, rhs: Self) -> Self {
97        self.add(rhs)
98    }
99}
100
101impl Mul for Block256 {
102    type Output = Self;
103
104    #[inline]
105    fn mul(self, rhs: Self) -> Self {
106        let (a0, a1) = self.split();
107        let (b0, b1) = rhs.split();
108
109        let v0 = a0 * b0;
110        let v1 = a1 * b1;
111        let vs = (a0 + a1) * (b0 + b1);
112
113        Self::new(v0 + v1.mul_tau(), v0 + vs)
114    }
115}
116
117impl AddAssign for Block256 {
118    fn add_assign(&mut self, rhs: Self) {
119        self.0[0] ^= rhs.0[0];
120        self.0[1] ^= rhs.0[1];
121    }
122}
123
124impl SubAssign for Block256 {
125    fn sub_assign(&mut self, rhs: Self) {
126        self.0[0] ^= rhs.0[0];
127        self.0[1] ^= rhs.0[1];
128    }
129}
130
131impl MulAssign for Block256 {
132    fn mul_assign(&mut self, rhs: Self) {
133        *self = *self * rhs;
134    }
135}
136
137impl CanonicalSerialize for Block256 {
138    fn serialized_size(&self) -> usize {
139        32
140    }
141
142    fn serialize(&self, writer: &mut [u8]) -> Result<(), ()> {
143        if writer.len() < 32 {
144            return Err(());
145        }
146
147        writer[0..16].copy_from_slice(&self.0[0].to_le_bytes());
148        writer[16..32].copy_from_slice(&self.0[1].to_le_bytes());
149
150        Ok(())
151    }
152}
153
154impl CanonicalDeserialize for Block256 {
155    fn deserialize(bytes: &[u8]) -> Result<Self, ()> {
156        if bytes.len() < 32 {
157            return Err(());
158        }
159
160        let mut lo_buf = [0u8; 16];
161        let mut hi_buf = [0u8; 16];
162
163        lo_buf.copy_from_slice(&bytes[0..16]);
164        hi_buf.copy_from_slice(&bytes[16..32]);
165
166        Ok(Self([
167            u128::from_le_bytes(lo_buf),
168            u128::from_le_bytes(hi_buf),
169        ]))
170    }
171}
172
173impl From<u8> for Block256 {
174    fn from(val: u8) -> Self {
175        Self([val as u128, 0])
176    }
177}
178
179impl From<u32> for Block256 {
180    #[inline]
181    fn from(val: u32) -> Self {
182        Self([val as u128, 0])
183    }
184}
185
186impl From<u64> for Block256 {
187    #[inline]
188    fn from(val: u64) -> Self {
189        Self([val as u128, 0])
190    }
191}
192
193impl From<u128> for Block256 {
194    #[inline]
195    fn from(val: u128) -> Self {
196        Self([val, 0])
197    }
198}
199
200impl From<Bit> for Block256 {
201    #[inline(always)]
202    fn from(val: Bit) -> Self {
203        Self([val.get() as u128, 0])
204    }
205}
206
207impl From<Block8> for Block256 {
208    #[inline(always)]
209    fn from(val: Block8) -> Self {
210        Self([val.0 as u128, 0])
211    }
212}
213
214impl From<Block16> for Block256 {
215    #[inline(always)]
216    fn from(val: Block16) -> Self {
217        Self([val.0 as u128, 0])
218    }
219}
220
221impl From<Block32> for Block256 {
222    #[inline(always)]
223    fn from(val: Block32) -> Self {
224        Self([val.0 as u128, 0])
225    }
226}
227
228impl From<Block64> for Block256 {
229    #[inline(always)]
230    fn from(val: Block64) -> Self {
231        Self([val.0 as u128, 0])
232    }
233}
234
235impl From<Block128> for Block256 {
236    #[inline(always)]
237    fn from(val: Block128) -> Self {
238        Self([val.0, 0])
239    }
240}
241
242// ===================================
243// PACKED BLOCK 256 (Width = 2)
244// ===================================
245
246/// Lanes in [`PackedBlock256`].
247pub const PACKED_WIDTH_256: usize = 2;
248
249/// Two [`Block256`] lanes; operators run the scalar operation per lane.
250#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
251#[repr(C, align(64))]
252pub struct PackedBlock256(pub [Block256; PACKED_WIDTH_256]);
253
254impl PackedBlock256 {
255    /// Returns the packed value with every lane zero.
256    #[inline(always)]
257    pub fn zero() -> Self {
258        Self([Block256::ZERO; PACKED_WIDTH_256])
259    }
260
261    /// Returns `val` in every lane.
262    #[inline(always)]
263    pub fn broadcast(val: Block256) -> Self {
264        Self([val; PACKED_WIDTH_256])
265    }
266}
267
268impl PackableField for Block256 {
269    type Packed = PackedBlock256;
270
271    const WIDTH: usize = PACKED_WIDTH_256;
272
273    #[inline(always)]
274    fn pack(chunk: &[Self]) -> Self::Packed {
275        assert!(
276            chunk.len() >= PACKED_WIDTH_256,
277            "PackableField::pack: input slice too short",
278        );
279
280        let mut arr = [Self::ZERO; PACKED_WIDTH_256];
281        arr.copy_from_slice(&chunk[..PACKED_WIDTH_256]);
282
283        PackedBlock256(arr)
284    }
285
286    #[inline(always)]
287    fn unpack(packed: Self::Packed, output: &mut [Self]) {
288        assert!(
289            output.len() >= PACKED_WIDTH_256,
290            "PackableField::unpack: output slice too short",
291        );
292
293        output[..PACKED_WIDTH_256].copy_from_slice(&packed.0);
294    }
295}
296
297impl Add for PackedBlock256 {
298    type Output = Self;
299
300    #[inline(always)]
301    fn add(self, rhs: Self) -> Self {
302        let mut res = [Block256::ZERO; PACKED_WIDTH_256];
303        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
304            *out = *l + *r;
305        }
306
307        Self(res)
308    }
309}
310
311impl AddAssign for PackedBlock256 {
312    #[inline(always)]
313    fn add_assign(&mut self, rhs: Self) {
314        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
315            *l += *r;
316        }
317    }
318}
319
320impl Sub for PackedBlock256 {
321    type Output = Self;
322
323    #[inline(always)]
324    fn sub(self, rhs: Self) -> Self {
325        self.add(rhs)
326    }
327}
328
329impl SubAssign for PackedBlock256 {
330    #[inline(always)]
331    fn sub_assign(&mut self, rhs: Self) {
332        self.add_assign(rhs);
333    }
334}
335
336impl Mul for PackedBlock256 {
337    type Output = Self;
338
339    #[inline(always)]
340    fn mul(self, rhs: Self) -> Self {
341        let mut res = [Block256::ZERO; PACKED_WIDTH_256];
342        for ((out, l), r) in res.iter_mut().zip(self.0.iter()).zip(rhs.0.iter()) {
343            *out = *l * *r;
344        }
345
346        Self(res)
347    }
348}
349
350impl MulAssign for PackedBlock256 {
351    #[inline(always)]
352    fn mul_assign(&mut self, rhs: Self) {
353        for (l, r) in self.0.iter_mut().zip(rhs.0.iter()) {
354            *l *= *r;
355        }
356    }
357}
358
359impl Mul<Block256> for PackedBlock256 {
360    type Output = Self;
361
362    #[inline(always)]
363    fn mul(self, rhs: Block256) -> Self {
364        let mut res = [Block256::ZERO; PACKED_WIDTH_256];
365        for (out, v) in res.iter_mut().zip(self.0.iter()) {
366            *out = *v * rhs;
367        }
368
369        Self(res)
370    }
371}
372
373impl MulAssign<Block256> for PackedBlock256 {
374    #[inline(always)]
375    fn mul_assign(&mut self, rhs: Block256) {
376        for v in self.0.iter_mut() {
377            *v *= rhs;
378        }
379    }
380}
381
382impl HardwareField for Block256 {
383    #[inline(always)]
384    fn to_hardware(self) -> Flat<Self> {
385        let (lo, hi) = self.split();
386        let flat_lo = lo.to_hardware().into_raw().0;
387        let flat_hi = hi.to_hardware().into_raw().0;
388
389        Flat::from_raw(Block256([flat_lo, flat_hi]))
390    }
391
392    #[inline(always)]
393    fn from_hardware(value: Flat<Self>) -> Self {
394        let raw = value.into_raw();
395        let lo = Block128::from_hardware(Flat::from_raw(Block128(raw.0[0])));
396        let hi = Block128::from_hardware(Flat::from_raw(Block128(raw.0[1])));
397
398        Self::new(lo, hi)
399    }
400
401    #[inline(always)]
402    fn add_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
403        let l = lhs.into_raw();
404        let r = rhs.into_raw();
405
406        let c_lo = Block128::add_hardware(
407            Flat::from_raw(Block128(l.0[0])),
408            Flat::from_raw(Block128(r.0[0])),
409        );
410        let c_hi = Block128::add_hardware(
411            Flat::from_raw(Block128(l.0[1])),
412            Flat::from_raw(Block128(r.0[1])),
413        );
414
415        Flat::from_raw(Block256([c_lo.into_raw().0, c_hi.into_raw().0]))
416    }
417
418    #[inline(always)]
419    fn add_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
420        let lhs = lhs.into_raw().0;
421        let rhs = rhs.into_raw().0;
422
423        let mut res = [Block256::ZERO; PACKED_WIDTH_256];
424        for i in 0..PACKED_WIDTH_256 {
425            res[i] = Self::add_hardware(Flat::from_raw(lhs[i]), Flat::from_raw(rhs[i])).into_raw();
426        }
427
428        PackedFlat::from_raw(PackedBlock256(res))
429    }
430
431    #[inline(always)]
432    fn mul_hardware(lhs: Flat<Self>, rhs: Flat<Self>) -> Flat<Self> {
433        let a_lo = Flat::from_raw(Block128(lhs.into_raw().0[0]));
434        let a_hi = Flat::from_raw(Block128(lhs.into_raw().0[1]));
435        let b_lo = Flat::from_raw(Block128(rhs.into_raw().0[0]));
436        let b_hi = Flat::from_raw(Block128(rhs.into_raw().0[1]));
437
438        let tau = Flat::from_raw(Block128(TAU_FLAT));
439
440        let v0 = Block128::mul_hardware(a_lo, b_lo);
441        let v1 = Block128::mul_hardware(a_hi, b_hi);
442
443        let a_sum = Block128::add_hardware(a_lo, a_hi);
444        let b_sum = Block128::add_hardware(b_lo, b_hi);
445        let v_sum = Block128::mul_hardware(a_sum, b_sum);
446
447        let c_hi = Block128::add_hardware(v0, v_sum);
448
449        let v1_tau = Block128::mul_hardware(v1, tau);
450        let c_lo = Block128::add_hardware(v0, v1_tau);
451
452        Flat::from_raw(Block256([c_lo.into_raw().0, c_hi.into_raw().0]))
453    }
454
455    #[inline(always)]
456    fn mul_hardware_packed(lhs: PackedFlat<Self>, rhs: PackedFlat<Self>) -> PackedFlat<Self> {
457        let lhs = lhs.into_raw().0;
458        let rhs = rhs.into_raw().0;
459
460        let mut res = [Block256::ZERO; PACKED_WIDTH_256];
461        for i in 0..PACKED_WIDTH_256 {
462            res[i] = Self::mul_hardware(Flat::from_raw(lhs[i]), Flat::from_raw(rhs[i])).into_raw();
463        }
464
465        PackedFlat::from_raw(PackedBlock256(res))
466    }
467
468    #[inline(always)]
469    fn mul_hardware_scalar_packed(lhs: PackedFlat<Self>, rhs: Flat<Self>) -> PackedFlat<Self> {
470        let broadcasted = PackedBlock256::broadcast(rhs.into_raw());
471        Self::mul_hardware_packed(lhs, PackedFlat::from_raw(broadcasted))
472    }
473
474    #[inline(always)]
475    fn tower_bit_from_hardware(value: Flat<Self>, bit_idx: usize) -> u8 {
476        if bit_idx < 128 {
477            Block128::tower_bit_from_hardware(
478                Flat::from_raw(Block128(value.into_raw().0[0])),
479                bit_idx,
480            )
481        } else {
482            Block128::tower_bit_from_hardware(
483                Flat::from_raw(Block128(value.into_raw().0[1])),
484                bit_idx - 128,
485            )
486        }
487    }
488}
489
490// ===========================================
491// Binary Field Extras
492// ===========================================
493
494impl BinaryFieldExtras for Block256 {
495    #[inline(always)]
496    fn square(&self) -> Self {
497        // char 2:
498        // (lo + hi·X)^2 = lo^2 + hi^2·X^2,
499        // no cross term.
500        let (lo, hi) = self.split();
501        let hi2 = hi.square();
502
503        Self::new(lo.square() + hi2.mul_tau(), hi2)
504    }
505
506    #[inline(always)]
507    fn trace(&self) -> Bit {
508        // Tr_{256/128}(lo + hi·X) = hi:
509        // the conjugate of X is X + 1.
510        self.split().1.trace()
511    }
512
513    #[inline(always)]
514    fn solve_quadratic(c: Self) -> Option<Self> {
515        // xh^2 + xh = ch, then xl^2 + xl = cl + xh^2·τ.
516        // Tr_128(τ) = 1: exactly one root xh leaves
517        // the second equation solvable.
518        let (cl, ch) = c.split();
519        let r = Block128::solve_quadratic(ch)?;
520
521        let d = cl + r.square().mul_tau();
522        let flip = 0u128.wrapping_sub(d.trace().get() as u128);
523
524        let xh = Block128(r.0 ^ (flip & 1));
525        let d = Block128(d.0 ^ (flip & Block128::EXTENSION_TAU.0));
526
527        let xl = Block128::solve_quadratic(d)?;
528
529        Some(Self::new(xl, xh))
530    }
531}
532
533const PROMOTE_CHUNK: usize = 64;
534
535impl FlatPromote<Block8> for Block256 {
536    #[inline(always)]
537    fn promote_flat(val: Flat<Block8>) -> Flat<Self> {
538        let promoted = Block128::promote_flat(val);
539        Flat::from_raw(Block256([promoted.into_raw().0, 0]))
540    }
541
542    fn promote_flat_batch(input: &[Flat<Block8>], output: &mut [Flat<Self>]) {
543        promote_chunked(input, output);
544    }
545}
546
547impl FlatPromote<Block16> for Block256 {
548    #[inline(always)]
549    fn promote_flat(val: Flat<Block16>) -> Flat<Self> {
550        let promoted = Block128::promote_flat(val);
551        Flat::from_raw(Block256([promoted.into_raw().0, 0]))
552    }
553
554    fn promote_flat_batch(input: &[Flat<Block16>], output: &mut [Flat<Self>]) {
555        promote_chunked(input, output);
556    }
557}
558
559impl FlatPromote<Block32> for Block256 {
560    #[inline(always)]
561    fn promote_flat(val: Flat<Block32>) -> Flat<Self> {
562        let promoted = Block128::promote_flat(val);
563        Flat::from_raw(Block256([promoted.into_raw().0, 0]))
564    }
565
566    fn promote_flat_batch(input: &[Flat<Block32>], output: &mut [Flat<Self>]) {
567        promote_chunked(input, output);
568    }
569}
570
571impl FlatPromote<Block64> for Block256 {
572    #[inline(always)]
573    fn promote_flat(val: Flat<Block64>) -> Flat<Self> {
574        let promoted = Block128::promote_flat(val);
575        Flat::from_raw(Block256([promoted.into_raw().0, 0]))
576    }
577
578    fn promote_flat_batch(input: &[Flat<Block64>], output: &mut [Flat<Self>]) {
579        promote_chunked(input, output);
580    }
581}
582
583impl FlatPromote<Block128> for Block256 {
584    #[inline(always)]
585    fn promote_flat(val: Flat<Block128>) -> Flat<Self> {
586        Flat::from_raw(Block256([val.into_raw().0, 0]))
587    }
588
589    fn promote_flat_batch(input: &[Flat<Block128>], output: &mut [Flat<Self>]) {
590        let n = input.len().min(output.len());
591        for i in 0..n {
592            output[i] = Flat::from_raw(Block256([input[i].into_raw().0, 0]));
593        }
594    }
595}
596
597#[inline(always)]
598fn promote_chunked<FromF>(input: &[Flat<FromF>], output: &mut [Flat<Block256>])
599where
600    FromF: HardwareField,
601    Block128: FlatPromote<FromF>,
602{
603    let n = input.len().min(output.len());
604
605    let mut scratch = [Flat::from_raw(Block128::ZERO); PROMOTE_CHUNK];
606    let mut i = 0;
607
608    while i < n {
609        let len = (n - i).min(PROMOTE_CHUNK);
610        Block128::promote_flat_batch(&input[i..i + len], &mut scratch[..len]);
611
612        for j in 0..len {
613            output[i + j] = Flat::from_raw(Block256([scratch[j].into_raw().0, 0]));
614        }
615
616        i += len;
617    }
618}
619
620#[cfg(test)]
621mod tests {
622    use super::*;
623    use rand::{RngExt, rng};
624
625    #[test]
626    fn tau_flat_matches_derived() {
627        let derived = Block128::EXTENSION_TAU.to_hardware().into_raw().0;
628        assert_eq!(
629            TAU_FLAT, derived,
630            "TAU_FLAT drifted from Block128::EXTENSION_TAU.to_hardware()",
631        );
632    }
633
634    // ==================================
635    // BASIC
636    // ==================================
637
638    #[test]
639    fn tower_constants() {
640        // Check that tau is propagated correctly
641        // For Block256, tau must be (0, EXTENSION_TAU) from Block128.
642        let tau256 = Block256::EXTENSION_TAU;
643        let (lo256, hi256) = tau256.split();
644        assert_eq!(lo256, Block128::ZERO);
645        assert_eq!(hi256, Block128::EXTENSION_TAU);
646    }
647
648    #[test]
649    fn add_truth() {
650        let zero = Block256::ZERO;
651        let one = Block256::ONE;
652
653        assert_eq!(zero + zero, zero);
654        assert_eq!(zero + one, one);
655        assert_eq!(one + zero, one);
656        assert_eq!(one + one, zero);
657    }
658
659    #[test]
660    fn mul_truth() {
661        let zero = Block256::ZERO;
662        let one = Block256::ONE;
663
664        assert_eq!(zero * zero, zero);
665        assert_eq!(zero * one, zero);
666        assert_eq!(one * one, one);
667    }
668
669    #[test]
670    fn add() {
671        // 5 ^ 3 = 6
672        // 101 ^ 011 = 110
673        assert_eq!(Block256([5, 0]) + Block256([3, 0]), Block256([6, 0]));
674    }
675
676    #[test]
677    fn mul_simple() {
678        // x^1 * x^1 = x^2 (2 * 2 = 4) inside the Block8 subfield
679        assert_eq!(
680            Block256::from(2u32) * Block256::from(2u32),
681            Block256::from(4u32)
682        );
683    }
684
685    #[test]
686    fn mul_overflow() {
687        // AES reduction: 0x57 * 0x83 = 0xC1 inside the Block8 subfield
688        assert_eq!(
689            Block256::from(0x57u32) * Block256::from(0x83u32),
690            Block256::from(0xC1u32)
691        );
692    }
693
694    #[test]
695    fn karatsuba_correctness() {
696        // Y = (hi=ONE, lo=ZERO). Y^2 = Y + tau_256.
697        // So the result must be:
698        // hi = Block128::ONE (the Y component),
699        // lo = Block128::EXTENSION_TAU (the tau component).
700        let y = Block256::new(Block128::ZERO, Block128::ONE);
701        let squared = y * y;
702
703        let (res_lo, res_hi) = squared.split();
704
705        assert_eq!(res_hi, Block128::ONE, "Y^2 should contain Y component");
706        assert_eq!(
707            res_lo,
708            Block128::EXTENSION_TAU,
709            "Y^2 should contain tau_256 component"
710        );
711    }
712
713    #[test]
714    fn security_zeroize() {
715        let mut secret_val = Block256([0xDEAD_BEEF_CAFE_BABE_u128, 0xFEED_FACE_BAAD_F00D_u128]);
716        assert_ne!(secret_val, Block256::ZERO);
717
718        secret_val.zeroize();
719
720        assert_eq!(secret_val, Block256::ZERO, "Memory was not wiped!");
721        assert_eq!(
722            secret_val.0,
723            [0u128, 0u128],
724            "Underlying memory leak detected"
725        );
726    }
727
728    #[test]
729    fn invert_zero() {
730        assert_eq!(
731            Block256::ZERO.invert(),
732            Block256::ZERO,
733            "invert(0) must return 0"
734        );
735    }
736
737    #[test]
738    fn inversion_random() {
739        let mut rng = rng();
740        for _ in 0..1000 {
741            let val = Block256([rng.random(), rng.random()]);
742
743            if val != Block256::ZERO {
744                let inv = val.invert();
745                let identity = val * inv;
746
747                assert_eq!(
748                    identity,
749                    Block256::ONE,
750                    "Inversion identity failed: a * a^-1 != 1"
751                );
752            }
753        }
754    }
755
756    #[test]
757    fn tower_embedding() {
758        let mut rng = rng();
759        for _ in 0..100 {
760            let a = Block128(rng.random());
761            let b = Block128(rng.random());
762
763            // 1. Structure:
764            // Block128 -> Block256
765            let a_lifted: Block256 = a.into();
766            let (lo, hi) = a_lifted.split();
767
768            assert_eq!(lo, a, "Embedding structure failed: low part mismatch");
769            assert_eq!(
770                hi,
771                Block128::ZERO,
772                "Embedding structure failed: high part must be zero"
773            );
774
775            // 2. Addition Homomorphism
776            let sum_sub = a + b;
777            let sum_lifted: Block256 = sum_sub.into();
778            let sum_in_super = Block256::from(a) + Block256::from(b);
779
780            assert_eq!(sum_lifted, sum_in_super, "Homomorphism failed: add");
781
782            // 3. Multiplication Homomorphism
783            let prod_sub = a * b;
784            let prod_lifted: Block256 = prod_sub.into();
785            let prod_in_super = Block256::from(a) * Block256::from(b);
786
787            assert_eq!(prod_lifted, prod_in_super, "Homomorphism failed: mul");
788        }
789    }
790
791    // ==================================
792    // HARDWARE
793    // ==================================
794
795    #[test]
796    fn isomorphism_roundtrip() {
797        let mut rng = rng();
798        for _ in 0..1000 {
799            let val = Block256([rng.random::<u128>(), rng.random::<u128>()]);
800            assert_eq!(val.to_hardware().to_tower(), val);
801        }
802    }
803
804    #[test]
805    fn flat_mul_homomorphism() {
806        let mut rng = rng();
807        for _ in 0..1000 {
808            let a = Block256([rng.random(), rng.random()]);
809            let b = Block256([rng.random(), rng.random()]);
810
811            let expected_flat = (a * b).to_hardware();
812            let actual_flat = a.to_hardware() * b.to_hardware();
813
814            assert_eq!(
815                actual_flat, expected_flat,
816                "Block256 flat multiplication mismatch: (a*b)^H != a^H * b^H"
817            );
818        }
819    }
820
821    #[test]
822    fn packed_consistency() {
823        let mut rng = rng();
824        for _ in 0..100 {
825            let mut a_vals = [Block256::ZERO; PACKED_WIDTH_256];
826            let mut b_vals = [Block256::ZERO; PACKED_WIDTH_256];
827
828            for i in 0..PACKED_WIDTH_256 {
829                a_vals[i] = Block256([rng.random::<u128>(), rng.random::<u128>()]);
830                b_vals[i] = Block256([rng.random::<u128>(), rng.random::<u128>()]);
831            }
832
833            let a_flat_vals = a_vals.map(|x| x.to_hardware());
834            let b_flat_vals = b_vals.map(|x| x.to_hardware());
835            let a_packed = Flat::<Block256>::pack(&a_flat_vals);
836            let b_packed = Flat::<Block256>::pack(&b_flat_vals);
837
838            let add_res = Block256::add_hardware_packed(a_packed, b_packed);
839
840            let mut add_out = [Block256::ZERO.to_hardware(); PACKED_WIDTH_256];
841            Flat::<Block256>::unpack(add_res, &mut add_out);
842
843            for i in 0..PACKED_WIDTH_256 {
844                assert_eq!(
845                    add_out[i],
846                    (a_vals[i] + b_vals[i]).to_hardware(),
847                    "Block256 SIMD add mismatch at index {}",
848                    i
849                );
850            }
851
852            let mul_res = Block256::mul_hardware_packed(a_packed, b_packed);
853
854            let mut mul_out = [Block256::ZERO.to_hardware(); PACKED_WIDTH_256];
855            Flat::<Block256>::unpack(mul_res, &mut mul_out);
856
857            for i in 0..PACKED_WIDTH_256 {
858                let expected_flat = (a_vals[i] * b_vals[i]).to_hardware();
859                assert_eq!(
860                    mul_out[i], expected_flat,
861                    "Block256 SIMD mul mismatch at index {}",
862                    i
863                );
864            }
865        }
866    }
867
868    #[test]
869    fn tower_bit_from_hardware_matches_tower() {
870        let mut rng = rng();
871        for _ in 0..64 {
872            let val = Block256([rng.random::<u128>(), rng.random::<u128>()]);
873            let flat = val.to_hardware();
874
875            for bit in 0..Block256::BITS {
876                let expected = if bit < 128 {
877                    ((val.0[0] >> bit) & 1) as u8
878                } else {
879                    ((val.0[1] >> (bit - 128)) & 1) as u8
880                };
881
882                assert_eq!(
883                    Block256::tower_bit_from_hardware(flat, bit),
884                    expected,
885                    "tower_bit mismatch at bit {}",
886                    bit
887                );
888            }
889        }
890    }
891
892    // ==================================
893    // PROMOTE
894    // ==================================
895
896    #[test]
897    fn promote_flat_batch_matches_scalar_block8() {
898        let mut rng = rng();
899        let input: Vec<Flat<Block8>> = (0..200)
900            .map(|_| Block8(rng.random::<u8>()).to_hardware())
901            .collect();
902
903        let mut batch_out = vec![Flat::from_raw(Block256::ZERO); input.len()];
904        <Block256 as FlatPromote<Block8>>::promote_flat_batch(&input, &mut batch_out);
905
906        for i in 0..input.len() {
907            let scalar = <Block256 as FlatPromote<Block8>>::promote_flat(input[i]);
908            assert_eq!(
909                batch_out[i], scalar,
910                "Block8 batch/scalar mismatch at {}",
911                i
912            );
913        }
914    }
915
916    #[test]
917    fn promote_flat_batch_matches_scalar_block16() {
918        let mut rng = rng();
919        let input: Vec<Flat<Block16>> = (0..200)
920            .map(|_| Block16(rng.random::<u16>()).to_hardware())
921            .collect();
922
923        let mut batch_out = vec![Flat::from_raw(Block256::ZERO); input.len()];
924        <Block256 as FlatPromote<Block16>>::promote_flat_batch(&input, &mut batch_out);
925
926        for i in 0..input.len() {
927            let scalar = <Block256 as FlatPromote<Block16>>::promote_flat(input[i]);
928            assert_eq!(
929                batch_out[i], scalar,
930                "Block16 batch/scalar mismatch at {}",
931                i
932            );
933        }
934    }
935
936    #[test]
937    fn promote_flat_batch_matches_scalar_block32() {
938        let mut rng = rng();
939        let input: Vec<Flat<Block32>> = (0..200)
940            .map(|_| Block32(rng.random::<u32>()).to_hardware())
941            .collect();
942
943        let mut batch_out = vec![Flat::from_raw(Block256::ZERO); input.len()];
944        <Block256 as FlatPromote<Block32>>::promote_flat_batch(&input, &mut batch_out);
945
946        for i in 0..input.len() {
947            let scalar = <Block256 as FlatPromote<Block32>>::promote_flat(input[i]);
948            assert_eq!(
949                batch_out[i], scalar,
950                "Block32 batch/scalar mismatch at {}",
951                i
952            );
953        }
954    }
955
956    #[test]
957    fn promote_flat_batch_matches_scalar_block64() {
958        let mut rng = rng();
959        let input: Vec<Flat<Block64>> = (0..200)
960            .map(|_| Block64(rng.random::<u64>()).to_hardware())
961            .collect();
962
963        let mut batch_out = vec![Flat::from_raw(Block256::ZERO); input.len()];
964        <Block256 as FlatPromote<Block64>>::promote_flat_batch(&input, &mut batch_out);
965
966        for i in 0..input.len() {
967            let scalar = <Block256 as FlatPromote<Block64>>::promote_flat(input[i]);
968            assert_eq!(
969                batch_out[i], scalar,
970                "Block64 batch/scalar mismatch at {}",
971                i
972            );
973        }
974    }
975
976    #[test]
977    fn promote_flat_batch_matches_scalar_block128() {
978        let mut rng = rng();
979        let input: Vec<Flat<Block128>> = (0..200)
980            .map(|_| Block128(rng.random::<u128>()).to_hardware())
981            .collect();
982
983        let mut batch_out = vec![Flat::from_raw(Block256::ZERO); input.len()];
984        <Block256 as FlatPromote<Block128>>::promote_flat_batch(&input, &mut batch_out);
985
986        for i in 0..input.len() {
987            let scalar = <Block256 as FlatPromote<Block128>>::promote_flat(input[i]);
988            assert_eq!(
989                batch_out[i], scalar,
990                "Block128 batch/scalar mismatch at {}",
991                i
992            );
993        }
994    }
995
996    #[test]
997    fn promote_flat_batch_partial_slice() {
998        let mut rng = rng();
999        let input: Vec<Flat<Block8>> = (0..10)
1000            .map(|_| Block8(rng.random::<u8>()).to_hardware())
1001            .collect();
1002
1003        let mut out_short = vec![Flat::from_raw(Block256::ZERO); 5];
1004        <Block256 as FlatPromote<Block8>>::promote_flat_batch(&input, &mut out_short);
1005
1006        for i in 0..5 {
1007            let scalar = <Block256 as FlatPromote<Block8>>::promote_flat(input[i]);
1008            assert_eq!(out_short[i], scalar);
1009        }
1010
1011        let short_input = &input[..3];
1012
1013        let mut out_long = vec![Flat::from_raw(Block256::ZERO); 10];
1014        <Block256 as FlatPromote<Block8>>::promote_flat_batch(short_input, &mut out_long);
1015
1016        for i in 0..3 {
1017            let scalar = <Block256 as FlatPromote<Block8>>::promote_flat(short_input[i]);
1018            assert_eq!(out_long[i], scalar);
1019        }
1020
1021        for val in out_long.iter().skip(3) {
1022            assert_eq!(*val, Flat::from_raw(Block256::ZERO));
1023        }
1024    }
1025
1026    #[test]
1027    fn promote_flat_batch_across_chunk_boundary() {
1028        let mut rng = rng();
1029        // Exercise lengths straddling PROMOTE_CHUNK.
1030        for &n in &[
1031            PROMOTE_CHUNK - 1,
1032            PROMOTE_CHUNK,
1033            PROMOTE_CHUNK + 1,
1034            PROMOTE_CHUNK * 2 + 3,
1035        ] {
1036            let input: Vec<Flat<Block8>> = (0..n)
1037                .map(|_| Block8(rng.random::<u8>()).to_hardware())
1038                .collect();
1039
1040            let mut batch_out = vec![Flat::from_raw(Block256::ZERO); n];
1041            <Block256 as FlatPromote<Block8>>::promote_flat_batch(&input, &mut batch_out);
1042
1043            for i in 0..n {
1044                let scalar = <Block256 as FlatPromote<Block8>>::promote_flat(input[i]);
1045                assert_eq!(batch_out[i], scalar, "n={}, idx={}", n, i);
1046            }
1047        }
1048    }
1049}