Explorar el Código

contract/money: Implement integration test.

parazyd hace 3 años
padre
commit
e2c22c28aa

+ 4 - 0
Cargo.lock

@@ -1354,6 +1354,7 @@ dependencies = [
 name = "darkfi-money-contract"
 name = "darkfi-money-contract"
 version = "0.3.0"
 version = "0.3.0"
 dependencies = [
 dependencies = [
+ "async-std",
  "chacha20poly1305",
  "chacha20poly1305",
  "darkfi",
  "darkfi",
  "darkfi-sdk",
  "darkfi-sdk",
@@ -1362,6 +1363,9 @@ dependencies = [
  "halo2_proofs",
  "halo2_proofs",
  "log",
  "log",
  "rand",
  "rand",
+ "simplelog",
+ "sled",
+ "sqlx",
 ]
 ]
 
 
 [[package]]
 [[package]]

+ 3 - 0
Cargo.toml

@@ -170,6 +170,7 @@ blockchain = [
     "pasta_curves",
     "pasta_curves",
     "rand",
     "rand",
     "sled",
     "sled",
+    "sqlx",
     "url",
     "url",
     "crypto_api_chachapoly",
     "crypto_api_chachapoly",
 
 
@@ -180,6 +181,7 @@ blockchain = [
     "net",
     "net",
     "tx",
     "tx",
     "util",
     "util",
+    "wallet",
     "wasm-runtime",
     "wasm-runtime",
 ]
 ]
 
 
@@ -319,6 +321,7 @@ wasm-runtime = [
     "wasmer-compiler-singlepass",
     "wasmer-compiler-singlepass",
     "wasmer-middlewares",
     "wasmer-middlewares",
 
 
+    "blockchain",
     "darkfi-sdk",
     "darkfi-sdk",
 ]
 ]
 
 

+ 14 - 1
src/contract/money/Cargo.toml

@@ -20,6 +20,13 @@ halo2_proofs = { version = "0.2.0", optional = true }
 log = { version = "0.4.17", optional = true }
 log = { version = "0.4.17", optional = true }
 rand = { version = "0.8.5", optional = true }
 rand = { version = "0.8.5", optional = true }
 
 
+# These are used just for the integration tests
+[dev-dependencies]
+async-std = {version = "1.12.0", features = ["attributes"]}
+darkfi = {path = "../../../", features = ["tx", "wasm-runtime"]}
+simplelog = "0.12.0"
+sled = "0.34.7"
+sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}
 
 
 # We need to disable random using "custom" which makes the crate a noop
 # We need to disable random using "custom" which makes the crate a noop
 # so the wasm32-unknown-unknown target is enabled.
 # so the wasm32-unknown-unknown target is enabled.
@@ -29,4 +36,10 @@ getrandom = { version = "0.2.8", features = ["custom"] }
 [features]
 [features]
 default = []
 default = []
 no-entrypoint = []
 no-entrypoint = []
-client = ["darkfi", "rand", "chacha20poly1305", "log", "halo2_proofs"]
+client = [
+    "darkfi",
+    "rand",
+    "chacha20poly1305",
+    "log",
+    "halo2_proofs",
+]

+ 2 - 1
src/contract/money/Makefile

@@ -28,7 +28,8 @@ money_contract.wasm: $(ZKAS_BIN) $(WASM_SRC)
 	$(CARGO) build --release --package darkfi-money-contract --target wasm32-unknown-unknown
 	$(CARGO) build --release --package darkfi-money-contract --target wasm32-unknown-unknown
 	cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_money_contract.wasm $@
 	cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_money_contract.wasm $@
 
 
-test:
+test: all
+	$(CARGO) test --release --features=no-entrypoint,client --package darkfi-money-contract
 
 
 clean:
 clean:
 	rm -f $(ZKAS_BIN) $(WASM_BIN)
 	rm -f $(ZKAS_BIN) $(WASM_BIN)

+ 15 - 6
src/contract/money/src/client.rs

@@ -49,11 +49,15 @@ use darkfi_sdk::{
 };
 };
 use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
 use halo2_proofs::{arithmetic::Field, circuit::Value};
 use halo2_proofs::{arithmetic::Field, circuit::Value};
