Skip to content

Commit 7d9d4ac

Browse files
feat(core): improve documentation for verify function
1 parent 2543626 commit 7d9d4ac

File tree

1 file changed

+307
-14
lines changed

1 file changed

+307
-14
lines changed

src/core/verify.rs

Lines changed: 307 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,156 @@
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+
1154
use 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.
25179
pub const VERIFY_NONE: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NONE;
26180

181+
/// Validate Pay-to-Script-Hash (BIP 16).
27182
pub const VERIFY_P2SH: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_P2SH;
28183

184+
/// Require strict DER encoding for ECDSA signatures (BIP 66).
29185
pub const VERIFY_DERSIG: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_DERSIG;
30186

187+
/// Require the dummy element in OP_CHECKMULTISIG to be empty (BIP 147).
31188
pub const VERIFY_NULLDUMMY: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NULLDUMMY;
32189

190+
/// Enable OP_CHECKLOCKTIMEVERIFY (BIP 65).
33191
pub const VERIFY_CHECKLOCKTIMEVERIFY: btck_ScriptVerificationFlags =
34192
BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKLOCKTIMEVERIFY;
35193

194+
/// Enable OP_CHECKSEQUENCEVERIFY (BIP 112).
36195
pub const VERIFY_CHECKSEQUENCEVERIFY: btck_ScriptVerificationFlags =
37196
BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKSEQUENCEVERIFY;
38197

198+
/// Validate Segregated Witness programs (BIP 141/143).
39199
pub const VERIFY_WITNESS: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_WITNESS;
40200

201+
/// Validate Taproot spends (BIP 341/342). Requires spent outputs.
41202
pub const VERIFY_TAPROOT: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_TAPROOT;
42203

204+
/// All consensus rules.
43205
pub const VERIFY_ALL: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_ALL;
44206

207+
/// All consensus rules except Taproot.
45208
pub 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`.
65296
pub 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)]
145377
enum 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)]
177428
pub 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

Comments
 (0)