/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2022 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 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, Point, ScalarFixed, ScalarFixedShort, }, 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::{ circuit::{floor_planner, AssignedCell, Layouter, Value}, pasta::{group::Curve, pallas, Fp}, plonk, plonk::{Advice, Circuit, Column, ConstraintSystem, Instance as InstanceColumn}, }; use log::{debug, error}; pub use super::vm_stack::{StackVar, Witness}; use super::{ assign_free_advice, gadget::{ arithmetic::{ArithChip, ArithConfig, ArithInstruction}, less_than::{LessThanChip, LessThanConfig}, native_range_check::{NativeRangeCheckChip, NativeRangeCheckConfig}, small_range_check::{SmallRangeCheckChip, SmallRangeCheckConfig}, }, }; use crate::zkas::{ types::{LitType, StackType}, Opcode, ZkBinary, }; #[derive(Clone)] pub struct VmConfig { primary: Column, advices: [Column; 10], ecc_config: EccConfig, merkle_cfg1: MerkleConfig, merkle_cfg2: MerkleConfig, sinsemilla_cfg1: SinsemillaConfig, _sinsemilla_cfg2: SinsemillaConfig, poseidon_config: PoseidonConfig, arith_config: ArithConfig, native_64_range_check_config: NativeRangeCheckConfig<3, 64, 22>, native_253_range_check_config: NativeRangeCheckConfig<3, 253, 85>, lessthan_config: LessThanConfig<3, 253, 85>, boolcheck_config: SmallRangeCheckConfig, } impl VmConfig { fn ecc_chip(&self) -> EccChip { EccChip::construct(self.ecc_config.clone()) } fn merkle_chip_1( &self, ) -> MerkleChip { MerkleChip::construct(self.merkle_cfg1.clone()) } fn merkle_chip_2( &self, ) -> MerkleChip { MerkleChip::construct(self.merkle_cfg2.clone()) } fn poseidon_chip(&self) -> PoseidonChip { PoseidonChip::construct(self.poseidon_config.clone()) } fn arithmetic_chip(&self) -> ArithChip { ArithChip::construct(self.arith_config.clone()) } } pub struct ZkCircuit { constants: Vec, witnesses: Vec, literals: Vec<(LitType, String)>, opcodes: Vec<(Opcode, Vec<(StackType, usize)>)>, } impl ZkCircuit { pub fn new(witnesses: Vec, circuit_code: ZkBinary) -> Self { let constants = circuit_code.constants.iter().map(|x| x.1.clone()).collect(); #[allow(clippy::map_clone)] let literals = circuit_code.literals.iter().map(|x| x.clone()).collect(); Self { constants, witnesses, literals, opcodes: circuit_code.opcodes } } } impl Circuit for ZkCircuit { type Config = VmConfig; type FloorPlanner = floor_planner::V1; fn without_witnesses(&self) -> Self { Self { constants: self.constants.clone(), witnesses: self.witnesses.clone(), literals: self.literals.clone(), opcodes: self.opcodes.clone(), } } fn configure(meta: &mut ConstraintSystem) -> Self::Config { // Advice columns used in the circuit let advices = [ meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), ]; // 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()); // 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); } // 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, 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); VmConfig { primary, advices, ecc_config, merkle_cfg1, merkle_cfg2, sinsemilla_cfg1, _sinsemilla_cfg2, poseidon_config, arith_config, native_64_range_check_config, native_253_range_check_config, lessthan_config, boolcheck_config, } } fn synthesize( &self, config: Self::Config, mut layouter: impl Layouter, ) -> std::result::Result<(), plonk::Error> { debug!("Entering synthesize()"); // =================== // VM Setup //==================== // Our stack which holds every variable we reference and create. let mut stack: Vec = vec![]; // Our stack which holds all the literal values we have in the circuit. // For now, we only support u64. let mut litstack: 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. SinsemillaChip::load(config.sinsemilla_cfg1.clone(), &mut layouter)?; // Construct the 64-bit NativeRangeCheck and LessThan chips let rangecheck64_chip = NativeRangeCheckChip::<3, 64, 22>::construct( config.native_64_range_check_config.clone(), ); NativeRangeCheckChip::<3, 64, 22>::load_k_table( &mut layouter, config.native_64_range_check_config.k_values_table, )?; // Construct the 253-bit NativeRangeCheck and LessThan chips. let rangecheck253_chip = NativeRangeCheckChip::<3, 253, 85>::construct( config.native_253_range_check_config.clone(), ); let lessthan_chip = LessThanChip::<3, 253, 85>::construct(config.lessthan_config.clone()); NativeRangeCheckChip::<3, 253, 85>::load_k_table( &mut layouter, config.native_253_range_check_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 = SmallRangeCheckChip::construct(config.boolcheck_config.clone()); // ========================== // Constants setup // ========================== // This constant one is used for short multiplication let one = assign_free_advice( layouter.namespace(|| "Load constant one"), config.advices[0], Value::known(pallas::Base::one()), )?; // Lookup and push constants onto the stack for constant in &self.constants { debug!("Pushing constant `{}` to stack index {}", constant.as_str(), stack.len()); match constant.as_str() { "VALUE_COMMIT_VALUE" => { let vcv = ValueCommitV; let vcv = FixedPointShort::from_inner(ecc_chip.clone(), vcv); stack.push(StackVar::EcFixedPointShort(vcv)); } "VALUE_COMMIT_RANDOM" => { let vcr = OrchardFixedBasesFull::ValueCommitR; let vcr = FixedPoint::from_inner(ecc_chip.clone(), vcr); stack.push(StackVar::EcFixedPoint(vcr)); } "NULLIFIER_K" => { let nfk = NullifierK; let nfk = FixedPointBaseField::from_inner(ecc_chip.clone(), nfk); stack.push(StackVar::EcFixedPointBase(nfk)); } _ => { error!("Invalid constant name: {}", constant.as_str()); return Err(plonk::Error::Synthesis) } } } // Load the literals onto the literal stack. // N.B. Only uint64 is supported right now. for literal in &self.literals { match literal.0 { LitType::Uint64 => match literal.1.parse::() { Ok(v) => litstack.push(v), Err(e) => { error!("Failed converting u64 literal: {}", e); return Err(plonk::Error::Synthesis) } }, _ => { error!("Invalid literal: {:?}", literal); return Err(plonk::Error::Synthesis) } } } // Push the witnesses onto the stack, 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) => { debug!("Witnessing EcPoint into circuit"); let point = Point::new( ecc_chip.clone(), layouter.namespace(|| "Witness EcPoint"), w.as_ref().map(|cm| cm.to_affine()), )?; debug!("Pushing EcPoint to stack index {}", stack.len()); stack.push(StackVar::EcPoint(point)); } Witness::EcFixedPoint(_) => { error!("Unable to witness EcFixedPoint, this is unimplemented."); return Err(plonk::Error::Synthesis) } Witness::Base(w) => { debug!("Witnessing Base into circuit"); let base = assign_free_advice( layouter.namespace(|| "Witness Base"), config.advices[0], *w, )?; debug!("Pushing Base to stack index {}", stack.len()); stack.push(StackVar::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 stack. They get witnessed // when they get used. debug!("Pushing Scalar to stack index {}", stack.len()); stack.push(StackVar::Scalar(*w)); } Witness::MerklePath(w) => { debug!("Witnessing MerklePath into circuit"); let path: Value<[pallas::Base; MERKLE_DEPTH_ORCHARD]> = w.map(|typed_path| gen_const_array(|i| typed_path[i].inner())); debug!("Pushing MerklePath to stack index {}", stack.len()); stack.push(StackVar::MerklePath(path)); } Witness::Uint32(w) => { debug!("Pushing Uint32 to stack index {}", stack.len()); stack.push(StackVar::Uint32(*w)); } Witness::Uint64(w) => { debug!("Pushing Uint64 to stack index {}", stack.len()); stack.push(StackVar::Uint64(*w)); } } } // ============================= // And now, work through opcodes // ============================= // TODO: Copy constraints for opcode in &self.opcodes { match opcode.0 { Opcode::EcAdd => { debug!("Executing `EcAdd{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: Point> = stack[args[0].1].clone().into(); let rhs: Point> = stack[args[1].1].clone().into(); let ret = lhs.add(layouter.namespace(|| "EcAdd()"), &rhs)?; debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::EcPoint(ret)); } Opcode::EcMul => { debug!("Executing `EcMul{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: FixedPoint> = stack[args[1].1].clone().into(); let rhs = ScalarFixed::new( ecc_chip.clone(), layouter.namespace(|| "EcMul: ScalarFixed::new()"), stack[args[0].1].clone().into(), )?; let (ret, _) = lhs.mul(layouter.namespace(|| "EcMul()"), rhs)?; debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::EcPoint(ret)); } Opcode::EcMulBase => { debug!("Executing `EcMulBase{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: FixedPointBaseField> = stack[args[1].1].clone().into(); let rhs: AssignedCell = stack[args[0].1].clone().into(); let ret = lhs.mul(layouter.namespace(|| "EcMulBase()"), rhs)?; debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::EcPoint(ret)); } Opcode::EcMulShort => { debug!("Executing `EcMulShort{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: FixedPointShort> = stack[args[1].1].clone().into(); let rhs = ScalarFixedShort::new( ecc_chip.clone(), layouter.namespace(|| "EcMulShort: ScalarFixedShort::new()"), (stack[args[0].1].clone().into(), one.clone()), )?; let (ret, _) = lhs.mul(layouter.namespace(|| "EcMulShort()"), rhs)?; debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::EcPoint(ret)); } Opcode::EcGetX => { debug!("Executing `EcGetX{:?}` opcode", opcode.1); let args = &opcode.1; let point: Point> = stack[args[0].1].clone().into(); let ret = point.inner().x(); debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::Base(ret)); } Opcode::EcGetY => { debug!("Executing `EcGetY{:?}` opcode", opcode.1); let args = &opcode.1; let point: Point> = stack[args[0].1].clone().into(); let ret = point.inner().y(); debug!("Pushing result to stack index {}", stack.len()); stack.push(StackVar::Base(ret)); } Opcode::PoseidonHash => { debug!("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(stack[idx.1].clone().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(), layouter.namespace(|| "PoseidonHash init"), )?; let $output = $hasher.hash( layouter.namespace(|| "PoseidonHash hash"), poseidon_message.try_into().unwrap(), )?; let $cell: AssignedCell = $output.into(); debug!("Pushing hash to stack index {}", stack.len()); stack.push(StackVar::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!("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 => { debug!("Executing `MerkleRoot{:?}` opcode", opcode.1); let args = &opcode.1; let leaf_pos = stack[args[0].1].clone().into(); let merkle_path = stack[args[1].1].clone().into(); let leaf = stack[args[2].1].clone().into(); let merkle_inputs = MerklePath::construct( [config.merkle_chip_1(), config.merkle_chip_2()], OrchardHashDomains::MerkleCrh, leaf_pos, merkle_path, ); let root = merkle_inputs .calculate_root(layouter.namespace(|| "MerkleRoot()"), leaf)?; debug!("Pushing merkle root to stack index {}", stack.len()); stack.push(StackVar::Base(root)); } Opcode::BaseAdd => { debug!("Executing `BaseAdd{:?}` opcode", opcode.1); let args = &opcode.1; let lhs = &stack[args[0].1].clone().into(); let rhs = &stack[args[1].1].clone().into(); let sum = arith_chip.add(layouter.namespace(|| "BaseAdd()"), lhs, rhs)?; debug!("Pushing sum to stack index {}", stack.len()); stack.push(StackVar::Base(sum)); } Opcode::BaseMul => { debug!("Executing `BaseSub{:?}` opcode", opcode.1); let args = &opcode.1; let lhs = &stack[args[0].1].clone().into(); let rhs = &stack[args[1].1].clone().into(); let product = arith_chip.mul(layouter.namespace(|| "BaseMul()"), lhs, rhs)?; debug!("Pushing product to stack index {}", stack.len()); stack.push(StackVar::Base(product)); } Opcode::BaseSub => { debug!("Executing `BaseSub{:?}` opcode", opcode.1); let args = &opcode.1; let lhs = &stack[args[0].1].clone().into(); let rhs = &stack[args[1].1].clone().into(); let difference = arith_chip.sub(layouter.namespace(|| "BaseSub()"), lhs, rhs)?; debug!("Pushing difference to stack index {}", stack.len()); stack.push(StackVar::Base(difference)); } Opcode::WitnessBase => { debug!("Executing `WitnessBase{:?}` opcode", opcode.1); //let args = &opcode.1; let lit = litstack[literals_offset]; literals_offset += 1; let witness = assign_free_advice( layouter.namespace(|| "Witness literal"), config.advices[0], Value::known(pallas::Base::from(lit)), )?; debug!("Pushing assignment to stack index {}", stack.len()); stack.push(StackVar::Base(witness)); } Opcode::RangeCheck => { debug!("Executing `RangeCheck{:?}` opcode", opcode.1); let args = &opcode.1; let lit = litstack[literals_offset]; literals_offset += 1; let arg = stack[args[1].1].clone(); match lit { 64 => { rangecheck64_chip.copy_range_check( layouter.namespace(|| "copy range check 64"), arg.into(), true, )?; } 253 => { rangecheck253_chip.copy_range_check( layouter.namespace(|| "copy range check 253"), arg.into(), true, )?; } x => { error!("Unsupported bit-range {} for range_check", x); return Err(plonk::Error::Synthesis) } } } Opcode::LessThan => { debug!("Executing `LessThan{:?}` opcode", opcode.1); let args = &opcode.1; let a = stack[args[0].1].clone().into(); let b = stack[args[1].1].clone().into(); lessthan_chip.copy_less_than( layouter.namespace(|| "copy a { debug!("Executing `BoolCheck{:?}` opcode", opcode.1); let args = &opcode.1; let w = stack[args[0].1].clone().into(); boolcheck_chip .small_range_check(layouter.namespace(|| "copy boolean check"), w)?; } Opcode::ConstrainEqualBase => { debug!("Executing `ConstrainEqualBase{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: AssignedCell = stack[args[0].1].clone().into(); let rhs: AssignedCell = stack[args[1].1].clone().into(); layouter.assign_region( || "constrain witnessed base equality", |mut region| region.constrain_equal(lhs.cell(), rhs.cell()), )?; } Opcode::ConstrainEqualPoint => { debug!("Executing `ConstrainEqualPoint{:?}` opcode", opcode.1); let args = &opcode.1; let lhs: Point> = stack[args[0].1].clone().into(); let rhs: Point> = stack[args[1].1].clone().into(); lhs.constrain_equal( layouter.namespace(|| "constrain ec point equality"), &rhs, )?; } Opcode::ConstrainInstance => { debug!("Executing `ConstrainInstance{:?}` opcode", opcode.1); let args = &opcode.1; let var: AssignedCell = stack[args[0].1].clone().into(); layouter.constrain_instance( var.cell(), config.primary, public_inputs_offset, )?; public_inputs_offset += 1; } _ => { error!("Unsupported opcode"); return Err(plonk::Error::Synthesis) } } } debug!("Exiting synthesize() successfully"); Ok(()) } }