psbt_v2/v0/mod.rs
1// SPDX-License-Identifier: CC0-1.0
2
3//! Partially Signed Bitcoin Transactions Version 0.
4//!
5//! This module is code copied from [`rust-bitcoin`] and [`rust-miniscript`],
6//! specifically `v0.32.8` and `v12.3.5` respectively. Only bare minimal changes
7//! to make it build were made.
8//!
9//! [`rust-bitcoin`]: <https://github.com/rust-bitcoin/rust-bitcoin>
10//! [`rust-miniscript`]: <https://github.com/rust-bitcoin/rust-miniscript>
11
12/// Import of the [`bitcoin::psbt`] module.
13///
14/// [`bitcoin::psbt`]: <https://docs.rs/bitcoin/0.32.2/bitcoin/psbt/index.html>
15pub mod bitcoin;
16
17/// Import of the [`miniscript::psbt`] module.
18///
19/// [`miniscript::psbt`]: <https://docs.rs/miniscript/12.2.0/miniscript/psbt/index.html>
20#[cfg(feature = "miniscript")]
21pub mod miniscript;
22
23use core::fmt;
24
25use ::bitcoin::ScriptBuf;
26
27use crate::v0::bitcoin::OutputType;
28
29#[rustfmt::skip] // Keep public exports separate.
30#[doc(inline)]
31pub use self::bitcoin::{Psbt, Input, Output};
32
33// New stuff not found from `rust-bitcoin` or `rust-miniscript`
34impl Psbt {
35 /// Returns `Ok` if PSBT is
36 ///
37 /// From BIP-174:
38 ///
39 /// For a Signer to only produce valid signatures for what it expects to sign, it must check that the following conditions are true:
40 ///
41 /// - If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
42 /// - If a witness UTXO is provided, no non-witness signature may be created
43 /// - If a redeemScript is provided, the scriptPubKey must be for that redeemScript
44 /// - If a witnessScript is provided, the scriptPubKey or the redeemScript must be for that witnessScript
45 /// - If a sighash type is provided, the signer must check that the sighash is acceptable. If unacceptable, they must fail.
46 /// - If a sighash type is not provided, the signer should sign using SIGHASH_ALL, but may use any sighash type they wish.
47 pub fn signer_checks(&self) -> Result<(), SignerChecksError> {
48 let unsigned_tx = &self.unsigned_tx;
49 for (i, input) in self.inputs.iter().enumerate() {
50 if input.witness_utxo.is_some() {
51 match self.output_type(i) {
52 Ok(OutputType::Bare) => return Err(SignerChecksError::NonWitnessSig),
53 Ok(_) => {}
54 Err(_) => {} // TODO: Is this correct?
55 }
56 }
57
58 if let Some(ref tx) = input.non_witness_utxo {
59 if tx.compute_txid() != unsigned_tx.input[i].previous_output.txid {
60 return Err(SignerChecksError::NonWitnessUtxoTxidMismatch);
61 }
62 }
63
64 if let Some(ref redeem_script) = input.redeem_script {
65 match input.witness_utxo {
66 Some(ref tx_out) => {
67 let script_pubkey = ScriptBuf::new_p2sh(&redeem_script.script_hash());
68 if tx_out.script_pubkey != script_pubkey {
69 return Err(SignerChecksError::RedeemScriptMismatch);
70 }
71 }
72 None => return Err(SignerChecksError::MissingTxOut),
73 }
74 }
75
76 if let Some(ref witness_script) = input.witness_script {
77 match input.witness_utxo {
78 Some(ref utxo) => {
79 let script_pubkey = &utxo.script_pubkey;
80 if script_pubkey.is_p2wsh() {
81 if ScriptBuf::new_p2wsh(&witness_script.wscript_hash())
82 != *script_pubkey
83 {
84 return Err(SignerChecksError::WitnessScriptMismatchWsh);
85 }
86 } else if script_pubkey.is_p2sh() {
87 if let Some(ref redeem_script) = input.redeem_script {
88 if ScriptBuf::new_p2wsh(&redeem_script.wscript_hash())
89 != *script_pubkey
90 {
91 return Err(SignerChecksError::WitnessScriptMismatchShWsh);
92 }
93 }
94 } else {
95 // BIP does not specifically say there should not be a witness script here?
96 }
97 }
98 None => return Err(SignerChecksError::MissingTxOut),
99 }
100 }
101
102 if let Some(_sighash_type) = input.sighash_type {
103 // TODO: Check that sighash is accetable, what does that mean?
104 {}
105 }
106 }
107 Ok(())
108 }
109}
110
111/// Errors encountered while doing the signer checks.
112#[derive(Debug, Clone, PartialEq, Eq)]
113#[non_exhaustive]
114pub enum SignerChecksError {
115 /// Witness input will produce a non-witness signature.
116 NonWitnessSig,
117 /// Non-witness input has a mismatch between the txid and prevout txid.
118 NonWitnessUtxoTxidMismatch,
119 /// Input has both witness and non-witness utxos.
120 WitnessAndNonWitnessUtxo,
121 /// Redeem script hash did not match the hash in the script_pubkey.
122 RedeemScriptMismatch,
123 /// Missing witness_utxo.
124 MissingTxOut,
125 /// Native segwit p2wsh script_pubkey did not match witness script hash.
126 WitnessScriptMismatchWsh,
127 /// Nested segwit p2wsh script_pubkey did not match redeem script hash.
128 WitnessScriptMismatchShWsh,
129}
130
131impl fmt::Display for SignerChecksError {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 use SignerChecksError::*;
134
135 match *self {
136 NonWitnessSig => write!(f, "witness input will produce a non-witness signature"),
137 NonWitnessUtxoTxidMismatch =>
138 write!(f, "non-witness input has a mismatch between the txid and prevout txid"),
139 WitnessAndNonWitnessUtxo => write!(f, "input has both witness and non-witness utxos"),
140 RedeemScriptMismatch =>
141 write!(f, "redeem script hash did not match the hash in the script_pubkey"),
142 MissingTxOut => write!(f, "missing witness_utxo"),
143 WitnessScriptMismatchWsh =>
144 write!(f, "native segwit p2wsh script_pubkey did not match witness script hash"),
145 WitnessScriptMismatchShWsh =>
146 write!(f, "nested segwit p2wsh script_pubkey did not match redeem script hash"),
147 }
148 }
149}
150
151#[cfg(feature = "std")]
152impl std::error::Error for SignerChecksError {
153 // TODO: Match explicitly.
154 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
155}