proof/dao: More WIP, use the VM instead of custom circuit.

This commit is contained in:
parazyd
2022-02-15 11:33:20 +01:00
parent 22fdec7fed
commit cc308da5e0
2 changed files with 156 additions and 489 deletions

View File

@@ -1,35 +1,15 @@
use bitvec::prelude::*;
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointShort, Point,
},
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
primitives::{
poseidon,
poseidon::{ConstantLength, P128Pow5T3},
},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
merkle::{
chip::{MerkleChip, MerkleConfig},
MerklePath,
},
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use halo2_proofs::{
arithmetic::Field,
circuit::{AssignedCell, Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk,
plonk::{Advice, Circuit, Column, ConstraintSystem, Instance as InstanceColumn},
use halo2_gadgets::primitives::{
poseidon,
poseidon::{ConstantLength, P128Pow5T3},
};
use halo2_proofs::dev::MockProver;
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
use log::debug;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
group::{ff::PrimeField, Curve, Group},
arithmetic::CurveAffine,
group::{
ff::{Field, PrimeField},
Curve, Group,
},
pallas,
};
use rand::rngs::OsRng;
@@ -37,371 +17,16 @@ use simplelog::{ColorChoice::Auto, Config, LevelFilter, TermLogger, TerminalMode
use darkfi::{
crypto::{
constants::{
sinsemilla::{OrchardCommitDomains, OrchardHashDomains},
util::gen_const_array,
OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV, MERKLE_DEPTH_ORCHARD,
},
keypair::Keypair,
merkle_node::MerkleNode,
schnorr::SchnorrSecret,
util::{mod_r_p, pedersen_commitment_scalar},
},
zk::vm::{Witness, ZkCircuit},
zkas::decoder::ZkBinary,
Result,
};
#[derive(Clone)]
pub struct VmConfig {
primary: Column<InstanceColumn>,
advices: [Column<Advice>; 10],
ecc_config: EccConfig<OrchardFixedBases>,
merkle_cfg1: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
merkle_cfg2: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
sinsemilla_cfg1: SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
_sinsemilla_cfg2: SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
}
impl VmConfig {
fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
EccChip::construct(self.ecc_config.clone())
}
fn merkle_chip_1(
&self,
) -> MerkleChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
MerkleChip::construct(self.merkle_cfg1.clone())
}
fn merkle_chip_2(
&self,
) -> MerkleChip<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases> {
MerkleChip::construct(self.merkle_cfg2.clone())
}
fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
PoseidonChip::construct(self.poseidon_config.clone())
}
}
#[derive(Clone, Default)]
pub struct ZkCircuit {
a: Option<pallas::Base>, // contract address
s: Option<pallas::Base>, // serial number
t: Option<pallas::Base>, // treasury balance
b_b: Option<pallas::Base>, // bulla blinding
leaf_pos: Option<u32>,
merkle_path: Option<[MerkleNode; 32]>,
u: Option<pallas::Base>, // output 0 value
p_x: Option<pallas::Base>, // output0 pub_x
p_y: Option<pallas::Base>, // output0 pub_y
b_m: Option<pallas::Base>, // output0 blind
votes: Option<pallas::Base>,
vote_blinds: Option<pallas::Scalar>,
output_1_blind: Option<pallas::Scalar>,
}
impl UtilitiesInstructions<pallas::Base> for ZkCircuit {
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for ZkCircuit {
type Config = VmConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> 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::<OrchardFixedBases>::configure(
meta,
advices,
lagrange_coeffs,
range_check.clone(),
);
// Configuration for the Poseidon hash
let poseidon_config = PoseidonChip::configure::<P128Pow5T3>(
meta,
advices[6..9].try_into().unwrap(),
advices[5],
rc_a,
rc_b,
);
// 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.clone(),
);
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)
};
VmConfig {
primary,
advices,
ecc_config,
merkle_cfg1,
merkle_cfg2,
sinsemilla_cfg1,
_sinsemilla_cfg2,
poseidon_config,
}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> std::result::Result<(), plonk::Error> {
debug!("Entering synthesize()");
// Load the Sinsemilla generator lookup table used by the whole circuit.
SinsemillaChip::load(config.sinsemilla_cfg1.clone(), &mut layouter)?;
// Construct the ECC chip.
let ecc_chip = config.ecc_chip();
// This constant one is used for short multiplication
let one = self.load_private(
layouter.namespace(|| "Load constant one"),
config.advices[0],
Some(pallas::Base::one()),
)?;
let contract_address = self.load_private(
layouter.namespace(|| "Load contract address"),
config.advices[0],
self.a,
)?;
let serial_number = self.load_private(
layouter.namespace(|| "Load serial number"),
config.advices[0],
self.s,
)?;
let treasury_balance = self.load_private(
layouter.namespace(|| "Load treasury balance"),
config.advices[0],
self.t,
)?;
let bulla_blind = self.load_private(
layouter.namespace(|| "Load bulla blind"),
config.advices[0],
self.b_b,
)?;
let output0_value = self.load_private(
layouter.namespace(|| "Load output0 value"),
config.advices[0],
self.u,
)?;
let output0_pub_x = self.load_private(
layouter.namespace(|| "Load output0 dest pub x"),
config.advices[0],
self.p_x,
)?;
let output0_pub_y = self.load_private(
layouter.namespace(|| "Load output0 dest pub y"),
config.advices[0],
self.p_y,
)?;
let output0_blind = self.load_private(
layouter.namespace(|| "Load output0 blind"),
config.advices[0],
self.b_m,
)?;
let votes = self.load_private(
layouter.namespace(|| "Load votes summed"),
config.advices[0],
self.votes,
)?;
// Constrain the serial number
println!("Serial in circuit: {:?}", serial_number.value());
layouter.constrain_instance(serial_number.cell(), config.primary, 0)?;
// Hash the treasury bulla
let mut poseidon_message: Vec<AssignedCell<pallas::Base, pallas::Base>> =
Vec::with_capacity(4);
poseidon_message.push(contract_address);
poseidon_message.push(serial_number);
poseidon_message.push(treasury_balance);
poseidon_message.push(bulla_blind);
let hasher = PoseidonHash::<_, _, P128Pow5T3, ConstantLength<4>, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "PoseidonHash init"),
)?;
let output = hasher.hash(
layouter.namespace(|| "PoseidonHash hash"),
poseidon_message.try_into().unwrap(),
)?;
let dao_bulla: AssignedCell<pallas::Base, pallas::Base> = output.into();
// Constrain the merkle root
let path: Option<[pallas::Base; MERKLE_DEPTH_ORCHARD]> =
self.merkle_path.map(|typed_path| gen_const_array(|i| typed_path[i].inner()));
let merkle_inputs = MerklePath::construct(
config.merkle_chip_1(),
config.merkle_chip_2(),
OrchardHashDomains::MerkleCrh,
self.leaf_pos,
path,
);
let root = merkle_inputs
.calculate_root(layouter.namespace(|| "Calculate merkle root"), dao_bulla)?;
println!("Merkle root in circuit: {:?}", root.value());
layouter.constrain_instance(root.cell(), config.primary, 1)?;
// Hash output 0
let mut poseidon_message: Vec<AssignedCell<pallas::Base, pallas::Base>> =
Vec::with_capacity(4);
poseidon_message.push(output0_value);
poseidon_message.push(output0_pub_x);
poseidon_message.push(output0_pub_y);
poseidon_message.push(output0_blind);
let hasher = PoseidonHash::<_, _, P128Pow5T3, ConstantLength<4>, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "PoseidonHash init"),
)?;
let output = hasher.hash(
layouter.namespace(|| "PoseidonHash hash"),
poseidon_message.try_into().unwrap(),
)?;
let output0: AssignedCell<pallas::Base, pallas::Base> = output.into();
println!("Output0 in circuit: {:?}", output0.value());
// Constrain output 0
layouter.constrain_instance(output0.cell(), config.primary, 2)?;
// Commit to votes with votes_blind
let (commitment, _) = {
let value_commit_v = ValueCommitV;
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v);
value_commit_v.mul(layouter.namespace(|| "[value] ValueCommitV"), (votes, one))?
};
let (blind, _) = {
let rcv = self.vote_blinds;
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
let value_commit_r = FixedPoint::from_inner(ecc_chip.clone(), value_commit_r);
value_commit_r.mul(layouter.namespace(|| "[value_blind] ValueCommitR"), rcv)?
};
// Constrain votes_commit_x and votes_commit_y
let votes_commit = commitment.add(layouter.namespace(|| "valuecommit"), &blind)?;
println!("VoteComX in circuit: {:?}", votes_commit.inner().x().value());
println!("VoteComY in circuit: {:?}", votes_commit.inner().y().value());
layouter.constrain_instance(votes_commit.inner().x().cell(), config.primary, 3)?;
layouter.constrain_instance(votes_commit.inner().y().cell(), config.primary, 4)?;
// TODO: Enforce votes > 0
// TODO: Output 1 (change) = treasury_balance - output0_value
// Commit to output 1 value
// Constrain output1_commit_x and output1_commit_y
debug!("Exiting synthesize()");
Ok(())
}
}
fn main() -> Result<()> {
let loglevel = match option_env!("RUST_LOG") {
Some("debug") => LevelFilter::Debug,
@@ -410,106 +35,126 @@ fn main() -> Result<()> {
};
TermLogger::init(loglevel, Config::default(), Mixed, Auto)?;
/*
let bincode = include_bytes!("../proof/dao.zk.bin");
let bincode = include_bytes!("dao.zk.bin");
let zkbin = ZkBinary::decode(bincode)?;
*/
// Contract address
let a = pallas::Base::random(&mut OsRng);
// Serial number
let s = pallas::Base::random(&mut OsRng);
// Money in treasury
let t = pallas::Base::from(666);
// Bulla blind
let b_b = pallas::Base::random(&mut OsRng);
// =============
// Initial state
// =============
let authority = Keypair::random(&mut OsRng);
let message = [a, s, t, b_b];
let spend_contract = pallas::Base::random(&mut OsRng);
let cur_balance = pallas::Base::from(666);
let old_serial = pallas::Base::random(&mut OsRng);
let old_bulla_blind = pallas::Base::random(&mut OsRng);
let message = [spend_contract, cur_balance, old_serial, old_bulla_blind];
let hasher = poseidon::Hash::<_, P128Pow5T3, ConstantLength<4>, 3, 2>::init();
let bulla = hasher.hash(message);
let our_dao = hasher.hash(message);
// Merkle tree of DAOs
let mut tree = BridgeTree::<MerkleNode, 32>::new(100);
let dao0 = pallas::Base::random(&mut OsRng);
let dao2 = pallas::Base::random(&mut OsRng);
tree.append(&MerkleNode(dao0));
tree.witness();
tree.append(&MerkleNode(bulla));
tree.append(&MerkleNode(our_dao));
tree.witness();
tree.append(&MerkleNode(dao2));
tree.witness();
let (leaf_pos, merkle_path) = tree.authentication_path(&MerkleNode(bulla)).unwrap();
// ========
// Proposal
// ========
let amount_to_send = pallas::Base::from(42);
let proposal_destination = pallas::Point::random(&mut OsRng);
let proposal_coords = proposal_destination.to_affine().coordinates().unwrap();
let proposal_blind = pallas::Base::random(&mut OsRng);
let message = [amount_to_send, *proposal_coords.x(), *proposal_coords.y(), proposal_blind];
let hasher = poseidon::Hash::<_, P128Pow5T3, ConstantLength<4>, 3, 2>::init();
let proposal = hasher.hash(message);
// Sign the proposal by the authority
let _signature = authority.secret.sign(&proposal.to_repr());
// ==============
// Voting process
// ==============
// The voting process happens now, and when finished, the votes are revealed.
// Votes are weighted by balance.
let vote0 = pallas::Base::from(44);
let vote0_blind = pallas::Scalar::random(&mut OsRng);
let vote1 = pallas::Base::from(13);
let vote1_blind = pallas::Scalar::random(&mut OsRng);
let vote2 = -pallas::Base::from(49); // This is a NO vote
let vote2_blind = pallas::Scalar::random(&mut OsRng);
let votes = vote0 + vote1 + vote2;
let vote_blinds = vote0_blind + vote1_blind + vote2_blind;
if votes < pallas::Base::from(1) {
// The voting process result is negative, so we don't do anything.
return Ok(())
}
// ==================
// Proof construction
// ==================
let (leaf_pos, merkle_path) = tree.authentication_path(&MerkleNode(our_dao)).unwrap();
let leaf_pos: u64 = leaf_pos.into();
let leaf_pos = leaf_pos as u32;
// Output 0:
let output0_val = pallas::Base::from(42);
let output0_dest = pallas::Point::random(&mut OsRng);
let output0_coords = output0_dest.to_affine().coordinates().unwrap();
let output0_blind = pallas::Base::random(&mut OsRng);
let new_serial = pallas::Base::random(&mut OsRng);
let new_bulla_blind = pallas::Base::random(&mut OsRng);
let message = [output0_val, *output0_coords.x(), *output0_coords.y(), output0_blind];
let new_balance = cur_balance - amount_to_send;
let message = [spend_contract, new_balance, new_serial, new_bulla_blind];
let hasher = poseidon::Hash::<_, P128Pow5T3, ConstantLength<4>, 3, 2>::init();
let output0 = hasher.hash(message);
let new_bulla = hasher.hash(message);
let authority = Keypair::random(&mut OsRng);
let _signature = authority.secret.sign(&output0.to_repr());
let merkle_root = tree.root();
let vote_1 = pallas::Base::from(44);
let vote_2 = pallas::Base::from(13);
// This is a NO vote
let vote_3 = -pallas::Base::from(49);
let value_blind = pallas::Scalar::random(&mut OsRng);
let value_commit = pedersen_commitment_scalar(mod_r_p(amount_to_send), value_blind);
let value_coords = value_commit.to_affine().coordinates().unwrap();
let vote_1_blind = pallas::Scalar::random(&mut OsRng);
let vote_1_commit = pedersen_commitment_scalar(mod_r_p(vote_1), vote_1_blind);
let public_inputs = vec![
spend_contract,
old_serial,
merkle_root.0,
proposal,
*value_coords.x(),
*value_coords.y(),
new_bulla,
];
let vote_2_blind = pallas::Scalar::random(&mut OsRng);
let vote_2_commit = pedersen_commitment_scalar(mod_r_p(vote_2), vote_2_blind);
let prover_witnesses = vec![
Witness::Base(Some(spend_contract)),
Witness::Base(Some(cur_balance)),
Witness::Base(Some(old_serial)),
Witness::Base(Some(old_bulla_blind)),
Witness::Uint32(Some(leaf_pos)),
Witness::MerklePath(Some(merkle_path.try_into().unwrap())),
Witness::Base(Some(amount_to_send)),
Witness::Base(Some(*proposal_coords.x())),
Witness::Base(Some(*proposal_coords.y())),
Witness::Base(Some(proposal_blind)),
Witness::Scalar(Some(value_blind)),
Witness::Base(Some(new_serial)),
Witness::Base(Some(new_bulla_blind)),
];
let vote_3_blind = pallas::Scalar::random(&mut OsRng);
let vote_3_commit = pedersen_commitment_scalar(mod_r_p(vote_3), vote_3_blind);
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
let vote_commit = vote_1_commit + vote_2_commit; //+ vote_3_commit;
let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
let votes = vote_1 + vote_2; //+vote_3;
let vote_blinds = vote_1_blind + vote_2_blind; //+ vote_3_blind;
let output_1_blind = pallas::Scalar::random(&mut OsRng);
/*
let number = pallas::Base::from(u64::MAX).to_bytes();
let bits = number.view_bits::<Lsb0>();
println!("Positive: {:?}", bits);
//let number = (-pallas::Base::from(u64::MAX)).to_bytes();
let number = pallas::Base::from(0).to_bytes();
let bits = number.view_bits::<Lsb0>();
println!("Negative: {:?}", bits);
*/
let circuit = ZkCircuit {
a: Some(a),
s: Some(s),
t: Some(t),
b_b: Some(b_b),
leaf_pos: Some(leaf_pos),
merkle_path: Some(merkle_path.try_into().unwrap()),
u: Some(output0_val),
p_x: Some(*output0_coords.x()),
p_y: Some(*output0_coords.y()),
b_m: Some(output0_blind),
votes: Some(votes),
vote_blinds: Some(vote_blinds),
output_1_blind: Some(output_1_blind),
};
let public_inputs =
vec![s, tree.root().inner(), output0, *vote_commit_coords.x(), *vote_commit_coords.y()];
println!("{:#?}", public_inputs);
let prover = MockProver::run(11, &circuit, vec![public_inputs]).unwrap();
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![public_inputs])?;
assert_eq!(prover.verify(), Ok(()));
Ok(())

View File

@@ -1,46 +1,68 @@
constant "DAO" {
EcFixedPoint VALUE_COMMIT_VALUE,
EcFixedPointShort VALUE_COMMIT_VALUE,
EcFixedPoint VALUE_COMMIT_RANDOM,
}
contract "DAO" {
Base a, # contract address
Base s, # serial number
Base T, # treasury balance
Base B_b, # bulla blinding
Base spend_contract,
Base cur_balance,
Base old_serial,
Base old_bulla_blind,
Uint32 leaf_pos,
MerklePath W, # merkle path to DAO
MerklePath merkle_path,
Base u, # output 0 value
Base P_x, # output 0 addr x
Base P_y, # output 0 addr y
Base b_m, # output blinding
Base amount_to_send,
Base pub_x,
Base pub_y,
Base proposal_blind,
Scalar proposal_value_blind,
Base votes,
Scalar vote_blinds,
Scalar output_1_blind,
Base new_serial,
Base new_bulla_blind,
}
circuit "DAO" {
# Reveal serial number
constrain_instance(s);
# Enforce spend contract
constrain_instance(spend_contract);
# Poseidon hash of the Bulla
bulla = poseidon_hash(a, s, T, B_b);
D = calculate_merkle_root(leaf_pos, W, bulla);
# Reveal serial number
constrain_instance(old_serial);
# Poseidon hash of the current treasury
bulla = poseidon_hash(
spend_contract,
cur_balance,
old_serial,
old_bulla_blind,
);
# Enforce the merkle root
D = calculate_merkle_root(leaf_pos, merkle_path, bulla);
constrain_instance(D);
# Output 0:
M = poseidon_hash(u, P_x, P_y, b_m);
constrain_instance(M);
# Poseidon hash of the proposal (output 0)
proposal = poseidon_hash(amount_to_send, pub_x, pub_y, proposal_blind);
constrain_instance(proposal);
vcv = ec_mul_short(votes, VALUE_COMMIT_VALUE);
vcr = ec_mul(vote_blinds, VALUE_COMMIT_RANDOM);
vote_commit = ec_add(vcv, vcr);
vote_commit_x = ec_get_x(vote_commit);
vote_commit_y = ec_get_y(vote_commit);
constrain_instance(vote_commit_x);
constrain_instance(vote_commit_y);
# Pedersen commitment to the amount we're sending
valc_v = ec_mul_short(amount_to_send, VALUE_COMMIT_VALUE);
valc_r = ec_mul(proposal_value_blind, VALUE_COMMIT_RANDOM);
value_commit = ec_add(valc_v, valc_r);
value_commit_x = ec_get_x(value_commit);
value_commit_y = ec_get_y(value_commit);
constrain_instance(value_commit_x);
constrain_instance(value_commit_y);
# Calculate remaining balance in treasury
new_balance = base_sub(cur_balance, amount_to_send);
# Poseidon hash of the new treasury
new_bulla = poseidon_hash(
spend_contract,
new_balance,
new_serial,
new_bulla_blind,
);
constrain_instance(new_bulla);
}