Bläddra i källkod

dao: begin migrating dao code over to new infra

x 3 år sedan
förälder
incheckning
525e57e97a

+ 1 - 0
Cargo.lock

@@ -1172,6 +1172,7 @@ name = "darkfi-dao-contract"
 version = "0.3.0"
 version = "0.3.0"
 dependencies = [
 dependencies = [
  "async-std",
  "async-std",
+ "chacha20poly1305",
  "darkfi",
  "darkfi",
  "darkfi-money-contract",
  "darkfi-money-contract",
  "darkfi-sdk",
  "darkfi-sdk",

+ 3 - 5
src/consensus/validator.rs

@@ -22,8 +22,8 @@ use async_std::sync::{Arc, RwLock};
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
         constants::MERKLE_DEPTH,
         constants::MERKLE_DEPTH,
-        contract_id::MONEY_CONTRACT_ID,
-        //contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+        //contract_id::MONEY_CONTRACT_ID,
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
         schnorr::{SchnorrPublic, SchnorrSecret},
         schnorr::{SchnorrPublic, SchnorrSecret},
         MerkleNode,
         MerkleNode,
         PublicKey,
         PublicKey,
@@ -146,7 +146,7 @@ impl ValidatorState {
         // The faucet pubkeys are pubkeys which are allowed to create clear inputs
         // The faucet pubkeys are pubkeys which are allowed to create clear inputs
         // in the money contract.
         // in the money contract.
         let money_contract_deploy_payload = serialize(&faucet_pubkeys);
         let money_contract_deploy_payload = serialize(&faucet_pubkeys);
-        //let dao_contract_deploy_payload = vec![];
+        let dao_contract_deploy_payload = vec![];
 
 
         // In this hashmap, we keep references to ZK proof verifying keys needed
         // In this hashmap, we keep references to ZK proof verifying keys needed
         // for the circuits our native contracts provide.
         // for the circuits our native contracts provide.
@@ -159,14 +159,12 @@ impl ValidatorState {
                 include_bytes!("../contract/money/money_contract.wasm").to_vec(),
                 include_bytes!("../contract/money/money_contract.wasm").to_vec(),
                 money_contract_deploy_payload,
                 money_contract_deploy_payload,
             ),
             ),
-            /*
             (
             (
                 "DAO Contract",
                 "DAO Contract",
                 *DAO_CONTRACT_ID,
                 *DAO_CONTRACT_ID,
                 include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
                 include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
                 dao_contract_deploy_payload,
                 dao_contract_deploy_payload,
             ),
             ),
-            */
         ];
         ];
 
 
         info!("Deploying native wasm contracts");
         info!("Deploying native wasm contracts");

+ 2 - 0
src/contract/dao/Cargo.toml

@@ -15,6 +15,7 @@ darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
 
 
 # The following dependencies are used for the client API and
 # The following dependencies are used for the client API and
 # probably shouldn't be in WASM
 # probably shouldn't be in WASM
+chacha20poly1305 = { version = "0.10.1", optional = true }
 darkfi = { path = "../../../", features = ["zk", "rpc"], optional = true }
 darkfi = { path = "../../../", features = ["zk", "rpc"], optional = true }
 halo2_proofs = { version = "0.2.0", optional = true }
 halo2_proofs = { version = "0.2.0", optional = true }
 log = { version = "0.4.17", optional = true }
 log = { version = "0.4.17", optional = true }
@@ -39,6 +40,7 @@ no-entrypoint = []
 client = [
 client = [
     "darkfi",
     "darkfi",
     "rand",
     "rand",
+    "chacha20poly1305",
     "log",
     "log",
     "halo2_proofs",
     "halo2_proofs",
 ]
 ]

+ 71 - 2
src/contract/dao/src/client.rs

@@ -22,14 +22,83 @@ use darkfi::{
     Result,
     Result,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
-    crypto::{poseidon_hash, PublicKey, SecretKey, TokenId},
+    crypto::{
+        coin::Coin, constants::MERKLE_DEPTH, poseidon_hash, MerkleNode, PublicKey, SecretKey,
+        TokenId,
+    },
+    incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::pallas,
     pasta::pallas,
 };
 };
 use halo2_proofs::circuit::Value;
 use halo2_proofs::circuit::Value;
 use log::debug;
 use log::debug;
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 
 
