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}