Jelajahi Sumber

drk/dao: use all the new keys

skoupidi 1 tahun lalu
induk
melakukan
2c1864baa4
5 mengubah file dengan 371 tambahan dan 135 penghapusan
  1. 4 0
      bin/drk/src/cli_util.rs
  2. 308 113
      bin/drk/src/dao.rs
  3. 55 18
      bin/drk/src/main.rs
  4. 2 2
      bin/drk/src/money.rs
  5. 2 2
      src/contract/test-harness/src/vks.rs

+ 4 - 0
bin/drk/src/cli_util.rs

@@ -261,6 +261,9 @@ pub fn generate_completions(shell: &str) -> Result<()> {
     let quorum = Arg::with_name("quorum")
         .help("Minimal threshold of participating total tokens needed for a proposal to pass");
 
+    let early_exec_quorum = Arg::with_name("early-exec-quorum")
+        .help("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.");
+
     let approval_ratio = Arg::with_name("approval-ratio")
         .help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
 
@@ -269,6 +272,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
     let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
         proposer_limit,
         quorum,
+        early_exec_quorum,
         approval_ratio,
         gov_token_id,
     ]);

+ 308 - 113
bin/drk/src/dao.rs

@@ -58,7 +58,7 @@ use darkfi_sdk::{
         poseidon_hash,
         smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
         util::{fp_mod_fv, fp_to_u64},
-        BaseBlind, Blind, FuncId, FuncRef, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
+        BaseBlind, Blind, FuncId, FuncRef, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
         SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
     },
     dark_tree::DarkTree,
@@ -129,45 +129,128 @@ pub const DAO_VOTES_COL_NULLIFIERS: &str = "nullifiers";
 pub struct DaoParams {
     /// The on chain representation of the DAO
     pub dao: Dao,
-    /// Secret key for the DAO
-    pub secret_key: SecretKey,
+    /// DAO notes decryption secret key
+    pub notes_secret_key: Option<SecretKey>,
+    /// DAO proposals creator secret key
+    pub proposer_secret_key: Option<SecretKey>,
+    /// DAO proposals viewer secret key
+    pub proposals_secret_key: Option<SecretKey>,
+    /// DAO votes viewer secret key
+    pub votes_secret_key: Option<SecretKey>,
+    /// DAO proposals executor secret key
+    pub exec_secret_key: Option<SecretKey>,
+    /// DAO strongly supported proposals executor secret key
+    pub early_exec_secret_key: Option<SecretKey>,
 }
 
 impl DaoParams {
+    /// Generate new `DaoParams`. If a specific secret key is provided,
+    /// the corresponding public key will be derived from it and ignore the provided one.
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         proposer_limit: u64,
         quorum: u64,
+        early_exec_quorum: u64,
         approval_ratio_base: u64,
         approval_ratio_quot: u64,
         gov_token_id: TokenId,
-        secret_key: SecretKey,
+        notes_secret_key: Option<SecretKey>,
+        notes_public_key: PublicKey,
+        proposer_secret_key: Option<SecretKey>,
+        proposer_public_key: PublicKey,
+        proposals_secret_key: Option<SecretKey>,
+        proposals_public_key: PublicKey,
+        votes_secret_key: Option<SecretKey>,
+        votes_public_key: PublicKey,
+        exec_secret_key: Option<SecretKey>,
+        exec_public_key: PublicKey,
+        early_exec_secret_key: Option<SecretKey>,
+        early_exec_public_key: PublicKey,
         bulla_blind: BaseBlind,
     ) -> Self {
-        // TODO: use diff keys and early exec quorum
+        // Derive corresponding keys from their secret or use the provided ones.
+        let notes_public_key = match notes_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => notes_public_key,
+        };
+        let proposer_public_key = match proposer_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => proposer_public_key,
+        };
+        let proposals_public_key = match proposals_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => proposals_public_key,
+        };
+        let votes_public_key = match votes_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => votes_public_key,
+        };
+        let exec_public_key = match exec_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => exec_public_key,
+        };
+        let early_exec_public_key = match early_exec_secret_key {
+            Some(secret_key) => PublicKey::from_secret(secret_key),
+            None => early_exec_public_key,
+        };
+
         let dao = Dao {
             proposer_limit,
             quorum,
-            early_exec_quorum: quorum,
+            early_exec_quorum,
             approval_ratio_base,
             approval_ratio_quot,
             gov_token_id,
-            notes_public_key: PublicKey::from_secret(secret_key),
-            proposer_public_key: PublicKey::from_secret(secret_key),
-            proposals_public_key: PublicKey::from_secret(secret_key),
-            votes_public_key: PublicKey::from_secret(secret_key),
-            exec_public_key: PublicKey::from_secret(secret_key),
-            early_exec_public_key: PublicKey::from_secret(secret_key),
+            notes_public_key,
+            proposer_public_key,
+            proposals_public_key,
+            votes_public_key,
+            exec_public_key,
+            early_exec_public_key,
             bulla_blind,
         };
