1+ //! Script verification and validation.
2+ //!
3+ //! This module provides functionality for verifying that transaction inputs satisfy
4+ //! the spending conditions defined by their corresponding output scripts.
5+ //!
6+ //! # Overview
7+ //!
8+ //! Script verification involves checking that a transaction input's
9+ //! unlocking script (scriptSig) and witness data satisfy the conditions
10+ //! specified in the output's locking script (scriptPubkey). The verification
11+ //! process depends on the script type and the consensus rules active at the
12+ //! time.
13+ //!
14+ //! # Verification Flags
15+ //!
16+ //! Consensus rules have evolved over time through soft forks. Verification flags
17+ //! allow you to specify which consensus rules to enforce:
18+ //!
19+ //! | Flag | Description | BIP |
20+ //! |------|-------------|-----|
21+ //! | [`VERIFY_P2SH`] | Pay-to-Script-Hash validation | BIP 16 |
22+ //! | [`VERIFY_DERSIG`] | Strict DER signature encoding | BIP 66 |
23+ //! | [`VERIFY_NULLDUMMY`] | Dummy stack element must be empty | BIP 147 |
24+ //! | [`VERIFY_CHECKLOCKTIMEVERIFY`] | CHECKLOCKTIMEVERIFY opcode | BIP 65 |
25+ //! | [`VERIFY_CHECKSEQUENCEVERIFY`] | CHECKSEQUENCEVERIFY opcode | BIP 112 |
26+ //! | [`VERIFY_WITNESS`] | Segregated Witness validation | BIP 141/143 |
27+ //! | [`VERIFY_TAPROOT`] | Taproot validation | BIP 341/342 |
28+ //!
29+ //! # Common Flag Combinations
30+ //!
31+ //! - [`VERIFY_ALL_PRE_TAPROOT`]: All rules except Taproot (for pre-Taproot blocks)
32+ //! - [`VERIFY_ALL`]: All consensus rules including Taproot
33+ //!
34+ //! # Examples
35+ //!
36+ //! ## Basic verification with all consensus rules
37+ //!
38+ //! ```no_run
39+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL};
40+ //! # let spending_tx_bytes = vec![];
41+ //! # let prev_tx_bytes = vec![];
42+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
43+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
44+ //! let prev_output = prev_tx.output(0).unwrap();
45+ //!
46+ //! let result = verify(
47+ //! &prev_output.script_pubkey(),
48+ //! Some(prev_output.value()),
49+ //! &spending_tx,
50+ //! 0,
51+ //! Some(VERIFY_ALL),
52+ //! &[prev_output],
53+ //! );
54+ //!
55+ //! match result {
56+ //! Ok(()) => println!("Script verification passed"),
57+ //! Err(e) => println!("Script verification failed: {}", e),
58+ //! }
59+ //! ```
60+ //!
61+ //! ## Verifying pre-Taproot transactions
62+ //!
63+ //! ```no_run
64+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL_PRE_TAPROOT};
65+ //! # let spending_tx_bytes = vec![];
66+ //! # let prev_tx_bytes = vec![];
67+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
68+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
69+ //! # let prev_output = prev_tx.output(0).unwrap();
70+ //! let result = verify(
71+ //! &prev_output.script_pubkey(),
72+ //! Some(prev_output.value()),
73+ //! &spending_tx,
74+ //! 0,
75+ //! Some(VERIFY_ALL_PRE_TAPROOT),
76+ //! &[prev_output],
77+ //! );
78+ //! ```
79+ //!
80+ //! ## Verifying with multiple spent outputs
81+ //!
82+ //! ```no_run
83+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL};
84+ //! # let spending_tx_bytes = vec![];
85+ //! # let prev_tx1_bytes = vec![];
86+ //! # let prev_tx2_bytes = vec![];
87+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
88+ //! # let prev_tx1 = Transaction::new(&prev_tx1_bytes).unwrap();
89+ //! # let prev_tx2 = Transaction::new(&prev_tx2_bytes).unwrap();
90+ //! let spent_outputs = vec![
91+ //! prev_tx1.output(0).unwrap(),
92+ //! prev_tx2.output(1).unwrap(),
93+ //! ];
94+ //!
95+ //! let result = verify(
96+ //! &spent_outputs[0].script_pubkey(),
97+ //! Some(spent_outputs[0].value()),
98+ //! &spending_tx,
99+ //! 0,
100+ //! Some(VERIFY_ALL),
101+ //! &spent_outputs,
102+ //! );
103+ //! ```
104+ //!
105+ //! ## Handling verification errors
106+ //!
107+ //! ```no_run
108+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL, KernelError, ScriptVerifyError};
109+ //! # let spending_tx_bytes = vec![];
110+ //! # let prev_tx_bytes = vec![];
111+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
112+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
113+ //! # let prev_output = prev_tx.output(0).unwrap();
114+ //! let result = verify(
115+ //! &prev_output.script_pubkey(),
116+ //! Some(prev_output.value()),
117+ //! &spending_tx,
118+ //! 0,
119+ //! Some(VERIFY_ALL),
120+ //! &[prev_output],
121+ //! );
122+ //!
123+ //! match result {
124+ //! Ok(()) => {
125+ //! println!("Valid transaction");
126+ //! }
127+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsRequired)) => {
128+ //! println!("This script type requires spent outputs");
129+ //! }
130+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlagsCombination)) => {
131+ //! println!("Invalid combination of verification flags");
132+ //! }
133+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::Invalid)) => {
134+ //! println!("Script verification failed - invalid script");
135+ //! }
136+ //! Err(e) => {
137+ //! println!("Other error: {}", e);
138+ //! }
139+ //! }
140+ //! ```
141+ //!
142+ //! # Thread Safety
143+ //!
144+ //! The [`verify`] function is thread-safe and can be called concurrently from multiple
145+ //! threads. All types used in verification are `Send + Sync`.
146+
1147use std:: {
2148 error:: Error ,
3149 fmt:: { self , Display , Formatter } ,
@@ -22,26 +168,36 @@ use crate::{
22168 KernelError , ScriptPubkeyExt , TransactionExt , TxOutExt ,
23169} ;
24170
171+ /// No verification flags.
25172pub const VERIFY_NONE : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NONE ;
26173
174+ /// Validate Pay-to-Script-Hash (BIP 16).
27175pub const VERIFY_P2SH : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_P2SH ;
28176
177+ /// Require strict DER encoding for ECDSA signatures (BIP 66).
29178pub const VERIFY_DERSIG : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_DERSIG ;
30179
180+ /// Require the dummy element in OP_CHECKMULTISIG to be empty (BIP 147).
31181pub const VERIFY_NULLDUMMY : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NULLDUMMY ;
32182
183+ /// Enable OP_CHECKLOCKTIMEVERIFY (BIP 65).
33184pub const VERIFY_CHECKLOCKTIMEVERIFY : btck_ScriptVerificationFlags =
34185 BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKLOCKTIMEVERIFY ;
35186
187+ /// Enable OP_CHECKSEQUENCEVERIFY (BIP 112).
36188pub const VERIFY_CHECKSEQUENCEVERIFY : btck_ScriptVerificationFlags =
37189 BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKSEQUENCEVERIFY ;
38190
191+ /// Validate Segregated Witness programs (BIP 141/143).
39192pub const VERIFY_WITNESS : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_WITNESS ;
40193
194+ /// Validate Taproot spends (BIP 341/342). Requires spent outputs.
41195pub const VERIFY_TAPROOT : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_TAPROOT ;
42196
197+ /// All consensus rules.
43198pub const VERIFY_ALL : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_ALL ;
44199
200+ /// All consensus rules except Taproot.
45201pub const VERIFY_ALL_PRE_TAPROOT : btck_ScriptVerificationFlags = VERIFY_P2SH
46202 | VERIFY_DERSIG
47203 | VERIFY_NULLDUMMY
@@ -51,17 +207,84 @@ pub const VERIFY_ALL_PRE_TAPROOT: btck_ScriptVerificationFlags = VERIFY_P2SH
51207
52208/// Verifies a transaction input against its corresponding output script.
53209///
210+ /// This function checks that the transaction input at the specified index properly
211+ /// satisfies the spending conditions defined by the output script. The verification
212+ /// process depends on the script type and the consensus rules specified by the flags.
213+ ///
54214/// # Arguments
55- /// * `script_pubkey` - The output script to verify against
56- /// * `amount` - Needs to be set if the segwit flag is set
57- /// * `tx_to` - The transaction containing the input to verify
58- /// * `input_index` - The index of the input within `tx_to` to verify
59- /// * `flags` - Defaults to all if none
60- /// * `spent_output` - The outputs being spent by this transaction
215+ ///
216+ /// * `script_pubkey` - The output script (locking script) to verify against
217+ /// * `amount` - The amount in satoshis of the output being spent. Required for SegWit
218+ /// and Taproot scripts (when [`VERIFY_WITNESS`] or [`VERIFY_TAPROOT`] flags are set).
219+ /// Optional for pre-SegWit scripts.
220+ /// * `tx_to` - The transaction containing the input to verify (the spending transaction)
221+ /// * `input_index` - The zero-based index of the input within `tx_to` to verify
222+ /// * `flags` - Verification flags specifying which consensus rules to enforce. If `None`,
223+ /// defaults to [`VERIFY_ALL`]. Combine multiple flags using bitwise OR (`|`).
224+ /// * `spent_outputs` - The outputs being spent by the transaction. For SegWit and Taproot,
225+ /// this should contain all outputs spent by all inputs in the transaction. For pre-SegWit,
226+ /// this can be empty or contain just the output being spent. The length must either be 0
227+ /// or match the number of inputs in the transaction.
61228///
62229/// # Returns
63- /// * `Ok(())` if verification succeeds
64- /// * [`KernelError::ScriptVerify`] an error describing the failure
230+ ///
231+ /// * `Ok(())` - Verification succeeded; the input properly spends the output
232+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::TxInputIndex))` - Input index out of bounds
233+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsMismatch))` - The spent_outputs
234+ /// length is non-zero but doesn't match the number of inputs
235+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlags))` - Invalid verification flags
236+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlagsCombination))` - Incompatible
237+ /// combination of flags
238+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsRequired))` - Spent outputs
239+ /// are required for this script type but were not provided
240+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::Invalid))` - Script verification failed;
241+ /// the input does not properly satisfy the output's spending conditions
242+ ///
243+ /// # Examples
244+ ///
245+ /// ## Verifying a P2PKH transaction
246+ ///
247+ /// ```no_run
248+ /// # use bitcoinkernel::{prelude::*, Transaction, TxOut, verify, VERIFY_ALL};
249+ /// # let tx_bytes = vec![];
250+ /// # let spending_tx = Transaction::new(&tx_bytes).unwrap();
251+ /// # let prev_tx = Transaction::new(&tx_bytes).unwrap();
252+ /// let prev_output = prev_tx.output(0).unwrap();
253+ ///
254+ /// let result = verify(
255+ /// &prev_output.script_pubkey(),
256+ /// None,
257+ /// &spending_tx,
258+ /// 0,
259+ /// Some(VERIFY_ALL),
260+ /// &[] as &[TxOut],
261+ /// );
262+ /// ```
263+ ///
264+ /// ## Using custom flags
265+ ///
266+ /// ```no_run
267+ /// # use bitcoinkernel::{prelude::*, Transaction, TxOut, verify, VERIFY_P2SH, VERIFY_DERSIG};
268+ /// # let tx_bytes = vec![];
269+ /// # let spending_tx = Transaction::new(&tx_bytes).unwrap();
270+ /// # let prev_output = spending_tx.output(0).unwrap();
271+ /// // Only verify P2SH and DERSIG rules
272+ /// let custom_flags = VERIFY_P2SH | VERIFY_DERSIG;
273+ ///
274+ /// let result = verify(
275+ /// &prev_output.script_pubkey(),
276+ /// None,
277+ /// &spending_tx,
278+ /// 0,
279+ /// Some(custom_flags),
280+ /// &[] as &[TxOut],
281+ /// );
282+ /// ```
283+ ///
284+ /// # Panics
285+ ///
286+ /// This function does not panic under normal circumstances. All error conditions
287+ /// are returned as `Result::Err`.
65288pub fn verify (
66289 script_pubkey : & impl ScriptPubkeyExt ,
67290 amount : Option < i64 > ,
@@ -136,18 +359,35 @@ pub fn verify(
136359 }
137360}
138361
139- /// Status of script verification operations .
362+ /// Internal status codes from the C verification function .
140363///
141- /// Indicates the result of verifying a transaction script, including any
142- /// configuration errors that prevented verification from proceeding.
364+ /// These are used internally to distinguish between setup errors (invalid flags,
365+ /// missing data) and actual script verification failures. Converted to
366+ /// [`KernelError::ScriptVerify`] variants in the public API.
143367#[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
144368#[ repr( u8 ) ]
145369enum ScriptVerifyStatus {
146370 /// Script verification completed successfully
147371 Ok = BTCK_SCRIPT_VERIFY_STATUS_OK ,
148- /// Invalid combination of verification flags was provided
372+
373+ /// Invalid or inconsistent verification flags were provided.
374+ ///
375+ /// This occurs when the supplied `script_verify_flags` combination violates
376+ /// internal consistency rules. For example:
377+ ///
378+ /// - `SCRIPT_VERIFY_CLEANSTACK` is set without also enabling either
379+ /// `SCRIPT_VERIFY_P2SH` or `SCRIPT_VERIFY_WITNESS`.
380+ /// - `SCRIPT_VERIFY_WITNESS` is set without also enabling `SCRIPT_VERIFY_P2SH`.
381+ ///
382+ /// These combinations are considered invalid and result in an immediate
383+ /// verification setup failure rather than a script execution failure.
149384 ErrorInvalidFlagsCombination = BTCK_SCRIPT_VERIFY_STATUS_ERROR_INVALID_FLAGS_COMBINATION ,
150- /// Spent outputs are required for this type of verification but were not provided
385+
386+ /// Spent outputs are required but were not provided.
387+ ///
388+ /// Taproot scripts require the complete set of outputs being spent to properly
389+ /// validate witness data. This occurs when the TAPROOT flag is set but no spent
390+ /// outputs were provided.
151391 ErrorSpentOutputsRequired = BTCK_SCRIPT_VERIFY_STATUS_ERROR_SPENT_OUTPUTS_REQUIRED ,
152392}
153393
@@ -172,14 +412,40 @@ impl From<btck_ScriptVerifyStatus> for ScriptVerifyStatus {
172412 }
173413}
174414
175- /// A collection of errors that may occur during script verification
415+ /// Errors that can occur during script verification.
416+ ///
417+ /// These errors represent both configuration problems (incorrect parameters)
418+ /// and actual verification failures (invalid scripts).
176419#[ derive( Debug ) ]
177420pub enum ScriptVerifyError {
421+ /// The specified input index is out of bounds.
422+ ///
423+ /// The `input_index` parameter is greater than or equal to the number
424+ /// of inputs in the transaction.
178425 TxInputIndex ,
426+
427+ /// Invalid verification flags were provided.
428+ ///
429+ /// The flags parameter contains bits that don't correspond to any
430+ /// defined verification flag.
179431 InvalidFlags ,
432+
433+ /// Invalid or inconsistent verification flags were provided.
434+ ///
435+ /// This occurs when the supplied `script_verify_flags` combination violates
436+ /// internal consistency rules.
180437 InvalidFlagsCombination ,
438+
439+ /// The spent_outputs array length doesn't match the input count.
440+ ///
441+ /// When spent_outputs is non-empty, it must contain exactly one output
442+ /// for each input in the transaction.
181443 SpentOutputsMismatch ,
444+
445+ /// Spent outputs are required but were not provided.
182446 SpentOutputsRequired ,
447+
448+ /// Script verification failed.
183449 Invalid ,
184450}
185451
0 commit comments