1use alloc::vec;
6use alloc::vec::Vec;
7use core::iter::repeat_n;
8use hekate_core::config::{Config, FoldShape, MIN_PRODUCTION_BITS};
9use hekate_core::errors::Error;
10use hekate_core::poly::PolyVariant;
11use hekate_core::trace::{ColumnType, Trace, TraceColumn, TraceCompatibleField};
12use hekate_core::utils::{compute_split_vars, support_floor_vars};
13use hekate_math::{
14 Bit, Block8, Block16, Block32, Block64, Block128, Flat, HardwareField, TowerField,
15};
16
17pub const RING_BLIND_BITS: usize = 128;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum ExpansionEntry {
22 ExpandBits {
23 count: usize,
24 storage: ColumnType,
25 },
26 PassThrough {
27 count: usize,
28 storage: ColumnType,
29 },
30 ControlBits {
31 count: usize,
32 },
33 ReusePassThrough {
34 phy_col_start: usize,
35 count: usize,
36 storage: ColumnType,
37 },
38 ReuseExpandBits {
39 phy_col_start: usize,
40 count: usize,
41 storage: ColumnType,
42 },
43}
44
45#[derive(Clone, Copy, Debug)]
47enum EntryKind {
48 ExpandBits { count: usize, storage: ColumnType },
51
52 PassThrough { count: usize, storage: ColumnType },
55
56 ControlBits { count: usize },
59}
60
61impl EntryKind {
62 fn count(&self) -> usize {
63 match self {
64 Self::ExpandBits { count, .. }
65 | Self::PassThrough { count, .. }
66 | Self::ControlBits { count } => *count,
67 }
68 }
69
70 fn storage(&self) -> ColumnType {
71 match self {
72 Self::ExpandBits { storage, .. } | Self::PassThrough { storage, .. } => *storage,
73 Self::ControlBits { .. } => ColumnType::Bit,
74 }
75 }
76}
77
78#[derive(Clone, Copy, Debug)]
81struct CompiledEntry {
82 phy_col_start: usize,
85
86 byte_offset: usize,
88 kind: EntryKind,
89
90 reuse: bool,
93}
94
95#[derive(Clone, Debug)]
103pub struct VirtualExpander {
104 entries: Vec<CompiledEntry>,
105 num_virtual: usize,
106 num_physical: usize,
107 physical_row_bytes: usize,
108 virtual_layout: Vec<ColumnType>,
109 error: Option<Error>,
110}
111
112impl VirtualExpander {
113 pub fn new() -> Self {
114 Self {
115 entries: Vec::new(),
116 num_virtual: 0,
117 num_physical: 0,
118 physical_row_bytes: 0,
119 virtual_layout: Vec::new(),
120 error: None,
121 }
122 }
123
124 pub fn build(self) -> Result<Self, Error> {
127 match self.error {
128 Some(e) => Err(e),
129 None => Ok(self),
130 }
131 }
132
133 pub fn expand_bits(mut self, count: usize, storage: ColumnType) -> Self {
136 if self.error.is_some() {
137 return self;
138 }
139
140 let bits_per = match expand_bit_width(storage) {
141 Ok(v) => v,
142 Err(e) => {
143 self.error = Some(e);
144 return self;
145 }
146 };
147
148 let byte_offset = self.physical_row_bytes;
149 let phy_col_start = self.num_physical;
150
151 self.entries.push(CompiledEntry {
152 phy_col_start,
153 byte_offset,
154 kind: EntryKind::ExpandBits { count, storage },
155 reuse: false,
156 });
157
158 let virt_count = count * bits_per;
159 self.virtual_layout
160 .extend(repeat_n(ColumnType::Bit, virt_count));
161
162 self.num_virtual += virt_count;
163 self.num_physical += count;
164 self.physical_row_bytes += count * storage.byte_size();
165
166 self
167 }
168
169 pub fn pass_through(mut self, count: usize, storage: ColumnType) -> Self {
172 let byte_offset = self.physical_row_bytes;
173 let phy_col_start = self.num_physical;
174
175 self.entries.push(CompiledEntry {
176 phy_col_start,
177 byte_offset,
178 kind: EntryKind::PassThrough { count, storage },
179 reuse: false,
180 });
181
182 self.virtual_layout.extend(repeat_n(storage, count));
183
184 self.num_virtual += count;
185 self.num_physical += count;
186 self.physical_row_bytes += count * storage.byte_size();
187
188 self
189 }
190
191 pub fn control_bits(mut self, count: usize) -> Self {
193 let byte_offset = self.physical_row_bytes;
194 let phy_col_start = self.num_physical;
195
196 self.entries.push(CompiledEntry {
197 phy_col_start,
198 byte_offset,
199 kind: EntryKind::ControlBits { count },
200 reuse: false,
201 });
202
203 self.virtual_layout.extend(repeat_n(ColumnType::Bit, count));
204
205 self.num_virtual += count;
206 self.num_physical += count;
207 self.physical_row_bytes += count;
208
209 self
210 }
211
212 pub fn reuse_pass_through(mut self, phy_col_start: usize, count: usize) -> Self {
216 if self.error.is_some() {
217 return self;
218 }
219
220 if phy_col_start + count > self.num_physical {
221 self.error = Some(Error::Protocol {
222 protocol: "virtual_expand",
223 message: "reuse_pass_through: range exceeds declared physical columns",
224 });
225 return self;
226 }
227
228 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
229 Ok(v) => v,
230 Err(e) => {
231 self.error = Some(e);
232 return self;
233 }
234 };
235
236 self.entries.push(CompiledEntry {
237 phy_col_start,
238 byte_offset,
239 kind: EntryKind::PassThrough { count, storage },
240 reuse: true,
241 });
242
243 self.virtual_layout.extend(repeat_n(storage, count));
244
245 self.num_virtual += count;
246
247 self
248 }
249
250 pub fn reuse_expand_bits(mut self, phy_col_start: usize, count: usize) -> Self {
254 if self.error.is_some() {
255 return self;
256 }
257
258 if phy_col_start + count > self.num_physical {
259 self.error = Some(Error::Protocol {
260 protocol: "virtual_expand",
261 message: "reuse_expand_bits: range exceeds declared physical columns",
262 });
263 return self;
264 }
265
266 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
267 Ok(v) => v,
268 Err(e) => {
269 self.error = Some(e);
270 return self;
271 }
272 };
273
274 let bits_per = match expand_bit_width(storage) {
275 Ok(v) => v,
276 Err(e) => {
277 self.error = Some(e);
278 return self;
279 }
280 };
281
282 self.entries.push(CompiledEntry {
283 phy_col_start,
284 byte_offset,
285 kind: EntryKind::ExpandBits { count, storage },
286 reuse: true,
287 });
288
289 let virt_count = count * bits_per;
290 self.virtual_layout
291 .extend(repeat_n(ColumnType::Bit, virt_count));
292
293 self.num_virtual += virt_count;
294
295 self
296 }
297
298 pub fn append(mut self, other: &VirtualExpander) -> Self {
302 if self.error.is_some() {
303 return self;
304 }
305
306 if let Some(e) = &other.error {
307 self.error = Some(*e);
308 return self;
309 }
310
311 let phy_shift = self.num_physical;
312 let byte_shift = self.physical_row_bytes;
313
314 for entry in &other.entries {
315 self.entries.push(CompiledEntry {
316 phy_col_start: entry.phy_col_start + phy_shift,
317 byte_offset: entry.byte_offset + byte_shift,
318 kind: entry.kind,
319 reuse: entry.reuse,
320 });
321 }
322
323 self.virtual_layout.extend_from_slice(&other.virtual_layout);
324
325 self.num_virtual += other.num_virtual;
326 self.num_physical += other.num_physical;
327 self.physical_row_bytes += other.physical_row_bytes;
328
329 self
330 }
331
332 #[inline]
333 pub fn num_virtual_columns(&self) -> usize {
334 self.num_virtual
335 }
336
337 #[inline]
338 pub fn num_physical_columns(&self) -> usize {
339 self.num_physical
340 }
341
342 #[inline]
343 pub fn physical_row_bytes(&self) -> usize {
344 self.physical_row_bytes
345 }
346
347 #[inline]
348 pub fn virtual_layout(&self) -> &[ColumnType] {
349 &self.virtual_layout
350 }
351
352 pub fn parse_row<F: TraceCompatibleField>(
356 &self,
357 bytes: &[u8],
358 res: &mut Vec<Flat<F>>,
359 ) -> Result<(), Error> {
360 if bytes.len() != self.physical_row_bytes {
361 return Err(Error::Protocol {
362 protocol: "virtual_expand",
363 message: "parse_row: byte slice length mismatch",
364 });
365 }
366
367 res.reserve(self.num_virtual);
368
369 for entry in &self.entries {
370 let off = entry.byte_offset;
371 match entry.kind {
372 EntryKind::ExpandBits { count, storage } => {
373 let bsz = storage.byte_size();
374 let bits = expand_bit_width(storage)?;
375
376 for i in 0..count {
377 let start = off + i * bsz;
378 for bit_idx in 0..bits {
379 let bit = parse_tower_bit(storage, &bytes[start..start + bsz], bit_idx);
380 res.push(Flat::from_raw(F::from(Bit::from(bit))));
381 }
382 }
383 }
384 EntryKind::PassThrough { count, storage } => {
385 let bsz = storage.byte_size();
386 for i in 0..count {
387 let start = off + i * bsz;
388 res.push(storage.parse_from_bytes(&bytes[start..start + bsz]));
389 }
390 }
391 EntryKind::ControlBits { count } => {
392 for i in 0..count {
393 res.push(Flat::from_raw(F::from(Bit::from(bytes[off + i] & 1))));
394 }
395 }
396 }
397 }
398
399 Ok(())
400 }
401
402 pub fn expand_variants<'a, F, T: Trace + ?Sized>(
406 &self,
407 trace: &'a T,
408 phy_start_idx: usize,
409 ) -> Result<Vec<PolyVariant<'a, F>>, Error>
410 where
411 F: TraceCompatibleField + 'static,
412 {
413 let columns = trace.columns();
414
415 let mut variants = Vec::with_capacity(self.num_virtual);
416 for entry in &self.entries {
417 let base = phy_start_idx + entry.phy_col_start;
418 match entry.kind {
419 EntryKind::ExpandBits { count, storage } => {
420 let bits = expand_bit_width(storage)?;
421 for i in 0..count {
422 let col = columns.get(base + i).ok_or(Error::Protocol {
423 protocol: "virtual_expand",
424 message: "missing physical column for ExpandBits",
425 })?;
426
427 for bit_idx in 0..bits {
428 variants.push(expand_packed_bit(col, storage, bit_idx)?);
429 }
430 }
431 }
432 EntryKind::PassThrough { count, storage } => {
433 for i in 0..count {
434 let col = columns.get(base + i).ok_or(Error::Protocol {
435 protocol: "virtual_expand",
436 message: "missing physical column for PassThrough",
437 })?;
438
439 variants.push(expand_pass_through(col, storage)?);
440 }
441 }
442 EntryKind::ControlBits { count } => {
443 for i in 0..count {
444 let col = columns.get(base + i).ok_or(Error::Protocol {
445 protocol: "virtual_expand",
446 message: "missing physical column for ControlBits",
447 })?;
448 let data = col.as_bit_slice().ok_or(Error::Protocol {
449 protocol: "virtual_expand",
450 message: "control column must be Bit",
451 })?;
452
453 variants.push(PolyVariant::BitSlice(data));
454 }
455 }
456 }
457 }
458
459 Ok(variants)
460 }
461
462 pub fn expansion_entries(&self) -> Vec<ExpansionEntry> {
464 self.entries
465 .iter()
466 .map(|e| match (e.kind, e.reuse) {
467 (EntryKind::PassThrough { count, storage }, true) => {
468 ExpansionEntry::ReusePassThrough {
469 phy_col_start: e.phy_col_start,
470 count,
471 storage,
472 }
473 }
474 (EntryKind::ExpandBits { count, storage }, true) => {
475 ExpansionEntry::ReuseExpandBits {
476 phy_col_start: e.phy_col_start,
477 count,
478 storage,
479 }
480 }
481 (EntryKind::ExpandBits { count, storage }, false) => {
482 ExpansionEntry::ExpandBits { count, storage }
483 }
484 (EntryKind::PassThrough { count, storage }, false) => {
485 ExpansionEntry::PassThrough { count, storage }
486 }
487 (EntryKind::ControlBits { count }, _) => ExpansionEntry::ControlBits { count },
488 })
489 .collect()
490 }
491
492 pub(crate) fn whole_column(&self, virt: usize) -> Option<usize> {
493 let mut base = 0usize;
494 for entry in &self.entries {
495 let width = match entry.kind {
496 EntryKind::ExpandBits { count, storage } => count * storage.byte_size() * 8,
497 EntryKind::PassThrough { count, .. } | EntryKind::ControlBits { count } => count,
498 };
499
500 if virt < base + width {
501 return match entry.kind {
502 EntryKind::ExpandBits { .. } => None,
503 EntryKind::PassThrough { .. } | EntryKind::ControlBits { .. } => {
504 Some(entry.phy_col_start + (virt - base))
505 }
506 };
507 }
508
509 base += width;
510 }
511
512 None
513 }
514
515 fn find_phy_source(
518 &self,
519 target_start: usize,
520 target_count: usize,
521 ) -> Result<(usize, ColumnType), Error> {
522 let mut running_phy = 0usize;
523 for entry in &self.entries {
524 if entry.phy_col_start != running_phy {
525 continue;
526 }
527
528 let entry_count = entry.kind.count();
529 let entry_end = running_phy + entry_count;
530
531 if target_start >= running_phy && target_start + target_count <= entry_end {
532 let storage = entry.kind.storage();
533 let offset_in_entry = target_start - running_phy;
534
535 return Ok((
536 entry.byte_offset + offset_in_entry * storage.byte_size(),
537 storage,
538 ));
539 }
540
541 running_phy = entry_end;
542 }
543
544 Err(Error::Protocol {
545 protocol: "virtual_expand",
546 message: "reuse: source columns not found in any single fresh entry",
547 })
548 }
549}
550
551impl Default for VirtualExpander {
552 fn default() -> Self {
553 Self::new()
554 }
555}
556
557pub struct RingSwitchPlan {
562 pub num_units: usize,
563 pub units: Vec<(bool, usize)>,
564 pub phys_rs: Vec<ColumnType>,
565 pub blind_slots: usize,
566 pub h_cols: usize,
567
568 phys_bit: Vec<Vec<usize>>,
569 phys_whole: Vec<Vec<usize>>,
570 h_whole: Vec<usize>,
571}
572
573impl RingSwitchPlan {
574 pub fn new(
575 layout: &[ColumnType],
576 entries: Option<&[ExpansionEntry]>,
577 num_blind: usize,
578 num_h: usize,
579 ) -> Result<Self, Error> {
580 let num_phys = layout.len();
581 let total = num_phys + num_blind;
582
583 let mut phys_bit = vec![Vec::new(); total];
584 let mut phys_whole = vec![Vec::new(); total];
585 let mut units: Vec<(bool, usize)> = Vec::new();
586
587 let mut phys_rs: Vec<ColumnType> = layout.iter().map(|ct| ct.rs_field()).collect();
588 phys_rs.extend((0..num_blind).map(|_| ColumnType::B128));
589
590 let bounds = |upper: usize| -> Result<(), Error> {
591 if upper > num_phys {
592 return Err(Error::Protocol {
593 protocol: "ring_switch_plan",
594 message: "expansion entry exceeds the physical column layout",
595 });
596 }
597
598 Ok(())
599 };
600
601 match entries {
602 Some(entries) => {
603 let mut running = 0usize;
604 for e in entries {
605 match *e {
606 ExpansionEntry::ExpandBits { count, storage } => {
607 let bits = expand_bit_width(storage)?;
608
609 bounds(running + count)?;
610
611 for j in 0..count {
612 phys_bit[running + j].push(units.len());
613 units.push((true, bits));
614 }
615
616 running += count;
617 }
618 ExpansionEntry::PassThrough { count, .. }
619 | ExpansionEntry::ControlBits { count } => {
620 bounds(running + count)?;
621
622 for j in 0..count {
623 phys_whole[running + j].push(units.len());
624 units.push((false, 1));
625 }
626
627 running += count;
628 }
629 ExpansionEntry::ReusePassThrough {
630 phy_col_start,
631 count,
632 ..
633 } => {
634 bounds(phy_col_start + count)?;
635
636 for j in 0..count {
637 phys_whole[phy_col_start + j].push(units.len());
638 units.push((false, 1));
639 }
640 }
641 ExpansionEntry::ReuseExpandBits {
642 phy_col_start,
643 count,
644 storage,
645 } => {
646 let bits = expand_bit_width(storage)?;
647
648 bounds(phy_col_start + count)?;
649
650 for j in 0..count {
651 phys_bit[phy_col_start + j].push(units.len());
652 units.push((true, bits));
653 }
654 }
655 }
656 }
657
658 if running != num_phys {
659 return Err(Error::Protocol {
660 protocol: "ring_switch_plan",
661 message: "expansion entries do not cover the physical column layout",
662 });
663 }
664 }
665 None => {
666 for pw in phys_whole.iter_mut().take(num_phys) {
667 pw.push(units.len());
668 units.push((false, 1));
669 }
670 }
671 }
672
673 let has_ring = units.iter().any(|(is_ring, _)| *is_ring);
674
675 for b in 0..num_blind {
676 phys_whole[num_phys + b].push(units.len());
677 units.push((false, 1));
678 }
679
680 let mut blind_slots = num_blind;
681
682 if has_ring && num_blind > 0 {
684 phys_bit.push(vec![units.len()]);
685 phys_whole.push(Vec::new());
686 phys_rs.push(ColumnType::B128);
687 units.push((true, RING_BLIND_BITS));
688 blind_slots += 1;
689 }
690
691 let mut h_whole = Vec::with_capacity(num_h);
692 for _ in 0..num_h {
693 h_whole.push(units.len());
694 units.push((false, 1));
695 }
696
697 let num_units = units.len();
698
699 Ok(Self {
700 units,
701 phys_bit,
702 phys_whole,
703 h_whole,
704 phys_rs,
705 blind_slots,
706 h_cols: num_h,
707 num_units,
708 })
709 }
710
711 pub fn has_ring(&self) -> bool {
712 self.units.iter().any(|(is_ring, _)| *is_ring)
713 }
714
715 pub fn has_ring_blind(&self) -> bool {
716 self.blind_slots > 0 && self.has_ring()
717 }
718
719 pub fn total_claims(&self) -> usize {
720 self.units.iter().map(|(_, n)| n).sum()
721 }
722
723 pub fn leaf_row_bytes(&self) -> usize {
724 self.phys_rs.iter().map(|ct| ct.byte_size()).sum()
725 }
726
727 pub fn h_leaf_row_bytes(&self) -> usize {
728 self.h_cols * ColumnType::B128.byte_size()
729 }
730
731 pub fn opened_row_bytes(&self) -> usize {
732 self.leaf_row_bytes() + self.h_leaf_row_bytes()
733 }
734
735 pub fn split_vars(&self, num_vars: usize, field_bits: usize, config: &Config) -> usize {
739 let split = compute_split_vars(
740 num_vars,
741 config.num_queries,
742 config.ldt_support_size,
743 self.opened_row_bytes(),
744 );
745
746 let floor = support_floor_vars(config.ldt_support_size)
747 .clamp(1, num_vars.max(1))
748 .min(split);
749
750 let bits = |c: usize| {
751 config.estimated_security_bits(
752 field_bits,
753 FoldShape {
754 grid_cols: 1 << c,
755 grid_rows: 1 << (num_vars - c),
756 units: self.num_units,
757 },
758 )
759 };
760
761 (floor..=split)
764 .rev()
765 .find(|&c| bits(c) >= MIN_PRODUCTION_BITS)
766 .unwrap_or(split)
767 }
768
769 pub fn column_coeffs<F>(&self, eta: Flat<F>) -> (Vec<Flat<F>>, Vec<Flat<F>>, Flat<F>)
773 where
774 F: HardwareField,
775 {
776 let mut eta_pows = Vec::with_capacity(self.num_units + 1);
777 let mut e = Flat::from_raw(F::ONE);
778
779 for _ in 0..=self.num_units {
780 eta_pows.push(e);
781 e *= eta;
782 }
783
784 let num_phys = self.phys_rs.len();
785 let total = num_phys + self.h_cols;
786
787 let mut coeff_bit = vec![Flat::from_raw(F::ZERO); total];
788 let mut coeff_whole = vec![Flat::from_raw(F::ZERO); total];
789
790 for p in 0..num_phys {
791 for &u in &self.phys_bit[p] {
792 coeff_bit[p] += eta_pows[u];
793 }
794
795 for &u in &self.phys_whole[p] {
796 coeff_whole[p] += eta_pows[u];
797 }
798 }
799
800 for (p, &u) in self.h_whole.iter().enumerate() {
801 coeff_whole[num_phys + p] += eta_pows[u];
802 }
803
804 (coeff_bit, coeff_whole, eta_pows[self.num_units])
805 }
806}
807
808fn expand_bit_width(storage: ColumnType) -> Result<usize, Error> {
809 match storage {
810 ColumnType::B8 => Ok(8),
811 ColumnType::B16 => Ok(16),
812 ColumnType::B32 => Ok(32),
813 ColumnType::B64 => Ok(64),
814 _ => Err(Error::Protocol {
815 protocol: "virtual_expand",
816 message: "ExpandBits requires B8/B16/B32/B64",
817 }),
818 }
819}
820
821fn parse_tower_bit(storage: ColumnType, bytes: &[u8], bit_idx: usize) -> u8 {
823 match storage {
824 ColumnType::B8 => Flat::from_raw(Block8(bytes[0])).tower_bit(bit_idx),
825 ColumnType::B16 => {
826 let mut arr = [0u8; 2];
827 arr.copy_from_slice(bytes);
828
829 Flat::from_raw(Block16(u16::from_le_bytes(arr))).tower_bit(bit_idx)
830 }
831 ColumnType::B32 => {
832 let mut arr = [0u8; 4];
833 arr.copy_from_slice(bytes);
834
835 Flat::from_raw(Block32(u32::from_le_bytes(arr))).tower_bit(bit_idx)
836 }
837 ColumnType::B64 => {
838 let mut arr = [0u8; 8];
839 arr.copy_from_slice(bytes);
840
841 Flat::from_raw(Block64(u64::from_le_bytes(arr))).tower_bit(bit_idx)
842 }
843 _ => unreachable!(),
844 }
845}
846
847fn expand_packed_bit<F: TraceCompatibleField + 'static>(
848 col: &'_ TraceColumn,
849 storage: ColumnType,
850 bit_idx: usize,
851) -> Result<PolyVariant<'_, F>, Error> {
852 match storage {
853 ColumnType::B8 => {
854 let data = col.as_b8_slice().ok_or(Error::Protocol {
855 protocol: "virtual_expand",
856 message: "ExpandBits B8: column type mismatch",
857 })?;
858
859 Ok(PolyVariant::PackedBitB8 { data, bit_idx })
860 }
861 ColumnType::B16 => {
862 let data = col.as_b16_slice().ok_or(Error::Protocol {
863 protocol: "virtual_expand",
864 message: "ExpandBits B16: column type mismatch",
865 })?;
866
867 Ok(PolyVariant::PackedBitB16 { data, bit_idx })
868 }
869 ColumnType::B32 => {
870 let data = col.as_b32_slice().ok_or(Error::Protocol {
871 protocol: "virtual_expand",
872 message: "ExpandBits B32: column type mismatch",
873 })?;
874
875 Ok(PolyVariant::PackedBitB32 { data, bit_idx })
876 }
877 ColumnType::B64 => {
878 let data = col.as_b64_slice().ok_or(Error::Protocol {
879 protocol: "virtual_expand",
880 message: "ExpandBits B64: column type mismatch",
881 })?;
882
883 Ok(PolyVariant::PackedBitB64 { data, bit_idx })
884 }
885 _ => unreachable!(),
886 }
887}
888
889fn expand_pass_through<F: TraceCompatibleField + 'static>(
890 col: &TraceColumn,
891 storage: ColumnType,
892) -> Result<PolyVariant<'_, F>, Error> {
893 match storage {
894 ColumnType::Bit => {
895 let data = col.as_bit_slice().ok_or(Error::Protocol {
896 protocol: "virtual_expand",
897 message: "PassThrough Bit: column type mismatch",
898 })?;
899
900 Ok(PolyVariant::BitSlice(data))
901 }
902 ColumnType::B8 => {
903 let data = col.as_b8_slice().ok_or(Error::Protocol {
904 protocol: "virtual_expand",
905 message: "PassThrough B8: column type mismatch",
906 })?;
907
908 Ok(PolyVariant::B8Slice(data))
909 }
910 ColumnType::B16 => {
911 let data = col.as_b16_slice().ok_or(Error::Protocol {
912 protocol: "virtual_expand",
913 message: "PassThrough B16: column type mismatch",
914 })?;
915
916 Ok(PolyVariant::B16Slice(data))
917 }
918 ColumnType::B32 => {
919 let data = col.as_b32_slice().ok_or(Error::Protocol {
920 protocol: "virtual_expand",
921 message: "PassThrough B32: column type mismatch",
922 })?;
923
924 Ok(PolyVariant::B32Slice(data))
925 }
926 ColumnType::B64 => {
927 let data = col.as_b64_slice().ok_or(Error::Protocol {
928 protocol: "virtual_expand",
929 message: "PassThrough B64: column type mismatch",
930 })?;
931
932 Ok(PolyVariant::B64Slice(data))
933 }
934 ColumnType::B128 => {
935 let data = col.as_b128_slice().ok_or(Error::Protocol {
936 protocol: "virtual_expand",
937 message: "PassThrough B128: column type mismatch",
938 })?;
939
940 Ok(PolyVariant::B128Slice(data))
941 }
942 }
943}
944
945pub fn eq_tensor_b(r: &[Block128]) -> Vec<Block128> {
946 let mut t = vec![Block128::ONE];
947 for &ri in r {
948 let len = t.len();
949 let mut nt = Vec::with_capacity(len * 2);
950
951 for &v in &t {
952 nt.push(v * (Block128::ONE + ri));
953 }
954
955 for &v in &t {
956 nt.push(v * ri);
957 }
958
959 t = nt;
960 }
961
962 t
963}
964
965pub fn ring_batch_b(bit_claims: &[Block128], eq_mix: &[Block128]) -> Block128 {
967 let mut acc = Block128::ZERO;
968 for (u, &m) in eq_mix.iter().enumerate() {
969 let mut shat = 0u128;
970 for (v, cv) in bit_claims.iter().enumerate() {
971 shat |= ((cv.0 >> u) & 1) << v;
972 }
973
974 acc += m * Block128(shat);
975 }
976
977 acc
978}
979
980pub fn ring_target<F>(
984 plan: &RingSwitchPlan,
985 claims: &[Flat<F>],
986 eta_tower: F,
987 r_mix: &[Block128],
988 shifted_claims: bool,
989) -> Block128
990where
991 F: HardwareField + Into<Block128>,
992{
993 let claim_halves = if shifted_claims { 2 } else { 1 };
994 let half = claims.len() / claim_halves;
995 let eq_mix = eq_tensor_b(r_mix);
996 let eta: Block128 = eta_tower.into();
997
998 let mut eta_pows = Vec::with_capacity(plan.num_units + 1);
999 let mut e = Block128::ONE;
1000
1001 for _ in 0..=plan.num_units {
1002 eta_pows.push(e);
1003 e *= eta;
1004 }
1005
1006 let eta_shift = eta_pows[plan.num_units];
1007
1008 let base = [(0usize, Block128::ONE)];
1009 let base_and_shift = [(0usize, Block128::ONE), (half, eta_shift)];
1010 let offsets: &[(usize, Block128)] = if shifted_claims {
1011 &base_and_shift
1012 } else {
1013 &base
1014 };
1015
1016 let mut target = Block128::ZERO;
1017 for &(offset, shift_mul) in offsets {
1018 let half_claims = &claims[offset..offset + half];
1019
1020 let mut ci = 0usize;
1021 for (unit_idx, &(is_ring, num_claims)) in plan.units.iter().enumerate() {
1022 let weight = eta_pows[unit_idx] * shift_mul;
1023 if is_ring {
1024 let bits: Vec<Block128> = half_claims[ci..ci + num_claims]
1025 .iter()
1026 .map(|f| f.to_tower().into())
1027 .collect();
1028
1029 target += weight * ring_batch_b(&bits, &eq_mix);
1030 } else {
1031 let c: Block128 = half_claims[ci].to_tower().into();
1032 target += weight * c;
1033 }
1034
1035 ci += num_claims;
1036 }
1037 }
1038
1039 target
1040}
1041
1042pub fn claim_weights<F>(
1046 plan: &RingSwitchPlan,
1047 eta_tower: F,
1048 shifted_claims: bool,
1049) -> Vec<(bool, Block128)>
1050where
1051 F: HardwareField + Into<Block128>,
1052{
1053 let eta: Block128 = eta_tower.into();
1054
1055 let mut eta_pows = Vec::with_capacity(plan.num_units + 1);
1056 let mut e = Block128::ONE;
1057
1058 for _ in 0..=plan.num_units {
1059 eta_pows.push(e);
1060 e *= eta;
1061 }
1062
1063 let shifts: &[Block128] = if shifted_claims {
1064 &[Block128::ONE, eta_pows[plan.num_units]]
1065 } else {
1066 &[Block128::ONE]
1067 };
1068
1069 let mut weights = Vec::with_capacity(plan.total_claims() * shifts.len());
1070 for &shift_mul in shifts {
1071 for (unit_idx, &(is_ring, num_claims)) in plan.units.iter().enumerate() {
1072 let weight = eta_pows[unit_idx] * shift_mul;
1073 for v in 0..num_claims {
1074 let basis = if is_ring {
1075 Block128(1u128 << v)
1076 } else {
1077 Block128::ONE
1078 };
1079
1080 weights.push((is_ring, weight * basis));
1081 }
1082 }
1083 }
1084
1085 weights
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090 use super::*;
1091 use hekate_core::trace::TraceBuilder;
1092 use hekate_math::{Block128, TowerField};
1093
1094 fn keccak_expander() -> VirtualExpander {
1095 VirtualExpander::new()
1096 .expand_bits(25, ColumnType::B64)
1097 .expand_bits(1, ColumnType::B64)
1098 .reuse_pass_through(0, 25)
1099 .control_bits(2)
1100 .build()
1101 .unwrap()
1102 }
1103
1104 fn keccak_physical_layout() -> Vec<ColumnType> {
1105 let mut layout = vec![ColumnType::B64; 26];
1106 layout.extend(repeat_n(ColumnType::Bit, 2));
1107
1108 layout
1109 }
1110
1111 fn plan_of(cols: usize) -> RingSwitchPlan {
1112 RingSwitchPlan::new(&vec![ColumnType::B128; cols], None, 1, 1).unwrap()
1113 }
1114
1115 #[test]
1116 fn ram_layout() {
1117 let e = VirtualExpander::new()
1118 .expand_bits(2, ColumnType::B32)
1119 .pass_through(13, ColumnType::B32)
1120 .pass_through(1, ColumnType::B128)
1121 .control_bits(4)
1122 .build()
1123 .unwrap();
1124
1125 assert_eq!(e.num_virtual_columns(), 82);
1126 assert_eq!(e.num_physical_columns(), 20);
1127 assert_eq!(e.physical_row_bytes(), 80);
1128
1129 let layout = e.virtual_layout();
1130
1131 assert_eq!(layout.len(), 82);
1132 assert!(layout[..64].iter().all(|&t| t == ColumnType::Bit));
1133 assert!(layout[64..77].iter().all(|&t| t == ColumnType::B32));
1134 assert_eq!(layout[77], ColumnType::B128);
1135 assert!(layout[78..82].iter().all(|&t| t == ColumnType::Bit));
1136 }
1137
1138 #[test]
1139 fn keccak_layout() {
1140 let e = VirtualExpander::new()
1141 .expand_bits(25, ColumnType::B64)
1142 .expand_bits(1, ColumnType::B64)
1143 .reuse_pass_through(0, 25)
1144 .control_bits(2)
1145 .build()
1146 .unwrap();
1147
1148 assert_eq!(e.num_virtual_columns(), 1691);
1149 assert_eq!(e.num_physical_columns(), 28);
1150 assert_eq!(e.physical_row_bytes(), 210);
1151
1152 let layout = e.virtual_layout();
1153
1154 assert_eq!(layout.len(), 1691);
1155 assert!(layout[..1600].iter().all(|&t| t == ColumnType::Bit));
1156 assert!(layout[1600..1664].iter().all(|&t| t == ColumnType::Bit));
1157 assert!(layout[1664..1689].iter().all(|&t| t == ColumnType::B64));
1158 assert!(layout[1689..1691].iter().all(|&t| t == ColumnType::Bit));
1159 }
1160
1161 #[test]
1162 fn ring_switch_plan_rejects_uncovered_columns() {
1163 let expander = keccak_expander();
1164 let entries = expander.expansion_entries();
1165
1166 let mut layout = keccak_physical_layout();
1167
1168 assert_eq!(expander.num_physical_columns(), layout.len());
1169 assert!(RingSwitchPlan::new(&layout, Some(&entries), 0, 0).is_ok());
1170
1171 layout.push(ColumnType::B64);
1172
1173 assert!(RingSwitchPlan::new(&layout, Some(&entries), 0, 0).is_err());
1174 }
1175
1176 #[test]
1177 fn ring_switch_plan_folds_every_committed_column() {
1178 let entries = keccak_expander().expansion_entries();
1179 let layout = keccak_physical_layout();
1180
1181 let plan = RingSwitchPlan::new(&layout, Some(&entries), 2, 0).unwrap();
1182
1183 let zero = Flat::from_raw(Block128::ZERO);
1184 let eta = Block128(0x2545F4914F6CDD1D_517CC1B727220A95).to_hardware();
1185 let (coeff_bit, coeff_whole, _) = plan.column_coeffs::<Block128>(eta);
1186
1187 for p in 0..plan.phys_rs.len() {
1188 assert!(
1189 coeff_bit[p] != zero || coeff_whole[p] != zero,
1190 "committed column {p} enters no master fold"
1191 );
1192 }
1193 }
1194
1195 #[test]
1196 fn h_units_trail_ring_blind() {
1197 let layout = [ColumnType::B32, ColumnType::B64];
1198 let expander = VirtualExpander::new()
1199 .expand_bits(1, ColumnType::B32)
1200 .pass_through(1, ColumnType::B64)
1201 .build()
1202 .unwrap();
1203 let entries = expander.expansion_entries();
1204
1205 let bare = RingSwitchPlan::new(&layout, Some(&entries), 1, 0).unwrap();
1206 let plan = RingSwitchPlan::new(&layout, Some(&entries), 1, 2).unwrap();
1207
1208 assert_eq!(plan.h_cols, 2);
1209 assert_eq!(plan.phys_rs, bare.phys_rs);
1210 assert_eq!(plan.total_claims(), bare.total_claims() + 2);
1211 assert_eq!(&plan.units[plan.num_units - 2..], &[(false, 1), (false, 1)]);
1212 assert_eq!(plan.units[plan.num_units - 3], (true, RING_BLIND_BITS));
1213 assert_eq!(plan.leaf_row_bytes(), bare.opened_row_bytes());
1214 assert_eq!(plan.opened_row_bytes(), bare.opened_row_bytes() + 32);
1215
1216 let eta = Block128::from(0x9E37_79B9u128).to_hardware();
1217 let (coeff_bit, coeff_whole, eta_shift) = plan.column_coeffs::<Block128>(eta);
1218 let n = plan.phys_rs.len();
1219
1220 let mut first_h = Flat::from_raw(Block128::ONE);
1221 for _ in 0..plan.num_units - 2 {
1222 first_h *= eta;
1223 }
1224
1225 assert_eq!(coeff_whole.len(), n + 2);
1226 assert_eq!(coeff_bit[n..], [Flat::from_raw(Block128::ZERO); 2]);
1227 assert_eq!(coeff_whole[n], first_h);
1228 assert_eq!(coeff_whole[n + 1], first_h * eta);
1229 assert_eq!(eta_shift, first_h * eta * eta);
1230 }
1231
1232 #[test]
1233 fn blinded_ring_plan_ends_in_ring_blind_unit() {
1234 let entries = keccak_expander().expansion_entries();
1235 let layout = keccak_physical_layout();
1236
1237 let raw = RingSwitchPlan::new(&layout, Some(&entries), 0, 0).unwrap();
1238 let blinded = RingSwitchPlan::new(&layout, Some(&entries), 2, 0).unwrap();
1239 let whole_only = RingSwitchPlan::new(&[ColumnType::B32; 3], None, 2, 0).unwrap();
1240
1241 assert_eq!(raw.blind_slots, 0);
1242 assert_eq!(raw.phys_rs.len(), layout.len());
1243
1244 assert_eq!(blinded.blind_slots, 3);
1245 assert_eq!(blinded.phys_rs.len(), layout.len() + 3);
1246 assert_eq!(blinded.units.last(), Some(&(true, RING_BLIND_BITS)));
1247 assert_eq!(
1248 blinded.total_claims(),
1249 raw.total_claims() + 2 + RING_BLIND_BITS
1250 );
1251
1252 assert_eq!(whole_only.blind_slots, 2);
1253 assert_eq!(whole_only.units.last(), Some(&(false, 1)));
1254
1255 let eta = Block128(0x2545F4914F6CDD1D_517CC1B727220A95).to_hardware();
1256 let (coeff_bit, coeff_whole, _) = blinded.column_coeffs::<Block128>(eta);
1257 let last = blinded.phys_rs.len() - 1;
1258
1259 assert_ne!(coeff_bit[last], Flat::from_raw(Block128::ZERO));
1260 assert_eq!(coeff_whole[last], Flat::from_raw(Block128::ZERO));
1261 }
1262
1263 #[test]
1264 fn reuse_partial_range() {
1265 let e = VirtualExpander::new()
1266 .expand_bits(10, ColumnType::B32)
1267 .reuse_pass_through(3, 4)
1268 .build()
1269 .unwrap();
1270
1271 assert_eq!(e.num_virtual_columns(), 324);
1272 assert_eq!(e.num_physical_columns(), 10);
1273 assert_eq!(e.physical_row_bytes(), 40);
1274
1275 let layout = e.virtual_layout();
1276
1277 assert_eq!(layout[320..324].len(), 4);
1278 assert!(layout[320..324].iter().all(|&t| t == ColumnType::B32));
1279 }
1280
1281 #[test]
1282 fn reuse_exceeds_declared() {
1283 let result = VirtualExpander::new()
1284 .expand_bits(5, ColumnType::B32)
1285 .reuse_pass_through(3, 5)
1286 .build();
1287
1288 assert!(result.is_err());
1289 }
1290
1291 #[test]
1292 fn reuse_expand_bits_from_pass_through() {
1293 let e = VirtualExpander::new()
1294 .pass_through(4, ColumnType::B64)
1295 .reuse_expand_bits(0, 4)
1296 .build()
1297 .unwrap();
1298
1299 assert_eq!(e.num_physical_columns(), 4);
1300 assert_eq!(e.physical_row_bytes(), 32);
1301 assert_eq!(e.num_virtual_columns(), 4 + 256);
1302
1303 let layout = e.virtual_layout();
1304
1305 assert!(layout[0..4].iter().all(|&t| t == ColumnType::B64));
1306 assert!(layout[4..260].iter().all(|&t| t == ColumnType::Bit));
1307 }
1308
1309 #[test]
1310 fn reuse_expand_bits_exceeds_declared() {
1311 let result = VirtualExpander::new()
1312 .pass_through(4, ColumnType::B64)
1313 .reuse_expand_bits(2, 4)
1314 .build();
1315
1316 assert!(result.is_err());
1317 }
1318
1319 #[test]
1320 fn reuse_expand_bits_rejects_b128_source() {
1321 let result = VirtualExpander::new()
1322 .pass_through(1, ColumnType::B128)
1323 .reuse_expand_bits(0, 1)
1324 .build();
1325
1326 assert!(result.is_err());
1327 }
1328
1329 #[test]
1330 fn expand_rejects_bit() {
1331 let result = VirtualExpander::new()
1332 .expand_bits(1, ColumnType::Bit)
1333 .build();
1334
1335 assert!(result.is_err());
1336 }
1337
1338 #[test]
1339 fn expand_rejects_b128() {
1340 let result = VirtualExpander::new()
1341 .expand_bits(1, ColumnType::B128)
1342 .build();
1343
1344 assert!(result.is_err());
1345 }
1346
1347 #[test]
1348 fn empty_expander() {
1349 let e = VirtualExpander::new();
1350 assert_eq!(e.num_virtual_columns(), 0);
1351 assert_eq!(e.num_physical_columns(), 0);
1352 assert_eq!(e.physical_row_bytes(), 0);
1353 assert!(e.virtual_layout().is_empty());
1354 }
1355
1356 #[test]
1357 fn parse_row_b32_roundtrip() {
1358 let expander = VirtualExpander::new()
1359 .expand_bits(1, ColumnType::B32)
1360 .pass_through(1, ColumnType::B32)
1361 .control_bits(1)
1362 .build()
1363 .unwrap();
1364
1365 let val: u32 = 0xDEAD_BEEF;
1366 let pass_val: u32 = 0x1234_5678;
1367
1368 let mut bytes = Vec::new();
1369 bytes.extend_from_slice(&val.to_le_bytes());
1370 bytes.extend_from_slice(&pass_val.to_le_bytes());
1371 bytes.push(1);
1372
1373 let mut res: Vec<Flat<Block128>> = Vec::new();
1374 expander.parse_row(&bytes, &mut res).unwrap();
1375
1376 assert_eq!(res.len(), 34);
1377
1378 for (bit_idx, elem) in res.iter().enumerate().take(32) {
1379 let expected = Flat::from_raw(Block32(val)).tower_bit(bit_idx);
1380 let got = elem.tower_bit(0);
1381 assert_eq!(got, expected, "bit {bit_idx} mismatch");
1382 }
1383
1384 let pass = res[32];
1385 assert_eq!(
1386 pass,
1387 <Block128 as hekate_math::FlatPromote<Block32>>::promote_flat(Flat::from_raw(Block32(
1388 pass_val
1389 )))
1390 );
1391
1392 let ctrl = res[33].tower_bit(0);
1393 assert_eq!(ctrl, 1);
1394 }
1395
1396 #[test]
1397 fn expand_variants_b32() {
1398 let expander = VirtualExpander::new()
1399 .expand_bits(1, ColumnType::B32)
1400 .pass_through(1, ColumnType::B32)
1401 .control_bits(1)
1402 .build()
1403 .unwrap();
1404
1405 let layout = [ColumnType::B32, ColumnType::B32, ColumnType::Bit];
1406 let num_vars = 2;
1407
1408 let mut tb = TraceBuilder::new(&layout, num_vars).unwrap();
1409 tb.set_b32(0, 0, Block32(0xAAAA_BBBB)).unwrap();
1410 tb.set_b32(1, 0, Block32(0x1111_2222)).unwrap();
1411 tb.set_bit(2, 0, Bit::ONE).unwrap();
1412
1413 let trace = tb.build();
1414
1415 let variants: Vec<PolyVariant<'_, Block128>> = expander.expand_variants(&trace, 0).unwrap();
1416
1417 assert_eq!(variants.len(), 34);
1418
1419 for (i, v) in variants.iter().enumerate().take(32) {
1420 assert!(matches!(v, PolyVariant::PackedBitB32 { bit_idx, .. } if *bit_idx == i));
1421 }
1422
1423 assert!(matches!(variants[32], PolyVariant::B32Slice(_)));
1424 assert!(matches!(variants[33], PolyVariant::BitSlice(_)));
1425 }
1426
1427 #[test]
1428 fn claim_weights_reproduce_ring_target() {
1429 let layout = [
1430 ColumnType::B32,
1431 ColumnType::B32,
1432 ColumnType::B64,
1433 ColumnType::Bit,
1434 ];
1435
1436 let expander = VirtualExpander::new()
1437 .expand_bits(2, ColumnType::B32)
1438 .pass_through(1, ColumnType::B64)
1439 .control_bits(1)
1440 .build()
1441 .unwrap();
1442
1443 let entries = expander.expansion_entries();
1444 let plan = RingSwitchPlan::new(&layout, Some(&entries), 2, 0).unwrap();
1445
1446 let mut state = 0x9e37_79b9_7f4a_7c15_0123_4567_89ab_cdefu128;
1447 let mut next = || {
1448 state ^= state << 13;
1449 state ^= state >> 7;
1450 state ^= state << 17;
1451
1452 Block128(state)
1453 };
1454
1455 let claims: Vec<Block128> = (0..2 * plan.total_claims()).map(|_| next()).collect();
1456 let eta = next();
1457 let r_mix: Vec<Block128> = (0..7).map(|_| next()).collect();
1458 let eq_mix = eq_tensor_b(&r_mix);
1459
1460 let flat: Vec<Flat<Block128>> = claims.iter().map(|c| c.to_hardware()).collect();
1461 let target = ring_target::<Block128>(&plan, &flat, eta, &r_mix, true);
1462
1463 let mut sum = Block128::ZERO;
1464 for ((is_ring, weight), claim) in claim_weights::<Block128>(&plan, eta, true)
1465 .into_iter()
1466 .zip(&claims)
1467 {
1468 let value = match is_ring {
1469 true => ring_batch_b(&[*claim], &eq_mix),
1470 false => *claim,
1471 };
1472
1473 sum += weight * value;
1474 }
1475
1476 assert_eq!(sum, target);
1477 }
1478
1479 #[test]
1483 fn adaptive_split_clears_floor_whenever_any_split_can() {
1484 let config = Config::prod();
1485
1486 let mut stepped_down = 0;
1487 for cols in [1usize, 4, 16, 64, 128] {
1488 let plan = plan_of(cols);
1489 for num_vars in 10usize..=30 {
1490 let chosen = plan.split_vars(num_vars, 128, &config);
1491 let bits = |c: usize| {
1492 config.estimated_security_bits(
1493 128,
1494 FoldShape {
1495 grid_cols: 1 << c,
1496 grid_rows: 1 << (num_vars - c),
1497 units: plan.num_units,
1498 },
1499 )
1500 };
1501
1502 let optimal = compute_split_vars(
1503 num_vars,
1504 config.num_queries,
1505 config.ldt_support_size,
1506 plan.opened_row_bytes(),
1507 );
1508
1509 assert!(chosen <= optimal, "cols={cols} n={num_vars}: stepped up");
1510 assert!(
1511 chosen >= support_floor_vars(config.ldt_support_size).min(optimal),
1512 "cols={cols} n={num_vars}: below the support floor"
1513 );
1514
1515 if chosen < optimal {
1516 stepped_down += 1;
1517 }
1518
1519 let best = (1..=optimal).map(bits).max().unwrap();
1520
1521 if best >= MIN_PRODUCTION_BITS {
1522 assert!(
1523 bits(chosen) >= MIN_PRODUCTION_BITS,
1524 "cols={cols} n={num_vars}: chose {chosen} at {} bits, \
1525 but some split reaches {best}",
1526 bits(chosen),
1527 );
1528 }
1529 }
1530 }
1531
1532 assert!(stepped_down > 0, "the adaptive walk never fired");
1533 }
1534}