From f01568c85a4fb1cb508fb2dae6ccb4fce8f982b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Blummer?= Date: Fri, 23 Aug 2019 18:49:31 +0200 Subject: [PATCH] use lambda instead of a hash map to find spent outputs (#319) * use lambda instead of a hash map to find spent outputs * check for double use of an input --- src/blockdata/script.rs | 13 ++++------- src/blockdata/transaction.rs | 45 ++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index ab688071..11a79e0a 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -34,7 +34,7 @@ use consensus::{encode, Decodable, Encodable}; use hashes::{hash160, sha256, Hash}; #[cfg(feature="bitcoinconsensus")] use bitcoinconsensus; #[cfg(feature="bitcoinconsensus")] use std::convert; -#[cfg(feature="bitcoinconsensus")] use hashes::sha256d; +#[cfg(feature="bitcoinconsensus")] use OutPoint; use util::key::PublicKey; @@ -95,11 +95,8 @@ pub enum Error { /// Error validating the script with bitcoinconsensus library BitcoinConsensus(bitcoinconsensus::Error), #[cfg(feature="bitcoinconsensus")] - /// Can not find the spent transaction - UnknownSpentTransaction(sha256d::Hash), - #[cfg(feature="bitcoinconsensus")] - /// The spent transaction does not have the referred output - WrongSpentOutputIndex(usize), + /// Can not find the spent output + UnknownSpentOutput(OutPoint), #[cfg(feature="bitcoinconsensus")] /// Can not serialize the spending transaction SerializationError @@ -122,9 +119,7 @@ impl error::Error for Error { #[cfg(feature="bitcoinconsensus")] Error::BitcoinConsensus(ref _n) => "bitcoinconsensus verification failed", #[cfg(feature="bitcoinconsensus")] - Error::UnknownSpentTransaction (ref _hash) => "unknown transaction referred in Transaction::verify()", - #[cfg(feature="bitcoinconsensus")] - Error::WrongSpentOutputIndex(ref _ix) => "unknown output index {} referred in Transaction::verify()", + Error::UnknownSpentOutput(ref _point) => "unknown spent output Transaction::verify()", #[cfg(feature="bitcoinconsensus")] Error::SerializationError => "can not serialize the spending transaction in Transaction::verify()", } diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 19a2124c..f6e759f5 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -26,7 +26,6 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::default::Default; use std::{fmt, io}; -#[cfg(feature="bitcoinconsensus")] use std::collections::HashMap; use hashes::{self, sha256d, Hash}; use hashes::hex::FromHex; @@ -406,18 +405,16 @@ impl Transaction { } #[cfg(feature="bitcoinconsensus")] - /// Verify that this transaction is able to spend some outputs of spent transactions - pub fn verify(&self, spent: &HashMap) -> Result<(), script::Error> { + /// Verify that this transaction is able to spend its inputs + /// The lambda spent should not return the same TxOut twice! + pub fn verify(&self, mut spent: S) -> Result<(), script::Error> + where S: FnMut(&OutPoint) -> Option { let tx = serialize(&*self); for (idx, input) in self.input.iter().enumerate() { - if let Some(ref s) = spent.get(&input.previous_output.txid) { - if let Some(ref output) = s.output.get(input.previous_output.vout as usize) { - output.script_pubkey.verify(idx, output.value, tx.as_slice())?; - } else { - return Err(script::Error::WrongSpentOutputIndex(input.previous_output.vout as usize)); - } + if let Some(output) = spent(&input.previous_output) { + output.script_pubkey.verify(idx, output.value, tx.as_slice())?; } else { - return Err(script::Error::UnknownSpentTransaction(input.previous_output.txid)); + return Err(script::Error::UnknownSpentOutput(input.previous_output.clone())); } } Ok(()) @@ -1130,12 +1127,36 @@ mod tests { spent.insert(spent1.txid(), spent1); spent.insert(spent2.txid(), spent2); spent.insert(spent3.txid(), spent3); + let mut spent2 = spent.clone(); + let mut spent3 = spent.clone(); - spending.verify(&spent).unwrap(); + spending.verify(|point: &OutPoint| { + if let Some(tx) = spent.remove(&point.txid) { + return tx.output.get(point.vout as usize).cloned(); + } + None + }).unwrap(); + + // test that we fail with repeated use of same input + let mut double_spending = spending.clone(); + let re_use = double_spending.input[0].clone(); + double_spending.input.push (re_use); + + assert!(double_spending.verify(|point: &OutPoint| { + if let Some(tx) = spent2.remove(&point.txid) { + return tx.output.get(point.vout as usize).cloned(); + } + None + }).is_err()); // test that we get a failure if we corrupt a signature spending.input[1].witness[0][10] = 42; - match spending.verify(&spent).err().unwrap() { + match spending.verify(|point: &OutPoint| { + if let Some(tx) = spent3.remove(&point.txid) { + return tx.output.get(point.vout as usize).cloned(); + } + None + }).err().unwrap() { script::Error::BitcoinConsensus(_) => {}, _ => panic!("Wrong error type"), }