|
|
@@ -23,12 +23,13 @@ use darkfi_sdk::{
|
|
|
constants::MERKLE_DEPTH,
|
|
|
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
|
|
|
keypair::Keypair,
|
|
|
+ pedersen::pedersen_commitment_u64,
|
|
|
poseidon_hash, MerkleNode, SecretKey, TokenId,
|
|
|
},
|
|
|
incrementalmerkletree::{bridgetree::BridgeTree, Tree},
|
|
|
pasta::{
|
|
|
arithmetic::CurveAffine,
|
|
|
- group::{ff::Field, Curve},
|
|
|
+ group::{ff::Field, Curve, Group},
|
|
|
pallas,
|
|
|
},
|
|
|
tx::ContractCall,
|
|
|
@@ -39,7 +40,7 @@ use rand::rngs::OsRng;
|
|
|
|
|
|
use darkfi_dao_contract::{
|
|
|
dao_client::{build_dao_mint_tx, MerkleTree, WalletCache},
|
|
|
- dao_propose_client, money_client, note, DaoFunction,
|
|
|
+ dao_propose_client, dao_vote_client, money_client, note, DaoFunction,
|
|
|
};
|
|
|
|
|
|
use darkfi_money_contract::{
|
|
|
@@ -487,5 +488,394 @@ async fn integration_test() -> Result<()> {
|
|
|
debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.inner());
|
|
|
debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla);
|
|
|
|
|
|
+ // =======================================================
|
|
|
+ // Proposal is accepted!
|
|
|
+ // Start the voting
|
|
|
+ // =======================================================
|
|
|
+
|
|
|
+ // Copying these schizo comments from python code:
|
|
|
+ // Lets the voting begin
|
|
|
+ // Voters have access to the proposal and dao data
|
|
|
+ // vote_state = VoteState()
|
|
|
+ // We don't need to copy nullifier set because it is checked from gov_state
|
|
|
+ // in vote_state_transition() anyway
|
|
|
+ //
|
|
|
+ // TODO: what happens if voters don't unblind their vote
|
|
|
+ // Answer:
|
|
|
+ // 1. there is a time limit
|
|
|
+ // 2. both the MPC or users can unblind
|
|
|
+ //
|
|
|
+ // TODO: bug if I vote then send money, then we can double vote
|
|
|
+ // TODO: all timestamps missing
|
|
|
+ // - timelock (future voting starts in 2 days)
|
|
|
+ // Fix: use nullifiers from money gov state only from
|
|
|
+ // beginning of gov period
|
|
|
+ // Cannot use nullifiers from before voting period
|
|
|
+
|
|
|
+ debug!(target: "demo", "Stage 5. Start voting");
|
|
|
+
|
|
|
+ // We were previously saving updates here for testing
|
|
|
+ // let mut updates = vec![];
|
|
|
+
|
|
|
+ // User 1: YES
|
|
|
+
|
|
|
+ let (money_leaf_position, money_merkle_path) = {
|
|
|
+ let tree = &cache.tree;
|
|
|
+ let leaf_position = gov_recv[0].leaf_position;
|
|
|
+ let root = tree.root(0).unwrap();
|
|
|
+ let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
|
|
|
+ (leaf_position, merkle_path)
|
|
|
+ };
|
|
|
+
|
|
|
+ let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+ let input = dao_vote_client::BuilderInput {
|
|
|
+ secret: dao_th.alice_kp.secret,
|
|
|
+ note: gov_recv[0].note.clone(),
|
|
|
+ leaf_position: money_leaf_position,
|
|
|
+ merkle_path: money_merkle_path,
|
|
|
+ signature_secret,
|
|
|
+ };
|
|
|
+
|
|
|
+ let vote_option: bool = true;
|
|
|
+ // assert!(vote_option || !vote_option); // wtf
|
|
|
+
|
|
|
+ // We create a new keypair to encrypt the vote.
|
|
|
+ // For the demo MVP, you can just use the dao_keypair secret
|
|
|
+ let vote_keypair_1 = Keypair::random(&mut OsRng);
|
|
|
+
|
|
|
+ let builder = dao_vote_client::Builder {
|
|
|
+ inputs: vec![input],
|
|
|
+ vote: dao_vote_client::Vote {
|
|
|
+ vote_option,
|
|
|
+ vote_option_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
+ },
|
|
|
+ vote_keypair: vote_keypair_1,
|
|
|
+ proposal: proposal.clone(),
|
|
|
+ dao: dao_params.clone(),
|
|
|
+ };
|
|
|
+ let (params, proofs) = builder.build(
|
|
|
+ &dao_th.dao_vote_burn_zkbin,
|
|
|
+ &dao_th.dao_vote_burn_pk,
|
|
|
+ &dao_th.dao_vote_main_zkbin,
|
|
|
+ &dao_th.dao_vote_main_pk,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ let contract_id = *DAO_CONTRACT_ID;
|
|
|
+
|
|
|
+ let mut data = vec![DaoFunction::Vote as u8];
|
|
|
+ params.encode(&mut data)?;
|
|
|
+ let calls = vec![ContractCall { contract_id, data }];
|
|
|
+ let proofs = vec![proofs];
|
|
|
+ let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
+ let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
+ tx.signatures = vec![sigs];
|
|
|
+
|
|
|
+ dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
|
|
|
+
|
|
|
+ // Secret vote info. Needs to be revealed at some point.
|
|
|
+ // TODO: look into verifiable encryption for notes
|
|
|
+ // TODO: look into timelock puzzle as a possibility
|
|
|
+ let vote_note_1 = {
|
|
|
+ // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
|
|
|
+ let enc_note = note::EncryptedNote2 {
|
|
|
+ ciphertext: params.ciphertext,
|
|
|
+ ephem_public: params.ephem_public,
|
|
|
+ };
|
|
|
+ let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_1.secret).unwrap();
|
|
|
+ note
|
|
|
+ };
|
|
|
+ debug!(target: "demo", "User 1 voted!");
|
|
|
+ debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option);
|
|
|
+ debug!(target: "demo", " value: {}", vote_note_1.vote_value);
|
|
|
+
|
|
|
+ // User 2: NO
|
|
|
+
|
|
|
+ let (money_leaf_position, money_merkle_path) = {
|
|
|
+ let tree = &cache.tree;
|
|
|
+ let leaf_position = gov_recv[1].leaf_position;
|
|
|
+ let root = tree.root(0).unwrap();
|
|
|
+ let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
|
|
|
+ (leaf_position, merkle_path)
|
|
|
+ };
|
|
|
+
|
|
|
+ let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+ let input = dao_vote_client::BuilderInput {
|
|
|
+ //secret: gov_keypair_2.secret,
|
|
|
+ secret: dao_th.bob_kp.secret,
|
|
|
+ note: gov_recv[1].note.clone(),
|
|
|
+ leaf_position: money_leaf_position,
|
|
|
+ merkle_path: money_merkle_path,
|
|
|
+ signature_secret,
|
|
|
+ };
|
|
|
+
|
|
|
+ let vote_option: bool = false;
|
|
|
+ // assert!(vote_option || !vote_option); // wtf
|
|
|
+
|
|
|
+ // We create a new keypair to encrypt the vote.
|
|
|
+ let vote_keypair_2 = Keypair::random(&mut OsRng);
|
|
|
+
|
|
|
+ let builder = dao_vote_client::Builder {
|
|
|
+ inputs: vec![input],
|
|
|
+ vote: dao_vote_client::Vote {
|
|
|
+ vote_option,
|
|
|
+ vote_option_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
+ },
|
|
|
+ vote_keypair: vote_keypair_2,
|
|
|
+ proposal: proposal.clone(),
|
|
|
+ dao: dao_params.clone(),
|
|
|
+ };
|
|
|
+ let (params, proofs) = builder.build(
|
|
|
+ &dao_th.dao_vote_burn_zkbin,
|
|
|
+ &dao_th.dao_vote_burn_pk,
|
|
|
+ &dao_th.dao_vote_main_zkbin,
|
|
|
+ &dao_th.dao_vote_main_pk,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ let contract_id = *DAO_CONTRACT_ID;
|
|
|
+
|
|
|
+ let mut data = vec![DaoFunction::Vote as u8];
|
|
|
+ params.encode(&mut data)?;
|
|
|
+ let calls = vec![ContractCall { contract_id, data }];
|
|
|
+ let proofs = vec![proofs];
|
|
|
+ let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
+ let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
+ tx.signatures = vec![sigs];
|
|
|
+
|
|
|
+ dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
|
|
|
+
|
|
|
+ let vote_note_2 = {
|
|
|
+ // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
|
|
|
+ let enc_note = note::EncryptedNote2 {
|
|
|
+ ciphertext: params.ciphertext,
|
|
|
+ ephem_public: params.ephem_public,
|
|
|
+ };
|
|
|
+ let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_2.secret).unwrap();
|
|
|
+ note
|
|
|
+ };
|
|
|
+ debug!(target: "demo", "User 2 voted!");
|
|
|
+ debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option);
|
|
|
+ debug!(target: "demo", " value: {}", vote_note_2.vote_value);
|
|
|
+
|
|
|
+ // User 3: YES
|
|
|
+
|
|
|
+ let (money_leaf_position, money_merkle_path) = {
|
|
|
+ let tree = &cache.tree;
|
|
|
+ let leaf_position = gov_recv[2].leaf_position;
|
|
|
+ let root = tree.root(0).unwrap();
|
|
|
+ let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
|
|
|
+ (leaf_position, merkle_path)
|
|
|
+ };
|
|
|
+
|
|
|
+ let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+ let input = dao_vote_client::BuilderInput {
|
|
|
+ //secret: gov_keypair_3.secret,
|
|
|
+ secret: dao_th.charlie_kp.secret,
|
|
|
+ note: gov_recv[2].note.clone(),
|
|
|
+ leaf_position: money_leaf_position,
|
|
|
+ merkle_path: money_merkle_path,
|
|
|
+ signature_secret,
|
|
|
+ };
|
|
|
+
|
|
|
+ let vote_option: bool = true;
|
|
|
+ // assert!(vote_option || !vote_option); // wtf
|
|
|
+
|
|
|
+ // We create a new keypair to encrypt the vote.
|
|
|
+ let vote_keypair_3 = Keypair::random(&mut OsRng);
|
|
|
+
|
|
|
+ let builder = dao_vote_client::Builder {
|
|
|
+ inputs: vec![input],
|
|
|
+ vote: dao_vote_client::Vote {
|
|
|
+ vote_option,
|
|
|
+ vote_option_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
+ },
|
|
|
+ vote_keypair: vote_keypair_3,
|
|
|
+ proposal: proposal.clone(),
|
|
|
+ dao: dao_params.clone(),
|
|
|
+ };
|
|
|
+ let (params, proofs) = builder.build(
|
|
|
+ &dao_th.dao_vote_burn_zkbin,
|
|
|
+ &dao_th.dao_vote_burn_pk,
|
|
|
+ &dao_th.dao_vote_main_zkbin,
|
|
|
+ &dao_th.dao_vote_main_pk,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ let contract_id = *DAO_CONTRACT_ID;
|
|
|
+
|
|
|
+ let mut data = vec![DaoFunction::Vote as u8];
|
|
|
+ params.encode(&mut data)?;
|
|
|
+ let calls = vec![ContractCall { contract_id, data }];
|
|
|
+ let proofs = vec![proofs];
|
|
|
+ let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
+ let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
+ tx.signatures = vec![sigs];
|
|
|
+
|
|
|
+ dao_th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
|
|
|
+
|
|
|
+ // Secret vote info. Needs to be revealed at some point.
|
|
|
+ // TODO: look into verifiable encryption for notes
|
|
|
+ // TODO: look into timelock puzzle as a possibility
|
|
|
+ let vote_note_3 = {
|
|
|
+ // TODO: EncryptedNote should be accessible by wasm and put in the structs directly
|
|
|
+ let enc_note = note::EncryptedNote2 {
|
|
|
+ ciphertext: params.ciphertext,
|
|
|
+ ephem_public: params.ephem_public,
|
|
|
+ };
|
|
|
+ let note: dao_vote_client::Note = enc_note.decrypt(&vote_keypair_3.secret).unwrap();
|
|
|
+ note
|
|
|
+ };
|
|
|
+ debug!(target: "demo", "User 3 voted!");
|
|
|
+ debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option);
|
|
|
+ debug!(target: "demo", " value: {}", vote_note_3.vote_value);
|
|
|
+
|
|
|
+ // Every votes produces a semi-homomorphic encryption of their vote.
|
|
|
+ // Which is either yes or no
|
|
|
+ // We copy the state tree for the governance token so coins can be used
|
|
|
+ // to vote on other proposals at the same time.
|
|
|
+ // With their vote, they produce a ZK proof + nullifier
|
|
|
+ // The votes are unblinded by MPC to a selected party at the end of the
|
|
|
+ // voting period.
|
|
|
+ // (that's if we want votes to be hidden during voting)
|
|
|
+
|
|
|
+ let mut yes_votes_value = 0;
|
|
|
+ let mut yes_votes_blind = pallas::Scalar::from(0);
|
|
|
+ let mut yes_votes_commit = pallas::Point::identity();
|
|
|
+
|
|
|
+ let mut all_votes_value = 0;
|
|
|
+ let mut all_votes_blind = pallas::Scalar::from(0);
|
|
|
+ let mut all_votes_commit = pallas::Point::identity();
|
|
|
+
|
|
|
+ // We were previously saving votes to a Vec<Update> for testing.
|
|
|
+ // However since Update is now UpdateBase it gets moved into update.apply().
|
|
|
+ // So we need to think of another way to run these tests.
|
|
|
+ //assert!(updates.len() == 3);
|
|
|
+
|
|
|
+ for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3]
|
|
|
+ .iter() /*.zip(updates)*/
|
|
|
+ .enumerate()
|
|
|
+ {
|
|
|
+ let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind);
|
|
|
+ //assert!(update.value_commit == all_vote_value_commit);
|
|
|
+ all_votes_commit += vote_commit;
|
|
|
+ all_votes_blind += note.vote_value_blind;
|
|
|
+
|
|
|
+ let yes_vote_commit = pedersen_commitment_u64(
|
|
|
+ note.vote.vote_option as u64 * note.vote_value,
|
|
|
+ note.vote.vote_option_blind,
|
|
|
+ );
|
|
|
+ //assert!(update.yes_vote_commit == yes_vote_commit);
|
|
|
+
|
|
|
+ yes_votes_commit += yes_vote_commit;
|
|
|
+ yes_votes_blind += note.vote.vote_option_blind;
|
|
|
+
|
|
|
+ let vote_option = note.vote.vote_option;
|
|
|
+
|
|
|
+ if vote_option {
|
|
|
+ yes_votes_value += note.vote_value;
|
|
|
+ }
|
|
|
+ all_votes_value += note.vote_value;
|
|
|
+ let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() };
|
|
|
+
|
|
|
+ debug!("Voter {} voted {}", i, vote_result);
|
|
|
+ }
|
|
|
+
|
|
|
+ debug!("Outcome = {} / {}", yes_votes_value, all_votes_value);
|
|
|
+
|
|
|
+ assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind));
|
|
|
+ assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind));
|
|
|
+
|
|
|
+ // =======================================================
|
|
|
+ // Execute the vote
|
|
|
+ // =======================================================
|
|
|
+
|
|
|
+ debug!(target: "demo", "Stage 6. Execute vote");
|
|
|
+
|
|
|
+ // Used to export user_data from this coin so it can be accessed by DAO::exec()
|
|
|
+ let user_data_blind = pallas::Base::random(&mut OsRng);
|
|
|
+
|
|
|
+ let user_serial = pallas::Base::random(&mut OsRng);
|
|
|
+ let user_coin_blind = pallas::Base::random(&mut OsRng);
|
|
|
+ let dao_serial = pallas::Base::random(&mut OsRng);
|
|
|
+ let dao_coin_blind = pallas::Base::random(&mut OsRng);
|
|
|
+ let input_value = treasury_note.value;
|
|
|
+ let input_value_blind = pallas::Scalar::random(&mut OsRng);
|
|
|
+ let tx_signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+ let exec_signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+
|
|
|
+ let (treasury_leaf_position, treasury_merkle_path) = {
|
|
|
+ let tree = &cache.tree;
|
|
|
+ let leaf_position = dao_recv_coin.leaf_position;
|
|
|
+ let root = tree.root(0).unwrap();
|
|
|
+ let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
|
|
|
+ (leaf_position, merkle_path)
|
|
|
+ };
|
|
|
+
|
|
|
+ let input = money_client::BuilderInputInfo {
|
|
|
+ leaf_position: treasury_leaf_position,
|
|
|
+ merkle_path: treasury_merkle_path,
|
|
|
+ secret: dao_th.dao_kp.secret,
|
|
|
+ note: treasury_note,
|
|
|
+ user_data_blind,
|
|
|
+ value_blind: input_value_blind,
|
|
|
+ signature_secret: tx_signature_secret,
|
|
|
+ };
|
|
|
+
|
|
|
+ // TODO: this should be the contract/func ID
|
|
|
+ let spend_hook = pallas::Base::from(110);
|
|
|
+ // The user_data can be a simple hash of the items passed into the ZK proof
|
|
|
+ // up to corresponding linked ZK proof to interpret however they need.
|
|
|
+ // In out case, it's the bulla for the DAO
|
|
|
+ let user_data = dao_bulla.inner();
|
|
|
+
|
|
|
+ let builder = money_client::Builder {
|
|
|
+ clear_inputs: vec![],
|
|
|
+ inputs: vec![input],
|
|
|
+ outputs: vec![
|
|
|
+ // Sending money
|
|
|
+ money_client::BuilderOutputInfo {
|
|
|
+ value: 1000,
|
|
|
+ token_id: xdrk_token_id,
|
|
|
+ //public: user_keypair.public,
|
|
|
+ public: receiver_keypair.public,
|
|
|
+ serial: proposal.serial,
|
|
|
+ coin_blind: proposal.blind,
|
|
|
+ spend_hook: pallas::Base::from(0),
|
|
|
+ user_data: pallas::Base::from(0),
|
|
|
+ },
|
|
|
+ // Change back to DAO
|
|
|
+ money_client::BuilderOutputInfo {
|
|
|
+ value: xdrk_supply - 1000,
|
|
|
+ token_id: xdrk_token_id,
|
|
|
+ public: dao_th.dao_kp.public,
|
|
|
+ serial: dao_serial,
|
|
|
+ coin_blind: dao_coin_blind,
|
|
|
+ spend_hook,
|
|
|
+ user_data,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+ //let (xfer_params, xfer_proofs) = builder.build(
|
|
|
+ // &dao_th.dao_propose_burn_zkbin,
|
|
|
+ // &dao_th.dao_propose_burn_pk,
|
|
|
+ // &dao_th.dao_propose_main_zkbin,
|
|
|
+ // &dao_th.dao_propose_main_pk,
|
|
|
+ //)?;
|
|
|
+
|
|
|
+ //let builder = dao::exec::wallet::Builder {
|
|
|
+ // proposal,
|
|
|
+ // dao: dao_params.clone(),
|
|
|
+ // yes_votes_value,
|
|
|
+ // all_votes_value,
|
|
|
+ // yes_votes_blind,
|
|
|
+ // all_votes_blind,
|
|
|
+ // user_serial,
|
|
|
+ // user_coin_blind,
|
|
|
+ // dao_serial,
|
|
|
+ // dao_coin_blind,
|
|
|
+ // input_value,
|
|
|
+ // input_value_blind,
|
|
|
+ // hook_dao_exec: *dao::exec::FUNC_ID,
|
|
|
+ // signature_secret: exec_signature_secret,
|
|
|
+ //};
|
|
|
+
|
|
|
Ok(())
|
|
|
}
|