darkfi/zk/
vm.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::HashSet;
20
21use darkfi_sdk::crypto::{
22    constants::{
23        sinsemilla::{OrchardCommitDomains, OrchardHashDomains, K},
24        util::gen_const_array,
25        ConstBaseFieldElement, OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV,
26        MERKLE_DEPTH_ORCHARD,
27    },
28    smt::SMT_FP_DEPTH,
29};
30use halo2_gadgets::{
31    ecc::{
32        chip::{EccChip, EccConfig},
33        FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point, ScalarFixed,
34        ScalarFixedShort, ScalarVar,
35    },
36    poseidon::{
37        primitives as poseidon, Hash as PoseidonHash, Pow5Chip as PoseidonChip,
38        Pow5Config as PoseidonConfig,
39    },
40    sinsemilla::{
41        chip::{SinsemillaChip, SinsemillaConfig},
42        merkle::{
43            chip::{MerkleChip, MerkleConfig},
44            MerklePath,
45        },
46    },
47    utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
48};
49use halo2_proofs::{
50    arithmetic::Field,
51    circuit::{floor_planner, AssignedCell, Layouter, Value},
52    pasta::{group::Curve, pallas, Fp},
53    plonk,
54    plonk::{Advice, Circuit, Column, ConstraintSystem, Instance as InstanceColumn},
55};
56use tracing::{error, trace};
57
58pub use super::vm_heap::{HeapVar, Witness};
59use super::{
60    assign_free_advice,
61    gadget::{
62        arithmetic::{ArithChip, ArithConfig, ArithInstruction},
63        cond_select::{ConditionalSelectChip, ConditionalSelectConfig},
64        less_than::{LessThanChip, LessThanConfig},
65        native_range_check::{NativeRangeCheckChip, NativeRangeCheckConfig},
66        small_range_check::{SmallRangeCheckChip, SmallRangeCheckConfig},
67        smt,
68        zero_cond::{ZeroCondChip, ZeroCondConfig},
69    },
70    tracer::ZkTracer,
71};
72use crate::zkas::{
73    types::{HeapType, LitType},
74    Opcode, ZkBinary,
75};
76
77/// Available chips/gadgets in the zkvm
78#[derive(Debug, Clone)]
79#[allow(clippy::large_enum_variant)]
80enum VmChip {
81    /// ECC Chip
82    Ecc(EccConfig<OrchardFixedBases>),
83
84    /// Merkle tree chip (using Sinsemilla)
85    Merkle(
86        (
87            MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
88            MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
89        ),
90    ),
91
92    /// Sparse merkle tree (using Poseidon)
93    SparseTree(smt::PathConfig),
94
95    /// Sinsemilla chip
96    Sinsemilla(
97        (
98            SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
99            SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
100        ),
101    ),
102
103    /// Poseidon hash chip
104    Poseidon(PoseidonConfig<pallas::Base, 3, 2>),
105
106    /// Base field arithmetic chip
107    Arithmetic(ArithConfig),
108
109    /// 64 bit native range check
110    NativeRange64(NativeRangeCheckConfig<K, 64>),
111
112    /// 253 bit native range check
113    NativeRange253(NativeRangeCheckConfig<K, 253>),
114
115    /// 253 bit `a < b` check
116    LessThan(LessThanConfig<K, 253>),
117
118    /// Boolean check
119    BoolCheck(SmallRangeCheckConfig),
120
121    /// Conditional selection
122    CondSelect(ConditionalSelectConfig<pallas::Base>),
123
124    /// Zero-Cond selection
125    ZeroCond(ZeroCondConfig<pallas::Base>),
126}
127
128/// zkvm configuration
129#[derive(Clone)]
130pub struct VmConfig {
131    /// Chips used in the circuit
132    chips: Vec<VmChip>,
133    /// Instance column used for public inputs
134    primary: Column<InstanceColumn>,
135    /// Advice column used to witness values
136    witness: Column<Advice>,
137}
138
139impl VmConfig {
140    fn ecc_chip(&self) -> Option<EccChip<OrchardFixedBases>> {
141        let Some(VmChip::Ecc(ecc_config)) =
142            self.chips.iter().find(|&c| matches!(c, VmChip::Ecc(_)))
143        else {
144            return None
145        };
146
147        Some(EccChip::construct(ecc_config.clone()))
148    }
149
150    fn merkle_chip_1(
151        &self,
152    ) -> Option<MerkleChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>> {
153        let Some(VmChip::Merkle((merkle_cfg1, _))) =
154            self.chips.iter().find(|&c| matches!(c, VmChip::Merkle(_)))
155        else {
156            return None
157        };
158
159        Some(MerkleChip::construct(merkle_cfg1.clone()))
160    }
161
162    fn merkle_chip_2(
163        &self,
164    ) -> Option<MerkleChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>> {
165        let Some(VmChip::Merkle((_, merkle_cfg2))) =
166            self.chips.iter().find(|&c| matches!(c, VmChip::Merkle(_)))
167        else {
168            return None
169        };
170
171        Some(MerkleChip::construct(merkle_cfg2.clone()))
172    }
173
174    fn smt_chip(&self) -> Option<smt::PathChip> {
175        let Some(VmChip::SparseTree(config)) =
176            self.chips.iter().find(|&c| matches!(c, VmChip::SparseTree(_)))
177        else {
178            return None
179        };
180
181        Some(smt::PathChip::construct(config.clone()))
182    }
183
184    fn poseidon_chip(&self) -> Option<PoseidonChip<pallas::Base, 3, 2>> {
185        let Some(VmChip::Poseidon(poseidon_config)) =
186            self.chips.iter().find(|&c| matches!(c, VmChip::Poseidon(_)))
187        else {
188            return None
189        };
190
191        Some(PoseidonChip::construct(poseidon_config.clone()))
192    }
193
194    fn arithmetic_chip(&self) -> Option<ArithChip<pallas::Base>> {
195        let Some(VmChip::Arithmetic(arith_config)) =
196            self.chips.iter().find(|&c| matches!(c, VmChip::Arithmetic(_)))
197        else {
198            return None
199        };
200
201        Some(ArithChip::construct(arith_config.clone()))
202    }
203
204    fn condselect_chip(&self) -> Option<ConditionalSelectChip<pallas::Base>> {
205        let Some(VmChip::CondSelect(condselect_config)) =
206            self.chips.iter().find(|&c| matches!(c, VmChip::CondSelect(_)))
207        else {
208            return None
209        };
210
211        Some(ConditionalSelectChip::construct(condselect_config.clone()))
212    }
213
214    fn zerocond_chip(&self) -> Option<ZeroCondChip<pallas::Base>> {
215        let Some(VmChip::ZeroCond(zerocond_config)) =
216            self.chips.iter().find(|&c| matches!(c, VmChip::ZeroCond(_)))
217        else {
218            return None
219        };
220
221        Some(ZeroCondChip::construct(zerocond_config.clone()))
222    }
223
224    fn rangecheck64_chip(&self) -> Option<NativeRangeCheckChip<K, 64>> {
225        let Some(VmChip::NativeRange64(range_config)) =
226            self.chips.iter().find(|&c| matches!(c, VmChip::NativeRange64(_)))
227        else {
228            return None
229        };
230
231        Some(NativeRangeCheckChip::construct(range_config.clone()))
232    }
233
234    fn rangecheck253_chip(&self) -> Option<NativeRangeCheckChip<K, 253>> {
235        let Some(VmChip::NativeRange253(range_config)) =
236            self.chips.iter().find(|&c| matches!(c, VmChip::NativeRange253(_)))
237        else {
238            return None
239        };
240
241        Some(NativeRangeCheckChip::construct(range_config.clone()))
242    }
243
244    fn lessthan_chip(&self) -> Option<LessThanChip<K, 253>> {
245        let Some(VmChip::LessThan(lessthan_config)) =
246            self.chips.iter().find(|&c| matches!(c, VmChip::LessThan(_)))
247        else {
248            return None
249        };
250
251        Some(LessThanChip::construct(lessthan_config.clone()))
252    }
253
254    fn boolcheck_chip(&self) -> Option<SmallRangeCheckChip<pallas::Base>> {
255        let Some(VmChip::BoolCheck(boolcheck_config)) =
256            self.chips.iter().find(|&c| matches!(c, VmChip::BoolCheck(_)))
257        else {
258            return None
259        };
260
261        Some(SmallRangeCheckChip::construct(boolcheck_config.clone()))
262    }
263}
264
265/// Configuration parameters for the circuit.
266/// Defines which chips we need to initialize and configure.
267#[derive(Default)]
268#[allow(dead_code)]
269pub struct ZkParams {
270    init_ecc: bool,
271    init_poseidon: bool,
272    init_sinsemilla: bool,
273    init_arithmetic: bool,
274    init_nativerange: bool,
275    init_lessthan: bool,
276    init_boolcheck: bool,
277    init_condselect: bool,
278    init_zerocond: bool,
279}
280
281#[derive(Clone, Debug)]
282pub struct ZkCircuit {
283    constants: Vec<String>,
284    pub(super) witnesses: Vec<Witness>,
285    literals: Vec<(LitType, String)>,
286    pub(super) opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>,
287    pub tracer: ZkTracer,
288}
289
290impl ZkCircuit {
291    pub fn new(witnesses: Vec<Witness>, circuit_code: &ZkBinary) -> Self {
292        let constants = circuit_code.constants.iter().map(|x| x.1.clone()).collect();
293        let literals = circuit_code.literals.clone();
294        Self {
295            constants,
296            witnesses,
297            literals,
298            opcodes: circuit_code.opcodes.clone(),
299            tracer: ZkTracer::new(true),
300        }
301    }
302
303    pub fn enable_trace(&mut self) {
304        self.tracer.init();
305    }
306}
307
308impl Circuit<pallas::Base> for ZkCircuit {
309    type Config = VmConfig;
310    type FloorPlanner = floor_planner::V1;
311    type Params = ZkParams;
312
313    fn without_witnesses(&self) -> Self {
314        Self {
315            constants: self.constants.clone(),
316            witnesses: self.witnesses.clone(),
317            literals: self.literals.clone(),
318            opcodes: self.opcodes.clone(),
319            tracer: ZkTracer::new(false),
320        }
321    }
322
323    fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
324        Self::configure_with_params(meta, ZkParams::default())
325    }
326
327    fn params(&self) -> Self::Params {
328        // Gather all opcodes used in the circuit.
329        let mut opcodes = HashSet::new();
330        for (opcode, _) in &self.opcodes {
331            opcodes.insert(opcode);
332        }
333
334        // Conditions on which we enable the ECC chip
335        let init_ecc = !self.constants.is_empty() ||
336            opcodes.contains(&Opcode::EcAdd) ||
337            opcodes.contains(&Opcode::EcMul) ||
338            opcodes.contains(&Opcode::EcMulBase) ||
339            opcodes.contains(&Opcode::EcMulShort) ||
340            opcodes.contains(&Opcode::EcMulVarBase) ||
341            opcodes.contains(&Opcode::EcGetX) ||
342            opcodes.contains(&Opcode::EcGetY) ||
343            opcodes.contains(&Opcode::ConstrainEqualPoint) ||
344            self.witnesses.iter().any(|x| {
345                matches!(x, Witness::EcPoint(_)) ||
346                    matches!(x, Witness::EcNiPoint(_)) ||
347                    matches!(x, Witness::EcFixedPoint(_)) ||
348                    matches!(x, Witness::Scalar(_))
349            });
350
351        // Conditions on which we enable the Poseidon hash chip
352        let init_poseidon = opcodes.contains(&Opcode::PoseidonHash);
353
354        // Conditions on which we enable the Sinsemilla and Merkle chips
355        let init_sinsemilla = opcodes.contains(&Opcode::MerkleRoot);
356
357        // Conditions on which we enable the base field Arithmetic chip
358        let init_arithmetic = opcodes.contains(&Opcode::BaseAdd) ||
359            opcodes.contains(&Opcode::BaseSub) ||
360            opcodes.contains(&Opcode::BaseMul);
361
362        // Conditions on which we enable the native range check chips
363        // TODO: Separate 253 and 64.
364        let init_nativerange = opcodes.contains(&Opcode::RangeCheck) ||
365            opcodes.contains(&Opcode::LessThanLoose) ||
366            opcodes.contains(&Opcode::LessThanStrict);
367
368        // Conditions on which we enable the less than comparison chip
369        let init_lessthan =
370            opcodes.contains(&Opcode::LessThanLoose) || opcodes.contains(&Opcode::LessThanStrict);
371
372        // Conditions on which we enable the boolean check chip
373        let init_boolcheck = opcodes.contains(&Opcode::BoolCheck);
374
375        // Conditions on which we enable the conditional selection chip
376        let init_condselect = opcodes.contains(&Opcode::CondSelect);
377
378        // Conditions on which we enable the zero cond selection chip
379        let init_zerocond = opcodes.contains(&Opcode::ZeroCondSelect);
380
381        ZkParams {
382            init_ecc,
383            init_poseidon,
384            init_sinsemilla,
385            init_arithmetic,
386            init_nativerange,
387            init_lessthan,
388            init_boolcheck,
389            init_condselect,
390            init_zerocond,
391        }
392    }
393
394    fn configure_with_params(
395        meta: &mut ConstraintSystem<pallas::Base>,
396        _params: Self::Params,
397    ) -> Self::Config {
398        // Advice columns used in the circuit
399        let mut advices = vec![];
400        for _ in 0..10 {
401            advices.push(meta.advice_column());
402        }
403
404        // Instance column used for public inputs
405        let primary = meta.instance_column();
406        meta.enable_equality(primary);
407
408        // Permutation over all advice columns
409        for advice in advices.iter() {
410            meta.enable_equality(*advice);
411        }
412
413        // Fixed columns for the Sinsemilla generator lookup table
414        let table_idx = meta.lookup_table_column();
415        let lookup = (table_idx, meta.lookup_table_column(), meta.lookup_table_column());
416
417        // Poseidon requires four advice columns, while ECC incomplete addition
418        // requires six. We can reduce the proof size by sharing fixed columns
419        // between the ECC and Poseidon chips.
420        // TODO: For multiple invocations perhaps they could/should be configured
421        // in parallel rather than sharing?
422        let lagrange_coeffs = [
423            meta.fixed_column(),
424            meta.fixed_column(),
425            meta.fixed_column(),
426            meta.fixed_column(),
427            meta.fixed_column(),
428            meta.fixed_column(),
429            meta.fixed_column(),
430            meta.fixed_column(),
431        ];
432        let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
433        let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
434
435        // Also use the first Lagrange coefficient column for loading global constants.
436        meta.enable_constant(lagrange_coeffs[0]);
437
438        // Use one of the right-most advice columns for all of our range checks.
439        let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
440
441        // Configuration for curve point operations.
442        // This uses 10 advice columns and spans the whole circuit.
443        let ecc_config = EccChip::<OrchardFixedBases>::configure(
444            meta,
445            advices[0..10].try_into().unwrap(),
446            lagrange_coeffs,
447            range_check,
448        );
449
450        // Configuration for the Poseidon hash
451        let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
452            meta,
453            advices[6..9].try_into().unwrap(),
454            advices[5],
455            rc_a,
456            rc_b,
457        );
458
459        // Configuration for the Arithmetic chip
460        let arith_config = ArithChip::configure(meta, advices[7], advices[8], advices[6]);
461
462        // Configuration for a Sinsemilla hash instantiation and a
463        // Merkle hash instantiation using this Sinsemilla instance.
464        // Since the Sinsemilla config uses only 5 advice columns,
465        // we can fit two instances side-by-side.
466        let (sinsemilla_cfg1, merkle_cfg1) = {
467            let sinsemilla_cfg1 = SinsemillaChip::configure(
468                meta,
469                advices[..5].try_into().unwrap(),
470                advices[6],
471                lagrange_coeffs[0],
472                lookup,
473                range_check,
474                false, // allow_init_from_private_point
475            );
476            let merkle_cfg1 = MerkleChip::configure(meta, sinsemilla_cfg1.clone());
477            (sinsemilla_cfg1, merkle_cfg1)
478        };
479
480        let (sinsemilla_cfg2, merkle_cfg2) = {
481            let sinsemilla_cfg2 = SinsemillaChip::configure(
482                meta,
483                advices[5..].try_into().unwrap(),
484                advices[7],
485                lagrange_coeffs[1],
486                lookup,
487                range_check,
488                false, // allow_init_from_private_point
489            );
490            let merkle_cfg2 = MerkleChip::configure(meta, sinsemilla_cfg2.clone());
491            (sinsemilla_cfg2, merkle_cfg2)
492        };
493
494        let smt_config = smt::PathChip::configure(
495            meta,
496            advices[0..2].try_into().unwrap(),
497            advices[2..6].try_into().unwrap(),
498            poseidon_config.clone(),
499        );
500
501        // K-table for 64 bit range check lookups
502        let native_64_range_check_config =
503            NativeRangeCheckChip::<K, 64>::configure(meta, advices[8], table_idx);
504
505        // K-table for 253 bit range check lookups
506        let native_253_range_check_config =
507            NativeRangeCheckChip::<K, 253>::configure(meta, advices[8], table_idx);
508
509        // TODO: FIXME: Configure these better, this is just a stop-gap
510        let z1 = meta.advice_column();
511        let z2 = meta.advice_column();
512
513        let lessthan_config = LessThanChip::<K, 253>::configure(
514            meta, advices[6], advices[7], advices[8], z1, z2, table_idx,
515        );
516
517        // Configuration for boolean checks, it uses the small_range_check
518        // chip with a range of 2, which enforces one bit, i.e. 0 or 1.
519        let boolcheck_config = SmallRangeCheckChip::configure(meta, advices[9], 2);
520
521        // Configuration for the conditional selection chip
522        let condselect_config =
523            ConditionalSelectChip::configure(meta, advices[1..5].try_into().unwrap());
524
525        // Configuration for the zero_cond selection chip
526        let zerocond_config = ZeroCondChip::configure(meta, advices[1..5].try_into().unwrap());
527
528        // Later we'll use this for optimisation
529        let chips = vec![
530            VmChip::Ecc(ecc_config),
531            VmChip::Merkle((merkle_cfg1, merkle_cfg2)),
532            VmChip::SparseTree(smt_config),
533            VmChip::Sinsemilla((sinsemilla_cfg1, sinsemilla_cfg2)),
534            VmChip::Poseidon(poseidon_config),
535            VmChip::Arithmetic(arith_config),
536            VmChip::NativeRange64(native_64_range_check_config),
537            VmChip::NativeRange253(native_253_range_check_config),
538            VmChip::LessThan(lessthan_config),
539            VmChip::BoolCheck(boolcheck_config),
540            VmChip::CondSelect(condselect_config),
541            VmChip::ZeroCond(zerocond_config),
542        ];
543
544        VmConfig { primary, witness: advices[0], chips }
545    }
546
547    fn synthesize(
548        &self,
549        config: Self::Config,
550        mut layouter: impl Layouter<pallas::Base>,
551    ) -> std::result::Result<(), plonk::Error> {
552        trace!(target: "zk::vm", "Entering synthesize()");
553
554        // ===================
555        // VM Setup
556        //====================
557
558        // Our heap which holds every variable we reference and create.
559        let mut heap: Vec<HeapVar> = vec![];
560
561        // Our heap which holds all the literal values we have in the circuit.
562        // For now, we only support u64.
563        let mut litheap: Vec<u64> = vec![];
564
565        // Offset for public inputs
566        let mut public_inputs_offset = 0;
567
568        // Offset for literals
569        let mut literals_offset = 0;
570
571        // Load the Sinsemilla generator lookup table used by the whole circuit.
572        if let Some(VmChip::Sinsemilla((sinsemilla_cfg1, _))) =
573            config.chips.iter().find(|&c| matches!(c, VmChip::Sinsemilla(_)))
574        {
575            trace!(target: "zk::vm", "Initializing Sinsemilla generator lookup table");
576            SinsemillaChip::load(sinsemilla_cfg1.clone(), &mut layouter)?;
577        }
578
579        let no_sinsemilla_chip = !config.chips.iter().any(|c| matches!(c, VmChip::Sinsemilla(_)));
580
581        // Construct the 64-bit NativeRangeCheck chip
582        let rangecheck64_chip = config.rangecheck64_chip();
583        if let Some(VmChip::NativeRange64(rangecheck64_config)) =
584            config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange64(_)))
585        {
586            if no_sinsemilla_chip {
587                trace!(target: "zk::vm", "Initializing k table for 64bit NativeRangeCheck");
588                NativeRangeCheckChip::<K, 64>::load_k_table(
589                    &mut layouter,
590                    rangecheck64_config.k_values_table,
591                )?;
592            }
593        }
594
595        let no_rangecheck64_chip =
596            !config.chips.iter().any(|c| matches!(c, VmChip::NativeRange64(_)));
597
598        // Construct the 253-bit NativeRangeCheck and LessThan chips.
599        let rangecheck253_chip = config.rangecheck253_chip();
600        let lessthan_chip = config.lessthan_chip();
601
602        if let Some(VmChip::NativeRange253(rangecheck253_config)) =
603            config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange253(_)))
604        {
605            if no_sinsemilla_chip && no_rangecheck64_chip {
606                trace!(target: "zk::vm", "Initializing k table for 253bit NativeRangeCheck");
607                NativeRangeCheckChip::<K, 253>::load_k_table(
608                    &mut layouter,
609                    rangecheck253_config.k_values_table,
610                )?;
611            }
612        }
613
614        // Construct the ECC chip.
615        let ecc_chip = config.ecc_chip();
616
617        // Construct the Arithmetic chip.
618        let arith_chip = config.arithmetic_chip();
619
620        // Construct the boolean check chip.
621        let boolcheck_chip = config.boolcheck_chip();
622
623        // Construct the conditional selection chip
624        let condselect_chip = config.condselect_chip();
625
626        // Construct the zero_cond selection chip
627        let zerocond_chip = config.zerocond_chip();
628
629        // Construct sparse Merkle tree chip
630        let smt_chip = config.smt_chip().unwrap();
631
632        // ==========================
633        // Constants setup
634        // ==========================
635
636        // This constant one is used for short multiplication
637        let one = assign_free_advice(
638            layouter.namespace(|| "Load constant one"),
639            config.witness,
640            Value::known(pallas::Base::ONE),
641        )?;
642        layouter.assign_region(
643            || "constrain constant",
644            |mut region| region.constrain_constant(one.cell(), pallas::Base::ONE),
645        )?;
646
647        // ANCHOR: constant_init
648        // Lookup and push constants onto the heap
649        for constant in &self.constants {
650            trace!(
651                target: "zk::vm",
652                "Pushing constant `{}` to heap address {}",
653                constant.as_str(),
654                heap.len()
655            );
656            match constant.as_str() {
657                "VALUE_COMMIT_VALUE" => {
658                    let vcv = ValueCommitV;
659                    let vcv = FixedPointShort::from_inner(ecc_chip.as_ref().unwrap().clone(), vcv);
660                    heap.push(HeapVar::EcFixedPointShort(vcv));
661                }
662                "VALUE_COMMIT_RANDOM" => {
663                    let vcr = OrchardFixedBasesFull::ValueCommitR;
664                    let vcr = FixedPoint::from_inner(ecc_chip.as_ref().unwrap().clone(), vcr);
665                    heap.push(HeapVar::EcFixedPoint(vcr));
666                }
667                "VALUE_COMMIT_RANDOM_BASE" => {
668                    let vcr = ConstBaseFieldElement::value_commit_r();
669                    let vcr =
670                        FixedPointBaseField::from_inner(ecc_chip.as_ref().unwrap().clone(), vcr);
671                    heap.push(HeapVar::EcFixedPointBase(vcr));
672                }
673                "NULLIFIER_K" => {
674                    let nfk = ConstBaseFieldElement::nullifier_k();
675                    let nfk =
676                        FixedPointBaseField::from_inner(ecc_chip.as_ref().unwrap().clone(), nfk);
677                    heap.push(HeapVar::EcFixedPointBase(nfk));
678                }
679
680                _ => {
681                    error!(target: "zk::vm", "Invalid constant name: {}", constant.as_str());
682                    return Err(plonk::Error::Synthesis)
683                }
684            }
685        }
686        // ANCHOR_END: constant_init
687
688        // ANCHOR: literals_init
689        // Load the literals onto the literal heap
690        // N.B. Only uint64 is supported right now.
691        for literal in &self.literals {
692            match literal.0 {
693                LitType::Uint64 => match literal.1.parse::<u64>() {
694                    Ok(v) => litheap.push(v),
695                    Err(e) => {
696                        error!(target: "zk::vm", "Failed converting u64 literal: {e}");
697                        return Err(plonk::Error::Synthesis)
698                    }
699                },
700                _ => {
701                    error!(target: "zk::vm", "Invalid literal: {literal:?}");
702                    return Err(plonk::Error::Synthesis)
703                }
704            }
705        }
706        // ANCHOR_END: literals_init
707
708        // ANCHOR: witness_init
709        // Push the witnesses onto the heap, and potentially, if the witness
710        // is in the Base field (like the entire circuit is), load it into a
711        // table cell.
712        for witness in &self.witnesses {
713            match witness {
714                Witness::EcPoint(w) => {
715                    trace!(target: "zk::vm", "Witnessing EcPoint into circuit");
716                    let point = Point::new(
717                        ecc_chip.as_ref().unwrap().clone(),
718                        layouter.namespace(|| "Witness EcPoint"),
719                        w.as_ref().map(|cm| cm.to_affine()),
720                    )?;
721
722                    trace!(target: "zk::vm", "Pushing EcPoint to heap address {}", heap.len());
723                    heap.push(HeapVar::EcPoint(point));
724                }
725
726                Witness::EcNiPoint(w) => {
727                    trace!(target: "zk::vm", "Witnessing EcNiPoint into circuit");
728                    let point = NonIdentityPoint::new(
729                        ecc_chip.as_ref().unwrap().clone(),
730                        layouter.namespace(|| "Witness EcNiPoint"),
731                        w.as_ref().map(|cm| cm.to_affine()),
732                    )?;
733
734                    trace!(target: "zk::vm", "Pushing EcNiPoint to heap address {}", heap.len());
735                    heap.push(HeapVar::EcNiPoint(point));
736                }
737
738                Witness::EcFixedPoint(_) => {
739                    error!(target: "zk::vm", "Unable to witness EcFixedPoint, this is unimplemented.");
740                    return Err(plonk::Error::Synthesis)
741                }
742
743                Witness::Base(w) => {
744                    trace!(target: "zk::vm", "Witnessing Base into circuit");
745                    let base = assign_free_advice(
746                        layouter.namespace(|| "Witness Base"),
747                        config.witness,
748                        *w,
749                    )?;
750
751                    trace!(target: "zk::vm", "Pushing Base to heap address {}", heap.len());
752                    heap.push(HeapVar::Base(base));
753                }
754
755                Witness::Scalar(w) => {
756                    trace!(target: "zk::vm", "Witnessing Scalar into circuit");
757                    let scalar = ScalarFixed::new(
758                        ecc_chip.as_ref().unwrap().clone(),
759                        layouter.namespace(|| "Witness ScalarFixed"),
760                        *w,
761                    )?;
762
763                    trace!(target: "zk::vm", "Pushing Scalar to heap address {}", heap.len());
764                    heap.push(HeapVar::Scalar(scalar));
765                }
766
767                Witness::MerklePath(w) => {
768                    trace!(target: "zk::vm", "Witnessing MerklePath into circuit");
769                    let path: Value<[pallas::Base; MERKLE_DEPTH_ORCHARD]> =
770                        w.map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
771
772                    trace!(target: "zk::vm", "Pushing MerklePath to heap address {}", heap.len());
773                    heap.push(HeapVar::MerklePath(path));
774                }
775
776                Witness::SparseMerklePath(w) => {
777                    let path: Value<[pallas::Base; SMT_FP_DEPTH]> =
778                        w.map(|typed_path| gen_const_array(|i| typed_path[i]));
779
780                    trace!(target: "zk::vm", "Pushing SparseMerklePath to heap address {}", heap.len());
781                    heap.push(HeapVar::SparseMerklePath(path));
782                }
783
784                Witness::Uint32(w) => {
785                    trace!(target: "zk::vm", "Pushing Uint32 to heap address {}", heap.len());
786                    heap.push(HeapVar::Uint32(*w));
787                }
788
789                Witness::Uint64(w) => {
790                    trace!(target: "zk::vm", "Pushing Uint64 to heap address {}", heap.len());
791                    heap.push(HeapVar::Uint64(*w));
792                }
793            }
794        }
795        // ANCHOR_END: witness_init
796
797        // =============================
798        // And now, work through opcodes
799        // =============================
800        self.tracer.clear();
801        // TODO: Copy constraints
802        // ANCHOR: opcode_begin
803        for opcode in &self.opcodes {
804            match opcode.0 {
805                Opcode::EcAdd => {
806                    trace!(target: "zk::vm", "Executing `EcAdd{:?}` opcode", opcode.1);
807                    let args = &opcode.1;
808
809                    let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
810                        heap[args[0].1].clone().try_into()?;
811
812                    let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
813                        heap[args[1].1].clone().try_into()?;
814
815                    let ret = lhs.add(layouter.namespace(|| "EcAdd()"), &rhs)?;
816
817                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
818                    self.tracer.push_ecpoint(&ret);
819                    heap.push(HeapVar::EcPoint(ret));
820                }
821                // ANCHOR_END: opcode_begin
822                Opcode::EcMul => {
823                    trace!(target: "zk::vm", "Executing `EcMul{:?}` opcode", opcode.1);
824                    let args = &opcode.1;
825
826                    let lhs: FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>> =
827                        heap[args[1].1].clone().try_into()?;
828
829                    let rhs: ScalarFixed<pallas::Affine, EccChip<OrchardFixedBases>> =
830                        heap[args[0].1].clone().try_into()?;
831
832                    let (ret, _) = lhs.mul(layouter.namespace(|| "EcMul()"), rhs)?;
833
834                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
835                    self.tracer.push_ecpoint(&ret);
836                    heap.push(HeapVar::EcPoint(ret));
837                }
838
839                Opcode::EcMulVarBase => {
840                    trace!(target: "zk::vm", "Executing `EcMulVarBase{:?}` opcode", opcode.1);
841                    let args = &opcode.1;
842
843                    let lhs: NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>> =
844                        heap[args[1].1].clone().try_into()?;
845
846                    let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
847                    let rhs = ScalarVar::from_base(
848                        ecc_chip.as_ref().unwrap().clone(),
849                        layouter.namespace(|| "EcMulVarBase::from_base()"),
850                        &rhs,
851                    )?;
852
853                    let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulVarBase()"), rhs)?;
854
855                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
856                    self.tracer.push_ecpoint(&ret);
857                    heap.push(HeapVar::EcPoint(ret));
858                }
859
860                Opcode::EcMulBase => {
861                    trace!(target: "zk::vm", "Executing `EcMulBase{:?}` opcode", opcode.1);
862                    let args = &opcode.1;
863
864                    let lhs: FixedPointBaseField<pallas::Affine, EccChip<OrchardFixedBases>> =
865                        heap[args[1].1].clone().try_into()?;
866
867                    let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
868
869                    let ret = lhs.mul(layouter.namespace(|| "EcMulBase()"), rhs)?;
870
871                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
872                    self.tracer.push_ecpoint(&ret);
873                    heap.push(HeapVar::EcPoint(ret));
874                }
875
876                Opcode::EcMulShort => {
877                    trace!(target: "zk::vm", "Executing `EcMulShort{:?}` opcode", opcode.1);
878                    let args = &opcode.1;
879
880                    let lhs: FixedPointShort<pallas::Affine, EccChip<OrchardFixedBases>> =
881                        heap[args[1].1].clone().try_into()?;
882
883                    let rhs = ScalarFixedShort::new(
884                        ecc_chip.as_ref().unwrap().clone(),
885                        layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"),
886                        (heap[args[0].1].clone().try_into()?, one.clone()),
887                    )?;
888
889                    let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?;
890
891                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
892                    self.tracer.push_ecpoint(&ret);
893                    heap.push(HeapVar::EcPoint(ret));
894                }
895
896                Opcode::EcGetX => {
897                    trace!(target: "zk::vm", "Executing `EcGetX{:?}` opcode", opcode.1);
898                    let args = &opcode.1;
899
900                    let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
901                        heap[args[0].1].clone().try_into()?;
902
903                    let ret = point.inner().x();
904
905                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
906                    self.tracer.push_base(&ret);
907                    heap.push(HeapVar::Base(ret));
908                }
909
910                Opcode::EcGetY => {
911                    trace!(target: "zk::vm", "Executing `EcGetY{:?}` opcode", opcode.1);
912                    let args = &opcode.1;
913
914                    let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
915                        heap[args[0].1].clone().try_into()?;
916
917                    let ret = point.inner().y();
918
919                    trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
920                    self.tracer.push_base(&ret);
921                    heap.push(HeapVar::Base(ret));
922                }
923
924                Opcode::PoseidonHash => {
925                    trace!(target: "zk::vm", "Executing `PoseidonHash{:?}` opcode", opcode.1);
926                    let args = &opcode.1;
927
928                    let mut poseidon_message: Vec<AssignedCell<Fp, Fp>> =
929                        Vec::with_capacity(args.len());
930
931                    for idx in args {
932                        poseidon_message.push(heap[idx.1].clone().try_into()?);
933                    }
934
935                    macro_rules! poseidon_hash {
936                        ($len:expr, $hasher:ident, $output:ident, $cell:ident) => {
937                            let $hasher = PoseidonHash::<
938                                _,
939                                _,
940                                poseidon::P128Pow5T3,
941                                poseidon::ConstantLength<$len>,
942                                3,
943                                2,
944                            >::init(
945                                config.poseidon_chip().unwrap(),
946                                layouter.namespace(|| "PoseidonHash init"),
947                            )?;
948
949                            let $output = $hasher.hash(
950                                layouter.namespace(|| "PoseidonHash hash"),
951                                poseidon_message.try_into().unwrap(),
952                            )?;
953
954                            let $cell: AssignedCell<Fp, Fp> = $output.into();
955
956                            trace!(target: "zk::vm", "Pushing hash to heap address {}", heap.len());
957                            self.tracer.push_base(&$cell);
958                            heap.push(HeapVar::Base($cell));
959                        };
960                    }
961
962                    macro_rules! vla {
963                        ($args:ident, $a:ident, $b:ident, $c:ident, $($num:tt)*) => {
964                            match $args.len() {
965                                $($num => {
966                                    poseidon_hash!($num, $a, $b, $c);
967                                })*
968                                _ => {
969                                    error!(target: "zk::vm", "Unsupported poseidon hash for {} elements", $args.len());
970                                    return Err(plonk::Error::Synthesis)
971                                }
972                            }
973                        };
974                    }
975
976                    vla!(args, a, b, c, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24);
977                }
978
979                Opcode::MerkleRoot => {
980                    // TODO: all these trace statements could have trace!(..., args) instead
981                    trace!(target: "zk::vm", "Executing `MerkleRoot{:?}` opcode", opcode.1);
982                    let args = &opcode.1;
983
984                    let leaf_pos = heap[args[0].1].clone().try_into()?;
985                    let merkle_path: Value<[Fp; MERKLE_DEPTH_ORCHARD]> =
986                        heap[args[1].1].clone().try_into()?;
987                    let leaf = heap[args[2].1].clone().try_into()?;
988
989                    let merkle_inputs = MerklePath::construct(
990                        [config.merkle_chip_1().unwrap(), config.merkle_chip_2().unwrap()],
991                        OrchardHashDomains::MerkleCrh,
992                        leaf_pos,
993                        merkle_path,
994                    );
995
996                    let root = merkle_inputs
997                        .calculate_root(layouter.namespace(|| "MerkleRoot()"), leaf)?;
998
999                    trace!(target: "zk::vm", "Pushing merkle root to heap address {}", heap.len());
1000                    self.tracer.push_base(&root);
1001                    heap.push(HeapVar::Base(root));
1002                }
1003
1004                Opcode::SparseMerkleRoot => {
1005                    trace!(target: "zk::vm", "Executing `SparseTreeIsMember{:?}` opcode", opcode.1);
1006                    let args = &opcode.1;
1007
1008                    let pos = heap[args[0].1].clone().try_into()?;
1009                    let path: Value<[Fp; SMT_FP_DEPTH]> = heap[args[1].1].clone().try_into()?;
1010                    let leaf = heap[args[2].1].clone().try_into()?;
1011
1012                    let root = smt_chip.check_membership(&mut layouter, pos, path, leaf)?;
1013
1014                    self.tracer.push_base(&root);
1015                    heap.push(HeapVar::Base(root));
1016                }
1017
1018                Opcode::BaseAdd => {
1019                    trace!(target: "zk::vm", "Executing `BaseAdd{:?}` opcode", opcode.1);
1020                    let args = &opcode.1;
1021
1022                    let lhs = &heap[args[0].1].clone().try_into()?;
1023                    let rhs = &heap[args[1].1].clone().try_into()?;
1024
1025                    let sum = arith_chip.as_ref().unwrap().add(
1026                        layouter.namespace(|| "BaseAdd()"),
1027                        lhs,
1028                        rhs,
1029                    )?;
1030
1031                    trace!(target: "zk::vm", "Pushing sum to heap address {}", heap.len());
1032                    self.tracer.push_base(&sum);
1033                    heap.push(HeapVar::Base(sum));
1034                }
1035
1036                Opcode::BaseMul => {
1037                    trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
1038                    let args = &opcode.1;
1039
1040                    let lhs = &heap[args[0].1].clone().try_into()?;
1041                    let rhs = &heap[args[1].1].clone().try_into()?;
1042
1043                    let product = arith_chip.as_ref().unwrap().mul(
1044                        layouter.namespace(|| "BaseMul()"),
1045                        lhs,
1046                        rhs,
1047                    )?;
1048
1049                    trace!(target: "zk::vm", "Pushing product to heap address {}", heap.len());
1050                    self.tracer.push_base(&product);
1051                    heap.push(HeapVar::Base(product));
1052                }
1053
1054                Opcode::BaseSub => {
1055                    trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
1056                    let args = &opcode.1;
1057
1058                    let lhs = &heap[args[0].1].clone().try_into()?;
1059                    let rhs = &heap[args[1].1].clone().try_into()?;
1060
1061                    let difference = arith_chip.as_ref().unwrap().sub(
1062                        layouter.namespace(|| "BaseSub()"),
1063                        lhs,
1064                        rhs,
1065                    )?;
1066
1067                    trace!(target: "zk::vm", "Pushing difference to heap address {}", heap.len());
1068                    self.tracer.push_base(&difference);
1069                    heap.push(HeapVar::Base(difference));
1070                }
1071
1072                Opcode::WitnessBase => {
1073                    trace!(target: "zk::vm", "Executing `WitnessBase{:?}` opcode", opcode.1);
1074                    //let args = &opcode.1;
1075
1076                    let lit = litheap[literals_offset];
1077                    literals_offset += 1;
1078
1079                    let witness = assign_free_advice(
1080                        layouter.namespace(|| "Witness literal"),
1081                        config.witness,
1082                        Value::known(pallas::Base::from(lit)),
1083                    )?;
1084
1085                    layouter.assign_region(
1086                        || "constrain constant",
1087                        |mut region| {
1088                            region.constrain_constant(witness.cell(), pallas::Base::from(lit))
1089                        },
1090                    )?;
1091
1092                    trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
1093                    self.tracer.push_base(&witness);
1094                    heap.push(HeapVar::Base(witness));
1095                }
1096
1097                Opcode::RangeCheck => {
1098                    trace!(target: "zk::vm", "Executing `RangeCheck{:?}` opcode", opcode.1);
1099                    let args = &opcode.1;
1100
1101                    let lit = litheap[literals_offset];
1102                    literals_offset += 1;
1103
1104                    let arg = heap[args[1].1].clone();
1105
1106                    match lit {
1107                        64 => {
1108                            rangecheck64_chip.as_ref().unwrap().copy_range_check(
1109                                layouter.namespace(|| "copy range check 64"),
1110                                arg.try_into()?,
1111                            )?;
1112                        }
1113                        253 => {
1114                            rangecheck253_chip.as_ref().unwrap().copy_range_check(
1115                                layouter.namespace(|| "copy range check 253"),
1116                                arg.try_into()?,
1117                            )?;
1118                        }
1119                        x => {
1120                            error!(target: "zk::vm", "Unsupported bit-range {x} for range_check");
1121                            return Err(plonk::Error::Synthesis)
1122                        }
1123                    }
1124                    self.tracer.push_void();
1125                }
1126
1127                Opcode::LessThanStrict => {
1128                    trace!(target: "zk::vm", "Executing `LessThanStrict{:?}` opcode", opcode.1);
1129                    let args = &opcode.1;
1130
1131                    let a = heap[args[0].1].clone().try_into()?;
1132                    let b = heap[args[1].1].clone().try_into()?;
1133
1134                    lessthan_chip.as_ref().unwrap().copy_less_than(
1135                        layouter.namespace(|| "copy a<b check"),
1136                        a,
1137                        b,
1138                        0,
1139                        true,
1140                    )?;
1141                    self.tracer.push_void();
1142                }
1143
1144                Opcode::LessThanLoose => {
1145                    trace!(target: "zk::vm", "Executing `LessThanLoose{:?}` opcode", opcode.1);
1146                    let args = &opcode.1;
1147
1148                    let a = heap[args[0].1].clone().try_into()?;
1149                    let b = heap[args[1].1].clone().try_into()?;
1150
1151                    lessthan_chip.as_ref().unwrap().copy_less_than(
1152                        layouter.namespace(|| "copy a<b check"),
1153                        a,
1154                        b,
1155                        0,
1156                        false,
1157                    )?;
1158                    self.tracer.push_void();
1159                }
1160
1161                Opcode::BoolCheck => {
1162                    trace!(target: "zk::vm", "Executing `BoolCheck{:?}` opcode", opcode.1);
1163                    let args = &opcode.1;
1164
1165                    let w = heap[args[0].1].clone().try_into()?;
1166
1167                    boolcheck_chip
1168                        .as_ref()
1169                        .unwrap()
1170                        .small_range_check(layouter.namespace(|| "copy boolean check"), w)?;
1171                    self.tracer.push_void();
1172                }
1173
1174                Opcode::CondSelect => {
1175                    trace!(target: "zk::vm", "Executing `CondSelect{:?}` opcode", opcode.1);
1176                    let args = &opcode.1;
1177
1178                    let cond: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
1179                    let lhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?;
1180                    let rhs: AssignedCell<Fp, Fp> = heap[args[2].1].clone().try_into()?;
1181
1182                    let out: AssignedCell<Fp, Fp> =
1183                        condselect_chip.as_ref().unwrap().conditional_select(
1184                            &mut layouter.namespace(|| "cond_select"),
1185                            lhs,
1186                            rhs,
1187                            cond,
1188                        )?;
1189
1190                    trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
1191                    self.tracer.push_base(&out);
1192                    heap.push(HeapVar::Base(out));
1193                }
1194
1195                Opcode::ZeroCondSelect => {
1196                    trace!(target: "zk::vm", "Executing `ZeroCondSelect{:?}` opcode", opcode.1);
1197                    let args = &opcode.1;
1198
1199                    let lhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
1200                    let rhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?;
1201
1202                    let out: AssignedCell<Fp, Fp> = zerocond_chip.as_ref().unwrap().assign(
1203                        layouter.namespace(|| "zero_cond"),
1204                        lhs,
1205                        rhs,
1206                    )?;
1207
1208                    trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
1209                    self.tracer.push_base(&out);
1210                    heap.push(HeapVar::Base(out));
1211                }
1212
1213                Opcode::ConstrainEqualBase => {
1214                    trace!(target: "zk::vm", "Executing `ConstrainEqualBase{:?}` opcode", opcode.1);
1215                    let args = &opcode.1;
1216
1217                    let lhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
1218                    let rhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?;
1219
1220                    layouter.assign_region(
1221                        || "constrain witnessed base equality",
1222                        |mut region| region.constrain_equal(lhs.cell(), rhs.cell()),
1223                    )?;
1224                    self.tracer.push_void();
1225                }
1226
1227                Opcode::ConstrainEqualPoint => {
1228                    trace!(target: "zk::vm", "Executing `ConstrainEqualPoint{:?}` opcode", opcode.1);
1229                    let args = &opcode.1;
1230
1231                    let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
1232                        heap[args[0].1].clone().try_into()?;
1233
1234                    let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> =
1235                        heap[args[1].1].clone().try_into()?;
1236
1237                    lhs.constrain_equal(
1238                        layouter.namespace(|| "constrain ec point equality"),
1239                        &rhs,
1240                    )?;
1241                    self.tracer.push_void();
1242                }
1243
1244                Opcode::ConstrainInstance => {
1245                    trace!(target: "zk::vm", "Executing `ConstrainInstance{:?}` opcode", opcode.1);
1246                    let args = &opcode.1;
1247
1248                    let var: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?;
1249
1250                    layouter.constrain_instance(
1251                        var.cell(),
1252                        config.primary,
1253                        public_inputs_offset,
1254                    )?;
1255
1256                    public_inputs_offset += 1;
1257                    self.tracer.push_void();
1258                }
1259
1260                Opcode::DebugPrint => {
1261                    trace!(target: "zk::vm", "Executing `DebugPrint{:?}` opcode", opcode.1);
1262                    let args = &opcode.1;
1263
1264                    println!("[ZKVM DEBUG] HEAP INDEX: {}", args[0].1);
1265                    println!("[ZKVM DEBUG] {:#?}", heap[args[0].1]);
1266                    self.tracer.push_void();
1267                }
1268
1269                Opcode::Noop => {
1270                    error!(target: "zk::vm", "Unsupported opcode");
1271                    return Err(plonk::Error::Synthesis)
1272                }
1273            }
1274        }
1275        self.tracer.assert_correct(self.opcodes.len());
1276
1277        trace!(target: "zk::vm", "Exiting synthesize() successfully");
1278        Ok(())
1279    }
1280}