-        Self { dao, secret_key }
+        Self {
+            dao,
+            notes_secret_key,
+            proposer_secret_key,
+            proposals_secret_key,
+            votes_secret_key,
+            exec_secret_key,
+            early_exec_secret_key,
+        }
     }
 }
 
 impl fmt::Display for DaoParams {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        // TODO: add missing fields
+        // Grab known secret keys
+        let notes_secret_key = match self.notes_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let proposer_secret_key = match self.proposer_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let proposals_secret_key = match self.proposals_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let votes_secret_key = match self.votes_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let exec_secret_key = match self.exec_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let early_exec_secret_key = match self.early_exec_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+
         let s = format!(
-            "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}",
+            "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}",
             "DAO Parameters",
             "==============",
             "Proposer limit",
@@ -180,10 +263,30 @@ impl fmt::Display for DaoParams {
             self.dao.approval_ratio_quot as f64 / self.dao.approval_ratio_base as f64,
             "Governance Token ID",
             self.dao.gov_token_id,
-            "Public key",
+            "Notes Public key",
             self.dao.notes_public_key,
-            "Secret key",
-            self.secret_key,
+            "Notes Secret key",
+            notes_secret_key,
+            "Proposer Public key",
+            self.dao.proposer_public_key,
+            "Proposer Secret key",
+            proposer_secret_key,
+            "Proposals Public key",
+            self.dao.proposals_public_key,
+            "Proposals Secret key",
+            proposals_secret_key,
+            "Votes Public key",
+            self.dao.votes_public_key,
+            "Votes Secret key",
+            votes_secret_key,
+            "Exec Public key",
+            self.dao.exec_public_key,
+            "Exec Secret key",
+            exec_secret_key,
+            "Early Exec Public key",
+            self.dao.early_exec_public_key,
+            "Early Exec Secret key",
+            early_exec_secret_key,
             "Bulla blind",
             self.dao.bulla_blind,
         );
@@ -221,15 +324,37 @@ impl DaoRecord {
     pub fn bulla(&self) -> DaoBulla {
         self.params.dao.to_bulla()
     }
-
-    pub fn keypair(&self) -> Keypair {
-        let public = PublicKey::from_secret(self.params.secret_key);
-        Keypair { public, secret: self.params.secret_key }
-    }
 }
 
 impl fmt::Display for DaoRecord {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // Grab known secret keys
+        let notes_secret_key = match self.params.notes_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let proposer_secret_key = match self.params.proposer_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let proposals_secret_key = match self.params.proposals_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let votes_secret_key = match self.params.votes_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let exec_secret_key = match self.params.exec_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+        let early_exec_secret_key = match self.params.early_exec_secret_key {
+            Some(secret_key) => format!("{secret_key}"),
+            None => "None".to_string(),
+        };
+
+        // Grab mint information
         let leaf_position = match self.leaf_position {
             Some(p) => format!("{p:?}"),
             None => "None".to_string(),
@@ -242,9 +367,9 @@ impl fmt::Display for DaoRecord {
             Some(c) => format!("{c}"),
             None => "None".to_string(),
         };
-        // TODO: add missing fields
+
         let s = format!(
-            "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
+            "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
             "DAO Parameters",
             "==============",
             "Name",
@@ -261,10 +386,30 @@ impl fmt::Display for DaoRecord {
             self.params.dao.approval_ratio_quot as f64 / self.params.dao.approval_ratio_base as f64,
             "Governance Token ID",
             self.params.dao.gov_token_id,
-            "Public key",
+            "Notes Public key",
             self.params.dao.notes_public_key,
-            "Secret key",
-            self.params.secret_key,
+            "Notes Secret key",
+            notes_secret_key,
+            "Proposer Public key",
+            self.params.dao.proposer_public_key,
+            "Proposer Secret key",
+            proposer_secret_key,
+            "Proposals Public key",
+            self.params.dao.proposals_public_key,
+            "Proposals Secret key",
+            proposals_secret_key,
+            "Votes Public key",
+            self.params.dao.votes_public_key,
+            "Votes Secret key",
+            votes_secret_key,
+            "Exec Public key",
+            self.params.dao.exec_public_key,
+            "Exec Secret key",
+            exec_secret_key,
+            "Early Exec Public key",
+            self.params.dao.early_exec_public_key,
+            "Early Exec Secret key",
+            early_exec_secret_key,
             "Bulla blind",
             self.params.dao.bulla_blind,
             "Leaf position",
@@ -462,12 +607,14 @@ impl Drk {
         }
     }
 
-    /// Fetch all DAO secret keys from the wallet.
-    pub async fn get_dao_secrets(&self) -> Result<Vec<SecretKey>> {
+    /// Fetch all DAO notes secret keys from the wallet.
+    pub async fn get_dao_notes_secrets(&self) -> Result<Vec<SecretKey>> {
         let daos = self.get_daos().await?;
         let mut ret = Vec::with_capacity(daos.len());
         for dao in daos {
-            ret.push(dao.params.secret_key);
+            if let Some(secret_key) = dao.params.notes_secret_key {
+                ret.push(secret_key);
+            }
         }
 
         Ok(ret)
@@ -679,32 +826,34 @@ impl Drk {
         let (mut daos_tree, proposals_tree) = self.get_dao_trees().await?;
         daos_tree.append(MerkleNode::from(new_bulla.inner()));
         for dao in &daos {
-            if dao.bulla() == new_bulla {
-                println!(
-                    "[apply_dao_mint_data] Found minted DAO {}, noting down for wallet update",
-                    new_bulla
-                );
-
-                // We have this DAO imported in our wallet. Add the metadata:
-                let mut dao_to_confirm = dao.clone();
-                dao_to_confirm.leaf_position = daos_tree.mark();
-                dao_to_confirm.tx_hash = Some(tx_hash);
-                dao_to_confirm.call_index = Some(call_index);
-
-                // Update wallet data
-                if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
-                    return Err(Error::DatabaseError(format!(
-                        "[apply_dao_mint_data] Put DAO tree failed: {e:?}"
-                    )))
-                }
-                if let Err(e) = self.confirm_dao(&dao_to_confirm).await {
-                    return Err(Error::DatabaseError(format!(
-                        "[apply_dao_mint_data] Confirm DAO failed: {e:?}"
-                    )))
-                }
+            if dao.bulla() != new_bulla {
+                continue
+            }
+
+            println!(
+                "[apply_dao_mint_data] Found minted DAO {}, noting down for wallet update",
+                new_bulla
+            );
 
-                return Ok(true);
+            // We have this DAO imported in our wallet. Add the metadata:
+            let mut dao_to_confirm = dao.clone();
+            dao_to_confirm.leaf_position = daos_tree.mark();
+            dao_to_confirm.tx_hash = Some(tx_hash);
+            dao_to_confirm.call_index = Some(call_index);
+
+            // Update wallet data
+            if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
+                return Err(Error::DatabaseError(format!(
+                    "[apply_dao_mint_data] Put DAO tree failed: {e:?}"
+                )))
             }
+            if let Err(e) = self.confirm_dao(&dao_to_confirm).await {
+                return Err(Error::DatabaseError(format!(
+                    "[apply_dao_mint_data] Confirm DAO failed: {e:?}"
+                )))
+            }
+
+            return Ok(true);
         }
 
         Ok(false)
@@ -726,51 +875,56 @@ impl Drk {
         // If we're able to decrypt this note, that's the way to link it
         // to a specific DAO.
         for dao in &daos {
-            if let Ok(note) = params.note.decrypt::<DaoProposal>(&dao.params.secret_key) {
-                // We managed to decrypt it. Let's place this in a proper ProposalRecord object
-                println!("[apply_dao_propose_data] Managed to decrypt DAO proposal note");
-
-                // We need to clone the trees here for reproducing the snapshot Merkle roots
-                let money_tree = self.get_money_tree().await?;
-                let nullifiers_smt = self.get_nullifiers_smt().await?;
-
-                // Check if we already got the record
-                let our_proposal =
-                    match self.get_dao_proposal_by_bulla(&params.proposal_bulla).await {
-                        Ok(p) => {
-                            let mut our_proposal = p;
-                            our_proposal.leaf_position = proposals_tree.mark();
-                            our_proposal.money_snapshot_tree = Some(money_tree);
-                            our_proposal.nullifiers_smt_snapshot = Some(nullifiers_smt);
-                            our_proposal.tx_hash = Some(tx_hash);
-                            our_proposal.call_index = Some(call_index);
-                            our_proposal
-                        }
-                        Err(_) => ProposalRecord {
-                            proposal: note,
-                            data: None,
-                            leaf_position: proposals_tree.mark(),
-                            money_snapshot_tree: Some(money_tree),
-                            nullifiers_smt_snapshot: Some(nullifiers_smt),
-                            tx_hash: Some(tx_hash),
-                            call_index: Some(call_index),
-                            exec_tx_hash: None,
-                        },
-                    };
-
-                if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
-                    return Err(Error::DatabaseError(format!(
-                        "[apply_dao_propose_data] Put DAO tree failed: {e:?}"
-                    )))
-                }
-                if let Err(e) = self.put_dao_proposal(&our_proposal).await {
-                    return Err(Error::DatabaseError(format!(
-                        "[apply_dao_propose_data] Put DAO proposals failed: {e:?}"
-                    )))
+            // Check if we have the proposals key
+            let Some(proposals_secret_key) = dao.params.proposals_secret_key else { continue };
+
+            // Try to decrypt the proposal note
+            let Ok(note) = params.note.decrypt::<DaoProposal>(&proposals_secret_key) else {
+                continue
+            };
+
+            // We managed to decrypt it. Let's place this in a proper ProposalRecord object
+            println!("[apply_dao_propose_data] Managed to decrypt DAO proposal note");
+
+            // We need to clone the trees here for reproducing the snapshot Merkle roots
+            let money_tree = self.get_money_tree().await?;
+            let nullifiers_smt = self.get_nullifiers_smt().await?;
+
+            // Check if we already got the record
+            let our_proposal = match self.get_dao_proposal_by_bulla(&params.proposal_bulla).await {
+                Ok(p) => {
+                    let mut our_proposal = p;
+                    our_proposal.leaf_position = proposals_tree.mark();
+                    our_proposal.money_snapshot_tree = Some(money_tree);
+                    our_proposal.nullifiers_smt_snapshot = Some(nullifiers_smt);
+                    our_proposal.tx_hash = Some(tx_hash);
+                    our_proposal.call_index = Some(call_index);
+                    our_proposal
                 }
+                Err(_) => ProposalRecord {
+                    proposal: note,
+                    data: None,
+                    leaf_position: proposals_tree.mark(),
+                    money_snapshot_tree: Some(money_tree),
+                    nullifiers_smt_snapshot: Some(nullifiers_smt),
+                    tx_hash: Some(tx_hash),
+                    call_index: Some(call_index),
+                    exec_tx_hash: None,
+                },
+            };
 
-                return Ok(true);
+            if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
+                return Err(Error::DatabaseError(format!(
+                    "[apply_dao_propose_data] Put DAO tree failed: {e:?}"
+                )))
+            }
+            if let Err(e) = self.put_dao_proposal(&our_proposal).await {
+                return Err(Error::DatabaseError(format!(
+                    "[apply_dao_propose_data] Put DAO proposals failed: {e:?}"
+                )))
             }
+
+            return Ok(true);
         }
 
         Ok(false)
@@ -802,8 +956,11 @@ impl Drk {
             }
         };
 
+        // Check if we have the votes key
+        let Some(votes_secret_key) = dao.params.votes_secret_key else { return Ok(false) };
+
         // Decrypt the vote note
-        let note = match params.note.decrypt_unsafe(&dao.params.secret_key) {
+        let note = match params.note.decrypt_unsafe(&votes_secret_key) {
             Ok(n) => n,
             Err(e) => {
                 return Err(Error::DatabaseError(format!(
@@ -1237,6 +1394,7 @@ impl Drk {
     pub async fn import_dao(&self, name: &str, params: DaoParams) -> Result<()> {
         // First let's check if we've imported this DAO with the given name before.
         if self.get_dao_by_name(name).await.is_ok() {
+            // TODO: update `DaoParams` if the dao already exists
             return Err(Error::DatabaseError(
                 "[import_dao] This DAO has already been imported".to_string(),
             ))
@@ -1503,6 +1661,19 @@ impl Drk {
             ))
         }
 
+        // Check that we have all the keys
+        if dao.params.notes_secret_key.is_none() ||
+            dao.params.proposer_secret_key.is_none() ||
+            dao.params.proposals_secret_key.is_none() ||
+            dao.params.votes_secret_key.is_none() ||
+            dao.params.exec_secret_key.is_none() ||
+            dao.params.early_exec_secret_key.is_none()
+        {
+            return Err(Error::Custom(
+                "[dao_mint] We need all the secrets key to mint the DAO on-chain".to_string(),
+            ))
+        }
+
         // Now we need to do a lookup for the zkas proof bincodes, and create
         // the circuit objects and proving keys so we can build the transaction.
         // We also do this through the RPC. First we grab the fee call from money.
@@ -1536,15 +1707,15 @@ impl Drk {
         let dao_mint_pk = ProvingKey::build(dao_mint_zkbin.k, &dao_mint_circuit);
 
         // Create the DAO mint call
-        // TODO: use diff keys
+        let notes_secret_key = dao.params.notes_secret_key.unwrap();
         let (params, proofs) = make_mint_call(
             &dao.params.dao,
-            &dao.params.secret_key,
-            &dao.params.secret_key,
-            &dao.params.secret_key,
-            &dao.params.secret_key,
-            &dao.params.secret_key,
-            &dao.params.secret_key,
+            &notes_secret_key,
+            &dao.params.proposer_secret_key.unwrap(),
+            &dao.params.proposals_secret_key.unwrap(),
+            &dao.params.votes_secret_key.unwrap(),
+            &dao.params.exec_secret_key.unwrap(),
+            &dao.params.early_exec_secret_key.unwrap(),
             &dao_mint_zkbin,
             &dao_mint_pk,
         )?;
@@ -1558,7 +1729,7 @@ impl Drk {
         // We first have to execute the fee-less tx to gather its used gas, and then we feed
         // it into the fee-creating function.
         let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[dao.params.secret_key])?;
+        let sigs = tx.create_sigs(&[notes_secret_key])?;
         tx.signatures.push(sigs);
 
         let tree = self.get_money_tree().await?;
@@ -1570,7 +1741,7 @@ impl Drk {
 
         // Now build the actual transaction and sign it with all necessary keys.
         let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[dao.params.secret_key])?;
+        let sigs = tx.create_sigs(&[notes_secret_key])?;
         tx.signatures.push(sigs);
         let sigs = tx.create_sigs(&fee_secrets)?;
         tx.signatures.push(sigs);
@@ -1598,6 +1769,13 @@ impl Drk {
             ))
         }
 
+        // Check that we have the proposer key
+        if dao.params.proposer_secret_key.is_none() {
+            return Err(Error::Custom(
+                "[dao_propose_transfer] We need the proposer secret key to create proposals for this DAO".to_string(),
+            ))
+        }
+
         // Fetch DAO unspent OwnCoins to see what its balance is
         let dao_spend_hook =
             FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
@@ -1708,6 +1886,13 @@ impl Drk {
             ))
         }
 
+        // Check that we have the proposer key
+        if dao.params.proposer_secret_key.is_none() {
+            return Err(Error::Custom(
+                "[dao_transfer_proposal_tx] We need the proposer secret key to create proposals for this DAO".to_string(),
+            ))
+        }
+
         // Fetch DAO unspent OwnCoins to see what its balance is for the coin
         let dao_spend_hook =
             FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
@@ -1863,9 +2048,8 @@ impl Drk {
             signature_secret,
         };
 
-        // TODO: use the proper key if we have it
         let (params, proofs) = call.make(
-            &dao.params.secret_key,
+            &dao.params.proposer_secret_key.unwrap(),
             &propose_burn_zkbin,
             &propose_burn_pk,
             &propose_main_zkbin,
@@ -2153,6 +2337,14 @@ impl Drk {
             ))
         }
 
+        // Check that we have the exec key
+        if dao.params.exec_secret_key.is_none() {
+            return Err(Error::Custom(
+                "[dao_exec_transfer] We need the exec secret key to execute proposals for this DAO"
+                    .to_string(),
+            ))
+        }
+
         // Check proposal is approved
         let votes = self.get_dao_proposal_votes(&proposal.bulla()).await?;
         let mut yes_vote_value = 0;
@@ -2310,7 +2502,7 @@ impl Drk {
         outputs.push(proposal_coinattrs.clone());
 
         let dao_coin_attrs = CoinAttributes {
-            public_key: dao.keypair().public,
+            public_key: dao.params.dao.notes_public_key,
             value: change_value,
             token_id: proposal_coinattrs.token_id,
             spend_hook: dao_spend_hook,
@@ -2348,9 +2540,12 @@ impl Drk {
             signature_secret: exec_signature_secret,
             current_blockwindow,
         };
-        // TODO: use the proper key if we have it
-        let (exec_params, exec_proofs) =
-            exec_builder.make(&dao.params.secret_key, &None, &dao_exec_zkbin, &dao_exec_pk)?;
+        let (exec_params, exec_proofs) = exec_builder.make(
+            &dao.params.exec_secret_key.unwrap(),
+            &None,
+            &dao_exec_zkbin,
+            &dao_exec_pk,
+        )?;
 
         // Encode the call
         let mut data = vec![DaoFunction::Exec as u8];

+ 55 - 18
bin/drk/src/main.rs

@@ -43,7 +43,8 @@ use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
 use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
 use darkfi_sdk::{
     crypto::{
-        note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, PublicKey, SecretKey, DAO_CONTRACT_ID,
+        note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, Keypair, PublicKey, SecretKey,
+        DAO_CONTRACT_ID,
     },
     pasta::{group::ff::PrimeField, pallas},
     tx::TransactionHash,
@@ -280,6 +281,10 @@ enum DaoSubcmd {
         proposer_limit: String,
         /// Minimal threshold of participating total tokens needed for a proposal to pass
         quorum: String,
+        /// 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.
+        early_exec_quorum: String,
         /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
         approval_ratio: f64,
         /// DAO's governance token ID
@@ -1132,7 +1137,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
         },
 
         Subcmd::Dao { command } => match command {
-            DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
+            DaoSubcmd::Create {
+                proposer_limit,
+                quorum,
+                early_exec_quorum,
+                approval_ratio,
+                gov_token_id,
+            } => {
                 if let Err(e) = f64::from_str(&proposer_limit) {
                     eprintln!("Invalid proposer limit: {e:?}");
                     exit(2);
@@ -1144,6 +1155,8 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
 
                 let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
                 let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
+                let early_exec_quorum =
+                    decode_base10(&early_exec_quorum, BALANCE_BASE10_DECIMALS, true)?;
 
                 if approval_ratio > 1.0 {
                     eprintln!("Error: Approval ratio cannot be >1.0");
@@ -1169,16 +1182,33 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                let secret_key = SecretKey::random(&mut OsRng);
+                let notes_keypair = Keypair::random(&mut OsRng);
+                let proposer_keypair = Keypair::random(&mut OsRng);
+                let proposals_keypair = Keypair::random(&mut OsRng);
+                let votes_keypair = Keypair::random(&mut OsRng);
+                let exec_keypair = Keypair::random(&mut OsRng);
+                let early_exec_keypair = Keypair::random(&mut OsRng);
                 let bulla_blind = BaseBlind::random(&mut OsRng);
 
                 let params = DaoParams::new(
                     proposer_limit,
                     quorum,
+                    early_exec_quorum,
                     approval_ratio_base,
                     approval_ratio_quot,
                     gov_token_id,
-                    secret_key,
+                    Some(notes_keypair.secret),
+                    notes_keypair.public,
+                    Some(proposer_keypair.secret),
+                    proposer_keypair.public,
+                    Some(proposals_keypair.secret),
+                    proposals_keypair.public,
+                    Some(votes_keypair.secret),
+                    votes_keypair.public,
+                    Some(exec_keypair.secret),
+                    exec_keypair.public,
+                    Some(early_exec_keypair.secret),
+                    early_exec_keypair.public,
                     bulla_blind,
                 );
 
@@ -1628,21 +1658,28 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 // Retrieve all DAOs to try to decrypt the proposal
                 let daos = drk.get_daos().await?;
                 for dao in &daos {
-                    if let Ok(proposal) =
-                        encrypted_proposal.decrypt::<ProposalRecord>(&dao.params.secret_key)
-                    {
-                        let proposal = match drk.get_dao_proposal_by_bulla(&proposal.bulla()).await
-                        {
-                            Ok(p) => {
-                                let mut our_proposal = p;
-                                our_proposal.data = proposal.data;
-                                our_proposal
-                            }
-                            Err(_) => proposal,
-                        };
+                    // Check if we have the proposals key
+                    let Some(proposals_secret_key) = dao.params.proposals_secret_key else {
+                        continue
+                    };
 
-                        return drk.put_dao_proposal(&proposal).await
-                    }
+                    // Try to decrypt the proposal
+                    let Ok(proposal) =
+                        encrypted_proposal.decrypt::<ProposalRecord>(&proposals_secret_key)
+                    else {
+                        continue
+                    };
+
+                    let proposal = match drk.get_dao_proposal_by_bulla(&proposal.bulla()).await {
+                        Ok(p) => {
+                            let mut our_proposal = p;
+                            our_proposal.data = proposal.data;
+                            our_proposal
+                        }
+                        Err(_) => proposal,
+                    };
+
+                    return drk.put_dao_proposal(&proposal).await
                 }
 
                 eprintln!("Couldn't decrypt the proposal with out DAO keys");

+ 2 - 2
bin/drk/src/money.rs

@@ -837,7 +837,7 @@ impl Drk {
     ) -> Result<bool> {
         let (nullifiers, coins, notes, freezes) = self.parse_money_call(call_idx, calls).await?;
         let secrets = self.get_money_secrets().await?;
-        let dao_secrets = self.get_dao_secrets().await?;
+        let dao_notes_secrets = self.get_dao_notes_secrets().await?;
         let mut tree = self.get_money_tree().await?;
 
         let mut owncoins = vec![];
@@ -847,7 +847,7 @@ impl Drk {
             tree.append(MerkleNode::from(coin.inner()));
 
             // Attempt to decrypt the note
-            for secret in secrets.iter().chain(dao_secrets.iter()) {
+            for secret in secrets.iter().chain(dao_notes_secrets.iter()) {
                 if let Ok(note) = note.decrypt::<MoneyNote>(secret) {
                     println!("[apply_tx_money_data] Successfully decrypted a Money Note");
                     println!("[apply_tx_money_data] Witnessing coin in Merkle tree");

+ 2 - 2
src/contract/test-harness/src/vks.rs

@@ -207,9 +207,9 @@ pub fn inject(sled_db: &sled::Db, vks: &Vks) -> Result<()> {
             DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS |
             DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS |
             DAO_CONTRACT_ZKAS_DAO_EXEC_NS |
+            DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS |
             DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS |
-            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS |
-            DAO_CONTRACT_ZKAS_DAO_EARLY_EXEC_NS => {
+            DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS => {
                 let key = serialize(&namespace.as_str());
                 let value = serialize(&(bincode.clone(), vk.clone()));
                 dao_tree.insert(key, value)?;