Skip to main content

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}