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+ //! The verification process validates:
15+ //! - Script execution semantics
16+ //! - Signature validity
17+ //! - Timelock conditions (CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY)
18+ //! - SegWit witness program validity
19+ //! - Taproot script path and key path spending rules
20+ //!
21+ //! # Verification Flags
22+ //!
23+ //! Consensus rules have evolved over time through soft forks. Verification flags
24+ //! allow you to specify which consensus rules to enforce:
25+ //!
26+ //! | Flag | Description | BIP |
27+ //! |------|-------------|-----|
28+ //! | [`VERIFY_P2SH`] | Pay-to-Script-Hash validation | BIP 16 |
29+ //! | [`VERIFY_DERSIG`] | Strict DER signature encoding | BIP 66 |
30+ //! | [`VERIFY_NULLDUMMY`] | Dummy stack element must be empty | BIP 147 |
31+ //! | [`VERIFY_CHECKLOCKTIMEVERIFY`] | CHECKLOCKTIMEVERIFY opcode | BIP 65 |
32+ //! | [`VERIFY_CHECKSEQUENCEVERIFY`] | CHECKSEQUENCEVERIFY opcode | BIP 112 |
33+ //! | [`VERIFY_WITNESS`] | Segregated Witness validation | BIP 141/143 |
34+ //! | [`VERIFY_TAPROOT`] | Taproot validation | BIP 341/342 |
35+ //!
36+ //! # Common Flag Combinations
37+ //!
38+ //! - [`VERIFY_ALL_PRE_TAPROOT`]: All rules except Taproot (for pre-Taproot blocks)
39+ //! - [`VERIFY_ALL`]: All consensus rules including Taproot
40+ //!
41+ //! # Examples
42+ //!
43+ //! ## Basic verification with all consensus rules
44+ //!
45+ //! ```no_run
46+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL};
47+ //! # let spending_tx_bytes = vec![];
48+ //! # let prev_tx_bytes = vec![];
49+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
50+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
51+ //! let prev_output = prev_tx.output(0).unwrap();
52+ //!
53+ //! let result = verify(
54+ //! &prev_output.script_pubkey(),
55+ //! Some(prev_output.value()),
56+ //! &spending_tx,
57+ //! 0,
58+ //! Some(VERIFY_ALL),
59+ //! &[prev_output],
60+ //! );
61+ //!
62+ //! match result {
63+ //! Ok(()) => println!("Script verification passed"),
64+ //! Err(e) => println!("Script verification failed: {}", e),
65+ //! }
66+ //! ```
67+ //!
68+ //! ## Verifying pre-Taproot transactions
69+ //!
70+ //! ```no_run
71+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL_PRE_TAPROOT};
72+ //! # let spending_tx_bytes = vec![];
73+ //! # let prev_tx_bytes = vec![];
74+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
75+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
76+ //! # let prev_output = prev_tx.output(0).unwrap();
77+ //! let result = verify(
78+ //! &prev_output.script_pubkey(),
79+ //! Some(prev_output.value()),
80+ //! &spending_tx,
81+ //! 0,
82+ //! Some(VERIFY_ALL_PRE_TAPROOT),
83+ //! &[prev_output],
84+ //! );
85+ //! ```
86+ //!
87+ //! ## Verifying with multiple spent outputs (SegWit)
88+ //!
89+ //! ```no_run
90+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL};
91+ //! # let spending_tx_bytes = vec![];
92+ //! # let prev_tx1_bytes = vec![];
93+ //! # let prev_tx2_bytes = vec![];
94+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
95+ //! # let prev_tx1 = Transaction::new(&prev_tx1_bytes).unwrap();
96+ //! # let prev_tx2 = Transaction::new(&prev_tx2_bytes).unwrap();
97+ //! let spent_outputs = vec![
98+ //! prev_tx1.output(0).unwrap(),
99+ //! prev_tx2.output(1).unwrap(),
100+ //! ];
101+ //!
102+ //! let result = verify(
103+ //! &spent_outputs[0].script_pubkey(),
104+ //! Some(spent_outputs[0].value()),
105+ //! &spending_tx,
106+ //! 0,
107+ //! Some(VERIFY_ALL),
108+ //! &spent_outputs,
109+ //! );
110+ //! ```
111+ //!
112+ //! ## Handling verification errors
113+ //!
114+ //! ```no_run
115+ //! # use bitcoinkernel::{prelude::*, Transaction, verify, VERIFY_ALL, KernelError, ScriptVerifyError};
116+ //! # let spending_tx_bytes = vec![];
117+ //! # let prev_tx_bytes = vec![];
118+ //! # let spending_tx = Transaction::new(&spending_tx_bytes).unwrap();
119+ //! # let prev_tx = Transaction::new(&prev_tx_bytes).unwrap();
120+ //! # let prev_output = prev_tx.output(0).unwrap();
121+ //! let result = verify(
122+ //! &prev_output.script_pubkey(),
123+ //! Some(prev_output.value()),
124+ //! &spending_tx,
125+ //! 0,
126+ //! Some(VERIFY_ALL),
127+ //! &[prev_output],
128+ //! );
129+ //!
130+ //! match result {
131+ //! Ok(()) => {
132+ //! println!("Valid transaction");
133+ //! }
134+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsRequired)) => {
135+ //! println!("This script type requires spent outputs");
136+ //! }
137+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlagsCombination)) => {
138+ //! println!("Invalid combination of verification flags");
139+ //! }
140+ //! Err(KernelError::ScriptVerify(ScriptVerifyError::Invalid)) => {
141+ //! println!("Script verification failed - invalid script");
142+ //! }
143+ //! Err(e) => {
144+ //! println!("Other error: {}", e);
145+ //! }
146+ //! }
147+ //! ```
148+ //!
149+ //! # Thread Safety
150+ //!
151+ //! The [`verify`] function is thread-safe and can be called concurrently from multiple
152+ //! threads. All types used in verification are `Send + Sync`.
153+
1154use std:: {
2155 error:: Error ,
3156 fmt:: { self , Display , Formatter } ,
@@ -22,26 +175,36 @@ use crate::{
22175 KernelError , ScriptPubkeyExt , TransactionExt , TxOutExt ,
23176} ;
24177
178+ /// No verification flags.
25179pub const VERIFY_NONE : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NONE ;
26180
181+ /// Validate Pay-to-Script-Hash (BIP 16).
27182pub const VERIFY_P2SH : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_P2SH ;
28183
184+ /// Require strict DER encoding for ECDSA signatures (BIP 66).
29185pub const VERIFY_DERSIG : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_DERSIG ;
30186
187+ /// Require the dummy element in OP_CHECKMULTISIG to be empty (BIP 147).
31188pub const VERIFY_NULLDUMMY : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NULLDUMMY ;
32189
190+ /// Enable OP_CHECKLOCKTIMEVERIFY (BIP 65).
33191pub const VERIFY_CHECKLOCKTIMEVERIFY : btck_ScriptVerificationFlags =
34192 BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKLOCKTIMEVERIFY ;
35193
194+ /// Enable OP_CHECKSEQUENCEVERIFY (BIP 112).
36195pub const VERIFY_CHECKSEQUENCEVERIFY : btck_ScriptVerificationFlags =
37196 BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKSEQUENCEVERIFY ;
38197
198+ /// Validate Segregated Witness programs (BIP 141/143).
39199pub const VERIFY_WITNESS : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_WITNESS ;
40200
201+ /// Validate Taproot spends (BIP 341/342). Requires spent outputs.
41202pub const VERIFY_TAPROOT : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_TAPROOT ;
42203
204+ /// All consensus rules.
43205pub const VERIFY_ALL : btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_ALL ;
44206
207+ /// All consensus rules except Taproot.
45208pub const VERIFY_ALL_PRE_TAPROOT : btck_ScriptVerificationFlags = VERIFY_P2SH
46209 | VERIFY_DERSIG
47210 | VERIFY_NULLDUMMY
@@ -51,17 +214,85 @@ pub const VERIFY_ALL_PRE_TAPROOT: btck_ScriptVerificationFlags = VERIFY_P2SH
51214
52215/// Verifies a transaction input against its corresponding output script.
53216///
217+ /// This function checks that the transaction input at the specified index properly
218+ /// satisfies the spending conditions defined by the output script. The verification
219+ /// process depends on the script type and the consensus rules specified by the flags.
220+ ///
54221/// # 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
222+ ///
223+ /// * `script_pubkey` - The output script (locking script) to verify against
224+ /// * `amount` - The amount in satoshis of the output being spent. Required for SegWit
225+ /// and Taproot scripts (when [`VERIFY_WITNESS`] or [`VERIFY_TAPROOT`] flags are set).
226+ /// Optional for pre-SegWit scripts.
227+ /// * `tx_to` - The transaction containing the input to verify (the spending transaction)
228+ /// * `input_index` - The zero-based index of the input within `tx_to` to verify
229+ /// * `flags` - Verification flags specifying which consensus rules to enforce. If `None`,
230+ /// defaults to [`VERIFY_ALL`]. Combine multiple flags using bitwise OR (`|`).
231+ /// * `spent_outputs` - The outputs being spent by the transaction. For SegWit and Taproot,
232+ /// this should contain all outputs spent by all inputs in the transaction. For pre-SegWit,
233+ /// this can be empty or contain just the output being spent. The length must either be 0
234+ /// or match the number of inputs in the transaction.
61235///
62236/// # Returns
63- /// * `Ok(())` if verification succeeds
64- /// * [`KernelError::ScriptVerify`] an error describing the failure
237+ ///
238+ /// * `Ok(())` - Verification succeeded; the input properly spends the output
239+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::TxInputIndex))` - Input index out of bounds
240+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsMismatch))` - The spent_outputs
241+ /// length is non-zero but doesn't match the number of inputs
242+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlags))` - Invalid verification flags
243+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlagsCombination))` - Incompatible
244+ /// combination of flags
245+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::SpentOutputsRequired))` - Spent outputs
246+ /// are required for this script type (Taproot) but were not provided
247+ /// * `Err(KernelError::ScriptVerify(ScriptVerifyError::Invalid))` - Script verification failed;
248+ /// the input does not properly satisfy the output's spending conditions
249+ ///
250+ /// # Examples
251+ ///
252+ /// ## Verifying a P2PKH transaction
253+ ///
254+ /// ```no_run
255+ /// # use bitcoinkernel::{prelude::*, Transaction, TxOut, verify, VERIFY_ALL};
256+ /// # let tx_bytes = vec![];
257+ /// # let spending_tx = Transaction::new(&tx_bytes).unwrap();
258+ /// # let prev_tx = Transaction::new(&tx_bytes).unwrap();
259+ /// let prev_output = prev_tx.output(0).unwrap();
260+ ///
261+ /// // P2PKH doesn't require amount or spent_outputs
262+ /// let result = verify(
263+ /// &prev_output.script_pubkey(),
264+ /// None,
265+ /// &spending_tx,
266+ /// 0,
267+ /// Some(VERIFY_ALL),
268+ /// &[] as &[TxOut],
269+ /// );
270+ /// ```
271+ ///
272+ /// ## Using custom flags
273+ ///
274+ /// ```no_run
275+ /// # use bitcoinkernel::{prelude::*, Transaction, TxOut, verify, VERIFY_P2SH, VERIFY_DERSIG};
276+ /// # let tx_bytes = vec![];
277+ /// # let spending_tx = Transaction::new(&tx_bytes).unwrap();
278+ /// # let prev_output = spending_tx.output(0).unwrap();
279+ /// // Only verify P2SH and DERSIG rules
280+ /// let custom_flags = VERIFY_P2SH | VERIFY_DERSIG;
281+ ///
282+ /// let result = verify(
283+ /// &prev_output.script_pubkey(),
284+ /// None,
285+ /// &spending_tx,
286+ /// 0,
287+ /// Some(custom_flags),
288+ /// &[] as &[TxOut],
289+ /// );
290+ /// ```
291+ ///
292+ /// # Panics
293+ ///
294+ /// This function does not panic under normal circumstances. All error conditions
295+ /// are returned as `Result::Err`.
65296pub fn verify (
66297 script_pubkey : & impl ScriptPubkeyExt ,
67298 amount : Option < i64 > ,
@@ -136,18 +367,35 @@ pub fn verify(
136367 }
137368}
138369
139- /// Status of script verification operations .
370+ /// Internal status codes from the C verification function .
140371///
141- /// Indicates the result of verifying a transaction script, including any
142- /// configuration errors that prevented verification from proceeding.
372+ /// These are used internally to distinguish between setup errors (invalid flags,
373+ /// missing data) and actual script verification failures. Converted to
374+ /// [`KernelError::ScriptVerify`] variants in the public API.
143375#[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
144376#[ repr( u8 ) ]
145377enum ScriptVerifyStatus {
146378 /// Script verification completed successfully
147379 Ok = BTCK_SCRIPT_VERIFY_STATUS_OK ,
148- /// Invalid combination of verification flags was provided
380+
381+ /// Invalid or inconsistent verification flags were provided.
382+ ///
383+ /// This occurs when the supplied `script_verify_flags` combination violates
384+ /// internal consistency rules. For example:
385+ ///
386+ /// - `SCRIPT_VERIFY_CLEANSTACK` is set without also enabling either
387+ /// `SCRIPT_VERIFY_P2SH` or `SCRIPT_VERIFY_WITNESS`.
388+ /// - `SCRIPT_VERIFY_WITNESS` is set without also enabling `SCRIPT_VERIFY_P2SH`.
389+ ///
390+ /// These combinations are considered invalid and result in an immediate
391+ /// verification setup failure rather than a script execution failure.
149392 ErrorInvalidFlagsCombination = BTCK_SCRIPT_VERIFY_STATUS_ERROR_INVALID_FLAGS_COMBINATION ,
150- /// Spent outputs are required for this type of verification but were not provided
393+
394+ /// Spent outputs are required but were not provided.
395+ ///
396+ /// Taproot scripts require the complete set of outputs being spent to properly
397+ /// validate witness data. This occurs when the TAPROOT flag is set but no spent
398+ /// outputs were provided.
151399 ErrorSpentOutputsRequired = BTCK_SCRIPT_VERIFY_STATUS_ERROR_SPENT_OUTPUTS_REQUIRED ,
152400}
153401
@@ -172,14 +420,59 @@ impl From<btck_ScriptVerifyStatus> for ScriptVerifyStatus {
172420 }
173421}
174422
175- /// A collection of errors that may occur during script verification
423+ /// Errors that can occur during script verification.
424+ ///
425+ /// These errors represent both configuration problems (incorrect parameters)
426+ /// and actual verification failures (invalid scripts).
176427#[ derive( Debug ) ]
177428pub enum ScriptVerifyError {
429+ /// The specified input index is out of bounds.
430+ ///
431+ /// The `input_index` parameter is greater than or equal to the number
432+ /// of inputs in the transaction.
178433 TxInputIndex ,
434+
435+ /// Invalid verification flags were provided.
436+ ///
437+ /// The flags parameter contains bits that don't correspond to any
438+ /// defined verification flag.
179439 InvalidFlags ,
440+
441+ /// Invalid or inconsistent verification flags were provided.
442+ ///
443+ /// This occurs when the supplied `script_verify_flags` combination violates
444+ /// internal consistency rules. For example:
445+ ///
446+ /// - `SCRIPT_VERIFY_CLEANSTACK` is set without also enabling either
447+ /// `SCRIPT_VERIFY_P2SH` or `SCRIPT_VERIFY_WITNESS`.
448+ /// - `SCRIPT_VERIFY_WITNESS` is set without also enabling `SCRIPT_VERIFY_P2SH`.
449+ ///
450+ /// These combinations are considered invalid and result in an immediate
451+ /// verification setup failure rather than a script execution failure.
180452 InvalidFlagsCombination ,
453+
454+ /// The spent_outputs array length doesn't match the input count.
455+ ///
456+ /// When spent_outputs is non-empty, it must contain exactly one output
457+ /// for each input in the transaction.
181458 SpentOutputsMismatch ,
459+
460+ /// Spent outputs are required but were not provided.
461+ ///
462+ /// Taproot scripts require the complete set of outputs being spent to properly
463+ /// validate witness data. This occurs when the TAPROOT flag is set but no spent
464+ /// outputs were provided.
182465 SpentOutputsRequired ,
466+
467+ /// Script verification failed.
468+ ///
469+ /// The input does not properly satisfy the output's spending conditions.
470+ /// This could be due to:
471+ /// - Invalid signatures
472+ /// - Incorrect witness data
473+ /// - Failed script execution
474+ /// - Violated timelock conditions
475+ /// - Other consensus rule violations
183476 Invalid ,
184477}
185478
0 commit comments