-use crate::state::{DaoBulla, DaoMintParams};
+use crate::{
+    note::EncryptedNote2,
+    state::{DaoBulla, DaoMintParams},
+};
+
+pub type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
+
+/*
+pub struct OwnCoin {
+    pub coin: Coin,
+    pub note: money::transfer::wallet::Note,
+    pub leaf_position: incrementalmerkletree::Position,
+}
+
+pub struct WalletCache {
+    // Normally this would be a HashMap, but SecretKey is not Hash-able
+    // TODO: This can be HashableBase
+    cache: Vec<(SecretKey, Vec<OwnCoin>)>,
+    /// The entire Merkle tree state
+    tree: MerkleTree,
+}
+
+impl Default for WalletCache {
+    fn default() -> Self {
+        Self { cache: Vec::new(), tree: MerkleTree::new(100) }
+    }
+}
+
+impl WalletCache {
+    pub fn new() -> Self {
+        Self { cache: Vec::new(), tree: MerkleTree::new(100) }
+    }
+
+    /// Must be called at the start to begin tracking received coins for this secret.
+    pub fn track(&mut self, secret: SecretKey) {
+        self.cache.push((secret, Vec::new()));
+    }
+
+    /// Get all coins received by this secret key
+    /// track() must be called on this secret before calling this or the function will panic.
+    pub fn get_received(&mut self, secret: &SecretKey) -> Vec<OwnCoin> {
+        for (other_secret, own_coins) in self.cache.iter_mut() {
+            if *secret == *other_secret {
+                // clear own_coins vec, and return current contents
+                return std::mem::take(own_coins)
+            }
+        }
+        panic!("you forget to track() this secret!");
+    }
+
+    pub fn try_decrypt_note(&mut self, coin: Coin, ciphertext: &EncryptedNote2) {
+        // Add the new coins to the Merkle tree
+        let node = MerkleNode::from(coin.inner());
+        self.tree.append(&node);
+
+        // Loop through all our secret keys...
+        for (secret, own_coins) in self.cache.iter_mut() {
+            // .. attempt to decrypt the note ...
+            if let Ok(note) = ciphertext.decrypt(secret) {
+                let leaf_position = self.tree.witness().expect("coin should be in tree");
+                own_coins.push(OwnCoin { coin, note, leaf_position });
+            }
+        }
+    }
+}
+*/
 
 
 struct DaoMintRevealed {
 struct DaoMintRevealed {
     pub bulla: DaoBulla,
     pub bulla: DaoBulla,

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

@@ -23,6 +23,9 @@ pub mod entrypoint;
 
 
 pub mod state;
 pub mod state;
 
 
+#[cfg(feature = "client")]
+pub mod note;
+
 #[cfg(feature = "client")]
 #[cfg(feature = "client")]
 /// Transaction building API for clients interacting with this contract
 /// Transaction building API for clients interacting with this contract
 pub mod client;
 pub mod client;

+ 119 - 0
src/contract/dao/src/note.rs

@@ -0,0 +1,119 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
+use darkfi_sdk::crypto::{
+    diffie_hellman::{kdf_sapling, sapling_ka_agree},
+    PublicKey, SecretKey,
+};
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
+use rand::rngs::OsRng;
+
+use darkfi::{Error, Result};
+
+pub const AEAD_TAG_SIZE: usize = 16;
+
+pub fn encrypt<T: Encodable>(note: &T, public: &PublicKey) -> Result<EncryptedNote2> {
+    let ephem_secret = SecretKey::random(&mut OsRng);
+    let ephem_public = PublicKey::from_secret(ephem_secret);
+    let shared_secret = sapling_ka_agree(&ephem_secret, public);
+    let key = kdf_sapling(&shared_secret, &ephem_public);
+
+    let mut input = Vec::new();
+    note.encode(&mut input)?;
+    let input_len = input.len();
+
+    let mut ciphertext = vec![0_u8; input_len + AEAD_TAG_SIZE];
+    ciphertext[..input_len].copy_from_slice(&input);
+
+    ChaCha20Poly1305::new(key.as_ref().into())
+        .encrypt_in_place([0u8; 12][..].into(), &[], &mut ciphertext)
+        .unwrap();
+
+    Ok(EncryptedNote2 { ciphertext, ephem_public })
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
+pub struct EncryptedNote2 {
+    ciphertext: Vec<u8>,
+    ephem_public: PublicKey,
+}
+
+impl EncryptedNote2 {
+    pub fn decrypt<T: Decodable>(&self, secret: &SecretKey) -> Result<T> {
+        let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
+        let key = kdf_sapling(&shared_secret, &self.ephem_public);
+
+        let ciphertext_len = self.ciphertext.len();
+        let mut plaintext = vec![0_u8; ciphertext_len];
+        plaintext.copy_from_slice(&self.ciphertext);
+
+        match ChaCha20Poly1305::new(key.as_ref().into()).decrypt_in_place(
+            [0u8; 12][..].into(),
+            &[],
+            &mut plaintext,
+        ) {
+            Ok(()) => {
+                Ok(T::decode(&plaintext[..ciphertext_len - AEAD_TAG_SIZE]).map_err(Error::from)?)
+            }
+            Err(e) => Err(Error::NoteDecryptionFailed(e.to_string())),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use darkfi::crypto::types::{DrkCoinBlind, DrkSerial, DrkValueBlind};
+    use darkfi_sdk::{
+        crypto::{Keypair, TokenId},
+        pasta::{group::ff::Field, pallas},
+    };
+
+    #[test]
+    fn test_note_encdec() {
+        #[derive(SerialEncodable, SerialDecodable)]
+        struct MyNote {
+            serial: DrkSerial,
+            value: u64,
+            token_id: TokenId,
+            coin_blind: DrkCoinBlind,
+            value_blind: DrkValueBlind,
+            token_blind: DrkValueBlind,
+            memo: Vec<u8>,
+        }
+        let note = MyNote {
+            serial: DrkSerial::random(&mut OsRng),
+            value: 110,
+            token_id: TokenId::from(pallas::Base::random(&mut OsRng)),
+            coin_blind: DrkCoinBlind::random(&mut OsRng),
+            value_blind: DrkValueBlind::random(&mut OsRng),
+            token_blind: DrkValueBlind::random(&mut OsRng),
+            memo: vec![32, 223, 231, 3, 1, 1],
+        };
+
+        let keypair = Keypair::random(&mut OsRng);
+
+        let encrypted_note = encrypt(&note, &keypair.public).unwrap();
+        let note2: MyNote = encrypted_note.decrypt(&keypair.secret).unwrap();
+        assert_eq!(note.value, note2.value);
+        assert_eq!(note.token_id, note2.token_id);
+        assert_eq!(note.token_blind, note2.token_blind);
+        assert_eq!(note.memo, note2.memo);
+    }
+}

+ 0 - 0
src/contract/dao/tests.unused/harness.rs → src/contract/dao/tests/harness.rs


+ 37 - 6
src/contract/dao/tests.unused/integration.rs → src/contract/dao/tests/integration.rs

@@ -18,19 +18,31 @@
 
 
 use darkfi::{tx::Transaction, Result};
 use darkfi::{tx::Transaction, Result};
 use darkfi_sdk::{
 use darkfi_sdk::{
-    crypto::TokenId,
+    crypto::{constants::MERKLE_DEPTH, MerkleNode, TokenId},
+    incrementalmerkletree::{bridgetree::BridgeTree, Tree},
     pasta::{group::ff::Field, pallas},
     pasta::{group::ff::Field, pallas},
     tx::ContractCall,
     tx::ContractCall,
 };
 };
 use darkfi_serial::Encodable;
 use darkfi_serial::Encodable;
-use log::info;
+use log::{debug, info};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 
 
-use darkfi_dao_contract::{client::build_dao_mint_tx, DaoFunction};
+use darkfi_dao_contract::{
+    client::{build_dao_mint_tx, MerkleTree},
+    DaoFunction,
+};
 
 
 mod harness;
 mod harness;
 use harness::{init_logger, DaoTestHarness};
 use harness::{init_logger, DaoTestHarness};
 
 
+// TODO: Anonymity leaks in this proof of concept:
+//
+// * Vote updates are linked to the proposal_bulla
+// * Nullifier of vote will link vote with the coin when it's spent
+
+// TODO: strategize and cleanup Result/Error usage
+// TODO: fix up code doc
+
 #[async_std::test]
 #[async_std::test]
 async fn integration_test() -> Result<()> {
 async fn integration_test() -> Result<()> {
     init_logger()?;
     init_logger()?;
@@ -51,9 +63,12 @@ async fn integration_test() -> Result<()> {
     let dao_approval_ratio_quot = 1;
     let dao_approval_ratio_quot = 1;
     let dao_approval_ratio_base = 2;
     let dao_approval_ratio_base = 2;
 
 
-    // =================
-    // DaoFunction::Mint
-    // =================
+    // =======================================================
+    // Dao::Mint
+    //
+    // Create the DAO bulla
+    // =======================================================
+    debug!(target: "demo", "Stage 1. Creating DAO bulla");
 
 
     let dao_bulla_blind = pallas::Base::random(&mut OsRng);
     let dao_bulla_blind = pallas::Base::random(&mut OsRng);
 
 
@@ -90,5 +105,21 @@ async fn integration_test() -> Result<()> {
     th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
     th.alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
     // TODO: Witness and add to wallet merkle tree?
     // TODO: Witness and add to wallet merkle tree?
 
 
+    let mut dao_tree = MerkleTree::new(100);
+    let dao_leaf_position = {
+        let node = MerkleNode::from(params.dao_bulla.inner());
+        dao_tree.append(&node);
+        dao_tree.witness().unwrap()
+    };
+    debug!(target: "demo", "Created DAO bulla: {:?}", params.dao_bulla.inner());
+
+    // =======================================================
+    // Money::Transfer
+    //
+    // Mint the initial supply of treasury token
+    // and send it all to the DAO directly
+    // =======================================================
+    debug!(target: "demo", "Stage 2. Minting treasury token");
+
     Ok(())
     Ok(())
 }
 }