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