vm.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 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 19 use std::collections::HashSet; 20 21 use 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 }; 30 use 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 }; 49 use 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 }; 56 use tracing::{error, trace}; 57 58 pub use super::vm_heap::{HeapVar, Witness}; 59 use 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 }; 72 use 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)] 80 enum 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)] 130 pub 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 139 impl 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)] 269 pub 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)] 282 pub 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 290 impl 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 308 impl 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 unreachable!(); 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 ); 475 let merkle_cfg1 = MerkleChip::configure(meta, sinsemilla_cfg1.clone()); 476 (sinsemilla_cfg1, merkle_cfg1) 477 }; 478 479 let (sinsemilla_cfg2, merkle_cfg2) = { 480 let sinsemilla_cfg2 = SinsemillaChip::configure( 481 meta, 482 advices[5..].try_into().unwrap(), 483 advices[7], 484 lagrange_coeffs[1], 485 lookup, 486 range_check, 487 ); 488 let merkle_cfg2 = MerkleChip::configure(meta, sinsemilla_cfg2.clone()); 489 (sinsemilla_cfg2, merkle_cfg2) 490 }; 491 492 let smt_config = smt::PathChip::configure( 493 meta, 494 advices[0..2].try_into().unwrap(), 495 advices[2..6].try_into().unwrap(), 496 poseidon_config.clone(), 497 ); 498 499 // K-table for 64 bit range check lookups 500 let native_64_range_check_config = 501 NativeRangeCheckChip::<K, 64>::configure(meta, advices[8], table_idx); 502 503 // K-table for 253 bit range check lookups 504 let native_253_range_check_config = 505 NativeRangeCheckChip::<K, 253>::configure(meta, advices[8], table_idx); 506 507 // TODO: FIXME: Configure these better, this is just a stop-gap 508 let z1 = meta.advice_column(); 509 let z2 = meta.advice_column(); 510 511 let lessthan_config = LessThanChip::<K, 253>::configure( 512 meta, advices[6], advices[7], advices[8], z1, z2, table_idx, 513 ); 514 515 // Configuration for boolean checks, it uses the small_range_check 516 // chip with a range of 2, which enforces one bit, i.e. 0 or 1. 517 let boolcheck_config = SmallRangeCheckChip::configure(meta, advices[9], 2); 518 519 // Configuration for the conditional selection chip 520 let condselect_config = 521 ConditionalSelectChip::configure(meta, advices[1..5].try_into().unwrap()); 522 523 // Configuration for the zero_cond selection chip 524 let zerocond_config = ZeroCondChip::configure(meta, advices[1..5].try_into().unwrap()); 525 526 // Later we'll use this for optimisation 527 let chips = vec![ 528 VmChip::Ecc(ecc_config), 529 VmChip::Merkle((merkle_cfg1, merkle_cfg2)), 530 VmChip::SparseTree(smt_config), 531 VmChip::Sinsemilla((sinsemilla_cfg1, sinsemilla_cfg2)), 532 VmChip::Poseidon(poseidon_config), 533 VmChip::Arithmetic(arith_config), 534 VmChip::NativeRange64(native_64_range_check_config), 535 VmChip::NativeRange253(native_253_range_check_config), 536 VmChip::LessThan(lessthan_config), 537 VmChip::BoolCheck(boolcheck_config), 538 VmChip::CondSelect(condselect_config), 539 VmChip::ZeroCond(zerocond_config), 540 ]; 541 542 VmConfig { primary, witness: advices[0], chips } 543 } 544 545 fn synthesize( 546 &self, 547 config: Self::Config, 548 mut layouter: impl Layouter<pallas::Base>, 549 ) -> std::result::Result<(), plonk::Error> { 550 trace!(target: "zk::vm", "Entering synthesize()"); 551 552 // =================== 553 // VM Setup 554 //==================== 555 556 // Our heap which holds every variable we reference and create. 557 let mut heap: Vec<HeapVar> = vec![]; 558 559 // Our heap which holds all the literal values we have in the circuit. 560 // For now, we only support u64. 561 let mut litheap: Vec<u64> = vec![]; 562 563 // Offset for public inputs 564 let mut public_inputs_offset = 0; 565 566 // Offset for literals 567 let mut literals_offset = 0; 568 569 // Load the Sinsemilla generator lookup table used by the whole circuit. 570 if let Some(VmChip::Sinsemilla((sinsemilla_cfg1, _))) = 571 config.chips.iter().find(|&c| matches!(c, VmChip::Sinsemilla(_))) 572 { 573 trace!(target: "zk::vm", "Initializing Sinsemilla generator lookup table"); 574 SinsemillaChip::load(sinsemilla_cfg1.clone(), &mut layouter)?; 575 } 576 577 let no_sinsemilla_chip = !config.chips.iter().any(|c| matches!(c, VmChip::Sinsemilla(_))); 578 579 // Construct the 64-bit NativeRangeCheck chip 580 let rangecheck64_chip = config.rangecheck64_chip(); 581 if let Some(VmChip::NativeRange64(rangecheck64_config)) = 582 config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange64(_))) 583 { 584 if no_sinsemilla_chip { 585 trace!(target: "zk::vm", "Initializing k table for 64bit NativeRangeCheck"); 586 NativeRangeCheckChip::<K, 64>::load_k_table( 587 &mut layouter, 588 rangecheck64_config.k_values_table, 589 )?; 590 } 591 } 592 593 let no_rangecheck64_chip = 594 !config.chips.iter().any(|c| matches!(c, VmChip::NativeRange64(_))); 595 596 // Construct the 253-bit NativeRangeCheck and LessThan chips. 597 let rangecheck253_chip = config.rangecheck253_chip(); 598 let lessthan_chip = config.lessthan_chip(); 599 600 if let Some(VmChip::NativeRange253(rangecheck253_config)) = 601 config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange253(_))) 602 { 603 if no_sinsemilla_chip && no_rangecheck64_chip { 604 trace!(target: "zk::vm", "Initializing k table for 253bit NativeRangeCheck"); 605 NativeRangeCheckChip::<K, 253>::load_k_table( 606 &mut layouter, 607 rangecheck253_config.k_values_table, 608 )?; 609 } 610 } 611 612 // Construct the ECC chip. 613 let ecc_chip = config.ecc_chip(); 614 615 // Construct the Arithmetic chip. 616 let arith_chip = config.arithmetic_chip(); 617 618 // Construct the boolean check chip. 619 let boolcheck_chip = config.boolcheck_chip(); 620 621 // Construct the conditional selection chip 622 let condselect_chip = config.condselect_chip(); 623 624 // Construct the zero_cond selection chip 625 let zerocond_chip = config.zerocond_chip(); 626 627 // Construct sparse Merkle tree chip 628 let smt_chip = config.smt_chip().unwrap(); 629 630 // ========================== 631 // Constants setup 632 // ========================== 633 634 // This constant one is used for short multiplication 635 let one = assign_free_advice( 636 layouter.namespace(|| "Load constant one"), 637 config.witness, 638 Value::known(pallas::Base::ONE), 639 )?; 640 layouter.assign_region( 641 || "constrain constant", 642 |mut region| region.constrain_constant(one.cell(), pallas::Base::ONE), 643 )?; 644 645 // ANCHOR: constant_init 646 // Lookup and push constants onto the heap 647 for constant in &self.constants { 648 trace!( 649 target: "zk::vm", 650 "Pushing constant `{}` to heap address {}", 651 constant.as_str(), 652 heap.len() 653 ); 654 match constant.as_str() { 655 "VALUE_COMMIT_VALUE" => { 656 let vcv = ValueCommitV; 657 let vcv = FixedPointShort::from_inner(ecc_chip.as_ref().unwrap().clone(), vcv); 658 heap.push(HeapVar::EcFixedPointShort(vcv)); 659 } 660 "VALUE_COMMIT_RANDOM" => { 661 let vcr = OrchardFixedBasesFull::ValueCommitR; 662 let vcr = FixedPoint::from_inner(ecc_chip.as_ref().unwrap().clone(), vcr); 663 heap.push(HeapVar::EcFixedPoint(vcr)); 664 } 665 "VALUE_COMMIT_RANDOM_BASE" => { 666 let vcr = ConstBaseFieldElement::value_commit_r(); 667 let vcr = 668 FixedPointBaseField::from_inner(ecc_chip.as_ref().unwrap().clone(), vcr); 669 heap.push(HeapVar::EcFixedPointBase(vcr)); 670 } 671 "NULLIFIER_K" => { 672 let nfk = ConstBaseFieldElement::nullifier_k(); 673 let nfk = 674 FixedPointBaseField::from_inner(ecc_chip.as_ref().unwrap().clone(), nfk); 675 heap.push(HeapVar::EcFixedPointBase(nfk)); 676 } 677 678 _ => { 679 error!(target: "zk::vm", "Invalid constant name: {}", constant.as_str()); 680 return Err(plonk::Error::Synthesis) 681 } 682 } 683 } 684 // ANCHOR_END: constant_init 685 686 // ANCHOR: literals_init 687 // Load the literals onto the literal heap 688 // N.B. Only uint64 is supported right now. 689 for literal in &self.literals { 690 match literal.0 { 691 LitType::Uint64 => match literal.1.parse::<u64>() { 692 Ok(v) => litheap.push(v), 693 Err(e) => { 694 error!(target: "zk::vm", "Failed converting u64 literal: {e}"); 695 return Err(plonk::Error::Synthesis) 696 } 697 }, 698 _ => { 699 error!(target: "zk::vm", "Invalid literal: {literal:?}"); 700 return Err(plonk::Error::Synthesis) 701 } 702 } 703 } 704 // ANCHOR_END: literals_init 705 706 // ANCHOR: witness_init 707 // Push the witnesses onto the heap, and potentially, if the witness 708 // is in the Base field (like the entire circuit is), load it into a 709 // table cell. 710 for witness in &self.witnesses { 711 match witness { 712 Witness::EcPoint(w) => { 713 trace!(target: "zk::vm", "Witnessing EcPoint into circuit"); 714 let point = Point::new( 715 ecc_chip.as_ref().unwrap().clone(), 716 layouter.namespace(|| "Witness EcPoint"), 717 w.as_ref().map(|cm| cm.to_affine()), 718 )?; 719 720 trace!(target: "zk::vm", "Pushing EcPoint to heap address {}", heap.len()); 721 heap.push(HeapVar::EcPoint(point)); 722 } 723 724 Witness::EcNiPoint(w) => { 725 trace!(target: "zk::vm", "Witnessing EcNiPoint into circuit"); 726 let point = NonIdentityPoint::new( 727 ecc_chip.as_ref().unwrap().clone(), 728 layouter.namespace(|| "Witness EcNiPoint"), 729 w.as_ref().map(|cm| cm.to_affine()), 730 )?; 731 732 trace!(target: "zk::vm", "Pushing EcNiPoint to heap address {}", heap.len()); 733 heap.push(HeapVar::EcNiPoint(point)); 734 } 735 736 Witness::EcFixedPoint(_) => { 737 error!(target: "zk::vm", "Unable to witness EcFixedPoint, this is unimplemented."); 738 return Err(plonk::Error::Synthesis) 739 } 740 741 Witness::Base(w) => { 742 trace!(target: "zk::vm", "Witnessing Base into circuit"); 743 let base = assign_free_advice( 744 layouter.namespace(|| "Witness Base"), 745 config.witness, 746 *w, 747 )?; 748 749 trace!(target: "zk::vm", "Pushing Base to heap address {}", heap.len()); 750 heap.push(HeapVar::Base(base)); 751 } 752 753 Witness::Scalar(w) => { 754 trace!(target: "zk::vm", "Witnessing Scalar into circuit"); 755 let scalar = ScalarFixed::new( 756 ecc_chip.as_ref().unwrap().clone(), 757 layouter.namespace(|| "Witness ScalarFixed"), 758 *w, 759 )?; 760 761 trace!(target: "zk::vm", "Pushing Scalar to heap address {}", heap.len()); 762 heap.push(HeapVar::Scalar(scalar)); 763 } 764 765 Witness::MerklePath(w) => { 766 trace!(target: "zk::vm", "Witnessing MerklePath into circuit"); 767 let path: Value<[pallas::Base; MERKLE_DEPTH_ORCHARD]> = 768 w.map(|typed_path| gen_const_array(|i| typed_path[i].inner())); 769 770 trace!(target: "zk::vm", "Pushing MerklePath to heap address {}", heap.len()); 771 heap.push(HeapVar::MerklePath(path)); 772 } 773 774 Witness::SparseMerklePath(w) => { 775 let path: Value<[pallas::Base; SMT_FP_DEPTH]> = 776 w.map(|typed_path| gen_const_array(|i| typed_path[i])); 777 778 trace!(target: "zk::vm", "Pushing SparseMerklePath to heap address {}", heap.len()); 779 heap.push(HeapVar::SparseMerklePath(path)); 780 } 781 782 Witness::Uint32(w) => { 783 trace!(target: "zk::vm", "Pushing Uint32 to heap address {}", heap.len()); 784 heap.push(HeapVar::Uint32(*w)); 785 } 786 787 Witness::Uint64(w) => { 788 trace!(target: "zk::vm", "Pushing Uint64 to heap address {}", heap.len()); 789 heap.push(HeapVar::Uint64(*w)); 790 } 791 } 792 } 793 // ANCHOR_END: witness_init 794 795 // ============================= 796 // And now, work through opcodes 797 // ============================= 798 self.tracer.clear(); 799 // TODO: Copy constraints 800 // ANCHOR: opcode_begin 801 for opcode in &self.opcodes { 802 match opcode.0 { 803 Opcode::EcAdd => { 804 trace!(target: "zk::vm", "Executing `EcAdd{:?}` opcode", opcode.1); 805 let args = &opcode.1; 806 807 let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 808 heap[args[0].1].clone().try_into()?; 809 810 let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 811 heap[args[1].1].clone().try_into()?; 812 813 let ret = lhs.add(layouter.namespace(|| "EcAdd()"), &rhs)?; 814 815 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 816 self.tracer.push_ecpoint(&ret); 817 heap.push(HeapVar::EcPoint(ret)); 818 } 819 // ANCHOR_END: opcode_begin 820 Opcode::EcMul => { 821 trace!(target: "zk::vm", "Executing `EcMul{:?}` opcode", opcode.1); 822 let args = &opcode.1; 823 824 let lhs: FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>> = 825 heap[args[1].1].clone().try_into()?; 826 827 let rhs: ScalarFixed<pallas::Affine, EccChip<OrchardFixedBases>> = 828 heap[args[0].1].clone().try_into()?; 829 830 let (ret, _) = lhs.mul(layouter.namespace(|| "EcMul()"), rhs)?; 831 832 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 833 self.tracer.push_ecpoint(&ret); 834 heap.push(HeapVar::EcPoint(ret)); 835 } 836 837 Opcode::EcMulVarBase => { 838 trace!(target: "zk::vm", "Executing `EcMulVarBase{:?}` opcode", opcode.1); 839 let args = &opcode.1; 840 841 let lhs: NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>> = 842 heap[args[1].1].clone().try_into()?; 843 844 let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 845 let rhs = ScalarVar::from_base( 846 ecc_chip.as_ref().unwrap().clone(), 847 layouter.namespace(|| "EcMulVarBase::from_base()"), 848 &rhs, 849 )?; 850 851 let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulVarBase()"), rhs)?; 852 853 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 854 self.tracer.push_ecpoint(&ret); 855 heap.push(HeapVar::EcPoint(ret)); 856 } 857 858 Opcode::EcMulBase => { 859 trace!(target: "zk::vm", "Executing `EcMulBase{:?}` opcode", opcode.1); 860 let args = &opcode.1; 861 862 let lhs: FixedPointBaseField<pallas::Affine, EccChip<OrchardFixedBases>> = 863 heap[args[1].1].clone().try_into()?; 864 865 let rhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 866 867 let ret = lhs.mul(layouter.namespace(|| "EcMulBase()"), rhs)?; 868 869 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 870 self.tracer.push_ecpoint(&ret); 871 heap.push(HeapVar::EcPoint(ret)); 872 } 873 874 Opcode::EcMulShort => { 875 trace!(target: "zk::vm", "Executing `EcMulShort{:?}` opcode", opcode.1); 876 let args = &opcode.1; 877 878 let lhs: FixedPointShort<pallas::Affine, EccChip<OrchardFixedBases>> = 879 heap[args[1].1].clone().try_into()?; 880 881 let rhs = ScalarFixedShort::new( 882 ecc_chip.as_ref().unwrap().clone(), 883 layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"), 884 (heap[args[0].1].clone().try_into()?, one.clone()), 885 )?; 886 887 let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?; 888 889 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 890 self.tracer.push_ecpoint(&ret); 891 heap.push(HeapVar::EcPoint(ret)); 892 } 893 894 Opcode::EcGetX => { 895 trace!(target: "zk::vm", "Executing `EcGetX{:?}` opcode", opcode.1); 896 let args = &opcode.1; 897 898 let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 899 heap[args[0].1].clone().try_into()?; 900 901 let ret = point.inner().x(); 902 903 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 904 self.tracer.push_base(&ret); 905 heap.push(HeapVar::Base(ret)); 906 } 907 908 Opcode::EcGetY => { 909 trace!(target: "zk::vm", "Executing `EcGetY{:?}` opcode", opcode.1); 910 let args = &opcode.1; 911 912 let point: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 913 heap[args[0].1].clone().try_into()?; 914 915 let ret = point.inner().y(); 916 917 trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len()); 918 self.tracer.push_base(&ret); 919 heap.push(HeapVar::Base(ret)); 920 } 921 922 Opcode::PoseidonHash => { 923 trace!(target: "zk::vm", "Executing `PoseidonHash{:?}` opcode", opcode.1); 924 let args = &opcode.1; 925 926 let mut poseidon_message: Vec<AssignedCell<Fp, Fp>> = 927 Vec::with_capacity(args.len()); 928 929 for idx in args { 930 poseidon_message.push(heap[idx.1].clone().try_into()?); 931 } 932 933 macro_rules! poseidon_hash { 934 ($len:expr, $hasher:ident, $output:ident, $cell:ident) => { 935 let $hasher = PoseidonHash::< 936 _, 937 _, 938 poseidon::P128Pow5T3, 939 poseidon::ConstantLength<$len>, 940 3, 941 2, 942 >::init( 943 config.poseidon_chip().unwrap(), 944 layouter.namespace(|| "PoseidonHash init"), 945 )?; 946 947 let $output = $hasher.hash( 948 layouter.namespace(|| "PoseidonHash hash"), 949 poseidon_message.try_into().unwrap(), 950 )?; 951 952 let $cell: AssignedCell<Fp, Fp> = $output.into(); 953 954 trace!(target: "zk::vm", "Pushing hash to heap address {}", heap.len()); 955 self.tracer.push_base(&$cell); 956 heap.push(HeapVar::Base($cell)); 957 }; 958 } 959 960 macro_rules! vla { 961 ($args:ident, $a:ident, $b:ident, $c:ident, $($num:tt)*) => { 962 match $args.len() { 963 $($num => { 964 poseidon_hash!($num, $a, $b, $c); 965 })* 966 _ => { 967 error!(target: "zk::vm", "Unsupported poseidon hash for {} elements", $args.len()); 968 return Err(plonk::Error::Synthesis) 969 } 970 } 971 }; 972 } 973 974 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); 975 } 976 977 Opcode::MerkleRoot => { 978 // TODO: all these trace statements could have trace!(..., args) instead 979 trace!(target: "zk::vm", "Executing `MerkleRoot{:?}` opcode", opcode.1); 980 let args = &opcode.1; 981 982 let leaf_pos = heap[args[0].1].clone().try_into()?; 983 let merkle_path: Value<[Fp; MERKLE_DEPTH_ORCHARD]> = 984 heap[args[1].1].clone().try_into()?; 985 let leaf = heap[args[2].1].clone().try_into()?; 986 987 let merkle_inputs = MerklePath::construct( 988 [config.merkle_chip_1().unwrap(), config.merkle_chip_2().unwrap()], 989 OrchardHashDomains::MerkleCrh, 990 leaf_pos, 991 merkle_path, 992 ); 993 994 let root = merkle_inputs 995 .calculate_root(layouter.namespace(|| "MerkleRoot()"), leaf)?; 996 997 trace!(target: "zk::vm", "Pushing merkle root to heap address {}", heap.len()); 998 self.tracer.push_base(&root); 999 heap.push(HeapVar::Base(root)); 1000 } 1001 1002 Opcode::SparseMerkleRoot => { 1003 trace!(target: "zk::vm", "Executing `SparseTreeIsMember{:?}` opcode", opcode.1); 1004 let args = &opcode.1; 1005 1006 let pos = heap[args[0].1].clone().try_into()?; 1007 let path: Value<[Fp; SMT_FP_DEPTH]> = heap[args[1].1].clone().try_into()?; 1008 let leaf = heap[args[2].1].clone().try_into()?; 1009 1010 let root = smt_chip.check_membership(&mut layouter, pos, path, leaf)?; 1011 1012 self.tracer.push_base(&root); 1013 heap.push(HeapVar::Base(root)); 1014 } 1015 1016 Opcode::BaseAdd => { 1017 trace!(target: "zk::vm", "Executing `BaseAdd{:?}` opcode", opcode.1); 1018 let args = &opcode.1; 1019 1020 let lhs = &heap[args[0].1].clone().try_into()?; 1021 let rhs = &heap[args[1].1].clone().try_into()?; 1022 1023 let sum = arith_chip.as_ref().unwrap().add( 1024 layouter.namespace(|| "BaseAdd()"), 1025 lhs, 1026 rhs, 1027 )?; 1028 1029 trace!(target: "zk::vm", "Pushing sum to heap address {}", heap.len()); 1030 self.tracer.push_base(&sum); 1031 heap.push(HeapVar::Base(sum)); 1032 } 1033 1034 Opcode::BaseMul => { 1035 trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1); 1036 let args = &opcode.1; 1037 1038 let lhs = &heap[args[0].1].clone().try_into()?; 1039 let rhs = &heap[args[1].1].clone().try_into()?; 1040 1041 let product = arith_chip.as_ref().unwrap().mul( 1042 layouter.namespace(|| "BaseMul()"), 1043 lhs, 1044 rhs, 1045 )?; 1046 1047 trace!(target: "zk::vm", "Pushing product to heap address {}", heap.len()); 1048 self.tracer.push_base(&product); 1049 heap.push(HeapVar::Base(product)); 1050 } 1051 1052 Opcode::BaseSub => { 1053 trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1); 1054 let args = &opcode.1; 1055 1056 let lhs = &heap[args[0].1].clone().try_into()?; 1057 let rhs = &heap[args[1].1].clone().try_into()?; 1058 1059 let difference = arith_chip.as_ref().unwrap().sub( 1060 layouter.namespace(|| "BaseSub()"), 1061 lhs, 1062 rhs, 1063 )?; 1064 1065 trace!(target: "zk::vm", "Pushing difference to heap address {}", heap.len()); 1066 self.tracer.push_base(&difference); 1067 heap.push(HeapVar::Base(difference)); 1068 } 1069 1070 Opcode::WitnessBase => { 1071 trace!(target: "zk::vm", "Executing `WitnessBase{:?}` opcode", opcode.1); 1072 //let args = &opcode.1; 1073 1074 let lit = litheap[literals_offset]; 1075 literals_offset += 1; 1076 1077 let witness = assign_free_advice( 1078 layouter.namespace(|| "Witness literal"), 1079 config.witness, 1080 Value::known(pallas::Base::from(lit)), 1081 )?; 1082 1083 layouter.assign_region( 1084 || "constrain constant", 1085 |mut region| { 1086 region.constrain_constant(witness.cell(), pallas::Base::from(lit)) 1087 }, 1088 )?; 1089 1090 trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len()); 1091 self.tracer.push_base(&witness); 1092 heap.push(HeapVar::Base(witness)); 1093 } 1094 1095 Opcode::RangeCheck => { 1096 trace!(target: "zk::vm", "Executing `RangeCheck{:?}` opcode", opcode.1); 1097 let args = &opcode.1; 1098 1099 let lit = litheap[literals_offset]; 1100 literals_offset += 1; 1101 1102 let arg = heap[args[1].1].clone(); 1103 1104 match lit { 1105 64 => { 1106 rangecheck64_chip.as_ref().unwrap().copy_range_check( 1107 layouter.namespace(|| "copy range check 64"), 1108 arg.try_into()?, 1109 )?; 1110 } 1111 253 => { 1112 rangecheck253_chip.as_ref().unwrap().copy_range_check( 1113 layouter.namespace(|| "copy range check 253"), 1114 arg.try_into()?, 1115 )?; 1116 } 1117 x => { 1118 error!(target: "zk::vm", "Unsupported bit-range {x} for range_check"); 1119 return Err(plonk::Error::Synthesis) 1120 } 1121 } 1122 self.tracer.push_void(); 1123 } 1124 1125 Opcode::LessThanStrict => { 1126 trace!(target: "zk::vm", "Executing `LessThanStrict{:?}` opcode", opcode.1); 1127 let args = &opcode.1; 1128 1129 let a = heap[args[0].1].clone().try_into()?; 1130 let b = heap[args[1].1].clone().try_into()?; 1131 1132 lessthan_chip.as_ref().unwrap().copy_less_than( 1133 layouter.namespace(|| "copy a<b check"), 1134 a, 1135 b, 1136 0, 1137 true, 1138 )?; 1139 self.tracer.push_void(); 1140 } 1141 1142 Opcode::LessThanLoose => { 1143 trace!(target: "zk::vm", "Executing `LessThanLoose{:?}` opcode", opcode.1); 1144 let args = &opcode.1; 1145 1146 let a = heap[args[0].1].clone().try_into()?; 1147 let b = heap[args[1].1].clone().try_into()?; 1148 1149 lessthan_chip.as_ref().unwrap().copy_less_than( 1150 layouter.namespace(|| "copy a<b check"), 1151 a, 1152 b, 1153 0, 1154 false, 1155 )?; 1156 self.tracer.push_void(); 1157 } 1158 1159 Opcode::BoolCheck => { 1160 trace!(target: "zk::vm", "Executing `BoolCheck{:?}` opcode", opcode.1); 1161 let args = &opcode.1; 1162 1163 let w = heap[args[0].1].clone().try_into()?; 1164 1165 boolcheck_chip 1166 .as_ref() 1167 .unwrap() 1168 .small_range_check(layouter.namespace(|| "copy boolean check"), w)?; 1169 self.tracer.push_void(); 1170 } 1171 1172 Opcode::CondSelect => { 1173 trace!(target: "zk::vm", "Executing `CondSelect{:?}` opcode", opcode.1); 1174 let args = &opcode.1; 1175 1176 let cond: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 1177 let lhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?; 1178 let rhs: AssignedCell<Fp, Fp> = heap[args[2].1].clone().try_into()?; 1179 1180 let out: AssignedCell<Fp, Fp> = 1181 condselect_chip.as_ref().unwrap().conditional_select( 1182 &mut layouter.namespace(|| "cond_select"), 1183 lhs, 1184 rhs, 1185 cond, 1186 )?; 1187 1188 trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len()); 1189 self.tracer.push_base(&out); 1190 heap.push(HeapVar::Base(out)); 1191 } 1192 1193 Opcode::ZeroCondSelect => { 1194 trace!(target: "zk::vm", "Executing `ZeroCondSelect{:?}` opcode", opcode.1); 1195 let args = &opcode.1; 1196 1197 let lhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 1198 let rhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?; 1199 1200 let out: AssignedCell<Fp, Fp> = zerocond_chip.as_ref().unwrap().assign( 1201 layouter.namespace(|| "zero_cond"), 1202 lhs, 1203 rhs, 1204 )?; 1205 1206 trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len()); 1207 self.tracer.push_base(&out); 1208 heap.push(HeapVar::Base(out)); 1209 } 1210 1211 Opcode::ConstrainEqualBase => { 1212 trace!(target: "zk::vm", "Executing `ConstrainEqualBase{:?}` opcode", opcode.1); 1213 let args = &opcode.1; 1214 1215 let lhs: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 1216 let rhs: AssignedCell<Fp, Fp> = heap[args[1].1].clone().try_into()?; 1217 1218 layouter.assign_region( 1219 || "constrain witnessed base equality", 1220 |mut region| region.constrain_equal(lhs.cell(), rhs.cell()), 1221 )?; 1222 self.tracer.push_void(); 1223 } 1224 1225 Opcode::ConstrainEqualPoint => { 1226 trace!(target: "zk::vm", "Executing `ConstrainEqualPoint{:?}` opcode", opcode.1); 1227 let args = &opcode.1; 1228 1229 let lhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 1230 heap[args[0].1].clone().try_into()?; 1231 1232 let rhs: Point<pallas::Affine, EccChip<OrchardFixedBases>> = 1233 heap[args[1].1].clone().try_into()?; 1234 1235 lhs.constrain_equal( 1236 layouter.namespace(|| "constrain ec point equality"), 1237 &rhs, 1238 )?; 1239 self.tracer.push_void(); 1240 } 1241 1242 Opcode::ConstrainInstance => { 1243 trace!(target: "zk::vm", "Executing `ConstrainInstance{:?}` opcode", opcode.1); 1244 let args = &opcode.1; 1245 1246 let var: AssignedCell<Fp, Fp> = heap[args[0].1].clone().try_into()?; 1247 1248 layouter.constrain_instance( 1249 var.cell(), 1250 config.primary, 1251 public_inputs_offset, 1252 )?; 1253 1254 public_inputs_offset += 1; 1255 self.tracer.push_void(); 1256 } 1257 1258 Opcode::DebugPrint => { 1259 trace!(target: "zk::vm", "Executing `DebugPrint{:?}` opcode", opcode.1); 1260 let args = &opcode.1; 1261 1262 println!("[ZKVM DEBUG] HEAP INDEX: {}", args[0].1); 1263 println!("[ZKVM DEBUG] {:#?}", heap[args[0].1]); 1264 self.tracer.push_void(); 1265 } 1266 1267 Opcode::Noop => { 1268 error!(target: "zk::vm", "Unsupported opcode"); 1269 return Err(plonk::Error::Synthesis) 1270 } 1271 } 1272 } 1273 self.tracer.assert_correct(self.opcodes.len()); 1274 1275 trace!(target: "zk::vm", "Exiting synthesize() successfully"); 1276 Ok(()) 1277 } 1278 }