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};
11
12pub use address::Address;
13use zcash_transparent::{address::TransparentAddress, bundle::TxOut};
14
15use crate::{
16    amount::{Amount, NonNegative},
17    block,
18    parameters::Network,
19    transaction,
20};
21
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    pub index: u32,
136}
137
138impl OutPoint {
139    /// Returns a new [`OutPoint`] from an in-memory output `index`.
140    ///
141    /// # Panics
142    ///
143    /// If `index` doesn't fit in a [`u32`].
144    pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
145        OutPoint {
146            hash,
147            index: index
148                .try_into()
149                .expect("valid in-memory output indexes fit in a u32"),
150        }
151    }
152}
153
154/// A transparent input to a transaction.
155#[derive(Clone, Debug, Eq, PartialEq)]
156#[cfg_attr(
157    any(test, feature = "proptest-impl", feature = "elasticsearch"),
158    derive(Serialize)
159)]
160pub enum Input {
161    /// A reference to an output of a previous transaction.
162    PrevOut {
163        /// The previous output transaction reference.
164        outpoint: OutPoint,
165        /// The script that authorizes spending `outpoint`.
166        unlock_script: Script,
167        /// The sequence number for the output.
168        sequence: u32,
169    },
170    /// New coins created by the block reward.
171    Coinbase {
172        /// The height of this block.
173        height: block::Height,
174        /// Free data inserted by miners after the block height.
175        data: CoinbaseData,
176        /// The sequence number for the output.
177        sequence: u32,
178    },
179}
180
181impl fmt::Display for Input {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            Input::PrevOut {
185                outpoint,
186                unlock_script,
187                ..
188            } => {
189                let mut fmter = f.debug_struct("transparent::Input::PrevOut");
190
191                fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
192                fmter.field("outpoint", outpoint);
193
194                fmter.finish()
195            }
196            Input::Coinbase { height, data, .. } => {
197                let mut fmter = f.debug_struct("transparent::Input::Coinbase");
198
199                fmter.field("height", height);
200                fmter.field("data_len", &data.0.len());
201
202                fmter.finish()
203            }
204        }
205    }
206}
207
208impl Input {
209    /// Returns a new coinbase input for `height` with optional `data` and `sequence`.
210    ///
211    /// # Consensus
212    ///
213    /// The combined serialized size of `height` and `data` can be at most 100 bytes.
214    ///
215    /// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
216    ///
217    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
218    ///
219    /// # Panics
220    ///
221    /// If the coinbase data is greater than [`MAX_COINBASE_DATA_LEN`].
222    pub fn new_coinbase(
223        height: block::Height,
224        data: Option<Vec<u8>>,
225        sequence: Option<u32>,
226    ) -> Input {
227        // "No extra coinbase data" is the default.
228        let data = data.unwrap_or_default();
229        let height_size = height.coinbase_zcash_serialized_size();
230
231        assert!(
232            data.len() + height_size <= MAX_COINBASE_DATA_LEN,
233            "invalid coinbase data: extra data {} bytes + height {height_size} bytes \
234             must be {} or less",
235            data.len(),
236            MAX_COINBASE_DATA_LEN,
237        );
238
239        Input::Coinbase {
240            height,
241            data: CoinbaseData(data),
242
243            // If the caller does not specify the sequence number,
244            // use a sequence number that activates the LockTime.
245            sequence: sequence.unwrap_or(0),
246        }
247    }
248
249    /// Returns the extra coinbase data in this input, if it is an [`Input::Coinbase`].
250    pub fn extra_coinbase_data(&self) -> Option<&CoinbaseData> {
251        match self {
252            Input::PrevOut { .. } => None,
253            Input::Coinbase { data, .. } => Some(data),
254        }
255    }
256
257    /// Returns the full coinbase script (the encoded height along with the
258    /// extra data) if this is an [`Input::Coinbase`]. Also returns `None` if
259    /// the coinbase is for the genesis block but does not match the expected
260    /// genesis coinbase data.
261    pub fn coinbase_script(&self) -> Option<Vec<u8>> {
262        match self {
263            Input::PrevOut { .. } => None,
264            Input::Coinbase { height, data, .. } => {
265                let mut height_and_data = Vec::new();
266                serialize::write_coinbase_height(*height, data, &mut height_and_data).ok()?;
267                height_and_data.extend(&data.0);
268                Some(height_and_data)
269            }
270        }
271    }
272
273    /// Returns the input's sequence number.
274    pub fn sequence(&self) -> u32 {
275        match self {
276            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
277        }
278    }
279
280    /// Sets the input's sequence number.
281    ///
282    /// Only for use in tests.
283    #[cfg(any(test, feature = "proptest-impl"))]
284    pub fn set_sequence(&mut self, new_sequence: u32) {
285        match self {
286            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
287                *sequence = new_sequence
288            }
289        }
290    }
291
292    /// If this is a [`Input::PrevOut`] input, returns this input's
293    /// [`OutPoint`]. Otherwise, returns `None`.
294    pub fn outpoint(&self) -> Option<OutPoint> {
295        if let Input::PrevOut { outpoint, .. } = self {
296            Some(*outpoint)
297        } else {
298            None
299        }
300    }
301
302    /// Set this input's [`OutPoint`].
303    ///
304    /// Should only be called on [`Input::PrevOut`] inputs.
305    ///
306    /// # Panics
307    ///
308    /// If `self` is a coinbase input.
309    #[cfg(any(test, feature = "proptest-impl"))]
310    pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
311        if let Input::PrevOut {
312            ref mut outpoint, ..
313        } = self
314        {
315            *outpoint = new_outpoint;
316        } else {
317            unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
318        }
319    }
320
321    /// Get the value spent by this input, by looking up its [`OutPoint`] in `outputs`.
322    /// See [`Self::value`] for details.
323    ///
324    /// # Panics
325    ///
326    /// If the provided [`Output`]s don't have this input's [`OutPoint`].
327    pub(crate) fn value_from_outputs(
328        &self,
329        outputs: &HashMap<OutPoint, Output>,
330    ) -> Amount<NonNegative> {
331        match self {
332            Input::PrevOut { outpoint, .. } => {
333                outputs
334                    .get(outpoint)
335                    .unwrap_or_else(|| {
336                        panic!(
337                            "provided Outputs (length {:?}) don't have spent {:?}",
338                            outputs.len(),
339                            outpoint
340                        )
341                    })
342                    .value
343            }
344            Input::Coinbase { .. } => Amount::zero(),
345        }
346    }
347
348    /// Get the value spent by this input, by looking up its [`OutPoint`] in
349    /// [`Utxo`]s.
350    ///
351    /// This amount is added to the transaction value pool by this input.
352    ///
353    /// # Panics
354    ///
355    /// If the provided [`Utxo`]s don't have this input's [`OutPoint`].
356    pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
357        if let Some(outpoint) = self.outpoint() {
358            // look up the specific Output and convert it to the expected format
359            let output = utxos
360                .get(&outpoint)
361                .expect("provided Utxos don't have spent OutPoint")
362                .output
363                .clone();
364            self.value_from_outputs(&iter::once((outpoint, output)).collect())
365        } else {
366            // coinbase inputs don't need any UTXOs
367            self.value_from_outputs(&HashMap::new())
368        }
369    }
370
371    /// Get the value spent by this input, by looking up its [`OutPoint`] in
372    /// [`OrderedUtxo`]s.
373    ///
374    /// See [`Self::value`] for details.
375    ///
376    /// # Panics
377    ///
378    /// If the provided [`OrderedUtxo`]s don't have this input's [`OutPoint`].
379    pub fn value_from_ordered_utxos(
380        &self,
381        ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
382    ) -> Amount<NonNegative> {
383        if let Some(outpoint) = self.outpoint() {
384            // look up the specific Output and convert it to the expected format
385            let output = ordered_utxos
386                .get(&outpoint)
387                .expect("provided Utxos don't have spent OutPoint")
388                .utxo
389                .output
390                .clone();
391            self.value_from_outputs(&iter::once((outpoint, output)).collect())
392        } else {
393            // coinbase inputs don't need any UTXOs
394            self.value_from_outputs(&HashMap::new())
395        }
396    }
397}
398
399/// A transparent output from a transaction.
400///
401/// The most fundamental building block of a transaction is a
402/// transaction output -- the ZEC you own in your "wallet" is in
403/// fact a subset of unspent transaction outputs (or "UTXO"s) of the
404/// global UTXO set.
405///
406/// UTXOs are indivisible, discrete units of value which can only be
407/// consumed in their entirety. Thus, if I want to send you 1 ZEC and
408/// I only own one UTXO worth 2 ZEC, I would construct a transaction
409/// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
410/// (just like receiving change).
411#[derive(Clone, Debug, Eq, PartialEq, Hash)]
412#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
413#[cfg_attr(
414    any(test, feature = "proptest-impl", feature = "elasticsearch"),
415    derive(Serialize)
416)]
417pub struct Output {
418    /// Transaction value.
419    // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
420    pub value: Amount<NonNegative>,
421
422    /// The lock script defines the conditions under which this output can be spent.
423    pub lock_script: Script,
424}
425
426impl Output {
427    /// Returns a new coinbase output that pays `amount` using `lock_script`.
428    pub fn new_coinbase(amount: Amount<NonNegative>, lock_script: Script) -> Output {
429        Output {
430            value: amount,
431            lock_script,
432        }
433    }
434
435    /// Get the value contained in this output.
436    /// This amount is subtracted from the transaction value pool by this output.
437    pub fn value(&self) -> Amount<NonNegative> {
438        self.value
439    }
440
441    /// Return the destination address from a transparent output.
442    ///
443    /// Returns None if the address type is not valid or unrecognized.
444    pub fn address(&self, net: &Network) -> Option<Address> {
445        match TxOut::try_from(self).ok()?.recipient_address()? {
446            TransparentAddress::PublicKeyHash(pkh) => {
447                Some(Address::from_pub_key_hash(net.t_addr_kind(), pkh))
448            }
449            TransparentAddress::ScriptHash(sh) => {
450                Some(Address::from_script_hash(net.t_addr_kind(), sh))
451            }
452        }
453    }
454}