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#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
11pub struct VerifyFlags(u32);
12
13impl VerifyFlags {
14 pub const NONE: Self = Self(0);
16 pub const P2SH: Self = Self(1 << 0);
18 pub const STRICTENC: Self = Self(1 << 1);
20 pub const DERSIG: Self = Self(1 << 2);
22 pub const LOW_S: Self = Self(1 << 3);
24 pub const NULLDUMMY: Self = Self(1 << 4);
26 pub const SIGPUSHONLY: Self = Self(1 << 5);
28 pub const MINIMALDATA: Self = Self(1 << 6);
30 pub const DISCOURAGE_UPGRADABLE_NOPS: Self = Self(1 << 7);
32 pub const CLEANSTACK: Self = Self(1 << 8);
34 pub const CHECKLOCKTIMEVERIFY: Self = Self(1 << 9);
36 pub const CHECKSEQUENCEVERIFY: Self = Self(1 << 10);
38 pub const WITNESS: Self = Self(1 << 11);
40 pub const DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: Self = Self(1 << 12);
42 pub const MINIMALIF: Self = Self(1 << 13);
44 pub const NULLFAIL: Self = Self(1 << 14);
46 pub const WITNESS_PUBKEYTYPE: Self = Self(1 << 15);
48 pub const CONST_SCRIPTCODE: Self = Self(1 << 16);
50 pub const TAPROOT: Self = Self(1 << 17);
52 pub const DISCOURAGE_UPGRADABLE_TAPROOT_VERSION: Self = Self(1 << 18);
54 pub const DISCOURAGE_OP_SUCCESS: Self = Self(1 << 19);
56 pub const DISCOURAGE_UPGRADABLE_PUBKEYTYPE: Self = Self(1 << 20);
58 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 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 #[must_use]
88 pub const fn from_bits(bits: u32) -> Self {
89 Self(bits)
90 }
91
92 #[must_use]
94 pub const fn bits(self) -> u32 {
95 self.0
96 }
97
98 #[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 #[must_use]
112 pub const fn kernel_bits(self) -> u32 {
113 self.0 & Self::MANDATORY.0
114 }
115
116 #[must_use]
118 pub const fn contains(self, other: Self) -> bool {
119 self.0 & other.0 == other.0
120 }
121
122 #[must_use]
124 pub const fn union(self, other: Self) -> Self {
125 Self(self.0 | other.0)
126 }
127
128 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#[derive(Debug, Error, PartialEq, Eq)]
182pub enum ScriptError {
183 #[error("input index {index} out of range for {inputs} inputs")]
185 InputIndexOutOfRange {
186 index: usize,
188 inputs: usize,
190 },
191 #[error("unknown script verify flag {name}")]
193 UnknownFlag {
194 name: String,
196 },
197 #[error("transaction serialization failed: {0}")]
199 Serialization(String),
200 #[error("script verification failed: {0}")]
202 Verification(String),
203 #[error("taproot key-path verification requires all prevouts for multi-input transactions")]
205 TaprootPrevoutsUnavailable,
206 #[error(
208 "taproot witness stack with {elements} elements requires unsupported annex or script-path validation"
209 )]
210 TaprootUnsupportedWitness {
211 elements: usize,
213 },
214}
215
216#[derive(Debug, Default, Clone, Copy)]
219pub struct Interpreter;
220
221impl Interpreter {
222 pub const BATCH_SCHNORR_THRESHOLD: usize = 16;
224
225 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}