Skip to main content

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}