/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::collections::HashSet;
use darkfi_sdk::crypto::constants::{
sinsemilla::{OrchardCommitDomains, OrchardHashDomains},
util::gen_const_array,
NullifierK, OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV, MERKLE_DEPTH_ORCHARD,
};
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point, ScalarFixed,
ScalarFixedShort, ScalarVar,
},
poseidon::{
primitives as poseidon, Hash as PoseidonHash, Pow5Chip as PoseidonChip,
Pow5Config as PoseidonConfig,
},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
merkle::{
chip::{MerkleChip, MerkleConfig},
MerklePath,
},
},
utilities::lookup_range_check::LookupRangeCheckConfig,
};
use halo2_proofs::{
arithmetic::Field,
circuit::{floor_planner, AssignedCell, Layouter, Value},
pasta::{group::Curve, pallas, Fp},
plonk,
plonk::{Advice, Circuit, Column, ConstraintSystem, Instance as InstanceColumn},
};
use log::{error, trace};
pub use super::vm_heap::{HeapVar, Witness};
use super::{
assign_free_advice,
gadget::{
arithmetic::{ArithChip, ArithConfig, ArithInstruction},
cond_select::{ConditionalSelectChip, ConditionalSelectConfig},
less_than::{LessThanChip, LessThanConfig},
native_range_check::{NativeRangeCheckChip, NativeRangeCheckConfig},
small_range_check::{SmallRangeCheckChip, SmallRangeCheckConfig},
zero_cond::{ZeroCondChip, ZeroCondConfig},
},
tracer::ZkTracer,
};
use crate::zkas::{
types::{HeapType, LitType},
Opcode, ZkBinary,
};
/// Available chips/gadgets in the zkvm
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
enum VmChip {
/// ECC Chip
Ecc(EccConfig),
/// Merkle tree chip (using Sinsemilla)
Merkle(
(
MerkleConfig,
MerkleConfig,
),
),
/// Sinsemilla chip
Sinsemilla(
(
SinsemillaConfig,
SinsemillaConfig,
),
),
/// Poseidon hash chip
Poseidon(PoseidonConfig),
/// Base field arithmetic chip
Arithmetic(ArithConfig),
/// 64 bit native range check
NativeRange64(NativeRangeCheckConfig<3, 64, 22>),
/// 253 bit native range check
NativeRange253(NativeRangeCheckConfig<3, 253, 85>),
/// 253 bit `a < b` check
LessThan(LessThanConfig<3, 253, 85>),
/// Boolean check
BoolCheck(SmallRangeCheckConfig),
/// Conditional selection
CondSelect(ConditionalSelectConfig),
/// Zero-Cond selection
ZeroCond(ZeroCondConfig),
}
/// zkvm configuration
#[derive(Clone)]
pub struct VmConfig {
/// Chips used in the circuit
chips: Vec,
/// Instance column used for public inputs
primary: Column,
/// Advice column used to witness values
witness: Column,
}
impl VmConfig {
fn ecc_chip(&self) -> Option> {
let Some(VmChip::Ecc(ecc_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::Ecc(_)))
else {
return None
};
Some(EccChip::construct(ecc_config.clone()))
}
fn merkle_chip_1(
&self,
) -> Option> {
let Some(VmChip::Merkle((merkle_cfg1, _))) =
self.chips.iter().find(|&c| matches!(c, VmChip::Merkle(_)))
else {
return None
};
Some(MerkleChip::construct(merkle_cfg1.clone()))
}
fn merkle_chip_2(
&self,
) -> Option> {
let Some(VmChip::Merkle((_, merkle_cfg2))) =
self.chips.iter().find(|&c| matches!(c, VmChip::Merkle(_)))
else {
return None
};
Some(MerkleChip::construct(merkle_cfg2.clone()))
}
fn poseidon_chip(&self) -> Option> {
let Some(VmChip::Poseidon(poseidon_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::Poseidon(_)))
else {
return None
};
Some(PoseidonChip::construct(poseidon_config.clone()))
}
fn arithmetic_chip(&self) -> Option> {
let Some(VmChip::Arithmetic(arith_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::Arithmetic(_)))
else {
return None
};
Some(ArithChip::construct(arith_config.clone()))
}
fn condselect_chip(&self) -> Option> {
let Some(VmChip::CondSelect(condselect_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::CondSelect(_)))
else {
return None
};
Some(ConditionalSelectChip::construct(condselect_config.clone(), ()))
}
fn zerocond_chip(&self) -> Option> {
let Some(VmChip::ZeroCond(zerocond_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::ZeroCond(_)))
else {
return None
};
Some(ZeroCondChip::construct(zerocond_config.clone()))
}
fn rangecheck64_chip(&self) -> Option> {
let Some(VmChip::NativeRange64(range_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::NativeRange64(_)))
else {
return None
};
Some(NativeRangeCheckChip::construct(range_config.clone()))
}
fn rangecheck253_chip(&self) -> Option> {
let Some(VmChip::NativeRange253(range_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::NativeRange253(_)))
else {
return None
};
Some(NativeRangeCheckChip::construct(range_config.clone()))
}
fn lessthan_chip(&self) -> Option> {
let Some(VmChip::LessThan(lessthan_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::LessThan(_)))
else {
return None
};
Some(LessThanChip::construct(lessthan_config.clone()))
}
fn boolcheck_chip(&self) -> Option> {
let Some(VmChip::BoolCheck(boolcheck_config)) =
self.chips.iter().find(|&c| matches!(c, VmChip::BoolCheck(_)))
else {
return None
};
Some(SmallRangeCheckChip::construct(boolcheck_config.clone()))
}
}
/// Configuration parameters for the circuit.
/// Defines which chips we need to initialize and configure.
#[derive(Default)]
#[allow(dead_code)]
pub struct ZkParams {
init_ecc: bool,
init_poseidon: bool,
init_sinsemilla: bool,
init_arithmetic: bool,
init_nativerange: bool,
init_lessthan: bool,
init_boolcheck: bool,
init_condselect: bool,
init_zerocond: bool,
}
#[derive(Clone)]
pub struct ZkCircuit {
constants: Vec,
pub(super) witnesses: Vec,
literals: Vec<(LitType, String)>,
pub(super) opcodes: Vec<(Opcode, Vec<(HeapType, usize)>)>,
pub tracer: ZkTracer,
}
impl ZkCircuit {
pub fn new(witnesses: Vec, circuit_code: &ZkBinary) -> Self {
let constants = circuit_code.constants.iter().map(|x| x.1.clone()).collect();
let literals = circuit_code.literals.clone();
Self {
constants,
witnesses,
literals,
opcodes: circuit_code.opcodes.clone(),
tracer: ZkTracer::new(true),
}
}
pub fn enable_trace(&mut self) {
self.tracer.init();
}
}
impl Circuit for ZkCircuit {
type Config = VmConfig;
type FloorPlanner = floor_planner::V1;
type Params = ZkParams;
fn without_witnesses(&self) -> Self {
Self {
constants: self.constants.clone(),
witnesses: self.witnesses.clone(),
literals: self.literals.clone(),
opcodes: self.opcodes.clone(),
tracer: ZkTracer::new(false),
}
}
fn configure(_meta: &mut ConstraintSystem) -> Self::Config {
unreachable!();
}
fn params(&self) -> Self::Params {
// Gather all opcodes used in the circuit.
let mut opcodes = HashSet::new();
for (opcode, _) in &self.opcodes {
opcodes.insert(opcode);
}
// Conditions on which we enable the ECC chip
let init_ecc = !self.constants.is_empty() ||
opcodes.contains(&Opcode::EcAdd) ||
opcodes.contains(&Opcode::EcMul) ||
opcodes.contains(&Opcode::EcMulBase) ||
opcodes.contains(&Opcode::EcMulShort) ||
opcodes.contains(&Opcode::EcMulVarBase) ||
opcodes.contains(&Opcode::EcGetX) ||
opcodes.contains(&Opcode::EcGetY) ||
opcodes.contains(&Opcode::ConstrainEqualPoint) ||
self.witnesses.iter().any(|x| {
matches!(x, Witness::EcPoint(_)) ||
matches!(x, Witness::EcNiPoint(_)) ||
matches!(x, Witness::EcFixedPoint(_)) ||
matches!(x, Witness::Scalar(_))
});
// Conditions on which we enable the Poseidon hash chip
let init_poseidon = opcodes.contains(&Opcode::PoseidonHash);
// Conditions on which we enable the Sinsemilla and Merkle chips
let init_sinsemilla = opcodes.contains(&Opcode::MerkleRoot);
// Conditions on which we enable the base field Arithmetic chip
let init_arithmetic = opcodes.contains(&Opcode::BaseAdd) ||
opcodes.contains(&Opcode::BaseSub) ||
opcodes.contains(&Opcode::BaseMul);
// Conditions on which we enable the native range check chips
// TODO: Separate 253 and 64.
let init_nativerange = opcodes.contains(&Opcode::RangeCheck) ||
opcodes.contains(&Opcode::LessThanLoose) ||
opcodes.contains(&Opcode::LessThanStrict);
// Conditions on which we enable the less than comparison chip
let init_lessthan =
opcodes.contains(&Opcode::LessThanLoose) || opcodes.contains(&Opcode::LessThanStrict);
// Conditions on which we enable the boolean check chip
let init_boolcheck = opcodes.contains(&Opcode::BoolCheck);
// Conditions on which we enable the conditional selection chip
let init_condselect = opcodes.contains(&Opcode::CondSelect);
// Conditions on which we enable the zero cond selection chip
let init_zerocond = opcodes.contains(&Opcode::ZeroCondSelect);
ZkParams {
init_ecc,
init_poseidon,
init_sinsemilla,
init_arithmetic,
init_nativerange,
init_lessthan,
init_boolcheck,
init_condselect,
init_zerocond,
}
}
fn configure_with_params(
meta: &mut ConstraintSystem,
_params: Self::Params,
) -> Self::Config {
// Advice columns used in the circuit
let mut advices = vec![];
for _ in 0..10 {
advices.push(meta.advice_column());
}
// Instance column used for public inputs
let primary = meta.instance_column();
meta.enable_equality(primary);
// Permutation over all advice columns
for advice in advices.iter() {
meta.enable_equality(*advice);
}
// Fixed columns for the Sinsemilla generator lookup table
let table_idx = meta.lookup_table_column();
let lookup = (table_idx, meta.lookup_table_column(), meta.lookup_table_column());
// Poseidon requires four advice columns, while ECC incomplete addition
// requires six. We can reduce the proof size by sharing fixed columns
// between the ECC and Poseidon chips.
// TODO: For multiple invocations perhaps they could/should be configured
// in parallel rather than sharing?
let lagrange_coeffs = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
// Also use the first Lagrange coefficient column for loading global constants.
meta.enable_constant(lagrange_coeffs[0]);
// Use one of the right-most advice columns for all of our range checks.
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
// Configuration for curve point operations.
// This uses 10 advice columns and spans the whole circuit.
let ecc_config = EccChip::::configure(
meta,
advices[0..10].try_into().unwrap(),
lagrange_coeffs,
range_check,
);
// Configuration for the Poseidon hash
let poseidon_config = PoseidonChip::configure::(
meta,
advices[6..9].try_into().unwrap(),
advices[5],
rc_a,
rc_b,
);
// Configuration for the Arithmetic chip
let arith_config = ArithChip::configure(meta, advices[7], advices[8], advices[6]);
// Configuration for a Sinsemilla hash instantiation and a
// Merkle hash instantiation using this Sinsemilla instance.
// Since the Sinsemilla config uses only 5 advice columns,
// we can fit two instances side-by-side.
let (sinsemilla_cfg1, merkle_cfg1) = {
let sinsemilla_cfg1 = SinsemillaChip::configure(
meta,
advices[..5].try_into().unwrap(),
advices[6],
lagrange_coeffs[0],
lookup,
range_check,
);
let merkle_cfg1 = MerkleChip::configure(meta, sinsemilla_cfg1.clone());
(sinsemilla_cfg1, merkle_cfg1)
};
let (sinsemilla_cfg2, merkle_cfg2) = {
let sinsemilla_cfg2 = SinsemillaChip::configure(
meta,
advices[5..].try_into().unwrap(),
advices[7],
lagrange_coeffs[1],
lookup,
range_check,
);
let merkle_cfg2 = MerkleChip::configure(meta, sinsemilla_cfg2.clone());
(sinsemilla_cfg2, merkle_cfg2)
};
// K-table for 64 bit range check lookups
let k_values_table_64 = meta.lookup_table_column();
let native_64_range_check_config =
NativeRangeCheckChip::<3, 64, 22>::configure(meta, advices[8], k_values_table_64);
// K-table for 253 bit range check lookups
let k_values_table_253 = meta.lookup_table_column();
let native_253_range_check_config =
NativeRangeCheckChip::<3, 253, 85>::configure(meta, advices[8], k_values_table_253);
// TODO: FIXME: Configure these better, this is just a stop-gap
let z1 = meta.advice_column();
let z2 = meta.advice_column();
let lessthan_config = LessThanChip::<3, 253, 85>::configure(
meta,
advices[6],
advices[7],
advices[8],
z1,
z2,
k_values_table_253,
);
// Configuration for boolean checks, it uses the small_range_check
// chip with a range of 2, which enforces one bit, i.e. 0 or 1.
let boolcheck_config = SmallRangeCheckChip::configure(meta, advices[9], 2);
// Configuration for the conditional selection chip
let condselect_config =
ConditionalSelectChip::configure(meta, advices[1..5].try_into().unwrap());
// Configuration for the zero_cond selection chip
let zerocond_config = ZeroCondChip::configure(meta, advices[1..5].try_into().unwrap());
// Later we'll use this for optimisation
let chips = vec![
VmChip::Ecc(ecc_config),
VmChip::Merkle((merkle_cfg1, merkle_cfg2)),
VmChip::Sinsemilla((sinsemilla_cfg1, sinsemilla_cfg2)),
VmChip::Poseidon(poseidon_config),
VmChip::Arithmetic(arith_config),
VmChip::NativeRange64(native_64_range_check_config),
VmChip::NativeRange253(native_253_range_check_config),
VmChip::LessThan(lessthan_config),
VmChip::BoolCheck(boolcheck_config),
VmChip::CondSelect(condselect_config),
VmChip::ZeroCond(zerocond_config),
];
VmConfig { primary, witness: advices[0], chips }
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter,
) -> std::result::Result<(), plonk::Error> {
trace!(target: "zk::vm", "Entering synthesize()");
// ===================
// VM Setup
//====================
// Our heap which holds every variable we reference and create.
let mut heap: Vec = vec![];
// Our heap which holds all the literal values we have in the circuit.
// For now, we only support u64.
let mut litheap: Vec = vec![];
// Offset for public inputs
let mut public_inputs_offset = 0;
// Offset for literals
let mut literals_offset = 0;
// Load the Sinsemilla generator lookup table used by the whole circuit.
if let Some(VmChip::Sinsemilla((sinsemilla_cfg1, _))) =
config.chips.iter().find(|&c| matches!(c, VmChip::Sinsemilla(_)))
{
trace!(target: "zk::vm", "Initializing Sinsemilla generator lookup table");
SinsemillaChip::load(sinsemilla_cfg1.clone(), &mut layouter)?;
}
// Construct the 64-bit NativeRangeCheck chip
let rangecheck64_chip = config.rangecheck64_chip();
if let Some(VmChip::NativeRange64(rangecheck64_config)) =
config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange64(_)))
{
trace!(target: "zk::vm", "Initializing k table for 64bit NativeRangeCheck");
NativeRangeCheckChip::<3, 64, 22>::load_k_table(
&mut layouter,
rangecheck64_config.k_values_table,
)?;
}
// Construct the 253-bit NativeRangeCheck and LessThan chips.
let rangecheck253_chip = config.rangecheck253_chip();
let lessthan_chip = config.lessthan_chip();
if let Some(VmChip::NativeRange253(rangecheck253_config)) =
config.chips.iter().find(|&c| matches!(c, VmChip::NativeRange253(_)))
{
trace!(target: "zk::vm", "Initializing k table for 253bit NativeRangeCheck");
NativeRangeCheckChip::<3, 253, 85>::load_k_table(
&mut layouter,
rangecheck253_config.k_values_table,
)?;
}
// Construct the ECC chip.
let ecc_chip = config.ecc_chip();
// Construct the Arithmetic chip.
let arith_chip = config.arithmetic_chip();
// Construct the boolean check chip.
let boolcheck_chip = config.boolcheck_chip();
// Construct the conditional selection chip
let condselect_chip = config.condselect_chip();
// Construct the zero_cond selection chip
let zerocond_chip = config.zerocond_chip();
// ==========================
// Constants setup
// ==========================
// This constant one is used for short multiplication
let one = assign_free_advice(
layouter.namespace(|| "Load constant one"),
config.witness,
Value::known(pallas::Base::ONE),
)?;
layouter.assign_region(
|| "constrain constant",
|mut region| region.constrain_constant(one.cell(), pallas::Base::ONE),
)?;
// ANCHOR: constant_init
// Lookup and push constants onto the heap
for constant in &self.constants {
trace!(
target: "zk::vm",
"Pushing constant `{}` to heap address {}",
constant.as_str(),
heap.len()
);
match constant.as_str() {
"VALUE_COMMIT_VALUE" => {
let vcv = ValueCommitV;
let vcv = FixedPointShort::from_inner(ecc_chip.as_ref().unwrap().clone(), vcv);
heap.push(HeapVar::EcFixedPointShort(vcv));
}
"VALUE_COMMIT_RANDOM" => {
let vcr = OrchardFixedBasesFull::ValueCommitR;
let vcr = FixedPoint::from_inner(ecc_chip.as_ref().unwrap().clone(), vcr);
heap.push(HeapVar::EcFixedPoint(vcr));
}
"NULLIFIER_K" => {
let nfk = NullifierK;
let nfk =
FixedPointBaseField::from_inner(ecc_chip.as_ref().unwrap().clone(), nfk);
heap.push(HeapVar::EcFixedPointBase(nfk));
}
_ => {
error!(target: "zk::vm", "Invalid constant name: {}", constant.as_str());
return Err(plonk::Error::Synthesis)
}
}
}
// ANCHOR_END: constant_init
// ANCHOR: literals_init
// Load the literals onto the literal heap
// N.B. Only uint64 is supported right now.
for literal in &self.literals {
match literal.0 {
LitType::Uint64 => match literal.1.parse::() {
Ok(v) => litheap.push(v),
Err(e) => {
error!(target: "zk::vm", "Failed converting u64 literal: {}", e);
return Err(plonk::Error::Synthesis)
}
},
_ => {
error!(target: "zk::vm", "Invalid literal: {:?}", literal);
return Err(plonk::Error::Synthesis)
}
}
}
// ANCHOR_END: literals_init
// ANCHOR: witness_init
// Push the witnesses onto the heap, and potentially, if the witness
// is in the Base field (like the entire circuit is), load it into a
// table cell.
for witness in &self.witnesses {
match witness {
Witness::EcPoint(w) => {
trace!(target: "zk::vm", "Witnessing EcPoint into circuit");
let point = Point::new(
ecc_chip.as_ref().unwrap().clone(),
layouter.namespace(|| "Witness EcPoint"),
w.as_ref().map(|cm| cm.to_affine()),
)?;
trace!(target: "zk::vm", "Pushing EcPoint to heap address {}", heap.len());
heap.push(HeapVar::EcPoint(point));
}
Witness::EcNiPoint(w) => {
trace!(target: "zk::vm", "Witnessing EcNiPoint into circuit");
let point = NonIdentityPoint::new(
ecc_chip.as_ref().unwrap().clone(),
layouter.namespace(|| "Witness EcNiPoint"),
w.as_ref().map(|cm| cm.to_affine()),
)?;
trace!(target: "zk::vm", "Pushing EcNiPoint to heap address {}", heap.len());
heap.push(HeapVar::EcNiPoint(point));
}
Witness::EcFixedPoint(_) => {
error!(target: "zk::vm", "Unable to witness EcFixedPoint, this is unimplemented.");
return Err(plonk::Error::Synthesis)
}
Witness::Base(w) => {
trace!(target: "zk::vm", "Witnessing Base into circuit");
let base = assign_free_advice(
layouter.namespace(|| "Witness Base"),
config.witness,
*w,
)?;
trace!(target: "zk::vm", "Pushing Base to heap address {}", heap.len());
heap.push(HeapVar::Base(base));
}
Witness::Scalar(w) => {
// NOTE: Because the type in `halo2_gadgets` does not have a `Clone`
// impl, we push scalars as-is to the heap. They get witnessed
// when they get used.
trace!(target: "zk::vm", "Pushing Scalar to heap address {}", heap.len());
heap.push(HeapVar::Scalar(*w));
}
Witness::MerklePath(w) => {
trace!(target: "zk::vm", "Witnessing MerklePath into circuit");
let path: Value<[pallas::Base; MERKLE_DEPTH_ORCHARD]> =
w.map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
trace!(target: "zk::vm", "Pushing MerklePath to heap address {}", heap.len());
heap.push(HeapVar::MerklePath(path));
}
Witness::Uint32(w) => {
trace!(target: "zk::vm", "Pushing Uint32 to heap address {}", heap.len());
heap.push(HeapVar::Uint32(*w));
}
Witness::Uint64(w) => {
trace!(target: "zk::vm", "Pushing Uint64 to heap address {}", heap.len());
heap.push(HeapVar::Uint64(*w));
}
}
}
// ANCHOR_END: witness_init
// =============================
// And now, work through opcodes
// =============================
self.tracer.clear();
// TODO: Copy constraints
// ANCHOR: opcode_begin
for opcode in &self.opcodes {
match opcode.0 {
Opcode::EcAdd => {
trace!(target: "zk::vm", "Executing `EcAdd{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: Point> =
heap[args[0].1].clone().try_into()?;
let rhs: Point> =
heap[args[1].1].clone().try_into()?;
let ret = lhs.add(layouter.namespace(|| "EcAdd()"), &rhs)?;
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_ecpoint(&ret);
heap.push(HeapVar::EcPoint(ret));
}
// ANCHOR_END: opcode_begin
Opcode::EcMul => {
trace!(target: "zk::vm", "Executing `EcMul{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: FixedPoint> =
heap[args[1].1].clone().try_into()?;
let rhs = ScalarFixed::new(
ecc_chip.as_ref().unwrap().clone(),
layouter.namespace(|| "EcMul: ScalarFixed::new()"),
heap[args[0].1].clone().try_into()?,
)?;
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMul()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_ecpoint(&ret);
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulVarBase => {
trace!(target: "zk::vm", "Executing `EcMulVarBase{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: NonIdentityPoint> =
heap[args[1].1].clone().try_into()?;
let rhs: AssignedCell = heap[args[0].1].clone().try_into()?;
let rhs = ScalarVar::from_base(
ecc_chip.as_ref().unwrap().clone(),
layouter.namespace(|| "EcMulVarBase::from_base()"),
&rhs,
)?;
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulVarBase()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_ecpoint(&ret);
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulBase => {
trace!(target: "zk::vm", "Executing `EcMulBase{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: FixedPointBaseField> =
heap[args[1].1].clone().try_into()?;
let rhs: AssignedCell = heap[args[0].1].clone().try_into()?;
let ret = lhs.mul(layouter.namespace(|| "EcMulBase()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_ecpoint(&ret);
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcMulShort => {
trace!(target: "zk::vm", "Executing `EcMulShort{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: FixedPointShort> =
heap[args[1].1].clone().try_into()?;
let rhs = ScalarFixedShort::new(
ecc_chip.as_ref().unwrap().clone(),
layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"),
(heap[args[0].1].clone().try_into()?, one.clone()),
)?;
let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?;
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_ecpoint(&ret);
heap.push(HeapVar::EcPoint(ret));
}
Opcode::EcGetX => {
trace!(target: "zk::vm", "Executing `EcGetX{:?}` opcode", opcode.1);
let args = &opcode.1;
let point: Point> =
heap[args[0].1].clone().try_into()?;
let ret = point.inner().x();
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_base(&ret);
heap.push(HeapVar::Base(ret));
}
Opcode::EcGetY => {
trace!(target: "zk::vm", "Executing `EcGetY{:?}` opcode", opcode.1);
let args = &opcode.1;
let point: Point> =
heap[args[0].1].clone().try_into()?;
let ret = point.inner().y();
trace!(target: "zk::vm", "Pushing result to heap address {}", heap.len());
self.tracer.push_base(&ret);
heap.push(HeapVar::Base(ret));
}
Opcode::PoseidonHash => {
trace!(target: "zk::vm", "Executing `PoseidonHash{:?}` opcode", opcode.1);
let args = &opcode.1;
let mut poseidon_message: Vec> =
Vec::with_capacity(args.len());
for idx in args {
poseidon_message.push(heap[idx.1].clone().try_into()?);
}
macro_rules! poseidon_hash {
($len:expr, $hasher:ident, $output:ident, $cell:ident) => {
let $hasher = PoseidonHash::<
_,
_,
poseidon::P128Pow5T3,
poseidon::ConstantLength<$len>,
3,
2,
>::init(
config.poseidon_chip().unwrap(),
layouter.namespace(|| "PoseidonHash init"),
)?;
let $output = $hasher.hash(
layouter.namespace(|| "PoseidonHash hash"),
poseidon_message.try_into().unwrap(),
)?;
let $cell: AssignedCell = $output.into();
trace!(target: "zk::vm", "Pushing hash to heap address {}", heap.len());
self.tracer.push_base(&$cell);
heap.push(HeapVar::Base($cell));
};
}
macro_rules! vla {
($args:ident, $a:ident, $b:ident, $c:ident, $($num:tt)*) => {
match $args.len() {
$($num => {
poseidon_hash!($num, $a, $b, $c);
})*
_ => {
error!(target: "zk::vm", "Unsupported poseidon hash for {} elements", $args.len());
return Err(plonk::Error::Synthesis)
}
}
};
}
vla!(args, a, b, c, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
}
Opcode::MerkleRoot => {
trace!(target: "zk::vm", "Executing `MerkleRoot{:?}` opcode", opcode.1);
let args = &opcode.1;
let leaf_pos = heap[args[0].1].clone().try_into()?;
let merkle_path = heap[args[1].1].clone().try_into()?;
let leaf = heap[args[2].1].clone().try_into()?;
let merkle_inputs = MerklePath::construct(
[config.merkle_chip_1().unwrap(), config.merkle_chip_2().unwrap()],
OrchardHashDomains::MerkleCrh,
leaf_pos,
merkle_path,
);
let root = merkle_inputs
.calculate_root(layouter.namespace(|| "MerkleRoot()"), leaf)?;
trace!(target: "zk::vm", "Pushing merkle root to heap address {}", heap.len());
self.tracer.push_base(&root);
heap.push(HeapVar::Base(root));
}
Opcode::BaseAdd => {
trace!(target: "zk::vm", "Executing `BaseAdd{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &heap[args[0].1].clone().try_into()?;
let rhs = &heap[args[1].1].clone().try_into()?;
let sum = arith_chip.as_ref().unwrap().add(
layouter.namespace(|| "BaseAdd()"),
lhs,
rhs,
)?;
trace!(target: "zk::vm", "Pushing sum to heap address {}", heap.len());
self.tracer.push_base(&sum);
heap.push(HeapVar::Base(sum));
}
Opcode::BaseMul => {
trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &heap[args[0].1].clone().try_into()?;
let rhs = &heap[args[1].1].clone().try_into()?;
let product = arith_chip.as_ref().unwrap().mul(
layouter.namespace(|| "BaseMul()"),
lhs,
rhs,
)?;
trace!(target: "zk::vm", "Pushing product to heap address {}", heap.len());
self.tracer.push_base(&product);
heap.push(HeapVar::Base(product));
}
Opcode::BaseSub => {
trace!(target: "zk::vm", "Executing `BaseSub{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs = &heap[args[0].1].clone().try_into()?;
let rhs = &heap[args[1].1].clone().try_into()?;
let difference = arith_chip.as_ref().unwrap().sub(
layouter.namespace(|| "BaseSub()"),
lhs,
rhs,
)?;
trace!(target: "zk::vm", "Pushing difference to heap address {}", heap.len());
self.tracer.push_base(&difference);
heap.push(HeapVar::Base(difference));
}
Opcode::WitnessBase => {
trace!(target: "zk::vm", "Executing `WitnessBase{:?}` opcode", opcode.1);
//let args = &opcode.1;
let lit = litheap[literals_offset];
literals_offset += 1;
let witness = assign_free_advice(
layouter.namespace(|| "Witness literal"),
config.witness,
Value::known(pallas::Base::from(lit)),
)?;
layouter.assign_region(
|| "constrain constant",
|mut region| {
region.constrain_constant(witness.cell(), pallas::Base::from(lit))
},
)?;
trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
self.tracer.push_base(&witness);
heap.push(HeapVar::Base(witness));
}
Opcode::RangeCheck => {
trace!(target: "zk::vm", "Executing `RangeCheck{:?}` opcode", opcode.1);
let args = &opcode.1;
let lit = litheap[literals_offset];
literals_offset += 1;
let arg = heap[args[1].1].clone();
match lit {
64 => {
rangecheck64_chip.as_ref().unwrap().copy_range_check(
layouter.namespace(|| "copy range check 64"),
arg.try_into()?,
)?;
}
253 => {
rangecheck253_chip.as_ref().unwrap().copy_range_check(
layouter.namespace(|| "copy range check 253"),
arg.try_into()?,
)?;
}
x => {
error!(target: "zk::vm", "Unsupported bit-range {} for range_check", x);
return Err(plonk::Error::Synthesis)
}
}
self.tracer.push_void();
}
Opcode::LessThanStrict => {
trace!(target: "zk::vm", "Executing `LessThanStrict{:?}` opcode", opcode.1);
let args = &opcode.1;
let a = heap[args[0].1].clone().try_into()?;
let b = heap[args[1].1].clone().try_into()?;
lessthan_chip.as_ref().unwrap().copy_less_than(
layouter.namespace(|| "copy a {
trace!(target: "zk::vm", "Executing `LessThanLoose{:?}` opcode", opcode.1);
let args = &opcode.1;
let a = heap[args[0].1].clone().try_into()?;
let b = heap[args[1].1].clone().try_into()?;
lessthan_chip.as_ref().unwrap().copy_less_than(
layouter.namespace(|| "copy a {
trace!(target: "zk::vm", "Executing `BoolCheck{:?}` opcode", opcode.1);
let args = &opcode.1;
let w = heap[args[0].1].clone().try_into()?;
boolcheck_chip
.as_ref()
.unwrap()
.small_range_check(layouter.namespace(|| "copy boolean check"), w)?;
self.tracer.push_void();
}
Opcode::CondSelect => {
trace!(target: "zk::vm", "Executing `CondSelect{:?}` opcode", opcode.1);
let args = &opcode.1;
let cond: AssignedCell = heap[args[0].1].clone().try_into()?;
let lhs: AssignedCell = heap[args[1].1].clone().try_into()?;
let rhs: AssignedCell = heap[args[2].1].clone().try_into()?;
let out: AssignedCell =
condselect_chip.as_ref().unwrap().conditional_select(
&mut layouter.namespace(|| "cond_select"),
lhs,
rhs,
cond,
)?;
trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
self.tracer.push_base(&out);
heap.push(HeapVar::Base(out));
}
Opcode::ZeroCondSelect => {
trace!(target: "zk::vm", "Executing `ZeroCondSelect{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: AssignedCell = heap[args[0].1].clone().try_into()?;
let rhs: AssignedCell = heap[args[1].1].clone().try_into()?;
let out: AssignedCell = zerocond_chip.as_ref().unwrap().assign(
layouter.namespace(|| "zero_cond"),
lhs,
rhs,
)?;
trace!(target: "zk::vm", "Pushing assignment to heap address {}", heap.len());
self.tracer.push_base(&out);
heap.push(HeapVar::Base(out));
}
Opcode::ConstrainEqualBase => {
trace!(target: "zk::vm", "Executing `ConstrainEqualBase{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: AssignedCell = heap[args[0].1].clone().try_into()?;
let rhs: AssignedCell = heap[args[1].1].clone().try_into()?;
layouter.assign_region(
|| "constrain witnessed base equality",
|mut region| region.constrain_equal(lhs.cell(), rhs.cell()),
)?;
self.tracer.push_void();
}
Opcode::ConstrainEqualPoint => {
trace!(target: "zk::vm", "Executing `ConstrainEqualPoint{:?}` opcode", opcode.1);
let args = &opcode.1;
let lhs: Point> =
heap[args[0].1].clone().try_into()?;
let rhs: Point> =
heap[args[1].1].clone().try_into()?;
lhs.constrain_equal(
layouter.namespace(|| "constrain ec point equality"),
&rhs,
)?;
self.tracer.push_void();
}
Opcode::ConstrainInstance => {
trace!(target: "zk::vm", "Executing `ConstrainInstance{:?}` opcode", opcode.1);
let args = &opcode.1;
let var: AssignedCell = heap[args[0].1].clone().try_into()?;
layouter.constrain_instance(
var.cell(),
config.primary,
public_inputs_offset,
)?;
public_inputs_offset += 1;
self.tracer.push_void();
}
Opcode::DebugPrint => {
trace!(target: "zk::vm", "Executing `DebugPrint{:?}` opcode", opcode.1);
let args = &opcode.1;
println!("[ZKVM DEBUG] HEAP INDEX: {}", args[0].1);
println!("[ZKVM DEBUG] {:#?}", heap[args[0].1]);
self.tracer.push_void();
}
Opcode::Noop => {
error!(target: "zk::vm", "Unsupported opcode");
return Err(plonk::Error::Synthesis)
}
}
}
self.tracer.assert_correct(self.opcodes.len());
trace!(target: "zk::vm", "Exiting synthesize() successfully");
Ok(())
}
}