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