From 0ff6763ee64e5a14e70ddd4642b99789a1648a32 Mon Sep 17 00:00:00 2001
From: Antioch Peverell <30642645+antiochp@users.noreply.github.com>
Date: Wed, 20 Jun 2018 15:18:52 -0400
Subject: [PATCH] [consensus breaking] New Improved Merkle Proofs (#1119)
* new improved Merkle proofs
* fix pool and chain tests
* fixup core tests after the merge
---
api/src/types.rs | 2 +-
chain/src/chain.rs | 2 +-
chain/src/txhashset.rs | 35 +++-
chain/src/types.rs | 4 +
chain/tests/test_coinbase_maturity.rs | 2 +-
chain/tests/test_txhashset_raw_txs.rs | 2 +-
config/src/types.rs | 4 +-
core/src/core/block.rs | 31 ----
core/src/core/merkle_proof.rs | 156 ++++++++++++++++
core/src/core/mod.rs | 1 +
core/src/core/pmmr.rs | 252 +++++++-------------------
core/src/core/transaction.rs | 65 +------
core/tests/common/mod.rs | 2 +-
core/tests/merkle_proof.rs | 193 ++++++++++++++++++++
core/tests/pmmr.rs | 238 +-----------------------
core/tests/vec_backend/mod.rs | 165 +++++++++++++++++
keychain/src/extkey.rs | 4 +-
keychain/src/types.rs | 2 +-
pool/tests/common/mod.rs | 2 +-
servers/src/mining/stratumserver.rs | 4 +-
store/src/leaf_set.rs | 2 +-
wallet/src/libtx/build.rs | 2 +-
wallet/src/libwallet/types.rs | 3 +-
23 files changed, 632 insertions(+), 541 deletions(-)
create mode 100644 core/src/core/merkle_proof.rs
create mode 100644 core/tests/merkle_proof.rs
create mode 100644 core/tests/vec_backend/mod.rs
diff --git a/api/src/types.rs b/api/src/types.rs
index 90875c24d..6b50a4afe 100644
--- a/api/src/types.rs
+++ b/api/src/types.rs
@@ -16,7 +16,7 @@ use std::sync::Arc;
use chain;
use core::core::hash::Hashed;
-use core::core::pmmr::MerkleProof;
+use core::core::merkle_proof::MerkleProof;
use core::{core, ser};
use p2p;
use serde;
diff --git a/chain/src/chain.rs b/chain/src/chain.rs
index b5001d62e..743924adb 100644
--- a/chain/src/chain.rs
+++ b/chain/src/chain.rs
@@ -21,7 +21,7 @@ use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use core::core::hash::{Hash, Hashed};
-use core::core::pmmr::MerkleProof;
+use core::core::merkle_proof::MerkleProof;
use core::core::target::Difficulty;
use core::core::Committed;
use core::core::{Block, BlockHeader, Output, OutputIdentifier, Transaction, TxKernel};
diff --git a/chain/src/txhashset.rs b/chain/src/txhashset.rs
index 336dfd529..46004156f 100644
--- a/chain/src/txhashset.rs
+++ b/chain/src/txhashset.rs
@@ -28,7 +28,9 @@ use util::secp::pedersen::{Commitment, RangeProof};
use core::core::committed::Committed;
use core::core::hash::{Hash, Hashed};
-use core::core::pmmr::{self, MerkleProof, PMMR};
+use core::core::merkle_proof::MerkleProof;
+use core::core::pmmr;
+use core::core::pmmr::PMMR;
use core::core::{Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, Transaction,
TxKernel};
use core::global;
@@ -539,17 +541,36 @@ impl<'a> Extension<'a> {
inputs: &Vec,
height: u64,
) -> Result<(), Error> {
- for x in inputs {
- if x.features.contains(OutputFeatures::COINBASE_OUTPUT) {
- let header = self.commit_index.get_block_header(&x.block_hash())?;
- let pos = self.get_output_pos(&x.commitment())?;
- let out_hash = self.output_pmmr.get_hash(pos).ok_or(Error::OutputNotFound)?;
- x.verify_maturity(out_hash, &header, height)?;
+ for input in inputs {
+ if input.features.contains(OutputFeatures::COINBASE_OUTPUT) {
+ self.verify_maturity_via_merkle_proof(input, height)?;
}
}
Ok(())
}
+ fn verify_maturity_via_merkle_proof(&self, input: &Input, height: u64) -> Result<(), Error> {
+ let header = self.commit_index.get_block_header(&input.block_hash())?;
+
+ // Check that the height indicates it has matured sufficiently
+ // we will check the Merkle proof below to ensure we are being
+ // honest about the height
+ if header.height + global::coinbase_maturity() >= height {
+ return Err(Error::ImmatureCoinbase);
+ }
+
+ // We need the MMR pos to verify the Merkle proof
+ let pos = self.get_output_pos(&input.commitment())?;
+
+ let out_id = OutputIdentifier::from_input(input);
+ let res = input
+ .merkle_proof()
+ .verify(header.output_root, &out_id, pos)
+ .map_err(|_| Error::MerkleProof)?;
+
+ Ok(res)
+ }
+
/// Apply a new set of blocks on top the existing sum trees. Blocks are
/// applied in order of the provided Vec. If pruning is enabled, inputs also
/// prune MMR data.
diff --git a/chain/src/types.rs b/chain/src/types.rs
index bb45566c0..66945f118 100644
--- a/chain/src/types.rs
+++ b/chain/src/types.rs
@@ -85,6 +85,10 @@ pub enum Error {
AlreadySpent(Commitment),
/// An output with that commitment already exists (should be unique)
DuplicateCommitment(Commitment),
+ /// Attempt to spend a coinbase output before it sufficiently matures.
+ ImmatureCoinbase,
+ /// Error validating a Merkle proof (coinbase output)
+ MerkleProof,
/// output not found
OutputNotFound,
/// output spent
diff --git a/chain/tests/test_coinbase_maturity.rs b/chain/tests/test_coinbase_maturity.rs
index 11880809e..4f924b006 100644
--- a/chain/tests/test_coinbase_maturity.rs
+++ b/chain/tests/test_coinbase_maturity.rs
@@ -123,7 +123,7 @@ fn test_coinbase_maturity() {
// Confirm the tx attempting to spend the coinbase output
// is not valid at the current block height given the current chain state.
match chain.verify_coinbase_maturity(&coinbase_txn) {
- Err(Error::Transaction(transaction::Error::ImmatureCoinbase)) => {}
+ Err(Error::ImmatureCoinbase) => {}
_ => panic!("Expected transaction error with immature coinbase."),
}
diff --git a/chain/tests/test_txhashset_raw_txs.rs b/chain/tests/test_txhashset_raw_txs.rs
index c7fb913b4..f961a3933 100644
--- a/chain/tests/test_txhashset_raw_txs.rs
+++ b/chain/tests/test_txhashset_raw_txs.rs
@@ -24,7 +24,7 @@ use chain::store::ChainKVStore;
use chain::txhashset::{self, TxHashSet};
use chain::types::Tip;
use chain::ChainStore;
-use core::core::pmmr::MerkleProof;
+use core::core::merkle_proof::MerkleProof;
use core::core::target::Difficulty;
use core::core::{Block, BlockHeader};
use keychain::{ExtKeychain, Keychain};
diff --git a/config/src/types.rs b/config/src/types.rs
index e337fcf1c..bde5afb06 100644
--- a/config/src/types.rs
+++ b/config/src/types.rs
@@ -14,9 +14,9 @@
//! Public types for config modules
-use std::path::PathBuf;
-use std::io;
use std::fmt;
+use std::io;
+use std::path::PathBuf;
use servers::{ServerConfig, StratumServerConfig};
use util::LoggingConfig;
diff --git a/core/src/core/block.rs b/core/src/core/block.rs
index ff1eaa31b..0b0fc5b50 100644
--- a/core/src/core/block.rs
+++ b/core/src/core/block.rs
@@ -52,15 +52,6 @@ pub enum Error {
Keychain(keychain::Error),
/// Underlying consensus error (sort order currently)
Consensus(consensus::Error),
- /// Coinbase has not yet matured and cannot be spent (1,000 blocks)
- ImmatureCoinbase {
- /// The height of the block containing the input spending the coinbase
- /// output
- height: u64,
- /// The lock_height needed to be reached for the coinbase output to
- /// mature
- lock_height: u64,
- },
/// Underlying Merkle proof error
MerkleProof,
/// Error when verifying kernel sums via committed trait.
@@ -682,7 +673,6 @@ impl Block {
self.verify_sorted()?;
self.verify_cut_through()?;
self.verify_coinbase()?;
- self.verify_inputs()?;
self.verify_kernel_lock_heights()?;
let sums = self.verify_kernel_sums(
@@ -725,27 +715,6 @@ 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 output_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(())
- }
-
fn verify_kernel_lock_heights(&self) -> Result<(), Error> {
for k in &self.kernels {
// check we have no kernels with lock_heights greater than current height
diff --git a/core/src/core/merkle_proof.rs b/core/src/core/merkle_proof.rs
new file mode 100644
index 000000000..7e1a18aef
--- /dev/null
+++ b/core/src/core/merkle_proof.rs
@@ -0,0 +1,156 @@
+// 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.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Merkle Proofs
+
+use core::hash::Hash;
+use core::pmmr;
+use ser;
+use ser::{PMMRIndexHashable, Readable, Reader, Writeable, Writer};
+use util;
+
+/// Merkle proof errors.
+#[derive(Clone, Debug, PartialEq)]
+pub enum MerkleProofError {
+ /// Merkle proof root hash does not match when attempting to verify.
+ RootMismatch,
+}
+
+/// A Merkle proof that proves a particular element exists in the MMR.
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, PartialOrd, Ord)]
+pub struct MerkleProof {
+ /// The size of the MMR at the time the proof was created.
+ pub mmr_size: u64,
+ /// The sibling path from the leaf up to the final sibling hashing to the
+ /// root.
+ pub path: Vec,
+}
+
+impl Writeable for MerkleProof {
+ fn write(&self, writer: &mut W) -> Result<(), ser::Error> {
+ writer.write_u64(self.mmr_size)?;
+ writer.write_u64(self.path.len() as u64)?;
+ self.path.write(writer)?;
+ Ok(())
+ }
+}
+
+impl Readable for MerkleProof {
+ fn read(reader: &mut Reader) -> Result {
+ let mmr_size = reader.read_u64()?;
+ let path_len = reader.read_u64()?;
+ let mut path = Vec::with_capacity(path_len as usize);
+ for _ in 0..path_len {
+ let hash = Hash::read(reader)?;
+ path.push(hash);
+ }
+
+ Ok(MerkleProof { mmr_size, path })
+ }
+}
+
+impl Default for MerkleProof {
+ fn default() -> MerkleProof {
+ MerkleProof::empty()
+ }
+}
+
+impl MerkleProof {
+ /// The "empty" Merkle proof.
+ pub fn empty() -> MerkleProof {
+ MerkleProof {
+ mmr_size: 0,
+ path: Vec::default(),
+ }
+ }
+
+ /// 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)
+ }
+
+ /// Verifies the Merkle proof against the provided
+ /// root hash, element and position in the MMR.
+ pub fn verify(
+ &self,
+ root: Hash,
+ element: &PMMRIndexHashable,
+ node_pos: u64,
+ ) -> Result<(), MerkleProofError> {
+ let mut proof = self.clone();
+ // calculate the peaks once as these are based on overall MMR size
+ // (and will not change)
+ let peaks_pos = pmmr::peaks(self.mmr_size);
+ proof.verify_consume(root, element, node_pos, peaks_pos)
+ }
+
+ /// Consumes the Merkle proof while verifying it.
+ /// The proof can no longer beused by the caller after dong this.
+ /// Caller must clone() the proof first.
+ fn verify_consume(
+ &mut self,
+ root: Hash,
+ element: &PMMRIndexHashable,
+ node_pos: u64,
+ peaks_pos: Vec,
+ ) -> Result<(), MerkleProofError> {
+ let node_hash = if node_pos > self.mmr_size {
+ element.hash_with_index(self.mmr_size)
+ } else {
+ element.hash_with_index(node_pos - 1)
+ };
+
+ // handle special case of only a single entry in the MMR
+ // (no siblings to hash together)
+ if self.path.len() == 0 {
+ if root == node_hash {
+ return Ok(());
+ } else {
+ return Err(MerkleProofError::RootMismatch);
+ }
+ }
+
+ let sibling = self.path.remove(0);
+ let (parent_pos, sibling_pos) = pmmr::family(node_pos);
+
+ if let Ok(x) = peaks_pos.binary_search(&node_pos) {
+ let parent = if x == peaks_pos.len() - 1 {
+ (sibling, node_hash)
+ } else {
+ (node_hash, sibling)
+ };
+ self.verify(root, &parent, parent_pos)
+ } else if parent_pos > self.mmr_size {
+ let parent = (sibling, node_hash);
+ self.verify(root, &parent, parent_pos)
+ } else {
+ let parent = if pmmr::is_left_sibling(sibling_pos) {
+ (sibling, node_hash)
+ } else {
+ (node_hash, sibling)
+ };
+ self.verify(root, &parent, parent_pos)
+ }
+ }
+}
diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs
index f3047493a..9b0f76235 100644
--- a/core/src/core/mod.rs
+++ b/core/src/core/mod.rs
@@ -18,6 +18,7 @@ pub mod block;
pub mod committed;
pub mod hash;
pub mod id;
+pub mod merkle_proof;
pub mod pmmr;
pub mod prune_list;
pub mod target;
diff --git a/core/src/core/pmmr.rs b/core/src/core/pmmr.rs
index e41ea470c..79748ebae 100644
--- a/core/src/core/pmmr.rs
+++ b/core/src/core/pmmr.rs
@@ -35,15 +35,15 @@
//! The underlying Hashes are stored in a Backend implementation that can
//! either be a simple Vec or a database.
+use std::marker;
+
use croaring::Bitmap;
use core::hash::Hash;
+use core::merkle_proof::MerkleProof;
use core::BlockHeader;
-use ser::{self, PMMRIndexHashable, PMMRable, Readable, Reader, Writeable, Writer};
-
-use std::clone::Clone;
-use std::marker;
-use util::{self, LOGGER};
+use ser::{PMMRIndexHashable, PMMRable};
+use util::LOGGER;
/// Storage backend for the MMR, just needs to be indexed by order of insertion.
/// The PMMR itself does not need the Backend to be accurate on the existence
@@ -106,168 +106,6 @@ where
fn dump_stats(&self);
}
-/// Maximum peaks for a Merkle proof
-pub const MAX_PEAKS: u64 = 100;
-
-/// Maximum path for a Merkle proof
-pub const MAX_PATH: u64 = 100;
-
-/// 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 output_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 size of the full Merkle tree
- pub mmr_size: u64,
- /// The full list of peak hashes in the MMR
- pub peaks: Vec,
- /// The sibling (hash, pos) along the path of the tree
- /// as we traverse from node to peak
- pub path: Vec<(Hash, u64)>,
-}
-
-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.mmr_size],
- [write_u64, self.peaks.len() as u64],
- [write_u64, self.path.len() as u64]
- );
-
- self.peaks.write(writer)?;
- self.path.write(writer)?;
-
- Ok(())
- }
-}
-
-impl Readable for MerkleProof {
- fn read(reader: &mut Reader) -> Result {
- let root = Hash::read(reader)?;
- let node = Hash::read(reader)?;
-
- let (mmr_size, peaks_len, path_len) = ser_multiread!(reader, read_u64, read_u64, read_u64);
-
- if peaks_len > MAX_PEAKS || path_len > MAX_PATH {
- return Err(ser::Error::CorruptedData);
- }
-
- 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 {
- let hash = Hash::read(reader)?;
- let pos = reader.read_u64()?;
- path.push((hash, pos));
- }
-
- Ok(MerkleProof {
- root,
- node,
- mmr_size,
- peaks,
- path,
- })
- }
-}
-
-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::default(),
- node: Hash::default(),
- mmr_size: 0,
- peaks: vec![],
- path: 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 representation 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(|_| "failed to deserialize a Merkle Proof".to_string())?;
- Ok(res)
- }
-
- /// Verify the Merkle proof.
- /// We do this by verifying the following -
- /// * 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().rev() {
- bagged = match bagged {
- None => Some(*peak),
- Some(rhs) => Some((*peak, rhs).hash_with_index(self.mmr_size)),
- }
- }
- return bagged == Some(self.root);
- }
-
- let mut path = self.path.clone();
- let (sibling, sibling_pos) = path.remove(0);
- let (parent_pos, _) = family(sibling_pos);
-
- // hash our node and sibling together (noting left/right position of the
- // sibling)
- let parent = if is_left_sibling(sibling_pos) {
- (sibling, self.node).hash_with_index(parent_pos - 1)
- } else {
- (self.node, sibling).hash_with_index(parent_pos - 1)
- };
-
- let proof = MerkleProof {
- root: self.root,
- node: parent,
- mmr_size: self.mmr_size,
- peaks: self.peaks.clone(),
- path,
- };
-
- 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.
@@ -324,6 +162,41 @@ where
.collect()
}
+ fn peak_path(&self, peak_pos: u64) -> Vec {
+ let rhs = self.bag_the_rhs(peak_pos);
+ let mut res = peaks(self.last_pos)
+ .into_iter()
+ .filter(|x| x < &peak_pos)
+ .filter_map(|x| self.backend.get_from_file(x))
+ .collect::>();
+ res.reverse();
+ if let Some(rhs) = rhs {
+ res.insert(0, rhs);
+ }
+ res
+ }
+
+ /// Takes a single peak position and hashes together
+ /// all the peaks to the right of this peak (if any).
+ /// If this return a hash then this is our peaks sibling.
+ /// If none then the sibling of our peak is the peak to the left.
+ pub fn bag_the_rhs(&self, peak_pos: u64) -> Option {
+ let rhs = peaks(self.last_pos)
+ .into_iter()
+ .filter(|x| x > &peak_pos)
+ .filter_map(|x| self.backend.get_from_file(x))
+ .collect::>();
+
+ let mut res = None;
+ for peak in rhs.iter().rev() {
+ res = match res {
+ None => Some(*peak),
+ Some(rhash) => Some((*peak, rhash).hash_with_index(self.unpruned_size())),
+ }
+ }
+ res
+ }
+
/// Computes the root of the MMR. Find all the peaks in the current
/// tree and "bags" them to get a single peak.
pub fn root(&self) -> Hash {
@@ -337,45 +210,46 @@ where
res.expect("no root, invalid tree")
}
- /// Build a Merkle proof for the element at the given position in the MMR
+ /// Build a Merkle proof for the element at the given position.
pub fn merkle_proof(&self, pos: u64) -> Result {
- debug!(
- LOGGER,
- "merkle_proof (via rewind) - {}, last_pos {}", pos, self.last_pos
- );
+ debug!(LOGGER, "merkle_proof {}, last_pos {}", pos, self.last_pos);
+ // check this pos is actually a leaf in the MMR
if !is_leaf(pos) {
return Err(format!("not a leaf at pos {}", pos));
}
- let root = self.root();
-
- let node = self.get_hash(pos)
+ // check we actually have a hash in the MMR at this pos
+ self.get_hash(pos)
.ok_or(format!("no element at pos {}", pos))?;
let mmr_size = self.unpruned_size();
+ // Edge case: an MMR with a single entry in it
+ // this entry is a leaf, a peak and the root itself
+ // and there are no siblings to hash with
+ if mmr_size == 1 {
+ return Ok(MerkleProof {
+ mmr_size,
+ path: vec![],
+ });
+ }
+
let family_branch = family_branch(pos, self.last_pos);
- let path = family_branch
+ let mut path = family_branch
.iter()
- .map(|x| (self.get_from_file(x.1).unwrap_or_default(), x.1))
+ .filter_map(|x| self.get_from_file(x.1))
.collect::>();
- let peaks = peaks(self.last_pos)
- .iter()
- .filter_map(|&x| self.get_from_file(x))
- .collect::>();
-
- let proof = MerkleProof {
- root,
- node,
- mmr_size,
- peaks,
- path,
+ let peak_pos = match family_branch.last() {
+ Some(&(x, _)) => x,
+ None => pos,
};
- Ok(proof)
+ path.append(&mut self.peak_path(peak_pos));
+
+ Ok(MerkleProof { mmr_size, path })
}
/// Push a new element into the MMR. Computes new related peaks at
diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs
index 8c4ab34d5..3e50d6788 100644
--- a/core/src/core/transaction.rs
+++ b/core/src/core/transaction.rs
@@ -26,8 +26,8 @@ use util::{kernel_sig_msg, static_secp_instance};
use consensus::{self, VerifySortOrder};
use core::hash::{Hash, Hashed, ZERO_HASH};
-use core::pmmr::MerkleProof;
-use core::{committed, global, BlockHeader, Committed};
+use core::merkle_proof::MerkleProof;
+use core::{committed, Committed};
use keychain::{self, BlindingFactor};
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,
WriteableSorted, Writer};
@@ -65,9 +65,6 @@ pub enum Error {
RangeProof,
/// Error originating from an invalid Merkle proof
MerkleProof,
- /// Error originating from an input attempting to spend an immature
- /// coinbase output
- ImmatureCoinbase,
/// Returns if the value hidden within the a RangeProof message isn't
/// repeated 3 times, indicating it's incorrect
InvalidProofMessage,
@@ -743,64 +740,6 @@ impl Input {
let merkle_proof = self.merkle_proof.clone();
merkle_proof.unwrap_or_else(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 output_root was built. The root associates the proof with a
- /// specific block header with that output_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 output_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.output_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);
- }
- }
- Ok(())
- }
}
bitflags! {
diff --git a/core/tests/common/mod.rs b/core/tests/common/mod.rs
index c6f328399..495a0425c 100644
--- a/core/tests/common/mod.rs
+++ b/core/tests/common/mod.rs
@@ -19,9 +19,9 @@ extern crate grin_keychain as keychain;
extern crate grin_util as util;
extern crate grin_wallet as wallet;
-use grin_core::core::Transaction;
use grin_core::core::block::{Block, BlockHeader};
use grin_core::core::target::Difficulty;
+use grin_core::core::Transaction;
use keychain::{Identifier, Keychain};
use wallet::libtx::build::{self, input, output, with_fee};
use wallet::libtx::reward;
diff --git a/core/tests/merkle_proof.rs b/core/tests/merkle_proof.rs
new file mode 100644
index 000000000..0c9810d70
--- /dev/null
+++ b/core/tests/merkle_proof.rs
@@ -0,0 +1,193 @@
+// 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.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[macro_use]
+extern crate grin_core as core;
+extern crate croaring;
+
+mod vec_backend;
+
+use core::core::merkle_proof::MerkleProof;
+use core::core::pmmr::PMMR;
+use core::ser;
+use core::ser::PMMRIndexHashable;
+use vec_backend::{TestElem, VecBackend};
+
+#[test]
+fn empty_merkle_proof() {
+ let proof = MerkleProof::empty();
+ assert_eq!(proof.path, vec![]);
+ assert_eq!(proof.mmr_size, 0);
+}
+
+#[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();
+
+ 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]
+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).unwrap();
+ let proof_2 = pmmr.merkle_proof(2).unwrap();
+ assert_eq!(proof, proof_2);
+}
+
+#[test]
+fn pmmr_merkle_proof() {
+ let elems = [
+ TestElem([0, 0, 0, 1]),
+ TestElem([0, 0, 0, 2]),
+ TestElem([0, 0, 0, 3]),
+ TestElem([0, 0, 0, 4]),
+ TestElem([0, 0, 0, 5]),
+ TestElem([0, 0, 0, 6]),
+ TestElem([0, 0, 0, 7]),
+ TestElem([0, 0, 0, 8]),
+ TestElem([1, 0, 0, 0]),
+ ];
+
+ let mut ba = VecBackend::new();
+ let mut pmmr = PMMR::new(&mut ba);
+
+ pmmr.push(elems[0]).unwrap();
+ let pos_0 = elems[0].hash_with_index(0);
+ assert_eq!(pmmr.get_hash(1).unwrap(), pos_0);
+
+ let proof = pmmr.merkle_proof(1).unwrap();
+ assert_eq!(proof.path, vec![]);
+ assert!(proof.verify(pmmr.root(), &elems[0], 1).is_ok());
+
+ pmmr.push(elems[1]).unwrap();
+ let pos_1 = elems[1].hash_with_index(1);
+ assert_eq!(pmmr.get_hash(2).unwrap(), pos_1);
+ let pos_2 = (pos_0, pos_1).hash_with_index(2);
+ assert_eq!(pmmr.get_hash(3).unwrap(), pos_2);
+
+ assert_eq!(pmmr.root(), pos_2);
+ assert_eq!(pmmr.peaks(), [pos_2]);
+
+ // single peak, path with single sibling
+ let proof = pmmr.merkle_proof(1).unwrap();
+ assert_eq!(proof.path, vec![pos_1]);
+ assert!(proof.verify(pmmr.root(), &elems[0], 1).is_ok());
+
+ let proof = pmmr.merkle_proof(2).unwrap();
+ assert_eq!(proof.path, vec![pos_0]);
+ assert!(proof.verify(pmmr.root(), &elems[1], 2).is_ok());
+
+ // three leaves, two peaks (one also the right-most leaf)
+ pmmr.push(elems[2]).unwrap();
+ let pos_3 = elems[2].hash_with_index(3);
+ assert_eq!(pmmr.get_hash(4).unwrap(), pos_3);
+
+ assert_eq!(pmmr.root(), (pos_2, pos_3).hash_with_index(4));
+ assert_eq!(pmmr.peaks(), [pos_2, pos_3]);
+
+ let proof = pmmr.merkle_proof(1).unwrap();
+ assert_eq!(proof.path, vec![pos_1, pos_3]);
+ assert!(proof.verify(pmmr.root(), &elems[0], 1).is_ok());
+
+ let proof = pmmr.merkle_proof(2).unwrap();
+ assert_eq!(proof.path, vec![pos_0, pos_3]);
+ assert!(proof.verify(pmmr.root(), &elems[1], 2).is_ok());
+
+ let proof = pmmr.merkle_proof(4).unwrap();
+ assert_eq!(proof.path, vec![pos_2]);
+ assert!(proof.verify(pmmr.root(), &elems[2], 4).is_ok());
+
+ // 7 leaves, 3 peaks, 11 pos in total
+ pmmr.push(elems[3]).unwrap();
+ let pos_4 = elems[3].hash_with_index(4);
+ assert_eq!(pmmr.get_hash(5).unwrap(), pos_4);
+ let pos_5 = (pos_3, pos_4).hash_with_index(5);
+ assert_eq!(pmmr.get_hash(6).unwrap(), pos_5);
+ let pos_6 = (pos_2, pos_5).hash_with_index(6);
+ assert_eq!(pmmr.get_hash(7).unwrap(), pos_6);
+
+ pmmr.push(elems[4]).unwrap();
+ let pos_7 = elems[4].hash_with_index(7);
+ assert_eq!(pmmr.get_hash(8).unwrap(), pos_7);
+
+ pmmr.push(elems[5]).unwrap();
+ let pos_8 = elems[5].hash_with_index(8);
+ assert_eq!(pmmr.get_hash(9).unwrap(), pos_8);
+
+ let pos_9 = (pos_7, pos_8).hash_with_index(9);
+ assert_eq!(pmmr.get_hash(10).unwrap(), pos_9);
+
+ pmmr.push(elems[6]).unwrap();
+ let pos_10 = elems[6].hash_with_index(10);
+ assert_eq!(pmmr.get_hash(11).unwrap(), pos_10);
+
+ assert_eq!(pmmr.unpruned_size(), 11);
+
+ let proof = pmmr.merkle_proof(1).unwrap();
+ assert_eq!(
+ proof.path,
+ vec![pos_1, pos_5, (pos_9, pos_10).hash_with_index(11)]
+ );
+ assert!(proof.verify(pmmr.root(), &elems[0], 1).is_ok());
+
+ let proof = pmmr.merkle_proof(2).unwrap();
+ assert_eq!(
+ proof.path,
+ vec![pos_0, pos_5, (pos_9, pos_10).hash_with_index(11)]
+ );
+ assert!(proof.verify(pmmr.root(), &elems[1], 2).is_ok());
+
+ let proof = pmmr.merkle_proof(4).unwrap();
+ assert_eq!(
+ proof.path,
+ vec![pos_4, pos_2, (pos_9, pos_10).hash_with_index(11)]
+ );
+ assert!(proof.verify(pmmr.root(), &elems[2], 4).is_ok());
+
+ let proof = pmmr.merkle_proof(5).unwrap();
+ assert_eq!(
+ proof.path,
+ vec![pos_3, pos_2, (pos_9, pos_10).hash_with_index(11)]
+ );
+ assert!(proof.verify(pmmr.root(), &elems[3], 5).is_ok());
+
+ let proof = pmmr.merkle_proof(8).unwrap();
+ assert_eq!(proof.path, vec![pos_8, pos_10, pos_6]);
+ assert!(proof.verify(pmmr.root(), &elems[4], 8).is_ok());
+
+ let proof = pmmr.merkle_proof(9).unwrap();
+ assert_eq!(proof.path, vec![pos_7, pos_10, pos_6]);
+ assert!(proof.verify(pmmr.root(), &elems[5], 9).is_ok());
+
+ let proof = pmmr.merkle_proof(11).unwrap();
+ assert_eq!(proof.path, vec![pos_9, pos_6]);
+ assert!(proof.verify(pmmr.root(), &elems[6], 11).is_ok());
+}
diff --git a/core/tests/pmmr.rs b/core/tests/pmmr.rs
index 0af563462..7538cd6e4 100644
--- a/core/tests/pmmr.rs
+++ b/core/tests/pmmr.rs
@@ -17,125 +17,13 @@
extern crate grin_core as core;
extern crate croaring;
-use croaring::Bitmap;
+mod vec_backend;
use core::core::hash::Hash;
-use core::core::pmmr::{self, Backend, MerkleProof, PMMR};
+use core::core::pmmr::{self, PMMR};
use core::core::prune_list::PruneList;
-use core::core::BlockHeader;
-use core::ser::{self, Error, PMMRIndexHashable, PMMRable, Readable, Reader, Writeable, Writer};
-
-/// Simple MMR backend implementation based on a Vector. Pruning does not
-/// compact the Vec itself.
-#[derive(Clone, Debug)]
-pub struct VecBackend
-where
- T: PMMRable,
-{
- /// Backend elements
- pub elems: Vec