Skip to main content

pczt/
transparent.rs

1//! The transparent fields of a PCZT.
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::cmp::Ordering;
7
8use crate::{
9    common::{Global, Zip32Derivation},
10    roles::combiner::{merge_map, merge_optional},
11};
12
13use getset::Getters;
14use serde::{Deserialize, Serialize};
15use serde_with::serde_as;
16
17#[cfg(feature = "transparent")]
18use zcash_script::script::Evaluable;
19
20/// PCZT fields that are specific to producing the transaction's transparent bundle (if
21/// any).
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters)]
23pub struct Bundle {
24    #[getset(get = "pub")]
25    pub(crate) inputs: Vec<Input>,
26    #[getset(get = "pub")]
27    pub(crate) outputs: Vec<Output>,
28}
29
30/// The canonical empty transparent bundle: the form the transparent bundle of a PCZT
31/// takes when it carries no transparent data.
32pub(crate) const EMPTY_BUNDLE: Bundle = Bundle {
33    inputs: Vec::new(),
34    outputs: Vec::new(),
35};
36
37/// Information about a transparent input within a transaction.
38#[serde_as]
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters)]
40pub struct Input {
41    //
42    // Transparent effecting data.
43    //
44    // These are required fields that are part of the final transaction, and are filled in
45    // by the Constructor when adding an output.
46    //
47    #[getset(get = "pub")]
48    pub(crate) prevout_txid: [u8; 32],
49    #[getset(get = "pub")]
50    pub(crate) prevout_index: u32,
51
52    /// The sequence number of this input.
53    ///
54    /// - This is set by the Constructor.
55    /// - If omitted, the sequence number is assumed to be the final sequence number
56    ///   (`0xffffffff`).
57    #[getset(get = "pub")]
58    pub(crate) sequence: Option<u32>,
59
60    /// The minimum Unix timstamp that this input requires to be set as the transaction's
61    /// lock time.
62    ///
63    /// - This is set by the Constructor.
64    /// - This must be greater than or equal to 500000000.
65    pub(crate) required_time_lock_time: Option<u32>,
66
67    /// The minimum block height that this input requires to be set as the transaction's
68    /// lock time.
69    ///
70    /// - This is set by the Constructor.
71    /// - This must be greater than 0 and less than 500000000.
72    pub(crate) required_height_lock_time: Option<u32>,
73
74    /// A satisfying witness for the `script_pubkey` of the input being spent.
75    ///
76    /// This is set by the Spend Finalizer.
77    pub(crate) script_sig: Option<Vec<u8>>,
78
79    // These are required by the Transaction Extractor, to derive the shielded sighash
80    // needed for computing the binding signatures.
81    #[getset(get = "pub")]
82    pub(crate) value: u64,
83    #[getset(get = "pub")]
84    pub(crate) script_pubkey: Vec<u8>,
85
86    /// The script required to spend this output, if it is P2SH.
87    ///
88    /// Set to `None` if this is a P2PKH output.
89    pub(crate) redeem_script: Option<Vec<u8>>,
90
91    /// A map from a pubkey to a signature created by it.
92    ///
93    /// - Each pubkey should appear in `script_pubkey` or `redeem_script`.
94    /// - Each entry is set by a Signer, and should contain an ECDSA signature that is
95    ///   valid under the corresponding pubkey.
96    /// - These are required by the Spend Finalizer to assemble `script_sig`.
97    #[serde_as(as = "BTreeMap<[_; 33], _>")]
98    pub(crate) partial_signatures: BTreeMap<[u8; 33], Vec<u8>>,
99
100    /// The sighash type to be used for this input.
101    ///
102    /// - Signers must use this sighash type to produce their signatures. Signers that
103    ///   cannot produce signatures for this sighash type must not provide a signature.
104    /// - Spend Finalizers must fail to finalize inputs which have signatures not matching
105    ///   this sighash type.
106    pub(crate) sighash_type: u8,
107
108    /// A map from a pubkey to the BIP 32 derivation path at which its corresponding
109    /// spending key can be found.
110    ///
111    /// - The pubkeys should appear in `script_pubkey` or `redeem_script`.
112    /// - Each entry is set by an Updater.
113    /// - Individual entries may be required by a Signer.
114    /// - It is not required that the map include entries for all of the used pubkeys.
115    ///   In particular, it is not possible to include entries for non-BIP-32 pubkeys.
116    #[serde_as(as = "BTreeMap<[_; 33], _>")]
117    pub(crate) bip32_derivation: BTreeMap<[u8; 33], Zip32Derivation>,
118
119    /// Mappings of the form `key = RIPEMD160(value)`.
120    ///
121    /// - These may be used by the Signer to inspect parts of `script_pubkey` or
122    ///   `redeem_script`.
123    pub(crate) ripemd160_preimages: BTreeMap<[u8; 20], Vec<u8>>,
124
125    /// Mappings of the form `key = SHA256(value)`.
126    ///
127    /// - These may be used by the Signer to inspect parts of `script_pubkey` or
128    ///   `redeem_script`.
129    pub(crate) sha256_preimages: BTreeMap<[u8; 32], Vec<u8>>,
130
131    /// Mappings of the form `key = RIPEMD160(SHA256(value))`.
132    ///
133    /// - These may be used by the Signer to inspect parts of `script_pubkey` or
134    ///   `redeem_script`.
135    pub(crate) hash160_preimages: BTreeMap<[u8; 20], Vec<u8>>,
136
137    /// Mappings of the form `key = SHA256(SHA256(value))`.
138    ///
139    /// - These may be used by the Signer to inspect parts of `script_pubkey` or
140    ///   `redeem_script`.
141    pub(crate) hash256_preimages: BTreeMap<[u8; 32], Vec<u8>>,
142
143    /// Proprietary fields related to the note being spent.
144    #[getset(get = "pub")]
145    pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
146}
147
148/// Information about a transparent output within a transaction.
149#[serde_as]
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters)]
151pub struct Output {
152    //
153    // Transparent effecting data.
154    //
155    // These are required fields that are part of the final transaction, and are filled in
156    // by the Constructor when adding an output.
157    //
158    #[getset(get = "pub")]
159    pub(crate) value: u64,
160    #[getset(get = "pub")]
161    pub(crate) script_pubkey: Vec<u8>,
162
163    /// The script required to spend this output, if it is P2SH.
164    ///
165    /// Set to `None` if this is a P2PKH output.
166    pub(crate) redeem_script: Option<Vec<u8>>,
167
168    /// A map from a pubkey to the BIP 32 derivation path at which its corresponding
169    /// spending key can be found.
170    ///
171    /// - The pubkeys should appear in `script_pubkey` or `redeem_script`.
172    /// - Each entry is set by an Updater.
173    /// - Individual entries may be required by a Signer.
174    /// - It is not required that the map include entries for all of the used pubkeys.
175    ///   In particular, it is not possible to include entries for non-BIP-32 pubkeys.
176    #[serde_as(as = "BTreeMap<[_; 33], _>")]
177    pub(crate) bip32_derivation: BTreeMap<[u8; 33], Zip32Derivation>,
178
179    /// The user-facing address to which this output is being sent, if any.
180    ///
181    /// - This is set by an Updater.
182    /// - Signers must parse this address (if present) and confirm that it contains
183    ///   `recipient` (either directly, or e.g. as a receiver within a Unified Address).
184    #[getset(get = "pub")]
185    pub(crate) user_address: Option<String>,
186
187    /// Proprietary fields related to the note being spent.
188    #[getset(get = "pub")]
189    pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
190}
191
192impl Bundle {
193    /// Merges this bundle with another.
194    ///
195    /// Returns `None` if the bundles have conflicting data.
196    pub(crate) fn merge(
197        mut self,
198        other: Self,
199        self_global: &Global,
200        other_global: &Global,
201    ) -> Option<Self> {
202        // Destructure `other` to ensure we handle everything.
203        let Self {
204            mut inputs,
205            mut outputs,
206        } = other;
207
208        match (
209            self_global.inputs_modifiable(),
210            other_global.inputs_modifiable(),
211            self.inputs.len().cmp(&inputs.len()),
212        ) {
213            // Fail if the merge would add inputs to a non-modifiable bundle.
214            (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
215            // If the other bundle has more inputs than us, move them over; these cannot
216            // conflict by construction.
217            (true, _, Ordering::Less) => self.inputs.extend(inputs.drain(self.inputs.len()..)),
218            // Do nothing otherwise.
219            (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
220        }
221
222        match (
223            self_global.outputs_modifiable(),
224            other_global.outputs_modifiable(),
225            self.outputs.len().cmp(&outputs.len()),
226        ) {
227            // Fail if the merge would add outputs to a non-modifiable bundle.
228            (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
229            // If the other bundle has more outputs than us, move them over; these cannot
230            // conflict by construction.
231            (true, _, Ordering::Less) => self.outputs.extend(outputs.drain(self.outputs.len()..)),
232            // Do nothing otherwise.
233            (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
234        }
235
236        // Leverage the early-exit behaviour of zip to confirm that the remaining data in
237        // the other bundle matches this one.
238        for (lhs, rhs) in self.inputs.iter_mut().zip(inputs) {
239            // Destructure `rhs` to ensure we handle everything.
240            let Input {
241                prevout_txid,
242                prevout_index,
243                sequence,
244                required_time_lock_time,
245                required_height_lock_time,
246                script_sig,
247                value,
248                script_pubkey,
249                redeem_script,
250                partial_signatures,
251                sighash_type,
252                bip32_derivation,
253                ripemd160_preimages,
254                sha256_preimages,
255                hash160_preimages,
256                hash256_preimages,
257                proprietary,
258            } = rhs;
259
260            if lhs.prevout_txid != prevout_txid
261                || lhs.prevout_index != prevout_index
262                || lhs.value != value
263                || lhs.script_pubkey != script_pubkey
264                || lhs.sighash_type != sighash_type
265            {
266                return None;
267            }
268
269            if !(merge_optional(&mut lhs.sequence, sequence)
270                && merge_optional(&mut lhs.required_time_lock_time, required_time_lock_time)
271                && merge_optional(
272                    &mut lhs.required_height_lock_time,
273                    required_height_lock_time,
274                )
275                && merge_optional(&mut lhs.script_sig, script_sig)
276                && merge_optional(&mut lhs.redeem_script, redeem_script)
277                && merge_map(&mut lhs.partial_signatures, partial_signatures)
278                && merge_map(&mut lhs.bip32_derivation, bip32_derivation)
279                && merge_map(&mut lhs.ripemd160_preimages, ripemd160_preimages)
280                && merge_map(&mut lhs.sha256_preimages, sha256_preimages)
281                && merge_map(&mut lhs.hash160_preimages, hash160_preimages)
282                && merge_map(&mut lhs.hash256_preimages, hash256_preimages)
283                && merge_map(&mut lhs.proprietary, proprietary))
284            {
285                return None;
286            }
287        }
288
289        for (lhs, rhs) in self.outputs.iter_mut().zip(outputs) {
290            // Destructure `rhs` to ensure we handle everything.
291            let Output {
292                value,
293                script_pubkey,
294                redeem_script,
295                bip32_derivation,
296                user_address,
297                proprietary,
298            } = rhs;
299
300            if lhs.value != value || lhs.script_pubkey != script_pubkey {
301                return None;
302            }
303
304            if !(merge_optional(&mut lhs.redeem_script, redeem_script)
305                && merge_map(&mut lhs.bip32_derivation, bip32_derivation)
306                && merge_optional(&mut lhs.user_address, user_address)
307                && merge_map(&mut lhs.proprietary, proprietary))
308            {
309                return None;
310            }
311        }
312
313        Some(self)
314    }
315}
316
317#[cfg(feature = "transparent")]
318impl Bundle {
319    pub(crate) fn into_parsed(
320        self,
321    ) -> Result<transparent::pczt::Bundle, transparent::pczt::ParseError> {
322        let inputs = self
323            .inputs
324            .into_iter()
325            .map(|input| {
326                transparent::pczt::Input::parse(
327                    input.prevout_txid,
328                    input.prevout_index,
329                    input.sequence,
330                    input.required_time_lock_time,
331                    input.required_height_lock_time,
332                    input.script_sig,
333                    input.value,
334                    input.script_pubkey,
335                    input.redeem_script,
336                    input.partial_signatures,
337                    input.sighash_type,
338                    input
339                        .bip32_derivation
340                        .into_iter()
341                        .map(|(k, v)| {
342                            transparent::pczt::Bip32Derivation::parse(
343                                v.seed_fingerprint,
344                                v.derivation_path,
345                            )
346                            .map(|v| (k, v))
347                        })
348                        .collect::<Result<_, _>>()?,
349                    input.ripemd160_preimages,
350                    input.sha256_preimages,
351                    input.hash160_preimages,
352                    input.hash256_preimages,
353                    input.proprietary,
354                )
355            })
356            .collect::<Result<_, _>>()?;
357
358        let outputs = self
359            .outputs
360            .into_iter()
361            .map(|output| {
362                transparent::pczt::Output::parse(
363                    output.value,
364                    output.script_pubkey,
365                    output.redeem_script,
366                    output
367                        .bip32_derivation
368                        .into_iter()
369                        .map(|(k, v)| {
370                            transparent::pczt::Bip32Derivation::parse(
371                                v.seed_fingerprint,
372                                v.derivation_path,
373                            )
374                            .map(|v| (k, v))
375                        })
376                        .collect::<Result<_, _>>()?,
377                    output.user_address,
378                    output.proprietary,
379                )
380            })
381            .collect::<Result<_, _>>()?;
382
383        transparent::pczt::Bundle::parse(inputs, outputs)
384    }
385
386    pub(crate) fn serialize_from(bundle: transparent::pczt::Bundle) -> Self {
387        let inputs = bundle
388            .inputs()
389            .iter()
390            .map(|input| Input {
391                prevout_txid: (*input.prevout_txid()).into(),
392                prevout_index: *input.prevout_index(),
393                sequence: *input.sequence(),
394                required_time_lock_time: *input.required_time_lock_time(),
395                required_height_lock_time: *input.required_height_lock_time(),
396                script_sig: input
397                    .script_sig()
398                    .as_ref()
399                    .map(|script_sig| script_sig.to_bytes()),
400                value: input.value().into_u64(),
401                script_pubkey: input.script_pubkey().to_bytes(),
402                redeem_script: input
403                    .redeem_script()
404                    .as_ref()
405                    .map(|redeem_script| redeem_script.to_bytes()),
406                partial_signatures: input.partial_signatures().clone(),
407                sighash_type: input.sighash_type().encode(),
408                bip32_derivation: input
409                    .bip32_derivation()
410                    .iter()
411                    .map(|(k, v)| {
412                        (
413                            *k,
414                            Zip32Derivation {
415                                seed_fingerprint: *v.seed_fingerprint(),
416                                derivation_path: v
417                                    .derivation_path()
418                                    .iter()
419                                    .copied()
420                                    .map(u32::from)
421                                    .collect(),
422                            },
423                        )
424                    })
425                    .collect(),
426                ripemd160_preimages: input.ripemd160_preimages().clone(),
427                sha256_preimages: input.sha256_preimages().clone(),
428                hash160_preimages: input.hash160_preimages().clone(),
429                hash256_preimages: input.hash256_preimages().clone(),
430                proprietary: input.proprietary().clone(),
431            })
432            .collect();
433
434        let outputs = bundle
435            .outputs()
436            .iter()
437            .map(|output| Output {
438                value: output.value().into_u64(),
439                script_pubkey: output.script_pubkey().to_bytes(),
440                redeem_script: output
441                    .redeem_script()
442                    .as_ref()
443                    .map(|redeem_script| redeem_script.to_bytes()),
444                bip32_derivation: output
445                    .bip32_derivation()
446                    .iter()
447                    .map(|(k, v)| {
448                        (
449                            *k,
450                            Zip32Derivation {
451                                seed_fingerprint: *v.seed_fingerprint(),
452                                derivation_path: v
453                                    .derivation_path()
454                                    .iter()
455                                    .copied()
456                                    .map(u32::from)
457                                    .collect(),
458                            },
459                        )
460                    })
461                    .collect(),
462                user_address: output.user_address().clone(),
463                proprietary: output.proprietary().clone(),
464            })
465            .collect();
466
467        Self { inputs, outputs }
468    }
469}