1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Descriptor wallet library extending bitcoin & miniscript functionality
// by LNP/BP Association (https://lnp-bp.org)
// Written in 2020-2021 by
//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the Apache-2.0 License
// along with this software.
// If not, see <https://opensource.org/licenses/Apache-2.0>.

use bitcoin::{Transaction, TxIn, TxOut, Txid};

use crate::{Input, Psbt};

/// Errors happening when PSBT or other resolver information does not match the
/// structure of bitcoin transaction
#[derive(
    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Error
)]
#[display(doc_comments)]
pub enum InputMatchError {
    /// no `witness_utxo` and `non_witness_utxo` is provided
    NoInputTx,

    /// provided `non_witness_utxo` does not match transaction input `prev_out`
    NoTxidMatch(Txid),

    /// spent transaction does not contain input #{0} referenced by the PSBT
    /// input
    UnmatchedInputNumber(u32),
}

/// API for accessing previous transaction output data
pub trait InputPrevout {
    /// Returns [`TxOut`] reference returned by resolver, if any, or reports
    /// specific matching error prevented from getting the output
    fn input_prevout(&self, txin: &TxIn) -> Result<&TxOut, InputMatchError>;
}

/// Errors happening during fee computation
#[derive(
    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Error, From
)]
#[display(doc_comments)]
pub enum FeeError {
    /// No input source information found because of wrong or incomplete PSBT
    /// structure
    #[from]
    MatchError(InputMatchError),

    /// Sum of inputs is less than sum of outputs
    InputsLessThanOutputs,
}

/// Fee computing resolver
pub trait Fee {
    /// Returns fee for a transaction, or returns error reporting resolver
    /// problem or wrong transaction structure
    fn fee(&self) -> Result<u64, FeeError>;
}

impl InputPrevout for Input {
    fn input_prevout(&self, txin: &TxIn) -> Result<&TxOut, InputMatchError> {
        let txid = txin.previous_output.txid;
        if let Some(txout) = &self.witness_utxo {
            Ok(txout)
        } else if let Some(tx) = &self.non_witness_utxo {
            if tx.txid() != txid {
                return Err(InputMatchError::NoTxidMatch(txid));
            }
            let prev_index = txin.previous_output.vout;
            tx.output
                .get(prev_index as usize)
                .ok_or(InputMatchError::UnmatchedInputNumber(prev_index))
        } else {
            Err(InputMatchError::NoInputTx)
        }
    }
}

impl Fee for Psbt {
    fn fee(&self) -> Result<u64, FeeError> {
        let mut input_sum = 0;
        for (inp, txin) in self.inputs.iter().zip(self.global.unsigned_tx.input.iter()) {
            input_sum += inp.input_prevout(txin)?.value;
        }

        let output_sum = self
            .global
            .unsigned_tx
            .output
            .iter()
            .map(|txout| txout.value)
            .sum();

        if input_sum < output_sum {
            Err(FeeError::InputsLessThanOutputs)
        } else {
            Ok(input_sum - output_sum)
        }
    }
}

/// Transaction-related PSBT extension trait
pub trait Tx {
    /// Returns transaction ID for an unsigned transaction. For SegWit
    /// transactions this is equal to the signed transaction id.
    #[inline]
    fn to_txid(&self) -> Txid { self.to_transaction().txid() }

    /// Returns transaction with empty `scriptSig` and witness
    fn to_transaction(&self) -> Transaction;
}

impl Tx for Psbt {
    #[inline]
    fn to_transaction(&self) -> Transaction {
        let mut tx = self.global.unsigned_tx.clone();
        for txin in &mut tx.input {
            txin.witness = Default::default();
            txin.script_sig = Default::default();
        }
        tx
    }
}