diff --git a/pczt/src/roles/combiner/mod.rs b/pczt/src/roles/combiner/mod.rs index 9a2cadb17..efe3793e8 100644 --- a/pczt/src/roles/combiner/mod.rs +++ b/pczt/src/roles/combiner/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use crate::Pczt; pub struct Combiner { @@ -54,6 +56,29 @@ pub(crate) fn merge_optional(lhs: &mut Option, rhs: Option) true } +/// Merges two maps together. +/// +/// Returns `false` if the values cannot be merged. +pub(crate) fn merge_map( + lhs: &mut BTreeMap, + rhs: BTreeMap, +) -> bool { + for (key, rhs_value) in rhs.into_iter() { + if let Some(lhs_value) = lhs.get_mut(&key) { + // If the key is present in both maps, and their values are not equal, fail. + // Here we differ from BIP 174. + if lhs_value != &rhs_value { + return false; + } + } else { + lhs.insert(key, rhs_value); + } + } + + // Success! + true +} + /// Errors that can occur while combining PCZTs. #[derive(Debug)] pub enum Error { diff --git a/pczt/src/transparent.rs b/pczt/src/transparent.rs index 7e2a6f12c..0a831ec2c 100644 --- a/pczt/src/transparent.rs +++ b/pczt/src/transparent.rs @@ -1,4 +1,6 @@ -use crate::roles::combiner::merge_optional; +use std::collections::BTreeMap; + +use crate::roles::combiner::{merge_map, merge_optional}; #[cfg(feature = "transparent")] use { @@ -58,6 +60,51 @@ pub(crate) struct Input { // needed for computing the binding signatures. pub(crate) value: u64, pub(crate) script_pubkey: Vec, + + /// The script required to spend this output, if it is P2SH. + /// + /// Set to `None` if this is a P2PKH output. + pub(crate) redeem_script: Option>, + + /// A map from a pubkey to a signature created by it. + /// + /// - Each pubkey should appear in `script_pubkey` or `redeem_script`. + /// - Each entry is set by a Signer, and should contain an ECDSA signature that is + /// valid under the corresponding pubkey. + /// - These are required by the Spend Finalizer to assemble `script_sig`. + pub(crate) partial_signatures: BTreeMap<[u8; 33], Vec>, + + /// The sighash type to be used for this input. + /// + /// - Signers must use this sighash type to produce their signatures. Signers that + /// cannot produce signatures for this sighash type must not provide a signature. + /// - Spend Finalizers must fail to finalize inputs which have signatures not matching + /// this sighash type. + pub(crate) sighash_type: u8, + + /// Mappings of the form `key = RIPEMD160(value)`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub(crate) ripemd160_preimages: BTreeMap<[u8; 20], Vec>, + + /// Mappings of the form `key = SHA256(value)`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub(crate) sha256_preimages: BTreeMap<[u8; 32], Vec>, + + /// Mappings of the form `key = RIPEMD160(SHA256(value))`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub(crate) hash160_preimages: BTreeMap<[u8; 20], Vec>, + + /// Mappings of the form `key = SHA256(SHA256(value))`. + /// + /// - These may be used by the Signer to inspect parts of `script_pubkey` or + /// `redeem_script`. + pub(crate) hash256_preimages: BTreeMap<[u8; 32], Vec>, } #[derive(Clone, Debug)] @@ -70,6 +117,11 @@ pub(crate) struct Output { // pub(crate) value: u64, pub(crate) script_pubkey: Vec, + + /// The script required to spend this output, if it is P2SH. + /// + /// Set to `None` if this is a P2PKH output. + pub(crate) redeem_script: Option>, } impl Bundle { @@ -101,12 +153,20 @@ impl Bundle { script_sig, value, script_pubkey, + redeem_script, + partial_signatures, + sighash_type, + ripemd160_preimages, + sha256_preimages, + hash160_preimages, + hash256_preimages, } = rhs; if lhs.prevout_txid != prevout_txid || lhs.prevout_index != prevout_index || lhs.value != value || lhs.script_pubkey != script_pubkey + || lhs.sighash_type != sighash_type { return None; } @@ -117,7 +177,13 @@ impl Bundle { &mut lhs.required_height_lock_time, required_height_lock_time, ) - && merge_optional(&mut lhs.script_sig, script_sig)) + && merge_optional(&mut lhs.script_sig, script_sig) + && merge_optional(&mut lhs.redeem_script, redeem_script) + && merge_map(&mut lhs.partial_signatures, partial_signatures) + && merge_map(&mut lhs.ripemd160_preimages, ripemd160_preimages) + && merge_map(&mut lhs.sha256_preimages, sha256_preimages) + && merge_map(&mut lhs.hash160_preimages, hash160_preimages) + && merge_map(&mut lhs.hash256_preimages, hash256_preimages)) { return None; } @@ -128,11 +194,16 @@ impl Bundle { let Output { value, script_pubkey, + redeem_script, } = rhs; if lhs.value != value || lhs.script_pubkey != script_pubkey { return None; } + + if !merge_optional(&mut lhs.redeem_script, redeem_script) { + return None; + } } Some(self)