diff --git a/core/src/core/block.rs b/core/src/core/block.rs index fea0ee1f7..b69c08656 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -70,6 +70,9 @@ pub enum Error { MerkleProof, /// Error when verifying kernel sums via committed trait. Committed(committed::Error), + /// Validation error relating to cut-through. + /// Specifically the tx is spending its own output, which is not valid. + CutThrough, /// Other unspecified error condition Other(String), } @@ -682,6 +685,7 @@ impl Block { ) -> Result<((Commitment, Commitment)), Error> { self.verify_weight()?; self.verify_sorted()?; + self.verify_cut_through()?; self.verify_coinbase()?; self.verify_inputs()?; self.verify_kernel_lock_heights()?; @@ -705,6 +709,7 @@ impl Block { Ok(()) } + // Verify that inputs|outputs|kernels are all sorted in lexicographical order. fn verify_sorted(&self) -> Result<(), Error> { self.inputs.verify_sort_order()?; self.outputs.verify_sort_order()?; @@ -712,6 +717,19 @@ impl Block { Ok(()) } + // Verify that no input is spending an ouput from the same block. + fn verify_cut_through(&self) -> Result<(), Error> { + for inp in &self.inputs { + if self.outputs + .iter() + .any(|out| out.commitment() == inp.commitment()) + { + return Err(Error::CutThrough); + } + } + 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 diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index dfe774cee..ce389338f 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -14,8 +14,8 @@ //! Transactions -use std::cmp::Ordering; use std::cmp::max; +use std::cmp::Ordering; use std::collections::HashSet; use std::io::Cursor; use std::{error, fmt}; @@ -26,12 +26,12 @@ use util::{kernel_sig_msg, static_secp_instance}; use consensus; use consensus::VerifySortOrder; -use core::BlockHeader; use core::committed; -use core::committed::Committed; use core::global; use core::hash::{Hash, Hashed, ZERO_HASH}; use core::pmmr::MerkleProof; +use core::BlockHeader; +use core::Committed; use keychain; use keychain::BlindingFactor; use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable, @@ -78,6 +78,12 @@ pub enum Error { InvalidProofMessage, /// Error when verifying kernel sums via committed trait. Committed(committed::Error), + /// Error when sums do not verify correctly during tx aggregation. + /// Likely a "double spend" across two unconfirmed txs. + AggregationError, + /// Validation error relating to cut-through (tx is spending its own + /// output). + CutThrough, } impl error::Error for Error { @@ -430,6 +436,7 @@ impl Transaction { return Err(Error::TooManyInputs); } self.verify_sorted()?; + self.verify_cut_through()?; self.verify_kernel_sums(self.overage(), self.offset, None, None)?; self.verify_rangeproofs()?; self.verify_kernel_signatures()?; @@ -464,12 +471,26 @@ impl Transaction { tx_weight as u32 } + // Verify that inputs|outputs|kernels are all sorted in lexicographical order. fn verify_sorted(&self) -> Result<(), Error> { self.inputs.verify_sort_order()?; self.outputs.verify_sort_order()?; self.kernels.verify_sort_order()?; Ok(()) } + + // Verify that no input is spending an ouput from the same block. + fn verify_cut_through(&self) -> Result<(), Error> { + for inp in &self.inputs { + if self.outputs + .iter() + .any(|out| out.commitment() == inp.commitment()) + { + return Err(Error::CutThrough); + } + } + Ok(()) + } } /// Aggregate a vec of transactions into a multi-kernel transaction with