use core::fmt::{self, Display, Formatter};
use core::str::FromStr;
use bitcoin::blockdata::transaction::ParseOutPointError;
use bitcoin::hashes::sha256;
use bitcoin::util::bip32;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::{EcdsaSighashType as SighashType, OutPoint};
use bitcoin_blockchain::locks::{self, SeqNo};
use bitcoin_hd::{DerivationSubpath, UnhardenedIndex};
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(StrictEncode, StrictDecode)]
pub struct InputDescriptor {
pub outpoint: OutPoint,
pub terminal: DerivationSubpath<UnhardenedIndex>,
pub seq_no: SeqNo,
pub tweak: Option<(Fingerprint, sha256::Hash)>,
pub sighash_type: SighashType,
}
impl Display for InputDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.outpoint, f)?;
f.write_str(" ")?;
Display::fmt(&self.terminal, f)?;
if let Some((fingerprint, tweak)) = self.tweak {
f.write_str(" ")?;
Display::fmt(&fingerprint, f)?;
Display::fmt(&tweak, f)?;
}
if self.seq_no != SeqNo::unencumbered(true) {
f.write_str(" ")?;
Display::fmt(&self.seq_no, f)?;
}
if self.sighash_type != SighashType::All {
f.write_str(" ")?;
Display::fmt(&self.sighash_type, f)?;
}
Ok(())
}
}
#[derive(Clone, PartialEq, Eq, Debug, Display, From)]
#[display(doc_comments)]
pub enum ParseError {
#[from]
InvalidSeqNo(locks::ParseError),
InvalidSighash(String),
#[from]
InvalidDerivation(bip32::Error),
#[from]
InvalidTweak(bitcoin::hashes::hex::Error),
#[from]
InvalidOutpoint(ParseOutPointError),
InvalidTweakFormat(String),
NoOutpoint,
NoDerivation,
UnrecognizedFragment(String),
}
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseError::InvalidSeqNo(err) => Some(err),
ParseError::InvalidSighash(_) => None,
ParseError::InvalidDerivation(err) => Some(err),
ParseError::InvalidTweak(err) => Some(err),
ParseError::InvalidOutpoint(err) => Some(err),
ParseError::InvalidTweakFormat(_) => None,
ParseError::NoOutpoint => None,
ParseError::NoDerivation => None,
ParseError::UnrecognizedFragment(_) => None,
}
}
}
impl FromStr for InputDescriptor {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split_whitespace();
let outpoint = split.next().ok_or(ParseError::NoOutpoint)?;
let derivation = split.next().ok_or(ParseError::NoDerivation)?;
let mut d = InputDescriptor {
outpoint: outpoint.parse()?,
terminal: derivation.parse()?,
seq_no: none!(),
tweak: None,
sighash_type: SighashType::All,
};
for fragment in split {
if let Ok(seq_no) = SeqNo::from_str(fragment) {
d.seq_no = seq_no;
} else if let Ok(sighash_type) = SighashType::from_str(fragment) {
d.sighash_type = sighash_type;
} else if fragment.contains(':') {
let mut split = fragment.split(':');
d.tweak = match (split.next(), split.next(), split.next()) {
(Some(""), _, _) => None,
(Some(fingerprint), Some(tweak), None) => {
Some((fingerprint.parse()?, tweak.parse()?))
}
(_, _, _) => return Err(ParseError::InvalidTweakFormat(fragment.to_owned())),
}
} else {
return Err(ParseError::UnrecognizedFragment(fragment.to_owned()));
}
}
Ok(d)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn display_from_str() {
let input = InputDescriptor {
outpoint: "9a035b0e6e9d07065a31c49884cb1c2d8953636346e91948df75b20e27f50f24:8"
.parse()
.unwrap(),
terminal: "/1/167".parse().unwrap(),
seq_no: "rbf(1)".parse().unwrap(),
tweak: None,
sighash_type: SighashType::AllPlusAnyoneCanPay,
};
assert_eq!(
input.to_string(),
"9a035b0e6e9d07065a31c49884cb1c2d8953636346e91948df75b20e27f50f24:8 /1/167 rbf(1) \
SIGHASH_ALL|SIGHASH_ANYONECANPAY"
);
assert_eq!(
input,
"9a035b0e6e9d07065a31c49884cb1c2d8953636346e91948df75b20e27f50f24:8 /1/167 rbf(1) \
SIGHASH_ALL|SIGHASH_ANYONECANPAY"
.parse()
.unwrap()
);
}
}