pczt/common.rs
1//! The common fields of a PCZT.
2
3use alloc::{collections::BTreeMap, string::String, vec::Vec};
4use core::fmt;
5
6use getset::Getters;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use subtle::ConstantTimeEq;
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::roles::combiner::merge_map;
12
13pub(crate) const FLAG_TRANSPARENT_INPUTS_MODIFIABLE: u8 = 0b0000_0001;
14pub(crate) const FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE: u8 = 0b0000_0010;
15pub(crate) const FLAG_HAS_SIGHASH_SINGLE: u8 = 0b0000_0100;
16pub(crate) const FLAG_SHIELDED_MODIFIABLE: u8 = 0b1000_0000;
17
18/// A placeholder anchor used internally when parsing a shielded bundle for an
19/// operation that does not read the anchor's value. Zero is always a valid anchor
20/// encoding for the Sapling, Orchard, and Ironwood note commitment trees.
21#[cfg(any(
22 feature = "io-finalizer",
23 feature = "orchard",
24 feature = "sapling",
25 feature = "signer",
26 feature = "tx-extractor"
27))]
28pub(crate) const PLACEHOLDER_ANCHOR: [u8; 32] = [0; 32];
29
30/// Governs whether a non-empty shielded bundle's `anchor` must be set in order to parse
31/// it into the form used by the payment protocol crates.
32///
33/// [ZIP 374](https://zips.z.cash/zip-0374) makes Sapling and Orchard-family bundle anchors
34/// deferrable in V6 and later transactions, so operations that do not depend on the
35/// anchor's value (for example computing a v6 sighash, which excludes anchors, or
36/// updating unrelated fields) can proceed with it absent.
37#[cfg(any(
38 feature = "io-finalizer",
39 feature = "orchard",
40 feature = "sapling",
41 feature = "signer",
42 feature = "tx-extractor"
43))]
44#[derive(Clone, Copy, Debug)]
45pub(crate) enum AnchorRequirement {
46 /// The operation does not read the anchor, so an absent anchor is parsed using a
47 /// placeholder value.
48 ///
49 /// Only constructed when the `orchard` or `sapling` feature is enabled, as those
50 /// are the only operations lenient enough to use it.
51 NotRequired,
52 /// The operation requires the anchor's real value; parsing fails if it is absent
53 /// from a non-empty bundle.
54 Required,
55}
56
57#[cfg(any(
58 feature = "io-finalizer",
59 feature = "orchard",
60 feature = "sapling",
61 feature = "signer",
62 feature = "tx-extractor"
63))]
64impl AnchorRequirement {
65 /// Returns the requirement for operations that do not themselves consume the
66 /// anchor, but still parse bundles through the protocol crates.
67 ///
68 /// V6 signatures do not commit to shielded anchors, so signing and IO
69 /// finalization can proceed before anchors are set. V5 signatures do commit to
70 /// anchors for Sapling spends and Orchard-protocol actions, so those bundles must
71 /// keep requiring them.
72 pub(crate) fn for_pre_authorization(tx_version: u32) -> Self {
73 match tx_version {
74 zcash_protocol::constants::V6_TX_VERSION => AnchorRequirement::NotRequired,
75 #[cfg(zcash_unstable = "nutachyon")]
76 zcash_protocol::constants::V7_TX_VERSION => AnchorRequirement::NotRequired,
77 _ => AnchorRequirement::Required,
78 }
79 }
80
81 /// Resolves a shielded bundle's wire `anchor` to the anchor that should be used
82 /// when parsing it, given whether the bundle has no transaction items (no Sapling
83 /// spends or outputs, or no Orchard-protocol actions).
84 ///
85 /// Returns `None` to mean "the anchor is absent and required"; callers should map
86 /// that case to their own missing-anchor error. An absent anchor on an empty bundle
87 /// resolves to [`PLACEHOLDER_ANCHOR`], since there is nothing to prove or extract.
88 pub(crate) fn resolve(
89 self,
90 anchor: Option<[u8; 32]>,
91 bundle_is_empty: bool,
92 ) -> Option<[u8; 32]> {
93 match (self, anchor) {
94 (_, Some(anchor)) => Some(anchor),
95 (AnchorRequirement::NotRequired, None) => Some(PLACEHOLDER_ANCHOR),
96 (AnchorRequirement::Required, None) if bundle_is_empty => Some(PLACEHOLDER_ANCHOR),
97 (AnchorRequirement::Required, None) => None,
98 }
99 }
100}
101
102/// Global fields that are relevant to the transaction as a whole.
103#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
104pub struct Global {
105 //
106 // Transaction effecting data.
107 //
108 // These are required fields that are part of the final transaction, and are filled in
109 // by the Creator when initializing the PCZT.
110 //
111 #[getset(get = "pub")]
112 pub(crate) tx_version: u32,
113 #[getset(get = "pub")]
114 pub(crate) version_group_id: u32,
115
116 /// The consensus branch ID for the chain in which this transaction will be mined.
117 ///
118 /// Non-optional because this commits to the set of consensus rules that will apply to
119 /// the transaction; differences therein can affect every role.
120 #[getset(get = "pub")]
121 pub(crate) consensus_branch_id: u32,
122
123 /// The transaction locktime to use if no inputs specify a required locktime.
124 ///
125 /// - This is set by the Creator.
126 /// - If omitted, the fallback locktime is assumed to be 0.
127 ///
128 /// The transaction's actual lock time is given by [`determine_lock_time`], which
129 /// takes the inputs' required lock times into account.
130 #[getset(get = "pub")]
131 pub(crate) fallback_lock_time: Option<u32>,
132
133 #[getset(get = "pub")]
134 pub(crate) expiry_height: u32,
135
136 /// The [SLIP 44] coin type, indicating the network for which this transaction is
137 /// being constructed.
138 ///
139 /// This is technically information that could be determined indirectly from the
140 /// `consensus_branch_id` but is included explicitly to enable easy identification.
141 /// Note that this field is not included in the transaction and has no consensus
142 /// effect (`consensus_branch_id` fills that role).
143 ///
144 /// - This is set by the Creator.
145 /// - Roles that encode network-specific information (for example, derivation paths
146 /// for key identification) should check against this field for correctness.
147 ///
148 /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
149 #[getset(get = "pub")]
150 pub(crate) coin_type: u32,
151
152 /// A bitfield for various transaction modification flags.
153 ///
154 /// - Bit 0 is the Transparent Inputs Modifiable Flag and indicates whether
155 /// transparent inputs can be modified.
156 /// - This is set to `true` by the Creator.
157 /// - This is checked by the Constructor before adding transparent inputs, and may
158 /// be set to `false` by the Constructor.
159 /// - This is set to `false` by the IO Finalizer if there are shielded spends or
160 /// outputs.
161 /// - This is set to `false` by a Signer that adds a signature that does not use
162 /// `SIGHASH_ANYONECANPAY` (which includes all shielded signatures).
163 /// - The Combiner merges this bit towards `false`.
164 /// - Bit 1 is the Transparent Outputs Modifiable Flag and indicates whether
165 /// transparent outputs can be modified.
166 /// - This is set to `true` by the Creator.
167 /// - This is checked by the Constructor before adding transparent outputs, and may
168 /// be set to `false` by the Constructor.
169 /// - This is set to `false` by the IO Finalizer if there are shielded spends or
170 /// outputs.
171 /// - This is set to `false` by a Signer that adds a signature that does not use
172 /// `SIGHASH_NONE` (which includes all shielded signatures).
173 /// - The Combiner merges this bit towards `false`.
174 /// - Bit 2 is the Has `SIGHASH_SINGLE` Flag and indicates whether the transaction has
175 /// a `SIGHASH_SINGLE` transparent signature who's input and output pairing must be
176 /// preserved.
177 /// - This is set to `false` by the Creator.
178 /// - This is updated by a Constructor.
179 /// - This is set to `true` by a Signer that adds a signature that uses
180 /// `SIGHASH_SINGLE`.
181 /// - This essentially indicates that the Constructor must iterate the transparent
182 /// inputs to determine whether and how to add a transparent input.
183 /// - The Combiner merges this bit towards `true`.
184 /// - Bits 3-6 must be 0.
185 /// - Bit 7 is the Shielded Modifiable Flag and indicates whether shielded spends or
186 /// outputs can be modified.
187 /// - This is set to `true` by the Creator.
188 /// - This is checked by the Constructor before adding shielded spends or outputs,
189 /// and may be set to `false` by the Constructor.
190 /// - This is set to `false` by the IO Finalizer if there are shielded spends or
191 /// outputs.
192 /// - This is set to `false` by every Signer (as all signatures commit to all
193 /// shielded spends and outputs).
194 /// - The Combiner merges this bit towards `false`.
195 ///
196 /// Individual flags are also available via [`Global::inputs_modifiable`],
197 /// [`Global::outputs_modifiable`], [`Global::has_sighash_single`], and
198 /// [`Global::shielded_modifiable`].
199 #[getset(get = "pub")]
200 pub(crate) tx_modifiable: u8,
201
202 /// Proprietary fields related to the overall transaction.
203 #[getset(get = "pub")]
204 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
205}
206
207impl Global {
208 /// Returns whether transparent inputs can be added to or removed from the
209 /// transaction.
210 pub fn inputs_modifiable(&self) -> bool {
211 (self.tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) != 0
212 }
213
214 /// Returns whether transparent outputs can be added to or removed from the
215 /// transaction.
216 pub fn outputs_modifiable(&self) -> bool {
217 (self.tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) != 0
218 }
219
220 /// Returns whether the transaction has a `SIGHASH_SINGLE` transparent signature who's
221 /// input and output pairing must be preserved.
222 pub fn has_sighash_single(&self) -> bool {
223 (self.tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0
224 }
225
226 /// Returns whether shielded spends or outputs can be added to or removed from the
227 /// transaction.
228 pub fn shielded_modifiable(&self) -> bool {
229 (self.tx_modifiable & FLAG_SHIELDED_MODIFIABLE) != 0
230 }
231
232 pub(crate) fn merge(mut self, other: Self) -> Option<Self> {
233 let Self {
234 tx_version,
235 version_group_id,
236 consensus_branch_id,
237 fallback_lock_time,
238 expiry_height,
239 coin_type,
240 tx_modifiable,
241 proprietary,
242 } = other;
243
244 if self.tx_version != tx_version
245 || self.version_group_id != version_group_id
246 || self.consensus_branch_id != consensus_branch_id
247 || self.fallback_lock_time != fallback_lock_time
248 || self.expiry_height != expiry_height
249 || self.coin_type != coin_type
250 {
251 return None;
252 }
253
254 // `tx_modifiable` is explicitly a bitmap; merge it bit-by-bit.
255 // - Bit 0 and Bit 1 merge towards `false`.
256 if (tx_modifiable & FLAG_TRANSPARENT_INPUTS_MODIFIABLE) == 0 {
257 self.tx_modifiable &= !FLAG_TRANSPARENT_INPUTS_MODIFIABLE;
258 }
259 if (tx_modifiable & FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE) == 0 {
260 self.tx_modifiable &= !FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE;
261 }
262 // - Bit 2 merges towards `true`.
263 if (tx_modifiable & FLAG_HAS_SIGHASH_SINGLE) != 0 {
264 self.tx_modifiable |= FLAG_HAS_SIGHASH_SINGLE;
265 }
266 // - Bits 3-6 must be 0.
267 if ((self.tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
268 || ((tx_modifiable & !FLAG_SHIELDED_MODIFIABLE) >> 3) != 0
269 {
270 return None;
271 }
272 // - Bit 7 merges towards `false`.
273 if (tx_modifiable & FLAG_SHIELDED_MODIFIABLE) == 0 {
274 self.tx_modifiable &= !FLAG_SHIELDED_MODIFIABLE;
275 }
276
277 if !merge_map(&mut self.proprietary, proprietary) {
278 return None;
279 }
280
281 Some(self)
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
286pub(crate) struct Zip32Derivation {
287 /// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints).
288 pub(crate) seed_fingerprint: [u8; 32],
289
290 /// The sequence of indices corresponding to the shielded HD path.
291 ///
292 /// Indices can be hardened or non-hardened (i.e. the hardened flag bit may be set).
293 /// When used with a Sapling or Orchard spend, the derivation path will generally be
294 /// entirely hardened; when used with a transparent spend, the derivation path will
295 /// generally include a non-hardened section matching either the [BIP 44] path, or the
296 /// path at which ephemeral addresses are derived for [ZIP 320] transactions.
297 ///
298 /// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
299 /// [ZIP 320]: https://zips.z.cash/zip-0320
300 pub(crate) derivation_path: Vec<u32>,
301}
302
303/// Determines the lock time for the transaction.
304///
305/// Implemented following the specification in [BIP 370], with the rationale that this
306/// makes integration of PCZTs simpler for codebases that already support PSBTs.
307///
308/// [BIP 370]: https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time
309pub fn determine_lock_time<L: LockTimeInput>(
310 global: &crate::common::Global,
311 inputs: &[L],
312) -> Option<u32> {
313 // The nLockTime field of a transaction is determined by inspecting the
314 // `Global.fallback_lock_time` and each input's `required_time_lock_time` and
315 // `required_height_lock_time` fields.
316
317 // If one or more inputs have a `required_time_lock_time` or `required_height_lock_time`,
318 let have_required_lock_time = inputs.iter().any(|input| {
319 input.required_time_lock_time().is_some() || input.required_height_lock_time().is_some()
320 });
321 // then the field chosen is the one which is supported by all of the inputs. This can
322 // be determined by looking at all of the inputs which specify a locktime in either of
323 // those fields, and choosing the field which is present in all of those inputs.
324 // Inputs not specifying a lock time field can take both types of lock times, as can
325 // those that specify both.
326 let time_lock_time_unsupported = inputs
327 .iter()
328 .any(|input| input.required_height_lock_time().is_some());
329 let height_lock_time_unsupported = inputs
330 .iter()
331 .any(|input| input.required_time_lock_time().is_some());
332
333 // The lock time chosen is then the maximum value of the chosen type of lock time.
334 match (
335 have_required_lock_time,
336 time_lock_time_unsupported,
337 height_lock_time_unsupported,
338 ) {
339 (true, true, true) => None,
340 (true, false, true) => Some(
341 inputs
342 .iter()
343 .filter_map(|input| input.required_time_lock_time())
344 .max()
345 .expect("iterator is non-empty because have_required_lock_time is true"),
346 ),
347 // If a PSBT has both types of locktimes possible because one or more inputs
348 // specify both `required_time_lock_time` and `required_height_lock_time`, then a
349 // locktime determined by looking at the `required_height_lock_time` fields of the
350 // inputs must be chosen.
351 (true, _, false) => Some(
352 inputs
353 .iter()
354 .filter_map(|input| input.required_height_lock_time())
355 .max()
356 .expect("iterator is non-empty because have_required_lock_time is true"),
357 ),
358 // If none of the inputs have a `required_time_lock_time` and
359 // `required_height_lock_time`, then `Global.fallback_lock_time` must be used. If
360 // `Global.fallback_lock_time` is not provided, then it is assumed to be 0.
361 (false, _, _) => Some(global.fallback_lock_time.unwrap_or(0)),
362 }
363}
364
365pub trait LockTimeInput {
366 fn required_time_lock_time(&self) -> Option<u32>;
367 fn required_height_lock_time(&self) -> Option<u32>;
368}
369
370impl LockTimeInput for crate::transparent::Input {
371 fn required_time_lock_time(&self) -> Option<u32> {
372 self.required_time_lock_time
373 }
374
375 fn required_height_lock_time(&self) -> Option<u32> {
376 self.required_height_lock_time
377 }
378}
379
380#[cfg(feature = "transparent")]
381impl LockTimeInput for ::transparent::pczt::Input {
382 fn required_time_lock_time(&self) -> Option<u32> {
383 *self.required_time_lock_time()
384 }
385
386 fn required_height_lock_time(&self) -> Option<u32> {
387 *self.required_height_lock_time()
388 }
389}
390
391/// The encoding of a secret key that a PCZT carries.
392///
393/// The bytes are erased when the value is dropped. The `Debug` output does not show
394/// them, and equality comparison takes constant time. The serialized form is the same
395/// as that of a `[u8; 32]`.
396#[derive(Clone)]
397pub struct SecretKeyBytes([u8; 32]);
398
399impl SecretKeyBytes {
400 /// Wraps the encoding of a secret key.
401 pub fn new(bytes: [u8; 32]) -> Self {
402 Self(bytes)
403 }
404
405 /// Returns the encoding of the secret key.
406 pub fn expose_secret(&self) -> &[u8; 32] {
407 &self.0
408 }
409}
410
411impl Drop for SecretKeyBytes {
412 fn drop(&mut self) {
413 self.0.zeroize();
414 }
415}
416
417impl ZeroizeOnDrop for SecretKeyBytes {}
418
419impl fmt::Debug for SecretKeyBytes {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 f.write_str("SecretKeyBytes(..)")
422 }
423}
424
425impl PartialEq for SecretKeyBytes {
426 fn eq(&self, other: &Self) -> bool {
427 self.0.ct_eq(&other.0).into()
428 }
429}
430
431impl Eq for SecretKeyBytes {}
432
433impl Serialize for SecretKeyBytes {
434 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
435 self.0.serialize(serializer)
436 }
437}
438
439impl<'de> Deserialize<'de> for SecretKeyBytes {
440 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
441 <[u8; 32]>::deserialize(deserializer).map(Self)
442 }
443}
444
445#[cfg(test)]
446mod secret_key_bytes_tests {
447 use alloc::format;
448
449 use proptest::prelude::*;
450
451 use super::SecretKeyBytes;
452
453 proptest! {
454 #[test]
455 fn serializes_as_byte_array(bytes in prop::array::uniform32(any::<u8>())) {
456 let secret = SecretKeyBytes::new(bytes);
457 let encoded = postcard::to_allocvec(&secret).unwrap();
458 prop_assert_eq!(&encoded, &postcard::to_allocvec(&bytes).unwrap());
459
460 let decoded: SecretKeyBytes = postcard::from_bytes(&encoded).unwrap();
461 prop_assert_eq!(decoded.expose_secret(), &bytes);
462 }
463
464 #[test]
465 fn debug_output_is_redacted(bytes in prop::array::uniform32(any::<u8>())) {
466 let debug = format!("{:?}", SecretKeyBytes::new(bytes));
467 prop_assert_eq!(debug.as_str(), "SecretKeyBytes(..)");
468 }
469
470 #[test]
471 fn equality_matches_byte_equality(
472 lhs in prop::array::uniform32(any::<u8>()),
473 rhs in prop::array::uniform32(any::<u8>()),
474 ) {
475 prop_assert_eq!(SecretKeyBytes::new(lhs) == SecretKeyBytes::new(rhs), lhs == rhs);
476 prop_assert!(SecretKeyBytes::new(lhs) == SecretKeyBytes::new(lhs));
477 }
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use alloc::collections::BTreeMap;
484
485 use zcash_protocol::{
486 consensus::BranchId,
487 constants::{V5_TX_VERSION, V5_VERSION_GROUP_ID, mainnet},
488 };
489
490 use super::{FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE, Global};
491
492 #[test]
493 fn global_getters() {
494 // Distinct values, so that a getter reading the wrong field is caught.
495 const FALLBACK_LOCK_TIME: u32 = 1;
496 const EXPIRY_HEIGHT: u32 = 2;
497 const TX_MODIFIABLE: u8 = FLAG_TRANSPARENT_INPUTS_MODIFIABLE | FLAG_SHIELDED_MODIFIABLE;
498
499 let global = Global {
500 tx_version: V5_TX_VERSION,
501 version_group_id: V5_VERSION_GROUP_ID,
502 consensus_branch_id: BranchId::Nu6.into(),
503 fallback_lock_time: Some(FALLBACK_LOCK_TIME),
504 expiry_height: EXPIRY_HEIGHT,
505 coin_type: mainnet::COIN_TYPE,
506 tx_modifiable: TX_MODIFIABLE,
507 proprietary: BTreeMap::new(),
508 };
509
510 assert_eq!(global.coin_type(), &mainnet::COIN_TYPE);
511 assert_eq!(global.fallback_lock_time(), &Some(FALLBACK_LOCK_TIME));
512 assert_eq!(global.tx_modifiable(), &TX_MODIFIABLE);
513
514 // The raw bitfield agrees with the semantic flag accessors.
515 assert!(global.inputs_modifiable());
516 assert!(!global.outputs_modifiable());
517 assert!(!global.has_sighash_single());
518 assert!(global.shielded_modifiable());
519 }
520
521 #[test]
522 fn tx_modifiable() {
523 let base = Global {
524 tx_version: 0,
525 version_group_id: 0,
526 consensus_branch_id: 0,
527 fallback_lock_time: None,
528 expiry_height: 0,
529 coin_type: 0,
530 tx_modifiable: 0b0000_0000,
531 proprietary: BTreeMap::new(),
532 };
533
534 for (left, right, expected) in [
535 (0b0000_0000, 0b0000_0000, Some(0b0000_0000)),
536 (0b0000_0000, 0b0000_0011, Some(0b0000_0000)),
537 (0b0000_0001, 0b0000_0011, Some(0b0000_0001)),
538 (0b0000_0010, 0b0000_0011, Some(0b0000_0010)),
539 (0b0000_0011, 0b0000_0011, Some(0b0000_0011)),
540 (0b0000_0000, 0b0000_0100, Some(0b0000_0100)),
541 (0b0000_0100, 0b0000_0100, Some(0b0000_0100)),
542 (0b0000_0011, 0b0000_0111, Some(0b0000_0111)),
543 (0b0000_0000, 0b0000_1000, None),
544 (0b0000_0000, 0b0001_0000, None),
545 (0b0000_0000, 0b0010_0000, None),
546 (0b0000_0000, 0b0100_0000, None),
547 (0b0000_0000, 0b1000_0000, Some(0b0000_0000)),
548 (0b1000_0000, 0b1000_0000, Some(0b1000_0000)),
549 ] {
550 let mut a = base.clone();
551 a.tx_modifiable = left;
552
553 let mut b = base.clone();
554 b.tx_modifiable = right;
555
556 assert_eq!(
557 a.clone()
558 .merge(b.clone())
559 .map(|global| global.tx_modifiable),
560 expected
561 );
562 assert_eq!(b.merge(a).map(|global| global.tx_modifiable), expected);
563 }
564 }
565}