bitcoin/consensus/
validation.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Transaction and script validation.
4//!
5//! Relies on the `bitcoinconsensus` crate that uses Bitcoin Core libconsensus to perform validation.
6
7use core::fmt;
8
9use internals::write_err;
10
11use crate::amount::Amount;
12use crate::blockdata::script::Script;
13use crate::blockdata::transaction::{OutPoint, Transaction, TxOut};
14#[cfg(doc)]
15use crate::consensus;
16use crate::consensus::encode;
17
18/// Verifies spend of an input script.
19///
20/// Shorthand for [`consensus::verify_script_with_flags`] with flag
21/// [`bitcoinconsensus::VERIFY_ALL`].
22///
23/// # Parameters
24///  * `index` - The input index in spending which is spending this transaction.
25///  * `amount` - The amount this script guards.
26///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
27///
28/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
29pub fn verify_script(
30    script: &Script,
31    index: usize,
32    amount: Amount,
33    spending_tx: &[u8],
34) -> Result<(), BitcoinconsensusError> {
35    verify_script_with_flags(script, index, amount, spending_tx, bitcoinconsensus::VERIFY_ALL)
36}
37
38/// Verifies spend of an input script.
39///
40/// # Parameters
41///  * `index` - The input index in spending which is spending this transaction.
42///  * `amount` - The amount this script guards.
43///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
44///  * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar.
45///
46/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
47pub fn verify_script_with_flags<F: Into<u32>>(
48    script: &Script,
49    index: usize,
50    amount: Amount,
51    spending_tx: &[u8],
52    flags: F,
53) -> Result<(), BitcoinconsensusError> {
54    bitcoinconsensus::verify_with_flags(
55        script.as_bytes(),
56        amount.to_sat(),
57        spending_tx,
58        index,
59        flags.into(),
60    )
61    .map_err(BitcoinconsensusError)
62}
63
64/// Verifies that this transaction is able to spend its inputs.
65///
66/// Shorthand for [`consensus::verify_transaction_with_flags`] with flag
67/// [`bitcoinconsensus::VERIFY_ALL`].
68///
69/// The `spent` closure should not return the same [`TxOut`] twice!
70///
71/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
72pub fn verify_transaction<S>(tx: &Transaction, spent: S) -> Result<(), TxVerifyError>
73where
74    S: FnMut(&OutPoint) -> Option<TxOut>,
75{
76    verify_transaction_with_flags(tx, spent, bitcoinconsensus::VERIFY_ALL)
77}
78
79/// Verifies that this transaction is able to spend its inputs.
80///
81/// The `spent` closure should not return the same [`TxOut`] twice!
82pub fn verify_transaction_with_flags<S, F>(
83    tx: &Transaction,
84    mut spent: S,
85    flags: F,
86) -> Result<(), TxVerifyError>
87where
88    S: FnMut(&OutPoint) -> Option<TxOut>,
89    F: Into<u32>,
90{
91    let serialized_tx = encode::serialize(tx);
92    let flags: u32 = flags.into();
93    for (idx, input) in tx.input.iter().enumerate() {
94        if let Some(output) = spent(&input.previous_output) {
95            verify_script_with_flags(
96                &output.script_pubkey,
97                idx,
98                output.value,
99                serialized_tx.as_slice(),
100                flags,
101            )?;
102        } else {
103            return Err(TxVerifyError::UnknownSpentOutput(input.previous_output));
104        }
105    }
106    Ok(())
107}
108
109impl Script {
110    /// Verifies spend of an input script.
111    ///
112    /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`].
113    ///
114    /// # Parameters
115    ///  * `index` - The input index in spending which is spending this transaction.
116    ///  * `amount` - The amount this script guards.
117    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
118    ///
119    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
120    pub fn verify(
121        &self,
122        index: usize,
123        amount: crate::Amount,
124        spending_tx: &[u8],
125    ) -> Result<(), BitcoinconsensusError> {
126        verify_script(self, index, amount, spending_tx)
127    }
128
129    /// Verifies spend of an input script.
130    ///
131    /// # Parameters
132    ///  * `index` - The input index in spending which is spending this transaction.
133    ///  * `amount` - The amount this script guards.
134    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
135    ///  * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar.
136    ///
137    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
138    pub fn verify_with_flags<F: Into<u32>>(
139        &self,
140        index: usize,
141        amount: crate::Amount,
142        spending_tx: &[u8],
143        flags: F,
144    ) -> Result<(), BitcoinconsensusError> {
145        verify_script_with_flags(self, index, amount, spending_tx, flags)
146    }
147}
148
149impl Transaction {
150    /// Verifies that this transaction is able to spend its inputs.
151    ///
152    /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`].
153    ///
154    /// The `spent` closure should not return the same [`TxOut`] twice!
155    ///
156    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
157    pub fn verify<S>(&self, spent: S) -> Result<(), TxVerifyError>
158    where
159        S: FnMut(&OutPoint) -> Option<TxOut>,
160    {
161        verify_transaction(self, spent)
162    }
163
164    /// Verifies that this transaction is able to spend its inputs.
165    ///
166    /// The `spent` closure should not return the same [`TxOut`] twice!
167    pub fn verify_with_flags<S, F>(&self, spent: S, flags: F) -> Result<(), TxVerifyError>
168    where
169        S: FnMut(&OutPoint) -> Option<TxOut>,
170        F: Into<u32>,
171    {
172        verify_transaction_with_flags(self, spent, flags)
173    }
174}
175
176/// Wrapped error from `bitcoinconsensus`.
177// We do this for two reasons:
178// 1. We don't want the error to be part of the public API because we do not want to expose the
179//    unusual versioning used in `bitcoinconsensus` to users of `rust-bitcoin`.
180// 2. We want to implement `std::error::Error` if the "std" feature is enabled in `rust-bitcoin` but
181//    not in `bitcoinconsensus`.
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[non_exhaustive]
184pub struct BitcoinconsensusError(bitcoinconsensus::Error);
185
186impl fmt::Display for BitcoinconsensusError {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        write_err!(f, "bitcoinconsensus error"; &self.0)
189    }
190}
191
192#[cfg(all(feature = "std", feature = "bitcoinconsensus-std"))]
193impl std::error::Error for BitcoinconsensusError {
194    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
195}
196
197#[cfg(all(feature = "std", not(feature = "bitcoinconsensus-std")))]
198impl std::error::Error for BitcoinconsensusError {
199    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
200}
201
202/// An error during transaction validation.
203#[derive(Debug, Clone, PartialEq, Eq)]
204#[non_exhaustive]
205pub enum TxVerifyError {
206    /// Error validating the script with bitcoinconsensus library.
207    ScriptVerification(BitcoinconsensusError),
208    /// Can not find the spent output.
209    UnknownSpentOutput(OutPoint),
210}
211
212internals::impl_from_infallible!(TxVerifyError);
213
214impl fmt::Display for TxVerifyError {
215    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216        use TxVerifyError::*;
217
218        match *self {
219            ScriptVerification(ref e) => write_err!(f, "bitcoinconsensus verification failed"; e),
220            UnknownSpentOutput(ref p) => write!(f, "unknown spent output: {}", p),
221        }
222    }
223}
224
225#[cfg(feature = "std")]
226impl std::error::Error for TxVerifyError {
227    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
228        use TxVerifyError::*;
229
230        match *self {
231            ScriptVerification(ref e) => Some(e),
232            UnknownSpentOutput(_) => None,
233        }
234    }
235}
236
237impl From<BitcoinconsensusError> for TxVerifyError {
238    fn from(e: BitcoinconsensusError) -> Self { TxVerifyError::ScriptVerification(e) }
239}