Просмотр исходного кода

contract/dao: split dao key into 6 keys for full control and introduced early execution flow

skoupidi 1 год назад
Родитель
Сommit
de9fc42745

+ 32 - 12
src/contract/dao/proof/auth-money-transfer.zk

@@ -2,8 +2,6 @@ k = 11;
 field = "pallas";
 
 constant "AuthMoneyTransfer" {
-    EcFixedPointShort VALUE_COMMIT_VALUE,
-    EcFixedPoint VALUE_COMMIT_RANDOM,
     EcFixedPointBase NULLIFIER_K,
 }
 
@@ -18,10 +16,21 @@ witness "AuthMoneyTransfer" {
     # DAO parameters
     Base dao_proposer_limit,
     Base dao_quorum,
+    Base dao_early_exec_quorum,
     Base dao_approval_ratio_quot,
     Base dao_approval_ratio_base,
     Base dao_gov_token_id,
-    EcNiPoint dao_public_key,
+    EcNiPoint dao_notes_public_key,
+    Base dao_proposer_public_x,
+    Base dao_proposer_public_y,
+    Base dao_proposals_public_x,
+    Base dao_proposals_public_y,
+    Base dao_votes_public_x,
+    Base dao_votes_public_y,
+    Base dao_exec_public_x,
+    Base dao_exec_public_y,
+    Base dao_early_exec_public_x,
+    Base dao_early_exec_public_y,
     Base dao_bulla_blind,
 
     # Dao input(s) user data blind
@@ -40,21 +49,32 @@ witness "AuthMoneyTransfer" {
 }
 
 circuit "AuthMoneyTransfer" {
-    # cast to EcPoint
+    # Cast to EcPoint
     # (otherwise zkas refuses to compile)
     ONE = witness_base(1);
-    dao_pubkey = ec_mul_var_base(ONE, dao_public_key);
-    dao_public_x = ec_get_x(dao_pubkey);
-    dao_public_y = ec_get_y(dao_pubkey);
+    dao_notes_pubkey = ec_mul_var_base(ONE, dao_notes_public_key);
+    dao_notes_public_x = ec_get_x(dao_notes_pubkey);
+    dao_notes_public_y = ec_get_y(dao_notes_pubkey);
 
     dao_bulla = poseidon_hash(
         dao_proposer_limit,
         dao_quorum,
+        dao_early_exec_quorum,
         dao_approval_ratio_quot,
         dao_approval_ratio_base,
         dao_gov_token_id,
-        dao_public_x,
-        dao_public_y,
+        dao_notes_public_x,
+        dao_notes_public_y,
+        dao_proposer_public_x,
+        dao_proposer_public_y,
+        dao_proposals_public_x,
+        dao_proposals_public_y,
+        dao_votes_public_x,
+        dao_votes_public_y,
+        dao_exec_public_x,
+        dao_exec_public_y,
+        dao_early_exec_public_x,
+        dao_early_exec_public_y,
         dao_bulla_blind,
     );
 
@@ -77,8 +97,8 @@ circuit "AuthMoneyTransfer" {
 
     # Change output should be sending back to the DAO
     dao_change_coin = poseidon_hash(
-        dao_public_x,
-        dao_public_y,
+        dao_notes_public_x,
+        dao_notes_public_y,
         # We don't need to check this value.
         # money::transfer() checks that sum(input values) = sum(output values)
         # which ensures it will be correct.
@@ -104,7 +124,7 @@ circuit "AuthMoneyTransfer" {
     constrain_instance(ec_get_y(ephem_public));
 
     # The shared_point C = eP = dE
-    shared_point = ec_mul_var_base(ephem_secret, dao_public_key);
+    shared_point = ec_mul_var_base(ephem_secret, dao_notes_public_key);
     shared_secret = poseidon_hash(
         ec_get_x(shared_point),
         ec_get_y(shared_point),

+ 136 - 0
src/contract/dao/proof/early-exec.zk

@@ -0,0 +1,136 @@
+k = 11;
+field = "pallas";
+
+constant "EarlyExec" {
+    EcFixedPointShort VALUE_COMMIT_VALUE,
+    EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPointBase NULLIFIER_K,
+}
+
+witness "EarlyExec" {
+    # Proposal parameters
+    Base proposal_auth_calls_commit,
+    Base proposal_creation_blockwindow,
+    Base proposal_duration_blockwindows,
+    Base proposal_user_data,
+    Base proposal_blind,
+
+    # DAO parameters
+    Base dao_proposer_limit,
+    Base dao_quorum,
+    Base dao_early_exec_quorum,
+    Base dao_approval_ratio_quot,
+    Base dao_approval_ratio_base,
+    Base dao_gov_token_id,
+    Base dao_notes_public_x,
+    Base dao_notes_public_y,
+    Base dao_proposer_public_x,
+    Base dao_proposer_public_y,
+    Base dao_proposals_public_x,
+    Base dao_proposals_public_y,
+    Base dao_votes_public_x,
+    Base dao_votes_public_y,
+    Base dao_exec_secret,
+    Base dao_early_exec_secret,
+    Base dao_bulla_blind,
+
+    # Votes
+    Base yes_vote_value,
+    Base all_vote_value,
+    Scalar yes_vote_blind,
+    Scalar all_vote_blind,
+
+    # Check whether the proposal has expired or not
+    Base current_blockwindow,
+
+    # Signature secret
+    Base signature_secret,
+}
+
+circuit "EarlyExec" {
+    # Derive DAO executor public key
+    dao_exec_public = ec_mul_base(dao_exec_secret, NULLIFIER_K);
+    dao_exec_public_x = ec_get_x(dao_exec_public);
+    dao_exec_public_y = ec_get_y(dao_exec_public);
+
+    # Derive DAO early executor public key for 2FA
+    dao_early_exec_public = ec_mul_base(dao_early_exec_secret, NULLIFIER_K);
+    dao_early_exec_public_x = ec_get_x(dao_early_exec_public);
+    dao_early_exec_public_y = ec_get_y(dao_early_exec_public);
+
+    dao_bulla = poseidon_hash(
+        dao_proposer_limit,
+        dao_quorum,
+        dao_early_exec_quorum,
+        dao_approval_ratio_quot,
+        dao_approval_ratio_base,
+        dao_gov_token_id,
+        dao_notes_public_x,
+        dao_notes_public_y,
+        dao_proposer_public_x,
+        dao_proposer_public_y,
+        dao_proposals_public_x,
+        dao_proposals_public_y,
+        dao_votes_public_x,
+        dao_votes_public_y,
+        dao_exec_public_x,
+        dao_exec_public_y,
+        dao_early_exec_public_x,
+        dao_early_exec_public_y,
+        dao_bulla_blind,
+    );
+
+    # Proposal bulla being valid means DAO bulla is also valid because
+    # dao-propose-main.zk already checks that when we first create the
+    # proposal - so it is redundant to check DAO bulla exists here.
+    proposal_bulla = poseidon_hash(
+        proposal_auth_calls_commit,
+        proposal_creation_blockwindow,
+        proposal_duration_blockwindows,
+        proposal_user_data,
+        dao_bulla,
+        proposal_blind,
+    );
+    constrain_instance(proposal_bulla);
+    constrain_instance(proposal_auth_calls_commit);
+
+    # Enforce that the proposal has not expired
+    end_time = base_add(proposal_creation_blockwindow, proposal_duration_blockwindows);
+    less_than_strict(current_blockwindow, end_time);
+    constrain_instance(current_blockwindow);
+
+    # Create Pedersen commitments for win_votes and total_votes, and
+    # constrain the commitments' coordinates.
+    yes_vote_value_c = ec_mul_short(yes_vote_value, VALUE_COMMIT_VALUE);
+    yes_vote_blind_c = ec_mul(yes_vote_blind, VALUE_COMMIT_RANDOM);
+    yes_vote_commit = ec_add(yes_vote_value_c, yes_vote_blind_c);
+    constrain_instance(ec_get_x(yes_vote_commit));
+    constrain_instance(ec_get_y(yes_vote_commit));
+
+    all_vote_value_c = ec_mul_short(all_vote_value, VALUE_COMMIT_VALUE);
+    all_vote_blind_c = ec_mul(all_vote_blind, VALUE_COMMIT_RANDOM);
+    all_vote_commit = ec_add(all_vote_value_c, all_vote_blind_c);
+    constrain_instance(ec_get_x(all_vote_commit));
+    constrain_instance(ec_get_y(all_vote_commit));
+
+    # Check that dao_early_exec_quorum is less than or equal to all_vote_value
+    one = witness_base(1);
+    all_vote_value_1 = base_add(all_vote_value, one);
+    less_than_strict(dao_early_exec_quorum, all_vote_value_1);
+
+    # approval_ratio_quot / approval_ratio_base <= yes_vote / all_vote
+    #
+    # The above is also equivalent to this:
+    #
+    # all_vote * approval_ratio_quot <= yes_vote * approval_ratio_base
+    lhs = base_mul(all_vote_value, dao_approval_ratio_quot);
+    rhs = base_mul(yes_vote_value, dao_approval_ratio_base);
+    rhs_1 = base_add(rhs, one);
+    less_than_strict(lhs, rhs_1);
+
+    # Derive a public key for the signature and constrain its coordinates
+    signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
+    constrain_instance(ec_get_x(signature_public));
+    constrain_instance(ec_get_y(signature_public));
+}
+

+ 33 - 6
src/contract/dao/proof/exec.zk

@@ -18,11 +18,21 @@ witness "Exec" {
     # DAO parameters
     Base dao_proposer_limit,
     Base dao_quorum,
+    Base dao_early_exec_quorum,
     Base dao_approval_ratio_quot,
     Base dao_approval_ratio_base,
     Base dao_gov_token_id,
-    Base dao_public_x,
-    Base dao_public_y,
+    Base dao_notes_public_x,
+    Base dao_notes_public_y,
+    Base dao_proposer_public_x,
+    Base dao_proposer_public_y,
+    Base dao_proposals_public_x,
+    Base dao_proposals_public_y,
+    Base dao_votes_public_x,
+    Base dao_votes_public_y,
+    Base dao_exec_secret,
+    Base dao_early_exec_public_x,
+    Base dao_early_exec_public_y,
     Base dao_bulla_blind,
 
     # Votes
@@ -39,14 +49,30 @@ witness "Exec" {
 }
 
 circuit "Exec" {
+    # Derive DAO executor public key
+    dao_exec_public = ec_mul_base(dao_exec_secret, NULLIFIER_K);
+    dao_exec_public_x = ec_get_x(dao_exec_public);
+    dao_exec_public_y = ec_get_y(dao_exec_public);
+
     dao_bulla = poseidon_hash(
         dao_proposer_limit,
         dao_quorum,
+        dao_early_exec_quorum,
         dao_approval_ratio_quot,
         dao_approval_ratio_base,
         dao_gov_token_id,
-        dao_public_x,
-        dao_public_y,
+        dao_notes_public_x,
+        dao_notes_public_y,
+        dao_proposer_public_x,
+        dao_proposer_public_y,
+        dao_proposals_public_x,
+        dao_proposals_public_y,
+        dao_votes_public_x,
+        dao_votes_public_y,
+        dao_exec_public_x,
+        dao_exec_public_y,
+        dao_early_exec_public_x,
+        dao_early_exec_public_y,
         dao_bulla_blind,
     );
 
@@ -65,8 +91,10 @@ circuit "Exec" {
     constrain_instance(proposal_auth_calls_commit);
 
     # Enforce that the proposal has expired
+    one = witness_base(1);
     end_time = base_add(proposal_creation_blockwindow, proposal_duration_blockwindows);
-    less_than_strict(end_time, current_blockwindow);
+    current_blockwindow_1 = base_add(current_blockwindow, one);
+    less_than_strict(end_time, current_blockwindow_1);
     constrain_instance(current_blockwindow);
 
     # Create Pedersen commitments for win_votes and total_votes, and
@@ -84,7 +112,6 @@ circuit "Exec" {
     constrain_instance(ec_get_y(all_vote_commit));
 
     # Check that dao_quorum is less than or equal to all_vote_value
-    one = witness_base(1);
     all_vote_value_1 = base_add(all_vote_value, one);
     less_than_strict(dao_quorum, all_vote_value_1);
 

+ 68 - 23
src/contract/dao/proof/mint.zk

@@ -2,39 +2,84 @@ k = 11;
 field = "pallas";
 
 constant "Mint" {
-    EcFixedPoint VALUE_COMMIT_RANDOM,
     EcFixedPointBase NULLIFIER_K,
 }
 
 witness "Mint" {
-    Base dao_proposer_limit,
-    Base dao_quorum,
-    Base dao_approval_ratio_quot,
-    Base dao_approval_ratio_base,
-    Base dao_gov_token_id,
-    Base dao_secret,
-    Base dao_bulla_blind,
+    Base proposer_limit,
+    Base quorum,
+    Base early_exec_quorum,
+    Base approval_ratio_quot,
+    Base approval_ratio_base,
+    Base gov_token_id,
+    Base notes_secret,
+    Base proposer_secret,
+    Base proposals_secret,
+    Base votes_secret,
+    Base exec_secret,
+    Base early_exec_secret,
+    Base bulla_blind,
 }
 
 circuit "Mint" {
-    # This circuit states that the bulla is a hash of 8 values
+    # Derive and constrain DAO notes public key
+    notes_public = ec_mul_base(notes_secret, NULLIFIER_K);
+    notes_public_x = ec_get_x(notes_public);
+    notes_public_y = ec_get_y(notes_public);
+    constrain_instance(notes_public_x);
+    constrain_instance(notes_public_y);
 
-    dao_public = ec_mul_base(dao_secret, NULLIFIER_K);
-    dao_public_x = ec_get_x(dao_public);
-    dao_public_y = ec_get_y(dao_public);
-    constrain_instance(dao_public_x);
-    constrain_instance(dao_public_y);
+    # Derive DAO proposer public key
+    proposer_public = ec_mul_base(proposer_secret, NULLIFIER_K);
+    proposer_public_x = ec_get_x(proposer_public);
+    proposer_public_y = ec_get_y(proposer_public);
 
+    # Derive DAO proposals public key
+    proposals_public = ec_mul_base(proposals_secret, NULLIFIER_K);
+    proposals_public_x = ec_get_x(proposals_public);
+    proposals_public_y = ec_get_y(proposals_public);
+
+    # Derive DAO votes public key
+    votes_public = ec_mul_base(votes_secret, NULLIFIER_K);
+    votes_public_x = ec_get_x(votes_public);
+    votes_public_y = ec_get_y(votes_public);
+
+    # Derive DAO executor public key
+    exec_public = ec_mul_base(exec_secret, NULLIFIER_K);
+    exec_public_x = ec_get_x(exec_public);
+    exec_public_y = ec_get_y(exec_public);
+
+    # Derive DAO early executor public key
+    early_exec_public = ec_mul_base(early_exec_secret, NULLIFIER_K);
+    early_exec_public_x = ec_get_x(early_exec_public);
+    early_exec_public_y = ec_get_y(early_exec_public);
+
+    # Check that early execution quorum is greater or equal to normal quorum
+    one = witness_base(1);
+    early_exec_quorum_1 = base_add(early_exec_quorum, one);
+    less_than_strict(quorum, early_exec_quorum_1);
+
+    # Derive and constrain the DAO bulla
     bulla = poseidon_hash(
-        dao_proposer_limit,
-        dao_quorum,
-        dao_approval_ratio_quot,
-        dao_approval_ratio_base,
-        dao_gov_token_id,
-        dao_public_x,
-        dao_public_y,
-        dao_bulla_blind,
+        proposer_limit,
+        quorum,
+        early_exec_quorum,
+        approval_ratio_quot,
+        approval_ratio_base,
+        gov_token_id,
+        notes_public_x,
+        notes_public_y,
+        proposer_public_x,
+        proposer_public_y,
+        proposals_public_x,
+        proposals_public_y,
+        votes_public_x,
+        votes_public_y,
+        exec_public_x,
+        exec_public_y,
+        early_exec_public_x,
+        early_exec_public_y,
+        bulla_blind,
     );
-
     constrain_instance(bulla);
 }

+ 32 - 5
src/contract/dao/proof/propose-main.zk

@@ -2,8 +2,9 @@ k = 11;
 field = "pallas";
 
 constant "ProposeMain" {
-    EcFixedPointShort VALUE_COMMIT_VALUE,
     EcFixedPoint VALUE_COMMIT_RANDOM,
+    EcFixedPointBase NULLIFIER_K,
+    EcFixedPointShort VALUE_COMMIT_VALUE,
 }
 
 witness "ProposeMain" {
@@ -24,11 +25,21 @@ witness "ProposeMain" {
     # DAO params
     Base dao_proposer_limit,
     Base dao_quorum,
+    Base dao_early_exec_quorum,
     Base dao_approval_ratio_quot,
     Base dao_approval_ratio_base,
     Base dao_gov_token_id,
-    Base dao_public_x,
-    Base dao_public_y,
+    Base dao_notes_public_x,
+    Base dao_notes_public_y,
+    Base dao_proposer_secret,
+    Base dao_proposals_public_x,
+    Base dao_proposals_public_y,
+    Base dao_votes_public_x,
+    Base dao_votes_public_y,
+    Base dao_exec_public_x,
+    Base dao_exec_public_y,
+    Base dao_early_exec_public_x,
+    Base dao_early_exec_public_y,
     Base dao_bulla_blind,
 
     Uint32 dao_leaf_pos,
@@ -39,14 +50,30 @@ circuit "ProposeMain" {
     token_commit = poseidon_hash(dao_gov_token_id, gov_token_blind);
     constrain_instance(token_commit);
 
+    # Derive DAO proposer public key
+    dao_proposer_public = ec_mul_base(dao_proposer_secret, NULLIFIER_K);
+    dao_proposer_public_x = ec_get_x(dao_proposer_public);
+    dao_proposer_public_y = ec_get_y(dao_proposer_public);
+
     dao_bulla = poseidon_hash(
         dao_proposer_limit,
         dao_quorum,
+        dao_early_exec_quorum,
         dao_approval_ratio_quot,
         dao_approval_ratio_base,
         dao_gov_token_id,
-        dao_public_x,
-        dao_public_y,
+        dao_notes_public_x,
+        dao_notes_public_y,
+        dao_proposer_public_x,
+        dao_proposer_public_y,
+        dao_proposals_public_x,
+        dao_proposals_public_y,
+        dao_votes_public_x,
+        dao_votes_public_y,
+        dao_exec_public_x,
+        dao_exec_public_y,
+        dao_early_exec_public_x,
+        dao_early_exec_public_y,
         dao_bulla_blind,
     );
 

+ 30 - 9
src/contract/dao/proof/vote-main.zk

@@ -2,10 +2,9 @@ k = 11;
 field = "pallas";
 
 constant "VoteMain" {
-    EcFixedPoint VALUE_COMMIT_RANDOM,
-    EcFixedPointShort VALUE_COMMIT_VALUE,
     EcFixedPointBase NULLIFIER_K,
     EcFixedPointBase VALUE_COMMIT_RANDOM_BASE,
+    EcFixedPointShort VALUE_COMMIT_VALUE,
 }
 
 witness "VoteMain" {
@@ -19,10 +18,21 @@ witness "VoteMain" {
     # DAO parameters
     Base dao_proposer_limit,
     Base dao_quorum,
+    Base dao_early_exec_quorum,
     Base dao_approval_ratio_quot,
     Base dao_approval_ratio_base,
     Base dao_gov_token_id,
-    EcNiPoint dao_public_key,
+    Base dao_notes_public_x,
+    Base dao_notes_public_y,
+    Base dao_proposer_public_x,
+    Base dao_proposer_public_y,
+    Base dao_proposals_public_x,
+    Base dao_proposals_public_y,
+    EcNiPoint dao_votes_public_key,
+    Base dao_exec_public_x,
+    Base dao_exec_public_y,
+    Base dao_early_exec_public_x,
+    Base dao_early_exec_public_y,
     Base dao_bulla_blind,
 
     # Is the vote yes or no
@@ -49,18 +59,29 @@ circuit "VoteMain" {
     # Cast to EcPoint
     # (otherwise zkas refuses to compile)
     ONE = witness_base(1);
-    dao_pubkey = ec_mul_var_base(ONE, dao_public_key);
-    dao_public_x = ec_get_x(dao_pubkey);
-    dao_public_y = ec_get_y(dao_pubkey);
+    dao_votes_pubkey = ec_mul_var_base(ONE, dao_votes_public_key);
+    dao_votes_public_x = ec_get_x(dao_votes_pubkey);
+    dao_votes_public_y = ec_get_y(dao_votes_pubkey);
 
     dao_bulla = poseidon_hash(
         dao_proposer_limit,
         dao_quorum,
+        dao_early_exec_quorum,
         dao_approval_ratio_quot,
         dao_approval_ratio_base,
         dao_gov_token_id,
-        dao_public_x,
-        dao_public_y,
+        dao_notes_public_x,
+        dao_notes_public_y,
+        dao_proposer_public_x,
+        dao_proposer_public_y,
+        dao_proposals_public_x,
+        dao_proposals_public_y,
+        dao_votes_public_x,
+        dao_votes_public_y,
+        dao_exec_public_x,
+        dao_exec_public_y,
+        dao_early_exec_public_x,
+        dao_early_exec_public_y,
         dao_bulla_blind,
     );
 
@@ -102,7 +123,7 @@ circuit "VoteMain" {
     ephem_public = ec_mul_base(ephem_secret, NULLIFIER_K);
     constrain_instance(ec_get_x(ephem_public));
     constrain_instance(ec_get_y(ephem_public));
-    shared_point = ec_mul_var_base(ephem_secret, dao_public_key);
+    shared_point = ec_mul_var_base(ephem_secret, dao_votes_public_key);
     shared_secret = poseidon_hash(
         ec_get_x(shared_point),
         ec_get_y(shared_point),

+ 20 - 3
src/contract/dao/src/client/auth_xfer.rs

@@ -110,7 +110,6 @@ impl DaoAuthMoneyTransferCall {
         let change_ephem_pubkey = PublicKey::from_secret(ephem_secret);
         let (ephem_x, ephem_y) = change_ephem_pubkey.xy();
 
-        let dao_public_key = self.dao.public_key.inner();
         let dao_change_value = pallas::Base::from(self.dao_coin_attrs.value);
 
         let note = [
@@ -120,14 +119,21 @@ impl DaoAuthMoneyTransferCall {
         ];
 
         let dao_change_attrs =
-            ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.public_key)?;
+            ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.notes_public_key)?;
 
         let params = DaoAuthMoneyTransferParams { enc_attrs, dao_change_attrs };
 
         let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
         let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
         let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
         let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
+        let dao_notes_public_key = self.dao.notes_public_key.inner();
+        let (dao_proposer_pub_x, dao_proposer_pub_y) = self.dao.proposer_public_key.xy();
+        let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
+        let (dao_votes_pub_x, dao_votes_pub_y) = self.dao.votes_public_key.xy();
+        let (dao_exec_pub_x, dao_exec_pub_y) = self.dao.exec_public_key.xy();
+        let (dao_early_exec_pub_x, dao_early_exec_pub_y) = self.dao.early_exec_public_key.xy();
 
         let input_user_data_enc =
             poseidon_hash([self.dao.to_bulla().inner(), self.input_user_data_blind.inner()]);
@@ -142,10 +148,21 @@ impl DaoAuthMoneyTransferCall {
             // DAO params
             Witness::Base(Value::known(dao_proposer_limit)),
             Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_early_exec_quorum)),
             Witness::Base(Value::known(dao_approval_ratio_quot)),
             Witness::Base(Value::known(dao_approval_ratio_base)),
             Witness::Base(Value::known(self.dao.gov_token_id.inner())),
-            Witness::EcNiPoint(Value::known(dao_public_key)),
+            Witness::EcNiPoint(Value::known(dao_notes_public_key)),
+            Witness::Base(Value::known(dao_proposer_pub_x)),
+            Witness::Base(Value::known(dao_proposer_pub_y)),
+            Witness::Base(Value::known(dao_proposals_pub_x)),
+            Witness::Base(Value::known(dao_proposals_pub_y)),
+            Witness::Base(Value::known(dao_votes_pub_x)),
+            Witness::Base(Value::known(dao_votes_pub_y)),
+            Witness::Base(Value::known(dao_exec_pub_x)),
+            Witness::Base(Value::known(dao_exec_pub_y)),
+            Witness::Base(Value::known(dao_early_exec_pub_x)),
+            Witness::Base(Value::known(dao_early_exec_pub_y)),
             Witness::Base(Value::known(self.dao.bulla_blind.inner())),
             // Dao input user data blind
             Witness::Base(Value::known(self.input_user_data_blind.inner())),

+ 34 - 6
src/contract/dao/src/client/exec.rs

@@ -49,6 +49,8 @@ pub struct DaoExecCall {
 impl DaoExecCall {
     pub fn make(
         self,
+        dao_exec_secret_key: &SecretKey,
+        dao_early_exec_secret_key: &Option<SecretKey>,
         exec_zkbin: &ZkBinary,
         exec_pk: &ProvingKey,
     ) -> Result<(DaoExecParams, Vec<Proof>)> {
@@ -57,10 +59,13 @@ impl DaoExecCall {
 
         let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
         let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
         let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
         let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
-
-        let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
+        let (dao_notes_pub_x, dao_notes_pub_y) = self.dao.notes_public_key.xy();
+        let (dao_proposer_pub_x, dao_proposer_pub_y) = self.dao.proposer_public_key.xy();
+        let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
+        let (dao_votes_pub_x, dao_votes_pub_y) = self.dao.votes_public_key.xy();
 
         let dao_bulla = self.dao.to_bulla();
         if dao_bulla != self.proposal.dao_bulla {
@@ -80,7 +85,7 @@ impl DaoExecCall {
 
         let current_blockwindow = pallas::Base::from(self.current_blockwindow);
 
-        let prover_witnesses = vec![
+        let mut prover_witnesses = vec![
             // Proposal params
             Witness::Base(Value::known(proposal_auth_calls_commit)),
             Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))),
@@ -90,11 +95,33 @@ impl DaoExecCall {
             // DAO params
             Witness::Base(Value::known(dao_proposer_limit)),
             Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_early_exec_quorum)),
             Witness::Base(Value::known(dao_approval_ratio_quot)),
             Witness::Base(Value::known(dao_approval_ratio_base)),
             Witness::Base(Value::known(self.dao.gov_token_id.inner())),
-            Witness::Base(Value::known(dao_pub_x)),
-            Witness::Base(Value::known(dao_pub_y)),
+            Witness::Base(Value::known(dao_notes_pub_x)),
+            Witness::Base(Value::known(dao_notes_pub_y)),
+            Witness::Base(Value::known(dao_proposer_pub_x)),
+            Witness::Base(Value::known(dao_proposer_pub_y)),
+            Witness::Base(Value::known(dao_proposals_pub_x)),
+            Witness::Base(Value::known(dao_proposals_pub_y)),
+            Witness::Base(Value::known(dao_votes_pub_x)),
+            Witness::Base(Value::known(dao_votes_pub_y)),
+            Witness::Base(Value::known(dao_exec_secret_key.inner())),
+        ];
+        // Early exec key
+        match dao_early_exec_secret_key {
+            Some(dao_early_exec_secret_key) => prover_witnesses
+                .push(Witness::Base(Value::known(dao_early_exec_secret_key.inner()))),
+            None => {
+                let (dao_early_exec_pub_x, dao_early_exec_pub_y) =
+                    self.dao.early_exec_public_key.xy();
+                prover_witnesses.push(Witness::Base(Value::known(dao_early_exec_pub_x)));
+                prover_witnesses.push(Witness::Base(Value::known(dao_early_exec_pub_y)));
+            }
+        };
+        // Rest witnesses
+        prover_witnesses.extend_from_slice(&[
             Witness::Base(Value::known(self.dao.bulla_blind.inner())),
             // Votes
             Witness::Base(Value::known(pallas::Base::from(self.yes_vote_value))),
@@ -105,7 +132,7 @@ impl DaoExecCall {
             Witness::Base(Value::known(current_blockwindow)),
             // Signature secret
             Witness::Base(Value::known(self.signature_secret.inner())),
-        ];
+        ]);
 
         debug!(target: "contract::dao::client::exec", "proposal_bulla: {:?}", proposal_bulla);
         let public_inputs = vec![
@@ -129,6 +156,7 @@ impl DaoExecCall {
             proposal_bulla,
             proposal_auth_calls: self.proposal.auth_calls,
             blind_total_vote: DaoBlindAggregateVote { yes_vote_commit, all_vote_commit },
+            early_exec: dao_early_exec_secret_key.is_some(),
             signature_public,
         };
 

+ 29 - 16
src/contract/dao/src/client/mint.rs

@@ -17,7 +17,7 @@
  */
 
 use darkfi::{
-    zk::{halo2, Proof, ProvingKey, Witness, ZkCircuit},
+    zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
     zkas::ZkBinary,
     Result,
 };
@@ -27,31 +27,44 @@ use rand::rngs::OsRng;
 
 use crate::model::{Dao, DaoMintParams};
 
+#[allow(clippy::too_many_arguments)]
 pub fn make_mint_call(
     dao: &Dao,
-    dao_secret_key: &SecretKey,
+    dao_notes_secret_key: &SecretKey,
+    dao_proposer_secret_key: &SecretKey,
+    dao_proposals_secret_key: &SecretKey,
+    dao_votes_secret_key: &SecretKey,
+    dao_exec_secret_key: &SecretKey,
+    dao_early_exec_secret_key: &SecretKey,
     dao_mint_zkbin: &ZkBinary,
     dao_mint_pk: &ProvingKey,
 ) -> Result<(DaoMintParams, Vec<Proof>)> {
-    debug!(target: "contract::dao::client::mint", "Building DAO contract mint transaction");
+    debug!(target: "contract::dao::client::mint", "Building DAO contract mint transaction call");
 
-    let dao_proposer_limit = pallas::Base::from(dao.proposer_limit);
-    let dao_quorum = pallas::Base::from(dao.quorum);
-    let dao_approval_ratio_quot = pallas::Base::from(dao.approval_ratio_quot);
-    let dao_approval_ratio_base = pallas::Base::from(dao.approval_ratio_base);
+    let proposer_limit = pallas::Base::from(dao.proposer_limit);
+    let quorum = pallas::Base::from(dao.quorum);
+    let early_exec_quorum = pallas::Base::from(dao.early_exec_quorum);
+    let approval_ratio_quot = pallas::Base::from(dao.approval_ratio_quot);
+    let approval_ratio_base = pallas::Base::from(dao.approval_ratio_base);
 
     // NOTE: It's important to keep these in the same order as the zkas code.
     let prover_witnesses = vec![
-        Witness::Base(halo2::Value::known(dao_proposer_limit)),
-        Witness::Base(halo2::Value::known(dao_quorum)),
-        Witness::Base(halo2::Value::known(dao_approval_ratio_quot)),
-        Witness::Base(halo2::Value::known(dao_approval_ratio_base)),
-        Witness::Base(halo2::Value::known(dao.gov_token_id.inner())),
-        Witness::Base(halo2::Value::known(dao_secret_key.inner())),
-        Witness::Base(halo2::Value::known(dao.bulla_blind.inner())),
+        Witness::Base(Value::known(proposer_limit)),
+        Witness::Base(Value::known(quorum)),
+        Witness::Base(Value::known(early_exec_quorum)),
+        Witness::Base(Value::known(approval_ratio_quot)),
+        Witness::Base(Value::known(approval_ratio_base)),
+        Witness::Base(Value::known(dao.gov_token_id.inner())),
+        Witness::Base(Value::known(dao_notes_secret_key.inner())),
+        Witness::Base(Value::known(dao_proposer_secret_key.inner())),
+        Witness::Base(Value::known(dao_proposals_secret_key.inner())),
+        Witness::Base(Value::known(dao_votes_secret_key.inner())),
+        Witness::Base(Value::known(dao_exec_secret_key.inner())),
+        Witness::Base(Value::known(dao_early_exec_secret_key.inner())),
+        Witness::Base(Value::known(dao.bulla_blind.inner())),
     ];
 
-    let (pub_x, pub_y) = dao.public_key.xy();
+    let (pub_x, pub_y) = dao.notes_public_key.xy();
     let dao_bulla = dao.to_bulla();
     let public = vec![pub_x, pub_y, dao_bulla.inner()];
 
@@ -59,7 +72,7 @@ pub fn make_mint_call(
     let circuit = ZkCircuit::new(prover_witnesses, dao_mint_zkbin);
     let proof = Proof::create(dao_mint_pk, &[circuit], &public, &mut OsRng)?;
 
-    let dao_mint_params = DaoMintParams { dao_bulla, dao_pubkey: dao.public_key };
+    let dao_mint_params = DaoMintParams { dao_bulla, dao_pubkey: dao.notes_public_key };
 
     Ok((dao_mint_params, vec![proof]))
 }

+ 22 - 5
src/contract/dao/src/client/propose.rs

@@ -65,6 +65,7 @@ pub struct DaoProposeCall<'a, T: StorageAdapter<Value = pallas::Base>> {
 impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
     pub fn make(
         self,
+        dao_proposer_secret_key: &SecretKey,
         burn_zkbin: &ZkBinary,
         burn_pk: &ProvingKey,
         main_zkbin: &ZkBinary,
@@ -182,9 +183,14 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
 
         let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
         let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
         let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
         let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
-        let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
+        let (dao_notes_pub_x, dao_notes_pub_y) = self.dao.notes_public_key.xy();
+        let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
+        let (dao_votes_pub_x, dao_votes_pub_y) = self.dao.votes_public_key.xy();
+        let (dao_exec_pub_x, dao_exec_pub_y) = self.dao.exec_public_key.xy();
+        let (dao_early_exec_pub_x, dao_early_exec_pub_y) = self.dao.early_exec_public_key.xy();
 
         let dao_leaf_position: u64 = self.dao_leaf_position.into();
 
@@ -199,7 +205,7 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
             Witness::Scalar(Value::known(total_funds_blinds.inner())),
             // Used for blinding exported gov token ID
             Witness::Base(Value::known(gov_token_blind.inner())),
-            // proposal params
+            // Proposal params
             Witness::Base(Value::known(self.proposal.auth_calls.commit())),
             Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))),
             Witness::Base(Value::known(pallas::Base::from(self.proposal.duration_blockwindows))),
@@ -208,11 +214,21 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
             // DAO params
             Witness::Base(Value::known(dao_proposer_limit)),
             Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_early_exec_quorum)),
             Witness::Base(Value::known(dao_approval_ratio_quot)),
             Witness::Base(Value::known(dao_approval_ratio_base)),
             Witness::Base(Value::known(self.dao.gov_token_id.inner())),
-            Witness::Base(Value::known(dao_pub_x)),
-            Witness::Base(Value::known(dao_pub_y)),
+            Witness::Base(Value::known(dao_notes_pub_x)),
+            Witness::Base(Value::known(dao_notes_pub_y)),
+            Witness::Base(Value::known(dao_proposer_secret_key.inner())),
+            Witness::Base(Value::known(dao_proposals_pub_x)),
+            Witness::Base(Value::known(dao_proposals_pub_y)),
+            Witness::Base(Value::known(dao_votes_pub_x)),
+            Witness::Base(Value::known(dao_votes_pub_y)),
+            Witness::Base(Value::known(dao_exec_pub_x)),
+            Witness::Base(Value::known(dao_exec_pub_y)),
+            Witness::Base(Value::known(dao_early_exec_pub_x)),
+            Witness::Base(Value::known(dao_early_exec_pub_y)),
             Witness::Base(Value::known(self.dao.bulla_blind.inner())),
             Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
             Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
@@ -232,7 +248,8 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'_, T> {
         proofs.push(main_proof);
 
         let enc_note =
-            AeadEncryptedNote::encrypt(&self.proposal, &self.dao.public_key, &mut OsRng).unwrap();
+            AeadEncryptedNote::encrypt(&self.proposal, &self.dao.proposals_public_key, &mut OsRng)
+                .unwrap();
         let params = DaoProposeParams {
             dao_merkle_root: self.dao_merkle_root,
             proposal_bulla,

+ 24 - 8
src/contract/dao/src/client/vote.rs

@@ -26,7 +26,7 @@ use darkfi_sdk::{
         pedersen_commitment_u64, poseidon_hash,
         smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
         util::fv_mod_fp_unsafe,
-        Blind, Keypair, MerkleNode, PublicKey, SecretKey,
+        Blind, MerkleNode, PublicKey, SecretKey,
     },
     pasta::pallas,
 };
@@ -60,7 +60,6 @@ pub struct DaoVoteCall<'a, T: StorageAdapter<Value = pallas::Base>> {
     pub vote_option: bool,
     pub proposal: DaoProposal,
     pub dao: Dao,
-    pub dao_keypair: Keypair,
     pub current_blockwindow: u64,
 }
 
@@ -72,7 +71,7 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
         main_zkbin: &ZkBinary,
         main_pk: &ProvingKey,
     ) -> Result<(DaoVoteParams, Vec<Proof>)> {
-        debug!(target: "contract::dao::client::vote", "build()");
+        debug!(target: "contract::dao::client::vote", "make()");
 
         if self.dao.to_bulla() != self.proposal.dao_bulla {
             return Err(ClientFailed::VerifyError(DaoError::InvalidCalls.to_string()).into())
@@ -197,7 +196,7 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
 
             //darkfi::zk::export_witness_json("proof/witness/vote-input.json", &prover_witnesses, &public_inputs);
             let circuit = ZkCircuit::new(prover_witnesses, burn_zkbin);
-            debug!(target: "dao", "input_proof Proof::create()");
+            debug!(target: "contract::dao::client::vote", "input_proof Proof::create()");
             let input_proof = Proof::create(burn_pk, &[circuit], &public_inputs, &mut OsRng)?;
             proofs.push(input_proof);
 
@@ -213,9 +212,15 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
 
         let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
         let dao_quorum = pallas::Base::from(self.dao.quorum);
+        let dao_early_exec_quorum = pallas::Base::from(self.dao.early_exec_quorum);
         let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
         let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
-        let dao_public_key = self.dao.public_key.inner();
+        let (dao_notes_pub_x, dao_notes_pub_y) = self.dao.notes_public_key.xy();
+        let (dao_proposer_pub_x, dao_proposer_pub_y) = self.dao.proposer_public_key.xy();
+        let (dao_proposals_pub_x, dao_proposals_pub_y) = self.dao.proposals_public_key.xy();
+        let dao_votes_public_key = self.dao.votes_public_key.inner();
+        let (dao_exec_pub_x, dao_exec_pub_y) = self.dao.exec_public_key.xy();
+        let (dao_early_exec_pub_x, dao_early_exec_pub_y) = self.dao.early_exec_public_key.xy();
 
         let vote_option = self.vote_option as u64;
         if vote_option != 0 && vote_option != 1 {
@@ -262,10 +267,21 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
             // DAO params
             Witness::Base(Value::known(dao_proposer_limit)),
             Witness::Base(Value::known(dao_quorum)),
+            Witness::Base(Value::known(dao_early_exec_quorum)),
             Witness::Base(Value::known(dao_approval_ratio_quot)),
             Witness::Base(Value::known(dao_approval_ratio_base)),
             Witness::Base(Value::known(self.dao.gov_token_id.inner())),
-            Witness::EcNiPoint(Value::known(dao_public_key)),
+            Witness::Base(Value::known(dao_notes_pub_x)),
+            Witness::Base(Value::known(dao_notes_pub_y)),
+            Witness::Base(Value::known(dao_proposer_pub_x)),
+            Witness::Base(Value::known(dao_proposer_pub_y)),
+            Witness::Base(Value::known(dao_proposals_pub_x)),
+            Witness::Base(Value::known(dao_proposals_pub_y)),
+            Witness::EcNiPoint(Value::known(dao_votes_public_key)),
+            Witness::Base(Value::known(dao_exec_pub_x)),
+            Witness::Base(Value::known(dao_exec_pub_y)),
+            Witness::Base(Value::known(dao_early_exec_pub_x)),
+            Witness::Base(Value::known(dao_early_exec_pub_y)),
             Witness::Base(Value::known(self.dao.bulla_blind.inner())),
             // Vote
             Witness::Base(Value::known(vote_option)),
@@ -283,7 +299,7 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
 
         let note = [vote_option, yes_vote_blind.inner(), all_vote_value_fp, all_vote_blind.inner()];
         let enc_note =
-            ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao_keypair.public)?;
+            ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.votes_public_key)?;
 
         let public_inputs = vec![
             token_commit,
@@ -304,7 +320,7 @@ impl<T: StorageAdapter<Value = pallas::Base>> DaoVoteCall<'_, T> {
         //darkfi::zk::export_witness_json("proof/witness/vote-main.json", &prover_witnesses, &public_inputs);
         let circuit = ZkCircuit::new(prover_witnesses, main_zkbin);
 
-        debug!(target: "dao", "main_proof = Proof::create()");
+        debug!(target: "contract::dao::client::vote", "main_proof = Proof::create()");
         let main_proof = Proof::create(main_pk, &[circuit], &public_inputs, &mut OsRng)?;
         proofs.push(main_proof);
 

+ 2 - 2
src/contract/dao/src/entrypoint/auth_xfer.rs

@@ -38,7 +38,7 @@ use crate::{
     DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS,
 };
 
-/// `get_metdata` function for `Dao::Exec`
+/// `get_metdata` function for `Dao::AuthMoneyTransfer`
 pub(crate) fn dao_authxfer_get_metadata(
     _cid: ContractId,
     call_idx: usize,
@@ -134,7 +134,7 @@ fn find_auth_in_parent(
     None
 }
 
-/// `process_instruction` function for `Dao::Exec`
+/// `process_instruction` function for `Dao::AuthMoneyTransfer`
 pub(crate) fn dao_authxfer_process_instruction(
     _cid: ContractId,
     call_idx: usize,

+ 9 - 2
src/contract/dao/src/entrypoint/exec.rs

@@ -30,7 +30,8 @@ use crate::{
     blockwindow,
     error::DaoError,
     model::{DaoExecParams, DaoExecUpdate, DaoProposalMetadata, VecAuthCallCommit},
-    DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+    DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS,
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
 };
 
 /// `get_metdata` function for `Dao::Exec`
@@ -54,8 +55,14 @@ pub(crate) fn dao_exec_get_metadata(
     let yes_vote_coords = blind_vote.yes_vote_commit.to_affine().coordinates().unwrap();
     let all_vote_coords = blind_vote.all_vote_commit.to_affine().coordinates().unwrap();
 
+    // Grab proof namespace to use, based on early execution flag
+    let proof_namespace = match params.early_exec {
+        true => DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS.to_string(),
+        false => DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
+    };
+
     zk_public_inputs.push((
-        DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
+        proof_namespace,
         vec![
             params.proposal_bulla.inner(),
             params.proposal_auth_calls.commit(),

+ 1 - 0
src/contract/dao/src/entrypoint/mod.rs

@@ -74,6 +74,7 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     wasm::db::zkas_db_set(&include_bytes!("../../proof/vote-input.zk.bin")[..])?;
     wasm::db::zkas_db_set(&include_bytes!("../../proof/vote-main.zk.bin")[..])?;
     wasm::db::zkas_db_set(&include_bytes!("../../proof/exec.zk.bin")[..])?;
+    wasm::db::zkas_db_set(&include_bytes!("../../proof/early-exec.zk.bin")[..])?;
     wasm::db::zkas_db_set(&include_bytes!("../../proof/auth-money-transfer.zk.bin")[..])?;
     wasm::db::zkas_db_set(&include_bytes!("../../proof/auth-money-transfer-enc-coin.zk.bin")[..])?;
 

+ 2 - 0
src/contract/dao/src/lib.rs

@@ -84,6 +84,8 @@ pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS: &str = "ProposeInput";
 pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS: &str = "ProposeMain";
 /// zkas dao exec circuit namespace
 pub const DAO_CONTRACT_ZKAS_DAO_EXEC_NS: &str = "Exec";
+/// zkas dao early exec circuit namespace
+pub const DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS: &str = "EarlyExec";
 /// zkas dao auth money_transfer circuit namespace
 pub const DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS: &str = "AuthMoneyTransfer";
 /// zkas dao auth money_transfer encrypted coin circuit namespace

+ 40 - 6
src/contract/dao/src/model.rs

@@ -41,13 +41,27 @@ pub struct Dao {
     pub proposer_limit: u64,
     /// Minimal threshold of participating total tokens needed for a proposal to pass
     pub quorum: u64,
+    /// Minimal threshold of participating total tokens needed for a proposal to
+    /// be considered as strongly supported, enabling early execution.
+    /// Must be greater or equal to normal quorum.
+    pub early_exec_quorum: u64,
     /// The ratio of winning/total votes needed for a proposal to pass
     pub approval_ratio_quot: u64,
     pub approval_ratio_base: u64,
     /// DAO's governance token ID
     pub gov_token_id: TokenId,
-    /// Public key of the DAO
-    pub public_key: PublicKey,
+    /// DAO notes decryption public key
+    pub notes_public_key: PublicKey,
+    /// DAO proposals creator public key
+    pub proposer_public_key: PublicKey,
+    /// DAO proposals viewer public key
+    pub proposals_public_key: PublicKey,
+    /// DAO votes viewer public key
+    pub votes_public_key: PublicKey,
+    /// DAO proposals executor public key
+    pub exec_public_key: PublicKey,
+    /// DAO strongly supported proposals executor public key
+    pub early_exec_public_key: PublicKey,
     /// DAO bulla blind
     pub bulla_blind: BaseBlind,
 }
@@ -57,17 +71,34 @@ impl Dao {
     pub fn to_bulla(&self) -> DaoBulla {
         let proposer_limit = pallas::Base::from(self.proposer_limit);
         let quorum = pallas::Base::from(self.quorum);
+        let early_exec_quorum = pallas::Base::from(self.early_exec_quorum);
         let approval_ratio_quot = pallas::Base::from(self.approval_ratio_quot);
         let approval_ratio_base = pallas::Base::from(self.approval_ratio_base);
-        let (pub_x, pub_y) = self.public_key.xy();
+        let (notes_pub_x, notes_pub_y) = self.notes_public_key.xy();
+        let (proposer_pub_x, proposer_pub_y) = self.proposer_public_key.xy();
+        let (proposals_pub_x, proposals_pub_y) = self.proposals_public_key.xy();
+        let (votes_pub_x, votes_pub_y) = self.votes_public_key.xy();
+        let (exec_pub_x, exec_pub_y) = self.exec_public_key.xy();
+        let (early_exec_pub_x, early_exec_pub_y) = self.early_exec_public_key.xy();
         let bulla = poseidon_hash([
             proposer_limit,
             quorum,
+            early_exec_quorum,
             approval_ratio_quot,
             approval_ratio_base,
             self.gov_token_id.inner(),
-            pub_x,
-            pub_y,
+            notes_pub_x,
+            notes_pub_y,
+            proposer_pub_x,
+            proposer_pub_y,
+            proposals_pub_x,
+            proposals_pub_y,
+            votes_pub_x,
+            votes_pub_y,
+            exec_pub_x,
+            exec_pub_y,
+            early_exec_pub_x,
+            early_exec_pub_y,
             self.bulla_blind.inner(),
         ]);
         DaoBulla(bulla)
@@ -215,7 +246,7 @@ darkfi_sdk::ty_from_fp!(DaoProposalBulla);
 pub struct DaoMintParams {
     /// The DAO bulla
     pub dao_bulla: DaoBulla,
-    /// The DAO public key
+    /// The DAO signature(notes) public key
     pub dao_pubkey: PublicKey,
 }
 // ANCHOR_END: dao-mint-params
@@ -357,9 +388,12 @@ impl Default for DaoBlindAggregateVote {
 pub struct DaoExecParams {
     /// The proposal bulla
     pub proposal_bulla: DaoProposalBulla,
+    /// The proposal auth calls
     pub proposal_auth_calls: Vec<DaoAuthCall>,
     /// Aggregated blinds for the vote commitments
     pub blind_total_vote: DaoBlindAggregateVote,
+    /// Flag indicating if its early execution
+    pub early_exec: bool,
     /// Public key for the signature.
     /// The signature ensures this DAO::exec call cannot be modified with other calls.
     pub signature_public: PublicKey,

+ 173 - 51
src/contract/dao/tests/integration.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi::Result;
+use darkfi::{util::pcg::Pcg32, Result};
 use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
 use darkfi_dao_contract::{
     blockwindow,
@@ -32,7 +32,7 @@ use darkfi_sdk::{
         pasta_prelude::*,
         pedersen_commitment_u64, poseidon_hash,
         util::{fp_mod_fv, fp_to_u64},
-        BaseBlind, Blind, FuncId, FuncRef, Keypair, ScalarBlind, DAO_CONTRACT_ID,
+        BaseBlind, Blind, FuncId, FuncRef, Keypair, ScalarBlind, SecretKey, DAO_CONTRACT_ID,
         MONEY_CONTRACT_ID,
     },
     pasta::pallas,
@@ -56,6 +56,7 @@ const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
 // DAO parameters configuration
 const PROPOSER_LIMIT: u64 = 100_000_000;
 const QUORUM: u64 = 200_000_000;
+const EARLY_EXEC_QUORUM: u64 = 200_000_000;
 const APPROVAL_RATIO_BASE: u64 = 2;
 const APPROVAL_RATIO_QUOT: u64 = 1;
 const PROPOSAL_DURATION_BLOCKWINDOW: u64 = 1;
@@ -92,14 +93,26 @@ fn integration_test() -> Result<()> {
         let mut current_block_height = 0;
 
         // DAO parameters
-        let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
+        let dao_notes_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
+        let mut rng = Pcg32::new(42);
+        let dao_proposer_keypair = Keypair::random(&mut rng);
+        let dao_proposals_keypair = Keypair::random(&mut rng);
+        let dao_votes_keypair = Keypair::random(&mut rng);
+        let dao_exec_keypair = Keypair::random(&mut rng);
+        let dao_early_exec_keypair = Keypair::random(&mut rng);
         let dao = Dao {
             proposer_limit: PROPOSER_LIMIT,
             quorum: QUORUM,
+            early_exec_quorum: EARLY_EXEC_QUORUM,
             approval_ratio_base: APPROVAL_RATIO_BASE,
             approval_ratio_quot: APPROVAL_RATIO_QUOT,
             gov_token_id,
-            public_key: dao_keypair.public,
+            notes_public_key: dao_notes_keypair.public,
+            proposer_public_key: dao_proposer_keypair.public,
+            proposals_public_key: dao_proposals_keypair.public,
+            votes_public_key: dao_votes_keypair.public,
+            exec_public_key: dao_exec_keypair.public,
+            early_exec_public_key: dao_early_exec_keypair.public,
             bulla_blind: Blind::random(&mut OsRng),
         };
 
@@ -146,8 +159,19 @@ fn integration_test() -> Result<()> {
         // Create the DAO bulla
         // ====================
         info!("[Dao] Building DAO mint tx");
-        let (dao_mint_tx, dao_mint_params, fee_params) =
-            th.dao_mint(&Holder::Alice, &dao, &dao_keypair, current_block_height).await?;
+        let (dao_mint_tx, dao_mint_params, fee_params) = th
+            .dao_mint(
+                &Holder::Alice,
+                &dao,
+                &dao_notes_keypair.secret,
+                &dao_proposer_keypair.secret,
+                &dao_proposals_keypair.secret,
+                &dao_votes_keypair.secret,
+                &dao_exec_keypair.secret,
+                &dao_early_exec_keypair.secret,
+                current_block_height,
+            )
+            .await?;
 
         for holder in &HOLDERS {
             info!("[{holder:?}] Executing DAO Mint tx");
@@ -169,7 +193,7 @@ fn integration_test() -> Result<()> {
         // ======================================
         // Mint the governance token to 3 holders
         // ======================================
-        info!("[Alice] Building governance token mint tx for Alice");
+        info!("[Dao] Building governance token mint tx for Alice");
         let (a_token_mint_tx, a_token_mint_params, a_auth_token_mint_params, a_fee_params) = th
             .token_mint(
                 ALICE_GOV_SUPPLY,
@@ -203,7 +227,7 @@ fn integration_test() -> Result<()> {
         assert!(_alice_tokens[0].note.token_id == gov_token_id);
         assert!(_alice_tokens[0].note.value == ALICE_GOV_SUPPLY);
 
-        info!("[Alice] Building governance token mint tx for Bob");
+        info!("[Dao] Building governance token mint tx for Bob");
         let (b_token_mint_tx, b_token_mint_params, b_auth_token_mint_params, b_fee_params) = th
             .token_mint(
                 BOB_GOV_SUPPLY,
@@ -237,7 +261,7 @@ fn integration_test() -> Result<()> {
         assert!(_bob_tokens[0].note.token_id == gov_token_id);
         assert!(_bob_tokens[0].note.value == BOB_GOV_SUPPLY);
 
-        info!("[Alice] Building governance token mint tx for Charlie");
+        info!("[Dao] Building governance token mint tx for Charlie");
         let (c_token_mint_tx, c_token_mint_params, c_auth_token_mint_params, c_fee_params) = th
             .token_mint(
                 CHARLIE_GOV_SUPPLY,
@@ -280,17 +304,98 @@ fn integration_test() -> Result<()> {
         // ============================
         // Execute proposals test cases
         // ============================
+        info!("[Dao] DAO transfer proposal tx test case");
         execute_transfer_proposal(
             &mut th,
             &dao,
-            &dao_keypair,
+            &dao_proposer_keypair.secret,
+            &dao_votes_keypair.secret,
+            &dao_exec_keypair.secret,
+            &None,
             user_data,
             &mut current_block_height,
+            0,
+            TRANSFER_PROPOSAL_AMOUNT,
+            TRANSFER_PROPOSAL_AMOUNT,
         )
         .await?;
-        execute_generic_proposal(&mut th, &dao, &dao_keypair, user_data, &mut current_block_height)
+
+        info!("[Dao] DAO early execution transfer proposal tx test case");
+        execute_transfer_proposal(
+            &mut th,
+            &dao,
+            &dao_proposer_keypair.secret,
+            &dao_votes_keypair.secret,
+            &dao_exec_keypair.secret,
+            &Some(dao_early_exec_keypair.secret),
+            user_data,
+            &mut current_block_height,
+            1,
+            TRANSFER_PROPOSAL_AMOUNT,
+            TRANSFER_PROPOSAL_AMOUNT * 2,
+        )
+        .await?;
+
+        info!("[Dao] DAO generic proposal tx test case");
+        execute_generic_proposal(
+            &mut th,
+            &dao,
+            &dao_proposer_keypair.secret,
+            &dao_votes_keypair.secret,
+            &dao_exec_keypair.secret,
+            &None,
+            user_data,
+            &mut current_block_height,
+        )
+        .await?;
+
+        // Now we will execute a random money transaction,
+        // to update our merkle tree so our snapshot is fresh.
+        info!("[Dao] Building governance token mint tx for Alice");
+        let (a_token_mint_tx, a_token_mint_params, a_auth_token_mint_params, a_fee_params) = th
+            .token_mint(
+                ALICE_GOV_SUPPLY,
+                &Holder::Alice,
+                &Holder::Alice,
+                gov_token_blind,
+                None,
+                None,
+                current_block_height,
+            )
             .await?;
 
+        for holder in &HOLDERS {
+            info!("[{holder:?}] Executing governance token mint tx for Alice");
+            th.execute_token_mint_tx(
+                holder,
+                a_token_mint_tx.clone(),
+                &a_token_mint_params,
+                &a_auth_token_mint_params,
+                &a_fee_params,
+                current_block_height,
+                true,
+            )
+            .await?;
+        }
+
+        th.assert_trees(&HOLDERS);
+
+        current_block_height += 1;
+
+        // Now we can continue our test cases
+        info!("[Dao] DAO early execution generic proposal tx test case");
+        execute_generic_proposal(
+            &mut th,
+            &dao,
+            &dao_proposer_keypair.secret,
+            &dao_votes_keypair.secret,
+            &dao_exec_keypair.secret,
+            &Some(dao_early_exec_keypair.secret),
+            user_data,
+            &mut current_block_height,
+        )
+        .await?;
+
         // Thanks for reading
         Ok(())
     })
@@ -298,24 +403,31 @@ fn integration_test() -> Result<()> {
 
 /// Test case:
 /// Generate a transfer proposal and execute it after voting passes.
+#[allow(clippy::too_many_arguments)]
 async fn execute_transfer_proposal(
     th: &mut TestHarness,
     dao: &Dao,
-    dao_keypair: &Keypair,
+    dao_proposer_secret_key: &SecretKey,
+    dao_votes_secret_key: &SecretKey,
+    dao_exec_secret_key: &SecretKey,
+    dao_early_exec_secret_key: &Option<SecretKey>,
     user_data: pallas::Base,
     current_block_height: &mut u32,
+    transfer_token_index: usize,
+    transfer_amount: u64,
+    dao_treasury_decrease: u64,
 ) -> Result<()> {
     // ================
     // Dao::Propose
     // Propose the vote
     // ================
-    info!("[Alice] Building DAO transfer proposal tx");
+    info!("[Dao] Building DAO transfer proposal tx");
 
     // These coins are passed around to all DAO members who verify its validity
     // They also check hashing them equals the proposal_commit
     let proposal_coinattrs = vec![CoinAttributes {
         public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
-        value: TRANSFER_PROPOSAL_AMOUNT,
+        value: transfer_amount,
         token_id: *DARK_TOKEN_ID,
         spend_hook: FuncId::none(),
         user_data: pallas::Base::ZERO,
@@ -333,6 +445,7 @@ async fn execute_transfer_proposal(
             &proposal_coinattrs,
             user_data,
             dao,
+            dao_proposer_secret_key,
             *current_block_height,
             PROPOSAL_DURATION_BLOCKWINDOW,
         )
@@ -358,19 +471,16 @@ async fn execute_transfer_proposal(
     // Proposal is accepted. Start the vote.
     // =====================================
     info!("[Alice] Building transfer vote tx (yes)");
-    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) = th
-        .dao_vote(&Holder::Alice, true, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) =
+        th.dao_vote(&Holder::Alice, true, dao, &proposal_info, *current_block_height).await?;
 
     info!("[Bob] Building transfer vote tx (no)");
-    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) = th
-        .dao_vote(&Holder::Bob, false, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) =
+        th.dao_vote(&Holder::Bob, false, dao, &proposal_info, *current_block_height).await?;
 
     info!("[Charlie] Building transfer vote tx (yes)");
-    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) = th
-        .dao_vote(&Holder::Charlie, true, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) =
+        th.dao_vote(&Holder::Charlie, true, dao, &proposal_info, *current_block_height).await?;
 
     for holder in &HOLDERS {
         info!("[{holder:?}] Executing Alice transfer vote tx");
@@ -406,9 +516,9 @@ async fn execute_transfer_proposal(
     th.assert_trees(&HOLDERS);
 
     // Gather and decrypt all generic vote notes
-    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
+    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
+    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
 
     // Count the votes
     let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
@@ -419,10 +529,12 @@ async fn execute_transfer_proposal(
         ]);
 
     // Wait until proposal has expired
-    let mut current_blockwindow = creation_blockwindow;
-    while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW + 1 {
-        *current_block_height += 1;
-        current_blockwindow = blockwindow(*current_block_height, block_target);
+    if dao_early_exec_secret_key.is_none() {
+        let mut current_blockwindow = creation_blockwindow;
+        while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW {
+            *current_block_height += 1;
+            current_blockwindow = blockwindow(*current_block_height, block_target);
+        }
     }
 
     // ================
@@ -434,6 +546,8 @@ async fn execute_transfer_proposal(
         .dao_exec_transfer(
             &Holder::Alice,
             dao,
+            dao_exec_secret_key,
+            dao_early_exec_secret_key,
             &proposal_info,
             proposal_coinattrs,
             total_yes_vote_value,
@@ -460,12 +574,14 @@ async fn execute_transfer_proposal(
     *current_block_height += 1;
 
     let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
-    assert!(rachel_wallet.unspent_money_coins[0].note.value == TRANSFER_PROPOSAL_AMOUNT);
-    assert!(rachel_wallet.unspent_money_coins[0].note.token_id == *DARK_TOKEN_ID);
+    assert!(rachel_wallet.unspent_money_coins[transfer_token_index].note.value == transfer_amount);
+    assert!(
+        rachel_wallet.unspent_money_coins[transfer_token_index].note.token_id == *DARK_TOKEN_ID
+    );
 
     let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
     assert!(
-        dao_wallet.unspent_money_coins[0].note.value == DRK_TOKEN_SUPPLY - TRANSFER_PROPOSAL_AMOUNT
+        dao_wallet.unspent_money_coins[0].note.value == DRK_TOKEN_SUPPLY - dao_treasury_decrease
     );
     assert!(dao_wallet.unspent_money_coins[0].note.token_id == *DARK_TOKEN_ID);
 
@@ -474,10 +590,14 @@ async fn execute_transfer_proposal(
 
 /// Test case:
 /// Generate a generic proposal and execute it after voting passes.
+#[allow(clippy::too_many_arguments)]
 async fn execute_generic_proposal(
     th: &mut TestHarness,
     dao: &Dao,
-    dao_keypair: &Keypair,
+    dao_proposer_secret_key: &SecretKey,
+    dao_votes_secret_key: &SecretKey,
+    dao_exec_secret_key: &SecretKey,
+    dao_early_exec_secret_key: &Option<SecretKey>,
     user_data: pallas::Base,
     current_block_height: &mut u32,
 ) -> Result<()> {
@@ -485,7 +605,7 @@ async fn execute_generic_proposal(
     // Dao::Propose
     // Propose the vote
     // ================
-    info!("[Alice] Building DAO generic proposal tx");
+    info!("[Dao] Building DAO generic proposal tx");
 
     // Grab creation blockwindow
     let block_target =
@@ -497,6 +617,7 @@ async fn execute_generic_proposal(
             &Holder::Alice,
             user_data,
             dao,
+            dao_proposer_secret_key,
             *current_block_height,
             PROPOSAL_DURATION_BLOCKWINDOW,
         )
@@ -522,19 +643,16 @@ async fn execute_generic_proposal(
     // Proposal is accepted. Start the vote.
     // =====================================
     info!("[Alice] Building generic vote tx (yes)");
-    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) = th
-        .dao_vote(&Holder::Alice, true, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (alice_vote_tx, alice_vote_params, alice_vote_fee_params) =
+        th.dao_vote(&Holder::Alice, true, dao, &proposal_info, *current_block_height).await?;
 
     info!("[Bob] Building generic vote tx (no)");
-    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) = th
-        .dao_vote(&Holder::Bob, false, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (bob_vote_tx, bob_vote_params, bob_vote_fee_params) =
+        th.dao_vote(&Holder::Bob, false, dao, &proposal_info, *current_block_height).await?;
 
     info!("[Charlie] Building generic vote tx (no)");
-    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) = th
-        .dao_vote(&Holder::Charlie, true, dao, dao_keypair, &proposal_info, *current_block_height)
-        .await?;
+    let (charlie_vote_tx, charlie_vote_params, charlie_vote_fee_params) =
+        th.dao_vote(&Holder::Charlie, true, dao, &proposal_info, *current_block_height).await?;
 
     for holder in &HOLDERS {
         info!("[{holder:?}] Executing Alice generic vote tx");
@@ -570,9 +688,9 @@ async fn execute_generic_proposal(
     th.assert_trees(&HOLDERS);
 
     // Gather and decrypt all generic vote notes
-    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
-    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
+    let vote_note_1 = alice_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
+    let vote_note_2 = bob_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
+    let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(dao_votes_secret_key).unwrap();
 
     // Count the votes
     let (total_yes_vote_value, total_all_vote_value, total_yes_vote_blind, total_all_vote_blind) =
@@ -583,10 +701,12 @@ async fn execute_generic_proposal(
         ]);
 
     // Wait until proposal has expired
-    let mut current_blockwindow = creation_blockwindow;
-    while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW + 1 {
-        *current_block_height += 1;
-        current_blockwindow = blockwindow(*current_block_height, block_target);
+    if dao_early_exec_secret_key.is_none() {
+        let mut current_blockwindow = creation_blockwindow;
+        while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW {
+            *current_block_height += 1;
+            current_blockwindow = blockwindow(*current_block_height, block_target);
+        }
     }
 
     // ================
@@ -598,6 +718,8 @@ async fn execute_generic_proposal(
         .dao_exec_generic(
             &Holder::Alice,
             dao,
+            dao_exec_secret_key,
+            dao_early_exec_secret_key,
             &proposal_info,
             total_yes_vote_value,
             total_all_vote_value,

+ 26 - 7
src/contract/test-harness/src/dao_exec.rs

@@ -25,7 +25,8 @@ use darkfi_dao_contract::{
     client::{DaoAuthMoneyTransferCall, DaoExecCall},
     model::{Dao, DaoProposal},
     DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
-    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS,
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
 };
 use darkfi_money_contract::{
     client::{transfer_v1 as xfer, MoneyNote, OwnCoin},
@@ -53,6 +54,8 @@ impl TestHarness {
         &mut self,
         holder: &Holder,
         dao: &Dao,
+        dao_exec_secret_key: &SecretKey,
+        dao_early_exec_secret_key: &Option<SecretKey>,
         proposal: &DaoProposal,
         proposal_coinattrs: Vec<CoinAttributes>,
         yes_vote_value: u64,
@@ -66,8 +69,10 @@ impl TestHarness {
         let (mint_pk, mint_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
         let (burn_pk, burn_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
 
-        let (dao_exec_pk, dao_exec_zkbin) =
-            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
+        let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key {
+            Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS).unwrap(),
+            None => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap(),
+        };
         let (dao_auth_xfer_pk, dao_auth_xfer_zkbin) =
             self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS).unwrap();
         let (dao_auth_xfer_enc_coin_pk, dao_auth_xfer_enc_coin_zkbin) =
@@ -160,7 +165,12 @@ impl TestHarness {
             current_blockwindow,
         };
 
-        let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?;
+        let (exec_params, exec_proofs) = exec_builder.make(
+            dao_exec_secret_key,
+            dao_early_exec_secret_key,
+            dao_exec_zkbin,
+            dao_exec_pk,
+        )?;
         let mut data = vec![DaoFunction::Exec as u8];
         exec_params.encode_async(&mut data).await?;
         let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
@@ -248,6 +258,8 @@ impl TestHarness {
         &mut self,
         holder: &Holder,
         dao: &Dao,
+        dao_exec_secret_key: &SecretKey,
+        dao_early_exec_secret_key: &Option<SecretKey>,
         proposal: &DaoProposal,
         yes_vote_value: u64,
         all_vote_value: u64,
@@ -257,8 +269,10 @@ impl TestHarness {
     ) -> Result<(Transaction, Option<MoneyFeeParamsV1>)> {
         let wallet = self.holders.get_mut(holder).unwrap();
 
-        let (dao_exec_pk, dao_exec_zkbin) =
-            self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap();
+        let (dao_exec_pk, dao_exec_zkbin) = match dao_early_exec_secret_key {
+            Some(_) => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS).unwrap(),
+            None => self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap(),
+        };
 
         // Create the exec call
         let exec_signature_secret = SecretKey::random(&mut OsRng);
@@ -274,7 +288,12 @@ impl TestHarness {
             signature_secret: exec_signature_secret,
             current_blockwindow,
         };
-        let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?;
+        let (exec_params, exec_proofs) = exec_builder.make(
+            dao_exec_secret_key,
+            dao_early_exec_secret_key,
+            dao_exec_zkbin,
+            dao_exec_pk,
+        )?;
 
         // Encode the call
         let mut data = vec![DaoFunction::Exec as u8];

+ 22 - 6
src/contract/test-harness/src/dao_mint.rs

@@ -30,7 +30,7 @@ use darkfi_money_contract::{
     model::MoneyFeeParamsV1,
 };
 use darkfi_sdk::{
-    crypto::{contract_id::DAO_CONTRACT_ID, Keypair, MerkleNode},
+    crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey},
     ContractCall,
 };
 use darkfi_serial::AsyncEncodable;
@@ -39,22 +39,38 @@ use log::debug;
 use super::{Holder, TestHarness};
 
 impl TestHarness {
-    /// Create a `Dao::Mint` transaction with the given [`Dao`] info and a keypair.
+    /// Create a `Dao::Mint` transaction with the given [`Dao`] info and keys.
     /// Takes a [`Holder`] for optionally paying the transaction fee.
     ///
     /// Returns the [`Transaction`] and any relevant parameters.
+    #[allow(clippy::too_many_arguments)]
     pub async fn dao_mint(
         &mut self,
         holder: &Holder,
         dao: &Dao,
-        dao_kp: &Keypair,
+        dao_notes_secret_key: &SecretKey,
+        dao_proposer_secret_key: &SecretKey,
+        dao_proposals_secret_key: &SecretKey,
+        dao_votes_secret_key: &SecretKey,
+        dao_exec_secret_key: &SecretKey,
+        dao_early_exec_secret_key: &SecretKey,
         block_height: u32,
     ) -> Result<(Transaction, DaoMintParams, Option<MoneyFeeParamsV1>)> {
         let (dao_mint_pk, dao_mint_zkbin) =
             self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_MINT_NS).unwrap();
 
         // Create the call
-        let (params, proofs) = make_mint_call(dao, &dao_kp.secret, dao_mint_zkbin, dao_mint_pk)?;
+        let (params, proofs) = make_mint_call(
+            dao,
+            dao_notes_secret_key,
+            dao_proposer_secret_key,
+            dao_proposals_secret_key,
+            dao_votes_secret_key,
+            dao_exec_secret_key,
+            dao_early_exec_secret_key,
+            dao_mint_zkbin,
+            dao_mint_pk,
+        )?;
 
         // Encode the call
         let mut data = vec![DaoFunction::Mint as u8];
@@ -67,7 +83,7 @@ impl TestHarness {
         let mut fee_signature_secrets = None;
         if self.verify_fees {
             let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&[dao_kp.secret])?;
+            let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
             tx.signatures = vec![sigs];
 
             let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
@@ -81,7 +97,7 @@ impl TestHarness {
 
         // Now build the actual transaction and sign it with necessary keys.
         let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[dao_kp.secret])?;
+        let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
         tx.signatures = vec![sigs];
         if let Some(fee_signature_secrets) = fee_signature_secrets {
             let sigs = tx.create_sigs(&fee_signature_secrets)?;

+ 5 - 0
src/contract/test-harness/src/dao_propose.rs

@@ -47,12 +47,14 @@ use super::{Holder, TestHarness};
 
 impl TestHarness {
     /// Create a transfer `Dao::Propose` transaction.
+    #[allow(clippy::too_many_arguments)]
     pub async fn dao_propose_transfer(
         &mut self,
         proposer: &Holder,
         proposal_coinattrs: &[CoinAttributes],
         user_data: pallas::Base,
         dao: &Dao,
+        dao_proposer_secret_key: &SecretKey,
         block_height: u32,
         duration_blockwindows: u64,
     ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
@@ -146,6 +148,7 @@ impl TestHarness {
         };
 
         let (params, proofs) = call.make(
+            dao_proposer_secret_key,
             dao_propose_burn_zkbin,
             dao_propose_burn_pk,
             dao_propose_main_zkbin,
@@ -193,6 +196,7 @@ impl TestHarness {
         proposer: &Holder,
         user_data: pallas::Base,
         dao: &Dao,
+        dao_proposer_secret_key: &SecretKey,
         block_height: u32,
         duration_blockwindows: u64,
     ) -> Result<(Transaction, DaoProposeParams, Option<MoneyFeeParamsV1>, DaoProposal)> {
@@ -264,6 +268,7 @@ impl TestHarness {
         };
 
         let (params, proofs) = call.make(
+            dao_proposer_secret_key,
             dao_propose_burn_zkbin,
             dao_propose_burn_pk,
             dao_propose_main_zkbin,

+ 1 - 3
src/contract/test-harness/src/dao_vote.rs

@@ -31,7 +31,7 @@ use darkfi_money_contract::{
     model::MoneyFeeParamsV1,
 };
 use darkfi_sdk::{
-    crypto::{contract_id::DAO_CONTRACT_ID, Keypair, MerkleNode, SecretKey},
+    crypto::{contract_id::DAO_CONTRACT_ID, MerkleNode, SecretKey},
     ContractCall,
 };
 use darkfi_serial::AsyncEncodable;
@@ -47,7 +47,6 @@ impl TestHarness {
         voter: &Holder,
         vote_option: bool,
         dao: &Dao,
-        dao_keypair: &Keypair,
         proposal: &DaoProposal,
         block_height: u32,
     ) -> Result<(Transaction, DaoVoteParams, Option<MoneyFeeParamsV1>)> {
@@ -87,7 +86,6 @@ impl TestHarness {
             vote_option,
             proposal: proposal.clone(),
             dao: dao.clone(),
-            dao_keypair: *dao_keypair,
             current_blockwindow,
         };
 

+ 9 - 7
src/contract/test-harness/src/vks.rs

@@ -31,10 +31,10 @@ use darkfi::{
 };
 use darkfi_dao_contract::{
     DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
-    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
-    DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS,
-    DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS,
-    DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS,
+    DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
+    DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+    DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
 };
 use darkfi_money_contract::{
     MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
@@ -49,8 +49,8 @@ use sled_overlay::sled;
 
 /// Update these if any circuits are changed.
 /// Delete the existing cachefiles, and enable debug logging, you will see the new hashes.
-const PKS_HASH: &str = "55d9535b390a819026bb3554f5de501997b831935bbc910951639fddfec44b14";
-const VKS_HASH: &str = "cbcb356ccacd0ad4ac953417f07bf24297927e61ba770f5aeddaa9e72f159272";
+const PKS_HASH: &str = "46a30a57bd14b6bc5851bbde8b011ba2e12765bba7901c5e42f511bdb68b3255";
+const VKS_HASH: &str = "4e6f5326b3acc7fd4f6525914be8076276b16c5601940d034de0617c94b1170f";
 
 /// Build a `PathBuf` to a cachefile
 fn cache_path(typ: &str) -> Result<PathBuf> {
@@ -134,6 +134,7 @@ pub fn get_cached_pks_and_vks() -> Result<(Pks, Vks)> {
         &include_bytes!("../../dao/proof/vote-input.zk.bin")[..],
         &include_bytes!("../../dao/proof/vote-main.zk.bin")[..],
         &include_bytes!("../../dao/proof/exec.zk.bin")[..],
+        &include_bytes!("../../dao/proof/early-exec.zk.bin")[..],
         &include_bytes!("../../dao/proof/auth-money-transfer.zk.bin")[..],
         &include_bytes!("../../dao/proof/auth-money-transfer-enc-coin.zk.bin")[..],
     ];
@@ -207,7 +208,8 @@ pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> {
             DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS |
             DAO_CONTRACT_ZKAS_DAO_EXEC_NS |
             DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS |
-            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS => {
+            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS |
+            DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS => {
                 let key = serialize(&namespace.as_str());
                 let value = serialize(&(bincode.clone(), vk.clone()));
                 dao_tree.insert(key, value)?;