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 Err(ScriptError::Verification(
286 "bitcoinconsensus backend is disabled".to_owned(),
287 ))
288}
289
290fn verify_taproot_keypath(
291 spending: &bitcoin::Transaction,
292 input_idx: usize,
293 prevout: &TxOut,
294 script: &Script,
295 witness: &[Vec<u8>],
296) -> Result<bool, ScriptError> {
297 if spending.input.len() != 1 || input_idx != 0 {
298 return Err(ScriptError::TaprootPrevoutsUnavailable);
299 }
300 let signature_bytes = taproot_keypath_signature(witness)?;
301 let (signature_bytes, sighash_type) = match signature_bytes.len() {
302 64 => (signature_bytes, TapSighashType::Default),
303 65 => {
304 let sighash_type = TapSighashType::from_consensus_u8(signature_bytes[64])
305 .map_err(|error| ScriptError::Verification(error.to_string()))?;
306 (&signature_bytes[..64], sighash_type)
307 }
308 len => {
309 return Err(ScriptError::Verification(format!(
310 "taproot key-path signature length {len} is not 64 or 65 bytes"
311 )));
312 }
313 };
314 let signature = bitcoin::secp256k1::schnorr::Signature::from_slice(signature_bytes)
315 .map_err(|error| ScriptError::Verification(error.to_string()))?;
316 let public_key = bitcoin::secp256k1::XOnlyPublicKey::from_slice(&script.as_bytes()[2..34])
317 .map_err(|error| ScriptError::Verification(error.to_string()))?;
318 let prevouts = [prevout.clone()];
319 let mut cache = SighashCache::new(spending);
320 let sighash = cache
321 .taproot_key_spend_signature_hash(input_idx, &Prevouts::All(&prevouts), sighash_type)
322 .map_err(|error| ScriptError::Verification(error.to_string()))?;
323 let message = bitcoin::secp256k1::Message::from_digest(*sighash.as_byte_array());
324 let secp = bitcoin::secp256k1::Secp256k1::verification_only();
325 secp.verify_schnorr(&signature, &message, &public_key)
326 .map(|()| true)
327 .map_err(|error| ScriptError::Verification(error.to_string()))
328}
329
330fn taproot_keypath_signature(witness: &[Vec<u8>]) -> Result<&[u8], ScriptError> {
331 match witness {
332 [signature] => Ok(signature),
333 [] => Err(ScriptError::Verification(
334 "missing taproot key-path signature".to_owned(),
335 )),
336 _ => Err(ScriptError::TaprootUnsupportedWitness {
337 elements: witness.len(),
338 }),
339 }
340}