From cc12798d7ac3864e345847c0647f69cf1426f647 Mon Sep 17 00:00:00 2001 From: Antioch Peverell <30642645+antiochp@users.noreply.github.com> Date: Fri, 2 Mar 2018 15:47:27 -0500 Subject: [PATCH] Merkle Proofs (#716) * family_branch() to recursively call family() up the branch todo - we hit a peak, then we need to get to the root somehow - actually get the hashes to build the proof * wip * some additional testing around merkle tree branches * track left/right branch for each sibling as we build the merkle path up * MerkleProof and basic (incomplete) verify fn * I think a MerkleProof verifies correctly now need to test on test case with multiple peaks * basic pmmr merkle proof working * MerkleProof now serializable/deserializable * coinbase maturity via merkle proof basically working * ser/deser merkle proof into hex in api and wallet.dat * cleanup * wip - temporarily saving merkle proofs to the commit index * assert merkle proof in store matches the rewound version there are cases where it does not... * commit * commit * can successfully rewind the output PMMR and generate a Merkle proof need to fix the tests up now and cleanup the code and add docs for functions etc. * core tests passing * fixup chain tests using merkle proofs * pool tests working with merkle proofs * api tests working with merkle proof * fix the broken comapct block hashing behavior made nonce for short_ids explicit to help with this * cleanup and comment as necessary * cleanup variety of TODOs --- api/src/handlers.rs | 2 +- api/src/types.rs | 50 +- chain/src/chain.rs | 45 +- chain/src/sumtree.rs | 78 ++- chain/src/types.rs | 4 +- chain/tests/mine_simple_chain.rs | 17 +- chain/tests/test_coinbase_maturity.rs | 27 +- core/src/core/block.rs | 73 ++- core/src/core/build.rs | 28 +- core/src/core/mod.rs | 20 +- core/src/core/pmmr.rs | 668 ++++++++++++++++++++++---- core/src/core/transaction.rs | 545 +++++++++++++-------- grin/src/adapters.rs | 2 +- pool/src/blockchain.rs | 8 +- pool/src/graph.rs | 2 + pool/src/pool.rs | 54 ++- pool/src/types.rs | 9 +- store/src/pmmr.rs | 41 +- wallet/src/checker.rs | 15 +- wallet/src/receiver.rs | 9 +- wallet/src/restore.rs | 7 +- wallet/src/sender.rs | 19 +- wallet/src/types.rs | 65 ++- 23 files changed, 1260 insertions(+), 528 deletions(-) diff --git a/api/src/handlers.rs b/api/src/handlers.rs index acefd9ad5..e8d579a02 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -128,7 +128,7 @@ impl UtxoHandler { commitments.is_empty() || commitments.contains(&output.commit) }) .map(|output| { - OutputPrintable::from_output(output, w(&self.chain), include_proof) + OutputPrintable::from_output(output, w(&self.chain), &block, include_proof) }) .collect(); BlockOutputs { diff --git a/api/src/types.rs b/api/src/types.rs index 9b51c0b73..0ecfaf70b 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ use std::sync::Arc; use core::{core, ser}; use core::core::hash::Hashed; +use core::core::pmmr::MerkleProof; use core::core::SwitchCommitHash; use chain; use p2p; use util; -use util::LOGGER; use util::secp::pedersen; use util::secp::constants::MAX_PROOF_SIZE; use serde; @@ -226,12 +226,15 @@ pub struct OutputPrintable { pub proof: Option, /// Rangeproof hash (as hex string) pub proof_hash: String, + + pub merkle_proof: Option, } impl OutputPrintable { pub fn from_output( output: &core::Output, chain: Arc, + block: &core::Block, include_proof: bool, ) -> OutputPrintable { let output_type = @@ -250,6 +253,17 @@ impl OutputPrintable { None }; + // Get the Merkle proof for all unspent coinbase outputs (to verify maturity on spend). + // We obtain the Merkle proof by rewinding the PMMR. + // We require the rewind() to be stable even after the PMMR is pruned and compacted + // so we can still recreate the necessary proof. + let mut merkle_proof = None; + if output.features.contains(core::transaction::OutputFeatures::COINBASE_OUTPUT) + && !spent + { + merkle_proof = chain.get_merkle_proof(&out_id, &block).ok() + }; + OutputPrintable { output_type, commit: output.commit, @@ -257,6 +271,7 @@ impl OutputPrintable { spent, proof, proof_hash: util::to_hex(output.proof.hash().to_vec()), + merkle_proof, } } @@ -277,13 +292,17 @@ impl OutputPrintable { impl serde::ser::Serialize for OutputPrintable { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer { - let mut state = serializer.serialize_struct("OutputPrintable", 6)?; + let mut state = serializer.serialize_struct("OutputPrintable", 7)?; state.serialize_field("output_type", &self.output_type)?; state.serialize_field("commit", &util::to_hex(self.commit.0.to_vec()))?; state.serialize_field("switch_commit_hash", &self.switch_commit_hash.to_hex())?; state.serialize_field("spent", &self.spent)?; state.serialize_field("proof", &self.proof)?; state.serialize_field("proof_hash", &self.proof_hash)?; + + let hex_merkle_proof = &self.merkle_proof.clone().map(|x| x.to_hex()); + state.serialize_field("merkle_proof", &hex_merkle_proof)?; + state.end() } } @@ -299,7 +318,8 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable { SwitchCommitHash, Spent, Proof, - ProofHash + ProofHash, + MerkleProof } struct OutputPrintableVisitor; @@ -319,6 +339,7 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable { let mut spent = None; let mut proof = None; let mut proof_hash = None; + let mut merkle_proof = None; while let Some(key) = map.next_key()? { match key { @@ -365,6 +386,16 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable { Field::ProofHash => { no_dup!(proof_hash); proof_hash = Some(map.next_value()?) + }, + Field::MerkleProof => { + no_dup!(merkle_proof); + if let Some(hex) = map.next_value::>()? { + if let Ok(res) = MerkleProof::from_hex(&hex) { + merkle_proof = Some(res); + } else { + merkle_proof = Some(MerkleProof::empty()); + } + } } } } @@ -375,7 +406,8 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable { switch_commit_hash: switch_commit_hash.unwrap(), spent: spent.unwrap(), proof: proof, - proof_hash: proof_hash.unwrap() + proof_hash: proof_hash.unwrap(), + merkle_proof: merkle_proof, }) } } @@ -498,7 +530,7 @@ impl BlockPrintable { let outputs = block .outputs .iter() - .map(|output| OutputPrintable::from_output(output, chain.clone(), include_proof)) + .map(|output| OutputPrintable::from_output(output, chain.clone(), &block, include_proof)) .collect(); let kernels = block .kernels @@ -532,10 +564,11 @@ impl CompactBlockPrintable { cb: &core::CompactBlock, chain: Arc, ) -> CompactBlockPrintable { + let block = chain.get_block(&cb.hash()).unwrap(); let out_full = cb .out_full .iter() - .map(|x| OutputPrintable::from_output(x, chain.clone(), false)) + .map(|x| OutputPrintable::from_output(x, chain.clone(), &block, false)) .collect(); let kern_full = cb .kern_full @@ -584,7 +617,8 @@ mod test { \"switch_commit_hash\":\"85daaf11011dc11e52af84ebe78e2f2d19cbdc76000000000000000000000000\",\ \"spent\":false,\ \"proof\":null,\ - \"proof_hash\":\"ed6ba96009b86173bade6a9227ed60422916593fa32dd6d78b25b7a4eeef4946\"\ + \"proof_hash\":\"ed6ba96009b86173bade6a9227ed60422916593fa32dd6d78b25b7a4eeef4946\",\ + \"merkle_proof\":null\ }"; let deserialized: OutputPrintable = serde_json::from_str(&hex_output).unwrap(); let serialized = serde_json::to_string(&deserialized).unwrap(); diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 7d6a1e02e..c9c75b0c3 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -20,12 +20,11 @@ use std::fs::File; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant}; -use core::core::{Input, OutputIdentifier, OutputStoreable, TxKernel}; +use core::core::{Block, BlockHeader, Input, OutputFeatures, OutputIdentifier, OutputStoreable, TxKernel}; use core::core::hash::{Hash, Hashed}; -use core::global; - -use core::core::{Block, BlockHeader}; +use core::core::pmmr::MerkleProof; use core::core::target::Difficulty; +use core::global; use grin_store::Error::NotFoundErr; use pipe; use store; @@ -388,7 +387,7 @@ impl Chain { /// Return an error if the output does not exist or has been spent. /// This querying is done in a way that is consistent with the current chain state, /// specifically the current winning (valid, most work) fork. - pub fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<(), Error> { + pub fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result { let mut sumtrees = self.sumtrees.write().unwrap(); sumtrees.is_unspent(output_ref) } @@ -406,8 +405,14 @@ impl Chain { /// This only applies to inputs spending coinbase outputs. /// An input spending a non-coinbase output will always pass this check. pub fn is_matured(&self, input: &Input, height: u64) -> Result<(), Error> { - let mut sumtrees = self.sumtrees.write().unwrap(); - sumtrees.is_matured(input, height) + if input.features.contains(OutputFeatures::COINBASE_OUTPUT) { + let mut sumtrees = self.sumtrees.write().unwrap(); + let output = OutputIdentifier::from_input(&input); + let hash = sumtrees.is_unspent(&output)?; + let header = self.get_block_header(&input.block_hash())?; + input.verify_maturity(hash, &header, height)?; + } + Ok(()) } /// Sets the sumtree roots on a brand new block by applying the block on the @@ -432,6 +437,22 @@ impl Chain { Ok(()) } + /// Return a pre-built Merkle proof for the given commitment from the store. + pub fn get_merkle_proof( + &self, + output: &OutputIdentifier, + block: &Block, + ) -> Result { + let mut sumtrees = self.sumtrees.write().unwrap(); + + let merkle_proof = sumtree::extending(&mut sumtrees, |extension| { + extension.force_rollback(); + extension.merkle_proof_via_rewind(output, block) + })?; + + Ok(merkle_proof) + } + /// Returns current sumtree roots pub fn get_sumtree_roots( &self, @@ -613,8 +634,10 @@ impl Chain { store::DifficultyIter::from(head.last_block_h, self.store.clone()) } - /// Check whether we have a block without reading it - pub fn block_exists(&self, h: Hash) -> Result { - self.store.block_exists(&h).map_err(|e| Error::StoreErr(e, "chain block exists".to_owned())) - } + /// Check whether we have a block without reading it + pub fn block_exists(&self, h: Hash) -> Result { + self.store + .block_exists(&h) + .map_err(|e| Error::StoreErr(e, "chain block exists".to_owned())) + } } diff --git a/chain/src/sumtree.rs b/chain/src/sumtree.rs index d3eb6041b..39b52665a 100644 --- a/chain/src/sumtree.rs +++ b/chain/src/sumtree.rs @@ -1,3 +1,4 @@ +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,7 +28,7 @@ use util::secp::pedersen::{RangeProof, Commitment}; use core::consensus::reward; use core::core::{Block, BlockHeader, Input, Output, OutputIdentifier, OutputFeatures, OutputStoreable, TxKernel}; -use core::core::pmmr::{self, PMMR}; +use core::core::pmmr::{self, PMMR, MerkleProof}; use core::core::hash::{Hash, Hashed}; use core::ser::{self, PMMRable}; @@ -106,10 +107,10 @@ impl SumTrees { }) } - /// Check is an output is unspent. + /// Check if an output is unspent. /// We look in the index to find the output MMR pos. /// Then we check the entry in the output MMR and confirm the hash matches. - pub fn is_unspent(&mut self, output_id: &OutputIdentifier) -> Result<(), Error> { + pub fn is_unspent(&mut self, output_id: &OutputIdentifier) -> Result { match self.commit_index.get_output_pos(&output_id.commit) { Ok(pos) => { let output_pmmr:PMMR = PMMR::at( @@ -117,11 +118,9 @@ impl SumTrees { self.utxo_pmmr_h.last_pos, ); if let Some((hash, _)) = output_pmmr.get(pos, false) { - println!("Getting output ID hash"); if hash == output_id.hash() { - Ok(()) + Ok(hash) } else { - println!("MISMATCH BECAUSE THE BLOODY THING MISMATCHES"); Err(Error::SumTreeErr(format!("sumtree hash mismatch"))) } } else { @@ -133,35 +132,6 @@ impl SumTrees { } } - /// Check the output being spent by the input has sufficiently matured. - /// This only applies for coinbase outputs being spent (1,000 blocks). - /// Non-coinbase outputs will always pass this check. - /// For a coinbase output we find the block by the block hash provided in the input - /// and check coinbase maturty based on the height of this block. - pub fn is_matured( - &mut self, - input: &Input, - height: u64, - ) -> Result<(), Error> { - // We should never be in a situation where we are checking maturity rules - // if the output is already spent (this should have already been checked). - let output = OutputIdentifier::from_input(&input); - assert!(self.is_unspent(&output).is_ok()); - - // At this point we can be sure the input is spending the output - // it claims to be spending, and that it is coinbase or non-coinbase. - // If we are spending a coinbase output then go find the block - // and check the coinbase maturity rule is being met. - if input.features.contains(OutputFeatures::COINBASE_OUTPUT) { - let block_hash = &input.out_block - .expect("input spending coinbase output must have a block hash"); - let block = self.commit_index.get_block(&block_hash)?; - block.verify_coinbase_maturity(&input, height) - .map_err(|_| Error::ImmatureCoinbase)?; - } - Ok(()) - } - /// returns the last N nodes inserted into the tree (i.e. the 'bottom' /// nodes at level 0 /// TODO: These need to return the actual data from the flat-files instead of hashes now @@ -298,7 +268,6 @@ impl<'a> Extension<'a> { commit_index: commit_index, new_output_commits: HashMap::new(), new_kernel_excesses: HashMap::new(), - rollback: false, } } @@ -335,14 +304,17 @@ impl<'a> Extension<'a> { for kernel in &b.kernels { self.apply_kernel(kernel)?; } + Ok(()) } fn save_pos_index(&self) -> Result<(), Error> { + // store all new output pos in the index for (commit, pos) in &self.new_output_commits { self.commit_index.save_output_pos(commit, *pos)?; } + // store all new kernel pos in the index for (excess, pos) in &self.new_kernel_excesses { self.commit_index.save_kernel_pos(excess, *pos)?; } @@ -364,16 +336,10 @@ impl<'a> Extension<'a> { return Err(Error::SumTreeErr(format!("output pmmr hash mismatch"))); } - // At this point we can be sure the input is spending the output - // it claims to be spending, and it is coinbase or non-coinbase. - // If we are spending a coinbase output then go find the block - // and check the coinbase maturity rule is being met. + // check coinbase maturity with the Merkle Proof on the input if input.features.contains(OutputFeatures::COINBASE_OUTPUT) { - let block_hash = &input.out_block - .expect("input spending coinbase output must have a block hash"); - let block = self.commit_index.get_block(&block_hash)?; - block.verify_coinbase_maturity(&input, height) - .map_err(|_| Error::ImmatureCoinbase)?; + let header = self.commit_index.get_block_header(&input.block_hash())?; + input.verify_maturity(read_hash, &header, height)?; } } @@ -444,6 +410,28 @@ impl<'a> Extension<'a> { Ok(()) } + /// Build a Merkle proof for the given output and the block by + /// rewinding the MMR to the last pos of the block. + /// Note: this relies on the MMR being stable even after pruning/compaction. + /// We need the hash of each sibling pos from the pos up to the peak + /// including the sibling leaf node which may have been removed. + pub fn merkle_proof_via_rewind( + &mut self, + output: &OutputIdentifier, + block: &Block, + ) -> Result { + debug!(LOGGER, "sumtree: merkle_proof_via_rewind: rewinding to block {:?}", block.hash()); + // rewind to the specified block + self.rewind(block)?; + // then calculate the Merkle Proof based on the known pos + let pos = self.get_output_pos(&output.commit)?; + let merkle_proof = self.utxo_pmmr + .merkle_proof(pos) + .map_err(&Error::SumTreeErr)?; + + Ok(merkle_proof) + } + /// Rewinds the MMRs to the provided block, using the last output and /// last kernel of the block we want to rewind to. pub fn rewind(&mut self, block: &Block) -> Result<(), Error> { diff --git a/chain/src/types.rs b/chain/src/types.rs index a16a64512..464fb35dc 100644 --- a/chain/src/types.rs +++ b/chain/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -80,8 +80,6 @@ pub enum Error { DuplicateCommitment(Commitment), /// A kernel with that excess commitment already exists (should be unique) DuplicateKernel(Commitment), - /// coinbase can only be spent after it has matured (n blocks) - ImmatureCoinbase, /// output not found OutputNotFound, /// output spent diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index 12d4c72ff..e7d08ebd7 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -26,7 +26,7 @@ use std::sync::Arc; use chain::Chain; use chain::types::*; -use core::core::{Block, BlockHeader, Transaction, OutputIdentifier, build}; +use core::core::{Block, BlockHeader, Transaction, OutputIdentifier, OutputFeatures, build}; use core::core::hash::Hashed; use core::core::target::Difficulty; use core::consensus; @@ -251,8 +251,12 @@ fn spend_in_fork() { // so we can spend the coinbase later let b = prepare_block(&kc, &fork_head, &chain, 2); let block_hash = b.hash(); + let out_id = OutputIdentifier::from_output(&b.outputs[0]); + assert!(out_id.features.contains(OutputFeatures::COINBASE_OUTPUT)); fork_head = b.header.clone(); - chain.process_block(b, chain::Options::SKIP_POW).unwrap(); + chain.process_block(b.clone(), chain::Options::SKIP_POW).unwrap(); + + let merkle_proof = chain.get_merkle_proof(&out_id, &b).unwrap(); println!("First block"); @@ -270,7 +274,12 @@ fn spend_in_fork() { let tx1 = build::transaction( vec![ - build::coinbase_input(consensus::REWARD, block_hash, kc.derive_key_id(2).unwrap()), + build::coinbase_input( + consensus::REWARD, + block_hash, + merkle_proof, + kc.derive_key_id(2).unwrap(), + ), build::output(consensus::REWARD - 20000, kc.derive_key_id(30).unwrap()), build::with_fee(20000), ], @@ -288,7 +297,7 @@ fn spend_in_fork() { let tx2 = build::transaction( vec![ - build::input(consensus::REWARD - 20000, next.hash(), kc.derive_key_id(30).unwrap()), + build::input(consensus::REWARD - 20000, kc.derive_key_id(30).unwrap()), build::output(consensus::REWARD - 40000, kc.derive_key_id(31).unwrap()), build::with_fee(20000), ], diff --git a/chain/tests/test_coinbase_maturity.rs b/chain/tests/test_coinbase_maturity.rs index 3d6d7d397..6b2fab66e 100644 --- a/chain/tests/test_coinbase_maturity.rs +++ b/chain/tests/test_coinbase_maturity.rs @@ -27,6 +27,7 @@ use chain::types::*; use core::core::build; use core::core::target::Difficulty; use core::core::transaction; +use core::core::OutputIdentifier; use core::consensus; use core::global; use core::global::ChainTypes; @@ -96,16 +97,21 @@ fn test_coinbase_maturity() { ).unwrap(); assert_eq!(block.outputs.len(), 1); + let coinbase_output = block.outputs[0]; assert!( - block.outputs[0] + coinbase_output .features .contains(transaction::OutputFeatures::COINBASE_OUTPUT) ); + let out_id = OutputIdentifier::from_output(&coinbase_output); + // we will need this later when we want to spend the coinbase output let block_hash = block.hash(); - chain.process_block(block, chain::Options::MINE).unwrap(); + chain.process_block(block.clone(), chain::Options::MINE).unwrap(); + + let merkle_proof = chain.get_merkle_proof(&out_id, &block).unwrap(); let prev = chain.head_header().unwrap(); @@ -118,7 +124,12 @@ fn test_coinbase_maturity() { // this is not a valid tx as the coinbase output cannot be spent yet let coinbase_txn = build::transaction( vec![ - build::coinbase_input(amount, block_hash, key_id1.clone()), + build::coinbase_input( + amount, + block_hash, + merkle_proof.clone(), + key_id1.clone(), + ), build::output(amount - 2, key_id2.clone()), build::with_fee(2), ], @@ -139,7 +150,7 @@ fn test_coinbase_maturity() { block.header.difficulty = difficulty.clone(); match chain.set_sumtree_roots(&mut block, false) { - Err(Error::ImmatureCoinbase) => (), + Err(Error::Transaction(transaction::Error::ImmatureCoinbase)) => (), _ => panic!("expected ImmatureCoinbase error here"), } @@ -185,7 +196,12 @@ fn test_coinbase_maturity() { let coinbase_txn = build::transaction( vec![ - build::coinbase_input(amount, block_hash, key_id1.clone()), + build::coinbase_input( + amount, + block_hash, + merkle_proof.clone(), + key_id1.clone(), + ), build::output(amount - 2, key_id2.clone()), build::with_fee(2), ], @@ -216,7 +232,6 @@ fn test_coinbase_maturity() { let result = chain.process_block(block, chain::Options::MINE); match result { Ok(_) => (), - Err(Error::ImmatureCoinbase) => panic!("we should not get an ImmatureCoinbase here"), Err(_) => panic!("we did not expect an error here"), }; } diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 9423efb94..f4b9c5fc5 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ use core::{ Committed, Input, Output, - OutputIdentifier, ShortId, SwitchCommitHash, Proof, @@ -75,6 +74,8 @@ pub enum Error { /// The lock_height needed to be reached for the coinbase output to mature lock_height: u64, }, + /// Underlying Merkle proof error + MerkleProof, /// Other unspecified error condition Other(String) } @@ -613,13 +614,13 @@ impl Block { let new_inputs = self.inputs .iter() .filter(|inp| !to_cut_through.contains(&inp.commitment())) - .map(|&inp| inp) + .cloned() .collect::>(); let new_outputs = self.outputs .iter() .filter(|out| !to_cut_through.contains(&out.commitment())) - .map(|&out| out) + .cloned() .collect::>(); Block { @@ -642,6 +643,7 @@ impl Block { self.verify_weight()?; self.verify_sorted()?; self.verify_coinbase()?; + self.verify_inputs()?; self.verify_kernels()?; Ok(()) } @@ -660,6 +662,26 @@ impl Block { Ok(()) } + /// We can verify the Merkle proof (for coinbase inputs) here in isolation. + /// But we cannot check the following as we need data from the index and the PMMR. + /// So we must be sure to check these at the appropriate point during block validation. + /// * node is in the correct pos in the PMMR + /// * block is the correct one (based on utxo_root from block_header via the index) + fn verify_inputs(&self) -> Result<(), Error> { + let coinbase_inputs = self.inputs + .iter() + .filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT)); + + for input in coinbase_inputs { + let merkle_proof = input.merkle_proof(); + if !merkle_proof.verify() { + return Err(Error::MerkleProof); + } + } + + Ok(()) + } + /// Verifies the sum of input/output commitments match the sum in kernels /// and that all kernel signatures are valid. fn verify_kernels(&self) -> Result<(), Error> { @@ -753,42 +775,6 @@ impl Block { Ok(()) } - /// NOTE: this happens during apply_block (not the earlier validate_block) - /// - /// Calculate lock_height as block_height + 1,000 - /// Confirm height <= lock_height - pub fn verify_coinbase_maturity( - &self, - input: &Input, - height: u64, - ) -> Result<(), Error> { - let output = OutputIdentifier::from_input(&input); - - // We should only be calling verify_coinbase_maturity - // if the sender claims we are spending a coinbase output - // _and_ that we trust this claim. - // We should have already confirmed the entry from the MMR exists - // and has the expected hash. - assert!(output.features.contains(OutputFeatures::COINBASE_OUTPUT)); - - if let Some(_) = self.outputs - .iter() - .find(|x| OutputIdentifier::from_output(&x) == output) - { - let lock_height = self.header.height + global::coinbase_maturity(); - if lock_height > height { - Err(Error::ImmatureCoinbase{ - height: height, - lock_height: lock_height, - }) - } else { - Ok(()) - } - } else { - Err(Error::Other(format!("output not found in block"))) - } - } - /// Builds the blinded output and related signature proof for the block reward. pub fn reward_output( keychain: &keychain::Keychain, @@ -860,7 +846,6 @@ impl Block { #[cfg(test)] mod test { use super::*; - use core::hash::ZERO_HASH; use core::Transaction; use core::build::{self, input, output, with_fee}; use core::test::{tx1i2o, tx2i1o}; @@ -892,7 +877,7 @@ mod test { key_id2: Identifier, ) -> Transaction { build::transaction( - vec![input(v, ZERO_HASH, key_id1), output(3, key_id2), with_fee(2)], + vec![input(v, key_id1), output(3, key_id2), with_fee(2)], &keychain, ).unwrap() } @@ -915,7 +900,7 @@ mod test { } let now = Instant::now(); - parts.append(&mut vec![input(500000, ZERO_HASH, pks.pop().unwrap()), with_fee(2)]); + parts.append(&mut vec![input(500000, pks.pop().unwrap()), with_fee(2)]); let mut tx = build::transaction(parts, &keychain) .unwrap(); println!("Build tx: {}", now.elapsed().as_secs()); @@ -952,7 +937,7 @@ mod test { let mut btx1 = tx2i1o(); let mut btx2 = build::transaction( - vec![input(7, ZERO_HASH, key_id1), output(5, key_id2.clone()), with_fee(2)], + vec![input(7, key_id1), output(5, key_id2.clone()), with_fee(2)], &keychain, ).unwrap(); diff --git a/core/src/core/build.rs b/core/src/core/build.rs index 42cdd6d7d..e9e6e8f7d 100644 --- a/core/src/core/build.rs +++ b/core/src/core/build.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ use util::{secp, kernel_sig_msg}; use core::{Transaction, TxKernel, Input, Output, OutputFeatures, ProofMessageElements, SwitchCommitHash}; use core::hash::Hash; +use core::pmmr::MerkleProof; use keychain; use keychain::{Keychain, BlindSum, BlindingFactor, Identifier}; use util::LOGGER; @@ -47,7 +48,8 @@ pub type Append = for<'a> Fn(&'a mut Context, (Transaction, TxKernel, BlindSum)) fn build_input( value: u64, features: OutputFeatures, - out_block: Option, + block_hash: Option, + merkle_proof: Option, key_id: Identifier, ) -> Box { Box::new(move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { @@ -55,7 +57,8 @@ fn build_input( let input = Input::new( features, commit, - out_block, + block_hash.clone(), + merkle_proof.clone(), ); (tx.with_input(input), kern, sum.sub_key_id(key_id.clone())) }) @@ -65,22 +68,22 @@ fn build_input( /// being built. pub fn input( value: u64, - out_block: Hash, key_id: Identifier, ) -> Box { debug!(LOGGER, "Building input (spending regular output): {}, {}", value, key_id); - build_input(value, OutputFeatures::DEFAULT_OUTPUT, Some(out_block), key_id) + build_input(value, OutputFeatures::DEFAULT_OUTPUT, None, None, key_id) } /// Adds a coinbase input spending a coinbase output. /// We will use the block hash to verify coinbase maturity. pub fn coinbase_input( value: u64, - out_block: Hash, + block_hash: Hash, + merkle_proof: MerkleProof, key_id: Identifier, ) -> Box { debug!(LOGGER, "Building input (spending coinbase): {}, {}", value, key_id); - build_input(value, OutputFeatures::COINBASE_OUTPUT, Some(out_block), key_id) + build_input(value, OutputFeatures::COINBASE_OUTPUT, Some(block_hash), Some(merkle_proof), key_id) } /// Adds an output with the provided value and key identifier from the @@ -261,7 +264,6 @@ pub fn transaction_with_offset( #[cfg(test)] mod test { use super::*; - use core::hash::ZERO_HASH; #[test] fn blind_simple_tx() { @@ -272,8 +274,8 @@ mod test { let tx = transaction( vec![ - input(10, ZERO_HASH, key_id1), - input(12, ZERO_HASH, key_id2), + input(10, key_id1), + input(12, key_id2), output(20, key_id3), with_fee(2), ], @@ -292,8 +294,8 @@ mod test { let tx = transaction_with_offset( vec![ - input(10, ZERO_HASH, key_id1), - input(12, ZERO_HASH, key_id2), + input(10, key_id1), + input(12, key_id2), output(20, key_id3), with_fee(2), ], @@ -310,7 +312,7 @@ mod test { let key_id2 = keychain.derive_key_id(2).unwrap(); let tx = transaction( - vec![input(6, ZERO_HASH, key_id1), output(2, key_id2), with_fee(4)], + vec![input(6, key_id1), output(2, key_id2), with_fee(4)], &keychain, ).unwrap(); diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 6d4a2b094..2a523240f 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -250,7 +250,7 @@ mod test { // blinding should fail as signing with a zero r*G shouldn't work build::transaction( vec![ - input(10, ZERO_HASH, key_id1.clone()), + input(10, key_id1.clone()), output(9, key_id1.clone()), with_fee(1), ], @@ -309,7 +309,7 @@ mod test { // first build a valid tx with corresponding blinding factor let tx = build::transaction( vec![ - input(10, ZERO_HASH, key_id1), + input(10, key_id1), output(5, key_id2), output(3, key_id3), with_fee(2), @@ -373,7 +373,7 @@ mod test { let tx = build::transaction( vec![ - input(75, ZERO_HASH, key_id1), + input(75, key_id1), output(42, key_id2), output(32, key_id3), with_fee(1), @@ -480,7 +480,7 @@ mod test { let (tx_alice, blind_sum) = { // Alice gets 2 of her pre-existing outputs to send 5 coins to Bob, they // become inputs in the new transaction - let (in1, in2) = (input(4, ZERO_HASH, key_id1), input(3, ZERO_HASH, key_id2)); + let (in1, in2) = (input(4, key_id1), input(3, key_id2)); // Alice builds her transaction, with change, which also produces the sum // of blinding factors before they're obscured. @@ -571,7 +571,7 @@ mod test { // and that the resulting block is valid let tx1 = build::transaction( vec![ - input(5, ZERO_HASH, key_id1.clone()), + input(5, key_id1.clone()), output(3, key_id2.clone()), with_fee(2), with_lock_height(1), @@ -591,7 +591,7 @@ mod test { // now try adding a timelocked tx where lock height is greater than current block height let tx1 = build::transaction( vec![ - input(5, ZERO_HASH, key_id1.clone()), + input(5, key_id1.clone()), output(3, key_id2.clone()), with_fee(2), with_lock_height(2), @@ -635,8 +635,8 @@ mod test { build::transaction_with_offset( vec![ - input(10, ZERO_HASH, key_id1), - input(11, ZERO_HASH, key_id2), + input(10, key_id1), + input(11, key_id2), output(19, key_id3), with_fee(2), ], @@ -651,7 +651,7 @@ mod test { let key_id2 = keychain.derive_key_id(2).unwrap(); build::transaction_with_offset( - vec![input(5, ZERO_HASH, key_id1), output(3, key_id2), with_fee(2)], + vec![input(5, key_id1), output(3, key_id2), with_fee(2)], &keychain, ).unwrap() } @@ -667,7 +667,7 @@ mod test { build::transaction_with_offset( vec![ - input(6, ZERO_HASH, key_id1), + input(6, key_id1), output(3, key_id2), output(1, key_id3), with_fee(2), diff --git a/core/src/core/pmmr.rs b/core/src/core/pmmr.rs index 1a4b29214..e5ea437b9 100644 --- a/core/src/core/pmmr.rs +++ b/core/src/core/pmmr.rs @@ -1,4 +1,4 @@ -// Copyright 2017 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -36,11 +36,12 @@ //! a simple Vec or a database. use std::clone::Clone; -use std::ops::Deref; use std::marker::PhantomData; - use core::hash::{Hash, Hashed}; +use ser; +use ser::{Readable, Reader, Writeable, Writer}; use ser::PMMRable; +use util; use util::LOGGER; /// Storage backend for the MMR, just needs to be indexed by order of insertion. @@ -50,7 +51,7 @@ use util::LOGGER; pub trait Backend where T:PMMRable { /// Append the provided Hashes to the backend storage, and optionally an associated - /// data element to flatfile storage (for leaf nodes only). The position of the + /// data element to flatfile storage (for leaf nodes only). The position of the /// first element of the Vec in the MMR is provided to help the implementation. fn append(&mut self, position: u64, data: Vec<(Hash, Option)>) -> Result<(), String>; @@ -60,11 +61,14 @@ pub trait Backend where /// occurred (see remove). fn rewind(&mut self, position: u64, index: u32) -> Result<(), String>; - /// Get a Hash/Element by insertion position. If include_data is true, will + /// Get a Hash/Element by insertion position. If include_data is true, will /// also return the associated data element fn get(&self, position: u64, include_data: bool) -> Option<(Hash, Option)>; - /// Remove Hashes/Data by insertion position. An index is also provided so the + /// Get a Hash/Element by original insertion position (ignoring the remove list). + fn get_from_file(&self, position: u64) -> Option; + + /// Remove HashSums by insertion position. An index is also provided so the /// underlying backend can implement some rollback of positions up to a /// given index (practically the index is a the height of a block that /// triggered removal). @@ -76,6 +80,169 @@ pub trait Backend where fn get_data_file_path(&self) -> String; } +/// A Merkle proof. +/// Proves inclusion of an output (node) in the output MMR. +/// We can use this to prove an output was unspent at the time of a given block +/// as the root will match the utxo_root of the block header. +/// The path and left_right can be used to reconstruct the peak hash for a given tree +/// in the MMR. +/// The root is the result of hashing all the peaks together. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct MerkleProof { + /// The root hash of the full Merkle tree (in an MMR the hash of all peaks) + pub root: Hash, + /// The hash of the element in the tree we care about + pub node: Hash, + /// The full list of peak hashes in the MMR + pub peaks: Vec, + /// The siblings along the path of the tree as we traverse from node to peak + pub path: Vec, + /// Order of siblings (left vs right) matters, so track this here for each path element + pub left_right: Vec, +} + +impl Writeable for MerkleProof { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + ser_multiwrite!( + writer, + [write_fixed_bytes, &self.root], + [write_fixed_bytes, &self.node], + [write_u64, self.peaks.len() as u64], + + // note: path length used for both path and left_right vecs + [write_u64, self.path.len() as u64] + ); + + try!(self.peaks.write(writer)); + try!(self.path.write(writer)); + + // TODO - how to serialize/deserialize these boolean values as bytes? + for x in &self.left_right { + if *x { + try!(writer.write_u8(1)); + } else { + try!(writer.write_u8(0)); + } + } + + Ok(()) + } +} + +impl Readable for MerkleProof { + fn read(reader: &mut Reader) -> Result { + let root = Hash::read(reader)?; + let node = Hash::read(reader)?; + + let (peaks_len, path_len) = + ser_multiread!(reader, read_u64, read_u64); + + let mut peaks = Vec::with_capacity(peaks_len as usize); + for _ in 0..peaks_len { + peaks.push(Hash::read(reader)?); + } + let mut path = Vec::with_capacity(path_len as usize); + for _ in 0..path_len { + path.push(Hash::read(reader)?); + } + + let left_right_bytes = reader.read_fixed_bytes(path_len as usize)?; + let left_right = left_right_bytes.iter().map(|&x| x == 1).collect(); + Ok( + MerkleProof { + root, + node, + peaks, + path, + left_right, + } + ) + } +} + +impl Default for MerkleProof { + fn default() -> MerkleProof { + MerkleProof::empty() + } +} + +impl MerkleProof { + /// The "empty" Merkle proof. + /// Basically some reasonable defaults. Will not verify successfully. + pub fn empty() -> MerkleProof { + MerkleProof { + root: Hash::zero(), + node: Hash::zero(), + peaks: vec![], + path: vec![], + left_right: vec![], + } + } + + /// Serialize the Merkle proof as a hex string (for api json endpoints) + pub fn to_hex(&self) -> String { + let mut vec = Vec::new(); + ser::serialize(&mut vec, &self).expect("serialization failed"); + util::to_hex(vec) + } + + /// Convert hex string represenation back to a Merkle proof instance + pub fn from_hex(hex: &str) -> Result { + let bytes = util::from_hex(hex.to_string()).unwrap(); + let res = ser::deserialize(&mut &bytes[..]) + .map_err(|_| format!("failed to deserialize a Merkle Proof"))?; + Ok(res) + } + + /// Verify the Merkle proof. + /// We do this by verifying the folloiwing - + /// * inclusion of the node beneath a peak (via the Merkle path/branch of siblings) + /// * inclusion of the peak in the "bag of peaks" beneath the root + pub fn verify(&self) -> bool { + // if we have no further elements in the path + // then this proof verifies successfully if our node is + // one of the peaks + // and the peaks themselves hash to give the root + if self.path.len() == 0 { + if !self.peaks.contains(&self.node) { + return false; + } + + let mut bagged = None; + for peak in self.peaks.iter().map(|&x| Some(x)) { + bagged = match (bagged, peak) { + (None, rhs) => rhs, + (lhs, None) => lhs, + (Some(lhs), Some(rhs)) => Some(lhs.hash_with(rhs)), + } + } + return bagged == Some(self.root); + } + + let mut path = self.path.clone(); + let sibling = path.remove(0); + let mut left_right = self.left_right.clone(); + + // hash our node and sibling together (noting left/right position of the sibling) + let parent = if left_right.remove(0) { + self.node.hash_with(sibling) + } else { + sibling.hash_with(self.node) + }; + + let proof = MerkleProof { + root: self.root, + node: parent, + peaks: self.peaks.clone(), + path, + left_right, + }; + + proof.verify() + } +} + + /// Prunable Merkle Mountain Range implementation. All positions within the tree /// start at 1 as they're postorder tree traversal positions rather than array /// indices. @@ -108,7 +275,7 @@ where } } - /// Build a new prunable Merkle Mountain Range pre-initlialized until + /// Build a new prunable Merkle Mountain Range pre-initialized until /// last_pos /// with the provided backend. pub fn at(backend: &'a mut B, last_pos: u64) -> PMMR { @@ -127,7 +294,6 @@ where .map(|pi| self.backend.get(pi, false)) .collect(); - let mut ret = None; for peak in peaks { ret = match (ret, peak) { @@ -139,6 +305,56 @@ where ret.expect("no root, invalid tree").0 } + /// Build a Merkle proof for the element at the given position in the MMR + pub fn merkle_proof(&self, pos: u64) -> Result { + debug!(LOGGER, "merkle_proof (via rewind) - {}, last_pos {}", pos, self.last_pos); + + if !is_leaf(pos) { + return Err(format!("not a leaf at pos {}", pos)); + } + + let root = self.root(); + + let node = self.get(pos, false) + .ok_or(format!("no element at pos {}", pos))? + .0; + + let family_branch = family_branch(pos, self.last_pos); + let left_right = family_branch + .iter() + .map(|x| x.2) + .collect::>(); + + let path = family_branch + .iter() + .filter_map(|x| { + // we want to find siblings here even if they + // have been "removed" from the MMR + // TODO - pruned/compacted MMR will need to maintain hashes of removed nodes + let res = self.get_from_file(x.1); + res + }) + .collect::>(); + + let peaks = peaks(self.last_pos) + .iter() + .filter_map(|&x| { + let res = self.get_from_file(x); + res + }) + .collect::>(); + + let proof = MerkleProof { + root, + node, + path, + peaks, + left_right, + }; + + Ok(proof) + } + /// Push a new element into the MMR. Computes new related peaks at /// the same time if applicable. pub fn push(&mut self, elmt: T) -> Result { @@ -206,7 +422,7 @@ where let mut to_prune = vec![]; let mut current = position; while current + 1 < self.last_pos { - let (parent, sibling) = family(current); + let (parent, sibling, _) = family(current); if parent > self.last_pos { // can't prune when our parent isn't here yet break; @@ -236,6 +452,13 @@ where } } + fn get_from_file(&self, position: u64) -> Option { + if position > self.last_pos { + None + } else { + self.backend.get_from_file(position) + } + } /// Helper function to get the last N nodes inserted, i.e. the last /// n nodes along the bottom of the tree @@ -275,7 +498,8 @@ where if bintree_postorder_height(n) > 0 { if let Some(hs) = self.get(n, false) { // take the left and right children, if they exist - let left_pos = bintree_move_down_left(n).unwrap(); + let left_pos = bintree_move_down_left(n) + .ok_or(format!("left_pos not found"))?; let right_pos = bintree_jump_right_sibling(left_pos); if let Some(left_child_hs) = self.get(left_pos, false) { @@ -283,7 +507,7 @@ where // add hashes and compare if left_child_hs.0+right_child_hs.0 != hs.0 { return Err(format!("Invalid MMR, hash of parent at {} does \ - not match children.", n)); + not match children.", n)); } } } @@ -332,72 +556,6 @@ where } } -/// Simple MMR backend implementation based on a Vector. Pruning does not -/// compact the Vector itself but still frees the reference to the -/// underlying Hash. -#[derive(Clone)] -pub struct VecBackend - where T:PMMRable { - /// Backend elements - pub elems: Vec)>>, -} - -impl Backend for VecBackend - where T: PMMRable { - #[allow(unused_variables)] - fn append(&mut self, position: u64, data: Vec<(Hash, Option)>) -> Result<(), String> { - self.elems.append(&mut map_vec!(data, |d| Some(d.clone()))); - Ok(()) - } - fn get(&self, position: u64, _include_data:bool) -> Option<(Hash, Option)> { - self.elems[(position - 1) as usize].clone() - } - #[allow(unused_variables)] - fn remove(&mut self, positions: Vec, index: u32) -> Result<(), String> { - for n in positions { - self.elems[(n - 1) as usize] = None - } - Ok(()) - } - #[allow(unused_variables)] - fn rewind(&mut self, position: u64, index: u32) -> Result<(), String> { - self.elems = self.elems[0..(position as usize) + 1].to_vec(); - Ok(()) - } - fn get_data_file_path(&self) -> String { - "".to_string() - } -} - -impl VecBackend - where T:PMMRable { - /// Instantiates a new VecBackend - pub fn new() -> VecBackend { - VecBackend { elems: vec![] } - } - - /// Current number of elements in the underlying Vec. - pub fn used_size(&self) -> usize { - let mut usz = self.elems.len(); - for elem in self.elems.deref() { - if elem.is_none() { - usz -= 1; - } - } - usz - } - - /// Resets the backend, emptying the underlying Vec. - pub fn clear(&mut self) { - self.elems = Vec::new(); - } - - /// Total length of the underlying vector. - pub fn len(&self) -> usize { - self.elems.len() - } -} - /// Maintains a list of previously pruned nodes in PMMR, compacting the list as /// parents get pruned and allowing checking whether a leaf is pruned. Given /// a node's position, computes how much it should get shifted given the @@ -444,7 +602,7 @@ impl PruneList { /// given leaf. Helpful if, for instance, data for each leaf is being stored /// separately in a continuous flat-file pub fn get_leaf_shift(&self, pos: u64) -> Option { - + // get the position where the node at pos would fit in the pruned list, if // it's already pruned, nothing to skip match self.pruned_pos(pos) { @@ -468,7 +626,7 @@ impl PruneList { pub fn add(&mut self, pos: u64) { let mut current = pos; loop { - let (parent, sibling) = family(current); + let (parent, sibling, _) = family(current); match self.pruned_nodes.binary_search(&sibling) { Ok(idx) => { self.pruned_nodes.remove(idx); @@ -498,7 +656,7 @@ impl PruneList { let next_peak_pos = self.pruned_nodes[idx]; let mut cursor = pos; loop { - let (parent, _) = family(cursor); + let (parent, _, _) = family(cursor); if next_peak_pos == parent { return None; } @@ -631,9 +789,7 @@ pub fn n_leaves(mut sz: u64) -> u64 { /// any node, from its postorder traversal position. Which is the order in which /// nodes are added in a MMR. /// -/// [1] https://github. -/// com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range. -/// md +/// [1] https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md pub fn bintree_postorder_height(num: u64) -> u64 { let mut h = num; while !all_ones(h) { @@ -642,22 +798,48 @@ pub fn bintree_postorder_height(num: u64) -> u64 { most_significant_pos(h) - 1 } -/// Calculates the positions of the parent and sibling of the node at the -/// provided position. -pub fn family(pos: u64) -> (u64, u64) { - let sibling: u64; - let parent: u64; +/// Is this position a leaf in the MMR? +/// We know the positions of all leaves based on the postorder height of an MMR of any size +/// (somewhat unintuitively but this is how the PMMR is "append only"). +pub fn is_leaf(pos: u64) -> bool { + bintree_postorder_height(pos) == 0 +} +/// Calculates the positions of the parent and sibling of the node at the +/// provided position. Also returns a boolean representing whether the sibling is on left +/// branch or right branch (left=0, right=1) +pub fn family(pos: u64) -> (u64, u64, bool) { let pos_height = bintree_postorder_height(pos); let next_height = bintree_postorder_height(pos + 1); if next_height > pos_height { - sibling = bintree_jump_left_sibling(pos); - parent = pos + 1; + let sibling = bintree_jump_left_sibling(pos); + let parent = pos + 1; + (parent, sibling, false) } else { - sibling = bintree_jump_right_sibling(pos); - parent = sibling + 1; + let sibling = bintree_jump_right_sibling(pos); + let parent = sibling + 1; + (parent, sibling, true) } - (parent, sibling) +} + +/// For a given starting position calculate the parent and sibling positions +/// for the branch/path from that position to the peak of the tree. +/// We will use the sibling positions to generate the "path" of a Merkle proof. +pub fn family_branch(pos: u64, last_pos: u64) -> Vec<(u64, u64, bool)> { + // loop going up the tree, from node to parent, as long as we stay inside + // the tree (as defined by last_pos). + let mut branch = vec![]; + let mut current = pos; + while current + 1 <= last_pos { + let (parent, sibling, sibling_branch) = family(current); + if parent > last_pos { + break; + } + branch.push((parent, sibling, sibling_branch)); + + current = parent; + } + branch } /// Calculates the position of the top-left child of a parent node in the @@ -731,6 +913,82 @@ mod test { use core::{Writer, Reader}; use core::hash::{Hash}; + /// Simple MMR backend implementation based on a Vector. Pruning does not + /// compact the Vec itself. + #[derive(Clone)] + pub struct VecBackend + where T:PMMRable { + /// Backend elements + pub elems: Vec)>>, + /// Positions of removed elements + pub remove_list: Vec, + } + + impl Backend for VecBackend + where T: PMMRable + { + fn append(&mut self, _position: u64, data: Vec<(Hash, Option)>) -> Result<(), String> { + self.elems.append(&mut map_vec!(data, |d| Some(d.clone()))); + Ok(()) + } + + fn get(&self, position: u64, _include_data: bool) -> Option<(Hash, Option)> { + if self.remove_list.contains(&position) { + None + } else { + self.elems[(position - 1) as usize].clone() + } + } + + fn get_from_file(&self, position: u64) -> Option { + if let Some(ref x) = self.elems[(position - 1) as usize] { + Some(x.0) + } else { + None + } + } + + fn remove(&mut self, positions: Vec, _index: u32) -> Result<(), String> { + for n in positions { + self.remove_list.push(n) + } + Ok(()) + } + + fn rewind(&mut self, position: u64, _index: u32) -> Result<(), String> { + self.elems = self.elems[0..(position as usize) + 1].to_vec(); + Ok(()) + } + + fn get_data_file_path(&self) -> String { + "".to_string() + } + } + + impl VecBackend + where T:PMMRable + { + /// Instantiates a new VecBackend + pub fn new() -> VecBackend { + VecBackend { + elems: vec![], + remove_list: vec![], + } + } + + /// Current number of elements in the underlying Vec. + pub fn used_size(&self) -> usize { + let mut usz = self.elems.len(); + for (idx, _) in self.elems.iter().enumerate() { + let idx = idx as u64; + if self.remove_list.contains(&idx) { + usz -= 1; + } + } + usz + } + } + #[test] fn test_leaf_index(){ assert_eq!(n_leaves(1),1); @@ -739,7 +997,7 @@ mod test { assert_eq!(n_leaves(5),4); assert_eq!(n_leaves(8),5); assert_eq!(n_leaves(9),6); - + } #[test] @@ -764,7 +1022,7 @@ mod test { #[test] #[allow(unused_variables)] - fn first_50_mmr_heights() { + fn first_100_mmr_heights() { let first_100_str = "0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 4 \ 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 4 5 \ 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 4 0 0 1 0 0"; @@ -782,19 +1040,130 @@ mod test { } } + // Trst our n_leaves impl does the right thing for various MMR sizes + #[test] + fn various_n_leaves() { + assert_eq!(n_leaves(1), 1); + // 2 is not a valid size for a tree, but n_leaves rounds up to next valid tree size + assert_eq!(n_leaves(2), 2); + assert_eq!(n_leaves(3), 2); + assert_eq!(n_leaves(7), 4); + } + + /// Find parent and sibling positions for various node positions. + #[test] + fn various_families() { + // 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 + assert_eq!(family(1), (3, 2, true)); + assert_eq!(family(2), (3, 1, false)); + assert_eq!(family(3), (7, 6, true)); + assert_eq!(family(4), (6, 5, true)); + assert_eq!(family(5), (6, 4, false)); + assert_eq!(family(6), (7, 3, false)); + assert_eq!(family(7), (15, 14, true)); + assert_eq!(family(1_000), (1_001, 997, false)); + } + + #[test] + fn various_branches() { + // the two leaf nodes in a 3 node tree (height 1) + assert_eq!(family_branch(1, 3), [(3, 2, true)]); + assert_eq!(family_branch(2, 3), [(3, 1, false)]); + + // the root node in a 3 node tree + assert_eq!(family_branch(3, 3), []); + + // leaf node in a larger tree of 7 nodes (height 2) + assert_eq!(family_branch(1, 7), [(3, 2, true), (7, 6, true)]); + + // note these only go as far up as the local peak, not necessarily the single root + assert_eq!(family_branch(1, 4), [(3, 2, true)]); + // pos 4 in a tree of size 4 is a local peak + assert_eq!(family_branch(4, 4), []); + // pos 4 in a tree of size 5 is also still a local peak + assert_eq!(family_branch(4, 5), []); + // pos 4 in a tree of size 6 has a parent and a sibling + assert_eq!(family_branch(4, 6), [(6, 5, true)]); + // a tree of size 7 is all under a single root + assert_eq!(family_branch(4, 7), [(6, 5, true), (7, 3, false)]); + + // ok now for a more realistic one, a tree with over a million nodes in it + // find the "family path" back up the tree from a leaf node at 0 + // Note: the first two entries in the branch are consistent with a small 7 node tree + // Note: each sibling is on the left branch, this is an example of the largest possible + // list of peaks before we start combining them into larger peaks. + assert_eq!( + family_branch(1, 1_049_000), + [ + (3, 2, true), + (7, 6, true), + (15, 14, true), + (31, 30, true), + (63, 62, true), + (127, 126, true), + (255, 254, true), + (511, 510, true), + (1023, 1022, true), + (2047, 2046, true), + (4095, 4094, true), + (8191, 8190, true), + (16383, 16382, true), + (32767, 32766, true), + (65535, 65534, true), + (131071, 131070, true), + (262143, 262142, true), + (524287, 524286, true), + (1048575, 1048574, true), + ] + ); + } + #[test] - #[allow(unused_variables)] fn some_peaks() { + // 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 let empty: Vec = vec![]; - assert_eq!(peaks(1), vec![1]); + assert_eq!(peaks(1), [1]); assert_eq!(peaks(2), empty); - assert_eq!(peaks(3), vec![3]); - assert_eq!(peaks(4), vec![3, 4]); - assert_eq!(peaks(11), vec![7, 10, 11]); - assert_eq!(peaks(22), vec![15, 22]); - assert_eq!(peaks(32), vec![31, 32]); - assert_eq!(peaks(35), vec![31, 34, 35]); - assert_eq!(peaks(42), vec![31, 38, 41, 42]); + assert_eq!(peaks(3), [3]); + assert_eq!(peaks(4), [3, 4]); + assert_eq!(peaks(5), empty); + assert_eq!(peaks(6), empty); + assert_eq!(peaks(7), [7]); + assert_eq!(peaks(8), [7, 8]); + assert_eq!(peaks(9), empty); + assert_eq!(peaks(10), [7, 10]); + assert_eq!(peaks(11), [7, 10, 11]); + assert_eq!(peaks(22), [15, 22]); + assert_eq!(peaks(32), [31, 32]); + assert_eq!(peaks(35), [31, 34, 35]); + assert_eq!(peaks(42), [31, 38, 41, 42]); + + // large realistic example with almost 1.5 million nodes + // note the distance between peaks decreases toward the right (trees get smaller) + assert_eq!( + peaks(1048555), + [ + 524287, + 786430, + 917501, + 983036, + 1015803, + 1032186, + 1040377, + 1044472, + 1046519, + 1047542, + 1048053, + 1048308, + 1048435, + 1048498, + 1048529, + 1048544, + 1048551, + 1048554, + 1048555, + ], + ); } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -828,6 +1197,97 @@ mod test { } } + #[test] + fn empty_merkle_proof() { + let proof = MerkleProof::empty(); + assert_eq!(proof.verify(), false); + } + + #[test] + fn pmmr_merkle_proof() { + // 0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 + + let mut ba = VecBackend::new(); + let mut pmmr = PMMR::new(&mut ba); + + pmmr.push(TestElem([0, 0, 0, 1])).unwrap(); + assert_eq!(pmmr.last_pos, 1); + let proof = pmmr.merkle_proof(1).unwrap(); + let root = pmmr.root(); + assert_eq!(proof.peaks, [root]); + assert!(proof.path.is_empty()); + assert!(proof.left_right.is_empty()); + assert!(proof.verify()); + + // push two more elements into the PMMR + pmmr.push(TestElem([0, 0, 0, 2])).unwrap(); + pmmr.push(TestElem([0, 0, 0, 3])).unwrap(); + assert_eq!(pmmr.last_pos, 4); + + let proof1 = pmmr.merkle_proof(1).unwrap(); + assert_eq!(proof1.peaks.len(), 2); + assert_eq!(proof1.path.len(), 1); + assert_eq!(proof1.left_right, [true]); + assert!(proof1.verify()); + + let proof2 = pmmr.merkle_proof(2).unwrap(); + assert_eq!(proof2.peaks.len(), 2); + assert_eq!(proof2.path.len(), 1); + assert_eq!(proof2.left_right, [false]); + assert!(proof2.verify()); + + // check that we cannot generate a merkle proof for pos 3 (not a leaf node) + assert_eq!(pmmr.merkle_proof(3).err(), Some(format!("not a leaf at pos 3"))); + + let proof4 = pmmr.merkle_proof(4).unwrap(); + assert_eq!(proof4.peaks.len(), 2); + assert!(proof4.path.is_empty()); + assert!(proof4.left_right.is_empty()); + assert!(proof4.verify()); + + // now add a few more elements to the PMMR to build a larger merkle proof + for x in 4..1000 { + pmmr.push(TestElem([0, 0, 0, x])).unwrap(); + } + let proof = pmmr.merkle_proof(1).unwrap(); + assert_eq!(proof.peaks.len(), 8); + assert_eq!(proof.path.len(), 9); + assert_eq!(proof.left_right.len(), 9); + assert!(proof.verify()); + } + + #[test] + fn pmmr_merkle_proof_prune_and_rewind() { + let mut ba = VecBackend::new(); + let mut pmmr = PMMR::new(&mut ba); + pmmr.push(TestElem([0, 0, 0, 1])).unwrap(); + pmmr.push(TestElem([0, 0, 0, 2])).unwrap(); + let proof = pmmr.merkle_proof(2).unwrap(); + + // now prune an element and check we can still generate + // the correct Merkle proof for the other element (after sibling pruned) + pmmr.prune(1, 1).unwrap(); + let proof_2 = pmmr.merkle_proof(2).unwrap(); + assert_eq!(proof, proof_2); + } + + #[test] + fn merkle_proof_ser_deser() { + let mut ba = VecBackend::new(); + let mut pmmr = PMMR::new(&mut ba); + for x in 0..15 { + pmmr.push(TestElem([0, 0, 0, x])).unwrap(); + } + let proof = pmmr.merkle_proof(9).unwrap(); + assert!(proof.verify()); + + let mut vec = Vec::new(); + ser::serialize(&mut vec, &proof).expect("serialization failed"); + let proof_2: MerkleProof = ser::deserialize(&mut &vec[..]).unwrap(); + + assert_eq!(proof, proof_2); + } + #[test] #[allow(unused_variables)] fn pmmr_push_root() { diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 90ab22525..d6d8e5b8c 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -24,12 +24,16 @@ use std::{error, fmt}; use consensus; use consensus::VerifySortOrder; use core::Committed; +use core::global; +use core::BlockHeader; use core::hash::{Hash, Hashed, ZERO_HASH}; -use keychain::{Identifier, Keychain, BlindingFactor}; +use core::pmmr::MerkleProof; use keychain; +use keychain::{Identifier, Keychain, BlindingFactor}; use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable, WriteableSorted, Writer, ser_vec}; use std::io::Cursor; use util; +use util::LOGGER; /// The size of the blake2 hash of a switch commitment (256 bits) pub const SWITCH_COMMIT_HASH_SIZE: usize = 32; @@ -90,10 +94,14 @@ pub enum Error { ConsensusError(consensus::Error), /// Error originating from an invalid lock-height LockHeight(u64), - /// Error originating from an invalid switch commitment (coinbase lock_height related) + /// Error originating from an invalid switch commitment SwitchCommitment, /// Range proof validation error RangeProof, + /// Error originating from an invalid Merkle proof + MerkleProof, + /// Error originating from an input attempting to spend an immature coinbase output + ImmatureCoinbase, } impl error::Error for Error { @@ -157,7 +165,7 @@ pub struct TxKernel { hashable_ord!(TxKernel); /// TODO - no clean way to bridge core::hash::Hash and std::hash::Hash implementations? -impl ::std::hash::Hash for Output { +impl ::std::hash::Hash for TxKernel { fn hash(&self, state: &mut H) { let mut vec = Vec::new(); ser::serialize(&mut vec, &self).expect("serialization failed"); @@ -455,6 +463,8 @@ impl Transaction { } self.verify_sorted()?; + self.verify_inputs()?; + for out in &self.outputs { out.verify_proof()?; } @@ -470,6 +480,26 @@ impl Transaction { self.kernels.verify_sort_order()?; Ok(()) } + + /// We can verify the Merkle proof (for coinbase inputs) here in isolation. + /// But we cannot check the following as we need data from the index and the PMMR. + /// So we must be sure to check these at the appropriate point during block validation. + /// * node is in the correct pos in the PMMR + /// * block is the correct one (based on utxo_root from block_header via the index) + fn verify_inputs(&self) -> Result<(), Error> { + let coinbase_inputs = self.inputs + .iter() + .filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT)); + + for input in coinbase_inputs { + let merkle_proof = input.merkle_proof(); + if !merkle_proof.verify() { + return Err(Error::MerkleProof); + } + } + + Ok(()) + } } /// A transaction input. @@ -477,7 +507,7 @@ impl Transaction { /// Primarily a reference to an output being spent by the transaction. /// But also information required to verify coinbase maturity through /// the lock_height hashed in the switch_commit_hash. -#[derive(Debug, Clone, Copy, Hash)] +#[derive(Debug, Clone)] pub struct Input{ /// The features of the output being spent. /// We will check maturity for coinbase output. @@ -486,21 +516,38 @@ pub struct Input{ pub commit: Commitment, /// The hash of the block the output originated from. /// Currently we only care about this for coinbase outputs. - /// TODO - include the merkle proof here once we support these. - pub out_block: Option, + pub block_hash: Option, + /// The Merkle Proof that shows the output being spent by this input + /// existed and was unspent at the time of this block (proof of inclusion in utxo_root) + pub merkle_proof: Option, } hashable_ord!(Input); +/// TODO - no clean way to bridge core::hash::Hash and std::hash::Hash implementations? +impl ::std::hash::Hash for Input { + fn hash(&self, state: &mut H) { + let mut vec = Vec::new(); + ser::serialize(&mut vec, &self).expect("serialization failed"); + ::std::hash::Hash::hash(&vec, state); + } +} + /// Implementation of Writeable for a transaction Input, defines how to write /// an Input as binary. impl Writeable for Input { fn write(&self, writer: &mut W) -> Result<(), ser::Error> { writer.write_u8(self.features.bits())?; - writer.write_fixed_bytes(&self.commit)?; + self.commit.write(writer)?; - if self.features.contains(OutputFeatures::COINBASE_OUTPUT) { - writer.write_fixed_bytes(&self.out_block.unwrap_or(ZERO_HASH))?; + if writer.serialization_mode() != ser::SerializationMode::Hash { + if self.features.contains(OutputFeatures::COINBASE_OUTPUT) { + let block_hash = &self.block_hash.unwrap_or(ZERO_HASH); + let merkle_proof = self.merkle_proof(); + + writer.write_fixed_bytes(block_hash)?; + merkle_proof.write(writer)?; + } } Ok(()) @@ -517,17 +564,23 @@ impl Readable for Input { let commit = Commitment::read(reader)?; - let out_block = if features.contains(OutputFeatures::COINBASE_OUTPUT) { - Some(Hash::read(reader)?) + if features.contains(OutputFeatures::COINBASE_OUTPUT) { + let block_hash = Some(Hash::read(reader)?); + let merkle_proof = Some(MerkleProof::read(reader)?); + Ok(Input::new( + features, + commit, + block_hash, + merkle_proof, + )) } else { - None - }; - - Ok(Input::new( - features, - commit, - out_block, - )) + Ok(Input::new( + features, + commit, + None, + None, + )) + } } } @@ -536,24 +589,102 @@ impl Readable for Input { /// Input must also provide the original output features and the hash of the block /// the output originated from. impl Input { - /// Build a new input from the data required to identify and verify an output beng spent. + /// Build a new input from the data required to identify and verify an output being spent. pub fn new( features: OutputFeatures, commit: Commitment, - out_block: Option, + block_hash: Option, + merkle_proof: Option, ) -> Input { Input { features, commit, - out_block, + block_hash, + merkle_proof, } } /// The input commitment which _partially_ identifies the output being spent. /// In the presence of a fork we need additional info to uniquely identify the output. - /// Specifically the block hash (so correctly calculate lock_height for coinbase outputs). + /// Specifically the block hash (to correctly calculate lock_height for coinbase outputs). pub fn commitment(&self) -> Commitment { - self.commit + self.commit.clone() + } + + /// Convenience functon to return the (optional) block_hash for this input. + /// Will return the "zero" hash if we do not have one. + pub fn block_hash(&self) -> Hash { + let block_hash = self.block_hash.clone(); + block_hash.unwrap_or(Hash::zero()) + } + + /// Convenience function to return the (optional) merkle_proof for this input. + /// Will return the "empty" Merkle proof if we do not have one. + /// We currently only care about the Merkle proof for inputs spending coinbase outputs. + pub fn merkle_proof(&self) -> MerkleProof { + let merkle_proof = self.merkle_proof.clone(); + merkle_proof.unwrap_or(MerkleProof::empty()) + } + + /// Verify the maturity of an output being spent by an input. + /// Only relevant for spending coinbase outputs currently (locked for 1,000 confirmations). + /// + /// The proof associates the output with the root by its hash (and pos) in the MMR. + /// The proof shows the output existed and was unspent at the time the utxo_root was built. + /// The root associates the proof with a specific block header with that utxo_root. + /// So the proof shows the output was unspent at the time of the block + /// and is at least as old as that block (may be older). + /// + /// We can verify maturity of the output being spent by - + /// + /// * verifying the Merkle Proof produces the correct root for the given hash (from MMR) + /// * verifying the root matches the utxo_root in the block_header + /// * verifying the hash matches the node hash in the Merkle Proof + /// * finally verify maturity rules based on height of the block header + /// + pub fn verify_maturity( + &self, + hash: Hash, + header: &BlockHeader, + height: u64, + ) -> Result<(), Error> { + if self.features.contains(OutputFeatures::COINBASE_OUTPUT) { + let block_hash = self.block_hash(); + let merkle_proof = self.merkle_proof(); + + // Check we are dealing with the correct block header + if block_hash != header.hash() { + return Err(Error::MerkleProof); + } + + // Is our Merkle Proof valid? Does node hash up consistently to the root? + if !merkle_proof.verify() { + return Err(Error::MerkleProof); + } + + // Is the root the correct root for the given block header? + if merkle_proof.root != header.utxo_root { + return Err(Error::MerkleProof); + } + + // Does the hash from the MMR actually match the one in the Merkle Proof? + if merkle_proof.node != hash { + return Err(Error::MerkleProof); + } + + // Finally has the output matured sufficiently now we know the block? + let lock_height = header.height + global::coinbase_maturity(); + if lock_height > height { + return Err(Error::ImmatureCoinbase); + } + debug!( + LOGGER, + "input: verify_maturity: success, coinbase maturity via Merkle proof: {} vs. {}", + lock_height, + height, + ); + } + Ok(()) } } @@ -703,7 +834,7 @@ pub struct Output { hashable_ord!(Output); /// TODO - no clean way to bridge core::hash::Hash and std::hash::Hash implementations? -impl ::std::hash::Hash for TxKernel { +impl ::std::hash::Hash for Output { fn hash(&self, state: &mut H) { let mut vec = Vec::new(); ser::serialize(&mut vec, &self).expect("serialization failed"); @@ -716,7 +847,7 @@ impl ::std::hash::Hash for TxKernel { impl Writeable for Output { fn write(&self, writer: &mut W) -> Result<(), ser::Error> { writer.write_u8(self.features.bits())?; - writer.write_fixed_bytes(&self.commit)?; + self.commit.write(writer)?; // Hash of an output doesn't cover the switch commit, it should // be wound into the range proof separately if writer.serialization_mode() != ser::SerializationMode::Hash { @@ -792,7 +923,7 @@ impl Output { } -/// An output_identifier can be build from either an input _or_ and output and +/// An output_identifier can be build from either an input _or_ an output and /// contains everything we need to uniquely identify an output being spent. /// Needed because it is not sufficient to pass a commitment around. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] @@ -873,25 +1004,25 @@ pub struct OutputStoreable { } impl OutputStoreable { -/// Build a StoreableOutput from an existing output. -pub fn from_output(output: &Output) -> OutputStoreable { - OutputStoreable { - features: output.features, - commit: output.commit, - switch_commit_hash: output.switch_commit_hash, + /// Build a StoreableOutput from an existing output. + pub fn from_output(output: &Output) -> OutputStoreable { + OutputStoreable { + features: output.features, + commit: output.commit, + switch_commit_hash: output.switch_commit_hash, + } } -} -/// Return a regular output -pub fn to_output(self, rproof: RangeProof) -> Output { - Output{ - features: self.features, - commit: self.commit, - switch_commit_hash: self.switch_commit_hash, - proof: rproof, + /// Return a regular output + pub fn to_output(self, rproof: RangeProof) -> Output { + Output{ + features: self.features, + commit: self.commit, + switch_commit_hash: self.switch_commit_hash, + proof: rproof, + } } } -} impl PMMRable for OutputStoreable { fn len() -> usize { @@ -966,178 +1097,176 @@ impl ProofMessageElements { #[cfg(test)] mod test { -use super::*; -use core::id::{ShortId, ShortIdentifiable}; -use keychain::Keychain; -use util::secp; + use super::*; + use core::id::{ShortId, ShortIdentifiable}; + use keychain::Keychain; + use util::secp; -#[test] -fn test_kernel_ser_deser() { - let keychain = Keychain::from_random_seed().unwrap(); - let key_id = keychain.derive_key_id(1).unwrap(); - let commit = keychain.commit(5, &key_id).unwrap(); + #[test] + fn test_kernel_ser_deser() { + let keychain = Keychain::from_random_seed().unwrap(); + let key_id = keychain.derive_key_id(1).unwrap(); + let commit = keychain.commit(5, &key_id).unwrap(); - // just some bytes for testing ser/deser - let sig = secp::Signature::from_raw_data(&[0;64]).unwrap(); + // just some bytes for testing ser/deser + let sig = secp::Signature::from_raw_data(&[0;64]).unwrap(); - let kernel = TxKernel { - features: KernelFeatures::DEFAULT_KERNEL, - lock_height: 0, - excess: commit, - excess_sig: sig.clone(), - fee: 10, - }; + let kernel = TxKernel { + features: KernelFeatures::DEFAULT_KERNEL, + lock_height: 0, + excess: commit, + excess_sig: sig.clone(), + fee: 10, + }; - let mut vec = vec![]; - ser::serialize(&mut vec, &kernel).expect("serialized failed"); - let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap(); - assert_eq!(kernel2.features, KernelFeatures::DEFAULT_KERNEL); - assert_eq!(kernel2.lock_height, 0); - assert_eq!(kernel2.excess, commit); - assert_eq!(kernel2.excess_sig, sig.clone()); - assert_eq!(kernel2.fee, 10); + let mut vec = vec![]; + ser::serialize(&mut vec, &kernel).expect("serialized failed"); + let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap(); + assert_eq!(kernel2.features, KernelFeatures::DEFAULT_KERNEL); + assert_eq!(kernel2.lock_height, 0); + assert_eq!(kernel2.excess, commit); + assert_eq!(kernel2.excess_sig, sig.clone()); + assert_eq!(kernel2.fee, 10); - // now check a kernel with lock_height serializes/deserializes correctly - let kernel = TxKernel { - features: KernelFeatures::DEFAULT_KERNEL, - lock_height: 100, - excess: commit, - excess_sig: sig.clone(), - fee: 10, - }; + // now check a kernel with lock_height serializes/deserializes correctly + let kernel = TxKernel { + features: KernelFeatures::DEFAULT_KERNEL, + lock_height: 100, + excess: commit, + excess_sig: sig.clone(), + fee: 10, + }; - let mut vec = vec![]; - ser::serialize(&mut vec, &kernel).expect("serialized failed"); - let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap(); - assert_eq!(kernel2.features, KernelFeatures::DEFAULT_KERNEL); - assert_eq!(kernel2.lock_height, 100); - assert_eq!(kernel2.excess, commit); - assert_eq!(kernel2.excess_sig, sig.clone()); - assert_eq!(kernel2.fee, 10); -} - -#[test] -fn test_output_ser_deser() { - let keychain = Keychain::from_random_seed().unwrap(); - let key_id = keychain.derive_key_id(1).unwrap(); - let commit = keychain.commit(5, &key_id).unwrap(); - let switch_commit = keychain.switch_commit(&key_id).unwrap(); - let switch_commit_hash = SwitchCommitHash::from_switch_commit( - switch_commit, - &keychain, - &key_id, - ); - let msg = secp::pedersen::ProofMessage::empty(); - let proof = keychain.range_proof(5, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap(); - - let out = Output { - features: OutputFeatures::DEFAULT_OUTPUT, - commit: commit, - switch_commit_hash: switch_commit_hash, - proof: proof, - }; - - let mut vec = vec![]; - ser::serialize(&mut vec, &out).expect("serialized failed"); - let dout: Output = ser::deserialize(&mut &vec[..]).unwrap(); - - assert_eq!(dout.features, OutputFeatures::DEFAULT_OUTPUT); - assert_eq!(dout.commit, out.commit); - assert_eq!(dout.proof, out.proof); -} - -#[test] -fn test_output_value_recovery() { - let keychain = Keychain::from_random_seed().unwrap(); - let key_id = keychain.derive_key_id(1).unwrap(); - let value = 1003; - - let commit = keychain.commit(value, &key_id).unwrap(); - let switch_commit = keychain.switch_commit(&key_id).unwrap(); - let switch_commit_hash = SwitchCommitHash::from_switch_commit( - switch_commit, - &keychain, - &key_id, - ); - let msg = (ProofMessageElements { - value: value, - }).to_proof_message(); - - let proof = keychain.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap(); - - let output = Output { - features: OutputFeatures::DEFAULT_OUTPUT, - commit: commit, - switch_commit_hash: switch_commit_hash, - proof: proof, - }; - - // check we can successfully recover the value with the original blinding factor - let result = output.recover_value(&keychain, &key_id); - // TODO: Remove this check once value recovery is supported within bullet proofs - if let Some(v) = result { - assert_eq!(v, 1003); - } else { - return; + let mut vec = vec![]; + ser::serialize(&mut vec, &kernel).expect("serialized failed"); + let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap(); + assert_eq!(kernel2.features, KernelFeatures::DEFAULT_KERNEL); + assert_eq!(kernel2.lock_height, 100); + assert_eq!(kernel2.excess, commit); + assert_eq!(kernel2.excess_sig, sig.clone()); + assert_eq!(kernel2.fee, 10); } - // Bulletproofs message unwind will just be gibberish given the wrong blinding factor -} - -#[test] -fn commit_consistency() { - let keychain = Keychain::from_seed(&[0; 32]).unwrap(); - let key_id = keychain.derive_key_id(1).unwrap(); - - let commit = keychain.commit(1003, &key_id).unwrap(); - let switch_commit = keychain.switch_commit(&key_id).unwrap(); - println!("Switch commit: {:?}", switch_commit); - println!("commit: {:?}", commit); - let key_id = keychain.derive_key_id(1).unwrap(); - - let switch_commit_2 = keychain.switch_commit(&key_id).unwrap(); - let commit_2 = keychain.commit(1003, &key_id).unwrap(); - println!("Switch commit 2: {:?}", switch_commit_2); - println!("commit2 : {:?}", commit_2); - - assert!(commit == commit_2); - assert!(switch_commit == switch_commit_2); -} - -#[test] -fn input_short_id() { - let keychain = Keychain::from_seed(&[0; 32]).unwrap(); - let key_id = keychain.derive_key_id(1).unwrap(); - let commit = keychain.commit(5, &key_id).unwrap(); - - let input = Input { - features: OutputFeatures::DEFAULT_OUTPUT, - commit: commit, - out_block: None, - }; - - let block_hash = Hash::from_hex( - "3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673", - ).unwrap(); - - let nonce = 0; - - let short_id = input.short_id(&block_hash, nonce); - assert_eq!(short_id, ShortId::from_hex("28fea5a693af").unwrap()); - - // now generate the short_id for a *very* similar output (single feature flag different) - // and check it generates a different short_id - let input = Input { - features: OutputFeatures::COINBASE_OUTPUT, - commit: commit, - out_block: None, - }; - - let block_hash = Hash::from_hex( - "3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673", - ).unwrap(); - - let short_id = input.short_id(&block_hash, nonce); - assert_eq!(short_id, ShortId::from_hex("c8af83b54e46").unwrap()); -} + #[test] + fn test_output_ser_deser() { + let keychain = Keychain::from_random_seed().unwrap(); + let key_id = keychain.derive_key_id(1).unwrap(); + let commit = keychain.commit(5, &key_id).unwrap(); + let switch_commit = keychain.switch_commit(&key_id).unwrap(); + let switch_commit_hash = SwitchCommitHash::from_switch_commit( + switch_commit, + &keychain, + &key_id, + ); + let msg = secp::pedersen::ProofMessage::empty(); + let proof = keychain.range_proof(5, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap(); + + let out = Output { + features: OutputFeatures::DEFAULT_OUTPUT, + commit: commit, + switch_commit_hash: switch_commit_hash, + proof: proof, + }; + + let mut vec = vec![]; + ser::serialize(&mut vec, &out).expect("serialized failed"); + let dout: Output = ser::deserialize(&mut &vec[..]).unwrap(); + + assert_eq!(dout.features, OutputFeatures::DEFAULT_OUTPUT); + assert_eq!(dout.commit, out.commit); + assert_eq!(dout.proof, out.proof); + } + + #[test] + fn test_output_value_recovery() { + let keychain = Keychain::from_random_seed().unwrap(); + let key_id = keychain.derive_key_id(1).unwrap(); + let value = 1003; + + let commit = keychain.commit(value, &key_id).unwrap(); + let switch_commit = keychain.switch_commit(&key_id).unwrap(); + let switch_commit_hash = SwitchCommitHash::from_switch_commit( + switch_commit, + &keychain, + &key_id, + ); + let msg = (ProofMessageElements { + value: value, + }).to_proof_message(); + + let proof = keychain.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap(); + + let output = Output { + features: OutputFeatures::DEFAULT_OUTPUT, + commit: commit, + switch_commit_hash: switch_commit_hash, + proof: proof, + }; + + // check we can successfully recover the value with the original blinding factor + let result = output.recover_value(&keychain, &key_id); + // TODO: Remove this check once value recovery is supported within bullet proofs + if let Some(v) = result { + assert_eq!(v, 1003); + } else { + return; + } + + // Bulletproofs message unwind will just be gibberish given the wrong blinding factor + } + + #[test] + fn commit_consistency() { + let keychain = Keychain::from_seed(&[0; 32]).unwrap(); + let key_id = keychain.derive_key_id(1).unwrap(); + + let commit = keychain.commit(1003, &key_id).unwrap(); + let switch_commit = keychain.switch_commit(&key_id).unwrap(); + println!("Switch commit: {:?}", switch_commit); + println!("commit: {:?}", commit); + let key_id = keychain.derive_key_id(1).unwrap(); + + let switch_commit_2 = keychain.switch_commit(&key_id).unwrap(); + let commit_2 = keychain.commit(1003, &key_id).unwrap(); + println!("Switch commit 2: {:?}", switch_commit_2); + println!("commit2 : {:?}", commit_2); + + assert!(commit == commit_2); + assert!(switch_commit == switch_commit_2); + } + + #[test] + fn input_short_id() { + let keychain = Keychain::from_seed(&[0; 32]).unwrap(); + let key_id = keychain.derive_key_id(1).unwrap(); + let commit = keychain.commit(5, &key_id).unwrap(); + + let input = Input { + features: OutputFeatures::DEFAULT_OUTPUT, + commit: commit, + block_hash: None, + merkle_proof: None, + }; + + let block_hash = Hash::from_hex( + "3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673", + ).unwrap(); + + let nonce = 0; + + let short_id = input.short_id(&block_hash, nonce); + assert_eq!(short_id, ShortId::from_hex("28fea5a693af").unwrap()); + + // now generate the short_id for a *very* similar output (single feature flag different) + // and check it generates a different short_id + let input = Input { + features: OutputFeatures::COINBASE_OUTPUT, + commit: commit, + block_hash: None, + merkle_proof: None, + }; + + let short_id = input.short_id(&block_hash, nonce); + assert_eq!(short_id, ShortId::from_hex("2df325971ab0").unwrap()); + } } diff --git a/grin/src/adapters.rs b/grin/src/adapters.rs index 7fb3c2df9..448f855b0 100644 --- a/grin/src/adapters.rs +++ b/grin/src/adapters.rs @@ -573,7 +573,7 @@ impl PoolToChainAdapter { } impl pool::BlockChain for PoolToChainAdapter { - fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<(), pool::PoolError> { + fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result { wo(&self.chain) .is_unspent(output_ref) .map_err(|e| match e { diff --git a/pool/src/blockchain.rs b/pool/src/blockchain.rs index 76c93153a..97ea4778b 100644 --- a/pool/src/blockchain.rs +++ b/pool/src/blockchain.rs @@ -106,9 +106,9 @@ impl DummyChainImpl { } impl BlockChain for DummyChainImpl { - fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<(), PoolError> { + fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result { match self.utxo.read().unwrap().get_output(&output_ref.commit) { - Some(_) => Ok(()), + Some(_) => Ok(hash::Hash::zero()), None => Err(PoolError::GenericPoolError), } } @@ -117,7 +117,7 @@ impl BlockChain for DummyChainImpl { if !input.features.contains(OutputFeatures::COINBASE_OUTPUT) { return Ok(()); } - let block_hash = input.out_block.expect("requires a block hash"); + let block_hash = input.block_hash.expect("requires a block hash"); let headers = self.block_headers.read().unwrap(); if let Some(h) = headers .iter() @@ -127,7 +127,7 @@ impl BlockChain for DummyChainImpl { return Ok(()); } } - Err(PoolError::ImmatureCoinbase) + Err(PoolError::InvalidTx(transaction::Error::ImmatureCoinbase)) } fn head_header(&self) -> Result { diff --git a/pool/src/graph.rs b/pool/src/graph.rs index 0f38a3c44..1d89103f8 100644 --- a/pool/src/graph.rs +++ b/pool/src/graph.rs @@ -320,11 +320,13 @@ mod tests { OutputFeatures::DEFAULT_OUTPUT, keychain.commit(50, &key_id2).unwrap(), None, + None, ), core::transaction::Input::new( OutputFeatures::DEFAULT_OUTPUT, keychain.commit(25, &key_id3).unwrap(), None, + None, ), ]; let msg = secp::pedersen::ProofMessage::empty(); diff --git a/pool/src/pool.rs b/pool/src/pool.rs index fbbd22893..76d217b56 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -186,7 +186,7 @@ where } // Making sure the transaction is valid before anything else. - tx.validate().map_err(|_e| PoolError::Invalid)?; + tx.validate().map_err(|e| PoolError::InvalidTx(e))?; // The first check involves ensuring that an identical transaction is // not already in the pool's transaction set. @@ -646,9 +646,10 @@ mod tests { use blake2; use core::global::ChainTypes; use core::core::SwitchCommitHash; - use core::core::hash::ZERO_HASH; use core::core::hash::{Hash, Hashed}; + use core::core::pmmr::MerkleProof; use core::core::target::Difficulty; + use types::PoolError::InvalidTx; macro_rules! expect_output_parent { ($pool:expr, $expected:pat, $( $output:expr ),+ ) => { @@ -873,7 +874,7 @@ mod tests { ); let result = write_pool.add_to_memory_pool(test_source(), txn); match result { - Err(PoolError::ImmatureCoinbase) => {}, + Err(InvalidTx(transaction::Error::ImmatureCoinbase)) => {}, _ => panic!("expected ImmatureCoinbase error here"), }; @@ -1024,22 +1025,22 @@ mod tests { // invalidated (orphaned). let conflict_child = test_transaction(vec![12], vec![2]); // 7. A transaction that descends from transaction 2 that should be - // valid due to its inputs being satisfied by the block. + // valid due to its inputs being satisfied by the block. let conflict_valid_child = test_transaction(vec![6], vec![4]); // 8. A transaction that descends from transaction 3 that should be - // invalidated due to an output conflict. + // invalidated due to an output conflict. let valid_child_conflict = test_transaction(vec![13], vec![9]); // 9. A transaction that descends from transaction 3 that should remain - // valid after reconciliation. + // valid after reconciliation. let valid_child_valid = test_transaction(vec![15], vec![11]); // 10. A transaction that descends from both transaction 6 and - // transaction 9 + // transaction 9 let mixed_child = test_transaction(vec![2, 11], vec![7]); // Add transactions. - // Note: There are some ordering constraints that must be followed here - // until orphans is 100% implemented. Once the orphans process has - // stabilized, we can mix these up to exercise that path a bit. + // Note: There are some ordering constraints that must be followed here + // until orphans is 100% implemented. Once the orphans process has + // stabilized, we can mix these up to exercise that path a bit. let mut txs_to_add = vec![ block_transaction, conflict_transaction, @@ -1056,7 +1057,7 @@ mod tests { let expected_pool_size = txs_to_add.len(); // First we add the above transactions to the pool; all should be - // accepted. + // accepted. { let mut write_pool = pool.write().unwrap(); assert_eq!(write_pool.total_size(), 0); @@ -1068,8 +1069,8 @@ mod tests { assert_eq!(write_pool.total_size(), expected_pool_size); } // Now we prepare the block that will cause the above condition. - // First, the transactions we want in the block: - // - Copy of 1 + // First, the transactions we want in the block: + // - Copy of 1 let block_tx_1 = test_transaction(vec![10], vec![8]); // - Conflict w/ 2, satisfies 7 let block_tx_2 = test_transaction(vec![20], vec![6]); @@ -1103,7 +1104,7 @@ mod tests { assert_eq!(evicted_transactions.unwrap().len(), 6); // TODO: Txids are not yet deterministic. When they are, we should - // check the specific transactions that were evicted. + // check the specific transactions that were evicted. } @@ -1204,8 +1205,8 @@ mod tests { txs = read_pool.prepare_mineable_transactions(3); assert_eq!(txs.len(), 3); // TODO: This is ugly, either make block::new take owned - // txs instead of mut refs, or change - // prepare_mineable_transactions to return mut refs + // txs instead of mut refs, or change + // prepare_mineable_transactions to return mut refs let block_txs: Vec = txs.drain(..).map(|x| *x).collect(); let tx_refs = block_txs.iter().collect(); @@ -1276,7 +1277,7 @@ mod tests { for input_value in input_values { let key_id = keychain.derive_key_id(input_value as u32).unwrap(); - tx_elements.push(build::input(input_value, ZERO_HASH, key_id)); + tx_elements.push(build::input(input_value, key_id)); } for output_value in output_values { @@ -1304,9 +1305,22 @@ mod tests { let mut tx_elements = Vec::new(); - // for input_value in input_values { + let merkle_proof = MerkleProof { + node: Hash::zero(), + root: Hash::zero(), + peaks: vec![Hash::zero()], + .. MerkleProof::default() + }; + let key_id = keychain.derive_key_id(input_value as u32).unwrap(); - tx_elements.push(build::coinbase_input(input_value, input_block_hash, key_id)); + tx_elements.push( + build::coinbase_input( + input_value, + input_block_hash, + merkle_proof, + key_id, + ), + ); for output_value in output_values { let key_id = keychain.derive_key_id(output_value as u32).unwrap(); @@ -1334,7 +1348,7 @@ mod tests { for input_value in input_values { let key_id = keychain.derive_key_id(input_value as u32).unwrap(); - tx_elements.push(build::input(input_value, ZERO_HASH, key_id)); + tx_elements.push(build::input(input_value, key_id)); } for output_value in output_values { diff --git a/pool/src/types.rs b/pool/src/types.rs index 9c6401224..3d6c9d2e0 100644 --- a/pool/src/types.rs +++ b/pool/src/types.rs @@ -103,8 +103,8 @@ impl fmt::Debug for Parent { /// Enum of errors #[derive(Debug)] pub enum PoolError { - /// An invalid pool entry - Invalid, + /// An invalid pool entry caused by underlying tx validation error + InvalidTx(transaction::Error), /// An entry already in the pool AlreadyInPool, /// A duplicate output @@ -123,9 +123,6 @@ pub enum PoolError { /// The spent output spent_output: Commitment, }, - /// Attempt to spend an output before it matures - /// lock_height must not exceed current block height - ImmatureCoinbase, /// Attempt to add a transaction to the pool with lock_height /// greater than height of current block ImmatureTransaction { @@ -155,7 +152,7 @@ pub trait BlockChain { /// orphans, etc. /// We do not maintain outputs themselves. The only information we have is the /// hash from the output MMR. - fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<(), PoolError>; + fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result; /// Check if an output being spent by the input has sufficiently matured. /// This is only applicable for coinbase outputs (1,000 blocks). diff --git a/store/src/pmmr.rs b/store/src/pmmr.rs index 47d8ba972..b7d432f98 100644 --- a/store/src/pmmr.rs +++ b/store/src/pmmr.rs @@ -69,13 +69,7 @@ where Ok(()) } - /// Get a Hash by insertion position - fn get(&self, position: u64, include_data:bool) -> Option<(Hash, Option)> { - // Check if this position has been pruned in the remove log or the - // pruned list - if self.rm_log.includes(position) { - return None; - } + fn get_from_file(&self, position: u64) -> Option { let shift = self.pruned_nodes.get_shift(position); if let None = shift { return None; @@ -89,8 +83,8 @@ where let hash_record_len = 32; let file_offset = ((pos - shift.unwrap()) as usize) * hash_record_len; let data = self.hash_file.read(file_offset, hash_record_len); - let hash_val = match ser::deserialize(&mut &data[..]) { - Ok(h) => h, + match ser::deserialize(&mut &data[..]) { + Ok(h) => Some(h), Err(e) => { error!( LOGGER, @@ -99,10 +93,26 @@ where ); return None; } - }; + } + } + /// Get a Hash by insertion position + fn get(&self, position: u64, include_data: bool) -> Option<(Hash, Option)> { + // Check if this position has been pruned in the remove log or the + // pruned list + if self.rm_log.includes(position) { + return None; + } + + let hash_val = self.get_from_file(position); + + // TODO - clean this up if !include_data { - return Some(((hash_val), None)); + if let Some(hash) = hash_val { + return Some((hash, None)); + } else { + return None; + } } // Optionally read flatfile storage to get data element @@ -123,7 +133,12 @@ where } }; - Some((hash_val, data)) + // TODO - clean this up + if let Some(hash) = hash_val { + return Some((hash, data)); + } else { + return None; + } } fn rewind(&mut self, position: u64, index: u32) -> Result<(), String> { @@ -338,5 +353,3 @@ where Ok(()) } } - - diff --git a/wallet/src/checker.rs b/wallet/src/checker.rs index 4e03b0f5f..cdbdb8ae7 100644 --- a/wallet/src/checker.rs +++ b/wallet/src/checker.rs @@ -1,4 +1,4 @@ -// Copyright 2017 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ use std::collections::HashMap; use failure::{ResultExt}; use api; -use core::core::hash::Hash; use types::*; use keychain::{Identifier, Keychain}; use util::secp::pedersen; @@ -65,7 +64,7 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R .values() .filter(|x| { x.root_key_id == keychain.root_key_id() && - x.block.hash() == Hash::zero() && + x.block.is_none() && x.status == OutputStatus::Unspent }) { @@ -113,11 +112,16 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R debug!(LOGGER, "{:?}", url); let mut api_blocks: HashMap = HashMap::new(); + let mut api_merkle_proofs: HashMap = HashMap::new(); match api::client::get::>(url.as_str()) { Ok(blocks) => { for block in blocks { for out in block.outputs { api_blocks.insert(out.commit, block.header.clone()); + if let Some(merkle_proof) = out.merkle_proof { + let wrapper = MerkleProofWrapper(merkle_proof); + api_merkle_proofs.insert(out.commit, wrapper); + } } } } @@ -138,8 +142,11 @@ fn refresh_missing_block_hashes(config: &WalletConfig, keychain: &Keychain) -> R if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) { if let Some(b) = api_blocks.get(&commit) { let output = output.get_mut(); - output.block = BlockIdentifier::from_str(&b.hash).unwrap(); + output.block = Some(BlockIdentifier::from_hex(&b.hash).unwrap()); output.height = b.height; + if let Some(merkle_proof) = api_merkle_proofs.get(&commit) { + output.merkle_proof = Some(merkle_proof.clone()); + } } } } diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 1df34e212..073b6954c 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -98,7 +98,8 @@ fn handle_sender_initiation( height: 0, lock_height: 0, is_coinbase: false, - block: BlockIdentifier::zero(), + block: None, + merkle_proof: None, }); key_id @@ -322,7 +323,8 @@ pub fn receive_coinbase( height: height, lock_height: lock_height, is_coinbase: true, - block: BlockIdentifier::zero(), + block: None, + merkle_proof: None, }); (key_id, derivation) @@ -404,7 +406,8 @@ fn build_final_transaction( height: 0, lock_height: 0, is_coinbase: false, - block: BlockIdentifier::zero(), + block: None, + merkle_proof: None, }); (key_id, derivation) diff --git a/wallet/src/restore.rs b/wallet/src/restore.rs index 913ec5813..8d0806638 100644 --- a/wallet/src/restore.rs +++ b/wallet/src/restore.rs @@ -1,4 +1,4 @@ -// Copyright 2017 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ use api; use core::global; use core::core::{Output, SwitchCommitHash}; use core::core::transaction::OutputFeatures; -use types::{BlockIdentifier, WalletConfig, WalletData, OutputData, OutputStatus, Error, ErrorKind}; +use types::{WalletConfig, WalletData, OutputData, OutputStatus, Error, ErrorKind}; use byteorder::{BigEndian, ByteOrder}; @@ -315,7 +315,8 @@ pub fn restore( height: output.4, lock_height: output.5, is_coinbase: output.6, - block: BlockIdentifier::zero(), + block: None, + merkle_proof: None, }); }; } diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index 0487e56b1..a337bc1ac 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -331,9 +331,18 @@ fn inputs_and_change( for coin in coins { let key_id = keychain.derive_key_id(coin.n_child).context(ErrorKind::Keychain)?; if coin.is_coinbase { - parts.push(build::coinbase_input(coin.value, coin.block.hash(), key_id)); + let block = coin.block.clone(); + let merkle_proof = coin.merkle_proof.clone(); + let merkle_proof = merkle_proof.unwrap().merkle_proof(); + + parts.push(build::coinbase_input( + coin.value, + block.unwrap().hash(), + merkle_proof, + key_id, + )); } else { - parts.push(build::input(coin.value, coin.block.hash(), key_id)); + parts.push(build::input(coin.value, key_id)); } } @@ -352,7 +361,8 @@ fn inputs_and_change( height: 0, lock_height: 0, is_coinbase: false, - block: BlockIdentifier::zero(), + block: None, + merkle_proof: None, }); change_key @@ -366,7 +376,6 @@ fn inputs_and_change( #[cfg(test)] mod test { use core::core::build; - use core::core::hash::ZERO_HASH; use keychain::Keychain; @@ -378,7 +387,7 @@ mod test { let key_id1 = keychain.derive_key_id(1).unwrap(); let tx1 = build::transaction(vec![build::output(105, key_id1.clone())], &keychain).unwrap(); - let tx2 = build::transaction(vec![build::input(105, ZERO_HASH, key_id1.clone())], &keychain).unwrap(); + let tx2 = build::transaction(vec![build::input(105, key_id1.clone())], &keychain).unwrap(); assert_eq!(tx1.outputs[0].features, tx2.inputs[0].features); assert_eq!(tx1.outputs[0].commitment(), tx2.inputs[0].commitment()); diff --git a/wallet/src/types.rs b/wallet/src/types.rs index b30f4f961..55a9c43bf 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2017 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ use failure::{Backtrace, Context, Fail, ResultExt}; use core::consensus; use core::core::Transaction; use core::core::hash::Hash; +use core::core::pmmr::MerkleProof; use core::ser; use keychain; use keychain::BlindingFactor; @@ -98,7 +99,7 @@ pub enum ErrorKind { #[fail(display = "JSON format error")] Format, - + #[fail(display = "I/O error")] IO, @@ -124,7 +125,6 @@ pub enum ErrorKind { /// Wallet seed already exists #[fail(display = "Wallet seed exists error")] WalletSeedExists, - #[fail(display = "Generic error: {}", _0)] GenericError(&'static str), @@ -226,6 +226,52 @@ impl fmt::Display for OutputStatus { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct MerkleProofWrapper(pub MerkleProof); + +impl MerkleProofWrapper { + pub fn merkle_proof(&self) -> MerkleProof { + self.0.clone() + } +} + +impl serde::ser::Serialize for MerkleProofWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.0.to_hex()) + } +} + +impl<'de> serde::de::Deserialize<'de> for MerkleProofWrapper { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + deserializer.deserialize_str(MerkleProofWrapperVisitor) + } +} + +struct MerkleProofWrapperVisitor; + +impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor { + type Value = MerkleProofWrapper; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a merkle proof") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + let merkle_proof = MerkleProof::from_hex(s).unwrap(); + Ok(MerkleProofWrapper(merkle_proof)) + } +} + + #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct BlockIdentifier(Hash); @@ -234,14 +280,10 @@ impl BlockIdentifier { self.0 } - pub fn from_str(hex: &str) -> Result { + pub fn from_hex(hex: &str) -> Result { let hash = Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex"))?; Ok(BlockIdentifier(hash)) } - - pub fn zero() -> BlockIdentifier { - BlockIdentifier(Hash::zero()) - } } impl serde::ser::Serialize for BlockIdentifier { @@ -302,7 +344,8 @@ pub struct OutputData { /// Is this a coinbase output? Is it subject to coinbase locktime? pub is_coinbase: bool, /// Hash of the block this output originated from. - pub block: BlockIdentifier, + pub block: Option, + pub merkle_proof: Option, } impl OutputData { @@ -529,8 +572,8 @@ impl WalletData { fn read_outputs(data_file_path: &str) -> Result, Error> { let data_file = File::open(data_file_path).context(ErrorKind::WalletData(&"Could not open wallet file"))?; serde_json::from_reader(data_file).map_err(|e| { e.context(ErrorKind::WalletData(&"Error reading wallet file ")).into()}) - - + + } /// Populate wallet_data with output_data from disk.