Skip to main content

bitcoin_rs_script/
interpreter.rs

1#[cfg(feature = "bitcoinconsensus")]
2use bitcoin::consensus::encode;
3use bitcoin::hashes::Hash as _;
4use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
5use bitcoin::{Script, ScriptBuf, Witness};
6use bitcoin_rs_primitives::TxOut;
7use thiserror::Error;
8
9/// Verification flags passed to the delegated consensus script engine.
10#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
11pub struct VerifyFlags(u32);
12
13impl VerifyFlags {
14    /// No verification flags.
15    pub const NONE: Self = Self(0);
16    /// Evaluate P2SH subscripts (BIP16).
17    pub const P2SH: Self = Self(1 << 0);
18    /// Require strict signature and public-key encodings.
19    pub const STRICTENC: Self = Self(1 << 1);
20    /// Require strict DER signatures (BIP66).
21    pub const DERSIG: Self = Self(1 << 2);
22    /// Require low-S ECDSA signatures.
23    pub const LOW_S: Self = Self(1 << 3);
24    /// Require empty CHECKMULTISIG dummy element (BIP147).
25    pub const NULLDUMMY: Self = Self(1 << 4);
26    /// Require scriptSig push-only form.
27    pub const SIGPUSHONLY: Self = Self(1 << 5);
28    /// Require minimal push and numeric encodings.
29    pub const MINIMALDATA: Self = Self(1 << 6);
30    /// Discourage NOPs reserved for future soft forks.
31    pub const DISCOURAGE_UPGRADABLE_NOPS: Self = Self(1 << 7);
32    /// Require a single true stack item after evaluation.
33    pub const CLEANSTACK: Self = Self(1 << 8);
34    /// Enable `OP_CHECKLOCKTIMEVERIFY` (BIP65).
35    pub const CHECKLOCKTIMEVERIFY: Self = Self(1 << 9);
36    /// Enable `OP_CHECKSEQUENCEVERIFY` (BIP112).
37    pub const CHECKSEQUENCEVERIFY: Self = Self(1 << 10);
38    /// Enable segregated witness validation (BIP141/BIP143).
39    pub const WITNESS: Self = Self(1 << 11);
40    /// Discourage unknown witness program versions.
41    pub const DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: Self = Self(1 << 12);
42    /// Require minimal IF/NOTIF arguments in segwit scripts.
43    pub const MINIMALIF: Self = Self(1 << 13);
44    /// Require failed signature checks to consume empty signatures.
45    pub const NULLFAIL: Self = Self(1 << 14);
46    /// Require compressed public keys in segwit scripts.
47    pub const WITNESS_PUBKEYTYPE: Self = Self(1 << 15);
48    /// Make `CODESEPARATOR` and `FindAndDelete` fail non-segwit scripts.
49    pub const CONST_SCRIPTCODE: Self = Self(1 << 16);
50    /// Enable taproot and tapscript validation (BIP341/BIP342).
51    pub const TAPROOT: Self = Self(1 << 17);
52    /// Discourage unknown taproot leaf versions.
53    pub const DISCOURAGE_UPGRADABLE_TAPROOT_VERSION: Self = Self(1 << 18);
54    /// Discourage unknown `OP_SUCCESS` opcodes.
55    pub const DISCOURAGE_OP_SUCCESS: Self = Self(1 << 19);
56    /// Discourage unknown public-key versions in tapscript.
57    pub const DISCOURAGE_UPGRADABLE_PUBKEYTYPE: Self = Self(1 << 20);
58    /// Mandatory consensus flags used for block validation after taproot activation.
59    pub const MANDATORY: Self = Self(
60        Self::P2SH.0
61            | Self::DERSIG.0
62            | Self::NULLDUMMY.0
63            | Self::CHECKLOCKTIMEVERIFY.0
64            | Self::CHECKSEQUENCEVERIFY.0
65            | Self::WITNESS.0
66            | Self::TAPROOT.0,
67    );
68    /// Standard relay flags; useful for vector tests that request policy checks.
69    pub const STANDARD: Self = Self(
70        Self::MANDATORY.0
71            | Self::STRICTENC.0
72            | Self::LOW_S.0
73            | Self::MINIMALDATA.0
74            | Self::DISCOURAGE_UPGRADABLE_NOPS.0
75            | Self::CLEANSTACK.0
76            | Self::DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM.0
77            | Self::MINIMALIF.0
78            | Self::NULLFAIL.0
79            | Self::WITNESS_PUBKEYTYPE.0
80            | Self::CONST_SCRIPTCODE.0
81            | Self::DISCOURAGE_UPGRADABLE_TAPROOT_VERSION.0
82            | Self::DISCOURAGE_OP_SUCCESS.0
83            | Self::DISCOURAGE_UPGRADABLE_PUBKEYTYPE.0,
84    );
85
86    /// Builds flags from raw Core-compatible bits.
87    #[must_use]
88    pub const fn from_bits(bits: u32) -> Self {
89        Self(bits)
90    }
91
92    /// Returns raw Core-compatible flag bits.
93    #[must_use]
94    pub const fn bits(self) -> u32 {
95        self.0
96    }
97
98    /// Returns only the bits accepted by rust-bitcoin's `bitcoinconsensus` backend.
99    #[must_use]
100    pub const fn consensus_bits(self) -> u32 {
101        self.0
102            & (Self::P2SH.0
103                | Self::DERSIG.0
104                | Self::NULLDUMMY.0
105                | Self::CHECKLOCKTIMEVERIFY.0
106                | Self::CHECKSEQUENCEVERIFY.0
107                | Self::WITNESS.0)
108    }
109
110    /// Returns the full consensus-authority bit set, including taproot for bitcoinkernel.
111    #[must_use]
112    pub const fn kernel_bits(self) -> u32 {
113        self.0 & Self::MANDATORY.0
114    }
115
116    /// Returns true when all `other` bits are present.
117    #[must_use]
118    pub const fn contains(self, other: Self) -> bool {
119        self.0 & other.0 == other.0
120    }
121
122    /// Adds another flag set.
123    #[must_use]
124    pub const fn union(self, other: Self) -> Self {
125        Self(self.0 | other.0)
126    }
127
128    /// Parses a comma-separated Core test-vector flag string.
129    pub fn from_core_names(names: &str) -> Result<Self, ScriptError> {
130        let mut flags = Self::NONE;
131        for name in names
132            .split(',')
133            .map(str::trim)
134            .filter(|name| !name.is_empty())
135        {
136            flags = flags.union(match name {
137                "NONE" => Self::NONE,
138                "P2SH" => Self::P2SH,
139                "STRICTENC" => Self::STRICTENC,
140                "DERSIG" => Self::DERSIG,
141                "LOW_S" => Self::LOW_S,
142                "NULLDUMMY" => Self::NULLDUMMY,
143                "SIGPUSHONLY" => Self::SIGPUSHONLY,
144                "MINIMALDATA" => Self::MINIMALDATA,
145                "DISCOURAGE_UPGRADABLE_NOPS" => Self::DISCOURAGE_UPGRADABLE_NOPS,
146                "CLEANSTACK" => Self::CLEANSTACK,
147                "CHECKLOCKTIMEVERIFY" => Self::CHECKLOCKTIMEVERIFY,
148                "CHECKSEQUENCEVERIFY" => Self::CHECKSEQUENCEVERIFY,
149                "WITNESS" => Self::WITNESS,
150                "DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM" => {
151                    Self::DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
152                }
153                "MINIMALIF" => Self::MINIMALIF,
154                "NULLFAIL" => Self::NULLFAIL,
155                "WITNESS_PUBKEYTYPE" => Self::WITNESS_PUBKEYTYPE,
156                "CONST_SCRIPTCODE" => Self::CONST_SCRIPTCODE,
157                "TAPROOT" => Self::TAPROOT,
158                "DISCOURAGE_UPGRADABLE_TAPROOT_VERSION" => {
159                    Self::DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
160                }
161                "DISCOURAGE_OP_SUCCESS" => Self::DISCOURAGE_OP_SUCCESS,
162                "DISCOURAGE_UPGRADABLE_PUBKEYTYPE" => Self::DISCOURAGE_UPGRADABLE_PUBKEYTYPE,
163                unknown => {
164                    return Err(ScriptError::UnknownFlag {
165                        name: unknown.to_owned(),
166                    });
167                }
168            });
169        }
170        Ok(flags)
171    }
172}
173
174impl From<VerifyFlags> for u32 {
175    fn from(flags: VerifyFlags) -> Self {
176        flags.bits()
177    }
178}
179
180/// Script execution errors surfaced by the script crate.
181#[derive(Debug, Error, PartialEq, Eq)]
182pub enum ScriptError {
183    /// The requested input index was not present in the transaction.
184    #[error("input index {index} out of range for {inputs} inputs")]
185    InputIndexOutOfRange {
186        /// Requested input index.
187        index: usize,
188        /// Transaction input count.
189        inputs: usize,
190    },
191    /// A Core vector flag name was not known by this crate.
192    #[error("unknown script verify flag {name}")]
193    UnknownFlag {
194        /// Unknown flag name.
195        name: String,
196    },
197    /// The transaction could not be serialized for the delegated verifier.
198    #[error("transaction serialization failed: {0}")]
199    Serialization(String),
200    /// The delegated consensus verifier rejected the script.
201    #[error("script verification failed: {0}")]
202    Verification(String),
203    /// Taproot key-path verification requires all prevouts for multi-input transactions.
204    #[error("taproot key-path verification requires all prevouts for multi-input transactions")]
205    TaprootPrevoutsUnavailable,
206    /// This portable path only validates one-element Taproot key-path witnesses.
207    #[error(
208        "taproot witness stack with {elements} elements requires unsupported annex or script-path validation"
209    )]
210    TaprootUnsupportedWitness {
211        /// Number of witness elements supplied for the P2TR spend.
212        elements: usize,
213    },
214}
215
216/// Public script verifier. Legacy and segwit-v0 spends use bitcoinconsensus when
217/// that backend is enabled; taproot key-path spends use the local BIP341 path.
218#[derive(Debug, Default, Clone, Copy)]
219pub struct Interpreter;
220
221impl Interpreter {
222    /// Number of taproot inputs at which block validation uses the batch Schnorr path.
223    pub const BATCH_SCHNORR_THRESHOLD: usize = 16;
224
225    /// Executes a script spend through the enabled script backend.
226    pub fn execute(
227        &self,
228        script_pubkey: &[u8],
229        script_sig: &[u8],
230        witness: &[Vec<u8>],
231        flags: VerifyFlags,
232        prevout: &TxOut,
233        tx: &bitcoin::Transaction,
234        input_idx: usize,
235    ) -> Result<bool, ScriptError> {
236        let mut spending = tx.clone();
237        let inputs = spending.input.len();
238        let input = spending
239            .input
240            .get_mut(input_idx)
241            .ok_or(ScriptError::InputIndexOutOfRange {
242                index: input_idx,
243                inputs,
244            })?;
245        input.script_sig = ScriptBuf::from_bytes(script_sig.to_vec());
246        input.witness = Witness::from_slice(witness);
247
248        let script = Script::from_bytes(script_pubkey);
249        if script.is_p2tr() && flags.contains(VerifyFlags::TAPROOT) {
250            return verify_taproot_keypath(&spending, input_idx, prevout, script, witness);
251        }
252
253        verify_with_bitcoinconsensus(input_idx, prevout, &spending, script, flags)
254    }
255}
256
257#[cfg(feature = "bitcoinconsensus")]
258fn verify_with_bitcoinconsensus(
259    input_idx: usize,
260    prevout: &TxOut,
261    spending: &bitcoin::Transaction,
262    script: &Script,
263    flags: VerifyFlags,
264) -> Result<bool, ScriptError> {
265    let serialized = encode::serialize(spending);
266    script
267        .verify_with_flags(
268            input_idx,
269            prevout.value,
270            serialized.as_slice(),
271            flags.consensus_bits(),
272        )
273        .map(|()| true)
274        .map_err(|error| ScriptError::Verification(error.to_string()))
275}
276
277#[cfg(not(feature = "bitcoinconsensus"))]
278fn verify_with_bitcoinconsensus(
279    input_idx: usize,
280    _prevout: &TxOut,
281    spending: &bitcoin::Transaction,
282    script: &Script,
283    _flags: VerifyFlags,
284) -> Result<bool, ScriptError> {
285    let input = spending
286        .input
287        .get(input_idx)
288        .ok_or(ScriptError::InputIndexOutOfRange {
289            index: input_idx,
290            inputs: spending.input.len(),
291        })?;
292    if script.as_bytes() == [0x51] && input.script_sig.is_empty() && input.witness.is_empty() {
293        return Ok(true);
294    }
295
296    Err(ScriptError::Verification(
297        "bitcoinconsensus backend is disabled".to_owned(),
298    ))
299}
300
301fn verify_taproot_keypath(
302    spending: &bitcoin::Transaction,
303    input_idx: usize,
304    prevout: &TxOut,
305    script: &Script,
306    witness: &[Vec<u8>],
307) -> Result<bool, ScriptError> {
308    if spending.input.len() != 1 || input_idx != 0 {
309        return Err(ScriptError::TaprootPrevoutsUnavailable);
310    }
311    let signature_bytes = taproot_keypath_signature(witness)?;
312    let (signature_bytes, sighash_type) = match signature_bytes.len() {
313        64 => (signature_bytes, TapSighashType::Default),
314        65 => {
315            let sighash_type = TapSighashType::from_consensus_u8(signature_bytes[64])
316                .map_err(|error| ScriptError::Verification(error.to_string()))?;
317            (&signature_bytes[..64], sighash_type)
318        }
319        len => {
320            return Err(ScriptError::Verification(format!(
321                "taproot key-path signature length {len} is not 64 or 65 bytes"
322            )));
323        }
324    };
325    let signature = bitcoin::secp256k1::schnorr::Signature::from_slice(signature_bytes)
326        .map_err(|error| ScriptError::Verification(error.to_string()))?;
327    let public_key = bitcoin::secp256k1::XOnlyPublicKey::from_slice(&script.as_bytes()[2..34])
328        .map_err(|error| ScriptError::Verification(error.to_string()))?;
329    let prevouts = [prevout.clone()];
330    let mut cache = SighashCache::new(spending);
331    let sighash = cache
332        .taproot_key_spend_signature_hash(input_idx, &Prevouts::All(&prevouts), sighash_type)
333        .map_err(|error| ScriptError::Verification(error.to_string()))?;
334    let message = bitcoin::secp256k1::Message::from_digest(*sighash.as_byte_array());
335    let secp = bitcoin::secp256k1::Secp256k1::verification_only();
336    secp.verify_schnorr(&signature, &message, &public_key)
337        .map(|()| true)
338        .map_err(|error| ScriptError::Verification(error.to_string()))
339}
340
341fn taproot_keypath_signature(witness: &[Vec<u8>]) -> Result<&[u8], ScriptError> {
342    match witness {
343        [signature] => Ok(signature),
344        [] => Err(ScriptError::Verification(
345            "missing taproot key-path signature".to_owned(),
346        )),
347        _ => Err(ScriptError::TaprootUnsupportedWitness {
348            elements: witness.len(),
349        }),
350    }
351}
352
353#[cfg(all(test, not(feature = "bitcoinconsensus")))]
354mod tests {
355    use bitcoin::hashes::Hash as _;
356    use bitcoin::{
357        Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, absolute,
358        transaction,
359    };
360
361    use super::{Interpreter, ScriptError, VerifyFlags};
362
363    #[test]
364    #[cfg(not(feature = "bitcoinconsensus"))]
365    fn no_backend_accepts_only_empty_op_true_spend() {
366        let interpreter = Interpreter;
367        let tx = unsigned_spend();
368        let prevout = TxOut {
369            value: Amount::from_sat(50_000),
370            script_pubkey: ScriptBuf::from_bytes(vec![0x51]),
371        };
372
373        assert_eq!(
374            interpreter.execute(
375                prevout.script_pubkey.as_bytes(),
376                &[],
377                &[],
378                VerifyFlags::MANDATORY,
379                &prevout,
380                &tx,
381                0,
382            ),
383            Ok(true)
384        );
385
386        assert!(matches!(
387            interpreter.execute(&[0x00], &[], &[], VerifyFlags::MANDATORY, &prevout, &tx, 0,),
388            Err(ScriptError::Verification(_))
389        ));
390    }
391
392    #[cfg(not(feature = "bitcoinconsensus"))]
393    fn unsigned_spend() -> Transaction {
394        Transaction {
395            version: transaction::Version::TWO,
396            lock_time: absolute::LockTime::ZERO,
397            input: vec![TxIn {
398                previous_output: OutPoint {
399                    txid: Txid::from_byte_array([1; 32]),
400                    vout: 0,
401                },
402                script_sig: ScriptBuf::new(),
403                sequence: Sequence::MAX,
404                witness: Witness::new(),
405            }],
406            output: vec![TxOut {
407                value: Amount::from_sat(49_000),
408                script_pubkey: ScriptBuf::new(),
409            }],
410        }
411    }
412}