-use log::{debug, error};
+use log::{debug, error, info};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 
 
 use crate::state::{ClearInput, Input, MoneyTransferParams, Output};
 use crate::state::{ClearInput, Input, MoneyTransferParams, Output};
 
 
+// Wallet SQL table constant names
+pub const MONEY_TREE_TABLE: &str = "money_tree";
+pub const MONEY_TREE_COL_TREE: &str = "tree";
+
 /// Byte length of the AEAD tag of the chacha20 cipher used for note encryption
 /// Byte length of the AEAD tag of the chacha20 cipher used for note encryption
 pub const AEAD_TAG_SIZE: usize = 16;
 pub const AEAD_TAG_SIZE: usize = 16;
 
 
@@ -145,12 +149,13 @@ impl Note {
 #[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
 #[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
 pub struct EncryptedNote {
 pub struct EncryptedNote {
     /// Ciphertext of the encrypted `Note`
     /// Ciphertext of the encrypted `Note`
-    ciphertext: Vec<u8>,
+    pub ciphertext: Vec<u8>,
     /// Ephemeral public key created at the time of encrypting the note
     /// Ephemeral public key created at the time of encrypting the note
-    ephem_public: PublicKey,
+    pub ephem_public: PublicKey,
 }
 }
 
 
 impl EncryptedNote {
 impl EncryptedNote {
+    /// Attempt to decrypt an `EncryptedNote` given a secret key.
     pub fn decrypt(&self, secret: &SecretKey) -> Result<Note> {
     pub fn decrypt(&self, secret: &SecretKey) -> Result<Note> {
         let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
         let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
         let key = kdf_sapling(&shared_secret, &self.ephem_public);
         let key = kdf_sapling(&shared_secret, &self.ephem_public);
@@ -460,7 +465,9 @@ pub fn build_transfer_tx(
 ) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>)> {
 ) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>)> {
     debug!("Building money contract transaction");
     debug!("Building money contract transaction");
     assert!(value != 0);
     assert!(value != 0);
-    assert!(!coins.is_empty());
+    if !clear_input {
+        assert!(!coins.is_empty());
+    }
     // Ensure the coins given to us are all of the same token_id.
     // Ensure the coins given to us are all of the same token_id.
     // The money contract base transfer doesn't allow conversions.
     // The money contract base transfer doesn't allow conversions.
     for coin in coins.iter() {
     for coin in coins.iter() {
@@ -548,7 +555,7 @@ pub fn build_transfer_tx(
     let mut output_blinds = vec![];
     let mut output_blinds = vec![];
     let mut zk_proofs = vec![];
     let mut zk_proofs = vec![];
 
 
-    for input in inputs {
+    for (i, input) in inputs.iter().enumerate() {
         let value_blind = ValueBlind::random(&mut OsRng);
         let value_blind = ValueBlind::random(&mut OsRng);
         input_blinds.push(value_blind);
         input_blinds.push(value_blind);
 
 
@@ -560,6 +567,7 @@ pub fn build_transfer_tx(
         let user_data = pallas::Base::zero();
         let user_data = pallas::Base::zero();
         let user_data_blind = pallas::Base::random(&mut OsRng);
         let user_data_blind = pallas::Base::random(&mut OsRng);
 
 
+        info!("Creating transfer burn proof for input {}", i);
         let (proof, revealed) = create_transfer_burn_proof(
         let (proof, revealed) = create_transfer_burn_proof(
             burn_zkbin,
             burn_zkbin,
             burn_pk,
             burn_pk,
@@ -574,7 +582,7 @@ pub fn build_transfer_tx(
             input.note.coin_blind,
             input.note.coin_blind,
             input.secret,
             input.secret,
             input.leaf_position,
             input.leaf_position,
-            input.merkle_path,
+            input.merkle_path.clone(),
             signature_secret,
             signature_secret,
         )?;
         )?;
 
 
@@ -610,6 +618,7 @@ pub fn build_transfer_tx(
         let spend_hook = pallas::Base::zero();
         let spend_hook = pallas::Base::zero();
         let user_data = pallas::Base::zero();
         let user_data = pallas::Base::zero();
 
 
+        info!("Creating transfer mint proof for output {}", i);
         let (proof, revealed) = create_transfer_mint_proof(
         let (proof, revealed) = create_transfer_mint_proof(
             mint_zkbin,
             mint_zkbin,
             mint_pk,
             mint_pk,

+ 355 - 0
src/contract/money/tests/contract_exec.rs

@@ -0,0 +1,355 @@
+/* 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/>.
+ */
+
+//! In this test module we make sure execution of the contract works as it is
+//! intended to. We initialize a state, deploy the contract, create a clear
+//! input, and then we try to spend it.
+//! Let's see if we manage.
+use std::{collections::HashMap, io::Cursor};
+
+use darkfi::{
+    blockchain::Blockchain,
+    consensus::constants::{TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP},
+    crypto::proof::{ProvingKey, VerifyingKey},
+    runtime::vm_runtime::Runtime,
+    tx::Transaction,
+    util::parse::decode_base10,
+    zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        constants::MERKLE_DEPTH, poseidon_hash, ContractId, Keypair, MerkleNode, Nullifier,
+        PublicKey, TokenId,
+    },
+    incrementalmerkletree::{bridgetree::BridgeTree, Tree},
+    pasta::{
+        group::ff::{Field, PrimeField},
+        pallas,
+    },
+    tx::ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
+use log::info;
+use rand::rngs::OsRng;
+
+use darkfi_money_contract::{
+    client::{build_transfer_tx, Coin, EncryptedNote, OwnCoin},
+    state::MoneyTransferParams,
+    MoneyFunction,
+};
+
+#[async_std::test]
+async fn money_contract_execution() -> Result<()> {
+    // Debug log configuration
+    let mut cfg = simplelog::ConfigBuilder::new();
+    cfg.add_filter_ignore("sled".to_string());
+    simplelog::TermLogger::init(
+        simplelog::LevelFilter::Debug,
+        //simplelog::LevelFilter::Info,
+        cfg.build(),
+        simplelog::TerminalMode::Mixed,
+        simplelog::ColorChoice::Auto,
+    )?;
+
+    // Our main sled database references which live in memory during this test.
+    info!("Initializing sled DBs");
+    let faucet_sled_db = sled::Config::new().temporary(true).open()?;
+    let alice_sled_db = sled::Config::new().temporary(true).open()?;
+    let faucet_blockchain =
+        Blockchain::new(&faucet_sled_db, *TESTNET_GENESIS_TIMESTAMP, *TESTNET_GENESIS_HASH_BYTES)?;
+    let alice_blockchain =
+        Blockchain::new(&alice_sled_db, *TESTNET_GENESIS_TIMESTAMP, *TESTNET_GENESIS_HASH_BYTES)?;
+
+    // A keypair we can use for the faucet whitelist
+    let faucet_kp = Keypair::random(&mut OsRng);
+
+    // A keypair we'll use for Alice
+    let alice_kp = Keypair::random(&mut OsRng);
+
+    // We deploy the contract natively and initialize its state.
+    info!("Deploying WASM contract");
+    let wasm_bincode = include_bytes!("../money_contract.wasm");
+    let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+    let mut faucet_runtime =
+        Runtime::new(&wasm_bincode[..], faucet_blockchain.clone(), contract_id)?;
+    let mut alice_runtime = Runtime::new(&wasm_bincode[..], alice_blockchain.clone(), contract_id)?;
+
+    let faucet_pubkeys = vec![faucet_kp.public];
+    // Serialize the payload for the init/deploy function of the contract and run the deploy.
+    let payload = serialize(&faucet_pubkeys);
+    faucet_runtime.deploy(&payload)?;
+    alice_runtime.deploy(&payload)?;
+
+    // At this point we've deployed the contract and we can begin executing it.
+    // When the contract is deployed, we should be able to access everything from
+    // the sled databases. We do it here just to confirm correct behaviour.
+    info!("Looking up zkas circuits from DB");
+    let zkas_tree = String::from("zkas");
+    let zkas_mint_ns = String::from("Mint");
+    let zkas_burn_ns = String::from("Burn");
+    let db_handle =
+        alice_blockchain.contracts.lookup(&alice_blockchain.sled_db, &contract_id, &zkas_tree)?;
+    let mint_zkbin = db_handle.get(&serialize(&zkas_mint_ns))?.unwrap();
+    let burn_zkbin = db_handle.get(&serialize(&zkas_burn_ns))?.unwrap();
+    info!("Decoding bincode");
+    let mint_zkbin = ZkBinary::decode(&mint_zkbin.clone())?;
+    let burn_zkbin = ZkBinary::decode(&burn_zkbin.clone())?;
+    let mint_witnesses = empty_witnesses(&mint_zkbin);
+    let burn_witnesses = empty_witnesses(&burn_zkbin);
+    let mint_circuit = ZkCircuit::new(mint_witnesses, mint_zkbin.clone());
+    let burn_circuit = ZkCircuit::new(burn_witnesses, burn_zkbin.clone());
+
+    info!("Creating zk proving keys");
+    let k = 13;
+    let mut proving_keys = HashMap::<[u8; 32], Vec<(String, ProvingKey)>>::new();
+    let mint_pk = ProvingKey::build(k, &mint_circuit);
+    let burn_pk = ProvingKey::build(k, &burn_circuit);
+    let pks =
+        vec![(zkas_mint_ns.clone(), mint_pk.clone()), (zkas_burn_ns.clone(), burn_pk.clone())];
+    proving_keys.insert(contract_id.inner().to_repr(), pks);
+
+    info!("Creating zk verifying keys");
+    let mut verifying_keys = HashMap::<[u8; 32], Vec<(String, VerifyingKey)>>::new();
+    let mint_vk = VerifyingKey::build(k, &mint_circuit);
+    let burn_vk = VerifyingKey::build(k, &burn_circuit);
+    let vks =
+        vec![(zkas_mint_ns.clone(), mint_vk.clone()), (zkas_burn_ns.clone(), burn_vk.clone())];
+    verifying_keys.insert(contract_id.inner().to_repr(), vks);
+
+    // We also have to initialize the Merkle trees used for coins.
+    info!("Initializing Merkle trees");
+    let mut faucet_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+    let mut alice_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+
+    // The faucet will now mint some tokens for Alice.
+    let token_id = TokenId::from(pallas::Base::random(&mut OsRng));
+    let amount = decode_base10("42.69", 8, true)?;
+
+    info!("Building transfer tx for clear inputs");
+    let (params, proofs, secret_keys) = build_transfer_tx(
+        &faucet_kp,
+        &alice_kp.public,
+        amount,
+        token_id,
+        &[],
+        &faucet_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+        true,
+    )?;
+
+    // Build transaction
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &secret_keys)?;
+    tx.signatures = vec![sigs];
+
+    // Let's first execute this transaction for the faucet to see if it passes.
+    // Then Alice gets the tx and also executes it.
+    info!("Executing transaction on the faucet's blockchain db");
+    verify_transaction(&faucet_blockchain, &tx)?;
+    info!("Adding coin to faucet's Merkle tree");
+    faucet_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    info!("Executing transaction on Alice's blockchain db");
+    verify_transaction(&alice_blockchain, &tx)?;
+    // TODO: FIXME: Actually have a look at the `merkle_add` calls
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    let leaf_position = alice_merkle_tree.witness().unwrap();
+
+    // If the above succeeded, the state has been written, so Alice should have
+    // the minted coin. In practice, Alice's node should get the transaction, scan
+    // it, and add it to her wallet. In this test unit, we abstract that away for
+    // simplicity reasons. I should make some kind of API for this though.
+    info!("Deserializing params");
+    let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
+    let output = &params.outputs[0];
+    info!("Decrypting output note");
+    let encrypted_note =
+        EncryptedNote { ciphertext: output.ciphertext.clone(), ephem_public: output.ephem_public };
+    let note = encrypted_note.decrypt(&alice_kp.secret)?;
+
+    // Now since Alice got an output and a note to decrypt, we make the coin
+    // metadata so we can spend it.
+    let owncoin = OwnCoin {
+        coin: Coin::from(output.coin),
+        note: note.clone(),
+        secret: alice_kp.secret, // <-- What should this be?
+        nullifier: Nullifier::from(poseidon_hash([alice_kp.secret.inner(), note.serial])),
+        leaf_position,
+    };
+
+    // Alice can spend the coin and send another one to herself
+    info!("Building transfer tx for Alice from Alice");
+    let (params, proofs, secret_keys) = build_transfer_tx(
+        &alice_kp,
+        &alice_kp.public,
+        amount,
+        token_id,
+        &[owncoin],
+        &alice_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+        false,
+    )?;
+
+    // Build transaction
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &secret_keys)?;
+    tx.signatures = vec![sigs];
+
+    info!("Executing transaction on the faucet's blockchain db");
+    verify_transaction(&faucet_blockchain, &tx)?;
+    info!("Adding coin to faucet's Merkle tree");
+    faucet_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    info!("Executing transaction on Alice's blockchain db");
+    verify_transaction(&alice_blockchain, &tx)?;
+    // TODO: FIXME: Actually have a look at the `merkle_add` calls
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    let leaf_position = alice_merkle_tree.witness().unwrap();
+
+    // And again
+
+    info!("Deserializing params");
+    let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
+    let output = &params.outputs[0];
+    info!("Decrypting output note");
+    let encrypted_note =
+        EncryptedNote { ciphertext: output.ciphertext.clone(), ephem_public: output.ephem_public };
+    let note = encrypted_note.decrypt(&alice_kp.secret)?;
+
+    // Now since Alice got an output and a note to decrypt, we make the coin
+    // metadata so we can spend it.
+    let owncoin = OwnCoin {
+        coin: Coin::from(output.coin),
+        note: note.clone(),
+        secret: alice_kp.secret, // <-- What should this be?
+        nullifier: Nullifier::from(poseidon_hash([alice_kp.secret.inner(), note.serial])),
+        leaf_position,
+    };
+
+    // Alice can spend the coin and send another one to herself
+    info!("Building transfer tx for Alice from Alice");
+    let (params, proofs, secret_keys) = build_transfer_tx(
+        &alice_kp,
+        &alice_kp.public,
+        amount,
+        token_id,
+        &[owncoin],
+        &alice_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+        false,
+    )?;
+
+    // Build transaction
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &secret_keys)?;
+    tx.signatures = vec![sigs];
+
+    info!("Executing transaction on the faucet's blockchain db");
+    verify_transaction(&faucet_blockchain, &tx)?;
+    info!("Adding coin to faucet's Merkle tree");
+    faucet_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    info!("Executing transaction on Alice's blockchain db");
+    verify_transaction(&alice_blockchain, &tx)?;
+    // TODO: FIXME: Actually have a look at the `merkle_add` calls
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    Ok(())
+}
+
+fn verify_transaction(blockchain: &Blockchain, tx: &Transaction) -> Result<()> {
+    info!("Begin transcation verification");
+    // Table of public inputs used for ZK proof verification
+    let mut zkp_table = vec![];
+    // Table of public keys used for signature verification
+    let mut sig_table = vec![];
+    // State updates produced by contract execution
+    let mut updates = vec![];
+
+    // Iterate over all calls to get the metadata
+    for (idx, call) in tx.calls.iter().enumerate() {
+        info!("Verifying contract call {}", idx);
+        let bincode = blockchain.wasm_bincode.get(call.contract_id)?;
+        info!("Found wasm bincode for {}", call.contract_id);
+
+        // Write the actual payload data
+        let mut payload = vec![];
+        payload.write_u32(idx as u32)?; // Call index
+        tx.calls.encode(&mut payload)?; // Actual call_data
+
+        // Instantiate the wasm runtime
+        let mut runtime = Runtime::new(&bincode, blockchain.clone(), call.contract_id)?;
+        info!("Executing \"metadata\" call");
+        let metadata = runtime.metadata(&payload)?;
+        let mut decoder = Cursor::new(&metadata);
+        let zkp_pub: Vec<(String, Vec<pallas::Base>)> = Decodable::decode(&mut decoder)?;
+        let sig_pub: Vec<PublicKey> = Decodable::decode(&mut decoder)?;
+        zkp_table.push(zkp_pub);
+        sig_table.push(sig_pub);
+        info!("Successfully executed \"metadata\" call");
+
+        info!("Executing \"exec\" call");
+        let update = runtime.exec(&payload)?;
+        updates.push(update);
+        info!("Successfully executed \"exec\" call");
+    }
+
+    info!("Verifying transaction signatures");
+    tx.verify_sigs(sig_table)?;
+    info!("Signatures verified successfully");
+
+    info!("Verifying transaction ZK proofs");
+    tx.verify_zkps(zkp_table)?;
+    info!("Transaction ZK proofs verified successfully");
+
+    // After the verification stage has passed, just apply all the changes.
+    info!("Performing state updates");
+    assert!(tx.calls.len() == updates.len());
+    for (call, update) in tx.calls.iter().zip(updates.iter()) {
+        let bincode = blockchain.wasm_bincode.get(call.contract_id)?;
+        let mut runtime = Runtime::new(&bincode, blockchain.clone(), call.contract_id)?;
+        info!("Executing \"apply\" call");
+        runtime.apply(&update)?;
+        info!("Successfully executed \"apply\" call");
+    }
+
+    info!("Transaction verified successfully");
+    Ok(())
+}

+ 25 - 2
src/tx/mod.rs

@@ -18,14 +18,15 @@
 
 
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
-        schnorr::{SchnorrPublic, Signature},
-        PublicKey,
+        schnorr::{SchnorrPublic, SchnorrSecret, Signature},
+        PublicKey, SecretKey,
     },
     },
     pasta::pallas,
     pasta::pallas,
     tx::ContractCall,
     tx::ContractCall,
 };
 };
 use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
 use log::{debug, error};
 use log::{debug, error};
+use rand::{CryptoRng, RngCore};
 
 
 use crate::{crypto::Proof, Error, Result};
 use crate::{crypto::Proof, Error, Result};
 
 
@@ -51,11 +52,13 @@ impl Transaction {
     pub fn verify_sigs(&self, pub_table: Vec<Vec<PublicKey>>) -> Result<()> {
     pub fn verify_sigs(&self, pub_table: Vec<Vec<PublicKey>>) -> Result<()> {
         let tx_data = self.encode_without_sigs()?;
         let tx_data = self.encode_without_sigs()?;
         let data_hash = blake3::hash(&tx_data);
         let data_hash = blake3::hash(&tx_data);
+        debug!("tx.verify_sigs: data_hash: {:?}", data_hash.as_bytes());
 
 
         assert!(pub_table.len() == self.signatures.len());
         assert!(pub_table.len() == self.signatures.len());
 
 
         for (i, (sigs, pubkeys)) in self.signatures.iter().zip(pub_table.iter()).enumerate() {
         for (i, (sigs, pubkeys)) in self.signatures.iter().zip(pub_table.iter()).enumerate() {
             for (pubkey, signature) in pubkeys.iter().zip(sigs) {
             for (pubkey, signature) in pubkeys.iter().zip(sigs) {
+                debug!("Verifying signature with public key: {}", pubkey);
                 if !pubkey.verify(&data_hash.as_bytes()[..], &signature) {
                 if !pubkey.verify(&data_hash.as_bytes()[..], &signature) {
                     error!("tx::verify_sigs[{}] failed to verify", i);
                     error!("tx::verify_sigs[{}] failed to verify", i);
                     return Err(Error::InvalidSignature)
                     return Err(Error::InvalidSignature)
@@ -67,6 +70,26 @@ impl Transaction {
         Ok(())
         Ok(())
     }
     }
 
 
+    /// Create Schnorr signatures for the entire transaction.
+    pub fn create_sigs(
+        &self,
+        rng: &mut (impl CryptoRng + RngCore),
+        secret_keys: &[SecretKey],
+    ) -> Result<Vec<Signature>> {
+        let tx_data = self.encode_without_sigs()?;
+        let data_hash = blake3::hash(&tx_data);
+        debug!("tx.create_sigs: data_hash: {:?}", data_hash.as_bytes());
+
+        let mut sigs = vec![];
+        for secret in secret_keys {
+            debug!("Creating signature with public key: {}", PublicKey::from_secret(*secret));
+            let signature = secret.sign(rng, &data_hash.as_bytes()[..]);
+            sigs.push(signature);
+        }
+
+        Ok(sigs)
+    }
+
     /// Encode the object into a byte vector for signing
     /// Encode the object into a byte vector for signing
     pub fn encode_without_sigs(&self) -> Result<Vec<u8>> {
     pub fn encode_without_sigs(&self) -> Result<Vec<u8>> {
         let mut buf = vec![];
         let mut buf = vec![];