diff --git a/Cargo.toml b/Cargo.toml index 9e36aeb..d5d8157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,8 @@ version = "0.1.0" authors = ["Alekos Filini "] [dependencies] -bitcoin = "~0.14.2" \ No newline at end of file +bitcoin = "~0.14.2" + +[dev-dependencies] +hex = "~0.3.2" +rand = "~0.4.6" diff --git a/src/contract.rs b/src/contract.rs index fb9e7d4..df10698 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -19,7 +19,9 @@ use traits::NeededTx; #[derive(Clone, Debug)] pub struct Contract { pub title: String, + /// Will be spent in the contract transaction pub issuance_utxo: OutPoint, + /// Will own the issued assets pub initial_owner_utxo: OutPoint, pub network: Network, pub total_supply: u32, diff --git a/src/output_entry.rs b/src/output_entry.rs index 501ee3b..7d92acb 100644 --- a/src/output_entry.rs +++ b/src/output_entry.rs @@ -5,8 +5,15 @@ use bitcoin::network::serialize::SimpleDecoder; use bitcoin::network::serialize::SimpleEncoder; use bitcoin::util::hash::Sha256dHash; +/// RGB output #[derive(Clone, Debug)] -pub struct OutputEntry(Sha256dHash, u32, Option); // asset_id, amount -> vout +pub struct OutputEntry( + /// Asset id + Sha256dHash, + /// Asset amount + u32, + /// Vout (optional): the index of this RGB output as bitcoin transaction output (?) + Option); impl OutputEntry { pub fn new(asset_id: Sha256dHash, amount: u32, vout: Option) -> OutputEntry { diff --git a/src/proof.rs b/src/proof.rs index 80a192e..a2d3e66 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -20,10 +20,14 @@ use traits::NeededTx; #[derive(Clone, Debug)] pub struct Proof { + /// The spent assets are held by these txos pub bind_to: Vec, + /// All the proofs of inputs txs pub input: Vec, + /// RGB outputs. If output entry.vout is None then use the index in this vector pub output: Vec, - pub contract: Option>, // Only needed for root proofs + /// Issuance contract, only needed for root proofs + pub contract: Option>, } impl Proof { @@ -42,6 +46,8 @@ impl Proof { return self.contract.is_some() && self.bind_to.len() == 1 && self.bind_to[0] == self.contract.as_ref().unwrap().initial_owner_utxo; } + /// Look for test_proof OutputEntries spent in the first elements of self.bind_to, + /// if test_proof is a first level parent of the tx associated to this proof (?) fn get_entries_for_us(&self, test_proof: &Proof, needed_txs: &HashMap<&NeededTx, Transaction>) -> Vec { // We know that [0] is equal to all others (checked in verify) let committing_tx_this = needed_txs.get(&NeededTx::WhichSpendsOutPoint(self.bind_to[0])).unwrap(); diff --git a/src/tests/contract.rs b/src/tests/contract.rs new file mode 100644 index 0000000..3cc38f6 --- /dev/null +++ b/src/tests/contract.rs @@ -0,0 +1,64 @@ +use bitcoin::util::hash::Sha256dHash; +use output_entry::OutputEntry; +use contract::Contract; +use bitcoin::OutPoint; +use bitcoin::network::constants::Network; +use bitcoin::Transaction; +use traits::{Verify, NeededTx}; +use std::collections::HashMap; +use bitcoin::TxOut; + +#[test] +fn output_entry() { + let asset_id = Sha256dHash::from_hex(&hex::encode([0x42; 32])).unwrap(); + let output_entry = OutputEntry::new(asset_id, 100, None); + assert_eq!(output_entry.get_asset_id(), asset_id); + assert_eq!(output_entry.get_amount(), 100); + assert_eq!(output_entry.get_vout(), None); + let output_entry = OutputEntry::new(Sha256dHash::default(), 100, Some(7)); + match output_entry.get_vout() { + Some(x) => assert_eq!(x, 7), + _ => panic!() + } +} + +#[test] +fn verify() { + let issuance_utxo = OutPoint {txid: Sha256dHash::default(), vout: 1000}; + let contract = Contract { + title: String::from("title"), + issuance_utxo, + initial_owner_utxo: issuance_utxo, + network: Network::Testnet, + total_supply: 12, + }; + let void_tx = Transaction {version: 1, lock_time: 0, input: vec![], output: vec![]}; + + let needed_txs = contract.get_needed_txs(); + assert_eq!(needed_txs.len(), 1); + // let NeededTx::WhichSpendsOutPoint(outpoint) = needed_txs[0]; + let outpoint = match needed_txs[0] { + NeededTx::WhichSpendsOutPoint(o) => o, + _ => panic!(), + }; + assert_eq!(outpoint, contract.issuance_utxo); + + let mut txs: HashMap<&NeededTx, Transaction> = [ + (&needed_txs[0], void_tx) + ].iter().cloned().collect(); + + assert_eq!(contract.verify(&txs), false); + + let commitment_out: TxOut = TxOut { + script_pubkey: contract.get_expected_script(), + value: 7 + }; + println!("test: expected {}", contract.get_expected_script()); + let issuance_tx = Transaction {version: 1, lock_time: 0, input: vec![], output: vec![commitment_out]}; + + txs = [ + (&needed_txs[0], issuance_tx) + ].iter().cloned().collect(); + + assert_eq!(contract.verify(&txs), true); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index e1b8725..6e0e66f 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,6 +1,4 @@ -// TODO! - -#[test] -fn it_works() { - assert_eq!(2 + 2, 4); -} \ No newline at end of file +extern crate hex; +extern crate rand; +mod contract; +mod proof; diff --git a/src/tests/proof.rs b/src/tests/proof.rs new file mode 100644 index 0000000..221d090 --- /dev/null +++ b/src/tests/proof.rs @@ -0,0 +1,82 @@ +use bitcoin::OutPoint; +use output_entry::OutputEntry; +use bitcoin::util::hash::Sha256dHash; +use proof::Proof; +use contract::Contract; +use traits::Verify; +use bitcoin::network::constants::Network; +use tests::rand::Rng; + + +fn make_txid() -> Sha256dHash { + let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); + Sha256dHash::from_hex(&hex::encode(random_bytes)).unwrap() +} + +fn mock_contract(initial_owner_txid: Sha256dHash) -> Contract { + Contract { + title: "Fake contract".to_string(), + issuance_utxo: OutPoint::default(), + initial_owner_utxo: OutPoint {txid: initial_owner_txid, vout: 42}, + network: Network::Testnet, + total_supply: 7 + } +} + +fn mock_root_proof(contract: Option>, initial_owner_txid: Sha256dHash) -> Proof { + Proof { + bind_to: vec![OutPoint {txid: initial_owner_txid, vout: 42}], + input: Vec::new(), + output: Vec::new(), + contract + } +} + +fn mock_proof(bind_to: Vec, input: Vec, output: Vec) -> Proof { + Proof {bind_to, input, output, contract: None} +} + + +#[test] +fn get_needed_txs() { + let initial_owner_txid = make_txid(); + let contract = mock_contract(initial_owner_txid); + let root = mock_root_proof(Some(Box::new(contract)), initial_owner_txid); + + let needed_txs = root.get_needed_txs(); + // 1 tx: contract + // 1 tx: root proof + assert_eq!(needed_txs.len(), 2); + + // Transaction #2 + let root_outpoint_0 = OutPoint { + txid: make_txid(), + vout: 42 + }; + let proof_1 = mock_proof(vec![root_outpoint_0], vec![root.clone()], vec![]); + assert_eq!(proof_1.get_needed_txs().len(), 3); + + // Transaction #3 + let root_outpoint_1 = OutPoint { + txid: root_outpoint_0.txid, + vout: 43 + }; + let proof_2 = mock_proof(vec![root_outpoint_1], vec![root.clone()], vec![]); + assert_eq!(proof_2.get_needed_txs().len(), 3); + + // Transaction #4 + let outpoint_tx2 = OutPoint { + txid: make_txid(), + vout: 42 + }; + let outpoint_tx3 = OutPoint { + txid: make_txid(), + vout: 42 + }; + let bind_to_3 = vec![outpoint_tx2, outpoint_tx3]; + let input_3 = vec![proof_1, proof_2]; + let proof_3 = mock_proof(bind_to_3, input_3, vec![]); + // The proof_3.get_needed_txs() vector has 2 duplicated entries + // TODO: check this behavior + assert_eq!(proof_3.get_needed_txs().len(), 8); +}