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::{
10 ffi::{c_int, c_uint, c_void},
11 sync::Arc,
12};
13
14use thiserror::Error;
15
16use zcash_script::{
17 zcash_script_error_t, zcash_script_error_t_zcash_script_ERR_OK,
18 zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE,
19 zcash_script_error_t_zcash_script_ERR_TX_INDEX,
20 zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH,
21};
22
23use zebra_chain::{
24 parameters::NetworkUpgrade,
25 transaction::{HashType, SigHasher, Transaction},
26 transparent,
27};
28
29#[derive(Copy, Clone, Debug, Error, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum Error {
33 ScriptInvalid,
35 TxDeserialize,
37 TxIndex,
39 TxSizeMismatch,
41 TxCoinbase,
43 Unknown(zcash_script_error_t),
45}
46
47impl fmt::Display for Error {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.write_str(&match self {
50 Error::ScriptInvalid => "script verification failed".to_owned(),
51 Error::TxDeserialize => "could not deserialize tx".to_owned(),
52 Error::TxIndex => "input index out of bounds".to_owned(),
53 Error::TxSizeMismatch => "tx has an invalid size".to_owned(),
54 Error::TxCoinbase => {
55 "tx is a coinbase transaction and should not be verified".to_owned()
56 }
57 Error::Unknown(e) => format!("unknown error from zcash_script: {e}"),
58 })
59 }
60}
61
62impl From<zcash_script_error_t> for Error {
63 #[allow(non_upper_case_globals)]
64 fn from(err_code: zcash_script_error_t) -> Error {
65 match err_code {
66 zcash_script_error_t_zcash_script_ERR_OK => Error::ScriptInvalid,
67 zcash_script_error_t_zcash_script_ERR_TX_DESERIALIZE => Error::TxDeserialize,
68 zcash_script_error_t_zcash_script_ERR_TX_INDEX => Error::TxIndex,
69 zcash_script_error_t_zcash_script_ERR_TX_SIZE_MISMATCH => Error::TxSizeMismatch,
70 unknown => Error::Unknown(unknown),
71 }
72 }
73}
74
75#[derive(Debug)]
78pub struct CachedFfiTransaction {
79 transaction: Arc<Transaction>,
83
84 all_previous_outputs: Vec<transparent::Output>,
87}
88
89struct SigHashContext<'a> {
91 input_index: usize,
93 sighasher: SigHasher<'a>,
95}
96
97extern "C" fn sighash(
99 sighash_out: *mut u8,
100 sighash_out_len: c_uint,
101 ctx: *const c_void,
102 script_code: *const u8,
103 script_code_len: c_uint,
104 hash_type: c_int,
105) {
106 unsafe {
111 let ctx = ctx as *const SigHashContext;
112 let script_code_vec =
113 std::slice::from_raw_parts(script_code, script_code_len as usize).to_vec();
114 let sighash = (*ctx).sighasher.sighash(
115 HashType::from_bits_truncate(hash_type as u32),
116 Some(((*ctx).input_index, script_code_vec)),
117 );
118 assert_eq!(sighash_out_len, sighash.0.len() as c_uint);
120 std::ptr::copy_nonoverlapping(sighash.0.as_ptr(), sighash_out, sighash.0.len());
121 }
122}
123
124impl CachedFfiTransaction {
125 pub fn new(
129 transaction: Arc<Transaction>,
130 all_previous_outputs: Vec<transparent::Output>,
131 ) -> Self {
132 Self {
133 transaction,
134 all_previous_outputs,
135 }
136 }
137
138 pub fn inputs(&self) -> &[transparent::Input] {
140 self.transaction.inputs()
141 }
142
143 pub fn all_previous_outputs(&self) -> &Vec<transparent::Output> {
146 &self.all_previous_outputs
147 }
148
149 #[allow(clippy::unwrap_in_result)]
152 pub fn is_valid(&self, nu: NetworkUpgrade, input_index: usize) -> Result<(), Error> {
153 let previous_output = self
154 .all_previous_outputs
155 .get(input_index)
156 .ok_or(Error::TxIndex)?
157 .clone();
158 let transparent::Output {
159 value: _,
160 lock_script,
161 } = previous_output;
162 let script_pub_key: &[u8] = lock_script.as_raw_bytes();
163
164 #[allow(clippy::useless_conversion)]
166 let n_in = input_index
167 .try_into()
168 .expect("transaction indexes are much less than c_uint::MAX");
169
170 let flags = zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_P2SH
171 | zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY;
172 #[allow(clippy::useless_conversion)]
174 let flags = flags
175 .try_into()
176 .expect("zcash_script_SCRIPT_FLAGS_VERIFY_* enum values fit in a c_uint");
177
178 let mut err = 0;
179 let lock_time = self.transaction.raw_lock_time() as i64;
180 let is_final = if self.transaction.inputs()[input_index].sequence() == u32::MAX {
181 1
182 } else {
183 0
184 };
185 let signature_script = match &self.transaction.inputs()[input_index] {
186 transparent::Input::PrevOut {
187 outpoint: _,
188 unlock_script,
189 sequence: _,
190 } => unlock_script.as_raw_bytes(),
191 transparent::Input::Coinbase { .. } => Err(Error::TxCoinbase)?,
192 };
193
194 let ctx = Box::new(SigHashContext {
195 input_index: n_in,
196 sighasher: SigHasher::new(&self.transaction, nu, &self.all_previous_outputs),
197 });
198 let ret = unsafe {
200 zcash_script::zcash_script_verify_callback(
201 (&*ctx as *const SigHashContext) as *const c_void,
202 Some(sighash),
203 lock_time,
204 is_final,
205 script_pub_key.as_ptr(),
206 script_pub_key.len() as u32,
207 signature_script.as_ptr(),
208 signature_script.len() as u32,
209 flags,
210 &mut err,
211 )
212 };
213
214 if ret == 1 {
215 Ok(())
216 } else {
217 Err(Error::from(err))
218 }
219 }
220
221 #[allow(clippy::unwrap_in_result)]
224 pub fn legacy_sigop_count(&self) -> Result<u64, Error> {
225 let mut count: u64 = 0;
226
227 for input in self.transaction.inputs() {
228 count += match input {
229 transparent::Input::PrevOut {
230 outpoint: _,
231 unlock_script,
232 sequence: _,
233 } => {
234 let script = unlock_script.as_raw_bytes();
235 unsafe {
237 zcash_script::zcash_script_legacy_sigop_count_script(
238 script.as_ptr(),
239 script.len() as u32,
240 )
241 }
242 }
243 transparent::Input::Coinbase { .. } => 0,
244 } as u64;
245 }
246
247 for output in self.transaction.outputs() {
248 let script = output.lock_script.as_raw_bytes();
249 let ret = unsafe {
251 zcash_script::zcash_script_legacy_sigop_count_script(
252 script.as_ptr(),
253 script.len() as u32,
254 )
255 };
256 count += ret as u64;
257 }
258 Ok(count)
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use hex::FromHex;
265 use std::sync::Arc;
266 use zebra_chain::{
267 parameters::NetworkUpgrade,
268 serialization::{ZcashDeserialize, ZcashDeserializeInto},
269 transaction::Transaction,
270 transparent::{self, Output},
271 };
272 use zebra_test::prelude::*;
273
274 lazy_static::lazy_static! {
275 pub static ref SCRIPT_PUBKEY: Vec<u8> = <Vec<u8>>::from_hex("76a914f47cac1e6fec195c055994e8064ffccce0044dd788ac")
276 .unwrap();
277 pub static ref SCRIPT_TX: Vec<u8> = <Vec<u8>>::from_hex("0400008085202f8901fcaf44919d4a17f6181a02a7ebe0420be6f7dad1ef86755b81d5a9567456653c010000006a473044022035224ed7276e61affd53315eca059c92876bc2df61d84277cafd7af61d4dbf4002203ed72ea497a9f6b38eb29df08e830d99e32377edb8a574b8a289024f0241d7c40121031f54b095eae066d96b2557c1f99e40e967978a5fd117465dbec0986ca74201a6feffffff020050d6dc0100000017a9141b8a9bda4b62cd0d0582b55455d0778c86f8628f870d03c812030000001976a914e4ff5512ffafe9287992a1cd177ca6e408e0300388ac62070d0095070d000000000000000000000000")
278 .expect("Block bytes are in valid hex representation");
279 }
280
281 fn verify_valid_script(
282 nu: NetworkUpgrade,
283 tx: &[u8],
284 amount: u64,
285 pubkey: &[u8],
286 ) -> Result<()> {
287 let transaction =
288 tx.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
289 let output = transparent::Output {
290 value: amount.try_into()?,
291 lock_script: transparent::Script::new(pubkey),
292 };
293 let input_index = 0;
294
295 let previous_output = vec![output];
296 let verifier = super::CachedFfiTransaction::new(transaction, previous_output);
297 verifier.is_valid(nu, input_index)?;
298
299 Ok(())
300 }
301
302 #[test]
303 fn verify_valid_script_v4() -> Result<()> {
304 let _init_guard = zebra_test::init();
305
306 verify_valid_script(
307 NetworkUpgrade::Blossom,
308 &SCRIPT_TX,
309 212 * u64::pow(10, 8),
310 &SCRIPT_PUBKEY,
311 )
312 }
313
314 #[test]
315 fn count_legacy_sigops() -> Result<()> {
316 let _init_guard = zebra_test::init();
317
318 let transaction =
319 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
320
321 let cached_tx = super::CachedFfiTransaction::new(transaction, Vec::new());
322 assert_eq!(cached_tx.legacy_sigop_count()?, 1);
323
324 Ok(())
325 }
326
327 #[test]
328 fn fail_invalid_script() -> Result<()> {
329 let _init_guard = zebra_test::init();
330
331 let transaction =
332 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
333 let coin = u64::pow(10, 8);
334 let amount = 211 * coin;
335 let output = transparent::Output {
336 value: amount.try_into()?,
337 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()[..]),
338 };
339 let input_index = 0;
340 let verifier = super::CachedFfiTransaction::new(transaction, vec![output]);
341 verifier
342 .is_valid(NetworkUpgrade::Blossom, input_index)
343 .expect_err("verification should fail");
344
345 Ok(())
346 }
347
348 #[test]
349 fn reuse_script_verifier_pass_pass() -> Result<()> {
350 let _init_guard = zebra_test::init();
351
352 let coin = u64::pow(10, 8);
353 let transaction =
354 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
355 let amount = 212 * coin;
356 let output = transparent::Output {
357 value: amount.try_into()?,
358 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
359 };
360
361 let verifier = super::CachedFfiTransaction::new(transaction, vec![output]);
362
363 let input_index = 0;
364
365 verifier.is_valid(NetworkUpgrade::Blossom, input_index)?;
366 verifier.is_valid(NetworkUpgrade::Blossom, input_index)?;
367
368 Ok(())
369 }
370
371 #[test]
372 fn reuse_script_verifier_pass_fail() -> 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(transaction, vec![output]);
385
386 let input_index = 0;
387
388 verifier.is_valid(NetworkUpgrade::Blossom, input_index)?;
389 verifier
390 .is_valid(NetworkUpgrade::Blossom, input_index + 1)
391 .expect_err("verification should fail");
392
393 Ok(())
394 }
395
396 #[test]
397 fn reuse_script_verifier_fail_pass() -> Result<()> {
398 let _init_guard = zebra_test::init();
399
400 let coin = u64::pow(10, 8);
401 let amount = 212 * coin;
402 let output = transparent::Output {
403 value: amount.try_into()?,
404 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
405 };
406 let transaction =
407 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
408
409 let verifier = super::CachedFfiTransaction::new(transaction, vec![output]);
410
411 let input_index = 0;
412
413 verifier
414 .is_valid(NetworkUpgrade::Blossom, input_index + 1)
415 .expect_err("verification should fail");
416 verifier.is_valid(NetworkUpgrade::Blossom, input_index)?;
417
418 Ok(())
419 }
420
421 #[test]
422 fn reuse_script_verifier_fail_fail() -> Result<()> {
423 let _init_guard = zebra_test::init();
424
425 let coin = u64::pow(10, 8);
426 let amount = 212 * coin;
427 let output = transparent::Output {
428 value: amount.try_into()?,
429 lock_script: transparent::Script::new(&SCRIPT_PUBKEY.clone()),
430 };
431 let transaction =
432 SCRIPT_TX.zcash_deserialize_into::<Arc<zebra_chain::transaction::Transaction>>()?;
433
434 let verifier = super::CachedFfiTransaction::new(transaction, vec![output]);
435
436 let input_index = 0;
437
438 verifier
439 .is_valid(NetworkUpgrade::Blossom, input_index + 1)
440 .expect_err("verification should fail");
441
442 verifier
443 .is_valid(NetworkUpgrade::Blossom, input_index + 1)
444 .expect_err("verification should fail");
445
446 Ok(())
447 }
448
449 #[test]
450 fn p2sh() -> Result<()> {
451 let _init_guard = zebra_test::init();
452
453 let serialized_tx = "0400008085202f8901c21354bf2305e474ad695382e68efc06e2f8b83c512496f615d153c2e00e688b00000000fdfd0000483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453aeffffffff0250954903000000001976a914a5a4e1797dac40e8ce66045d1a44c4a63d12142988acccf41c590000000017a9141c973c68b2acc6d6688eff9c7a9dd122ac1346ab8786c72400000000000000000000000000000000";
455 let serialized_output = "4065675c0000000017a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187";
456 let tx = Transaction::zcash_deserialize(&hex::decode(serialized_tx).unwrap().to_vec()[..])
457 .unwrap();
458
459 let previous_output =
460 Output::zcash_deserialize(&hex::decode(serialized_output).unwrap().to_vec()[..])
461 .unwrap();
462
463 let verifier = super::CachedFfiTransaction::new(Arc::new(tx), vec![previous_output]);
464
465 verifier.is_valid(NetworkUpgrade::Nu5, 0)?;
466
467 Ok(())
468 }
469}