Skip to content

Commit

Permalink
use lambda instead of a hash map to find spent outputs (rust-bitcoin#319
Browse files Browse the repository at this point in the history
)

* use lambda instead of a hash map to find spent outputs
* check for double use of an input
  • Loading branch information
tamasblummer authored Aug 23, 2019
1 parent 8ff904c commit f01568c
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 21 deletions.
13 changes: 4 additions & 9 deletions src/blockdata/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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()",
}
Expand Down
45 changes: 33 additions & 12 deletions src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<sha256d::Hash, Transaction>) -> 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<S>(&self, mut spent: S) -> Result<(), script::Error>
where S: FnMut(&OutPoint) -> Option<TxOut> {
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(())
Expand Down Expand Up @@ -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(|point: &OutPoint| {
if let Some(tx) = spent.remove(&point.txid) {
return tx.output.get(point.vout as usize).cloned();
}
None
}).unwrap();

spending.verify(&spent).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"),
}
Expand Down

0 comments on commit f01568c

Please sign in to comment.