pczt/
common.rs

1//! The common fields of a PCZT.
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use getset::Getters;
8use serde::{Deserialize, Serialize};
9
10use crate::roles::combiner::merge_map;
11
12pub(crate) const FLAG_TRANSPARENT_INPUTS_MODIFIABLE: u8 = 0b0000_0001;
13pub(crate) const FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE: u8 = 0b0000_0010;
14pub(crate) const FLAG_HAS_SIGHASH_SINGLE: u8 = 0b0000_0100;
15pub(crate) const FLAG_SHIELDED_MODIFIABLE: u8 = 0b1000_0000;
16
17/// Global fields that are relevant to the transaction as a whole.
18#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
19pub struct Global {
20    //
21    // Transaction effecting data.
22    //
23    // These are required fields that are part of the final transaction, and are filled in
24    // by the Creator when initializing the PCZT.
25    //
26    #[getset(get = "pub")]
27    pub(crate) tx_version: u32,
28    #[getset(get = "pub")]
29    pub(crate) version_group_id: u32,
30
31    /// The consensus branch ID for the chain in which this transaction will be mined.
32    ///
33    /// Non-optional because this commits to the set of consensus rules that will apply to
34    /// the transaction; differences therein can affect every role.
35    #[getset(get = "pub")]
36    pub(crate) consensus_branch_id: u32,
37
38    /// The transaction locktime to use if no inputs specify a required locktime.
39    ///
40    /// - This is set by the Creator.
41    /// - If omitted, the fallback locktime is assumed to be 0.
42    pub(crate) fallback_lock_time: Option<u32>,
43
44    #[getset(get = "pub")]
45    pub(crate) expiry_height: u32,
46
47    /// The [SLIP 44] coin type, indicating the network for which this transaction is
48    /// being constructed.
49    ///
50    /// This is technically information that could be determined indirectly from the
51    /// `consensus_branch_id` but is included explicitly to enable easy identification.
52    /// Note that this field is not included in the transaction and has no consensus
53    /// effect (`consensus_branch_id` fills that role).
54    ///
55    /// - This is set by the Creator.
56    /// - Roles that encode network-specific information (for example, derivation paths
57    ///   for key identification) should check against this field for correctness.
58    ///
59    /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
60    pub(crate) coin_type: u32,
61
62    /// A bitfield for various transaction modification flags.
63    ///
64    /// - Bit 0 is the Transparent Inputs Modifiable Flag and indicates whether
65    ///   transparent inputs can be modified.
66    ///   - This is set to `true` by the Creator.
67    ///   - This is checked by the Constructor before adding transparent inputs, and may
68    ///     be set to `false` by the Constructor.
69    ///   - This is set to `false` by the IO Finalizer if there are shielded spends or
70    ///     outputs.
71    ///   - This is set to `false` by a Signer that adds a signature that does not use
72    ///     `SIGHASH_ANYONECANPAY` (which includes all shielded signatures).
73    ///   - The Combiner merges this bit towards `false`.
74    /// - Bit 1 is the Transparent Outputs Modifiable Flag and indicates whether
75    ///   transparent outputs can be modified.
76    ///   - This is set to `true` by the Creator.
77    ///   - This is checked by the Constructor before adding transparent outputs, and may
78    ///     be set to `false` by the Constructor.
79    ///   - This is set to `false` by the IO Finalizer if there are shielded spends or
80    ///     outputs.
81    ///   - This is set to `false` by a Signer that adds a signature that does not use
82    ///     `SIGHASH_NONE` (which includes all shielded signatures).
83    ///   - The Combiner merges this bit towards `false`.
84    /// - Bit 2 is the Has `SIGHASH_SINGLE` Flag and indicates whether the transaction has
85    ///   a `SIGHASH_SINGLE` transparent signature who's input and output pairing must be
86    ///   preserved.
87    ///   - This is set to `false` by the Creator.
88    ///   - This is updated by a Constructor.
89    ///   - This is set to `true` by a Signer that adds a signature that uses
90    ///     `SIGHASH_SINGLE`.
91    ///   - This essentially indicates that the Constructor must iterate the transparent
92    ///     inputs to determine whether and how to add a transparent input.
93    ///   - The Combiner merges this bit towards `true`.
94    /// - Bits 3-6 must be 0.
95    /// - Bit 7 is the Shielded Modifiable Flag and indicates whether shielded spends or
96    ///   outputs can be modified.
97    ///   - This is set to `true` by the Creator.
98    ///   - This is checked by the Constructor before adding shielded spends or outputs,
99    ///     and may be set to `false` by the Constructor.
100    ///   - This is set to `false` by the IO Finalizer if there are shielded spends or
101    ///     outputs.
102    ///   - This is set to `false` by every Signer (as all signatures commit to all
103    ///     shielded spends and outputs).
104    ///   - The Combiner merges this bit towards `false`.
105    pub(crate) tx_modifiable: u8,
106
107    /// Proprietary fields related to the overall transaction.
108    #[getset(get = "pub")]
109    pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
110}
111
112impl Global {
113    /// Returns whether transparent inputs can be added to or removed from the
114    /// transaction.
115    pub fn inputs_modifiable(&self) -> bool {
116        (self.tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) != 0
117    }
118
119    /// Returns whether transparent outputs can be added to or removed from the
120    /// transaction.
121    pub fn outputs_modifiable(&self) -> bool {
122        (self.tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) != 0
123    }
124
125    /// Returns whether the transaction has a `SIGHASH_SINGLE` transparent signature who's
126    /// input and output pairing must be preserved.
127    pub fn has_sighash_single(&self) -> bool {
128        (self.tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0
129    }
130
131    /// Returns whether shielded spends or outputs can be added to or removed from the
132    /// transaction.
133    pub fn shielded_modifiable(&self) -> bool {
134        (self.tx_modifiable & FLAG_SHIELDED_MODIFIABLE) != 0
135    }
136
137    pub(crate) fn merge(mut self, other: Self) -> Option<Self> {
138        let Self {
139            tx_version,
140            version_group_id,
141            consensus_branch_id,
142            fallback_lock_time,
143            expiry_height,
144            coin_type,
145            tx_modifiable,
146            proprietary,
147        } = other;
148
149        if self.tx_version != tx_version
150            || self.version_group_id != version_group_id
151            || self.consensus_branch_id != consensus_branch_id
152            || self.fallback_lock_time != fallback_lock_time
153            || self.expiry_height != expiry_height
154            || self.coin_type != coin_type
155        {
156            return None;
157        }
158
159        // `tx_modifiable` is explicitly a bitmap; merge it bit-by-bit.
160        // - Bit 0 and Bit 1 merge towards `false`.
161        if (tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) == 0 {
162            self.tx_modifiable &= !FLAG_TRANSPARENT_INPUTS_MODIFIABLE;
163        }
164        if (tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) == 0 {
165            self.tx_modifiable &= !FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE;
166        }
167        // - Bit 2 merges towards `true`.
168        if (tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0 {
169            self.tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
170        }
171        // - Bits 3-6 must be 0.
172        if ((self.tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
173            || ((tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
174        {
175            return None;
176        }
177        // - Bit 7 merges towards `false`.
178        if (tx_modifiable & FLAG_SHIELDED_MODIFIABLE) == 0 {
179            self.tx_modifiable &= !FLAG_SHIELDED_MODIFIABLE;
180        }
181
182        if !merge_map(&mut self.proprietary, proprietary) {
183            return None;
184        }
185
186        Some(self)
187    }
188}
189
190#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
191pub(crate) struct Zip32Derivation {
192    /// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints).
193    pub(crate) seed_fingerprint: [u8; 32],
194
195    /// The sequence of indices corresponding to the shielded HD path.
196    ///
197    /// Indices can be hardened or non-hardened (i.e. the hardened flag bit may be set).
198    /// When used with a Sapling or Orchard spend, the derivation path will generally be
199    /// entirely hardened; when used with a transparent spend, the derivation path will
200    /// generally include a non-hardened section matching either the [BIP 44] path, or the
201    /// path at which ephemeral addresses are derived for [ZIP 320] transactions.
202    ///
203    /// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
204    /// [ZIP 320]: https://zips.z.cash/zip-0320
205    pub(crate) derivation_path: Vec<u32>,
206}
207
208/// Determines the lock time for the transaction.
209///
210/// Implemented following the specification in [BIP 370], with the rationale that this
211/// makes integration of PCZTs simpler for codebases that already support PSBTs.
212///
213/// [BIP 370]: https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time
214pub fn determine_lock_time<L: LockTimeInput>(
215    global: &crate::common::Global,
216    inputs: &[L],
217) -> Option<u32> {
218    // The nLockTime field of a transaction is determined by inspecting the
219    // `Global.fallback_lock_time` and each input's `required_time_lock_time` and
220    // `required_height_lock_time` fields.
221
222    // If one or more inputs have a `required_time_lock_time` or `required_height_lock_time`,
223    let have_required_lock_time = inputs.iter().any(|input| {
224        input.required_time_lock_time().is_some() || input.required_height_lock_time().is_some()
225    });
226    // then the field chosen is the one which is supported by all of the inputs. This can
227    // be determined by looking at all of the inputs which specify a locktime in either of
228    // those fields, and choosing the field which is present in all of those inputs.
229    // Inputs not specifying a lock time field can take both types of lock times, as can
230    // those that specify both.
231    let time_lock_time_unsupported = inputs
232        .iter()
233        .any(|input| input.required_height_lock_time().is_some());
234    let height_lock_time_unsupported = inputs
235        .iter()
236        .any(|input| input.required_time_lock_time().is_some());
237
238    // The lock time chosen is then the maximum value of the chosen type of lock time.
239    match (
240        have_required_lock_time,
241        time_lock_time_unsupported,
242        height_lock_time_unsupported,
243    ) {
244        (true, true, true) => None,
245        (true, false, true) => Some(
246            inputs
247                .iter()
248                .filter_map(|input| input.required_time_lock_time())
249                .max()
250                .expect("iterator is non-empty because have_required_lock_time is true"),
251        ),
252        // If a PSBT has both types of locktimes possible because one or more inputs
253        // specify both `required_time_lock_time` and `required_height_lock_time`, then a
254        // locktime determined by looking at the `required_height_lock_time` fields of the
255        // inputs must be chosen.
256        (true, _, false) => Some(
257            inputs
258                .iter()
259                .filter_map(|input| input.required_height_lock_time())
260                .max()
261                .expect("iterator is non-empty because have_required_lock_time is true"),
262        ),
263        // If none of the inputs have a `required_time_lock_time` and
264        // `required_height_lock_time`, then `Global.fallback_lock_time` must be used. If
265        // `Global.fallback_lock_time` is not provided, then it is assumed to be 0.
266        (false, _, _) => Some(global.fallback_lock_time.unwrap_or(0)),
267    }
268}
269
270pub trait LockTimeInput {
271    fn required_time_lock_time(&self) -> Option<u32>;
272    fn required_height_lock_time(&self) -> Option<u32>;
273}
274
275impl LockTimeInput for crate::transparent::Input {
276    fn required_time_lock_time(&self) -> Option<u32> {
277        self.required_time_lock_time
278    }
279
280    fn required_height_lock_time(&self) -> Option<u32> {
281        self.required_height_lock_time
282    }
283}
284
285#[cfg(feature = "transparent")]
286impl LockTimeInput for ::transparent::pczt::Input {
287    fn required_time_lock_time(&self) -> Option<u32> {
288        *self.required_time_lock_time()
289    }
290
291    fn required_height_lock_time(&self) -> Option<u32> {
292        *self.required_height_lock_time()
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use alloc::collections::BTreeMap;
299
300    use super::Global;
301
302    #[test]
303    fn tx_modifiable() {
304        let base = Global {
305            tx_version: 0,
306            version_group_id: 0,
307            consensus_branch_id: 0,
308            fallback_lock_time: None,
309            expiry_height: 0,
310            coin_type: 0,
311            tx_modifiable: 0b0000_0000,
312            proprietary: BTreeMap::new(),
313        };
314
315        for (left, right, expected) in [
316            (0b0000_0000, 0b0000_0000, Some(0b0000_0000)),
317            (0b0000_0000, 0b0000_0011, Some(0b0000_0000)),
318            (0b0000_0001, 0b0000_0011, Some(0b0000_0001)),
319            (0b0000_0010, 0b0000_0011, Some(0b0000_0010)),
320            (0b0000_0011, 0b0000_0011, Some(0b0000_0011)),
321            (0b0000_0000, 0b0000_0100, Some(0b0000_0100)),
322            (0b0000_0100, 0b0000_0100, Some(0b0000_0100)),
323            (0b0000_0011, 0b0000_0111, Some(0b0000_0111)),
324            (0b0000_0000, 0b0000_1000, None),
325            (0b0000_0000, 0b0001_0000, None),
326            (0b0000_0000, 0b0010_0000, None),
327            (0b0000_0000, 0b0100_0000, None),
328            (0b0000_0000, 0b1000_0000, Some(0b0000_0000)),
329            (0b1000_0000, 0b1000_0000, Some(0b1000_0000)),
330        ] {
331            let mut a = base.clone();
332            a.tx_modifiable = left;
333
334            let mut b = base.clone();
335            b.tx_modifiable = right;
336
337            assert_eq!(
338                a.clone()
339                    .merge(b.clone())
340                    .map(|global| global.tx_modifiable),
341                expected
342            );
343            assert_eq!(b.merge(a).map(|global| global.tx_modifiable), expected);
344        }
345    }
346}