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
8#[cfg(test)]
9mod tests;
10
11use core::fmt;
12use std::sync::Arc;
13
14use thiserror::Error;
15
16use zcash_script::ZcashScript;
17
18use zebra_chain::{
19 parameters::NetworkUpgrade,
20 transaction::{HashType, SigHasher},
21 transparent,
22};
23
24#[derive(Clone, Debug, Error, PartialEq, Eq)]
26#[non_exhaustive]
27pub enum Error {
28 ScriptInvalid,
30 TxIndex,
32 TxCoinbase,
34 Unknown(zcash_script::Error),
36 TxInvalid(#[from] zebra_chain::Error),
38}
39
40impl fmt::Display for Error {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.write_str(&match self {
43 Error::ScriptInvalid => "script verification failed".to_owned(),
44 Error::TxIndex => "input index out of bounds".to_owned(),
45 Error::TxCoinbase => {
46 "tx is a coinbase transaction and should not be verified".to_owned()
47 }
48 Error::Unknown(e) => format!("unknown error from zcash_script: {e:?}"),
49 Error::TxInvalid(e) => format!("tx is invalid: {e}"),
50 })
51 }
52}
53
54impl From<zcash_script::Error> for Error {
55 #[allow(non_upper_case_globals)]
56 fn from(err_code: zcash_script::Error) -> Error {
57 match err_code {
58 zcash_script::Error::Ok(_) => Error::ScriptInvalid,
59 unknown => Error::Unknown(unknown),
60 }
61 }
62}
63
64fn get_interpreter(
66 sighash: zcash_script::SighashCalculator<'_>,
67 lock_time: u32,
68 is_final: bool,
69 #[allow(unused)] flags: zcash_script::VerificationFlags,
70) -> impl ZcashScript + use<'_> {
71 #[cfg(feature = "comparison-interpreter")]
72 return zcash_script::cxx_rust_comparison_interpreter(sighash, lock_time, is_final, flags);
73 #[cfg(not(feature = "comparison-interpreter"))]
74 zcash_script::CxxInterpreter {
75 sighash,
76 lock_time,
77 is_final,
78 }
79}
80
81#[derive(Debug)]
84pub struct CachedFfiTransaction {
85 transaction: Arc<zebra_chain::transaction::Transaction>,
89
90 all_previous_outputs: Arc<Vec<transparent::Output>>,
93
94 sighasher: SigHasher,
96}
97
98impl CachedFfiTransaction {
99 pub fn new(
103 transaction: Arc<zebra_chain::transaction::Transaction>,
104 all_previous_outputs: Arc<Vec<transparent::Output>>,
105 nu: NetworkUpgrade,
106 ) -> Result<Self, Error> {
107 let sighasher = transaction.sighasher(nu, all_previous_outputs.clone())?;
108 Ok(Self {
109 transaction,
110 all_previous_outputs,
111 sighasher,
112 })
113 }
114
115 pub fn inputs(&self) -> &[transparent::Input] {
117 self.transaction.inputs()
118 }
119
120 pub fn all_previous_outputs(&self) -> &Vec<transparent::Output> {
123 &self.all_previous_outputs
124 }
125
126 pub fn sighasher(&self) -> &SigHasher {
128 &self.sighasher
129 }
130
131 #[allow(clippy::unwrap_in_result)]
134 pub fn is_valid(&self, input_index: usize) -> Result<(), Error> {
135 let previous_output = self
136 .all_previous_outputs
137 .get(input_index)
138 .ok_or(Error::TxIndex)?
139 .clone();
140 let transparent::Output {
141 value: _,
142 lock_script,
143 } = previous_output;
144 let script_pub_key: &[u8] = lock_script.as_raw_bytes();
145
146 let flags = zcash_script::VerificationFlags::P2SH
147 | zcash_script::VerificationFlags::CHECKLOCKTIMEVERIFY;
148
149 let lock_time = self.transaction.raw_lock_time();
150 let is_final = self.transaction.inputs()[input_index].sequence() == u32::MAX;
151 let signature_script = match &self.transaction.inputs()[input_index] {
152 transparent::Input::PrevOut {
153 outpoint: _,
154 unlock_script,
155 sequence: _,
156 } => unlock_script.as_raw_bytes(),
157 transparent::Input::Coinbase { .. } => Err(Error::TxCoinbase)?,
158 };
159
160 let calculate_sighash = |script_code: &[u8], hash_type: zcash_script::HashType| {
161 let script_code_vec = script_code.to_vec();
162 let mut our_hash_type = match hash_type.signed_outputs {
163 zcash_script::SignedOutputs::All => HashType::ALL,
164 zcash_script::SignedOutputs::Single => HashType::SINGLE,
165 zcash_script::SignedOutputs::None => HashType::NONE,
166 };
167 if hash_type.anyone_can_pay {
168 our_hash_type |= HashType::ANYONECANPAY;
169 }
170 Some(
171 self.sighasher()
172 .sighash(our_hash_type, Some((input_index, script_code_vec)))
173 .0,
174 )
175 };
176 let interpreter = get_interpreter(&calculate_sighash, lock_time, is_final, flags);
177 interpreter
178 .verify_callback(script_pub_key, signature_script, flags)
179 .map_err(Error::from)
180 }
181}
182
183pub trait Sigops {
186 fn sigops(&self) -> Result<u32, zcash_script::Error> {
189 let interpreter = get_interpreter(
191 &|_, _| None,
192 0,
193 true,
194 zcash_script::VerificationFlags::P2SH
195 | zcash_script::VerificationFlags::CHECKLOCKTIMEVERIFY,
196 );
197
198 self.scripts().try_fold(0, |acc, s| {
199 interpreter.legacy_sigop_count_script(s).map(|n| acc + n)
200 })
201 }
202
203 fn scripts(&self) -> impl Iterator<Item = &[u8]>;
207}
208
209impl Sigops for zebra_chain::transaction::Transaction {
210 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
211 self.inputs()
212 .iter()
213 .filter_map(|input| match input {
214 transparent::Input::PrevOut { unlock_script, .. } => {
215 Some(unlock_script.as_raw_bytes())
216 }
217 transparent::Input::Coinbase { .. } => None,
218 })
219 .chain(self.outputs().iter().map(|o| o.lock_script.as_raw_bytes()))
220 }
221}
222
223impl Sigops for zebra_chain::transaction::UnminedTx {
224 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
225 self.transaction.scripts()
226 }
227}
228
229impl Sigops for CachedFfiTransaction {
230 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
231 self.transaction.scripts()
232 }
233}
234
235impl Sigops for zcash_primitives::transaction::Transaction {
236 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
237 self.transparent_bundle().into_iter().flat_map(|bundle| {
238 (!bundle.is_coinbase())
239 .then(|| bundle.vin.iter().map(|i| i.script_sig.0.as_slice()))
240 .into_iter()
241 .flatten()
242 .chain(bundle.vout.iter().map(|o| o.script_pubkey.0.as_slice()))
243 })
244 }
245}