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