diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index 96a6e04927..1effe0017c 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -28,27 +28,24 @@ //! `bt listunspent` //! +use std::boxed::Box; use std::collections::BTreeMap; use std::fmt; use std::str::FromStr; use bitcoin::consensus::encode; -use bitcoin::hashes::hex::{self, FromHex}; +use bitcoin::hashes::hex::FromHex; use bitcoin::locktime::absolute; use bitcoin::secp256k1::{Secp256k1, Signing, Verification}; -use bitcoin::util::amount::ParseAmountError; use bitcoin::util::bip32::{ - self, ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, - IntoDerivationPath, + ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDerivationPath, }; use bitcoin::util::psbt::{self, Input, Psbt, PsbtSighashType}; use bitcoin::{ - address, Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, Sequence, - Transaction, TxIn, TxOut, Txid, Witness, + Address, Amount, Network, OutPoint, PublicKey, Script, Sequence, Transaction, TxIn, TxOut, + Txid, Witness, }; -use self::psbt_sign::*; - type Result = std::result::Result; // Get this from the output of `bt dumpwallet `. @@ -141,33 +138,14 @@ impl ColdStorage { /// Signs `psbt` with this signer. fn sign_psbt(&self, secp: &Secp256k1, mut psbt: Psbt) -> Result { - let sk = self.private_key_to_sign(secp, &psbt.inputs[0])?; - psbt_sign::sign(&mut psbt, &sk, 0, secp)?; - - Ok(psbt) - } - - /// Returns the private key required to sign `input` if we have it. - fn private_key_to_sign( - &self, - secp: &Secp256k1, - input: &Input, - ) -> Result { - match input.bip32_derivation.iter().next() { - Some((pk, (fingerprint, path))) => { - if *fingerprint != self.master_fingerprint() { - return Err(Error::WrongFingerprint); - } - - let sk = self.master_xpriv.derive_priv(secp, &path)?.to_priv(); - if *pk != sk.public_key(secp).inner { - return Err(Error::WrongPubkey); - } - - Ok(sk) + match psbt.sign(&self.master_xpriv, secp) { + Ok(keys) => assert_eq!(keys.len(), 1), + Err((_, e)) => { + let e = e.get(&0).expect("at least one error"); + return Err(e.clone().into()); } - None => Err(Error::MissingBip32Derivation), - } + }; + Ok(psbt) } } @@ -247,7 +225,7 @@ impl WatchOnly { map.insert(pk.inner, (fingerprint, path)); input.bip32_derivation = map; - let ty = PsbtSighashType::from_str("SIGHASH_ALL").map_err(|_| Error::SighashTypeParse)?; + let ty = PsbtSighashType::from_str("SIGHASH_ALL")?; input.sighash_type = Some(ty); psbt.inputs = vec![input]; @@ -260,11 +238,10 @@ impl WatchOnly { use bitcoin::util::psbt::serialize::Serialize; if psbt.inputs.is_empty() { - return Err(Error::InputsEmpty); + return Err(psbt::SignError::MissingInputUtxo.into()); } let sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect(); - let mut script_witness: Witness = Witness::new(); script_witness.push(&sigs[0].serialize()); script_witness.push(self.input_xpub.to_pub().serialize()); @@ -313,301 +290,12 @@ fn previous_output() -> TxOut { TxOut { value: amount.to_sat(), script_pubkey } } -#[derive(Clone, Debug, PartialEq, Eq)] -enum Error { - /// Bip32 error. - Bip32(bip32::Error), - /// PSBT error. - Psbt(psbt::Error), - /// PSBT sighash error. - PsbtSighash(SighashError), - /// Bitcoin_hashes hex error. - Hex(hex::Error), - /// Address error. - Address(address::Error), - /// Parse amount error. - ParseAmount(ParseAmountError), - /// Parsing sighash type string failed. - SighashTypeParse, - /// PSBT inputs field is empty. - InputsEmpty, - /// BIP32 data missing. - MissingBip32Derivation, - /// Fingerprint does not match that in input. - WrongFingerprint, - /// Pubkey for derivation path does not match that in input. - WrongPubkey, -} +struct Error(Box); -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +impl From for Error { + fn from(e: T) -> Self { Error(Box::new(e)) } } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } -} - -impl From for Error { - fn from(e: bip32::Error) -> Error { Error::Bip32(e) } -} - -impl From for Error { - fn from(e: psbt::Error) -> Error { Error::Psbt(e) } -} - -impl From for Error { - fn from(e: SighashError) -> Error { Error::PsbtSighash(e) } -} - -impl From for Error { - fn from(e: hex::Error) -> Error { Error::Hex(e) } -} - -impl From for Error { - fn from(e: address::Error) -> Error { Error::Address(e) } -} - -impl From for Error { - fn from(e: ParseAmountError) -> Error { Error::ParseAmount(e) } -} - -/// This module implements signing a PSBT. It is based on code in `rust-miniscript` with a bit of a -/// look at `bdk` as well. Since this example only uses ECDSA signatures the signing code is -/// sufficient however before we can merge this into the main `rust-bitcoin` crate we need to handle -/// taproot as well. See PR: https://github.com/rust-bitcoin/rust-bitcoin/pull/957 -/// -/// All functions that take a `psbt` argument should be implemented on `Psbt` and use `self` instead. -mod psbt_sign { - use std::fmt; - use std::ops::Deref; - - use bitcoin::psbt::{Input, Prevouts, Psbt, PsbtSighashType}; - use bitcoin::sighash::{self, EcdsaSighashType, SchnorrSighashType, SighashCache}; - use bitcoin::util::taproot::TapLeafHash; - use bitcoin::{EcdsaSig, EcdsaSigError, PrivateKey, Script, Transaction, TxOut}; - use secp256k1::{Message, Secp256k1, Signing}; - - /// Signs the input at `input_index` with private key `sk`. - pub fn sign( - psbt: &mut Psbt, - sk: &PrivateKey, - input_index: usize, - secp: &Secp256k1, - ) -> Result<(), SighashError> { - check_index_is_within_bounds(psbt, input_index)?; - - let mut cache = SighashCache::new(&psbt.unsigned_tx); - let (msg, sighash_ty) = sighash(psbt, input_index, &mut cache, None)?; - - let sig = secp.sign_ecdsa(&msg, &sk.inner); - - let mut final_signature = Vec::with_capacity(75); - final_signature.extend_from_slice(&sig.serialize_der()); - final_signature.push(sighash_ty.to_u32() as u8); - - let pk = sk.public_key(secp); - psbt.inputs[input_index].partial_sigs.insert(pk, EcdsaSig::from_slice(&final_signature)?); - - Ok(()) - } - - /// Returns the sighash message to sign along with the sighash type. - fn sighash>( - psbt: &Psbt, - input_index: usize, - cache: &mut SighashCache, - tapleaf_hash: Option, - ) -> Result<(Message, PsbtSighashType), SighashError> { - check_index_is_within_bounds(psbt, input_index)?; - - let input = &psbt.inputs[input_index]; - let prevouts = prevouts(psbt)?; - - let utxo = spend_utxo(psbt, input_index)?; - let script = utxo.script_pubkey.clone(); // scriptPubkey for input spend utxo. - - if script.is_v1_p2tr() { - return taproot_sighash(input, prevouts, input_index, cache, tapleaf_hash); - } - - let hash_ty = input - .sighash_type - .map(|ty| ty.ecdsa_hash_ty()) - .unwrap_or(Ok(EcdsaSighashType::All)) - .map_err(|_| SighashError::InvalidSighashType)?; // Only support standard sighash types. - - let is_wpkh = script.is_v0_p2wpkh(); - let is_wsh = script.is_v0_p2wsh(); - - let is_nested_wpkh = script.is_p2sh() - && input.redeem_script.as_ref().map(|s| s.is_v0_p2wpkh()).unwrap_or(false); - - let is_nested_wsh = script.is_p2sh() - && input.redeem_script.as_ref().map(|x| x.is_v0_p2wsh()).unwrap_or(false); - - let is_segwit = is_wpkh || is_wsh || is_nested_wpkh || is_nested_wsh; - - let sighash = if is_segwit { - if is_wpkh || is_nested_wpkh { - let script_code = if is_wpkh { - Script::p2wpkh_script_code(&script).ok_or(SighashError::NotWpkh)? - } else { - Script::p2wpkh_script_code(input.redeem_script.as_ref().expect("checked above")) - .ok_or(SighashError::NotWpkh)? - }; - cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)? - } else { - let script_code = - input.witness_script.as_ref().ok_or(SighashError::MissingWitnessScript)?; - cache.segwit_signature_hash(input_index, script_code, utxo.value, hash_ty)? - } - } else { - let script_code = if script.is_p2sh() { - input.redeem_script.as_ref().ok_or(SighashError::MissingRedeemScript)? - } else { - &script - }; - cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())? - }; - - Ok((Message::from_slice(&sighash).expect("sighashes are 32 bytes"), hash_ty.into())) - } - - /// Returns the prevouts for this PSBT. - fn prevouts(psbt: &Psbt) -> Result, SighashError> { - let len = psbt.inputs.len(); - let mut utxos = Vec::with_capacity(len); - - for i in 0..len { - utxos.push(spend_utxo(psbt, i)?); - } - - Ok(utxos) - } - - /// Returns the spending utxo for this PSBT's input at `input_index`. - fn spend_utxo(psbt: &Psbt, input_index: usize) -> Result<&TxOut, SighashError> { - check_index_is_within_bounds(psbt, input_index)?; - - let input = &psbt.inputs[input_index]; - let utxo = if let Some(witness_utxo) = &input.witness_utxo { - witness_utxo - } else if let Some(non_witness_utxo) = &input.non_witness_utxo { - let vout = psbt.unsigned_tx.input[input_index].previous_output.vout; - &non_witness_utxo.output[vout as usize] - } else { - return Err(SighashError::MissingSpendUtxo); - }; - Ok(utxo) - } - - /// Checks `input_index` is within bounds for the PSBT `inputs` array and - /// for the PSBT `unsigned_tx` `input` array. - fn check_index_is_within_bounds(psbt: &Psbt, input_index: usize) -> Result<(), SighashError> { - if input_index >= psbt.inputs.len() { - return Err(SighashError::IndexOutOfBounds(input_index, psbt.inputs.len())); - } - - if input_index >= psbt.unsigned_tx.input.len() { - return Err(SighashError::IndexOutOfBounds(input_index, psbt.unsigned_tx.input.len())); - } - - Ok(()) - } - - /// Returns the sighash message and sighash type for this `input`. - fn taproot_sighash>( - input: &Input, - prevouts: Vec<&TxOut>, - input_index: usize, - cache: &mut SighashCache, - tapleaf_hash: Option, - ) -> Result<(Message, PsbtSighashType), SighashError> { - // Note that as per PSBT spec we should have access to spent utxos for the transaction. Even - // if the transaction does not require SIGHASH_ALL, we create `Prevouts::All` for simplicity. - let prevouts = Prevouts::All(&prevouts); - - let hash_ty = input - .sighash_type - .map(|ty| ty.schnorr_hash_ty()) - .unwrap_or(Ok(SchnorrSighashType::Default)) - .map_err(|_e| SighashError::InvalidSighashType)?; - - let sighash = match tapleaf_hash { - Some(leaf_hash) => cache.taproot_script_spend_signature_hash( - input_index, - &prevouts, - leaf_hash, - hash_ty, - )?, - None => cache.taproot_key_spend_signature_hash(input_index, &prevouts, hash_ty)?, - }; - let msg = Message::from_slice(&sighash).expect("sighashes are 32 bytes"); - Ok((msg, hash_ty.into())) - } - - /// Errors encountered while calculating the sighash message. - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] - pub enum SighashError { - /// Input index out of bounds (actual index, maximum index allowed). - IndexOutOfBounds(usize, usize), - /// Missing spending utxo. - MissingSpendUtxo, - /// Missing witness script. - MissingWitnessScript, - /// Missing Redeem script. - MissingRedeemScript, - /// Invalid Sighash type. - InvalidSighashType, - /// The `scriptPubkey` is not a P2WPKH script. - NotWpkh, - /// Sighash computation error. - SighashComputation(sighash::Error), - /// An ECDSA key-related error occurred. - EcdsaSig(EcdsaSigError), - } - - impl fmt::Display for SighashError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SighashError::IndexOutOfBounds(ind, len) => { - write!(f, "index {}, psbt input len: {}", ind, len) - } - SighashError::MissingSpendUtxo => write!(f, "missing spend utxon in PSBT"), - SighashError::MissingWitnessScript => write!(f, "missing witness script"), - SighashError::MissingRedeemScript => write!(f, "missing redeem script"), - SighashError::InvalidSighashType => write!(f, "invalid sighash type"), - SighashError::NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), - // If merged into rust-bitcoin these two should use `write_err!`. - SighashError::SighashComputation(e) => write!(f, "sighash: {}", e), - SighashError::EcdsaSig(e) => write!(f, "ecdsa: {}", e), - } - } - } - - impl From for SighashError { - fn from(e: sighash::Error) -> Self { SighashError::SighashComputation(e) } - } - - impl From for SighashError { - fn from(e: EcdsaSigError) -> Self { SighashError::EcdsaSig(e) } - } - - #[cfg(feature = "std")] - impl std::error::Error for SighashError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::SighashError::*; - - match self { - IndexOutOfBounds(_, _) - | MissingSpendUtxo - | MissingWitnessScript - | MissingRedeemScript - | InvalidSighashType - | NotWpkh => None, - SighashComputation(e) => Some(e), - EcdsaSig(e) => Some(e), - } - } - } +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } diff --git a/bitcoin/src/util/psbt/mod.rs b/bitcoin/src/util/psbt/mod.rs index 9ec723c953..1ee5d218d1 100644 --- a/bitcoin/src/util/psbt/mod.rs +++ b/bitcoin/src/util/psbt/mod.rs @@ -7,32 +7,40 @@ //! except we define PSBTs containing non-standard sighash types as invalid. //! -use core::cmp; +#[cfg(feature = "std")] +use std::collections::{HashMap, HashSet}; -use crate::blockdata::script::Script; -use crate::blockdata::transaction::{ TxOut, Transaction}; -use crate::consensus::{encode, Encodable, Decodable}; -pub use crate::util::sighash::Prevouts; +use core::{fmt, cmp}; +use core::ops::Deref; -use crate::prelude::*; +use secp256k1::{Message, Secp256k1, Signing}; +use bitcoin_internals::write_err; +use crate::prelude::*; use crate::io; -mod error; -pub use self::error::Error; -pub mod raw; +use crate::blockdata::script::Script; +use crate::blockdata::transaction::{Transaction, TxOut}; +use crate::consensus::{encode, Encodable, Decodable}; +use crate::util::bip32::{self, ExtendedPrivKey, ExtendedPubKey, KeySource}; +use crate::util::ecdsa::{EcdsaSig, EcdsaSigError}; +use crate::util::key::{PublicKey, PrivateKey}; +use crate::util::sighash::{self, EcdsaSighashType, SighashCache}; + +pub use crate::util::sighash::Prevouts; #[macro_use] mod macros; - +pub mod raw; pub mod serialize; +mod error; +pub use self::error::Error; + mod map; pub use self::map::{Input, Output, TapTree, PsbtSighashType, IncompleteTapTree}; use self::map::Map; -use crate::util::bip32::{ExtendedPubKey, KeySource}; - /// Partially signed transaction, commonly referred to as a PSBT. pub type Psbt = PartiallySignedTransaction; @@ -198,6 +206,522 @@ impl PartiallySignedTransaction { Ok(()) } + + /// Attempts to create _all_ the required signatures for this PSBT using `k`. + /// + /// **NOTE**: Taproot inputs are, as yet, not supported by this function. We currently only + /// attempt to sign ECDSA inputs. + /// + /// If you just want to sign an input with one specific key consider using `sighash_ecdsa`. This + /// function does not support scripts that contain `OP_CODESEPARATOR`. + /// + /// # Returns + /// + /// Either Ok(SigningKeys) or Err((SigningKeys, SigningErrors)), where + /// - SigningKeys: A map of input index -> pubkey associated with secret key used to sign. + /// - SigningKeys: A map of input index -> the error encountered while attempting to sign. + /// + /// If an error is returned some signatures may already have been added to the PSBT. Since + /// `partial_sigs` is a [`BTreeMap`] it is safe to retry, previous sigs will be overwritten. + pub fn sign( + &mut self, + k: &K, + secp: &Secp256k1, + ) -> Result + where + C: Signing, + K: GetKey, + { + let tx = self.unsigned_tx.clone(); // clone because we need to mutably borrow when signing. + let mut cache = SighashCache::new(&tx); + + let mut used = BTreeMap::new(); + let mut errors = BTreeMap::new(); + + for i in 0..self.inputs.len() { + if let Ok(SigningAlgorithm::Ecdsa) = self.signing_algorithm(i) { + match self.bip32_sign_ecdsa(k, i, &mut cache, secp) { + Ok(v) => { used.insert(i, v); }, + Err(e) => { errors.insert(i, e); }, + } + }; + } + if errors.is_empty() { + Ok(used) + } else { + Err((used, errors)) + } + } + + /// Attempts to create all signatures required by this PSBT's `bip32_derivation` field, adding + /// them to `partial_sigs`. + /// + /// # Returns + /// + /// - Ok: A list of the public keys used in signing. + /// - Err: Error encountered trying to calculate the sighash AND we had the signing key. + fn bip32_sign_ecdsa( + &mut self, + k: &K, + input_index: usize, + cache: &mut SighashCache, + secp: &Secp256k1, + ) -> Result, SignError> + where + C: Signing, + T: Deref, + K: GetKey, + { + let msg_sighash_ty_res = self.sighash_ecdsa(input_index, cache); + + let input = &mut self.inputs[input_index]; // Index checked in call to `sighash_ecdsa`. + + let mut used = vec![]; // List of pubkeys used to sign the input. + + for (pk, key_source) in input.bip32_derivation.iter() { + let sk = if let Ok(Some(sk)) = k.get_key(KeyRequest::Bip32(key_source.clone()), secp) { + sk + } else if let Ok(Some(sk)) = k.get_key(KeyRequest::Pubkey(PublicKey::new(*pk)), secp) { + sk + } else { + continue; + }; + + // Only return the error if we have a secret key to sign this input. + let (msg, sighash_ty) = match msg_sighash_ty_res { + Err(e) => return Err(e), + Ok((msg, sighash_ty)) => (msg, sighash_ty), + }; + + let sig = EcdsaSig { + sig: secp.sign_ecdsa(&msg, &sk.inner), + hash_ty: sighash_ty, + }; + + let pk = sk.public_key(secp); + + input.partial_sigs.insert(pk, sig); + used.push(pk); + } + + Ok(used) + } + + /// Returns the sighash message to sign an ECDSA input along with the sighash type. + /// + /// Uses the [`EcdsaSighashType`] from this input if one is specified. If no sighash type is + /// specified uses [`EcdsaSighashType::All`]. This function does not support scripts that + /// contain `OP_CODESEPARATOR`. + pub fn sighash_ecdsa>( + &self, + input_index: usize, + cache: &mut SighashCache, + ) -> Result<(Message, EcdsaSighashType), SignError> { + use OutputType::*; + + if self.signing_algorithm(input_index)? != SigningAlgorithm::Ecdsa { + return Err(SignError::WrongSigningAlgorithm); + } + + let input = self.checked_input(input_index)?; + let utxo = self.spend_utxo(input_index)?; + let spk = &utxo.script_pubkey; // scriptPubkey for input spend utxo. + + let hash_ty = input.ecdsa_hash_ty() + .map_err(|_| SignError::InvalidSighashType)?; // Only support standard sighash types. + + let sighash = match self.output_type(input_index)? { + Bare => { + cache.legacy_signature_hash(input_index, spk, hash_ty.to_u32())? + }, + Sh => { + let script_code = input.redeem_script.as_ref().ok_or(SignError::MissingRedeemScript)?; + cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())? + }, + Wpkh => { + let script_code = Script::p2wpkh_script_code(spk).ok_or(SignError::NotWpkh)?; + cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)? + } + ShWpkh => { + let script_code = Script::p2wpkh_script_code(input.redeem_script.as_ref().expect("checked above")) + .ok_or(SignError::NotWpkh)?; + cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)? + }, + Wsh | ShWsh => { + let script_code = input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; + cache.segwit_signature_hash(input_index, script_code, utxo.value, hash_ty)? + }, + Tr => { + // This PSBT signing API is WIP, taproot to come shortly. + return Err(SignError::Unsupported); + } + }; + + Ok((Message::from_slice(&sighash).expect("sighashes are 32 bytes"), hash_ty)) + } + + /// Returns the spending utxo for this PSBT's input at `input_index`. + pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> { + let input = self.checked_input(input_index)?; + let utxo = if let Some(witness_utxo) = &input.witness_utxo { + witness_utxo + } else if let Some(non_witness_utxo) = &input.non_witness_utxo { + let vout = self.unsigned_tx.input[input_index].previous_output.vout; + &non_witness_utxo.output[vout as usize] + } else { + return Err(SignError::MissingSpendUtxo); + }; + Ok(utxo) + } + + /// Gets the input at `input_index` after checking that it is a valid index. + fn checked_input(&self, input_index: usize) -> Result<&Input, SignError> { + self.check_index_is_within_bounds(input_index)?; + Ok(&self.inputs[input_index]) + } + + /// Checks `input_index` is within bounds for the PSBT `inputs` array and + /// for the PSBT `unsigned_tx` `input` array. + fn check_index_is_within_bounds(&self, input_index: usize) -> Result<(), SignError> { + if input_index >= self.inputs.len() { + return Err(SignError::IndexOutOfBounds(input_index, self.inputs.len())); + } + + if input_index >= self.unsigned_tx.input.len() { + return Err(SignError::IndexOutOfBounds(input_index, self.unsigned_tx.input.len())); + } + + Ok(()) + } + + /// Returns the algorithm used to sign this PSBT's input at `input_index`. + fn signing_algorithm(&self, input_index: usize) -> Result { + let output_type = self.output_type(input_index)?; + Ok(output_type.signing_algorithm()) + } + + /// Returns the [`OutputType`] of the spend utxo for this PBST's input at `input_index`. + fn output_type(&self, input_index: usize) -> Result { + let input = self.checked_input(input_index)?; + let utxo = self.spend_utxo(input_index)?; + let spk = utxo.script_pubkey.clone(); + + // Anything that is not segwit and is not p2sh is `Bare`. + if !(spk.is_witness_program() || spk.is_p2sh()) { + return Ok(OutputType::Bare); + } + + if spk.is_v0_p2wpkh() { + return Ok(OutputType::Wpkh); + } + + if spk.is_v0_p2wsh() { + return Ok(OutputType::Wsh); + } + + if spk.is_p2sh() { + if input.redeem_script.as_ref().map(|s| s.is_v0_p2wpkh()).unwrap_or(false) { + return Ok(OutputType::ShWpkh); + } + if input.redeem_script.as_ref().map(|x| x.is_v0_p2wsh()).unwrap_or(false) { + return Ok(OutputType::ShWsh); + } + return Ok(OutputType::Sh); + } + + if spk.is_v1_p2tr() { + return Ok(OutputType::Tr); + } + + // Something is wrong with the input scriptPubkey or we do not know how to sign + // because there has been a new softfork that we do not yet support. + Err(SignError::UnknownOutputType) + } +} + +/// Data required to call [`GetKey`] to get the private key to sign an input. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum KeyRequest { + /// Request a private key using the associated public key. + Pubkey(PublicKey), + /// Request a private key using BIP-32 fingerprint and derivation path. + Bip32(KeySource), +} + +/// Trait to get a private key from a key request, key is then used to sign an input. +pub trait GetKey { + /// An error occurred while getting the key. + type Error: core::fmt::Debug; + + /// Attempts to get the private key for `key_request`. + /// + /// # Returns + /// - `Some(key)` if the key is found. + /// - `None` if the key was not found but no error was encountered. + /// - `Err` if an error was encountered while looking for the key. + fn get_key(&self, key_request: KeyRequest, secp: &Secp256k1) -> Result, Self::Error>; +} + +impl GetKey for ExtendedPrivKey { + type Error = GetKeyError; + + fn get_key(&self, key_request: KeyRequest, secp: &Secp256k1) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), + KeyRequest::Bip32((fingerprint, path)) => { + let key = if self.fingerprint(secp) == fingerprint { + let k = self.derive_priv(secp, &path)?; + Some(k.to_priv()) + } else { + None + }; + Ok(key) + } + } + } +} + +/// Map of input index -> pubkey associated with secret key used to create signature for that input. +pub type SigningKeys = BTreeMap>; + +/// Map of input index -> the error encountered while attempting to sign that input. +pub type SigningErrors = BTreeMap; + +#[rustfmt::skip] +macro_rules! impl_get_key_for_set { + ($set:ident) => { + +impl GetKey for $set { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + secp: &Secp256k1 + ) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), + KeyRequest::Bip32((fingerprint, path)) => { + for xpriv in self.iter() { + if xpriv.parent_fingerprint == fingerprint { + let k = xpriv.derive_priv(secp, &path)?; + return Ok(Some(k.to_priv())); + } + } + Ok(None) + } + } + } +}}} +impl_get_key_for_set!(BTreeSet); +#[cfg(feature = "std")] +impl_get_key_for_set!(HashSet); + +#[rustfmt::skip] +macro_rules! impl_get_key_for_map { + ($map:ident) => { + +impl GetKey for $map { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + _: &Secp256k1, + ) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()), + KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported), + } + } +}}} +impl_get_key_for_map!(BTreeMap); +#[cfg(feature = "std")] +impl_get_key_for_map!(HashMap); + +/// Errors when getting a key. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[non_exhaustive] +pub enum GetKeyError { + /// A bip32 error. + Bip32(bip32::Error), + /// The GetKey operation is not supported for this key request. + NotSupported, +} + +impl fmt::Display for GetKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetKeyError::*; + + match *self { + Bip32(ref e) => write_err!(f, "a bip23 error"; e), + NotSupported => f.write_str("the GetKey operation is not supported for this key request"), + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for GetKeyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetKeyError::*; + + match *self { + NotSupported => None, + Bip32(ref e) => Some(e), + } + } +} + +impl From for GetKeyError { + fn from(e: bip32::Error) -> Self { + GetKeyError::Bip32(e) + } +} + +/// The various output types supported by the Bitcoin network. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum OutputType { + /// An output of type: pay-to-pubkey or pay-to-pubkey-hash. + Bare, + /// A pay-to-witness-pubkey-hash output (P2WPKH). + Wpkh, + /// A pay-to-witness-script-hash output (P2WSH). + Wsh, + /// A nested segwit output, pay-to-witness-pubkey-hash nested in a pay-to-script-hash. + ShWpkh, + /// A nested segwit output, pay-to-witness-script-hash nested in a pay-to-script-hash. + ShWsh, + /// A pay-to-script-hash output excluding wrapped segwit (P2SH). + Sh, + /// A taproot output (P2TR). + Tr, +} + +impl OutputType { + /// The signing algorithm used to sign this output type. + pub fn signing_algorithm(&self) -> SigningAlgorithm { + use OutputType::*; + + match self { + Bare | Wpkh | Wsh | ShWpkh | ShWsh | Sh => SigningAlgorithm::Ecdsa, + Tr => SigningAlgorithm::Schnorr, + } + } +} + +/// Signing algorithms supported by the Bitcoin network. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum SigningAlgorithm { + /// The Elliptic Curve Digital Signature Algorithm (see [wikipedia]). + /// + /// [wikipedia]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm + Ecdsa, + /// The Schnorr signature algorithm (see [wikipedia]). + /// + /// [wikipedia]: https://en.wikipedia.org/wiki/Schnorr_signature + Schnorr, +} + +/// Errors encountered while calculating the sighash message. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub enum SignError { + /// An ECDSA key-related error occurred. + EcdsaSig(EcdsaSigError), + /// Input index out of bounds (actual index, maximum index allowed). + IndexOutOfBounds(usize, usize), + /// Invalid Sighash type. + InvalidSighashType, + /// Missing input utxo. + MissingInputUtxo, + /// Missing Redeem script. + MissingRedeemScript, + /// Missing spending utxo. + MissingSpendUtxo, + /// Missing witness script. + MissingWitnessScript, + /// Signing algorithm and key type does not match. + MismatchedAlgoKey, + /// Attempted to ECDSA sign an non-ECDSA input. + NotEcdsa, + /// The `scriptPubkey` is not a P2WPKH script. + NotWpkh, + /// Sighash computation error. + SighashComputation(sighash::Error), + /// Unable to determine the output type. + UnknownOutputType, + /// Unable to find key. + KeyNotFound, + /// Attempt to sign an input with the wrong signing algorithm. + WrongSigningAlgorithm, + /// Signing request currently unsupported. + Unsupported +} + +impl fmt::Display for SignError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::SignError::*; + + match *self { + IndexOutOfBounds(ind, len) => { + write!(f, "index {}, psbt input len: {}", ind, len) + } + InvalidSighashType => write!(f, "invalid sighash type"), + MissingInputUtxo => write!(f, "missing input utxo in PBST"), + MissingRedeemScript => write!(f, "missing redeem script"), + MissingSpendUtxo => write!(f, "missing spend utxo in PSBT"), + MissingWitnessScript => write!(f, "missing witness script"), + MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"), + NotEcdsa => write!(f, "attempted to ECDSA sign an non-ECDSA input"), + NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), + SighashComputation(e) => write!(f, "sighash: {}", e), + EcdsaSig(ref e) => write_err!(f, "ecdsa signature"; e), + UnknownOutputType => write!(f, "unable to determine the output type"), + KeyNotFound => write!(f, "unable to find key"), + WrongSigningAlgorithm => write!(f, "attempt to sign an input with the wrong signing algorithm"), + Unsupported => write!(f, "signing request currently unsupported"), + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for SignError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use self::SignError::*; + + match *self { + IndexOutOfBounds(_, _) + | InvalidSighashType + | MissingInputUtxo + | MissingRedeemScript + | MissingSpendUtxo + | MissingWitnessScript + | MismatchedAlgoKey + | NotEcdsa + | NotWpkh + | UnknownOutputType + | KeyNotFound + | WrongSigningAlgorithm + | Unsupported => None, + EcdsaSig(ref e) => Some(e), + SighashComputation(ref e) => Some(e), + } + } +} + +impl From for SignError { + fn from(e: sighash::Error) -> Self { + SignError::SighashComputation(e) + } +} + +impl From for SignError { + fn from(e: EcdsaSigError) -> Self { + SignError::EcdsaSig(e) + } } #[cfg(feature = "base64")] @@ -341,6 +865,8 @@ mod tests { use crate::hash_types::Txid; use secp256k1::{Secp256k1, self}; + #[cfg(feature = "rand")] + use secp256k1::{All, SecretKey}; use crate::blockdata::script::Script; use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence}; @@ -1137,4 +1663,74 @@ mod tests { assert_eq!(psbt1, psbt2); } + + #[cfg(feature = "rand")] + fn gen_keys() -> (PrivateKey, PublicKey, Secp256k1) { + use secp256k1::rand::thread_rng; + + let secp = Secp256k1::new(); + + let sk = SecretKey::new(&mut thread_rng()); + let priv_key = PrivateKey::new(sk, crate::Network::Regtest); + let pk = PublicKey::from_private_key(&secp, &priv_key); + + (priv_key, pk, secp) + } + + #[test] + #[cfg(feature = "rand")] + fn get_key_btree_map() { + let (priv_key, pk, secp) = gen_keys(); + + let mut key_map = BTreeMap::new(); + key_map.insert(pk, priv_key); + + let got = key_map.get_key(KeyRequest::Pubkey(pk), &secp).expect("failed to get key"); + assert_eq!(got.unwrap(), priv_key) + } + + #[test] + #[cfg(feature = "rand")] + fn sign_psbt() { + use crate::WPubkeyHash; + use crate::util::bip32::{Fingerprint, DerivationPath}; + + let unsigned_tx = Transaction { + version: 2, + lock_time: absolute::PackedLockTime::ZERO, + input: vec![TxIn::default(), TxIn::default()], + output: vec![TxOut::default()], + }; + let mut psbt = PartiallySignedTransaction::from_unsigned_tx(unsigned_tx).unwrap(); + + let (priv_key, pk, secp) = gen_keys(); + + // key_map implements `GetKey` using KeyRequest::Pubkey. A pubkey key request does not use + // keysource so we use default `KeySource` (fingreprint and derivation path) below. + let mut key_map = BTreeMap::new(); + key_map.insert(pk, priv_key); + + // First input we can spend. See comment above on key_map for why we use defaults here. + let txout_wpkh = TxOut{ + value: 10, + script_pubkey: Script::new_v0_p2wpkh(&WPubkeyHash::hash(&pk.to_bytes())), + }; + psbt.inputs[0].witness_utxo = Some(txout_wpkh); + + let mut map = BTreeMap::new(); + map.insert(pk.inner, (Fingerprint::default(), DerivationPath::default())); + psbt.inputs[0].bip32_derivation = map; + + // Second input is unspendable by us e.g., from another wallet that supports future upgrades. + let txout_unknown_future = TxOut{ + value: 10, + script_pubkey: Script::new_witness_program(crate::address::WitnessVersion::V4, &[0xaa; 34]), + }; + psbt.inputs[1].witness_utxo = Some(txout_unknown_future); + + let sigs = psbt.sign(&key_map, &secp).unwrap(); + + assert!(sigs.len() == 1); + assert!(sigs[&0] == vec![pk]); + } }