Skip to content

Commit

Permalink
Merge pull request #13 from tcharding/12-18-errors
Browse files Browse the repository at this point in the history
Epic error rework
  • Loading branch information
tcharding authored Dec 21, 2023
2 parents 0d268e1 + da178cb commit 1755939
Show file tree
Hide file tree
Showing 24 changed files with 1,831 additions and 1,460 deletions.
4 changes: 2 additions & 2 deletions examples/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl Alice {
// The dummy input utxo we are spending and the pubkey/keysource that will be used to sign it.
input.witness_utxo = Some(self.input_utxo()?);
let (pk, key_source) = self.bip32_derivation()?;
input.bip32_derivation.insert(pk, key_source);
input.bip32_derivations.insert(pk, key_source);
Ok(psbt)
}

Expand Down Expand Up @@ -219,7 +219,7 @@ impl Bob {
// The dummy input utxo we are spending and the pubkey/keysource that will be used to sign it.
input.witness_utxo = Some(self.input_utxo()?);
let (pk, key_source) = self.bip32_derivation()?;
input.bip32_derivation.insert(pk, key_source);
input.bip32_derivations.insert(pk, key_source);
Ok(psbt)
}

Expand Down
63 changes: 63 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,66 @@ pub(crate) const PSBT_OUT_TAP_TREE: u8 = 0x06;
pub(crate) const PSBT_OUT_TAP_BIP32_DERIVATION: u8 = 0x07;
/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC
pub(crate) const PSBT_OUT_PROPRIETARY: u8 = 0xFC;

/// Converts a global key type value consts to a string, useful for debugging.
pub(crate) fn psbt_global_key_type_value_to_str(v: u8) -> &'static str {
match v {
PSBT_GLOBAL_UNSIGNED_TX => "PSBT_GLOBAL_UNSIGNED_TX",
PSBT_GLOBAL_XPUB => "PSBT_GLOBAL_XPUB",
PSBT_GLOBAL_TX_VERSION => "PSBT_GLOBAL_TX_VERSION",
PSBT_GLOBAL_FALLBACK_LOCKTIME => "PSBT_GLOBAL_FALLBACK_LOCKTIME",
PSBT_GLOBAL_INPUT_COUNT => "PSBT_GLOBAL_INPUT_COUNT",
PSBT_GLOBAL_OUTPUT_COUNT => "PSBT_GLOBAL_OUTPUT_COUNT",
PSBT_GLOBAL_TX_MODIFIABLE => "PSBT_GLOBAL_TX_MODIFIABLE",
PSBT_GLOBAL_VERSION => "PSBT_GLOBAL_VERSION",
PSBT_GLOBAL_PROPRIETARY => "PSBT_GLOBAL_PROPRIETARY",
_ => "unknown PSBT_GLOBAL_ key type value",
}
}

pub(crate) fn psbt_in_key_type_value_to_str(v: u8) -> &'static str {
match v {
PSBT_IN_NON_WITNESS_UTXO => "PSBT_IN_NON_WITNESS_UTXO",
PSBT_IN_WITNESS_UTXO => "PSBT_IN_WITNESS_UTXO",
PSBT_IN_PARTIAL_SIG => "PSBT_IN_PARTIAL_SIG",
PSBT_IN_SIGHASH_TYPE => "PSBT_IN_SIGHASH_TYPE",
PSBT_IN_REDEEM_SCRIPT => "PSBT_IN_REDEEM_SCRIPT",
PSBT_IN_WITNESS_SCRIPT => "PSBT_IN_WITNESS_SCRIPT",
PSBT_IN_BIP32_DERIVATION => "PSBT_IN_BIP32_DERIVATION",
PSBT_IN_FINAL_SCRIPTSIG => "PSBT_IN_FINAL_SCRIPTSIG",
PSBT_IN_FINAL_SCRIPTWITNESS => "PSBT_IN_FINAL_SCRIPTWITNESS",
PSBT_IN_POR_COMMITMENT => "PSBT_IN_POR_COMMITMENT",
PSBT_IN_RIPEMD160 => "PSBT_IN_RIPEMD160",
PSBT_IN_SHA256 => "PSBT_IN_SHA256",
PSBT_IN_HASH160 => "PSBT_IN_HASH160",
PSBT_IN_HASH256 => "PSBT_IN_HASH256",
PSBT_IN_PREVIOUS_TXID => "PSBT_IN_PREVIOUS_TXID",
PSBT_IN_OUTPUT_INDEX => "PSBT_IN_OUTPUT_INDEX",
PSBT_IN_SEQUENCE => "PSBT_IN_SEQUENCE",
PSBT_IN_REQUIRED_TIME_LOCKTIME => "PSBT_IN_REQUIRED_TIME_LOCKTIME",
PSBT_IN_REQUIRED_HEIGHT_LOCKTIME => "PSBT_IN_REQUIRED_HEIGHT_LOCKTIME",
PSBT_IN_TAP_KEY_SIG => "PSBT_IN_TAP_KEY_SIG",
PSBT_IN_TAP_SCRIPT_SIG => "PSBT_IN_TAP_SCRIPT_SIG",
PSBT_IN_TAP_LEAF_SCRIPT => "PSBT_IN_TAP_LEAF_SCRIPT",
PSBT_IN_TAP_BIP32_DERIVATION => "PSBT_IN_TAP_BIP32_DERIVATION",
PSBT_IN_TAP_INTERNAL_KEY => "PSBT_IN_TAP_INTERNAL_KEY",
PSBT_IN_TAP_MERKLE_ROOT => "PSBT_IN_TAP_MERKLE_ROOT",
PSBT_IN_PROPRIETARY => "PSBT_IN_PROPRIETARY",
_ => "unknown PSBT_IN_ key type value",
}
}

