Skip to main content

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_script::{opcode::Evaluable as _, pattern::push_num};
13use zcash_transparent::{address::TransparentAddress, bundle::TxOut};
14
15use crate::{
16    amount::{Amount, NonNegative},
17    block,
18    parameters::Network,
19    serialization::ZcashSerialize,
20    transaction,
21    transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG,
22};
23
24pub use address::Address;
25pub use script::Script;
26pub use utxo::{
27    new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
28    CoinbaseSpendRestriction, OrderedUtxo, Utxo,
29};
30
31#[cfg(any(test, feature = "proptest-impl"))]
32pub use utxo::{
33    new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
34};
35
36#[cfg(any(test, feature = "proptest-impl"))]
37mod arbitrary;
38
39#[cfg(test)]
40mod tests;
41
42#[cfg(any(test, feature = "proptest-impl"))]
43use proptest_derive::Arbitrary;
44
45/// The maturity threshold for transparent coinbase outputs.
46///
47/// "A transaction MUST NOT spend a transparent output of a coinbase transaction
48/// from a block less than 100 blocks prior to the spend. Note that transparent
49/// outputs of coinbase transactions include Founders' Reward outputs and
50/// transparent Funding Stream outputs."
51/// [7.1](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus)
52//
53// TODO: change type to HeightDiff
54pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
55
56/// The rate used to calculate the dust threshold, in zatoshis per 1000 bytes.
57///
58/// History: <https://github.com/zcash/zcash/blob/v6.10.0/src/policy/policy.h#L43-L89>
59pub const ONE_THIRD_DUST_THRESHOLD_RATE: u32 = 100;
60
61/// OutPoint
62///
63/// A particular transaction output reference.
64#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
65#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
66#[cfg_attr(
67    any(test, feature = "proptest-impl", feature = "elasticsearch"),
68    derive(Serialize)
69)]
70pub struct OutPoint {
71    /// References the transaction that contains the UTXO being spent.
72    ///
73    /// # Correctness
74    ///
75    /// Consensus-critical serialization uses [`ZcashSerialize`].
76    /// [`serde`]-based hex serialization must only be used for testing.
77    #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))]
78    pub hash: transaction::Hash,
79
80    /// Identifies which UTXO from that transaction is referenced; the
81    /// first output is 0, etc.
82    // TODO: Use OutputIndex here
83    pub index: u32,
84}
85
86impl OutPoint {
87    /// Returns a new [`OutPoint`] from an in-memory output `index`.
88    ///
89    /// # Panics
90    ///
91    /// If `index` doesn't fit in a [`u32`].
92    pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
93        OutPoint {
94            hash,
95            index: index
96                .try_into()
97                .expect("valid in-memory output indexes fit in a u32"),
98        }
99    }
100}
101
102/// A transparent input to a transaction.
103#[derive(Clone, Debug, Eq, PartialEq)]
104#[cfg_attr(
105    any(test, feature = "proptest-impl", feature = "elasticsearch"),
106    derive(Serialize)
107)]
108pub enum Input {
109    /// A reference to an output of a previous transaction.
110    PrevOut {
111        /// The previous output transaction reference.
112        outpoint: OutPoint,
113        /// The script that authorizes spending `outpoint`.
114        unlock_script: Script,
115        /// The sequence number for the output.
116        sequence: u32,
117    },
118    /// New coins created by the block reward.
119    Coinbase {
120        /// The height of this block.
121        height: block::Height,
122        /// Optional, arbitrary data miners can insert into a coinbase tx.
123        /// Limited to ~ 94 bytes.
124        data: Vec<u8>,
125        /// The sequence number for the output.
126        sequence: u32,
127    },
128}
129
130impl fmt::Display for Input {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            Input::PrevOut {
134                outpoint,
135                unlock_script,
136                ..
137            } => {
138                let mut fmter = f.debug_struct("transparent::Input::PrevOut");
139
140                fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
141                fmter.field("outpoint", outpoint);
142
143                fmter.finish()
144            }
145            Input::Coinbase { height, data, .. } => {
146                let mut fmter = f.debug_struct("transparent::Input::Coinbase");
147
148                fmter.field("height", height);
149                fmter.field("data_len", &data.len());
150
151                fmter.finish()
152            }
153        }
154    }
155}
156
157impl Input {
158    /// Returns the miner data in this input, if it is an [`Input::Coinbase`].
159    pub fn miner_data(&self) -> Option<&Vec<u8>> {
160        match self {
161            Input::Coinbase { data, .. } => Some(data),
162            _ => None,
163        }
164    }
165
166    /// Returns the full coinbase script (the encoded height along with the optional miner data) if
167    /// this is an [`Input::Coinbase`]. Also returns `None` if the coinbase is for the genesis block
168    /// but does not match the expected genesis coinbase data.
169    pub fn coinbase_script(&self) -> Option<Vec<u8>> {
170        match self {
171            Input::PrevOut { .. } => None,
172            Input::Coinbase { height, data, .. } => {
173                if height.is_min() {
174                    (data.as_slice() == GENESIS_COINBASE_SCRIPT_SIG)
175                        .then_some(GENESIS_COINBASE_SCRIPT_SIG.to_vec())
176                } else {
177                    let mut script = push_num(height.into()).to_bytes();
178                    script.extend_from_slice(data);
179
180                    Some(script)
181                }
182            }
183        }
184    }
185
186    /// Returns the input's sequence number.
187    pub fn sequence(&self) -> u32 {
188        match self {
189            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
190        }
191    }
192
193    /// Sets the input's sequence number.
194    ///
195    /// Only for use in tests.
196    #[cfg(any(test, feature = "proptest-impl"))]
197    pub fn set_sequence(&mut self, new_sequence: u32) {
198        match self {
199            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
200                *sequence = new_sequence
201            }
202        }
203    }
204
205    /// If this is a [`Input::PrevOut`] input, returns this input's
206    /// [`OutPoint`]. Otherwise, returns `None`.
207    pub fn outpoint(&self) -> Option<OutPoint> {
208        if let Input::PrevOut { outpoint, .. } = self {
209            Some(*outpoint)
210        } else {
211            None
212        }
213    }
214
215    /// Set this input's [`OutPoint`].
216    ///
217    /// Should only be called on [`Input::PrevOut`] inputs.
218    ///
219    /// # Panics
220    ///
221    /// If `self` is a coinbase input.
222    #[cfg(any(test, feature = "proptest-impl"))]
223    pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
224        if let Input::PrevOut {
225            ref mut outpoint, ..
226        } = self
227        {
228            *outpoint = new_outpoint;
229        } else {
230            unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
231        }
232    }
233
234    /// Get the value spent by this input, by looking up its [`OutPoint`] in `outputs`.
235    /// See [`Self::value`] for details.
236    ///
237    /// # Panics
238    ///
239    /// If the provided [`Output`]s don't have this input's [`OutPoint`].
240    pub(crate) fn value_from_outputs(
241        &self,
242        outputs: &HashMap<OutPoint, Output>,
243    ) -> Amount<NonNegative> {
244        match self {
245            Input::PrevOut { outpoint, .. } => {
246                outputs
247                    .get(outpoint)
248                    .unwrap_or_else(|| {
249                        panic!(
250                            "provided Outputs (length {:?}) don't have spent {:?}",
251                            outputs.len(),
252                            outpoint
253                        )
254                    })
255                    .value
256            }
257            Input::Coinbase { .. } => Amount::zero(),
258        }
259    }
260
261    /// Get the value spent by this input, by looking up its [`OutPoint`] in
262    /// [`Utxo`]s.
263    ///
264    /// This amount is added to the transaction value pool by this input.
265    ///
266    /// # Panics
267    ///
268    /// If the provided [`Utxo`]s don't have this input's [`OutPoint`].
269    pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
270        if let Some(outpoint) = self.outpoint() {
271            // look up the specific Output and convert it to the expected format
272            let output = utxos
273                .get(&outpoint)
274                .expect("provided Utxos don't have spent OutPoint")
275                .output
276                .clone();
277            self.value_from_outputs(&iter::once((outpoint, output)).collect())
278        } else {
279            // coinbase inputs don't need any UTXOs
280            self.value_from_outputs(&HashMap::new())
281        }
282    }
283
284    /// Get the value spent by this input, by looking up its [`OutPoint`] in
285    /// [`OrderedUtxo`]s.
286    ///
287    /// See [`Self::value`] for details.
288    ///
289    /// # Panics
290    ///
291    /// If the provided [`OrderedUtxo`]s don't have this input's [`OutPoint`].
292    pub fn value_from_ordered_utxos(
293        &self,
294        ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
295    ) -> Amount<NonNegative> {
296        if let Some(outpoint) = self.outpoint() {
297            // look up the specific Output and convert it to the expected format
298            let output = ordered_utxos
299                .get(&outpoint)
300                .expect("provided Utxos don't have spent OutPoint")
301                .utxo
302                .output
303                .clone();
304            self.value_from_outputs(&iter::once((outpoint, output)).collect())
305        } else {
306            // coinbase inputs don't need any UTXOs
307            self.value_from_outputs(&HashMap::new())
308        }
309    }
310}
311
312/// A transparent output from a transaction.
313///
314/// The most fundamental building block of a transaction is a
315/// transaction output -- the ZEC you own in your "wallet" is in
316/// fact a subset of unspent transaction outputs (or "UTXO"s) of the
317/// global UTXO set.
318///
319/// UTXOs are indivisible, discrete units of value which can only be
320/// consumed in their entirety. Thus, if I want to send you 1 ZEC and
321/// I only own one UTXO worth 2 ZEC, I would construct a transaction
322/// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
323/// (just like receiving change).
324#[derive(Clone, Debug, Eq, PartialEq, Hash)]
325#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
326#[cfg_attr(
327    any(test, feature = "proptest-impl", feature = "elasticsearch"),
328    derive(Serialize)
329)]
330pub struct Output {
331    /// Transaction value.
332    // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
333    pub value: Amount<NonNegative>,
334
335    /// The lock script defines the conditions under which this output can be spent.
336    pub lock_script: Script,
337}
338
339impl Output {
340    /// Returns a new [`Output`].
341    pub fn new(amount: Amount<NonNegative>, lock_script: Script) -> Output {
342        Output {
343            value: amount,
344            lock_script,
345        }
346    }
347
348    /// Get the value contained in this output.
349    /// This amount is subtracted from the transaction value pool by this output.
350    pub fn value(&self) -> Amount<NonNegative> {
351        self.value
352    }
353
354    /// Return the destination address from a transparent output.
355    ///
356    /// Returns None if the address type is not valid or unrecognized.
357    pub fn address(&self, net: &Network) -> Option<Address> {
358        match TxOut::try_from(self).ok()?.recipient_address()? {
359            TransparentAddress::PublicKeyHash(pkh) => {
360                Some(Address::from_pub_key_hash(net.t_addr_kind(), pkh))
361            }
362            TransparentAddress::ScriptHash(sh) => {
363                Some(Address::from_script_hash(net.t_addr_kind(), sh))
364            }
365        }
366    }
367
368    /// Returns true if this output is considered dust.
369    pub fn is_dust(&self) -> bool {
370        let output_size: u32 = self
371            .zcash_serialized_size()
372            .try_into()
373            .expect("output size should fit in u32");
374
375        // https://github.com/zcash/zcash/blob/v6.10.0/src/primitives/transaction.cpp#L75-L80
376        let threshold = 3 * (ONE_THIRD_DUST_THRESHOLD_RATE * (output_size + 148) / 1000);
377
378        // https://github.com/zcash/zcash/blob/v6.10.0/src/primitives/transaction.h#L396-L399
379        self.value.zatoshis() < threshold as i64
380    }
381}
382
383/// A transparent output's index in its transaction.
384#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
385pub struct OutputIndex(u32);
386
387impl OutputIndex {
388    /// Create a transparent output index from the Zcash consensus integer type.
389    ///
390    /// `u32` is also the inner type.
391    pub const fn from_index(output_index: u32) -> OutputIndex {
392        OutputIndex(output_index)
393    }
394
395    /// Returns this index as the inner type.
396    pub const fn index(&self) -> u32 {
397        self.0
398    }
399
400    /// Create a transparent output index from `usize`.
401    #[allow(dead_code)]
402    pub fn from_usize(output_index: usize) -> OutputIndex {
403        OutputIndex(
404            output_index
405                .try_into()
406                .expect("the maximum valid index fits in the inner type"),
407        )
408    }
409
410    /// Return this index as `usize`.
411    #[allow(dead_code)]
412    pub fn as_usize(&self) -> usize {
413        self.0
414            .try_into()
415            .expect("the maximum valid index fits in usize")
416    }
417
418    /// Create a transparent output index from `u64`.
419    #[allow(dead_code)]
420    pub fn from_u64(output_index: u64) -> OutputIndex {
421        OutputIndex(
422            output_index
423                .try_into()
424                .expect("the maximum u64 index fits in the inner type"),
425        )
426    }
427
428    /// Return this index as `u64`.
429    #[allow(dead_code)]
430    pub fn as_u64(&self) -> u64 {
431        self.0.into()
432    }
433}
434
435impl AddAssign<u32> for OutputIndex {
436    fn add_assign(&mut self, rhs: u32) {
437        self.0 += rhs
438    }
439}