Skip to main content

hekate_core/
trace.rs

1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>. All rights reserved.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::errors;
19use crate::poly::variant::PolyVariant;
20use alloc::vec;
21use alloc::vec::Vec;
22use core::any::TypeId;
23use core::fmt;
24use core::mem::transmute;
25use hekate_math::{
26    Bit, Block8, Block16, Block32, Block64, Block128, CanonicalSerialize, Flat, FlatPromote,
27    HardwareField, PackableField, TowerField,
28};
29use zeroize::Zeroize;
30#[cfg(feature = "secure-memory")]
31use zeroize::ZeroizeOnDrop;
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum Error {
35    InvalidParameters {
36        message: &'static str,
37    },
38    ColumnLengthMismatch {
39        expected_len: usize,
40        got_len: usize,
41    },
42    ColumnIndexOutOfBounds {
43        col_idx: usize,
44        num_cols: usize,
45    },
46    RowIndexOutOfBounds {
47        row_idx: usize,
48        num_rows: usize,
49    },
50    PointDimensionMismatch {
51        expected_len: usize,
52        got_len: usize,
53    },
54    ColumnTypeMismatch {
55        col_idx: usize,
56        expected: &'static str,
57        got: &'static str,
58    },
59}
60
61impl fmt::Display for Error {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::InvalidParameters { message } => {
65                write!(f, "Trace invalid parameters: {message}")
66            }
67            Self::ColumnLengthMismatch {
68                expected_len,
69                got_len,
70            } => write!(
71                f,
72                "Trace column length mismatch: expected {expected_len}, got {got_len}",
73            ),
74            Self::ColumnIndexOutOfBounds { col_idx, num_cols } => write!(
75                f,
76                "Trace column index out of bounds: col_idx={col_idx}, num_cols={num_cols}",
77            ),
78            Self::RowIndexOutOfBounds { row_idx, num_rows } => write!(
79                f,
80                "Trace row index out of bounds: row_idx={row_idx}, num_rows={num_rows}",
81            ),
82            Self::PointDimensionMismatch {
83                expected_len,
84                got_len,
85            } => write!(
86                f,
87                "Trace evaluation point dimension mismatch: expected {expected_len}, got {got_len}",
88            ),
89            Self::ColumnTypeMismatch {
90                col_idx,
91                expected,
92                got,
93            } => write!(
94                f,
95                "Trace column type mismatch at col_idx={col_idx}: expected {expected}, got {got}",
96            ),
97        }
98    }
99}
100
101/// Bound for the proving field `F`:
102/// it must losslessly represent every
103/// `ColumnType` used by the trace
104/// (Bit, B8, B16, B32, B64, B128).
105pub trait TraceCompatibleField:
106    TowerField
107    + HardwareField
108    + PackableField
109    + FlatPromote<Block8>
110    + FlatPromote<Block16>
111    + FlatPromote<Block32>
112    + FlatPromote<Block64>
113    + FlatPromote<Block128>
114    + From<Bit>
115    + From<Block8>
116    + From<Block16>
117    + From<Block32>
118    + From<Block64>
119    + From<Block128>
120    + Send
121    + Sync
122{
123}
124
125impl<T> TraceCompatibleField for T where
126    T: TowerField
127        + HardwareField
128        + PackableField
129        + FlatPromote<Block8>
130        + FlatPromote<Block16>
131        + FlatPromote<Block32>
132        + FlatPromote<Block64>
133        + FlatPromote<Block128>
134        + From<Bit>
135        + From<Block8>
136        + From<Block16>
137        + From<Block32>
138        + From<Block64>
139        + From<Block128>
140        + Send
141        + Sync
142{
143}
144
145// =========================================================
146// TRACE TRAIT DEFINITION
147// =========================================================
148
149/// Execution-trace interface. Separates physical
150/// storage (`TraceColumn`) from the virtual
151/// polynomial view consumed by Sumcheck.
152pub trait Trace: Send + Sync {
153    /// `log2` of the trace height.
154    fn num_vars(&self) -> usize;
155
156    fn columns(&self) -> &[TraceColumn];
157
158    /// `2^num_vars`.
159    fn num_rows(&self) -> errors::Result<usize> {
160        num_rows_from_num_vars(self.num_vars())
161    }
162
163    fn num_cols(&self) -> usize {
164        self.columns().len()
165    }
166
167    fn column_layout(&self) -> Vec<ColumnType> {
168        self.columns().iter().map(|col| col.column_type()).collect()
169    }
170
171    /// Read a single trace cell and lift
172    /// it into `F` in the flat/hardware basis.
173    fn get_element<F: TraceCompatibleField>(
174        &self,
175        col_idx: usize,
176        row_idx: usize,
177    ) -> errors::Result<Flat<F>> {
178        let cols = self.columns();
179        let num_cols = self.num_cols();
180
181        if col_idx >= num_cols {
182            return Err(Error::ColumnIndexOutOfBounds { col_idx, num_cols }.into());
183        }
184
185        let num_rows = self.num_rows()?;
186        if row_idx >= num_rows {
187            return Err(Error::RowIndexOutOfBounds { row_idx, num_rows }.into());
188        }
189
190        match &cols[col_idx] {
191            TraceColumn::Bit(v) => Ok(Flat::from_raw(F::from(v[row_idx]))),
192            TraceColumn::B8(v) => Ok(F::promote_flat(v[row_idx])),
193            TraceColumn::B16(v) => Ok(F::promote_flat(v[row_idx])),
194            TraceColumn::B32(v) => Ok(F::promote_flat(v[row_idx])),
195            TraceColumn::B64(v) => Ok(F::promote_flat(v[row_idx])),
196            TraceColumn::B128(v) => Ok(F::promote_flat(v[row_idx])),
197        }
198    }
199
200    /// Zero-copy typed slice over a column.
201    /// Fails if `F` does not match
202    /// the column's storage type.
203    fn get_column_slice<F: 'static>(&self, col_idx: usize) -> errors::Result<&[F]> {
204        let cols = self.columns();
205        let num_cols = self.num_cols();
206
207        if col_idx >= num_cols {
208            return Err(Error::ColumnIndexOutOfBounds { col_idx, num_cols }.into());
209        }
210
211        let got = core::any::type_name::<F>();
212
213        match &cols[col_idx] {
214            TraceColumn::Bit(vec) => {
215                if TypeId::of::<F>() != TypeId::of::<Bit>() {
216                    return Err(Error::ColumnTypeMismatch {
217                        col_idx,
218                        expected: "Bit",
219                        got,
220                    }
221                    .into());
222                }
223
224                // SAFETY:
225                // The TypeId check guarantees F == Bit.
226                Ok(unsafe { transmute::<&[Bit], &[F]>(vec.as_slice()) })
227            }
228            TraceColumn::B8(vec) => {
229                if TypeId::of::<F>() != TypeId::of::<Flat<Block8>>() {
230                    return Err(Error::ColumnTypeMismatch {
231                        col_idx,
232                        expected: "Flat<Block8>",
233                        got,
234                    }
235                    .into());
236                }
237                Ok(unsafe { transmute::<&[Flat<Block8>], &[F]>(vec.as_slice()) })
238            }
239            TraceColumn::B16(vec) => {
240                if TypeId::of::<F>() != TypeId::of::<Flat<Block16>>() {
241                    return Err(Error::ColumnTypeMismatch {
242                        col_idx,
243                        expected: "Flat<Block16>",
244                        got,
245                    }
246                    .into());
247                }
248                Ok(unsafe { transmute::<&[Flat<Block16>], &[F]>(vec.as_slice()) })
249            }
250            TraceColumn::B32(vec) => {
251                if TypeId::of::<F>() != TypeId::of::<Flat<Block32>>() {
252                    return Err(Error::ColumnTypeMismatch {
253                        col_idx,
254                        expected: "Flat<Block32>",
255                        got,
256                    }
257                    .into());
258                }
259                Ok(unsafe { transmute::<&[Flat<Block32>], &[F]>(vec.as_slice()) })
260            }
261            TraceColumn::B64(vec) => {
262                if TypeId::of::<F>() != TypeId::of::<Flat<Block64>>() {
263                    return Err(Error::ColumnTypeMismatch {
264                        col_idx,
265                        expected: "Flat<Block64>",
266                        got,
267                    }
268                    .into());
269                }
270                Ok(unsafe { transmute::<&[Flat<Block64>], &[F]>(vec.as_slice()) })
271            }
272            TraceColumn::B128(vec) => {
273                if TypeId::of::<F>() != TypeId::of::<Flat<Block128>>() {
274                    return Err(Error::ColumnTypeMismatch {
275                        col_idx,
276                        expected: "Flat<Block128>",
277                        got,
278                    }
279                    .into());
280                }
281                Ok(unsafe { transmute::<&[Flat<Block128>], &[F]>(vec.as_slice()) })
282            }
283        }
284    }
285
286    /// Map physical columns to the `PolyVariant`s
287    /// consumed by Sumcheck. Default is a 1:1
288    /// `BitSlice` / `B{N}Slice` mapping; chiplets
289    /// that pack data (e.g. Keccak) override this
290    /// to expose virtual bit-columns.
291    fn get_poly_variants<F>(&'_ self) -> errors::Result<Vec<PolyVariant<'_, F>>>
292    where
293        F: TraceCompatibleField + 'static,
294    {
295        let cols = self.columns();
296        let mut variants = Vec::with_capacity(cols.len());
297
298        for (i, col) in cols.iter().enumerate() {
299            if i >= self.num_cols() {
300                return Err(errors::Error::Protocol {
301                    protocol: "air",
302                    message: "trace has fewer columns than required by AIR",
303                });
304            }
305
306            let variant = if let Some(s) = col.as_bit_slice() {
307                PolyVariant::BitSlice(s)
308            } else if let Some(s) = col.as_b8_slice() {
309                PolyVariant::B8Slice(s)
310            } else if let Some(s) = col.as_b16_slice() {
311                PolyVariant::B16Slice(s)
312            } else if let Some(s) = col.as_b32_slice() {
313                PolyVariant::B32Slice(s)
314            } else if let Some(s) = col.as_b64_slice() {
315                PolyVariant::B64Slice(s)
316            } else if let Some(s) = col.as_b128_slice() {
317                PolyVariant::B128Slice(s)
318            } else {
319                return Err(errors::Error::Protocol {
320                    protocol: "air",
321                    message: "unsupported trace column variant",
322                });
323            };
324
325            variants.push(variant);
326        }
327
328        Ok(variants)
329    }
330}
331
332// =========================================================
333// COLUMN LAYOUT METADATA
334// =========================================================
335
336/// Column storage type, without the data itself.
337/// Mixed-field traces require the verifier to know
338/// the exact byte width of every opened column in
339/// order to parse the raw LDT bytes.
340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
341pub enum ColumnType {
342    /// AIR authors MUST `cs.assert_boolean(cs.col(idx))`
343    /// on every `Bit` used as selector, constraint operand,
344    /// or LogUp source, parse is byte-preserving, the
345    /// verifier accepts any byte and lifts it to `F`.
346    Bit,
347    B8,
348    B16,
349    B32,
350    B64,
351    B128,
352}
353
354impl ColumnType {
355    #[inline]
356    pub const fn byte_size(&self) -> usize {
357        match self {
358            Self::Bit => 1,
359            Self::B8 => 1,
360            Self::B16 => 2,
361            Self::B32 => 4,
362            Self::B64 => 8,
363            Self::B128 => 16,
364        }
365    }
366
367    /// Parse a field element from its on-wire
368    /// bytes (little-endian, hardware basis)
369    /// without intermediate allocation.
370    pub fn parse_from_bytes<F>(&self, bytes: &[u8]) -> Flat<F>
371    where
372        F: TraceCompatibleField,
373    {
374        match self {
375            Self::Bit => Flat::from_raw(F::from(Bit(bytes[0]))),
376            Self::B8 => F::promote_flat(Flat::from_raw(Block8(bytes[0]))),
377            Self::B16 => {
378                let mut buf = [0u8; 2];
379                buf.copy_from_slice(&bytes[0..2]);
380
381                F::promote_flat(Flat::from_raw(Block16(u16::from_le_bytes(buf))))
382            }
383            Self::B32 => {
384                let mut buf = [0u8; 4];
385                buf.copy_from_slice(&bytes[0..4]);
386
387                F::promote_flat(Flat::from_raw(Block32(u32::from_le_bytes(buf))))
388            }
389            Self::B64 => {
390                let mut buf = [0u8; 8];
391                buf.copy_from_slice(&bytes[0..8]);
392
393                F::promote_flat(Flat::from_raw(Block64(u64::from_le_bytes(buf))))
394            }
395            Self::B128 => {
396                let mut buf = [0u8; 16];
397                buf.copy_from_slice(&bytes[0..16]);
398
399                F::promote_flat(Flat::from_raw(Block128(u128::from_le_bytes(buf))))
400            }
401        }
402    }
403}
404
405/// One typed column of the execution trace.
406/// Stored in hardware (flat) basis for all
407/// non-`Bit` variants.
408#[derive(Clone, Debug, Zeroize)]
409#[cfg_attr(feature = "secure-memory", derive(ZeroizeOnDrop))]
410pub enum TraceColumn {
411    Bit(Vec<Bit>),
412    B8(Vec<Flat<Block8>>),
413    B16(Vec<Flat<Block16>>),
414    B32(Vec<Flat<Block32>>),
415    B64(Vec<Flat<Block64>>),
416    B128(Vec<Flat<Block128>>),
417}
418
419impl TraceColumn {
420    /// Narrow a vector of `Block128` into a column
421    /// of `target_type` by truncating to the low bytes.
422    pub fn from_data(data: Vec<Block128>, target_type: ColumnType) -> Self {
423        match target_type {
424            ColumnType::Bit => {
425                let converted: Vec<Bit> = data
426                    .iter()
427                    .map(|val| {
428                        let bytes = val.to_bytes();
429                        Bit::from(bytes[0] & 1)
430                    })
431                    .collect();
432                TraceColumn::Bit(converted)
433            }
434            ColumnType::B8 => {
435                let converted: Vec<Flat<Block8>> = data
436                    .iter()
437                    .map(|val| {
438                        let bytes = val.to_bytes();
439                        Block8::from(bytes[0]).to_hardware()
440                    })
441                    .collect();
442                TraceColumn::B8(converted)
443            }
444            ColumnType::B16 => {
445                let converted: Vec<Flat<Block16>> = data
446                    .iter()
447                    .map(|val| {
448                        let bytes = val.to_bytes();
449                        let mut chunk = [0u8; 2];
450                        chunk.copy_from_slice(&bytes[0..2]);
451
452                        Block16::from(u16::from_le_bytes(chunk)).to_hardware()
453                    })
454                    .collect();
455                TraceColumn::B16(converted)
456            }
457            ColumnType::B32 => {
458                let converted: Vec<Flat<Block32>> = data
459                    .iter()
460                    .map(|val| {
461                        let bytes = val.to_bytes();
462                        let mut chunk = [0u8; 4];
463                        chunk.copy_from_slice(&bytes[0..4]);
464
465                        Block32::from(u32::from_le_bytes(chunk)).to_hardware()
466                    })
467                    .collect();
468                TraceColumn::B32(converted)
469            }
470            ColumnType::B64 => {
471                let converted: Vec<Flat<Block64>> = data
472                    .iter()
473                    .map(|val| {
474                        let bytes = val.to_bytes();
475                        let mut chunk = [0u8; 8];
476                        chunk.copy_from_slice(&bytes[0..8]);
477
478                        Block64::from(u64::from_le_bytes(chunk)).to_hardware()
479                    })
480                    .collect();
481                TraceColumn::B64(converted)
482            }
483            ColumnType::B128 => {
484                TraceColumn::B128(data.into_iter().map(|value| value.to_hardware()).collect())
485            }
486        }
487    }
488
489    pub fn len(&self) -> usize {
490        match self {
491            Self::Bit(v) => v.len(),
492            Self::B8(v) => v.len(),
493            Self::B16(v) => v.len(),
494            Self::B32(v) => v.len(),
495            Self::B64(v) => v.len(),
496            Self::B128(v) => v.len(),
497        }
498    }
499
500    pub fn is_empty(&self) -> bool {
501        self.len() == 0
502    }
503
504    pub fn is_all_zeros(&self) -> bool {
505        match self {
506            Self::Bit(v) => v.iter().all(|x| x.0 == 0),
507            Self::B8(v) => v.iter().all(|x| x.into_raw().0 == 0),
508            Self::B16(v) => v.iter().all(|x| x.into_raw().0 == 0),
509            Self::B32(v) => v.iter().all(|x| x.into_raw().0 == 0),
510            Self::B64(v) => v.iter().all(|x| x.into_raw().0 == 0),
511            Self::B128(v) => v.iter().all(|x| x.into_raw() == Block128::ZERO),
512        }
513    }
514
515    /// The `ColumnType` tag matching this variant.
516    pub fn column_type(&self) -> ColumnType {
517        match self {
518            Self::Bit(_) => ColumnType::Bit,
519            Self::B8(_) => ColumnType::B8,
520            Self::B16(_) => ColumnType::B16,
521            Self::B32(_) => ColumnType::B32,
522            Self::B64(_) => ColumnType::B64,
523            Self::B128(_) => ColumnType::B128,
524        }
525    }
526
527    /// Append this row's little-endian
528    /// serialization to `buf`. Used for
529    /// Merkle leaf hashing and LDT opening bytes.
530    pub fn append_bytes_at(&self, row_idx: usize, buf: &mut Vec<u8>) {
531        match self {
532            Self::Bit(v) => {
533                buf.push(v[row_idx].0);
534            }
535            Self::B8(v) => {
536                buf.push(v[row_idx].into_raw().0);
537            }
538            Self::B16(v) => {
539                buf.extend_from_slice(&v[row_idx].into_raw().0.to_le_bytes());
540            }
541            Self::B32(v) => {
542                buf.extend_from_slice(&v[row_idx].into_raw().0.to_le_bytes());
543            }
544            Self::B64(v) => {
545                buf.extend_from_slice(&v[row_idx].into_raw().0.to_le_bytes());
546            }
547            Self::B128(v) => {
548                buf.extend_from_slice(&v[row_idx].into_raw().0.to_le_bytes());
549            }
550        }
551    }
552
553    // ===========================================
554    // Typed slice accessors
555    // ===========================================
556
557    pub fn as_bit_slice(&self) -> Option<&[Bit]> {
558        if let Self::Bit(v) = self {
559            Some(v)
560        } else {
561            None
562        }
563    }
564
565    pub fn as_b8_slice(&self) -> Option<&[Flat<Block8>]> {
566        if let Self::B8(v) = self {
567            Some(v)
568        } else {
569            None
570        }
571    }
572    pub fn as_b16_slice(&self) -> Option<&[Flat<Block16>]> {
573        if let Self::B16(v) = self {
574            Some(v)
575        } else {
576            None
577        }
578    }
579    pub fn as_b32_slice(&self) -> Option<&[Flat<Block32>]> {
580        if let Self::B32(v) = self {
581            Some(v)
582        } else {
583            None
584        }
585    }
586    pub fn as_b64_slice(&self) -> Option<&[Flat<Block64>]> {
587        if let Self::B64(v) = self {
588            Some(v)
589        } else {
590            None
591        }
592    }
593    pub fn as_b128_slice(&self) -> Option<&[Flat<Block128>]> {
594        if let Self::B128(v) = self {
595            Some(v)
596        } else {
597            None
598        }
599    }
600}
601
602/// A concrete implementation of
603/// Trace using column-major storage.
604#[derive(Clone, Debug, Zeroize)]
605#[cfg_attr(feature = "secure-memory", derive(ZeroizeOnDrop))]
606pub struct ColumnTrace {
607    pub columns: Vec<TraceColumn>,
608    pub num_vars: usize,
609}
610
611impl Trace for ColumnTrace {
612    fn num_vars(&self) -> usize {
613        self.num_vars
614    }
615
616    fn columns(&self) -> &[TraceColumn] {
617        &self.columns
618    }
619}
620
621impl ColumnTrace {
622    pub fn new(num_vars: usize) -> errors::Result<Self> {
623        let num_rows = num_rows_from_num_vars(num_vars)?;
624        if num_rows == 0 {
625            return Err(Error::InvalidParameters {
626                message: "trace height is zero",
627            }
628            .into());
629        }
630
631        Ok(Self {
632            num_vars,
633            columns: Vec::new(),
634        })
635    }
636
637    /// Consume the trace and return
638    /// its owned column storage.
639    pub fn into_columns(mut self) -> Vec<TraceColumn> {
640        core::mem::take(&mut self.columns)
641    }
642
643    pub fn add_column(&mut self, col: TraceColumn) -> errors::Result<()> {
644        let expected_len = self.num_rows()?;
645        let got_len = col.len();
646
647        if got_len != expected_len {
648            return Err(Error::ColumnLengthMismatch {
649                expected_len,
650                got_len,
651            }
652            .into());
653        }
654
655        self.columns.push(col);
656
657        Ok(())
658    }
659}
660
661pub trait IntoTraceColumn {
662    fn into_trace_column(self) -> TraceColumn;
663}
664
665impl IntoTraceColumn for Vec<Bit> {
666    fn into_trace_column(self) -> TraceColumn {
667        TraceColumn::Bit(self)
668    }
669}
670
671impl IntoTraceColumn for Vec<Block8> {
672    fn into_trace_column(self) -> TraceColumn {
673        TraceColumn::B8(self.into_iter().map(|value| value.to_hardware()).collect())
674    }
675}
676
677impl IntoTraceColumn for Vec<Block16> {
678    fn into_trace_column(self) -> TraceColumn {
679        TraceColumn::B16(self.into_iter().map(|value| value.to_hardware()).collect())
680    }
681}
682
683impl IntoTraceColumn for Vec<Block32> {
684    fn into_trace_column(self) -> TraceColumn {
685        TraceColumn::B32(self.into_iter().map(|value| value.to_hardware()).collect())
686    }
687}
688
689impl IntoTraceColumn for Vec<Block64> {
690    fn into_trace_column(self) -> TraceColumn {
691        TraceColumn::B64(self.into_iter().map(|value| value.to_hardware()).collect())
692    }
693}
694
695impl IntoTraceColumn for Vec<Block128> {
696    fn into_trace_column(self) -> TraceColumn {
697        TraceColumn::B128(self.into_iter().map(|value| value.to_hardware()).collect())
698    }
699}
700
701impl IntoTraceColumn for Vec<Flat<Block8>> {
702    fn into_trace_column(self) -> TraceColumn {
703        TraceColumn::B8(self)
704    }
705}
706
707impl IntoTraceColumn for Vec<Flat<Block16>> {
708    fn into_trace_column(self) -> TraceColumn {
709        TraceColumn::B16(self)
710    }
711}
712
713impl IntoTraceColumn for Vec<Flat<Block32>> {
714    fn into_trace_column(self) -> TraceColumn {
715        TraceColumn::B32(self)
716    }
717}
718
719impl IntoTraceColumn for Vec<Flat<Block64>> {
720    fn into_trace_column(self) -> TraceColumn {
721        TraceColumn::B64(self)
722    }
723}
724
725impl IntoTraceColumn for Vec<Flat<Block128>> {
726    fn into_trace_column(self) -> TraceColumn {
727        TraceColumn::B128(self)
728    }
729}
730
731/// Zero-copy byte views `(ptr, elem_width)` for
732/// every column in a trace. Centralizes the
733/// `#[repr(transparent)]`-dependent pointer casts.
734pub fn get_col_views(columns: &[TraceColumn]) -> Vec<(&[u8], usize)> {
735    columns
736        .iter()
737        .map(|col| match col {
738            TraceColumn::Bit(v) => (
739                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len()) },
740                1,
741            ),
742            TraceColumn::B8(v) => (
743                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len()) },
744                1,
745            ),
746            TraceColumn::B16(v) => (
747                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 2) },
748                2,
749            ),
750            TraceColumn::B32(v) => (
751                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4) },
752                4,
753            ),
754            TraceColumn::B64(v) => (
755                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 8) },
756                8,
757            ),
758            TraceColumn::B128(v) => (
759                unsafe { core::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 16) },
760                16,
761            ),
762        })
763        .collect()
764}
765
766// =========================================================
767// TRACE BUILDER
768// =========================================================
769
770/// Schema-driven builder. Every column
771/// is allocated zero-filled from the layout;
772/// unfilled rows stay zero, so padding is implicit.
773pub struct TraceBuilder {
774    columns: Vec<TraceColumn>,
775    num_vars: usize,
776    num_rows: usize,
777    cursors: Vec<usize>,
778}
779
780impl TraceBuilder {
781    pub fn new(layout: &[ColumnType], num_vars: usize) -> errors::Result<Self> {
782        let num_rows = num_rows_from_num_vars(num_vars)?;
783        let columns = layout
784            .iter()
785            .map(|ct| match ct {
786                ColumnType::Bit => TraceColumn::Bit(vec![Bit::ZERO; num_rows]),
787                ColumnType::B8 => TraceColumn::B8(vec![Block8::ZERO.to_hardware(); num_rows]),
788                ColumnType::B16 => TraceColumn::B16(vec![Block16::ZERO.to_hardware(); num_rows]),
789                ColumnType::B32 => TraceColumn::B32(vec![Block32::ZERO.to_hardware(); num_rows]),
790                ColumnType::B64 => TraceColumn::B64(vec![Block64::ZERO.to_hardware(); num_rows]),
791                ColumnType::B128 => TraceColumn::B128(vec![Block128::ZERO.to_hardware(); num_rows]),
792            })
793            .collect();
794
795        Ok(Self {
796            columns,
797            num_vars,
798            num_rows,
799            cursors: vec![0; layout.len()],
800        })
801    }
802
803    /// `2^num_vars`.
804    #[inline]
805    pub fn num_rows(&self) -> usize {
806        self.num_rows
807    }
808
809    // =========================================================
810    // Indexed write (random access)
811    // =========================================================
812
813    #[inline]
814    pub fn set_bit(&mut self, col: usize, row: usize, val: Bit) -> errors::Result<()> {
815        let num_rows = self.num_rows;
816        let data = self.expect_bit_col(col)?;
817        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
818            row_idx: row,
819            num_rows,
820        })?;
821
822        *slot = val;
823
824        Ok(())
825    }
826
827    #[inline]
828    pub fn set_b8(&mut self, col: usize, row: usize, val: Block8) -> errors::Result<()> {
829        let num_rows = self.num_rows;
830        let data = self.expect_b8_col(col)?;
831        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
832            row_idx: row,
833            num_rows,
834        })?;
835
836        *slot = val.to_hardware();
837
838        Ok(())
839    }
840
841    #[inline]
842    pub fn set_b16(&mut self, col: usize, row: usize, val: Block16) -> errors::Result<()> {
843        let num_rows = self.num_rows;
844        let data = self.expect_b16_col(col)?;
845        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
846            row_idx: row,
847            num_rows,
848        })?;
849
850        *slot = val.to_hardware();
851
852        Ok(())
853    }
854
855    #[inline]
856    pub fn set_b32(&mut self, col: usize, row: usize, val: Block32) -> errors::Result<()> {
857        let num_rows = self.num_rows;
858        let data = self.expect_b32_col(col)?;
859        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
860            row_idx: row,
861            num_rows,
862        })?;
863
864        *slot = val.to_hardware();
865
866        Ok(())
867    }
868
869    #[inline]
870    pub fn set_b64(&mut self, col: usize, row: usize, val: Block64) -> errors::Result<()> {
871        let num_rows = self.num_rows;
872        let data = self.expect_b64_col(col)?;
873        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
874            row_idx: row,
875            num_rows,
876        })?;
877
878        *slot = val.to_hardware();
879
880        Ok(())
881    }
882
883    #[inline]
884    pub fn set_b128(&mut self, col: usize, row: usize, val: Block128) -> errors::Result<()> {
885        let num_rows = self.num_rows;
886        let data = self.expect_b128_col(col)?;
887        let slot = data.get_mut(row).ok_or(Error::RowIndexOutOfBounds {
888            row_idx: row,
889            num_rows,
890        })?;
891
892        *slot = val.to_hardware();
893
894        Ok(())
895    }
896
897    // =========================================================
898    // Push write (sequential overwrite-at-cursor)
899    // =========================================================
900
901    #[inline]
902    pub fn push_bit(&mut self, col: usize, val: Bit) -> errors::Result<()> {
903        let row = self.cursor(col)?;
904        self.set_bit(col, row, val)?;
905
906        self.cursors[col] = row + 1;
907
908        Ok(())
909    }
910
911    #[inline]
912    pub fn push_b8(&mut self, col: usize, val: Block8) -> errors::Result<()> {
913        let row = self.cursor(col)?;
914        self.set_b8(col, row, val)?;
915
916        self.cursors[col] = row + 1;
917
918        Ok(())
919    }
920
921    #[inline]
922    pub fn push_b16(&mut self, col: usize, val: Block16) -> errors::Result<()> {
923        let row = self.cursor(col)?;
924        self.set_b16(col, row, val)?;
925
926        self.cursors[col] = row + 1;
927
928        Ok(())
929    }
930
931    #[inline]
932    pub fn push_b32(&mut self, col: usize, val: Block32) -> errors::Result<()> {
933        let row = self.cursor(col)?;
934        self.set_b32(col, row, val)?;
935
936        self.cursors[col] = row + 1;
937
938        Ok(())
939    }
940
941    #[inline]
942    pub fn push_b64(&mut self, col: usize, val: Block64) -> errors::Result<()> {
943        let row = self.cursor(col)?;
944        self.set_b64(col, row, val)?;
945
946        self.cursors[col] = row + 1;
947
948        Ok(())
949    }
950
951    #[inline]
952    pub fn push_b128(&mut self, col: usize, val: Block128) -> errors::Result<()> {
953        let row = self.cursor(col)?;
954        self.set_b128(col, row, val)?;
955
956        self.cursors[col] = row + 1;
957
958        Ok(())
959    }
960
961    // =========================================================
962    // Array column helpers
963    // =========================================================
964
965    pub fn set_bit_array(&mut self, base: usize, row: usize, values: &[Bit]) -> errors::Result<()> {
966        for (i, &val) in values.iter().enumerate() {
967            self.set_bit(base + i, row, val)?;
968        }
969
970        Ok(())
971    }
972
973    pub fn set_b8_array(
974        &mut self,
975        base: usize,
976        row: usize,
977        values: &[Block8],
978    ) -> errors::Result<()> {
979        for (i, &val) in values.iter().enumerate() {
980            self.set_b8(base + i, row, val)?;
981        }
982
983        Ok(())
984    }
985
986    pub fn set_b16_array(
987        &mut self,
988        base: usize,
989        row: usize,
990        values: &[Block16],
991    ) -> errors::Result<()> {
992        for (i, &val) in values.iter().enumerate() {
993            self.set_b16(base + i, row, val)?;
994        }
995
996        Ok(())
997    }
998
999    pub fn set_b32_array(
1000        &mut self,
1001        base: usize,
1002        row: usize,
1003        values: &[Block32],
1004    ) -> errors::Result<()> {
1005        for (i, &val) in values.iter().enumerate() {
1006            self.set_b32(base + i, row, val)?;
1007        }
1008
1009        Ok(())
1010    }
1011
1012    pub fn set_b64_array(
1013        &mut self,
1014        base: usize,
1015        row: usize,
1016        values: &[Block64],
1017    ) -> errors::Result<()> {
1018        for (i, &val) in values.iter().enumerate() {
1019            self.set_b64(base + i, row, val)?;
1020        }
1021
1022        Ok(())
1023    }
1024
1025    pub fn set_b128_array(
1026        &mut self,
1027        base: usize,
1028        row: usize,
1029        values: &[Block128],
1030    ) -> errors::Result<()> {
1031        for (i, &val) in values.iter().enumerate() {
1032            self.set_b128(base + i, row, val)?;
1033        }
1034
1035        Ok(())
1036    }
1037
1038    // =========================================================
1039    // Selector helpers
1040    // =========================================================
1041
1042    /// Write `ONE` into rows `[0, active_rows)`
1043    /// of a `Bit` column. The tail stays zero from allocation.
1044    pub fn fill_selector(&mut self, col: usize, active_rows: usize) -> errors::Result<()> {
1045        let limit = active_rows.min(self.num_rows);
1046        let data = self.expect_bit_col(col)?;
1047
1048        for slot in data.iter_mut().take(limit) {
1049            *slot = Bit::ONE;
1050        }
1051
1052        Ok(())
1053    }
1054
1055    // =========================================================
1056    // Finalization
1057    // =========================================================
1058
1059    /// Consume the builder and return a `ColumnTrace`.
1060    /// Column order matches the schema passed to `new`.
1061    pub fn build(self) -> ColumnTrace {
1062        ColumnTrace {
1063            columns: self.columns,
1064            num_vars: self.num_vars,
1065        }
1066    }
1067
1068    // =========================================================
1069    // Internal helpers
1070    // =========================================================
1071
1072    #[inline]
1073    fn cursor(&self, col: usize) -> errors::Result<usize> {
1074        self.cursors.get(col).copied().ok_or_else(|| {
1075            Error::ColumnIndexOutOfBounds {
1076                col_idx: col,
1077                num_cols: self.columns.len(),
1078            }
1079            .into()
1080        })
1081    }
1082
1083    #[inline]
1084    fn expect_bit_col(&mut self, col: usize) -> errors::Result<&mut Vec<Bit>> {
1085        let num_cols = self.columns.len();
1086        let tc = self
1087            .columns
1088            .get_mut(col)
1089            .ok_or(Error::ColumnIndexOutOfBounds {
1090                col_idx: col,
1091                num_cols,
1092            })?;
1093
1094        match tc {
1095            TraceColumn::Bit(data) => Ok(data),
1096            other => Err(Error::ColumnTypeMismatch {
1097                col_idx: col,
1098                expected: "Bit",
1099                got: other.column_type_name(),
1100            }
1101            .into()),
1102        }
1103    }
1104
1105    #[inline]
1106    fn expect_b8_col(&mut self, col: usize) -> errors::Result<&mut Vec<Flat<Block8>>> {
1107        let num_cols = self.columns.len();
1108        let tc = self
1109            .columns
1110            .get_mut(col)
1111            .ok_or(Error::ColumnIndexOutOfBounds {
1112                col_idx: col,
1113                num_cols,
1114            })?;
1115
1116        match tc {
1117            TraceColumn::B8(data) => Ok(data),
1118            other => Err(Error::ColumnTypeMismatch {
1119                col_idx: col,
1120                expected: "B8",
1121                got: other.column_type_name(),
1122            }
1123            .into()),
1124        }
1125    }
1126
1127    #[inline]
1128    fn expect_b16_col(&mut self, col: usize) -> errors::Result<&mut Vec<Flat<Block16>>> {
1129        let num_cols = self.columns.len();
1130        let tc = self
1131            .columns
1132            .get_mut(col)
1133            .ok_or(Error::ColumnIndexOutOfBounds {
1134                col_idx: col,
1135                num_cols,
1136            })?;
1137
1138        match tc {
1139            TraceColumn::B16(data) => Ok(data),
1140            other => Err(Error::ColumnTypeMismatch {
1141                col_idx: col,
1142                expected: "B16",
1143                got: other.column_type_name(),
1144            }
1145            .into()),
1146        }
1147    }
1148
1149    #[inline]
1150    fn expect_b32_col(&mut self, col: usize) -> errors::Result<&mut Vec<Flat<Block32>>> {
1151        let num_cols = self.columns.len();
1152        let tc = self
1153            .columns
1154            .get_mut(col)
1155            .ok_or(Error::ColumnIndexOutOfBounds {
1156                col_idx: col,
1157                num_cols,
1158            })?;
1159
1160        match tc {
1161            TraceColumn::B32(data) => Ok(data),
1162            other => Err(Error::ColumnTypeMismatch {
1163                col_idx: col,
1164                expected: "B32",
1165                got: other.column_type_name(),
1166            }
1167            .into()),
1168        }
1169    }
1170
1171    #[inline]
1172    fn expect_b64_col(&mut self, col: usize) -> errors::Result<&mut Vec<Flat<Block64>>> {
1173        let num_cols = self.columns.len();
1174        let tc = self
1175            .columns
1176            .get_mut(col)
1177            .ok_or(Error::ColumnIndexOutOfBounds {
1178                col_idx: col,
1179                num_cols,
1180            })?;
1181
1182        match tc {
1183            TraceColumn::B64(data) => Ok(data),
1184            other => Err(Error::ColumnTypeMismatch {
1185                col_idx: col,
1186                expected: "B64",
1187                got: other.column_type_name(),
1188            }
1189            .into()),
1190        }
1191    }
1192
1193    #[inline]
1194    fn expect_b128_col(&mut self, col: usize) -> errors::Result<&mut Vec<Flat<Block128>>> {
1195        let num_cols = self.columns.len();
1196        let tc = self
1197            .columns
1198            .get_mut(col)
1199            .ok_or(Error::ColumnIndexOutOfBounds {
1200                col_idx: col,
1201                num_cols,
1202            })?;
1203
1204        match tc {
1205            TraceColumn::B128(data) => Ok(data),
1206            other => Err(Error::ColumnTypeMismatch {
1207                col_idx: col,
1208                expected: "B128",
1209                got: other.column_type_name(),
1210            }
1211            .into()),
1212        }
1213    }
1214}
1215
1216impl TraceColumn {
1217    fn column_type_name(&self) -> &'static str {
1218        match self {
1219            Self::Bit(_) => "Bit",
1220            Self::B8(_) => "B8",
1221            Self::B16(_) => "B16",
1222            Self::B32(_) => "B32",
1223            Self::B64(_) => "B64",
1224            Self::B128(_) => "B128",
1225        }
1226    }
1227}
1228
1229fn num_rows_from_num_vars(num_vars: usize) -> errors::Result<usize> {
1230    let num_vars_u32 = match u32::try_from(num_vars) {
1231        Ok(v) => v,
1232        Err(_) => {
1233            return Err(Error::InvalidParameters {
1234                message: "num_vars too large",
1235            }
1236            .into());
1237        }
1238    };
1239
1240    let Some(num_rows) = 1usize.checked_shl(num_vars_u32) else {
1241        return Err(Error::InvalidParameters {
1242            message: "num_rows overflow",
1243        }
1244        .into());
1245    };
1246
1247    if num_rows == 0 {
1248        return Err(Error::InvalidParameters {
1249            message: "num_rows is zero",
1250        }
1251        .into());
1252    }
1253
1254    Ok(num_rows)
1255}
1256
1257// =================================================================
1258// UNIT TESTS
1259// =================================================================
1260
1261#[cfg(test)]
1262mod tests {
1263    use super::*;
1264    use crate::errors;
1265    use hekate_math::HardwareField;
1266
1267    fn create_mock_trace(num_vars: usize) -> ColumnTrace {
1268        ColumnTrace::new(num_vars).unwrap()
1269    }
1270
1271    #[test]
1272    fn trace_construction_basic() {
1273        let num_vars = 3;
1274        let mut trace = create_mock_trace(num_vars);
1275
1276        let col_data = vec![Block128::from(1u8); 8];
1277        trace.add_column(col_data.into_trace_column()).unwrap();
1278
1279        assert_eq!(trace.num_rows().unwrap(), 8);
1280        assert_eq!(trace.num_cols(), 1);
1281        assert_eq!(trace.num_vars, 3);
1282    }
1283
1284    #[test]
1285    fn trace_add_column_wrong_len() {
1286        let num_vars = 2;
1287        let mut trace = create_mock_trace(num_vars);
1288
1289        let col_data = vec![Block128::ZERO; 5];
1290        let err = trace
1291            .add_column(col_data.into_trace_column())
1292            .expect_err("Expected length mismatch error");
1293
1294        assert!(matches!(
1295            err,
1296            errors::Error::Trace(Error::ColumnLengthMismatch { .. })
1297        ));
1298    }
1299
1300    #[test]
1301    fn trace_get_element_mixed_types() {
1302        let num_vars = 1;
1303        let mut trace = create_mock_trace(num_vars);
1304
1305        trace
1306            .add_column(TraceColumn::Bit(vec![Bit::new(0), Bit::new(1)]))
1307            .unwrap();
1308        trace
1309            .add_column(vec![Block32::from(10u32), Block32::from(20u32)].into_trace_column())
1310            .unwrap();
1311
1312        let val0_r0: Flat<Block128> = trace.get_element(0, 0).unwrap();
1313        let val0_r1: Flat<Block128> = trace.get_element(0, 1).unwrap();
1314        let val1_r0: Flat<Block128> = trace.get_element(1, 0).unwrap();
1315        let val1_r1: Flat<Block128> = trace.get_element(1, 1).unwrap();
1316
1317        assert_eq!(val0_r0.into_raw(), Block128::ZERO);
1318        assert_eq!(val0_r1.into_raw(), Block128::ONE);
1319
1320        let expected_10 = Block128::promote_flat(Block32::from(10u32).to_hardware()).into_raw();
1321        let expected_20 = Block128::promote_flat(Block32::from(20u32).to_hardware()).into_raw();
1322
1323        assert_eq!(val1_r0.into_raw(), expected_10);
1324        assert_eq!(val1_r1.into_raw(), expected_20);
1325    }
1326
1327    #[test]
1328    fn get_element_oob_row() {
1329        let mut trace = create_mock_trace(1);
1330        trace
1331            .add_column(TraceColumn::Bit(vec![Bit::ZERO; 2]))
1332            .unwrap();
1333        trace
1334            .get_element::<Block128>(0, 2)
1335            .expect_err("Expected out-of-bounds row error");
1336    }
1337
1338    #[test]
1339    fn get_element_oob_col() {
1340        let trace = create_mock_trace(1);
1341        trace
1342            .get_element::<Block128>(0, 0)
1343            .expect_err("Expected out-of-bounds column error");
1344    }
1345
1346    // ===========================================
1347    // SAFETY & SLICE TESTS
1348    // ===========================================
1349
1350    #[test]
1351    fn get_column_slice_correct_type() {
1352        let mut trace = create_mock_trace(2);
1353        let data = vec![
1354            Block32::from(1u32),
1355            Block32::from(2u32),
1356            Block32::from(3u32),
1357            Block32::from(4u32),
1358        ];
1359        trace.add_column(data.clone().into_trace_column()).unwrap();
1360
1361        let expected_hw: Vec<Flat<Block32>> = data.into_iter().map(|x| x.to_hardware()).collect();
1362
1363        let slice: &[Flat<Block32>] = trace.get_column_slice(0).unwrap();
1364        assert_eq!(slice, expected_hw.as_slice());
1365    }
1366
1367    #[test]
1368    fn get_column_slice_wrong_type() {
1369        let mut trace = create_mock_trace(1);
1370        trace
1371            .add_column(vec![Block128::ZERO; 2].into_trace_column())
1372            .unwrap();
1373
1374        trace
1375            .get_column_slice::<Flat<Block32>>(0)
1376            .expect_err("Expected column type mismatch error");
1377    }
1378
1379    #[test]
1380    fn trace_stores_hardware_basis() {
1381        let mut trace = create_mock_trace(2);
1382
1383        let tower_data = vec![
1384            Block32::from(42u32),
1385            Block32::from(13u32),
1386            Block32::from(255u32),
1387            Block32::from(1u32),
1388        ];
1389
1390        let expected_hardware: Vec<Block32> = tower_data
1391            .iter()
1392            .map(|x| x.to_hardware().into_raw())
1393            .collect();
1394
1395        trace.add_column(tower_data.into_trace_column()).unwrap();
1396
1397        let stored: &[Flat<Block32>] = trace.get_column_slice(0).unwrap();
1398
1399        for (i, (&stored_val, &expected_val)) in
1400            stored.iter().zip(expected_hardware.iter()).enumerate()
1401        {
1402            assert_eq!(
1403                stored_val.into_raw(),
1404                expected_val,
1405                "Row {}: stored value {:?} != expected hardware {:?}",
1406                i,
1407                stored_val,
1408                expected_val
1409            );
1410        }
1411    }
1412
1413    #[test]
1414    fn trace_hardware_basis_homomorphism() {
1415        let mut trace = create_mock_trace(3);
1416
1417        let a_tower = vec![Block32::from(5u32); 8];
1418        let b_tower = vec![Block32::from(7u32); 8];
1419
1420        trace
1421            .add_column(a_tower.clone().into_trace_column())
1422            .unwrap();
1423        trace
1424            .add_column(b_tower.clone().into_trace_column())
1425            .unwrap();
1426
1427        let a_stored: &[Flat<Block32>] = trace.get_column_slice(0).unwrap();
1428        let b_stored: &[Flat<Block32>] = trace.get_column_slice(1).unwrap();
1429
1430        let a_hw_expected = a_tower[0].to_hardware().into_raw();
1431        let b_hw_expected = b_tower[0].to_hardware().into_raw();
1432
1433        assert_eq!(a_stored[0].into_raw(), a_hw_expected);
1434        assert_eq!(b_stored[0].into_raw(), b_hw_expected);
1435
1436        let product_hw = a_stored[0] * b_stored[0];
1437        let product_expected = (a_tower[0] * b_tower[0]).to_hardware().into_raw();
1438
1439        assert_eq!(product_hw.into_raw(), product_expected);
1440    }
1441
1442    // =========================================================
1443    // TRACE BUILDER TESTS
1444    // =========================================================
1445
1446    #[test]
1447    fn trace_builder_construction_and_auto_padding() {
1448        let layout = &[ColumnType::B32, ColumnType::Bit];
1449        let tb = TraceBuilder::new(layout, 2).unwrap(); // 4 rows
1450        assert_eq!(tb.num_rows(), 4);
1451
1452        let trace = tb.build();
1453        assert_eq!(trace.num_cols(), 2);
1454        assert_eq!(trace.num_rows().unwrap(), 4);
1455
1456        // All values should be zero (auto-padding)
1457        assert!(trace.columns[0].is_all_zeros());
1458        assert!(trace.columns[1].is_all_zeros());
1459    }
1460
1461    #[test]
1462    fn trace_builder_set_b32_stores_hardware_basis() {
1463        let layout = &[ColumnType::B32];
1464        let mut tb = TraceBuilder::new(layout, 1).unwrap(); // 2 rows
1465
1466        tb.set_b32(0, 0, Block32::from(42u32)).unwrap();
1467        tb.set_b32(0, 1, Block32::from(13u32)).unwrap();
1468
1469        let trace = tb.build();
1470        let stored: &[Flat<Block32>] = trace.get_column_slice(0).unwrap();
1471
1472        assert_eq!(stored[0], Block32::from(42u32).to_hardware());
1473        assert_eq!(stored[1], Block32::from(13u32).to_hardware());
1474    }
1475
1476    #[test]
1477    fn trace_builder_column_ordering_matches_schema() {
1478        let layout = &[ColumnType::Bit, ColumnType::B32, ColumnType::B128];
1479        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1480
1481        tb.set_bit(0, 0, Bit::ONE).unwrap();
1482        tb.set_b32(1, 0, Block32::from(99u32)).unwrap();
1483        tb.set_b128(2, 0, Block128::from(7u8)).unwrap();
1484
1485        let trace = tb.build();
1486        assert_eq!(trace.columns[0].column_type(), ColumnType::Bit);
1487        assert_eq!(trace.columns[1].column_type(), ColumnType::B32);
1488        assert_eq!(trace.columns[2].column_type(), ColumnType::B128);
1489    }
1490
1491    #[test]
1492    fn trace_builder_type_mismatch_returns_error() {
1493        let layout = &[ColumnType::Bit];
1494        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1495
1496        let err = tb.set_b32(0, 0, Block32::ZERO);
1497        assert!(err.is_err());
1498    }
1499
1500    #[test]
1501    fn trace_builder_row_out_of_bounds_returns_error() {
1502        let layout = &[ColumnType::B32];
1503        let mut tb = TraceBuilder::new(layout, 1).unwrap(); // 2 rows
1504
1505        let err = tb.set_b32(0, 2, Block32::ZERO);
1506        assert!(err.is_err());
1507    }
1508
1509    #[test]
1510    fn trace_builder_col_out_of_bounds_returns_error() {
1511        let layout = &[ColumnType::B32];
1512        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1513
1514        let err = tb.set_b32(1, 0, Block32::ZERO);
1515        assert!(err.is_err());
1516    }
1517
1518    #[test]
1519    fn trace_builder_fill_selector() {
1520        let layout = &[ColumnType::Bit];
1521        let mut tb = TraceBuilder::new(layout, 2).unwrap(); // 4 rows
1522
1523        tb.fill_selector(0, 3).unwrap(); // rows 0,1,2 = ONE
1524
1525        let trace = tb.build();
1526        let bits = trace.columns[0].as_bit_slice().unwrap();
1527        assert_eq!(bits[0], Bit::ONE);
1528        assert_eq!(bits[1], Bit::ONE);
1529        assert_eq!(bits[2], Bit::ONE);
1530        assert_eq!(bits[3], Bit::ZERO); // padding
1531    }
1532
1533    #[test]
1534    fn trace_builder_push_mode() {
1535        let layout = &[ColumnType::B32, ColumnType::Bit];
1536        let mut tb = TraceBuilder::new(layout, 1).unwrap(); // 2 rows
1537
1538        tb.push_b32(0, Block32::from(10u32)).unwrap();
1539        tb.push_b32(0, Block32::from(20u32)).unwrap();
1540        tb.push_bit(1, Bit::ONE).unwrap();
1541        // row 1 of col 1 stays zero (auto-pad)
1542
1543        let trace = tb.build();
1544        let b32s: &[Flat<Block32>] = trace.get_column_slice(0).unwrap();
1545        assert_eq!(b32s[0], Block32::from(10u32).to_hardware());
1546        assert_eq!(b32s[1], Block32::from(20u32).to_hardware());
1547
1548        let bits = trace.columns[1].as_bit_slice().unwrap();
1549        assert_eq!(bits[0], Bit::ONE);
1550        assert_eq!(bits[1], Bit::ZERO);
1551    }
1552
1553    #[test]
1554    fn trace_builder_set_b32_array() {
1555        let layout = &[ColumnType::B32, ColumnType::B32, ColumnType::B32];
1556        let mut tb = TraceBuilder::new(layout, 1).unwrap(); // 2 rows
1557
1558        let vals = [
1559            Block32::from(1u32),
1560            Block32::from(2u32),
1561            Block32::from(3u32),
1562        ];
1563        tb.set_b32_array(0, 0, &vals).unwrap();
1564
1565        let trace = tb.build();
1566        for (i, &expected) in vals.iter().enumerate() {
1567            let stored: &[Flat<Block32>] = trace.get_column_slice(i).unwrap();
1568            assert_eq!(stored[0], expected.to_hardware());
1569        }
1570    }
1571
1572    #[test]
1573    fn trace_builder_set_b8() {
1574        let layout = &[ColumnType::B8];
1575        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1576
1577        tb.set_b8(0, 0, Block8(0xAB)).unwrap();
1578        tb.set_b8(0, 1, Block8(0xCD)).unwrap();
1579
1580        let trace = tb.build();
1581        let stored: &[Flat<Block8>] = trace.get_column_slice(0).unwrap();
1582        assert_eq!(stored[0], Block8(0xAB).to_hardware());
1583        assert_eq!(stored[1], Block8(0xCD).to_hardware());
1584    }
1585
1586    #[test]
1587    fn trace_builder_set_b16() {
1588        let layout = &[ColumnType::B16];
1589        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1590
1591        tb.set_b16(0, 0, Block16(1000)).unwrap();
1592        tb.set_b16(0, 1, Block16(2000)).unwrap();
1593
1594        let trace = tb.build();
1595        let stored: &[Flat<Block16>] = trace.get_column_slice(0).unwrap();
1596        assert_eq!(stored[0], Block16(1000).to_hardware());
1597        assert_eq!(stored[1], Block16(2000).to_hardware());
1598    }
1599
1600    #[test]
1601    fn trace_builder_set_b64() {
1602        let layout = &[ColumnType::B64];
1603        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1604
1605        tb.set_b64(0, 0, Block64(0xDEADBEEF_CAFEBABE)).unwrap();
1606
1607        let trace = tb.build();
1608        let stored: &[Flat<Block64>] = trace.get_column_slice(0).unwrap();
1609        assert_eq!(stored[0], Block64(0xDEADBEEF_CAFEBABE).to_hardware());
1610        assert_eq!(stored[1], Block64::ZERO.to_hardware()); // auto-padding
1611    }
1612
1613    #[test]
1614    fn trace_builder_set_b128() {
1615        let layout = &[ColumnType::B128];
1616        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1617
1618        let val = Block128::from(0xFFu8);
1619        tb.set_b128(0, 0, val).unwrap();
1620
1621        let trace = tb.build();
1622        let stored: &[Flat<Block128>] = trace.get_column_slice(0).unwrap();
1623        assert_eq!(stored[0], val.to_hardware());
1624    }
1625
1626    #[test]
1627    fn trace_builder_push_all_types() {
1628        let layout = &[
1629            ColumnType::Bit,
1630            ColumnType::B8,
1631            ColumnType::B16,
1632            ColumnType::B32,
1633            ColumnType::B64,
1634            ColumnType::B128,
1635        ];
1636        let mut tb = TraceBuilder::new(layout, 1).unwrap(); // 2 rows
1637
1638        tb.push_bit(0, Bit::ONE).unwrap();
1639        tb.push_b8(1, Block8(0x42)).unwrap();
1640        tb.push_b16(2, Block16(1234)).unwrap();
1641        tb.push_b32(3, Block32::from(5678u32)).unwrap();
1642        tb.push_b64(4, Block64(9999)).unwrap();
1643        tb.push_b128(5, Block128::from(77u8)).unwrap();
1644
1645        let trace = tb.build();
1646        assert_eq!(trace.num_cols(), 6);
1647
1648        let bits = trace.columns[0].as_bit_slice().unwrap();
1649        assert_eq!(bits[0], Bit::ONE);
1650        assert_eq!(bits[1], Bit::ZERO); // auto-pad
1651
1652        let b8s: &[Flat<Block8>] = trace.get_column_slice(1).unwrap();
1653        assert_eq!(b8s[0], Block8(0x42).to_hardware());
1654
1655        let b16s: &[Flat<Block16>] = trace.get_column_slice(2).unwrap();
1656        assert_eq!(b16s[0], Block16(1234).to_hardware());
1657
1658        let b32s: &[Flat<Block32>] = trace.get_column_slice(3).unwrap();
1659        assert_eq!(b32s[0], Block32::from(5678u32).to_hardware());
1660
1661        let b64s: &[Flat<Block64>] = trace.get_column_slice(4).unwrap();
1662        assert_eq!(b64s[0], Block64(9999).to_hardware());
1663
1664        let b128s: &[Flat<Block128>] = trace.get_column_slice(5).unwrap();
1665        assert_eq!(b128s[0], Block128::from(77u8).to_hardware());
1666    }
1667
1668    #[test]
1669    fn trace_builder_type_mismatch_all_setters() {
1670        // B32 column, every non-B32 setter should fail
1671        let layout = &[ColumnType::B32];
1672        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1673
1674        assert!(tb.set_bit(0, 0, Bit::ONE).is_err());
1675        assert!(tb.set_b8(0, 0, Block8(1)).is_err());
1676        assert!(tb.set_b16(0, 0, Block16(1)).is_err());
1677        assert!(tb.set_b64(0, 0, Block64(1)).is_err());
1678        assert!(tb.set_b128(0, 0, Block128::ONE).is_err());
1679
1680        // Correct type succeeds
1681        assert!(tb.set_b32(0, 0, Block32::ONE).is_ok());
1682    }
1683
1684    #[test]
1685    fn trace_builder_array_setters_all_types() {
1686        let layout = &[
1687            ColumnType::Bit,
1688            ColumnType::Bit,
1689            ColumnType::B8,
1690            ColumnType::B8,
1691            ColumnType::B64,
1692            ColumnType::B64,
1693            ColumnType::B128,
1694            ColumnType::B128,
1695        ];
1696        let mut tb = TraceBuilder::new(layout, 1).unwrap();
1697
1698        tb.set_bit_array(0, 0, &[Bit::ONE, Bit::ZERO]).unwrap();
1699        tb.set_b8_array(2, 0, &[Block8(10), Block8(20)]).unwrap();
1700        tb.set_b64_array(4, 0, &[Block64(100), Block64(200)])
1701            .unwrap();
1702        tb.set_b128_array(6, 0, &[Block128::ONE, Block128::from(2u8)])
1703            .unwrap();
1704
1705        let trace = tb.build();
1706
1707        let bits = trace.columns[0].as_bit_slice().unwrap();
1708        assert_eq!(bits[0], Bit::ONE);
1709
1710        let bits1 = trace.columns[1].as_bit_slice().unwrap();
1711        assert_eq!(bits1[0], Bit::ZERO);
1712
1713        let b8s: &[Flat<Block8>] = trace.get_column_slice(2).unwrap();
1714        assert_eq!(b8s[0], Block8(10).to_hardware());
1715
1716        let b8s1: &[Flat<Block8>] = trace.get_column_slice(3).unwrap();
1717        assert_eq!(b8s1[0], Block8(20).to_hardware());
1718    }
1719
1720    #[test]
1721    fn trace_builder_invalid_num_vars() {
1722        let layout = &[ColumnType::B32];
1723        // num_vars too large to shift
1724        assert!(TraceBuilder::new(layout, 128).is_err());
1725    }
1726}