zebra_script/
lib.rs

1//! Zebra script verification wrapping zcashd's zcash_script library
2#![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// We allow unsafe code, so we can call zcash_script
6#![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/// An Error type representing the error codes returned from zcash_script.
22#[derive(Clone, Debug, Error, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum Error {
25    /// script verification failed
26    ScriptInvalid,
27    /// input index out of bounds
28    TxIndex,
29    /// tx is a coinbase transaction and should not be verified
30    TxCoinbase,
31    /// unknown error from zcash_script: {0}
32    Unknown(zcash_script::Error),
33    /// transaction is invalid according to zebra_chain (not a zcash_script error)
34    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
61/// Get the interpreter according to the feature flag
62fn 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/// A preprocessed Transaction which can be used to verify scripts within said
79/// Transaction.
80#[derive(Debug)]
81pub struct CachedFfiTransaction {
82    /// The deserialized Zebra transaction.
83    ///
84    /// This field is private so that `transaction`, and `all_previous_outputs` always match.
85    transaction: Arc<Transaction>,
86
87    /// The outputs from previous transactions that match each input in the transaction
88    /// being verified.
89    all_previous_outputs: Arc<Vec<transparent::Output>>,
90
91    /// The sighasher context to use to compute sighashes.
92    sighasher: SigHasher,
93}
94
95impl CachedFfiTransaction {
96    /// Construct a `PrecomputedTransaction` from a `Transaction` and the outputs
97    /// from previous transactions that match each input in the transaction
98    /// being verified.
99    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    /// Returns the transparent inputs for this transaction.
113    pub fn inputs(&self) -> &[transparent::Input] {
114        self.transaction.inputs()
115    }
116
117    /// Returns the outputs from previous transactions that match each input in the transaction
118    /// being verified.
119    pub fn all_previous_outputs(&self) -> &Vec<transparent::Output> {
120        &self.all_previous_outputs
121    }
122
123    /// Return the sighasher being used for this transaction.
124    pub fn sighasher(&self) -> &SigHasher {
125        &self.sighasher
126    }
127
128    /// Verify if the script in the input at `input_index` of a transaction correctly spends the
129    /// matching [`transparent::Output`] it refers to.
130    #[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/// Returns the number of transparent signature operations in the
181/// transparent inputs and outputs of the given transaction.
182#[allow(clippy::unwrap_in_result)]
183pub fn legacy_sigop_count(transaction: &Transaction) -> Result<u64, Error> {
184    let mut count: u64 = 0;
185
186    // Create a dummy interpreter since these inputs are not used to count
187    // the sigops
188    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        // real tx with txid 51ded0b026f1ff56639447760bcd673b9f4e44a8afbf3af1dbaa6ca1fd241bea
439        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}