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 libzcash_script::ZcashScript;
17
18use zcash_script::script;
19use zebra_chain::{
20 parameters::NetworkUpgrade,
21 transaction::{HashType, SigHasher},
22 transparent,
23};
24
25#[derive(Clone, Debug, Error, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum Error {
29 ScriptInvalid,
31 TxIndex,
33 TxCoinbase,
35 Unknown(libzcash_script::Error),
37 TxInvalid(#[from] zebra_chain::Error),
39}
40
41impl fmt::Display for Error {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.write_str(&match self {
44 Error::ScriptInvalid => "script verification failed".to_owned(),
45 Error::TxIndex => "input index out of bounds".to_owned(),
46 Error::TxCoinbase => {
47 "tx is a coinbase transaction and should not be verified".to_owned()
48 }
49 Error::Unknown(e) => format!("unknown error from zcash_script: {e:?}"),
50 Error::TxInvalid(e) => format!("tx is invalid: {e}"),
51 })
52 }
53}
54
55impl From<libzcash_script::Error> for Error {
56 #[allow(non_upper_case_globals)]
57 fn from(err_code: libzcash_script::Error) -> Error {
58 Error::Unknown(err_code)
59 }
60}
61
62fn get_interpreter(
64 sighash: zcash_script::interpreter::SighashCalculator<'_>,
65 lock_time: u32,
66 is_final: bool,
67) -> impl ZcashScript + use<'_> {
68 #[cfg(feature = "comparison-interpreter")]
69 return libzcash_script::cxx_rust_comparison_interpreter(sighash, lock_time, is_final);
70 #[cfg(not(feature = "comparison-interpreter"))]
71 libzcash_script::CxxInterpreter {
72 sighash,
73 lock_time,
74 is_final,
75 }
76}
77
78#[derive(Debug)]
81pub struct CachedFfiTransaction {
82 transaction: Arc<zebra_chain::transaction::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<zebra_chain::transaction::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::interpreter::Flags::P2SH
144 | zcash_script::interpreter::Flags::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 script =
158 script::Raw::from_raw_parts(signature_script.to_vec(), script_pub_key.to_vec());
159
160 let calculate_sighash =
161 |script_code: &script::Code, hash_type: &zcash_script::signature::HashType| {
162 if self.transaction.version() >= 5 {
166 let valid_v5_types: &[i32] = &[0x01, 0x02, 0x03, 0x81, 0x82, 0x83];
167 if !valid_v5_types.contains(&hash_type.raw_bits()) {
168 return None;
169 }
170 }
171
172 let script_code_vec = script_code.0.clone();
173
174 if self.transaction.version() < 5 {
178 let raw_byte = hash_type.raw_bits() as u8;
179 return Some(
180 self.sighasher()
181 .sighash_v4_raw(raw_byte, Some((input_index, script_code_vec)))
182 .0,
183 );
184 }
185
186 let mut our_hash_type = match hash_type.signed_outputs() {
187 zcash_script::signature::SignedOutputs::All => HashType::ALL,
188 zcash_script::signature::SignedOutputs::Single => HashType::SINGLE,
189 zcash_script::signature::SignedOutputs::None => HashType::NONE,
190 };
191 if hash_type.anyone_can_pay() {
192 our_hash_type |= HashType::ANYONECANPAY;
193 }
194 Some(
195 self.sighasher()
196 .sighash(our_hash_type, Some((input_index, script_code_vec)))
197 .0,
198 )
199 };
200 let interpreter = get_interpreter(&calculate_sighash, lock_time, is_final);
201 interpreter
202 .verify_callback(&script, flags)
203 .map_err(|(_, e)| Error::from(e))
204 .and_then(|res| {
205 if res {
206 Ok(())
207 } else {
208 Err(Error::ScriptInvalid)
209 }
210 })
211 }
212}
213
214pub trait Sigops {
217 fn sigops(&self) -> Result<u32, libzcash_script::Error> {
220 let interpreter = get_interpreter(&|_, _| None, 0, true);
221
222 Ok(self.scripts().try_fold(0, |acc, s| {
223 interpreter
224 .legacy_sigop_count_script(&script::Code(s.to_vec()))
225 .map(|n| acc + n)
226 })?)
227 }
228
229 fn scripts(&self) -> impl Iterator<Item = &[u8]>;
233}
234
235impl Sigops for zebra_chain::transaction::Transaction {
236 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
237 self.inputs()
238 .iter()
239 .filter_map(|input| match input {
240 transparent::Input::PrevOut { unlock_script, .. } => {
241 Some(unlock_script.as_raw_bytes())
242 }
243 transparent::Input::Coinbase { .. } => None,
244 })
245 .chain(self.outputs().iter().map(|o| o.lock_script.as_raw_bytes()))
246 }
247}
248
249impl Sigops for zebra_chain::transaction::UnminedTx {
250 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
251 self.transaction.scripts()
252 }
253}
254
255impl Sigops for CachedFfiTransaction {
256 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
257 self.transaction.scripts()
258 }
259}
260
261impl Sigops for zcash_primitives::transaction::Transaction {
262 fn scripts(&self) -> impl Iterator<Item = &[u8]> {
263 self.transparent_bundle().into_iter().flat_map(|bundle| {
264 (!bundle.is_coinbase())
265 .then(|| bundle.vin.iter().map(|i| i.script_sig().0 .0.as_slice()))
266 .into_iter()
267 .flatten()
268 .chain(
269 bundle
270 .vout
271 .iter()
272 .map(|o| o.script_pubkey().0 .0.as_slice()),
273 )
274 })
275 }
276}