zebra_consensus/transaction/check.rs
1//! Transaction checks.
2//!
3//! Code in this file can freely assume that no pre-V4 transactions are present.
4
5use std::{
6 borrow::Cow,
7 collections::{HashMap, HashSet},
8 hash::Hash,
9 sync::Arc,
10};
11
12use chrono::{DateTime, Utc};
13
14use zcash_script::{
15 opcode::PossiblyBad,
16 script::{self, Evaluable as _},
17 solver, Opcode,
18};
19use zebra_chain::{
20 amount::{Amount, NegativeAllowed, NonNegative},
21 block::Height,
22 orchard::Flags,
23 parameters::{Network, NetworkUpgrade},
24 primitives::zcash_note_encryption,
25 transaction::{LockTime, Transaction},
26 transparent,
27};
28
29use crate::error::TransactionError;
30
31/// Checks if the transaction's lock time allows this transaction to be included in a block.
32///
33/// Arguments:
34/// - `block_height`: the height of the mined block, or the height of the next block for mempool
35/// transactions
36/// - `block_time`: the time in the mined block header, or the median-time-past of the next block
37/// for the mempool. Optional if the lock time is a height.
38///
39/// # Panics
40///
41/// If the lock time is a time, and `block_time` is `None`.
42///
43/// # Consensus
44///
45/// > The transaction must be finalized: either its locktime must be in the past (or less
46/// > than or equal to the current block height), or all of its sequence numbers must be
47/// > 0xffffffff.
48///
49/// [`Transaction::lock_time`] validates the transparent input sequence numbers, returning [`None`]
50/// if they indicate that the transaction is finalized by them.
51/// Otherwise, this function checks that the lock time is in the past.
52///
53/// ## Mempool Consensus for Block Templates
54///
55/// > the nTime field MUST represent a time strictly greater than the median of the
56/// > timestamps of the past PoWMedianBlockSpan blocks.
57///
58/// <https://zips.z.cash/protocol/protocol.pdf#blockheader>
59///
60/// > The transaction can be added to any block whose block time is greater than the locktime.
61///
62/// <https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number>
63///
64/// If the transaction's lock time is less than the median-time-past,
65/// it will always be less than the next block's time,
66/// because the next block's time is strictly greater than the median-time-past.
67/// (That is, `lock-time < median-time-past < block-header-time`.)
68///
69/// Using `median-time-past + 1s` (the next block's mintime) would also satisfy this consensus rule,
70/// but we prefer the rule implemented by `zcashd`'s mempool:
71/// <https://github.com/zcash/zcash/blob/9e1efad2d13dca5ee094a38e6aa25b0f2464da94/src/main.cpp#L776-L784>
72pub fn lock_time_has_passed(
73 tx: &Transaction,
74 block_height: Height,
75 block_time: impl Into<Option<DateTime<Utc>>>,
76) -> Result<(), TransactionError> {
77 match tx.lock_time() {
78 Some(LockTime::Height(unlock_height)) => {
79 // > The transaction can be added to any block which has a greater height.
80 // The Bitcoin documentation is wrong or outdated here,
81 // so this code is based on the `zcashd` implementation at:
82 // https://github.com/zcash/zcash/blob/1a7c2a3b04bcad6549be6d571bfdff8af9a2c814/src/main.cpp#L722
83 if block_height > unlock_height {
84 Ok(())
85 } else {
86 Err(TransactionError::LockedUntilAfterBlockHeight(unlock_height))
87 }
88 }
89 Some(LockTime::Time(unlock_time)) => {
90 // > The transaction can be added to any block whose block time is greater than the locktime.
91 // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
92 let block_time = block_time
93 .into()
94 .expect("time must be provided if LockTime is a time");
95
96 if block_time > unlock_time {
97 Ok(())
98 } else {
99 Err(TransactionError::LockedUntilAfterBlockTime(unlock_time))
100 }
101 }
102 None => Ok(()),
103 }
104}
105
106/// Checks that the transaction has inputs and outputs.
107///
108/// # Consensus
109///
110/// > [Sapling onward] If effectiveVersion < 5, then at least one of
111/// > tx_in_count, nSpendsSapling, and nJoinSplit MUST be nonzero.
112///
113/// > [Sapling onward] If effectiveVersion < 5, then at least one of
114/// > tx_out_count, nOutputsSapling, and nJoinSplit MUST be nonzero.
115///
116/// > [NU5 onward] If effectiveVersion = 5 then this condition MUST hold:
117/// > tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1).
118///
119/// > [NU5 onward] If effectiveVersion = 5 then this condition MUST hold:
120/// > tx_out_count > 0 or nOutputsSapling > 0 or (nActionsOrchard > 0 and enableOutputsOrchard = 1).
121///
122/// > [NU6.3 onward] If effectiveVersion >= 6 then this condition MUST hold:
123/// > tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1) or (nActionsIronwood > 0 and enableSpendsIronwood = 1).
124///
125/// > [NU6.3 onward] If effectiveVersion >= 6 then this condition MUST hold:
126/// > tx_out_count > 0 or nOutputsSapling > 0 or (nActionsOrchard > 0 and enableOutputsOrchard = 1) or (nActionsIronwood > 0 and enableOutputsIronwood = 1).
127///
128/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
129///
130/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
131pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
132 if !tx.has_transparent_or_shielded_inputs() {
133 Err(TransactionError::NoInputs)
134 } else if !tx.has_transparent_or_shielded_outputs() {
135 Err(TransactionError::NoOutputs)
136 } else {
137 Ok(())
138 }
139}
140
141/// Checks that the transaction has enough orchard flags.
142///
143/// # Consensus
144///
145/// For `Transaction::V5` only:
146///
147/// > [NU5 onward] If effectiveVersion >= 5 and nActionsOrchard > 0, then at least one of enableSpendsOrchard and enableOutputsOrchard MUST be 1.
148///
149/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
150pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
151 if !tx.has_enough_orchard_flags() {
152 return Err(TransactionError::NotEnoughOrchardFlags);
153 }
154 Ok(())
155}
156
157/// Checks that a transaction with Ironwood actions has at least one Ironwood flag set.
158///
159/// # Consensus
160///
161/// > [NU6.3 onward] If effectiveVersion ≥ 6 and nActionsIronwood > 0, then at least one of
162/// > enableSpendsIronwood and enableOutputsIronwood MUST be 1.
163///
164/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
165///
166/// (No-op for transactions without Ironwood actions, i.e. all pre-v6 transactions.)
167pub fn has_enough_ironwood_flags(tx: &Transaction) -> Result<(), TransactionError> {
168 if !tx.has_enough_ironwood_flags() {
169 return Err(TransactionError::NotEnoughIronwoodFlags);
170 }
171 Ok(())
172}
173
174/// Checks that the Orchard pool does not enable cross-address transfers (NU6.3 onward).
175///
176/// # Consensus
177///
178/// > [NU6.3 onward] The `enableCrossAddress` flag of `flagsOrchard` MUST be 0.
179///
180/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
181///
182/// An Orchard bundle can never carry this flag off the wire: bit 2 is rejected at deserialization
183/// for the Orchard pool in every tx version (only the Ironwood pool permits it). So this is a
184/// defense-in-depth check that also covers an in-memory-constructed bundle.
185pub fn orchard_cross_address_disabled(tx: &Transaction) -> Result<(), TransactionError> {
186 if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
187 if orchard_shielded_data
188 .flags
189 .contains(Flags::ENABLE_CROSS_ADDRESS)
190 {
191 return Err(TransactionError::OrchardHasEnableCrossAddress);
192 }
193 }
194 Ok(())
195}
196
197/// Checks that no net new value is shielded into the Orchard pool from NU6.3 onward.
198///
199/// # Consensus
200///
201/// > [NU6.3 onward] `valueBalanceOrchard` MUST be nonnegative.
202///
203/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
204///
205/// From NU6.3, newly shielded value is routed to the Ironwood pool, so the Orchard pool is frozen
206/// against new inflows. An Orchard bundle may still spend existing notes — Orchard-to-Orchard note
207/// management nets to a zero balance and Orchard-to-transparent unshielding to a positive one — but
208/// a net-negative `valueBalanceOrchard` (which would move new value into the pool) is rejected.
209///
210/// This applies to both v5 and v6 Orchard bundles, since v5 Orchard bundles remain valid after
211/// NU6.3 (so that non-upgraded hardware wallets can keep authorizing Orchard spends).
212///
213/// (No-op for transactions without an Orchard bundle, and before NU6.3.)
214pub fn orchard_value_balance_non_negative(
215 tx: &Transaction,
216 network_upgrade: NetworkUpgrade,
217) -> Result<(), TransactionError> {
218 if network_upgrade >= NetworkUpgrade::Nu6_3 {
219 if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
220 if orchard_shielded_data.value_balance() < Amount::<NegativeAllowed>::zero() {
221 return Err(TransactionError::NegativeOrchardValueBalance);
222 }
223 }
224 }
225
226 Ok(())
227}
228
229/// Checks that a coinbase transaction has an empty Orchard component from NU6.3 onward.
230///
231/// # Consensus
232///
233/// > [NU6.3 onward] Coinbase transactions MUST have an empty Orchard component.
234///
235/// <https://zips.z.cash/zip-0229>
236///
237/// From NU6.3, newly shielded coinbase value is routed to the Ironwood pool instead, so coinbase
238/// transactions can no longer create Orchard notes. This is stronger than the pre-NU6.3 rule (which
239/// only forbids `enableSpendsOrchard`) and applies regardless of transaction version: a v5 coinbase
240/// mined at NU6.3 is constrained too, so the rule cannot be bypassed by using an older format.
241///
242/// (No-op for non-coinbase transactions, transactions without an Orchard component, and before
243/// NU6.3.)
244pub fn coinbase_orchard_component_empty(
245 tx: &Transaction,
246 network_upgrade: NetworkUpgrade,
247) -> Result<(), TransactionError> {
248 if network_upgrade >= NetworkUpgrade::Nu6_3
249 && tx.is_coinbase()
250 && tx.orchard_shielded_data().is_some()
251 {
252 return Err(TransactionError::CoinbaseHasOrchardActions);
253 }
254
255 Ok(())
256}
257
258/// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends.
259///
260/// # Consensus
261///
262/// > A coinbase transaction MUST NOT have any JoinSplit descriptions.
263///
264/// > A coinbase transaction MUST NOT have any Spend descriptions.
265///
266/// > [NU5 onward] In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0.
267///
268/// This check only counts `PrevOut` transparent inputs.
269///
270/// > [Pre-Heartwood] A coinbase transaction also MUST NOT have any Output descriptions.
271///
272/// Zebra does not validate this last rule explicitly because we checkpoint until Canopy activation.
273///
274/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
275pub fn coinbase_tx_no_prevout_joinsplit_spend(tx: &Transaction) -> Result<(), TransactionError> {
276 if tx.is_coinbase() {
277 if tx.joinsplit_count() > 0 {
278 return Err(TransactionError::CoinbaseHasJoinSplit);
279 } else if tx.sapling_spends_per_anchor().count() > 0 {
280 return Err(TransactionError::CoinbaseHasSpend);
281 }
282
283 if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
284 if orchard_shielded_data.flags.contains(Flags::ENABLE_SPENDS) {
285 return Err(TransactionError::CoinbaseHasEnableSpendsOrchard);
286 }
287 }
288
289 // The stronger NU6.3 rule that a coinbase transaction must have an *empty* Orchard component
290 // is height-gated and applies to every transaction version, so it lives in
291 // `coinbase_orchard_component_empty` (called from `check_structure_and_network_rules`).
292
293 // > [NU6.3 onward] In a version 6 coinbase transaction, the enableSpendsIronwood flag MUST
294 // > be 0.
295 //
296 // (`ironwood_shielded_data` is only ever present in v6 transactions, so this is a no-op for
297 // earlier versions.)
298 if let Some(ironwood_shielded_data) = tx.ironwood_shielded_data() {
299 if ironwood_shielded_data.flags.contains(Flags::ENABLE_SPENDS) {
300 return Err(TransactionError::CoinbaseHasEnableSpendsIronwood);
301 }
302 }
303 }
304
305 Ok(())
306}
307
308/// Check if JoinSplits in the transaction have one of its v_{pub} values equal
309/// to zero.
310///
311/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
312pub fn joinsplit_has_vpub_zero(tx: &Transaction) -> Result<(), TransactionError> {
313 let zero = Amount::<NonNegative>::zero();
314
315 let vpub_pairs = tx
316 .output_values_to_sprout()
317 .zip(tx.input_values_from_sprout());
318 for (vpub_old, vpub_new) in vpub_pairs {
319 // # Consensus
320 //
321 // > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero.
322 //
323 // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
324 if *vpub_old != zero && *vpub_new != zero {
325 return Err(TransactionError::BothVPubsNonZero);
326 }
327 }
328
329 Ok(())
330}
331
332/// Check if a transaction is adding to the sprout pool after Canopy
333/// network upgrade given a block height and a network.
334///
335/// <https://zips.z.cash/zip-0211>
336/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
337pub fn disabled_add_to_sprout_pool(
338 tx: &Transaction,
339 height: Height,
340 network: &Network,
341) -> Result<(), TransactionError> {
342 let canopy_activation_height = NetworkUpgrade::Canopy
343 .activation_height(network)
344 .expect("Canopy activation height must be present for both networks");
345
346 // # Consensus
347 //
348 // > [Canopy onward]: `vpub_old` MUST be zero.
349 //
350 // https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
351 if height >= canopy_activation_height {
352 let zero = Amount::<NonNegative>::zero();
353
354 let tx_sprout_pool = tx.output_values_to_sprout();
355 for vpub_old in tx_sprout_pool {
356 if *vpub_old != zero {
357 return Err(TransactionError::DisabledAddToSproutPool);
358 }
359 }
360 }
361
362 Ok(())
363}
364
365/// Check if a transaction has any internal spend conflicts.
366///
367/// An internal spend conflict happens if the transaction spends a UTXO more than once or if it
368/// reveals a nullifier more than once.
369///
370/// Consensus rules:
371///
372/// "each output of a particular transaction
373/// can only be used as an input once in the block chain.
374/// Any subsequent reference is a forbidden double spend-
375/// an attempt to spend the same satoshis twice."
376///
377/// <https://developer.bitcoin.org/devguide/block_chain.html#introduction>
378///
379/// A _nullifier_ *MUST NOT* repeat either within a _transaction_, or across _transactions_ in a
380/// _valid blockchain_ . *Sprout* and *Sapling* and *Orchard* _nulliers_ are considered disjoint,
381/// even if they have the same bit pattern.
382///
383/// <https://zips.z.cash/protocol/protocol.pdf#nullifierset>
384pub fn spend_conflicts(transaction: &Transaction) -> Result<(), TransactionError> {
385 use crate::error::TransactionError::*;
386
387 let transparent_outpoints = transaction.spent_outpoints().map(Cow::Owned);
388 let sprout_nullifiers = transaction.sprout_nullifiers().map(Cow::Borrowed);
389 let sapling_nullifiers = transaction.sapling_nullifiers().map(Cow::Borrowed);
390 let orchard_nullifiers = transaction.orchard_nullifiers().map(Cow::Borrowed);
391 // `ironwood_nullifiers()` yields owned `ironwood::Nullifier`s (the Ironwood-pool newtype), so
392 // they are wrapped as `Cow::Owned`. Ironwood and Orchard nullifiers are disjoint.
393 let ironwood_nullifiers = transaction.ironwood_nullifiers().map(Cow::Owned);
394
395 check_for_duplicates(transparent_outpoints, DuplicateTransparentSpend)?;
396 check_for_duplicates(sprout_nullifiers, DuplicateSproutNullifier)?;
397 check_for_duplicates(sapling_nullifiers, DuplicateSaplingNullifier)?;
398 check_for_duplicates(orchard_nullifiers, DuplicateOrchardNullifier)?;
399 check_for_duplicates(ironwood_nullifiers, DuplicateIronwoodNullifier)?;
400
401 Ok(())
402}
403
404/// Check for duplicate items in a collection.
405///
406/// Each item should be wrapped by a [`Cow`] instance so that this helper function can properly
407/// handle borrowed items and owned items.
408///
409/// If a duplicate is found, an error created by the `error_wrapper` is returned.
410fn check_for_duplicates<'t, T>(
411 items: impl IntoIterator<Item = Cow<'t, T>>,
412 error_wrapper: impl FnOnce(T) -> TransactionError,
413) -> Result<(), TransactionError>
414where
415 T: Clone + Eq + Hash + 't,
416{
417 let mut hash_set = HashSet::new();
418
419 for item in items {
420 if let Some(duplicate) = hash_set.replace(item) {
421 return Err(error_wrapper(duplicate.into_owned()));
422 }
423 }
424
425 Ok(())
426}
427
428/// Checks compatibility with [ZIP-212] shielded Sapling and Orchard coinbase output decryption
429///
430/// Pre-Heartwood: returns `Ok`.
431/// Heartwood-onward: returns `Ok` if all Sapling or Orchard outputs, if any, decrypt successfully with
432/// an all-zeroes outgoing viewing key. Returns `Err` otherwise.
433///
434/// This is used to validate coinbase transactions:
435///
436/// # Consensus
437///
438/// > [Heartwood onward] All Sapling and Orchard outputs in coinbase transactions MUST decrypt to a note
439/// > plaintext, i.e. the procedure in § 4.20.3 ‘Decryption using a Full Viewing Key (Sapling and Orchard)’
440/// > does not return ⊥, using a sequence of 32 zero bytes as the outgoing viewing key. (This implies that before
441/// > Canopy activation, Sapling outputs of a coinbase transaction MUST have note plaintext lead byte equal to
442/// > 0x01.)
443///
444/// > [Canopy onward] Any Sapling or Orchard output of a coinbase transaction decrypted to a note plaintext
445/// > according to the preceding rule MUST have note plaintext lead byte equal to 0x02. (This applies even during
446/// > the "grace period" specified in [ZIP-212].)
447///
448/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
449///
450/// [ZIP-212]: https://zips.z.cash/zip-0212#consensus-rule-change-for-coinbase-transactions
451///
452/// TODO: Currently, a 0x01 lead byte is allowed in the "grace period" mentioned since we're
453/// using `librustzcash` to implement this and it doesn't currently allow changing that behavior.
454/// <https://github.com/ZcashFoundation/zebra/issues/3027>
455pub fn coinbase_outputs_are_decryptable(
456 transaction: &Transaction,
457 network: &Network,
458 height: Height,
459) -> Result<(), TransactionError> {
460 // Do quick checks first so we can avoid an expensive tx conversion
461 // in `zcash_note_encryption::decrypts_successfully`.
462
463 // The consensus rule only applies to coinbase txs with shielded outputs.
464 if !transaction.has_shielded_outputs() {
465 return Ok(());
466 }
467
468 // The consensus rule only applies to Heartwood onward.
469 if height
470 < NetworkUpgrade::Heartwood
471 .activation_height(network)
472 .expect("Heartwood height is known")
473 {
474 return Ok(());
475 }
476
477 // The passed tx should have been be a coinbase tx.
478 if !transaction.is_coinbase() {
479 return Err(TransactionError::NotCoinbase);
480 }
481
482 if !zcash_note_encryption::decrypts_successfully(transaction, network, height) {
483 return Err(TransactionError::CoinbaseOutputsNotDecryptable);
484 }
485
486 Ok(())
487}
488
489/// Returns `Ok(())` if the expiry height for the coinbase transaction is valid
490/// according to specifications [7.1] and [ZIP-203].
491///
492/// [7.1]: https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
493/// [ZIP-203]: https://zips.z.cash/zip-0203
494pub fn coinbase_expiry_height(
495 block_height: &Height,
496 coinbase: &Transaction,
497 network: &Network,
498) -> Result<(), TransactionError> {
499 let expiry_height = coinbase.expiry_height();
500
501 if let Some(nu5_activation_height) = NetworkUpgrade::Nu5.activation_height(network) {
502 // # Consensus
503 //
504 // > [NU5 onward] The nExpiryHeight field of a coinbase transaction
505 // > MUST be equal to its block height.
506 //
507 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
508 if *block_height >= nu5_activation_height {
509 if expiry_height != Some(*block_height) {
510 return Err(TransactionError::CoinbaseExpiryBlockHeight {
511 expiry_height,
512 block_height: *block_height,
513 transaction_hash: coinbase.hash(),
514 });
515 } else {
516 return Ok(());
517 }
518 }
519 }
520
521 // # Consensus
522 //
523 // > [Overwinter to Canopy inclusive, pre-NU5] nExpiryHeight MUST be less than
524 // > or equal to 499999999.
525 //
526 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
527 validate_expiry_height_max(expiry_height, true, block_height, coinbase)
528}
529
530/// Returns `Ok(())` if the expiry height for a non coinbase transaction is
531/// valid according to specifications [7.1] and [ZIP-203].
532///
533/// [7.1]: https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
534/// [ZIP-203]: https://zips.z.cash/zip-0203
535pub fn non_coinbase_expiry_height(
536 block_height: &Height,
537 transaction: &Transaction,
538) -> Result<(), TransactionError> {
539 if transaction.is_overwintered() {
540 let expiry_height = transaction.expiry_height();
541
542 // # Consensus
543 //
544 // > [Overwinter to Canopy inclusive, pre-NU5] nExpiryHeight MUST be
545 // > less than or equal to 499999999.
546 //
547 // > [NU5 onward] nExpiryHeight MUST be less than or equal to 499999999
548 // > for non-coinbase transactions.
549 //
550 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
551 validate_expiry_height_max(expiry_height, false, block_height, transaction)?;
552
553 // # Consensus
554 //
555 // > [Overwinter onward] If a transaction is not a coinbase transaction and its
556 // > nExpiryHeight field is nonzero, then it MUST NOT be mined at a block
557 // > height greater than its nExpiryHeight.
558 //
559 // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
560 validate_expiry_height_mined(expiry_height, block_height, transaction)?;
561 }
562 Ok(())
563}
564
565/// Checks that the expiry height of a transaction does not exceed the maximal
566/// value.
567///
568/// Only the `expiry_height` parameter is used for the check. The
569/// remaining parameters are used to give details about the error when the check
570/// fails.
571fn validate_expiry_height_max(
572 expiry_height: Option<Height>,
573 is_coinbase: bool,
574 block_height: &Height,
575 transaction: &Transaction,
576) -> Result<(), TransactionError> {
577 if let Some(expiry_height) = expiry_height {
578 if expiry_height > Height::MAX_EXPIRY_HEIGHT {
579 Err(TransactionError::MaximumExpiryHeight {
580 expiry_height,
581 is_coinbase,
582 block_height: *block_height,
583 transaction_hash: transaction.hash(),
584 })?;
585 }
586 }
587
588 Ok(())
589}
590
591/// Checks that a transaction does not exceed its expiry height.
592///
593/// The `transaction` parameter is only used to give details about the error
594/// when the check fails.
595fn validate_expiry_height_mined(
596 expiry_height: Option<Height>,
597 block_height: &Height,
598 transaction: &Transaction,
599) -> Result<(), TransactionError> {
600 if let Some(expiry_height) = expiry_height {
601 if *block_height > expiry_height {
602 Err(TransactionError::ExpiredTransaction {
603 expiry_height,
604 block_height: *block_height,
605 transaction_hash: transaction.hash(),
606 })?;
607 }
608 }
609
610 Ok(())
611}
612
613/// Accepts a transaction, block height, block UTXOs, and
614/// the transaction's spent UTXOs from the chain.
615///
616/// Returns `Ok(())` if spent transparent coinbase outputs are
617/// valid for the block height, or a [`Err(TransactionError)`](TransactionError)
618pub fn tx_transparent_coinbase_spends_maturity(
619 network: &Network,
620 tx: Arc<Transaction>,
621 height: Height,
622 block_new_outputs: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
623 spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
624) -> Result<(), TransactionError> {
625 for spend in tx.spent_outpoints() {
626 let utxo = block_new_outputs
627 .get(&spend)
628 .map(|ordered_utxo| ordered_utxo.utxo.clone())
629 .or_else(|| spent_utxos.get(&spend).cloned())
630 .expect("load_spent_utxos_fut.await should return an error if a utxo is missing");
631
632 let spend_restriction = tx.coinbase_spend_restriction(network, height);
633
634 zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo)?;
635 }
636
637 Ok(())
638}
639
640/// The maximum number of signature operations in the redeem script of a standard P2SH input.
641///
642/// This is zcashd's `MAX_P2SH_SIGOPS` standardness (policy) constant:
643/// <https://github.com/zcash/zcash/blob/v6.11.0/src/policy/policy.h#L20>
644pub const MAX_P2SH_SIGOPS: u32 = 15;
645
646/// The maximum size in bytes of the scriptSig of a standard transaction input.
647///
648/// This is zcashd's `MAX_STANDARD_SCRIPTSIG_SIZE` standardness (policy) constant:
649/// <https://github.com/zcash/zcash/blob/v6.11.0/src/policy/policy.cpp#L92-L99>
650pub const MAX_STANDARD_SCRIPTSIG_SIZE: usize = 1650;
651
652/// Classify a script using the `zcash_script` solver.
653///
654/// Returns `Some(kind)` for standard script types, `None` for non-standard.
655///
656/// Mirrors the classification done by zcashd's `Solver()`.
657pub fn standard_script_kind(lock_script: &transparent::Script) -> Option<solver::ScriptKind> {
658 let code = script::Code(lock_script.as_raw_bytes().to_vec());
659 let component = code.to_component().ok()?.refine().ok()?;
660 solver::standard(&component)
661}
662
663/// Returns the expected number of scriptSig arguments for a given script kind.
664///
665/// Mirrors zcashd's `ScriptSigArgsExpected()`:
666/// <https://github.com/zcash/zcash/blob/v6.11.0/src/script/standard.cpp#L135>
667///
668/// Returns `None` for non-standard types (TX_NONSTANDARD, TX_NULL_DATA).
669pub(super) fn script_sig_args_expected(kind: &solver::ScriptKind) -> Option<usize> {
670 match kind {
671 solver::ScriptKind::PubKey { .. } => Some(1),
672 solver::ScriptKind::PubKeyHash { .. } => Some(2),
673 solver::ScriptKind::ScriptHash { .. } => Some(1),
674 solver::ScriptKind::MultiSig { required, .. } => Some(*required as usize + 1),
675 solver::ScriptKind::NullData { .. } => None,
676 }
677}
678
679/// Extract the redeemed script bytes from a P2SH scriptSig.
680///
681/// The redeemed script is the last data push in the scriptSig.
682/// Returns `None` if the scriptSig has no push operations.
683///
684/// # Precondition
685///
686/// The scriptSig should be push-only (enforced by [`mempool_standard_input_scripts`] before this
687/// function is reached). Non-push opcodes are silently ignored.
688pub(super) fn extract_p2sh_redeemed_script(unlock_script: &transparent::Script) -> Option<Vec<u8>> {
689 let code = script::Code(unlock_script.as_raw_bytes().to_vec());
690 let mut last_push_data: Option<Vec<u8>> = None;
691 for opcode in code.parse().flatten() {
692 if let PossiblyBad::Good(Opcode::PushValue(pv)) = opcode {
693 last_push_data = Some(pv.value());
694 }
695 }
696 last_push_data
697}
698
699/// Count the number of push operations in a script.
700///
701/// For a push-only script (already enforced for mempool scriptSigs),
702/// this equals the stack depth after evaluation.
703pub(super) fn count_script_push_ops(script_bytes: &[u8]) -> usize {
704 let code = script::Code(script_bytes.to_vec());
705 code.parse()
706 .filter(|op| matches!(op, Ok(PossiblyBad::Good(Opcode::PushValue(_)))))
707 .count()
708}
709
710/// Returns `true` if all of a transaction's transparent inputs are standard.
711///
712/// Mirrors zcashd's `AreInputsStandard()`:
713/// <https://github.com/zcash/zcash/blob/v6.11.0/src/policy/policy.cpp#L136>
714///
715/// For each input:
716/// 1. The spent output's scriptPubKey must be a known standard type (via the `zcash_script`
717/// solver). Non-standard scripts and OP_RETURN outputs are rejected.
718/// 2. The scriptSig stack depth must match `ScriptSigArgsExpected()`.
719/// 3. For P2SH inputs:
720/// - If the redeemed script is standard, its expected args are added to the total.
721/// - If the redeemed script is non-standard, it must have at most [`MAX_P2SH_SIGOPS`] sigops.
722///
723/// # Correctness
724///
725/// Callers must ensure `spent_outputs.len()` matches the number of transparent inputs.
726/// If the lengths differ, `false` is returned.
727pub fn are_inputs_standard(tx: &Transaction, spent_outputs: &[transparent::Output]) -> bool {
728 if tx.inputs().len() != spent_outputs.len() {
729 return false;
730 }
731 for (input, spent_output) in tx.inputs().iter().zip(spent_outputs.iter()) {
732 let unlock_script = match input {
733 transparent::Input::PrevOut { unlock_script, .. } => unlock_script,
734 transparent::Input::Coinbase { .. } => continue,
735 };
736
737 // Step 1: Classify the spent output's scriptPubKey via the zcash_script solver.
738 let script_kind = match standard_script_kind(&spent_output.lock_script) {
739 Some(kind) => kind,
740 None => return false,
741 };
742
743 // Step 2: Get expected number of scriptSig arguments.
744 // Returns None for TX_NONSTANDARD and TX_NULL_DATA.
745 let mut n_args_expected = match script_sig_args_expected(&script_kind) {
746 Some(n) => n,
747 None => return false,
748 };
749
750 // Step 3: Count actual push operations in scriptSig.
751 // For push-only scripts (enforced before this function), this equals the stack depth.
752 let stack_size = count_script_push_ops(unlock_script.as_raw_bytes());
753
754 // Step 4: P2SH-specific checks.
755 if matches!(script_kind, solver::ScriptKind::ScriptHash { .. }) {
756 let Some(redeemed_bytes) = extract_p2sh_redeemed_script(unlock_script) else {
757 return false;
758 };
759
760 let redeemed_code = script::Code(redeemed_bytes);
761
762 // Classify the redeemed script using the zcash_script solver.
763 let redeemed_kind = {
764 let component = redeemed_code
765 .to_component()
766 .ok()
767 .and_then(|c| c.refine().ok());
768 component.and_then(|c| solver::standard(&c))
769 };
770
771 match redeemed_kind {
772 Some(ref inner_kind) => {
773 // Standard redeemed script: add its expected args.
774 match script_sig_args_expected(inner_kind) {
775 Some(inner) => n_args_expected += inner,
776 None => return false,
777 }
778 }
779 None => {
780 // Non-standard redeemed script: accept if sigops <= limit.
781 // Matches zcashd: "Any other Script with less than 15 sigops OK:
782 // ... extra data left on the stack after execution is OK, too"
783 let sigops = redeemed_code.sig_op_count(true);
784 if sigops > MAX_P2SH_SIGOPS {
785 return false;
786 }
787
788 // This input is acceptable; move on to the next input.
789 continue;
790 }
791 }
792 }
793
794 // Step 5: Reject if scriptSig has wrong number of stack items.
795 if stack_size != n_args_expected {
796 return false;
797 }
798 }
799 true
800}
801
802/// Standardness (policy) checks on a mempool transaction's transparent input scripts, applied
803/// *before* the transaction is dispatched to script verification. The goal is to avoid the
804/// expensive verification for non-standard transactions which would be rejected anyway
805/// by `Storage::reject_if_non_standard_tx()`; this is a subset of the checks
806/// in that function.
807///
808/// `spent_outputs` must contain the output spent by each of the transaction's transparent inputs,
809/// in input order.
810///
811/// # Correctness
812///
813/// `spent_outputs.len()` must equal the number of transparent inputs in `tx`: if the lengths
814/// differ, `zip()` silently truncates, and some inputs are not checked.
815pub fn mempool_standard_input_scripts(
816 tx: &Transaction,
817 spent_outputs: &[transparent::Output],
818) -> Result<(), TransactionError> {
819 if tx.inputs().len() != spent_outputs.len() {
820 return Err(TransactionError::Other(format!(
821 "spent_outputs must align with transaction inputs for non-coinbase txs: inputs={}, spent_outputs={}",
822 tx.inputs().len(),
823 spent_outputs.len(),
824 )));
825 }
826
827 for (input_index, input) in tx.inputs().iter().enumerate() {
828 let unlock_script = match input {
829 transparent::Input::PrevOut { unlock_script, .. } => unlock_script,
830 transparent::Input::Coinbase { .. } => continue,
831 };
832
833 // Rule: the scriptSig must be within the standard size limit.
834 let size = unlock_script.as_raw_bytes().len();
835 if size > MAX_STANDARD_SCRIPTSIG_SIZE {
836 return Err(TransactionError::NonStandardScriptSigSize { input_index, size });
837 }
838
839 // Rule: the scriptSig must be push-only.
840 if !script::Code(unlock_script.as_raw_bytes().to_vec()).is_push_only() {
841 return Err(TransactionError::NonStandardScriptSigNotPushOnly { input_index });
842 }
843 }
844
845 // Rule: all transparent inputs must pass `AreInputsStandard()` checks:
846 // https://github.com/zcash/zcash/blob/v6.11.0/src/policy/policy.cpp#L137
847 if !are_inputs_standard(tx, spent_outputs) {
848 return Err(TransactionError::NonStandardInputs);
849 }
850
851 Ok(())
852}
853
854/// Checks the `nConsensusBranchId` field.
855///
856/// # Consensus
857///
858/// ## [7.1.2 Transaction Consensus Rules]
859///
860/// > [**NU5** onward] If `effectiveVersion` ≥ 5, the `nConsensusBranchId` field **MUST** match the
861/// > consensus branch ID used for SIGHASH transaction hashes, as specified in [ZIP-244].
862///
863/// ### Notes
864///
865/// - When deserializing transactions, Zebra converts the `nConsensusBranchId` into
866/// [`NetworkUpgrade`].
867///
868/// - The values returned by [`Transaction::version`] match `effectiveVersion` so we use them in
869/// place of `effectiveVersion`. More details in [`Transaction::version`].
870///
871/// [ZIP-244]: <https://zips.z.cash/zip-0244>
872/// [7.1.2 Transaction Consensus Rules]: <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
873pub fn consensus_branch_id(
874 tx: &Transaction,
875 height: Height,
876 network: &Network,
877) -> Result<(), TransactionError> {
878 let current_nu = NetworkUpgrade::current(network, height);
879
880 if current_nu < NetworkUpgrade::Nu5 || tx.version() < 5 {
881 return Ok(());
882 }
883
884 let Some(tx_nu) = tx.network_upgrade() else {
885 return Err(TransactionError::MissingConsensusBranchId);
886 };
887
888 if tx_nu != current_nu {
889 return Err(TransactionError::WrongConsensusBranchId);
890 }
891
892 Ok(())
893}