diff --git a/Cargo.lock b/Cargo.lock index 51ccb6324..61a3d0365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,7 @@ name = "darkfi-dao-contract" version = "0.3.0" dependencies = [ "async-std", + "bs58", "chacha20poly1305", "darkfi", "darkfi-money-contract", diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index ccdac8398..1c5c48b6f 100644 --- a/bin/drk/src/main.rs +++ b/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, }, /// 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")?; diff --git a/bin/drk/src/rpc_swap.rs b/bin/drk/src/rpc_swap.rs index 3204d4267..6b3c622ae 100644 --- a/bin/drk/src/rpc_swap.rs +++ b/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 {}", diff --git a/bin/drk/src/rpc_transfer.rs b/bin/drk/src/rpc_transfer.rs index fe5bc2815..ac5686001 100644 --- a/bin/drk/src/rpc_transfer.rs +++ b/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, ) -> Result { + let dao_bulla: Option = 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 = 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, diff --git a/bin/drk/src/rpc_wallet.rs b/bin/drk/src/rpc_wallet.rs index 7b5384444..073dbbe61 100644 --- a/bin/drk/src/rpc_wallet.rs +++ b/bin/drk/src/rpc_wallet.rs @@ -350,6 +350,7 @@ impl Drk { let mut balmap: HashMap = 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)) diff --git a/bin/faucetd/src/main.rs b/bin/faucetd/src/main.rs index d098ce9a0..22ab72fa4 100644 --- a/bin/faucetd/src/main.rs +++ b/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, diff --git a/src/contract/dao/Cargo.toml b/src/contract/dao/Cargo.toml index 68452df12..e18c88b37 100644 --- a/src/contract/dao/Cargo.toml +++ b/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 diff --git a/src/contract/dao/src/dao_model.rs b/src/contract/dao/src/dao_model.rs index 0a86452dd..fdde7ff1c 100644 --- a/src/contract/dao/src/dao_model.rs +++ b/src/contract/dao/src/dao_model.rs @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -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 for DaoBulla { } } +impl TryFrom<&str> for DaoBulla { + type Error = ContractError; + + fn try_from(s: &str) -> Result { + 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, diff --git a/src/contract/money/src/client.rs b/src/contract/money/src/client.rs index 715cff355..f8b2d1587 100644 --- a/src/contract/money/src/client.rs +++ b/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, diff --git a/src/contract/money/tests/drop_pay_swap.rs b/src/contract/money/tests/drop_pay_swap.rs index f01d5aecc..20515915b 100644 --- a/src/contract/money/tests/drop_pay_swap.rs +++ b/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, diff --git a/src/contract/money/tests/harness.rs b/src/contract/money/tests/harness.rs index b2810c3ff..41517dad2 100644 --- a/src/contract/money/tests/harness.rs +++ b/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,