1use alloc::vec;
19use alloc::vec::Vec;
20use core::iter::repeat_n;
21use hekate_core::config::Config;
22use hekate_core::errors::Error;
23use hekate_core::poly::PolyVariant;
24use hekate_core::trace::{ColumnType, Trace, TraceColumn, TraceCompatibleField};
25use hekate_core::utils::compute_split_vars;
26use hekate_math::{Bit, Block8, Block16, Block32, Block64, Flat, HardwareField};
27
28#[derive(Clone, Copy, Debug)]
30pub enum ExpansionEntry {
31 ExpandBits {
32 count: usize,
33 storage: ColumnType,
34 },
35 PassThrough {
36 count: usize,
37 storage: ColumnType,
38 },
39 ControlBits {
40 count: usize,
41 },
42 ReusePassThrough {
43 phy_col_start: usize,
44 count: usize,
45 storage: ColumnType,
46 },
47 ReuseExpandBits {
48 phy_col_start: usize,
49 count: usize,
50 storage: ColumnType,
51 },
52}
53
54#[derive(Clone, Copy, Debug)]
56enum EntryKind {
57 ExpandBits { count: usize, storage: ColumnType },
60
61 PassThrough { count: usize, storage: ColumnType },
64
65 ControlBits { count: usize },
68}
69
70impl EntryKind {
71 fn count(&self) -> usize {
72 match self {
73 Self::ExpandBits { count, .. }
74 | Self::PassThrough { count, .. }
75 | Self::ControlBits { count } => *count,
76 }
77 }
78
79 fn storage(&self) -> ColumnType {
80 match self {
81 Self::ExpandBits { storage, .. } | Self::PassThrough { storage, .. } => *storage,
82 Self::ControlBits { .. } => ColumnType::Bit,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug)]
90struct CompiledEntry {
91 phy_col_start: usize,
94
95 byte_offset: usize,
97 kind: EntryKind,
98
99 reuse: bool,
102}
103
104#[derive(Clone, Debug)]
112pub struct VirtualExpander {
113 entries: Vec<CompiledEntry>,
114 num_virtual: usize,
115 num_physical: usize,
116 physical_row_bytes: usize,
117 virtual_layout: Vec<ColumnType>,
118 error: Option<Error>,
119}
120
121impl VirtualExpander {
122 pub fn new() -> Self {
123 Self {
124 entries: Vec::new(),
125 num_virtual: 0,
126 num_physical: 0,
127 physical_row_bytes: 0,
128 virtual_layout: Vec::new(),
129 error: None,
130 }
131 }
132
133 pub fn build(self) -> Result<Self, Error> {
136 match self.error {
137 Some(e) => Err(e),
138 None => Ok(self),
139 }
140 }
141
142 pub fn expand_bits(mut self, count: usize, storage: ColumnType) -> Self {
145 if self.error.is_some() {
146 return self;
147 }
148
149 let bits_per = match expand_bit_width(storage) {
150 Ok(v) => v,
151 Err(e) => {
152 self.error = Some(e);
153 return self;
154 }
155 };
156
157 let byte_offset = self.physical_row_bytes;
158 let phy_col_start = self.num_physical;
159
160 self.entries.push(CompiledEntry {
161 phy_col_start,
162 byte_offset,
163 kind: EntryKind::ExpandBits { count, storage },
164 reuse: false,
165 });
166
167 let virt_count = count * bits_per;
168 self.virtual_layout
169 .extend(repeat_n(ColumnType::Bit, virt_count));
170
171 self.num_virtual += virt_count;
172 self.num_physical += count;
173 self.physical_row_bytes += count * storage.byte_size();
174
175 self
176 }
177
178 pub fn pass_through(mut self, count: usize, storage: ColumnType) -> Self {
181 let byte_offset = self.physical_row_bytes;
182 let phy_col_start = self.num_physical;
183
184 self.entries.push(CompiledEntry {
185 phy_col_start,
186 byte_offset,
187 kind: EntryKind::PassThrough { count, storage },
188 reuse: false,
189 });
190
191 self.virtual_layout.extend(repeat_n(storage, count));
192
193 self.num_virtual += count;
194 self.num_physical += count;
195 self.physical_row_bytes += count * storage.byte_size();
196
197 self
198 }
199
200 pub fn control_bits(mut self, count: usize) -> Self {
202 let byte_offset = self.physical_row_bytes;
203 let phy_col_start = self.num_physical;
204
205 self.entries.push(CompiledEntry {
206 phy_col_start,
207 byte_offset,
208 kind: EntryKind::ControlBits { count },
209 reuse: false,
210 });
211
212 self.virtual_layout.extend(repeat_n(ColumnType::Bit, count));
213
214 self.num_virtual += count;
215 self.num_physical += count;
216 self.physical_row_bytes += count;
217
218 self
219 }
220
221 pub fn reuse_pass_through(mut self, phy_col_start: usize, count: usize) -> Self {
225 if self.error.is_some() {
226 return self;
227 }
228
229 if phy_col_start + count > self.num_physical {
230 self.error = Some(Error::Protocol {
231 protocol: "virtual_expand",
232 message: "reuse_pass_through: range exceeds declared physical columns",
233 });
234 return self;
235 }
236
237 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
238 Ok(v) => v,
239 Err(e) => {
240 self.error = Some(e);
241 return self;
242 }
243 };
244
245 self.entries.push(CompiledEntry {
246 phy_col_start,
247 byte_offset,
248 kind: EntryKind::PassThrough { count, storage },
249 reuse: true,
250 });
251
252 self.virtual_layout.extend(repeat_n(storage, count));
253
254 self.num_virtual += count;
255
256 self
257 }
258
259 pub fn reuse_expand_bits(mut self, phy_col_start: usize, count: usize) -> Self {
263 if self.error.is_some() {
264 return self;
265 }
266
267 if phy_col_start + count > self.num_physical {
268 self.error = Some(Error::Protocol {
269 protocol: "virtual_expand",
270 message: "reuse_expand_bits: range exceeds declared physical columns",
271 });
272 return self;
273 }
274
275 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
276 Ok(v) => v,
277 Err(e) => {
278 self.error = Some(e);
279 return self;
280 }
281 };
282
283 let bits_per = match expand_bit_width(storage) {
284 Ok(v) => v,
285 Err(e) => {
286 self.error = Some(e);
287 return self;
288 }
289 };
290
291 self.entries.push(CompiledEntry {
292 phy_col_start,
293 byte_offset,
294 kind: EntryKind::ExpandBits { count, storage },
295 reuse: true,
296 });
297
298 let virt_count = count * bits_per;
299 self.virtual_layout
300 .extend(repeat_n(ColumnType::Bit, virt_count));
301
302 self.num_virtual += virt_count;
303
304 self
305 }
306
307 #[inline]
308 pub fn num_virtual_columns(&self) -> usize {
309 self.num_virtual
310 }
311
312 #[inline]
313 pub fn num_physical_columns(&self) -> usize {
314 self.num_physical
315 }
316
317 #[inline]
318 pub fn physical_row_bytes(&self) -> usize {
319 self.physical_row_bytes
320 }
321
322 #[inline]
323 pub fn virtual_layout(&self) -> &[ColumnType] {
324 &self.virtual_layout
325 }
326
327 pub fn parse_row<F: TraceCompatibleField>(
331 &self,
332 bytes: &[u8],
333 res: &mut Vec<Flat<F>>,
334 ) -> Result<(), Error> {
335 if bytes.len() != self.physical_row_bytes {
336 return Err(Error::Protocol {
337 protocol: "virtual_expand",
338 message: "parse_row: byte slice length mismatch",
339 });
340 }
341
342 res.reserve(self.num_virtual);
343
344 for entry in &self.entries {
345 let off = entry.byte_offset;
346 match entry.kind {
347 EntryKind::ExpandBits { count, storage } => {
348 let bsz = storage.byte_size();
349 let bits = expand_bit_width(storage)?;
350
351 for i in 0..count {
352 let start = off + i * bsz;
353 for bit_idx in 0..bits {
354 let bit = parse_tower_bit(storage, &bytes[start..start + bsz], bit_idx);
355 res.push(Flat::from_raw(F::from(Bit::from(bit))));
356 }
357 }
358 }
359 EntryKind::PassThrough { count, storage } => {
360 let bsz = storage.byte_size();
361 for i in 0..count {
362 let start = off + i * bsz;
363 res.push(storage.parse_from_bytes(&bytes[start..start + bsz]));
364 }
365 }
366 EntryKind::ControlBits { count } => {
367 for i in 0..count {
368 res.push(Flat::from_raw(F::from(Bit::from(bytes[off + i] & 1))));
369 }
370 }
371 }
372 }
373
374 Ok(())
375 }
376
377 pub fn expand_variants<'a, F, T: Trace + ?Sized>(
381 &self,
382 trace: &'a T,
383 phy_start_idx: usize,
384 ) -> Result<Vec<PolyVariant<'a, F>>, Error>
385 where
386 F: TraceCompatibleField + 'static,
387 {
388 let columns = trace.columns();
389
390 let mut variants = Vec::with_capacity(self.num_virtual);
391 for entry in &self.entries {
392 let base = phy_start_idx + entry.phy_col_start;
393 match entry.kind {
394 EntryKind::ExpandBits { count, storage } => {
395 let bits = expand_bit_width(storage)?;
396 for i in 0..count {
397 let col = columns.get(base + i).ok_or(Error::Protocol {
398 protocol: "virtual_expand",
399 message: "missing physical column for ExpandBits",
400 })?;
401
402 for bit_idx in 0..bits {
403 variants.push(expand_packed_bit(col, storage, bit_idx)?);
404 }
405 }
406 }
407 EntryKind::PassThrough { count, storage } => {
408 for i in 0..count {
409 let col = columns.get(base + i).ok_or(Error::Protocol {
410 protocol: "virtual_expand",
411 message: "missing physical column for PassThrough",
412 })?;
413
414 variants.push(expand_pass_through(col, storage)?);
415 }
416 }
417 EntryKind::ControlBits { count } => {
418 for i in 0..count {
419 let col = columns.get(base + i).ok_or(Error::Protocol {
420 protocol: "virtual_expand",
421 message: "missing physical column for ControlBits",
422 })?;
423 let data = col.as_bit_slice().ok_or(Error::Protocol {
424 protocol: "virtual_expand",
425 message: "control column must be Bit",
426 })?;
427
428 variants.push(PolyVariant::BitSlice(data));
429 }
430 }
431 }
432 }
433
434 Ok(variants)
435 }
436
437 pub fn expansion_entries(&self) -> Vec<ExpansionEntry> {
439 self.entries
440 .iter()
441 .map(|e| match (e.kind, e.reuse) {
442 (EntryKind::PassThrough { count, storage }, true) => {
443 ExpansionEntry::ReusePassThrough {
444 phy_col_start: e.phy_col_start,
445 count,
446 storage,
447 }
448 }
449 (EntryKind::ExpandBits { count, storage }, true) => {
450 ExpansionEntry::ReuseExpandBits {
451 phy_col_start: e.phy_col_start,
452 count,
453 storage,
454 }
455 }
456 (EntryKind::ExpandBits { count, storage }, false) => {
457 ExpansionEntry::ExpandBits { count, storage }
458 }
459 (EntryKind::PassThrough { count, storage }, false) => {
460 ExpansionEntry::PassThrough { count, storage }
461 }
462 (EntryKind::ControlBits { count }, _) => ExpansionEntry::ControlBits { count },
463 })
464 .collect()
465 }
466
467 fn find_phy_source(
470 &self,
471 target_start: usize,
472 target_count: usize,
473 ) -> Result<(usize, ColumnType), Error> {
474 let mut running_phy = 0usize;
475 for entry in &self.entries {
476 if entry.phy_col_start != running_phy {
477 continue;
478 }
479
480 let entry_count = entry.kind.count();
481 let entry_end = running_phy + entry_count;
482
483 if target_start >= running_phy && target_start + target_count <= entry_end {
484 let storage = entry.kind.storage();
485 let offset_in_entry = target_start - running_phy;
486
487 return Ok((
488 entry.byte_offset + offset_in_entry * storage.byte_size(),
489 storage,
490 ));
491 }
492
493 running_phy = entry_end;
494 }
495
496 Err(Error::Protocol {
497 protocol: "virtual_expand",
498 message: "reuse: source columns not found in any single fresh entry",
499 })
500 }
501}
502
503impl Default for VirtualExpander {
504 fn default() -> Self {
505 Self::new()
506 }
507}
508
509pub struct RingSwitchPlan {
514 pub num_units: usize,
515 pub units: Vec<(bool, usize)>,
516 pub phys_rs: Vec<ColumnType>,
517 phys_bit: Vec<Vec<usize>>,
518 phys_whole: Vec<Vec<usize>>,
519}
520
521impl RingSwitchPlan {
522 pub fn new(
523 layout: &[ColumnType],
524 entries: Option<&[ExpansionEntry]>,
525 num_blind: usize,
526 ) -> Result<Self, Error> {
527 let num_phys = layout.len();
528 let total = num_phys + num_blind;
529
530 let mut phys_bit = vec![Vec::new(); total];
531 let mut phys_whole = vec![Vec::new(); total];
532 let mut units: Vec<(bool, usize)> = Vec::new();
533
534 let mut phys_rs: Vec<ColumnType> = layout.iter().map(|ct| ct.rs_field()).collect();
535 phys_rs.extend((0..num_blind).map(|_| ColumnType::B128));
536
537 let bounds = |upper: usize| -> Result<(), Error> {
538 if upper > num_phys {
539 return Err(Error::Protocol {
540 protocol: "ring_switch_plan",
541 message: "expansion entry exceeds the physical column layout",
542 });
543 }
544
545 Ok(())
546 };
547
548 match entries {
549 Some(entries) => {
550 let mut running = 0usize;
551 for e in entries {
552 match *e {
553 ExpansionEntry::ExpandBits { count, storage } => {
554 let bits = expand_bit_width(storage)?;
555
556 bounds(running + count)?;
557
558 for j in 0..count {
559 phys_bit[running + j].push(units.len());
560 units.push((true, bits));
561 }
562
563 running += count;
564 }
565 ExpansionEntry::PassThrough { count, .. }
566 | ExpansionEntry::ControlBits { count } => {
567 bounds(running + count)?;
568
569 for j in 0..count {
570 phys_whole[running + j].push(units.len());
571 units.push((false, 1));
572 }
573
574 running += count;
575 }
576 ExpansionEntry::ReusePassThrough {
577 phy_col_start,
578 count,
579 ..
580 } => {
581 bounds(phy_col_start + count)?;
582
583 for j in 0..count {
584 phys_whole[phy_col_start + j].push(units.len());
585 units.push((false, 1));
586 }
587 }
588 ExpansionEntry::ReuseExpandBits {
589 phy_col_start,
590 count,
591 storage,
592 } => {
593 let bits = expand_bit_width(storage)?;
594
595 bounds(phy_col_start + count)?;
596
597 for j in 0..count {
598 phys_bit[phy_col_start + j].push(units.len());
599 units.push((true, bits));
600 }
601 }
602 }
603 }
604
605 if running != num_phys {
606 return Err(Error::Protocol {
607 protocol: "ring_switch_plan",
608 message: "expansion entries do not cover the physical column layout",
609 });
610 }
611 }
612 None => {
613 for pw in phys_whole.iter_mut().take(num_phys) {
614 pw.push(units.len());
615 units.push((false, 1));
616 }
617 }
618 }
619
620 for b in 0..num_blind {
621 phys_whole[num_phys + b].push(units.len());
622 units.push((false, 1));
623 }
624
625 let num_units = units.len();
626
627 Ok(Self {
628 units,
629 phys_bit,
630 phys_whole,
631 phys_rs,
632 num_units,
633 })
634 }
635
636 pub fn has_ring(&self) -> bool {
637 self.units.iter().any(|(is_ring, _)| *is_ring)
638 }
639
640 pub fn total_claims(&self) -> usize {
641 self.units.iter().map(|(_, n)| n).sum()
642 }
643
644 pub fn opened_row_bytes(&self) -> usize {
645 self.phys_rs.iter().map(|ct| ct.byte_size()).sum()
646 }
647
648 pub fn num_master_vectors(&self) -> usize {
649 1 + usize::from(self.has_ring())
650 }
651
652 pub fn split_vars(&self, num_vars: usize, config: &Config) -> usize {
653 compute_split_vars(
654 num_vars,
655 config.num_queries,
656 config.ldt_support_size,
657 self.opened_row_bytes(),
658 self.num_master_vectors(),
659 )
660 }
661
662 pub fn column_coeffs<F>(&self, eta: Flat<F>) -> (Vec<Flat<F>>, Vec<Flat<F>>, Flat<F>)
666 where
667 F: HardwareField,
668 {
669 let mut eta_pows = Vec::with_capacity(self.num_units + 1);
670 let mut e = Flat::from_raw(F::ONE);
671
672 for _ in 0..=self.num_units {
673 eta_pows.push(e);
674 e *= eta;
675 }
676
677 let total = self.phys_rs.len();
678
679 let mut coeff_bit = vec![Flat::from_raw(F::ZERO); total];
680 let mut coeff_whole = vec![Flat::from_raw(F::ZERO); total];
681
682 for p in 0..total {
683 for &u in &self.phys_bit[p] {
684 coeff_bit[p] += eta_pows[u];
685 }
686
687 for &u in &self.phys_whole[p] {
688 coeff_whole[p] += eta_pows[u];
689 }
690 }
691
692 (coeff_bit, coeff_whole, eta_pows[self.num_units])
693 }
694}
695
696fn expand_bit_width(storage: ColumnType) -> Result<usize, Error> {
697 match storage {
698 ColumnType::B8 => Ok(8),
699 ColumnType::B16 => Ok(16),
700 ColumnType::B32 => Ok(32),
701 ColumnType::B64 => Ok(64),
702 _ => Err(Error::Protocol {
703 protocol: "virtual_expand",
704 message: "ExpandBits requires B8/B16/B32/B64",
705 }),
706 }
707}
708
709fn parse_tower_bit(storage: ColumnType, bytes: &[u8], bit_idx: usize) -> u8 {
711 match storage {
712 ColumnType::B8 => Flat::from_raw(Block8(bytes[0])).tower_bit(bit_idx),
713 ColumnType::B16 => {
714 let mut arr = [0u8; 2];
715 arr.copy_from_slice(bytes);
716
717 Flat::from_raw(Block16(u16::from_le_bytes(arr))).tower_bit(bit_idx)
718 }
719 ColumnType::B32 => {
720 let mut arr = [0u8; 4];
721 arr.copy_from_slice(bytes);
722
723 Flat::from_raw(Block32(u32::from_le_bytes(arr))).tower_bit(bit_idx)
724 }
725 ColumnType::B64 => {
726 let mut arr = [0u8; 8];
727 arr.copy_from_slice(bytes);
728
729 Flat::from_raw(Block64(u64::from_le_bytes(arr))).tower_bit(bit_idx)
730 }
731 _ => unreachable!(),
732 }
733}
734
735fn expand_packed_bit<F: TraceCompatibleField + 'static>(
736 col: &'_ TraceColumn,
737 storage: ColumnType,
738 bit_idx: usize,
739) -> Result<PolyVariant<'_, F>, Error> {
740 match storage {
741 ColumnType::B8 => {
742 let data = col.as_b8_slice().ok_or(Error::Protocol {
743 protocol: "virtual_expand",
744 message: "ExpandBits B8: column type mismatch",
745 })?;
746
747 Ok(PolyVariant::PackedBitB8 { data, bit_idx })
748 }
749 ColumnType::B16 => {
750 let data = col.as_b16_slice().ok_or(Error::Protocol {
751 protocol: "virtual_expand",
752 message: "ExpandBits B16: column type mismatch",
753 })?;
754
755 Ok(PolyVariant::PackedBitB16 { data, bit_idx })
756 }
757 ColumnType::B32 => {
758 let data = col.as_b32_slice().ok_or(Error::Protocol {
759 protocol: "virtual_expand",
760 message: "ExpandBits B32: column type mismatch",
761 })?;
762
763 Ok(PolyVariant::PackedBitB32 { data, bit_idx })
764 }
765 ColumnType::B64 => {
766 let data = col.as_b64_slice().ok_or(Error::Protocol {
767 protocol: "virtual_expand",
768 message: "ExpandBits B64: column type mismatch",
769 })?;
770
771 Ok(PolyVariant::PackedBitB64 { data, bit_idx })
772 }
773 _ => unreachable!(),
774 }
775}
776
777fn expand_pass_through<F: TraceCompatibleField + 'static>(
778 col: &TraceColumn,
779 storage: ColumnType,
780) -> Result<PolyVariant<'_, F>, Error> {
781 match storage {
782 ColumnType::Bit => {
783 let data = col.as_bit_slice().ok_or(Error::Protocol {
784 protocol: "virtual_expand",
785 message: "PassThrough Bit: column type mismatch",
786 })?;
787
788 Ok(PolyVariant::BitSlice(data))
789 }
790 ColumnType::B8 => {
791 let data = col.as_b8_slice().ok_or(Error::Protocol {
792 protocol: "virtual_expand",
793 message: "PassThrough B8: column type mismatch",
794 })?;
795
796 Ok(PolyVariant::B8Slice(data))
797 }
798 ColumnType::B16 => {
799 let data = col.as_b16_slice().ok_or(Error::Protocol {
800 protocol: "virtual_expand",
801 message: "PassThrough B16: column type mismatch",
802 })?;
803
804 Ok(PolyVariant::B16Slice(data))
805 }
806 ColumnType::B32 => {
807 let data = col.as_b32_slice().ok_or(Error::Protocol {
808 protocol: "virtual_expand",
809 message: "PassThrough B32: column type mismatch",
810 })?;
811
812 Ok(PolyVariant::B32Slice(data))
813 }
814 ColumnType::B64 => {
815 let data = col.as_b64_slice().ok_or(Error::Protocol {
816 protocol: "virtual_expand",
817 message: "PassThrough B64: column type mismatch",
818 })?;
819
820 Ok(PolyVariant::B64Slice(data))
821 }
822 ColumnType::B128 => {
823 let data = col.as_b128_slice().ok_or(Error::Protocol {
824 protocol: "virtual_expand",
825 message: "PassThrough B128: column type mismatch",
826 })?;
827
828 Ok(PolyVariant::B128Slice(data))
829 }
830 }
831}
832
833#[cfg(test)]
834mod tests {
835 use super::*;
836 use hekate_core::trace::TraceBuilder;
837 use hekate_math::{Block128, TowerField};
838
839 fn keccak_expander() -> VirtualExpander {
840 VirtualExpander::new()
841 .expand_bits(25, ColumnType::B64)
842 .expand_bits(1, ColumnType::B64)
843 .reuse_pass_through(0, 25)
844 .control_bits(2)
845 .build()
846 .unwrap()
847 }
848
849 fn keccak_physical_layout() -> Vec<ColumnType> {
850 let mut layout = vec![ColumnType::B64; 26];
851 layout.extend(repeat_n(ColumnType::Bit, 2));
852
853 layout
854 }
855
856 #[test]
857 fn ram_layout() {
858 let e = VirtualExpander::new()
859 .expand_bits(2, ColumnType::B32)
860 .pass_through(13, ColumnType::B32)
861 .pass_through(1, ColumnType::B128)
862 .control_bits(4)
863 .build()
864 .unwrap();
865
866 assert_eq!(e.num_virtual_columns(), 82);
867 assert_eq!(e.num_physical_columns(), 20);
868 assert_eq!(e.physical_row_bytes(), 80);
869
870 let layout = e.virtual_layout();
871
872 assert_eq!(layout.len(), 82);
873 assert!(layout[..64].iter().all(|&t| t == ColumnType::Bit));
874 assert!(layout[64..77].iter().all(|&t| t == ColumnType::B32));
875 assert_eq!(layout[77], ColumnType::B128);
876 assert!(layout[78..82].iter().all(|&t| t == ColumnType::Bit));
877 }
878
879 #[test]
880 fn keccak_layout() {
881 let e = VirtualExpander::new()
882 .expand_bits(25, ColumnType::B64)
883 .expand_bits(1, ColumnType::B64)
884 .reuse_pass_through(0, 25)
885 .control_bits(2)
886 .build()
887 .unwrap();
888
889 assert_eq!(e.num_virtual_columns(), 1691);
890 assert_eq!(e.num_physical_columns(), 28);
891 assert_eq!(e.physical_row_bytes(), 210);
892
893 let layout = e.virtual_layout();
894
895 assert_eq!(layout.len(), 1691);
896 assert!(layout[..1600].iter().all(|&t| t == ColumnType::Bit));
897 assert!(layout[1600..1664].iter().all(|&t| t == ColumnType::Bit));
898 assert!(layout[1664..1689].iter().all(|&t| t == ColumnType::B64));
899 assert!(layout[1689..1691].iter().all(|&t| t == ColumnType::Bit));
900 }
901
902 #[test]
903 fn ring_switch_plan_rejects_uncovered_columns() {
904 let expander = keccak_expander();
905 let entries = expander.expansion_entries();
906
907 let mut layout = keccak_physical_layout();
908
909 assert_eq!(expander.num_physical_columns(), layout.len());
910 assert!(RingSwitchPlan::new(&layout, Some(&entries), 0).is_ok());
911
912 layout.push(ColumnType::B64);
913
914 assert!(RingSwitchPlan::new(&layout, Some(&entries), 0).is_err());
915 }
916
917 #[test]
918 fn ring_switch_plan_folds_every_committed_column() {
919 let entries = keccak_expander().expansion_entries();
920 let layout = keccak_physical_layout();
921
922 let plan = RingSwitchPlan::new(&layout, Some(&entries), 2).unwrap();
923
924 let zero = Flat::from_raw(Block128::ZERO);
925 let eta = Block128(0x2545F4914F6CDD1D_517CC1B727220A95).to_hardware();
926 let (coeff_bit, coeff_whole, _) = plan.column_coeffs::<Block128>(eta);
927
928 for p in 0..plan.phys_rs.len() {
929 assert!(
930 coeff_bit[p] != zero || coeff_whole[p] != zero,
931 "committed column {p} enters no master fold"
932 );
933 }
934 }
935
936 #[test]
937 fn reuse_partial_range() {
938 let e = VirtualExpander::new()
939 .expand_bits(10, ColumnType::B32)
940 .reuse_pass_through(3, 4)
941 .build()
942 .unwrap();
943
944 assert_eq!(e.num_virtual_columns(), 324);
945 assert_eq!(e.num_physical_columns(), 10);
946 assert_eq!(e.physical_row_bytes(), 40);
947
948 let layout = e.virtual_layout();
949
950 assert_eq!(layout[320..324].len(), 4);
951 assert!(layout[320..324].iter().all(|&t| t == ColumnType::B32));
952 }
953
954 #[test]
955 fn reuse_exceeds_declared() {
956 let result = VirtualExpander::new()
957 .expand_bits(5, ColumnType::B32)
958 .reuse_pass_through(3, 5)
959 .build();
960
961 assert!(result.is_err());
962 }
963
964 #[test]
965 fn reuse_expand_bits_from_pass_through() {
966 let e = VirtualExpander::new()
967 .pass_through(4, ColumnType::B64)
968 .reuse_expand_bits(0, 4)
969 .build()
970 .unwrap();
971
972 assert_eq!(e.num_physical_columns(), 4);
973 assert_eq!(e.physical_row_bytes(), 32);
974 assert_eq!(e.num_virtual_columns(), 4 + 256);
975
976 let layout = e.virtual_layout();
977
978 assert!(layout[0..4].iter().all(|&t| t == ColumnType::B64));
979 assert!(layout[4..260].iter().all(|&t| t == ColumnType::Bit));
980 }
981
982 #[test]
983 fn reuse_expand_bits_exceeds_declared() {
984 let result = VirtualExpander::new()
985 .pass_through(4, ColumnType::B64)
986 .reuse_expand_bits(2, 4)
987 .build();
988
989 assert!(result.is_err());
990 }
991
992 #[test]
993 fn reuse_expand_bits_rejects_b128_source() {
994 let result = VirtualExpander::new()
995 .pass_through(1, ColumnType::B128)
996 .reuse_expand_bits(0, 1)
997 .build();
998
999 assert!(result.is_err());
1000 }
1001
1002 #[test]
1003 fn expand_rejects_bit() {
1004 let result = VirtualExpander::new()
1005 .expand_bits(1, ColumnType::Bit)
1006 .build();
1007
1008 assert!(result.is_err());
1009 }
1010
1011 #[test]
1012 fn expand_rejects_b128() {
1013 let result = VirtualExpander::new()
1014 .expand_bits(1, ColumnType::B128)
1015 .build();
1016
1017 assert!(result.is_err());
1018 }
1019
1020 #[test]
1021 fn empty_expander() {
1022 let e = VirtualExpander::new();
1023 assert_eq!(e.num_virtual_columns(), 0);
1024 assert_eq!(e.num_physical_columns(), 0);
1025 assert_eq!(e.physical_row_bytes(), 0);
1026 assert!(e.virtual_layout().is_empty());
1027 }
1028
1029 #[test]
1030 fn parse_row_b32_roundtrip() {
1031 let expander = VirtualExpander::new()
1032 .expand_bits(1, ColumnType::B32)
1033 .pass_through(1, ColumnType::B32)
1034 .control_bits(1)
1035 .build()
1036 .unwrap();
1037
1038 let val: u32 = 0xDEAD_BEEF;
1039 let pass_val: u32 = 0x1234_5678;
1040
1041 let mut bytes = Vec::new();
1042 bytes.extend_from_slice(&val.to_le_bytes());
1043 bytes.extend_from_slice(&pass_val.to_le_bytes());
1044 bytes.push(1);
1045
1046 let mut res: Vec<Flat<Block128>> = Vec::new();
1047 expander.parse_row(&bytes, &mut res).unwrap();
1048
1049 assert_eq!(res.len(), 34);
1050
1051 for (bit_idx, elem) in res.iter().enumerate().take(32) {
1052 let expected = Flat::from_raw(Block32(val)).tower_bit(bit_idx);
1053 let got = elem.tower_bit(0);
1054 assert_eq!(got, expected, "bit {bit_idx} mismatch");
1055 }
1056
1057 let pass = res[32];
1058 assert_eq!(
1059 pass,
1060 <Block128 as hekate_math::FlatPromote<Block32>>::promote_flat(Flat::from_raw(Block32(
1061 pass_val
1062 )))
1063 );
1064
1065 let ctrl = res[33].tower_bit(0);
1066 assert_eq!(ctrl, 1);
1067 }
1068
1069 #[test]
1070 fn expand_variants_b32() {
1071 let expander = VirtualExpander::new()
1072 .expand_bits(1, ColumnType::B32)
1073 .pass_through(1, ColumnType::B32)
1074 .control_bits(1)
1075 .build()
1076 .unwrap();
1077
1078 let layout = [ColumnType::B32, ColumnType::B32, ColumnType::Bit];
1079 let num_vars = 2;
1080
1081 let mut tb = TraceBuilder::new(&layout, num_vars).unwrap();
1082 tb.set_b32(0, 0, Block32(0xAAAA_BBBB)).unwrap();
1083 tb.set_b32(1, 0, Block32(0x1111_2222)).unwrap();
1084 tb.set_bit(2, 0, Bit::ONE).unwrap();
1085
1086 let trace = tb.build();
1087
1088 let variants: Vec<PolyVariant<'_, Block128>> = expander.expand_variants(&trace, 0).unwrap();
1089
1090 assert_eq!(variants.len(), 34);
1091
1092 for (i, v) in variants.iter().enumerate().take(32) {
1093 assert!(matches!(v, PolyVariant::PackedBitB32 { bit_idx, .. } if *bit_idx == i));
1094 }
1095
1096 assert!(matches!(variants[32], PolyVariant::B32Slice(_)));
1097 assert!(matches!(variants[33], PolyVariant::BitSlice(_)));
1098 }
1099}