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