Эх сурвалжийг харах

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

parazyd 4 жил өмнө
parent
commit
cc308da5e0
2 өөрчлөгдсөн 154 нэмэгдсэн , 487 устгасан
  1. 103 458
      proof/dao.rs
  2. 51 29
      proof/dao.zk

+ 103 - 458
proof/dao.rs

@@ -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 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 = [a, s, t, b_b];
+    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();
-    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);
+    // ========
+    // 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 = [output0_val, *output0_coords.x(), *output0_coords.y(), output0_blind];
+    let message = [amount_to_send, *proposal_coords.x(), *proposal_coords.y(), proposal_blind];
     let hasher = poseidon::Hash::<_, P128Pow5T3, ConstantLength<4>, 3, 2>::init();
-    let output0 = hasher.hash(message);
-
-    let authority = Keypair::random(&mut OsRng);
-    let _signature = authority.secret.sign(&output0.to_repr());
-
-    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 proposal = hasher.hash(message);
 
-    let vote_1_blind = pallas::Scalar::random(&mut OsRng);
-    let vote_1_commit = pedersen_commitment_scalar(mod_r_p(vote_1), vote_1_blind);
+    // Sign the proposal by the authority
+    let _signature = authority.secret.sign(&proposal.to_repr());
 
-    let vote_2_blind = pallas::Scalar::random(&mut OsRng);
-    let vote_2_commit = pedersen_commitment_scalar(mod_r_p(vote_2), vote_2_blind);
+    // ==============
+    // Voting process
+    // ==============
+    // The voting process happens now, and when finished, the votes are revealed.
+    // Votes are weighted by balance.
 
-    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 vote0 = pallas::Base::from(44);
+    let vote0_blind = pallas::Scalar::random(&mut OsRng);
 
-    let vote_commit = vote_1_commit + vote_2_commit; //+ vote_3_commit;
-    let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
+    let vote1 = pallas::Base::from(13);
+    let vote1_blind = pallas::Scalar::random(&mut OsRng);
 
-    let votes = vote_1 + vote_2; //+vote_3;
-    let vote_blinds = vote_1_blind + vote_2_blind; //+ vote_3_blind;
+    let vote2 = -pallas::Base::from(49); // This is a NO vote
+    let vote2_blind = pallas::Scalar::random(&mut OsRng);
 
-    let output_1_blind = pallas::Scalar::random(&mut OsRng);
+    let votes = vote0 + vote1 + vote2;
+    let vote_blinds = vote0_blind + vote1_blind + vote2_blind;
 
-    /*
-    let number = pallas::Base::from(u64::MAX).to_bytes();
-    let bits = number.view_bits::<Lsb0>();
-    println!("Positive: {:?}", bits);
+    if votes < pallas::Base::from(1) {
+        // The voting process result is negative, so we don't do anything.
+        return Ok(())
+    }
 
-    //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);
-    */
+    // ==================
+    // 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;
 
-    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 new_serial = pallas::Base::random(&mut OsRng);
+    let new_bulla_blind = pallas::Base::random(&mut OsRng);
 
-    let public_inputs =
-        vec![s, tree.root().inner(), output0, *vote_commit_coords.x(), *vote_commit_coords.y()];
-    println!("{:#?}", public_inputs);
+    let new_balance = cur_balance - amount_to_send;
 
-    let prover = MockProver::run(11, &circuit, vec![public_inputs]).unwrap();
+    let message = [spend_contract, new_balance, new_serial, new_bulla_blind];
+    let hasher = poseidon::Hash::<_, P128Pow5T3, ConstantLength<4>, 3, 2>::init();
+    let new_bulla = hasher.hash(message);
+
+    let merkle_root = tree.root();
+
+    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 public_inputs = vec![
+        spend_contract,
+        old_serial,
+        merkle_root.0,
+        proposal,
+        *value_coords.x(),
+        *value_coords.y(),
+        new_bulla,
+    ];
+
+    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 circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
+
+    let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![public_inputs])?;
     assert_eq!(prover.verify(), Ok(()));
 
     Ok(())

+ 51 - 29
proof/dao.zk

@@ -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" {
+	# Enforce spend contract
+	constrain_instance(spend_contract);
+
 	# Reveal serial number
-	constrain_instance(s);
+	constrain_instance(old_serial);
+
+	# Poseidon hash of the current treasury
+	bulla = poseidon_hash(
+		spend_contract,
+		cur_balance,
+		old_serial,
+		old_bulla_blind,
+	);
 
-	# Poseidon hash of the Bulla
-	bulla = poseidon_hash(a, s, T, B_b);
-	D = calculate_merkle_root(leaf_pos, W, bulla);
+	# 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);
-
-	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);
+	# Poseidon hash of the proposal (output 0)
+	proposal = poseidon_hash(amount_to_send, pub_x, pub_y, proposal_blind);
+	constrain_instance(proposal);
+
+	# 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);
 }