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

contract/money: Client spend-hook support.

parazyd 3 жил өмнө
parent
commit
fd79349982

+ 1 - 0
Cargo.lock

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

+ 8 - 2
bin/drk/src/main.rs

@@ -161,6 +161,12 @@ enum Subcmd {
 
         /// Recipient address
         recipient: String,
+
+        /// Mark if this is being sent to a DAO
+        dao: bool,
+
+        /// DAO bulla, if the tokens are being sent to a DAO
+        dao_bulla: Option<String>,
     },
 
     /// OTC atomic swap
@@ -534,7 +540,7 @@ async fn main() -> Result<()> {
             Ok(())
         }
 
-        Subcmd::Transfer { amount, token, recipient } => {
+        Subcmd::Transfer { amount, token, recipient, dao, dao_bulla } => {
             let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
             let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
             let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
@@ -546,7 +552,7 @@ async fn main() -> Result<()> {
             let drk = Drk { rpc_client };
 
             let tx = drk
-                .transfer(&amount, token_id, rcpt)
+                .transfer(&amount, token_id, rcpt, dao, dao_bulla)
                 .await
                 .with_context(|| "Failed to create payment transaction")?;
 

+ 1 - 0
bin/drk/src/rpc_swap.rs

@@ -67,6 +67,7 @@ impl Drk {
         let mut owncoins = self.wallet_coins(false).await?;
         // Then we see if we have one that we can send.
         owncoins.retain(|x| (x.0.note.value == value_send && x.0.note.token_id == token_send));
+        owncoins.retain(|x| (x.0.note.spend_hook == pallas::Base::zero()));
         if owncoins.is_empty() {
             return Err(anyhow!(
                 "Did not find any unspent coins of value {} and token_id {}",

+ 30 - 2
bin/drk/src/rpc_transfer.rs

@@ -20,15 +20,20 @@ use anyhow::{anyhow, Result};
 use darkfi::{
     tx::Transaction,
     util::parse::{decode_base10, encode_base10},
-    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
+    zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
     zkas::ZkBinary,
 };
+use darkfi_dao_contract::dao_model::DaoBulla;
 use darkfi_money_contract::{
     client::{build_transfer_tx, OwnCoin},
     MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
-    crypto::{contract_id::MONEY_CONTRACT_ID, Keypair, PublicKey, TokenId},
+    crypto::{
+        contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
+        Keypair, PublicKey, TokenId,
+    },
+    pasta::pallas,
     tx::ContractCall,
 };
 use darkfi_serial::Encodable;
@@ -43,13 +48,33 @@ impl Drk {
         amount: &str,
         token_id: TokenId,
         recipient: PublicKey,
+        dao: bool,
+        dao_bulla: Option<String>,
     ) -> Result<Transaction> {
+        let dao_bulla: Option<DaoBulla> = if dao {
+            let Some(dao_bulla) = dao_bulla else {
+                return Err(anyhow!("Missing DAO bulla in parameters"))
+            };
+
+            Some(DaoBulla::try_from(dao_bulla.as_str())?)
+        } else {
+            None
+        };
+
+        let (spend_hook, user_data, user_data_blind) = if dao {
+            (DAO_CONTRACT_ID.inner(), dao_bulla.unwrap().inner(), pallas::Base::random(&mut OsRng))
+        } else {
+            (pallas::Base::zero(), pallas::Base::zero(), pallas::Base::random(&mut OsRng))
+        };
+
         // First get all unspent OwnCoins to see what our balance is.
         eprintln!("Fetching OwnCoins");
         let owncoins = self.wallet_coins(false).await?;
         let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
         // We're only interested in the ones for the token_id we're sending
+        // And the ones not owned by some protocol (meaning spend-hook should be 0)
         owncoins.retain(|x| x.note.token_id == token_id);
+        owncoins.retain(|x| x.note.spend_hook == pallas::Base::zero());
         if owncoins.is_empty() {
             return Err(anyhow!("Did not find any coins with token ID: {}", token_id))
         }
@@ -109,6 +134,9 @@ impl Drk {
             &recipient,
             amount,
             token_id,
+            spend_hook,
+            user_data,
+            user_data_blind,
             &owncoins,
             &tree,
             &mint_zkbin,

+ 1 - 0
bin/drk/src/rpc_wallet.rs

@@ -350,6 +350,7 @@ impl Drk {
         let mut balmap: HashMap<String, u64> = HashMap::new();
 
         // Let's scan through the rows and see if we got anything.
+        // TODO: Separate tokens with spend-hook != 0
         for row in rows {
             let Some(row) = row.as_array() else {
                 return Err(anyhow!("Unexpected response from darkfid: {}", rep))

+ 5 - 2
bin/faucetd/src/main.rs

@@ -23,7 +23,7 @@ use async_trait::async_trait;
 use chrono::Utc;
 use darkfi::{
     tx::Transaction,
-    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
+    zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
     zkas::ZkBinary,
 };
 use darkfi_money_contract::{
@@ -40,7 +40,7 @@ use darkfi_sdk::{
     },
     db::SMART_CONTRACT_ZKAS_DB_NAME,
     incrementalmerkletree::bridgetree::BridgeTree,
-    pasta::group::ff::PrimeField,
+    pasta::{group::ff::PrimeField, pallas},
     tx::ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Encodable};
@@ -436,6 +436,9 @@ impl Faucetd {
             &pubkey,
             amount,
             token_id,
+            pallas::Base::zero(),
+            pallas::Base::zero(),
+            pallas::Base::random(&mut OsRng),
             &[], // <-- The faucet doesn't really have to pass OwnCoins I think
             &self.merkle_tree,
             &mint_zkbin,

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

@@ -12,6 +12,7 @@ crate-type = ["cdylib", "rlib"]
 darkfi-sdk = { path = "../../sdk" }
 darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
 darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
+bs58 = "0.4.0"
 
 # The following dependencies are used for the client API and
 # probably shouldn't be in WASM

+ 33 - 1
src/contract/dao/src/dao_model.rs

@@ -16,7 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::crypto::{pallas, pasta_prelude::*, MerkleNode, Nullifier, PublicKey};
+use darkfi_sdk::{
+    crypto::{pallas, pasta_prelude::*, MerkleNode, Nullifier, PublicKey},
+    error::ContractError,
+};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
@@ -34,6 +37,35 @@ impl From<pallas::Base> for DaoBulla {
     }
 }
 
+impl TryFrom<&str> for DaoBulla {
+    type Error = ContractError;
+
+    fn try_from(s: &str) -> Result<Self, Self::Error> {
+        let bytes: [u8; 32] = match bs58::decode(s).into_vec() {
+            Ok(v) => {
+                if v.len() != 32 {
+                    return Err(ContractError::IoError(
+                        "Decoded bs58 string for DaoBulla is not 32 bytes long".to_string(),
+                    ))
+                }
+
+                v.try_into().unwrap()
+            }
+            Err(e) => {
+                return Err(ContractError::IoError(format!(
+                    "Failed to decode bs58 for DaoBulla: {}",
+                    e
+                )))
+            }
+        };
+
+        match pallas::Base::from_repr(bytes).into() {
+            Some(v) => Ok(Self(v)),
+            None => Err(ContractError::IoError("Bytes for DaoBulla are noncanonical".to_string())),
+        }
+    }
+}
+
 #[derive(SerialEncodable, SerialDecodable)]
 pub struct DaoMintParams {
     pub dao_bulla: DaoBulla,

+ 8 - 11
src/contract/money/src/client.rs

@@ -827,6 +827,9 @@ pub fn build_half_swap_tx(
 /// * `pubkey` - Public key of the recipient
 /// * `value` - Value of the transfer
 /// * `token_id` - Token ID to transfer
+/// * `spend_hook` - Spend hook
+/// * `user_data` - User data
+/// * `user_data_blind` - Blinding for user data
 /// * `coins` - Set of coins we're able to spend
 /// * `tree` - Current Merkle tree of coins
 /// * `mint_zkbin` - ZkBinary of the mint circuit
@@ -841,6 +844,9 @@ pub fn build_transfer_tx(
     pubkey: &PublicKey,
     value: u64,
     token_id: TokenId,
+    spend_hook: pallas::Base,
+    user_data: pallas::Base,
+    user_data_blind: pallas::Base,
     coins: &[OwnCoin],
     tree: &MerkleTree,
     mint_zkbin: &ZkBinary,
@@ -948,11 +954,6 @@ pub fn build_transfer_tx(
         let signature_secret = SecretKey::random(&mut OsRng);
         signature_secrets.push(signature_secret);
 
-        // Disable composability for this old obsolete API
-        let spend_hook = pallas::Base::zero();
-        let user_data = pallas::Base::zero();
-        let user_data_blind = pallas::Base::random(&mut OsRng);
-
         info!(target: "money", "Creating transfer burn proof for input {}", i);
         let (proof, revealed) = create_transfer_burn_proof(
             burn_zkbin,
@@ -1000,10 +1001,6 @@ pub fn build_transfer_tx(
         let serial = pallas::Base::random(&mut OsRng);
         let coin_blind = pallas::Base::random(&mut OsRng);
 
-        // Disable composability for this old obsolete API
-        let spend_hook = pallas::Base::zero();
-        let user_data = pallas::Base::zero();
-
         info!(target: "money", "Creating transfer mint proof for output {}", i);
         let (proof, revealed) = create_transfer_mint_proof(
             mint_zkbin,
@@ -1026,8 +1023,8 @@ pub fn build_transfer_tx(
             serial,
             value: output.value,
             token_id: output.token_id,
-            spend_hook: pallas::Base::zero(),
-            user_data: pallas::Base::zero(),
+            spend_hook,
+            user_data,
             coin_blind,
             value_blind,
             token_blind,

+ 23 - 0
src/contract/money/tests/drop_pay_swap.rs

@@ -68,6 +68,11 @@ async fn money_contract_transfer() -> Result<()> {
     let alice_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
     let bob_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
 
+    // We're just going to be using a zero spend-hook and user-data
+    let spend_hook = pallas::Base::zero();
+    let user_data = pallas::Base::zero();
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
     let mut alice_owncoins = vec![];
     let mut bob_owncoins = vec![];
 
@@ -79,6 +84,9 @@ async fn money_contract_transfer() -> Result<()> {
         &th.alice_kp.public,
         ALICE_INITIAL,
         alice_token_id,
+        spend_hook,
+        user_data,
+        user_data_blind,
         &[],
         &th.faucet_merkle_tree,
         &th.mint_zkbin,
@@ -96,6 +104,9 @@ async fn money_contract_transfer() -> Result<()> {
         &th.bob_kp.public,
         BOB_INITIAL,
         bob_token_id,
+        spend_hook,
+        user_data,
+        user_data_blind,
         &[],
         &th.faucet_merkle_tree,
         &th.mint_zkbin,
@@ -207,6 +218,9 @@ async fn money_contract_transfer() -> Result<()> {
             &th.bob_kp.public,
             ALICE_FIRST_SEND,
             alice_token_id,
+            spend_hook,
+            user_data,
+            user_data_blind,
             &alice_owncoins,
             &th.alice_merkle_tree,
             &th.mint_zkbin,
@@ -302,6 +316,9 @@ async fn money_contract_transfer() -> Result<()> {
             &th.alice_kp.public,
             BOB_FIRST_SEND,
             bob_token_id,
+            spend_hook,
+            user_data,
+            user_data_blind,
             &bob_owncoins_tmp,
             &th.bob_merkle_tree,
             &th.mint_zkbin,
@@ -558,6 +575,9 @@ async fn money_contract_transfer() -> Result<()> {
             &th.alice_kp.public,
             ALICE_INITIAL,
             alice_token_id,
+            spend_hook,
+            user_data,
+            user_data_blind,
             &alice_owncoins,
             &th.alice_merkle_tree,
             &th.mint_zkbin,
@@ -635,6 +655,9 @@ async fn money_contract_transfer() -> Result<()> {
             &th.bob_kp.public,
             BOB_INITIAL,
             bob_token_id,
+            spend_hook,
+            user_data,
+            user_data_blind,
             &bob_owncoins,
             &th.bob_merkle_tree,
             &th.mint_zkbin,

+ 4 - 0
src/contract/money/tests/harness.rs

@@ -33,6 +33,7 @@ use darkfi_sdk::{
         pasta_prelude::*, ContractId, Keypair, MerkleTree, PublicKey, TokenId, MONEY_CONTRACT_ID,
     },
     db::SMART_CONTRACT_ZKAS_DB_NAME,
+    pasta::pallas,
     ContractCall,
 };
 use darkfi_serial::{serialize, Encodable};
@@ -218,6 +219,9 @@ impl MoneyTestHarness {
             rcpt,
             amount,
             token_id,
+            pallas::Base::zero(),
+            pallas::Base::zero(),
+            pallas::Base::random(&mut OsRng),
             &[],
             &self.faucet_merkle_tree,
             &self.mint_zkbin,