pub(crate) fn psbt_out_key_type_value_to_str(v: u8) -> &'static str {
match v {
PSBT_OUT_REDEEM_SCRIPT => "PSBT_OUT_REDEEM_SCRIPT",
PSBT_OUT_WITNESS_SCRIPT => "PSBT_OUT_WITNESS_SCRIPT",
PSBT_OUT_BIP32_DERIVATION => "PSBT_OUT_BIP32_DERIVATION",
PSBT_OUT_AMOUNT => "PSBT_OUT_AMOUNT",
PSBT_OUT_SCRIPT => "PSBT_OUT_SCRIPT",
PSBT_OUT_TAP_INTERNAL_KEY => "PSBT_OUT_TAP_INTERNAL_KEY",
PSBT_OUT_TAP_TREE => "PSBT_OUT_TAP_TREE",
PSBT_OUT_TAP_BIP32_DERIVATION => "PSBT_OUT_TAP_BIP32_DERIVATION",
PSBT_OUT_PROPRIETARY => "PSBT_OUT_PROPRIETARY",
_ => "unknown PSBT_OUT_ key type value",
}
}
272 changes: 68 additions & 204 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,238 +3,102 @@
use core::fmt;

use bitcoin::bip32::Xpub;
// TODO: This should be exposed like this in rust-bitcoin.
use bitcoin::consensus::encode as consensus;
use bitcoin::transaction::Transaction;
use bitcoin::{absolute, hashes, secp256k1, taproot};

use crate::prelude::*;
use crate::{io, raw, version};
/// Error combining two PSBTs, global extended public key has inconsistent key sources.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct InconsistentKeySourcesError(pub Xpub);

impl fmt::Display for InconsistentKeySourcesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "combining PSBT, key-source conflict for xpub {}", self.0)
}
}

