diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 0b5f47fe..c4d080da 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -41,6 +41,7 @@ secp256k1 = { version = "0.27.0", default-features = false, features = ["bitcoin hex_lit = "0.1.1" base64 = { version = "0.13.0", optional = true } +# Only use this feature for no-std builds, otherwise use bitcoinconsensus-std. bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true } core2 = { version = "0.3.2", default-features = false, features = ["alloc"], optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 547169d0..9084b2b9 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -12,8 +12,6 @@ use secp256k1::{Secp256k1, Verification}; use crate::blockdata::opcodes::all::*; use crate::blockdata::opcodes::{self}; use crate::blockdata::script::witness_version::WitnessVersion; -#[cfg(feature = "bitcoinconsensus")] -use crate::blockdata::script::Error; use crate::blockdata::script::{ bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, ScriptBuf, }; @@ -437,46 +435,6 @@ impl Script { InstructionIndices::from_instructions(self.instructions_minimal()) } - /// Shorthand for [`Self::verify_with_flags`] with flag [bitcoinconsensus::VERIFY_ALL]. - /// - /// # Parameters - /// * `index` - The input index in spending which is spending this transaction. - /// * `amount` - The amount this script guards. - /// * `spending_tx` - The transaction that attempts to spend the output holding this script. - #[cfg(feature = "bitcoinconsensus")] - pub fn verify( - &self, - index: usize, - amount: crate::Amount, - spending_tx: &[u8], - ) -> Result<(), Error> { - self.verify_with_flags(index, amount, spending_tx, bitcoinconsensus::VERIFY_ALL) - } - - /// Verifies spend of an input script. - /// - /// # Parameters - /// * `index` - The input index in spending which is spending this transaction. - /// * `amount` - The amount this script guards. - /// * `spending_tx` - The transaction that attempts to spend the output holding this script. - /// * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar. - #[cfg(feature = "bitcoinconsensus")] - pub fn verify_with_flags>( - &self, - index: usize, - amount: crate::Amount, - spending_tx: &[u8], - flags: F, - ) -> Result<(), Error> { - Ok(bitcoinconsensus::verify_with_flags( - &self.0[..], - amount.to_sat(), - spending_tx, - index, - flags.into(), - )?) - } - /// Writes the assembly decoding of the script to the formatter. pub fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result { bytes_to_asm_fmt(self.as_ref(), f) diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index 15eaee44..42f3065a 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -#[cfg(feature = "bitcoinconsensus")] -use core::convert::From; use core::default::Default; use core::fmt; diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index bc4c41a6..ecd68547 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -680,66 +680,19 @@ pub enum Error { EarlyEndOfScript, /// Tried to read an array off the stack as a number when it was more than 4 bytes. NumericOverflow, - /// Error validating the script with bitcoinconsensus library. - #[cfg(feature = "bitcoinconsensus")] - BitcoinConsensus(bitcoinconsensus::Error), /// Can not find the spent output. UnknownSpentOutput(OutPoint), /// Can not serialize the spending transaction. Serialization, } -// If bitcoinonsensus-std is off but bitcoinconsensus is present we patch the error type to -// implement `std::error::Error`. -#[cfg(all(feature = "std", feature = "bitcoinconsensus", not(feature = "bitcoinconsensus-std")))] -mod bitcoinconsensus_hack { - use core::fmt; - - #[repr(transparent)] - pub(crate) struct Error(bitcoinconsensus::Error); - - impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } - } - - impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } - } - - // bitcoinconsensus::Error has no sources at this time - impl std::error::Error for Error {} - - pub(crate) fn wrap_error(error: &bitcoinconsensus::Error) -> &Error { - // Unfortunately, we cannot have the reference inside `Error` struct because of the 'static - // bound on `source` return type, so we have to use unsafe to overcome the limitation. - // SAFETY: the type is repr(transparent) and the lifetimes match - unsafe { &*(error as *const _ as *const Error) } - } -} - -#[cfg(not(all( - feature = "std", - feature = "bitcoinconsensus", - not(feature = "bitcoinconsensus-std") -)))] -mod bitcoinconsensus_hack { - #[allow(unused_imports)] // conditionally used - pub(crate) use core::convert::identity as wrap_error; -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "bitcoinconsensus")] - use internals::write_err; - match *self { Error::NonMinimalPush => f.write_str("non-minimal datapush"), Error::EarlyEndOfScript => f.write_str("unexpected end of script"), Error::NumericOverflow => f.write_str("numeric overflow (number on stack larger than 4 bytes)"), - #[cfg(feature = "bitcoinconsensus")] - Error::BitcoinConsensus(ref e) => - write_err!(f, "bitcoinconsensus verification failed"; bitcoinconsensus_hack::wrap_error(e)), Error::UnknownSpentOutput(ref point) => write!(f, "unknown spent output: {}", point), Error::Serialization => f.write_str("can not serialize the spending transaction in Transaction::verify()"), @@ -758,8 +711,6 @@ impl std::error::Error for Error { | NumericOverflow | UnknownSpentOutput(_) | Serialization => None, - #[cfg(feature = "bitcoinconsensus")] - BitcoinConsensus(ref e) => Some(bitcoinconsensus_hack::wrap_error(e)), } } } @@ -779,8 +730,3 @@ impl From for Error { } } } - -#[cfg(feature = "bitcoinconsensus")] -impl From for Error { - fn from(err: bitcoinconsensus::Error) -> Error { Error::BitcoinConsensus(err) } -} diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 8418fbe6..6a1a2108 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -21,10 +21,10 @@ use internals::write_err; use super::Weight; use crate::blockdata::locktime::absolute::{self, Height, Time}; use crate::blockdata::locktime::relative; -#[cfg(feature = "bitcoinconsensus")] -use crate::blockdata::script; use crate::blockdata::script::{Script, ScriptBuf}; use crate::blockdata::witness::Witness; +#[cfg(feature = "bitcoinconsensus")] +pub use crate::consensus::validation::TxVerifyError; use crate::consensus::{encode, Decodable, Encodable}; use crate::crypto::sighash::LegacySighash; use crate::hash_types::{Txid, Wtxid}; @@ -929,36 +929,6 @@ impl Transaction { } } - /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`]. - #[cfg(feature = "bitcoinconsensus")] - pub fn verify(&self, spent: S) -> Result<(), script::Error> - where - S: FnMut(&OutPoint) -> Option, - { - self.verify_with_flags(spent, bitcoinconsensus::VERIFY_ALL) - } - - /// Verify that this transaction is able to spend its inputs. - /// - /// The `spent` closure should not return the same [`TxOut`] twice! - #[cfg(feature = "bitcoinconsensus")] - pub fn verify_with_flags(&self, mut spent: S, flags: F) -> Result<(), script::Error> - where - S: FnMut(&OutPoint) -> Option, - F: Into, - { - let tx = encode::serialize(self); - let flags: u32 = flags.into(); - for (idx, input) in self.input.iter().enumerate() { - if let Some(output) = spent(&input.previous_output) { - output.script_pubkey.verify_with_flags(idx, output.value, tx.as_slice(), flags)?; - } else { - return Err(script::Error::UnknownSpentOutput(input.previous_output)); - } - } - Ok(()) - } - /// Checks if this is a coinbase transaction. /// /// The first transaction in the block distributes the mining reward and is called the coinbase @@ -1838,7 +1808,6 @@ mod tests { fn test_transaction_verify() { use std::collections::HashMap; - use crate::blockdata::script; use crate::blockdata::witness::Witness; // a random recent segwit transaction from blockchain using both old and segwit inputs @@ -1895,7 +1864,7 @@ mod tests { .err() .unwrap() { - script::Error::BitcoinConsensus(_) => {} + TxVerifyError::ScriptVerification(_) => {} _ => panic!("Wrong error type"), } } diff --git a/bitcoin/src/consensus/mod.rs b/bitcoin/src/consensus/mod.rs index 77303148..454ac924 100644 --- a/bitcoin/src/consensus/mod.rs +++ b/bitcoin/src/consensus/mod.rs @@ -8,11 +8,17 @@ pub mod encode; pub mod params; +#[cfg(feature = "bitcoinconsensus")] +pub mod validation; pub use self::encode::{ deserialize, deserialize_partial, serialize, Decodable, Encodable, ReadExt, WriteExt, }; pub use self::params::Params; +#[cfg(feature = "bitcoinconsensus")] +pub use self::validation::{ + verify_script, verify_script_with_flags, verify_transaction, verify_transaction_with_flags, +}; #[cfg(feature = "serde")] pub mod serde; diff --git a/bitcoin/src/consensus/validation.rs b/bitcoin/src/consensus/validation.rs new file mode 100644 index 00000000..523bcacf --- /dev/null +++ b/bitcoin/src/consensus/validation.rs @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Transaction and script validation. +//! +//! Relies on the `bitcoinconsensus` crate that uses Bitcoin Core libconsensus to perform validation. + +use core::fmt; + +use internals::write_err; + +use crate::amount::Amount; +use crate::blockdata::script::Script; +use crate::blockdata::transaction::{OutPoint, Transaction, TxOut}; +#[cfg(doc)] +use crate::consensus; +use crate::consensus::encode; + +/// Verifies spend of an input script. +/// +/// Shorthand for [`consensus::verify_script_with_flags`] with flag +/// [`bitcoinconsensus::VERIFY_ALL`]. +/// +/// # Parameters +/// * `index` - The input index in spending which is spending this transaction. +/// * `amount` - The amount this script guards. +/// * `spending_tx` - The transaction that attempts to spend the output holding this script. +/// +/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html +pub fn verify_script( + script: &Script, + index: usize, + amount: Amount, + spending_tx: &[u8], +) -> Result<(), bitcoinconsensus::Error> { + verify_script_with_flags(script, index, amount, spending_tx, bitcoinconsensus::VERIFY_ALL) +} + +/// Verifies spend of an input script. +/// +/// # Parameters +/// * `index` - The input index in spending which is spending this transaction. +/// * `amount` - The amount this script guards. +/// * `spending_tx` - The transaction that attempts to spend the output holding this script. +/// * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar. +/// +/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html +pub fn verify_script_with_flags>( + script: &Script, + index: usize, + amount: Amount, + spending_tx: &[u8], + flags: F, +) -> Result<(), bitcoinconsensus::Error> { + bitcoinconsensus::verify_with_flags( + script.as_bytes(), + amount.to_sat(), + spending_tx, + index, + flags.into(), + ) +} + +/// Verifies that this transaction is able to spend its inputs. +/// +/// Shorthand for [`consensus::verify_transaction_with_flags`] with flag +/// [`bitcoinconsensus::VERIFY_ALL`]. +/// +/// The `spent` closure should not return the same [`TxOut`] twice! +/// +/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html +pub fn verify_transaction(tx: &Transaction, spent: S) -> Result<(), TxVerifyError> +where + S: FnMut(&OutPoint) -> Option, +{ + verify_transaction_with_flags(tx, spent, bitcoinconsensus::VERIFY_ALL) +} + +/// Verifies that this transaction is able to spend its inputs. +/// +/// The `spent` closure should not return the same [`TxOut`] twice! +pub fn verify_transaction_with_flags( + tx: &Transaction, + mut spent: S, + flags: F, +) -> Result<(), TxVerifyError> +where + S: FnMut(&OutPoint) -> Option, + F: Into, +{ + let serialized_tx = encode::serialize(tx); + let flags: u32 = flags.into(); + for (idx, input) in tx.input.iter().enumerate() { + if let Some(output) = spent(&input.previous_output) { + verify_script_with_flags( + &output.script_pubkey, + idx, + output.value, + serialized_tx.as_slice(), + flags, + )?; + } else { + return Err(TxVerifyError::UnknownSpentOutput(input.previous_output)); + } + } + Ok(()) +} + +impl Script { + /// Verifies spend of an input script. + /// + /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`]. + /// + /// # Parameters + /// * `index` - The input index in spending which is spending this transaction. + /// * `amount` - The amount this script guards. + /// * `spending_tx` - The transaction that attempts to spend the output holding this script. + /// + /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html + pub fn verify( + &self, + index: usize, + amount: crate::Amount, + spending_tx: &[u8], + ) -> Result<(), bitcoinconsensus::Error> { + verify_script(self, index, amount, spending_tx) + } + + /// Verifies spend of an input script. + /// + /// # Parameters + /// * `index` - The input index in spending which is spending this transaction. + /// * `amount` - The amount this script guards. + /// * `spending_tx` - The transaction that attempts to spend the output holding this script. + /// * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar. + /// + /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html + pub fn verify_with_flags>( + &self, + index: usize, + amount: crate::Amount, + spending_tx: &[u8], + flags: F, + ) -> Result<(), bitcoinconsensus::Error> { + verify_script_with_flags(self, index, amount, spending_tx, flags) + } +} + +impl Transaction { + /// Verifies that this transaction is able to spend its inputs. + /// + /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`]. + /// + /// The `spent` closure should not return the same [`TxOut`] twice! + /// + /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html + pub fn verify(&self, spent: S) -> Result<(), TxVerifyError> + where + S: FnMut(&OutPoint) -> Option, + { + verify_transaction(self, spent) + } + + /// Verifies that this transaction is able to spend its inputs. + /// + /// The `spent` closure should not return the same [`TxOut`] twice! + pub fn verify_with_flags(&self, spent: S, flags: F) -> Result<(), TxVerifyError> + where + S: FnMut(&OutPoint) -> Option, + F: Into, + { + verify_transaction_with_flags(self, spent, flags) + } +} + +/// An error during transaction validation. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TxVerifyError { + /// Error validating the script with bitcoinconsensus library. + ScriptVerification(bitcoinconsensus::Error), + /// Can not find the spent output. + UnknownSpentOutput(OutPoint), +} + +impl fmt::Display for TxVerifyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TxVerifyError::*; + + match *self { + ScriptVerification(ref e) => { + write_err!(f, "bitcoinconsensus verification failed"; bitcoinconsensus_hack::wrap_error(e)) + } + UnknownSpentOutput(ref p) => write!(f, "unknown spent output: {}", p), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TxVerifyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use TxVerifyError::*; + + match *self { + ScriptVerification(ref e) => Some(bitcoinconsensus_hack::wrap_error(e)), + UnknownSpentOutput(_) => None, + } + } +} + +impl From for TxVerifyError { + fn from(e: bitcoinconsensus::Error) -> Self { TxVerifyError::ScriptVerification(e) } +} + +// If bitcoinonsensus-std is off but bitcoinconsensus is present we patch the error type to +// implement `std::error::Error`. +#[cfg(all(feature = "std", feature = "bitcoinconsensus", not(feature = "bitcoinconsensus-std")))] +mod bitcoinconsensus_hack { + use core::fmt; + + #[repr(transparent)] + pub(crate) struct Error(bitcoinconsensus::Error); + + impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } + } + + impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } + } + + // bitcoinconsensus::Error has no sources at this time + impl std::error::Error for Error {} + + pub(crate) fn wrap_error(error: &bitcoinconsensus::Error) -> &Error { + // Unfortunately, we cannot have the reference inside `Error` struct because of the 'static + // bound on `source` return type, so we have to use unsafe to overcome the limitation. + // SAFETY: the type is repr(transparent) and the lifetimes match + unsafe { &*(error as *const _ as *const Error) } + } +} + +#[cfg(not(all( + feature = "std", + feature = "bitcoinconsensus", + not(feature = "bitcoinconsensus-std") +)))] +mod bitcoinconsensus_hack { + #[allow(unused_imports)] // conditionally used + pub(crate) use core::convert::identity as wrap_error; +}