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