bitcoinkernel/core/
verify.rs

1use libbitcoinkernel_sys::{
2    btck_ScriptVerificationFlags, btck_ScriptVerifyStatus, btck_TransactionOutput,
3    btck_script_pubkey_verify,
4};
5
6use crate::{
7    c_helpers,
8    ffi::{
9        BTCK_SCRIPT_VERIFICATION_FLAGS_ALL, BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKLOCKTIMEVERIFY,
10        BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKSEQUENCEVERIFY, BTCK_SCRIPT_VERIFICATION_FLAGS_DERSIG,
11        BTCK_SCRIPT_VERIFICATION_FLAGS_NONE, BTCK_SCRIPT_VERIFICATION_FLAGS_NULLDUMMY,
12        BTCK_SCRIPT_VERIFICATION_FLAGS_P2SH, BTCK_SCRIPT_VERIFICATION_FLAGS_TAPROOT,
13        BTCK_SCRIPT_VERIFICATION_FLAGS_WITNESS,
14        BTCK_SCRIPT_VERIFY_STATUS_ERROR_INVALID_FLAGS_COMBINATION,
15        BTCK_SCRIPT_VERIFY_STATUS_ERROR_SPENT_OUTPUTS_REQUIRED, BTCK_SCRIPT_VERIFY_STATUS_OK,
16    },
17    KernelError, ScriptPubkeyExt, TransactionExt, TxOutExt,
18};
19
20pub const VERIFY_NONE: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NONE;
21
22pub const VERIFY_P2SH: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_P2SH;
23
24pub const VERIFY_DERSIG: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_DERSIG;
25
26pub const VERIFY_NULLDUMMY: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_NULLDUMMY;
27
28pub const VERIFY_CHECKLOCKTIMEVERIFY: btck_ScriptVerificationFlags =
29    BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKLOCKTIMEVERIFY;
30
31pub const VERIFY_CHECKSEQUENCEVERIFY: btck_ScriptVerificationFlags =
32    BTCK_SCRIPT_VERIFICATION_FLAGS_CHECKSEQUENCEVERIFY;
33
34pub const VERIFY_WITNESS: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_WITNESS;
35
36pub const VERIFY_TAPROOT: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_TAPROOT;
37
38pub const VERIFY_ALL: btck_ScriptVerificationFlags = BTCK_SCRIPT_VERIFICATION_FLAGS_ALL;
39
40pub const VERIFY_ALL_PRE_TAPROOT: btck_ScriptVerificationFlags = VERIFY_P2SH
41    | VERIFY_DERSIG
42    | VERIFY_NULLDUMMY
43    | VERIFY_CHECKLOCKTIMEVERIFY
44    | VERIFY_CHECKSEQUENCEVERIFY
45    | VERIFY_WITNESS;
46
47/// Verifies a transaction input against its corresponding output script.
48///
49/// # Arguments
50/// * `script_pubkey` - The output script to verify against
51/// * `amount` - Needs to be set if the segwit flag is set
52/// * `tx_to` - The transaction containing the input to verify
53/// * `input_index` - The index of the input within `tx_to` to verify
54/// * `flags` - Defaults to all if none
55/// * `spent_output` - The outputs being spent by this transaction
56///
57/// # Returns
58/// * `Ok(())` if verification succeeds
59/// * [`KernelError::ScriptVerify`] an error describing the failure
60pub fn verify(
61    script_pubkey: &impl ScriptPubkeyExt,
62    amount: Option<i64>,
63    tx_to: &impl TransactionExt,
64    input_index: usize,
65    flags: Option<u32>,
66    spent_outputs: &[impl TxOutExt],
67) -> Result<(), KernelError> {
68    let input_count = tx_to.input_count();
69
70    if input_index >= input_count {
71        return Err(KernelError::ScriptVerify(ScriptVerifyError::TxInputIndex));
72    }
73
74    if !spent_outputs.is_empty() && spent_outputs.len() != input_count {
75        return Err(KernelError::ScriptVerify(
76            ScriptVerifyError::SpentOutputsMismatch,
77        ));
78    }
79
80    let kernel_flags = if let Some(flag) = flags {
81        if (flag & !VERIFY_ALL) != 0 {
82            return Err(KernelError::ScriptVerify(ScriptVerifyError::InvalidFlags));
83        }
84        flag
85    } else {
86        VERIFY_ALL
87    };
88
89    let status = ScriptVerifyStatus::Ok;
90    let kernel_amount = amount.unwrap_or_default();
91    let kernel_spent_outputs: Vec<*const btck_TransactionOutput> =
92        spent_outputs.iter().map(|utxo| utxo.as_ptr()).collect();
93
94    let spent_outputs_ptr = if kernel_spent_outputs.is_empty() {
95        std::ptr::null_mut()
96    } else {
97        kernel_spent_outputs.as_ptr() as *mut *const btck_TransactionOutput
98    };
99
100    let ret = unsafe {
101        btck_script_pubkey_verify(
102            script_pubkey.as_ptr(),
103            kernel_amount,
104            tx_to.as_ptr(),
105            spent_outputs_ptr,
106            spent_outputs.len(),
107            input_index as u32,
108            kernel_flags,
109            &mut status.into(),
110        )
111    };
112
113    let script_status = ScriptVerifyStatus::try_from(status).map_err(|_| {
114        KernelError::Internal(format!("Invalid script verify status: {:?}", status))
115    })?;
116
117    if !c_helpers::verification_passed(ret) {
118        let err = match script_status {
119            ScriptVerifyStatus::ErrorInvalidFlagsCombination => {
120                ScriptVerifyError::InvalidFlagsCombination
121            }
122            ScriptVerifyStatus::ErrorSpentOutputsRequired => {
123                ScriptVerifyError::SpentOutputsRequired
124            }
125            _ => ScriptVerifyError::Invalid,
126        };
127        Err(KernelError::ScriptVerify(err))
128    } else {
129        Ok(())
130    }
131}
132
133/// Status of script verification operations.
134///
135/// Indicates the result of verifying a transaction script, including any
136/// configuration errors that prevented verification from proceeding.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
138#[repr(u8)]
139pub enum ScriptVerifyStatus {
140    /// Script verification completed successfully
141    Ok = BTCK_SCRIPT_VERIFY_STATUS_OK,
142    /// Invalid combination of verification flags was provided
143    ErrorInvalidFlagsCombination = BTCK_SCRIPT_VERIFY_STATUS_ERROR_INVALID_FLAGS_COMBINATION,
144    /// Spent outputs are required for this type of verification but were not provided
145    ErrorSpentOutputsRequired = BTCK_SCRIPT_VERIFY_STATUS_ERROR_SPENT_OUTPUTS_REQUIRED,
146}
147
148impl From<ScriptVerifyStatus> for btck_ScriptVerifyStatus {
149    fn from(status: ScriptVerifyStatus) -> Self {
150        status as btck_ScriptVerifyStatus
151    }
152}
153
154impl From<btck_ScriptVerifyStatus> for ScriptVerifyStatus {
155    fn from(value: btck_ScriptVerifyStatus) -> Self {
156        match value {
157            BTCK_SCRIPT_VERIFY_STATUS_OK => ScriptVerifyStatus::Ok,
158            BTCK_SCRIPT_VERIFY_STATUS_ERROR_INVALID_FLAGS_COMBINATION => {
159                ScriptVerifyStatus::ErrorInvalidFlagsCombination
160            }
161            BTCK_SCRIPT_VERIFY_STATUS_ERROR_SPENT_OUTPUTS_REQUIRED => {
162                ScriptVerifyStatus::ErrorSpentOutputsRequired
163            }
164            _ => panic!("Unknown script verify status: {}", value),
165        }
166    }
167}
168
169/// A collection of errors that may occur during script verification
170#[derive(Debug)]
171pub enum ScriptVerifyError {
172    TxInputIndex,
173    TxSizeMismatch,
174    TxDeserialize,
175    InvalidFlags,
176    InvalidFlagsCombination,
177    SpentOutputsMismatch,
178    SpentOutputsRequired,
179    Invalid,
180}