/// Enum for marking psbt hash error.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum PsbtHash {
Ripemd,
Sha256,
Hash160,
Hash256,
#[cfg(feature = "std")]
impl std::error::Error for InconsistentKeySourcesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Ways that a Partially Signed Transaction might fail.
// TODO: This general error needs splitting up into specific error types.
#[derive(Debug)]
/// An error while calculating the fee.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most
/// significant byte order.
InvalidMagic,
/// Missing both the witness and non-witness utxo.
MissingUtxo,
/// The separator for a PSBT must be `0xff`.
InvalidSeparator,
/// Returned when output index is out of bounds in relation to the output in non-witness UTXO.
PsbtUtxoOutOfbounds,
/// Known keys must be according to spec.
InvalidKey(raw::Key),
/// Non-proprietary key type found when proprietary key was expected
InvalidProprietaryKey,
/// Keys within key-value map should never be duplicated.
DuplicateKey(raw::Key),
/// The scriptSigs for the unsigned transaction must be empty.
UnsignedTxHasScriptSigs,
/// The scriptWitnesses for the unsigned transaction must be empty.
UnsignedTxHasScriptWitnesses,
/// A PSBT must have an unsigned transaction.
MustHaveUnsignedTx,
/// Signals that there are no more key-value pairs in a key-value map.
NoMorePairs,
/// Attempting to combine with a PSBT describing a different unsigned
/// transaction.
UnexpectedUnsignedTx {
/// Expected
expected: Box<Transaction>,
/// Actual
actual: Box<Transaction>,
},
/// Unable to parse as a standard sighash type.
NonStandardSighashType(u32),
/// Invalid hash when parsing slice.
InvalidHash(hashes::FromSliceError),
/// The pre-image must hash to the correponding psbt hash
InvalidPreimageHashPair {
/// Hash-type
hash_type: PsbtHash,
/// Pre-image
preimage: Box<[u8]>,
/// Hash value
hash: Box<[u8]>,
},
/// Conflicting data during combine procedure:
/// global extended public key has inconsistent key sources
CombineInconsistentKeySources(Box<Xpub>),
/// Serialization error in bitcoin consensus-encoded structures
ConsensusEncoding(consensus::Error),
/// Negative fee
NegativeFee,
/// Integer overflow in fee calculation
FeeOverflow,
/// Parsing error indicating invalid public keys
InvalidPublicKey(bitcoin::key::Error),
/// Parsing error indicating invalid secp256k1 public keys
InvalidSecp256k1PublicKey(secp256k1::Error),
/// Parsing error indicating invalid xonly public keys
InvalidXOnlyPublicKey,
/// Parsing error indicating invalid ECDSA signatures
InvalidEcdsaSignature(bitcoin::ecdsa::Error),
/// Parsing error indicating invalid taproot signatures
InvalidTaprootSignature(bitcoin::taproot::SigFromSliceError),
/// Parsing error indicating invalid control block
InvalidControlBlock,
/// Parsing error indicating invalid leaf version
InvalidLeafVersion,
/// Parsing error indicating a taproot error
Taproot(&'static str),
/// Taproot tree deserilaization error
TapTree(taproot::IncompleteBuilderError),
/// Error related to an xpub key
XPubKey(&'static str),
/// Error related to PSBT version
Version(&'static str),
/// PSBT data is not consumed entirely
PartialDataConsumption,
/// I/O error.
Io(io::Error),
/// Couldn't converting parsed u32 to a lock time.
LockTime(absolute::Error),
/// Found a keypair type that is explicitly excluded.
ExcludedKey(u8),
/// Unsupported PSBT version.
UnsupportedVersion(version::UnsupportedVersionError),
pub enum FeeError {
/// Funding utxo error for input.
FundingUtxo(FundingUtxoError),
/// Integer overflow in fee calculation adding input.
InputOverflow,
/// Integer overflow in fee calculation adding output.
OutputOverflow,
/// Negative fee.
Negative,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
impl fmt::Display for FeeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FeeError::*;

match *self {
InvalidMagic => f.write_str("invalid magic"),
MissingUtxo => f.write_str("UTXO information is not present in PSBT"),
InvalidSeparator => f.write_str("invalid separator"),
PsbtUtxoOutOfbounds =>
f.write_str("output index is out of bounds of non witness script output array"),
InvalidKey(ref rkey) => write!(f, "invalid key: {}", rkey),
InvalidProprietaryKey =>
write!(f, "non-proprietary key type found when proprietary key was expected"),
DuplicateKey(ref rkey) => write!(f, "duplicate key: {}", rkey),
UnsignedTxHasScriptSigs => f.write_str("the unsigned transaction has script sigs"),
UnsignedTxHasScriptWitnesses =>
f.write_str("the unsigned transaction has script witnesses"),
MustHaveUnsignedTx =>
f.write_str("partially signed transactions must have an unsigned transaction"),
NoMorePairs => f.write_str("no more key-value pairs for this psbt map"),
UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(
f,
"different unsigned transaction: expected {}, actual {}",
e.txid(),
a.txid()
),
NonStandardSighashType(ref sht) => write!(f, "non-standard sighash type: {}", sht),
InvalidHash(ref e) => write_err!(f, "invalid hash when parsing slice"; e),
InvalidPreimageHashPair { ref preimage, ref hash, ref hash_type } => {
// directly using debug forms of psbthash enums
write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash)
}
CombineInconsistentKeySources(ref s) => {
write!(f, "combine conflict: {}", s)
}
ConsensusEncoding(ref e) => write_err!(f, "bitcoin consensus encoding error"; e),
NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"),
FeeOverflow => f.write_str("integer overflow in fee calculation"),
InvalidPublicKey(ref e) => write_err!(f, "invalid public key"; e),
InvalidSecp256k1PublicKey(ref e) => write_err!(f, "invalid secp256k1 public key"; e),
InvalidXOnlyPublicKey => f.write_str("invalid xonly public key"),
InvalidEcdsaSignature(ref e) => write_err!(f, "invalid ECDSA signature"; e),
InvalidTaprootSignature(ref e) => write_err!(f, "invalid taproot signature"; e),
InvalidControlBlock => f.write_str("invalid control block"),
InvalidLeafVersion => f.write_str("invalid leaf version"),
Taproot(s) => write!(f, "taproot error - {}", s),
TapTree(ref e) => write_err!(f, "taproot tree error"; e),
XPubKey(s) => write!(f, "xpub key error - {}", s),
Version(s) => write!(f, "version error {}", s),
PartialDataConsumption =>
f.write_str("data not consumed entirely when explicitly deserializing"),
Io(ref e) => write_err!(f, "I/O error"; e),
LockTime(ref e) => write_err!(f, "parsed locktime invalid"; e),
ExcludedKey(t) =>
write!(f, "found a keypair type that is explicitly excluded: {:x}", t),
UnsupportedVersion(ref e) => write_err!(f, "unsupported version"; e),
FundingUtxo(ref e) => write_err!(f, "funding utxo error for input"; e),
InputOverflow => f.write_str("integer overflow in fee calculation adding input"),
OutputOverflow => f.write_str("integer overflow in fee calculation adding output"),
Negative => f.write_str("PSBT has a negative fee which is not allowed"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {
impl std::error::Error for FeeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
use FeeError::*;

match *self {
InvalidHash(ref e) => Some(e),
ConsensusEncoding(ref e) => Some(e),
Io(ref e) => Some(e),
LockTime(ref e) => Some(e),
UnsupportedVersion(ref e) => Some(e),
InvalidMagic
| MissingUtxo
| InvalidSeparator
| PsbtUtxoOutOfbounds
| InvalidKey(_)
| InvalidProprietaryKey
| DuplicateKey(_)
| UnsignedTxHasScriptSigs
| UnsignedTxHasScriptWitnesses
| MustHaveUnsignedTx
| NoMorePairs
| UnexpectedUnsignedTx { .. }
| NonStandardSighashType(_)
| InvalidPreimageHashPair { .. }
| CombineInconsistentKeySources(_)
| NegativeFee
| FeeOverflow
| InvalidPublicKey(_)
| InvalidSecp256k1PublicKey(_)
| InvalidXOnlyPublicKey
| InvalidEcdsaSignature(_)
| InvalidTaprootSignature(_)
| InvalidControlBlock
| InvalidLeafVersion
| Taproot(_)
| TapTree(_)
| XPubKey(_)
| Version(_)
| PartialDataConsumption
| ExcludedKey(_) => None,
FundingUtxo(ref e) => Some(e),
InputOverflow | OutputOverflow | Negative => None,
}
}
}

impl From<hashes::FromSliceError> for Error {
fn from(e: hashes::FromSliceError) -> Error { Error::InvalidHash(e) }
impl From<FundingUtxoError> for FeeError {
fn from(e: FundingUtxoError) -> Self { Self::FundingUtxo(e) }
}

impl From<consensus::Error> for Error {
fn from(e: consensus::Error) -> Self { Error::ConsensusEncoding(e) }
/// An error getting the funding transaction for this input.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FundingUtxoError {
/// The vout is out of bounds for non-witness transaction.
OutOfBounds {
/// The vout used as list index.
vout: usize,
/// The length of the utxo list.
len: usize,
},
/// No funding utxo found.
MissingUtxo,
}

impl From<io::Error> for Error {
fn from(e: io::Error) -> Self { Error::Io(e) }
}
impl fmt::Display for FundingUtxoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use FundingUtxoError::*;

impl From<absolute::Error> for Error {
fn from(e: absolute::Error) -> Self { Error::LockTime(e) }
match *self {
OutOfBounds { vout, len } =>
write!(f, "vout {} out of bounds for tx list len: {}", vout, len),
MissingUtxo => write!(f, "no funding utxo found"),
}
}
}

impl From<version::UnsupportedVersionError> for Error {
fn from(e: version::UnsupportedVersionError) -> Self { Error::UnsupportedVersion(e) }
#[cfg(feature = "std")]
impl std::error::Error for FundingUtxoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use FundingUtxoError::*;

match *self {
OutOfBounds { .. } | MissingUtxo => None,
}
}
}

/// Formats error.
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ use crate::version::Version;

#[rustfmt::skip] // Keep pubic re-exports separate
pub use crate::{
error::Error,
sighash_type::PsbtSighashType,
};

Expand Down
Loading

0 comments on commit 1755939

Please sign in to comment.