1#![doc(html_favicon_url = "https://zfnd.org/wp-content/uploads/2022/03/zebra-favicon-128.png")]
3#![doc(html_logo_url = "https://zfnd.org/wp-content/uploads/2022/03/zebra-icon.png")]
4#![doc(html_root_url = "https://docs.rs/zebra_script")]
5#![allow(unsafe_code)]
7
8use core::fmt;
9use std::sync::Arc;
10
11use thiserror::Error;
12
13use zcash_script::ZcashScript;
14
15use zebra_chain::{
16 parameters::NetworkUpgrade,
17 transaction::{HashType, SigHasher, Transaction},
18 transparent,
19};
20
21#[derive(Clone, Debug, Error, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum Error {
25 ScriptInvalid,
27 TxIndex,
29 TxCoinbase,
31 Unknown(zcash_script::Error),
33 TxInvalid(#[from] zebra_chain::Error),
35}
36
37impl fmt::Display for Error {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str(&match self {
40 Error::ScriptInvalid => "script verification failed".to_owned(),
41 Error::TxIndex => "input index out of bounds".to_owned(),
42 Error::TxCoinbase => {
43 "tx is a coinbase transaction and should not be verified".to_owned()
44 }
45 Error::Unknown(e) => format!("unknown error from zcash_script: {e:?}"),
46 Error::TxInvalid(e) => format!("tx is invalid: {e}"),
47 })
48 }
49}
50
51impl From<zcash_script::Error> for Error {
52 #[allow(non_upper_case_globals)]
53 fn from(err_code: zcash_script::Error) -> Error {
54 match err_code {
55 zcash_script::Error::Ok(_) => Error::ScriptInvalid,
56 unknown => Error::Unknown(unknown),
57 }
58 }
59}
60
61fn get_interpreter(
63 sighash: zcash_script::SighashCalculator,
64 lock_time: u32,
65 is_final: bool,
66 #[allow(unused)] flags: zcash_script::VerificationFlags,
67) -> impl ZcashScript + use<'_> {
68 #[cfg(feature = "comparison-interpreter")]
69 return zcash_script::cxx_rust_comparison_interpreter(sighash, lock_time, is_final, flags);
70 #[cfg(not(feature = "comparison-interpreter"))]
71 zcash_script::CxxInterpreter {
72 sighash,
73 lock_time,
74 is_final,
75 }
76}
77
78#[derive(Debug)]
81pub struct CachedFfiTransaction {
82 transaction: Arc<Transaction>,
86
87 all_previous_outputs: Arc<Vec<transparent::Output>>,
90
91 sighasher: SigHasher,
93}
94
95impl CachedFfiTransaction {
96 pub fn new(
100 transaction: Arc<Transaction>,
101 all_previous_outputs: Arc<Vec<transparent::Output>>,
102 nu: NetworkUpgrade,
103 ) -> Result<Self, Error> {
104 let sighasher = transaction.sighasher(nu, all_previous_outputs.clone())?;
105 Ok(Self {
106 transaction,
107 all_previous_outputs,
108 sighasher,
109 })
110 }
111
112 pub fn inputs(&self) -> &[transparent::Input] {
114 self.transaction.inputs()
115 }
116
117 pub fn all_previous_outputs(&self) -> &Vec<transparent::Output> {
120 &self.all_previous_outputs
121 }
122
123 pub fn sighasher(&self) -> &SigHasher {
125 &self.sighasher
126 }
127
128 #[allow(clippy::unwrap_in_result)]
131 pub fn is_valid(&self, input_index: usize) -> Result<(), Error> {
132 let previous_output = self
133 .all_previous_outputs
134 .get(input_index)
135 .ok_or(Error::TxIndex)?
136 .clone();
137 let transparent::Output {
138 value: _,
139 lock_script,
140 } = previous_output;
141 let script_pub_key: &[u8] = lock_script.as_raw_bytes();
142
143 let flags = zcash_script::VerificationFlags::P2SH
144 | zcash_script::VerificationFlags::CHECKLOCKTIMEVERIFY;
145
146 let lock_time = self.transaction.raw_lock_time();
147 let is_final = self.transaction.inputs()[input_index].sequence() == u32::MAX;
148 let signature_script = match &self.transaction.inputs()[input_index] {
149 transparent::Input::PrevOut {
150 outpoint: _,
151 unlock_script,
152 sequence: _,
153 } => unlock_script.as_raw_bytes(),
154 transparent::Input::Coinbase { .. } => Err(Error::TxCoinbase)?,
155 };
156
157 let calculate_sighash = |script_code: &[u8], hash_type: zcash_script::HashType| {
158 let script_code_vec = script_code.to_vec();
159 let mut our_hash_type = match hash_type.signed_outputs {
160 zcash_script::SignedOutputs::All => HashType::ALL,
161 zcash_script::SignedOutputs::Single => HashType::SINGLE,
162 zcash_script::SignedOutputs::None => HashType::NONE,
163 };
164 if hash_type.anyone_can_pay {
165 our_hash_type |= HashType::ANYONECANPAY;
166 }
167 Some(
168 self.sighasher()
169 .sighash(our_hash_type, Some((input_index, script_code_vec)))
170 .0,
171 )
172 };
173 let interpreter = get_interpreter(&calculate_sighash, lock_time, is_final, flags);
174 interpreter
175 .verify_callback(script_pub_key, signature_script, flags)
176 .map_err(Error::from)
177 }
178}
179
180#[allow(clippy::unwrap_in_result)]
183pub fn legacy_sigop_count(transaction: &Transaction) -> Result<u64, Error> {
184 let mut count: u64 = 0;
185
186 let interpreter = get_interpreter(
189 &|_, _| None,
190 0,
191 true,
192 zcash_script::VerificationFlags::P2SH
193 | zcash_script::VerificationFlags::CHECKLOCKTIMEVERIFY,
194 );
195
196 for input in transaction.inputs() {
197 count += match input {
198 transparent::Input::PrevOut {
199 outpoint: _,
200 unlock_script,
201 sequence: _,
202 } => {
203 let script = unlock_script.as_raw_bytes();
204 interpreter
205 .legacy_sigop_count_script(script)
206 .map_err(Error::from)?
207 }
208 transparent::Input::Coinbase { .. } => 0,
209 } as u64;
210 }
211
212 for output in transaction.outputs() {
213 let script = output.lock_script.as_raw_bytes();
214 let ret = interpreter
215 .legacy_sigop_count_script(script)
216 .map_err(Error::from)?;
217 count += ret as u64;
218 }
219 Ok(count)
220}
221
222#[cfg(test)]
223mod tests {
224 use hex::FromHex;
225 use std::sync::Arc;
226 use zebra_chain::{
227 parameters::NetworkUpgrade,
228 serialization::{ZcashDeserialize, ZcashDeserializeInto},
229 transaction::Transaction,
230 transparent::{self, Output},
231 };
232 use zebra_test::prelude::*;
233
234 lazy_static::lazy_static! {
235 pub static ref SCRIPT_PUBKEY: Vec<u8> = <Vec<u8>>::from_hex("76a914f47cac1e6fec195c055994e8064ffccce0044dd788ac")
236 .unwrap();
237 pub static ref SCRIPT_TX: Vec<u8> = <Vec<u8>>::from_hex("0400008085202f8901fcaf44919d4a17f6181a02a7ebe0420be6f7dad1ef86755b81d5a9567456653c010000006a473044022035224ed7276e61affd53315eca059c92876bc2df61d84277cafd7af61d4dbf4002203ed72ea497a9f6b38eb29df08e830d99e32377edb8a574b8a289024f0241d7c40121031f54b095eae066d96b2557c1f99e40e967978a5fd117465dbec0986ca74201a6feffffff020050d6dc0100000017a9141b8a9bda4b62cd0d0582b55455d0778c86f8628f870d03c812030000001976a914e4ff5512ffafe9287992a1cd177ca6e408e0300388ac62070d0095070d000000000000000000000000")
238 .expect("Block bytes are in valid hex representation");
239 }
240
241 fn verify_valid_script(
242 nu: NetworkUpgrade,
243 tx: &[u8],
244 amount: u64,
245 pubkey: &[u8],
246 ) -> Result<()> {
247 let transaction =
248 tx.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
249 let output = transparent::Output {
250 value: amount.try_into()?,
251 lock_script: transparent::Script::new(pubkey),
252 };
253 let input_index = 0;
254
255 let previous_output = Arc::new(vec![output]);
256 let verifier = super::CachedFfiTransaction::new(transaction, previous_output, nu)
257 .expect("network upgrade should be valid for tx");
258 verifier.is_valid(input_index)?;
259
260 Ok(())
261 }
262
263 #[test]
264 fn verify_valid_script_v4() -> Result<()> {
265 let _init_guard = zebra_test::init();
266
267 verify_valid_script(
268 NetworkUpgrade::Blossom,
269 &SCRIPT_TX,
270 212 * u64::pow(10, 8),
271 &SCRIPT_PUBKEY,
272 )
273 }
274
275 #[test]
276 fn count_legacy_sigops() -> Result<()> {
277 let _init_guard = zebra_test::init();
278
279 let transaction =
280 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
281
282 assert_eq!(super::legacy_sigop_count(&transaction)?, 1);
283
284 Ok(())
285 }
286
287 #[test]
288 fn fail_invalid_script() -> Result<()> {
289 let _init_guard = zebra_test::init();
290
291 let transaction =
292 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
293 let coin = u64::pow(10, 8);
294 let amount = 211 * coin;
295 let output = transparent::Output {
296 value: amount.try_into()?,
297 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()[..]),
298 };
299 let input_index = 0;
300 let verifier = super::CachedFfiTransaction::new(
301 transaction,
302 Arc::new(vec![output]),
303 NetworkUpgrade::Blossom,
304 )
305 .expect("network upgrade should be valid for tx");
306 verifier
307 .is_valid(input_index)
308 .expect_err("verification should fail");
309
310 Ok(())
311 }
312
313 #[test]
314 fn reuse_script_verifier_pass_pass() -> Result<()> {
315 let _init_guard = zebra_test::init();
316
317 let coin = u64::pow(10, 8);
318 let transaction =
319 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
320 let amount = 212 * coin;
321 let output = transparent::Output {
322 value: amount.try_into()?,
323 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
324 };
325
326 let verifier = super::CachedFfiTransaction::new(
327 transaction,
328 Arc::new(vec![output]),
329 NetworkUpgrade::Blossom,
330 )
331 .expect("network upgrade should be valid for tx");
332
333 let input_index = 0;
334
335 verifier.is_valid(input_index)?;
336 verifier.is_valid(input_index)?;
337
338 Ok(())
339 }
340
341 #[test]
342 fn reuse_script_verifier_pass_fail() -> Result<()> {
343 let _init_guard = zebra_test::init();
344
345 let coin = u64::pow(10, 8);
346 let amount = 212 * coin;
347 let output = transparent::Output {
348 value: amount.try_into()?,
349 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
350 };
351 let transaction =
352 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
353
354 let verifier = super::CachedFfiTransaction::new(
355 transaction,
356 Arc::new(vec![output]),
357 NetworkUpgrade::Blossom,
358 )
359 .expect("network upgrade should be valid for tx");
360
361 let input_index = 0;
362
363 verifier.is_valid(input_index)?;
364 verifier
365 .is_valid(input_index + 1)
366 .expect_err("verification should fail");
367
368 Ok(())
369 }
370
371 #[test]
372 fn reuse_script_verifier_fail_pass() -> Result<()> {
373 let _init_guard = zebra_test::init();
374
375 let coin = u64::pow(10, 8);
376 let amount = 212 * coin;
377 let output = transparent::Output {
378 value: amount.try_into()?,
379 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
380 };
381 let transaction =
382 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
383
384 let verifier = super::CachedFfiTransaction::new(
385 transaction,
386 Arc::new(vec![output]),
387 NetworkUpgrade::Blossom,
388 )
389 .expect("network upgrade should be valid for tx");
390
391 let input_index = 0;
392
393 verifier
394 .is_valid(input_index + 1)
395 .expect_err("verification should fail");
396 verifier.is_valid(input_index)?;
397
398 Ok(())
399 }
400
401 #[test]
402 fn reuse_script_verifier_fail_fail() -> Result<()> {
403 let _init_guard = zebra_test::init();
404
405 let coin = u64::pow(10, 8);
406 let amount = 212 * coin;
407 let output = transparent::Output {
408 value: amount.try_into()?,
409 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
410 };
411 let transaction =
412 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
413
414 let verifier = super::CachedFfiTransaction::new(
415 transaction,
416 Arc::new(vec![output]),
417 NetworkUpgrade::Blossom,
418 )
419 .expect("network upgrade should be valid for tx");
420
421 let input_index = 0;
422
423 verifier
424 .is_valid(input_index + 1)
425 .expect_err("verification should fail");
426
427 verifier
428 .is_valid(input_index + 1)
429 .expect_err("verification should fail");
430
431 Ok(())
432 }
433
434 #[test]
435 fn p2sh() -> Result<()> {
436 let _init_guard = zebra_test::init();
437
438 let serialized_tx = "0400008085202f8901c21354bf2305e474ad695382e68efc06e2f8b83c512496f615d153c2e00e688b00000000fdfd0000483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453aeffffffff0250954903000000001976a914a5a4e1797dac40e8ce66045d1a44c4a63d12142988acccf41c590000000017a9141c973c68b2acc6d6688eff9c7a9dd122ac1346ab8786c72400000000000000000000000000000000";
440 let serialized_output = "4065675c0000000017a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187";
441 let tx = Transaction::zcash_deserialize(&hex::decode(serialized_tx).unwrap().to_vec()[..])
442 .unwrap();
443
444 let previous_output =
445 Output::zcash_deserialize(&hex::decode(serialized_output).unwrap().to_vec()[..])
446 .unwrap();
447
448 let verifier = super::CachedFfiTransaction::new(
449 Arc::new(tx),
450 Arc::new(vec![previous_output]),
451 NetworkUpgrade::Nu5,
452 )
453 .expect("network upgrade should be valid for tx");
454
455 verifier.is_valid(0)?;
456
457 Ok(())
458 }
459}