zebra_chain/
transparent.rs

1//! Transparent-related (Bitcoin-inherited) functionality.
2
3mod address;
4mod keys;
5mod opcodes;
6mod script;
7mod serialize;
8mod utxo;
9
10use std::{collections::HashMap, fmt, iter, ops::AddAssign};
11
12use zcash_transparent::{address::TransparentAddress, bundle::TxOut};
13
14use crate::{
15    amount::{Amount, NonNegative},
16    block,
17    parameters::Network,
18    serialization::ZcashSerialize,
19    transaction,
20};
21
22pub use address::Address;
23pub use script::Script;
24pub use serialize::{GENESIS_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN};
25pub use utxo::{
26    new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
27    CoinbaseSpendRestriction, OrderedUtxo, Utxo,
28};
29
30#[cfg(any(test, feature = "proptest-impl"))]
31pub use utxo::{
32    new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
33};
34
35#[cfg(any(test, feature = "proptest-impl"))]
36mod arbitrary;
37
38#[cfg(test)]
39mod tests;
40
41#[cfg(any(test, feature = "proptest-impl"))]
42use proptest_derive::Arbitrary;
43
44/// The maturity threshold for transparent coinbase outputs.
45///
46/// "A transaction MUST NOT spend a transparent output of a coinbase transaction
47/// from a block less than 100 blocks prior to the spend. Note that transparent
48/// outputs of coinbase transactions include Founders' Reward outputs and
49/// transparent Funding Stream outputs."
50/// [7.1](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus)
51//
52// TODO: change type to HeightDiff
53pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
54
55/// Extra coinbase data that identifies some coinbase transactions generated by Zebra.
56/// <https://emojipedia.org/zebra/>
57//
58// # Note
59//
60// rust-analyzer will crash in some editors when moving over an actual Zebra emoji,
61// so we encode it here. This is a known issue in emacs-lsp and other lsp implementations:
62// - https://github.com/rust-lang/rust-analyzer/issues/9121
63// - https://github.com/emacs-lsp/lsp-mode/issues/2080
64// - https://github.com/rust-lang/rust-analyzer/issues/13709
65pub const EXTRA_ZEBRA_COINBASE_DATA: &str = "z\u{1F993}";
66
67/// The rate used to calculate the dust threshold, in zatoshis per 1000 bytes.
68///
69/// History: <https://github.com/zcash/zcash/blob/v6.10.0/src/policy/policy.h#L43-L89>
70pub const ONE_THIRD_DUST_THRESHOLD_RATE: u32 = 100;
71
72/// Arbitrary data inserted by miners into a coinbase transaction.
73//
74// TODO: rename to ExtraCoinbaseData, because height is also part of the coinbase data?
75#[derive(Clone, Eq, PartialEq)]
76#[cfg_attr(
77    any(test, feature = "proptest-impl", feature = "elasticsearch"),
78    derive(Serialize)
79)]
80pub struct CoinbaseData(
81    /// Invariant: this vec, together with the coinbase height, must be less than
82    /// 100 bytes. We enforce this by only constructing CoinbaseData fields by
83    /// parsing blocks with 100-byte data fields, and checking newly created
84    /// CoinbaseData lengths in the transaction builder.
85    pub(super) Vec<u8>,
86);
87
88#[cfg(any(test, feature = "proptest-impl"))]
89impl CoinbaseData {
90    /// Create a new `CoinbaseData` containing `data`.
91    ///
92    /// Only for use in tests.
93    pub fn new(data: Vec<u8>) -> CoinbaseData {
94        CoinbaseData(data)
95    }
96}
97
98impl AsRef<[u8]> for CoinbaseData {
99    fn as_ref(&self) -> &[u8] {
100        self.0.as_ref()
101    }
102}
103
104impl std::fmt::Debug for CoinbaseData {
105    #[allow(clippy::unwrap_in_result)]
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        let escaped = String::from_utf8(
108            self.0
109                .iter()
110                .cloned()
111                .flat_map(std::ascii::escape_default)
112                .collect(),
113        )
114        .expect("ascii::escape_default produces utf8");
115        f.debug_tuple("CoinbaseData").field(&escaped).finish()
116    }
117}
118
119/// OutPoint
120///
121/// A particular transaction output reference.
122#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
123#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
124#[cfg_attr(
125    any(test, feature = "proptest-impl", feature = "elasticsearch"),
126    derive(Serialize)
127)]
128pub struct OutPoint {
129    /// References the transaction that contains the UTXO being spent.
130    ///
131    /// # Correctness
132    ///
133    /// Consensus-critical serialization uses [`ZcashSerialize`].
134    /// [`serde`]-based hex serialization must only be used for testing.
135    #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))]
136    pub hash: transaction::Hash,
137
138    /// Identifies which UTXO from that transaction is referenced; the
139    /// first output is 0, etc.
140    // TODO: Use OutputIndex here
141    pub index: u32,
142}
143
144impl OutPoint {
145    /// Returns a new [`OutPoint`] from an in-memory output `index`.
146    ///
147    /// # Panics
148    ///
149    /// If `index` doesn't fit in a [`u32`].
150    pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
151        OutPoint {
152            hash,
153            index: index
154                .try_into()
155                .expect("valid in-memory output indexes fit in a u32"),
156        }
157    }
158}
159
160/// A transparent input to a transaction.
161#[derive(Clone, Debug, Eq, PartialEq)]
162#[cfg_attr(
163    any(test, feature = "proptest-impl", feature = "elasticsearch"),
164    derive(Serialize)
165)]
166pub enum Input {
167    /// A reference to an output of a previous transaction.
168    PrevOut {
169        /// The previous output transaction reference.
170        outpoint: OutPoint,
171        /// The script that authorizes spending `outpoint`.
172        unlock_script: Script,
173        /// The sequence number for the output.
174        sequence: u32,
175    },
176    /// New coins created by the block reward.
177    Coinbase {
178        /// The height of this block.
179        height: block::Height,
180        /// Free data inserted by miners after the block height.
181        data: CoinbaseData,
182        /// The sequence number for the output.
183        sequence: u32,
184    },
185}
186
187impl fmt::Display for Input {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        match self {
190            Input::PrevOut {
191                outpoint,
192                unlock_script,
193                ..
194            } => {
195                let mut fmter = f.debug_struct("transparent::Input::PrevOut");
196
197                fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
198                fmter.field("outpoint", outpoint);
199
200                fmter.finish()
201            }
202            Input::Coinbase { height, data, .. } => {
203                let mut fmter = f.debug_struct("transparent::Input::Coinbase");
204
205                fmter.field("height", height);
206                fmter.field("data_len", &data.0.len());
207
208                fmter.finish()
209            }
210        }
211    }
212}
213
214impl Input {
215    /// Returns a new coinbase input for `height` with optional `data` and `sequence`.
216    ///
217    /// # Consensus
218    ///
219    /// The combined serialized size of `height` and `data` can be at most 100 bytes.
220    ///
221    /// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
222    ///
223    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
224    ///
225    /// # Panics
226    ///
227    /// If the coinbase data is greater than [`MAX_COINBASE_DATA_LEN`].
228    pub fn new_coinbase(height: block::Height, data: Vec<u8>, sequence: Option<u32>) -> Input {
229        // `zcashd` includes an extra byte after the coinbase height in the coinbase data. We do
230        // that only if the data is empty to stay compliant with the following consensus rule:
231        //
232        // > A coinbase transaction script MUST have length in {2 .. 100} bytes.
233        //
234        // ## Rationale
235        //
236        // Coinbase heights < 17 are serialized as a single byte, and if there is no coinbase data,
237        // the script of a coinbase tx with such a height would consist only of this single byte,
238        // violating the consensus rule.
239        let data = if data.is_empty() { vec![0] } else { data };
240        let data_limit = MAX_COINBASE_DATA_LEN - height.coinbase_zcash_serialized_size();
241
242        assert!(
243            data.len() <= data_limit,
244            "miner data has {} bytes, which exceeds the limit of {data_limit} bytes",
245            data.len(),
246        );
247
248        Input::Coinbase {
249            height,
250            data: CoinbaseData(data),
251            // If the caller does not specify the sequence number, use a sequence number that
252            // activates the LockTime.
253            sequence: sequence.unwrap_or(0),
254        }
255    }
256
257    /// Returns the extra coinbase data in this input, if it is an [`Input::Coinbase`].
258    pub fn extra_coinbase_data(&self) -> Option<&CoinbaseData> {
259        match self {
260            Input::PrevOut { .. } => None,
261            Input::Coinbase { data, .. } => Some(data),
262        }
263    }
264
265    /// Returns the full coinbase script (the encoded height along with the
266    /// extra data) if this is an [`Input::Coinbase`]. Also returns `None` if
267    /// the coinbase is for the genesis block but does not match the expected
268    /// genesis coinbase data.
269    pub fn coinbase_script(&self) -> Option<Vec<u8>> {
270        match self {
271            Input::PrevOut { .. } => None,
272            Input::Coinbase { height, data, .. } => {
273                let mut height_and_data = Vec::new();
274                serialize::write_coinbase_height(*height, data, &mut height_and_data).ok()?;
275                height_and_data.extend(&data.0);
276                Some(height_and_data)
277            }
278        }
279    }
280
281    /// Returns the input's sequence number.
282    pub fn sequence(&self) -> u32 {
283        match self {
284            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
285        }
286    }
287
288    /// Sets the input's sequence number.
289    ///
290    /// Only for use in tests.
291    #[cfg(any(test, feature = "proptest-impl"))]
292    pub fn set_sequence(&mut self, new_sequence: u32) {
293        match self {
294            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
295                *sequence = new_sequence
296            }
297        }
298    }
299
300    /// If this is a [`Input::PrevOut`] input, returns this input's
301    /// [`OutPoint`]. Otherwise, returns `None`.
302    pub fn outpoint(&self) -> Option<OutPoint> {
303        if let Input::PrevOut { outpoint, .. } = self {
304            Some(*outpoint)
305        } else {
306            None
307        }
308    }
309
310    /// Set this input's [`OutPoint`].
311    ///
312    /// Should only be called on [`Input::PrevOut`] inputs.
313    ///
314    /// # Panics
315    ///
316    /// If `self` is a coinbase input.
317    #[cfg(any(test, feature = "proptest-impl"))]
318    pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
319        if let Input::PrevOut {
320            ref mut outpoint, ..
321        } = self
322        {
323            *outpoint = new_outpoint;
324        } else {
325            unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
326        }
327    }
328
329    /// Get the value spent by this input, by looking up its [`OutPoint`] in `outputs`.
330    /// See [`Self::value`] for details.
331    ///
332    /// # Panics
333    ///
334    /// If the provided [`Output`]s don't have this input's [`OutPoint`].
335    pub(crate) fn value_from_outputs(
336        &self,
337        outputs: &HashMap<OutPoint, Output>,
338    ) -> Amount<NonNegative> {
339        match self {
340            Input::PrevOut { outpoint, .. } => {
341                outputs
342                    .get(outpoint)
343                    .unwrap_or_else(|| {
344                        panic!(
345                            "provided Outputs (length {:?}) don't have spent {:?}",
346                            outputs.len(),
347                            outpoint
348                        )
349                    })
350                    .value
351            }
352            Input::Coinbase { .. } => Amount::zero(),
353        }
354    }
355
356    /// Get the value spent by this input, by looking up its [`OutPoint`] in
357    /// [`Utxo`]s.
358    ///
359    /// This amount is added to the transaction value pool by this input.
360    ///
361    /// # Panics
362    ///
363    /// If the provided [`Utxo`]s don't have this input's [`OutPoint`].
364    pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
365        if let Some(outpoint) = self.outpoint() {
366            // look up the specific Output and convert it to the expected format
367            let output = utxos
368                .get(&outpoint)
369                .expect("provided Utxos don't have spent OutPoint")
370                .output
371                .clone();
372            self.value_from_outputs(&iter::once((outpoint, output)).collect())
373        } else {
374            // coinbase inputs don't need any UTXOs
375            self.value_from_outputs(&HashMap::new())
376        }
377    }
378
379    /// Get the value spent by this input, by looking up its [`OutPoint`] in
380    /// [`OrderedUtxo`]s.
381    ///
382    /// See [`Self::value`] for details.
383    ///
384    /// # Panics
385    ///
386    /// If the provided [`OrderedUtxo`]s don't have this input's [`OutPoint`].
387    pub fn value_from_ordered_utxos(
388        &self,
389        ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
390    ) -> Amount<NonNegative> {
391        if let Some(outpoint) = self.outpoint() {
392            // look up the specific Output and convert it to the expected format
393            let output = ordered_utxos
394                .get(&outpoint)
395                .expect("provided Utxos don't have spent OutPoint")
396                .utxo
397                .output
398                .clone();
399            self.value_from_outputs(&iter::once((outpoint, output)).collect())
400        } else {
401            // coinbase inputs don't need any UTXOs
402            self.value_from_outputs(&HashMap::new())
403        }
404    }
405}
406
407/// A transparent output from a transaction.
408///
409/// The most fundamental building block of a transaction is a
410/// transaction output -- the ZEC you own in your "wallet" is in
411/// fact a subset of unspent transaction outputs (or "UTXO"s) of the
412/// global UTXO set.
413///
414/// UTXOs are indivisible, discrete units of value which can only be
415/// consumed in their entirety. Thus, if I want to send you 1 ZEC and
416/// I only own one UTXO worth 2 ZEC, I would construct a transaction
417/// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
418/// (just like receiving change).
419#[derive(Clone, Debug, Eq, PartialEq, Hash)]
420#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
421#[cfg_attr(
422    any(test, feature = "proptest-impl", feature = "elasticsearch"),
423    derive(Serialize)
424)]
425pub struct Output {
426    /// Transaction value.
427    // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
428    pub value: Amount<NonNegative>,
429
430    /// The lock script defines the conditions under which this output can be spent.
431    pub lock_script: Script,
432}
433
434impl Output {
435    /// Returns a new coinbase output that pays `amount` using `lock_script`.
436    pub fn new_coinbase(amount: Amount<NonNegative>, lock_script: Script) -> Output {
437        Output {
438            value: amount,
439            lock_script,
440        }
441    }
442
443    /// Get the value contained in this output.
444    /// This amount is subtracted from the transaction value pool by this output.
445    pub fn value(&self) -> Amount<NonNegative> {
446        self.value
447    }
448
449    /// Return the destination address from a transparent output.
450    ///
451    /// Returns None if the address type is not valid or unrecognized.
452    pub fn address(&self, net: &Network) -> Option<Address> {
453        match TxOut::try_from(self).ok()?.recipient_address()? {
454            TransparentAddress::PublicKeyHash(pkh) => {
455                Some(Address::from_pub_key_hash(net.t_addr_kind(), pkh))
456            }
457            TransparentAddress::ScriptHash(sh) => {
458                Some(Address::from_script_hash(net.t_addr_kind(), sh))
459            }
460        }
461    }
462
463    /// Returns true if this output is considered dust.
464    pub fn is_dust(&self) -> bool {
465        let output_size: u32 = self
466            .zcash_serialized_size()
467            .try_into()
468            .expect("output size should fit in u32");
469
470        // https://github.com/zcash/zcash/blob/v6.10.0/src/primitives/transaction.cpp#L75-L80
471        let threshold = 3 * (ONE_THIRD_DUST_THRESHOLD_RATE * (output_size + 148) / 1000);
472
473        // https://github.com/zcash/zcash/blob/v6.10.0/src/primitives/transaction.h#L396-L399
474        self.value.zatoshis() < threshold as i64
475    }
476}
477
478/// A transparent output's index in its transaction.
479#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
480pub struct OutputIndex(u32);
481
482impl OutputIndex {
483    /// Create a transparent output index from the Zcash consensus integer type.
484    ///
485    /// `u32` is also the inner type.
486    pub const fn from_index(output_index: u32) -> OutputIndex {
487        OutputIndex(output_index)
488    }
489
490    /// Returns this index as the inner type.
491    pub const fn index(&self) -> u32 {
492        self.0
493    }
494
495    /// Create a transparent output index from `usize`.
496    #[allow(dead_code)]
497    pub fn from_usize(output_index: usize) -> OutputIndex {
498        OutputIndex(
499            output_index
500                .try_into()
501                .expect("the maximum valid index fits in the inner type"),
502        )
503    }
504
505    /// Return this index as `usize`.
506    #[allow(dead_code)]
507    pub fn as_usize(&self) -> usize {
508        self.0
509            .try_into()
510            .expect("the maximum valid index fits in usize")
511    }
512
513    /// Create a transparent output index from `u64`.
514    #[allow(dead_code)]
515    pub fn from_u64(output_index: u64) -> OutputIndex {
516        OutputIndex(
517            output_index
518                .try_into()
519                .expect("the maximum u64 index fits in the inner type"),
520        )
521    }
522
523    /// Return this index as `u64`.
524    #[allow(dead_code)]
525    pub fn as_u64(&self) -> u64 {
526        self.0.into()
527    }
528}
529
530impl AddAssign<u32> for OutputIndex {
531    fn add_assign(&mut self, rhs: u32) {
532        self.0 += rhs
533    }
534}