1use 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
30const TAU_FLAT: u128 = 0x66340c45203fe3685d08f8c248334a81;
33
34#[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 pub fn new(lo: Block128, hi: Block128) -> Self {
45 Self([lo.0, hi.0])
46 }
47
48 #[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
242pub const PACKED_WIDTH_256: usize = 2;
248
249#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
251#[repr(C, align(64))]
252pub struct PackedBlock256(pub [Block256; PACKED_WIDTH_256]);
253
254impl PackedBlock256 {
255 #[inline(always)]
257 pub fn zero() -> Self {
258 Self([Block256::ZERO; PACKED_WIDTH_256])
259 }
260
261 #[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
490impl BinaryFieldExtras for Block256 {
495 #[inline(always)]
496 fn square(&self) -> Self {
497 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 self.split().1.trace()
511 }
512
513 #[inline(always)]
514 fn solve_quadratic(c: Self) -> Option<Self> {
515 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 #[test]
639 fn tower_constants() {
640 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 assert_eq!(Block256([5, 0]) + Block256([3, 0]), Block256([6, 0]));
674 }
675
676 #[test]
677 fn mul_simple() {
678 assert_eq!(
680 Block256::from(2u32) * Block256::from(2u32),
681 Block256::from(4u32)
682 );
683 }
684
685 #[test]
686 fn mul_overflow() {
687 assert_eq!(
689 Block256::from(0x57u32) * Block256::from(0x83u32),
690 Block256::from(0xC1u32)
691 );
692 }
693
694 #[test]
695 fn karatsuba_correctness() {
696 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 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 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 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 #[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 #[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 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}