Merge rust-bitcoin/rust-bitcoin#957: Add API to PSBT to enable signing inputs
dd8730e14f
Use new PSBT signing API in example (Tobin C. Harding)d2367fb187
Add PSBT sign functionality (Tobin C. Harding)b80e5aeaab
Re-order import statements (Tobin C. Harding) Pull request description: Add an API for signing inputs to the `PSBT` struct. This is work based on code in `rust-miniscript` and the API design suggestions below from @sanket1729 and @Kixunil. Please note, this adds an `unimplemented!` call for taproot inputs. ECDSA signing is complete. Includes a patch adding the psbt example from https://github.com/rust-bitcoin/rust-bitcoin/pull/940 updated to use this new api. Run `cargo run --example psbt --features=bitcoinconsensus` to test it out. ACKs for top commit: dunxen: ACKdd8730e
apoelstra: ACKdd8730e14f
sanket1729: reACKdd8730e14f
Tree-SHA512: 6345571e53cd3aa4b7ad962536da47ae03ab7c0b088107dc4104676bdb64fcf892e8fa60e0b716f3ef158d88d7058938bf267046721ccf74b2d1b092e9b9aaaa
This commit is contained in:
commit
fcf9bd07bc
|
@ -28,27 +28,24 @@
|
||||||
//! `bt listunspent`
|
//! `bt listunspent`
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use std::boxed::Box;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitcoin::consensus::encode;
|
use bitcoin::consensus::encode;
|
||||||
use bitcoin::hashes::hex::{self, FromHex};
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::locktime::absolute;
|
use bitcoin::locktime::absolute;
|
||||||
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
|
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
|
||||||
use bitcoin::util::amount::ParseAmountError;
|
|
||||||
use bitcoin::util::bip32::{
|
use bitcoin::util::bip32::{
|
||||||
self, ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
|
ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDerivationPath,
|
||||||
IntoDerivationPath,
|
|
||||||
};
|
};
|
||||||
use bitcoin::util::psbt::{self, Input, Psbt, PsbtSighashType};
|
use bitcoin::util::psbt::{self, Input, Psbt, PsbtSighashType};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
address, Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, Sequence,
|
Address, Amount, Network, OutPoint, PublicKey, Script, Sequence, Transaction, TxIn, TxOut,
|
||||||
Transaction, TxIn, TxOut, Txid, Witness,
|
Txid, Witness,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::psbt_sign::*;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
// Get this from the output of `bt dumpwallet <file>`.
|
// Get this from the output of `bt dumpwallet <file>`.
|
||||||
|
@ -141,33 +138,14 @@ impl ColdStorage {
|
||||||
|
|
||||||
/// Signs `psbt` with this signer.
|
/// Signs `psbt` with this signer.
|
||||||
fn sign_psbt<C: Signing>(&self, secp: &Secp256k1<C>, mut psbt: Psbt) -> Result<Psbt> {
|
fn sign_psbt<C: Signing>(&self, secp: &Secp256k1<C>, mut psbt: Psbt) -> Result<Psbt> {
|
||||||
let sk = self.private_key_to_sign(secp, &psbt.inputs[0])?;
|
match psbt.sign(&self.master_xpriv, secp) {
|
||||||
psbt_sign::sign(&mut psbt, &sk, 0, secp)?;
|
Ok(keys) => assert_eq!(keys.len(), 1),
|
||||||
|
Err((_, e)) => {
|
||||||
Ok(psbt)
|
let e = e.get(&0).expect("at least one error");
|
||||||
}
|
return Err(e.clone().into());
|
||||||
|
|
||||||
/// Returns the private key required to sign `input` if we have it.
|
|
||||||
fn private_key_to_sign<C: Signing>(
|
|
||||||
&self,
|
|
||||||
secp: &Secp256k1<C>,
|
|
||||||
input: &Input,
|
|
||||||
) -> Result<PrivateKey> {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
None => Err(Error::MissingBip32Derivation),
|
};
|
||||||
}
|
Ok(psbt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +225,7 @@ impl WatchOnly {
|
||||||
map.insert(pk.inner, (fingerprint, path));
|
map.insert(pk.inner, (fingerprint, path));
|
||||||
input.bip32_derivation = map;
|
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);
|
input.sighash_type = Some(ty);
|
||||||
|
|
||||||
psbt.inputs = vec![input];
|
psbt.inputs = vec![input];
|
||||||
|
@ -260,11 +238,10 @@ impl WatchOnly {
|
||||||
use bitcoin::util::psbt::serialize::Serialize;
|
use bitcoin::util::psbt::serialize::Serialize;
|
||||||
|
|
||||||
if psbt.inputs.is_empty() {
|
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 sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect();
|
||||||
|
|
||||||
let mut script_witness: Witness = Witness::new();
|
let mut script_witness: Witness = Witness::new();
|
||||||
script_witness.push(&sigs[0].serialize());
|
script_witness.push(&sigs[0].serialize());
|
||||||
script_witness.push(self.input_xpub.to_pub().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 }
|
TxOut { value: amount.to_sat(), script_pubkey }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
struct Error(Box<dyn std::error::Error>);
|
||||||
enum Error {
|
|
||||||
/// Bip32 error.
|
impl<T: std::error::Error + 'static> From<T> for Error {
|
||||||
Bip32(bip32::Error),
|
fn from(e: T) -> Self { Error(Box::new(e)) }
|
||||||
/// 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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl fmt::Debug for Error {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bip32::Error> for Error {
|
|
||||||
fn from(e: bip32::Error) -> Error { Error::Bip32(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<psbt::Error> for Error {
|
|
||||||
fn from(e: psbt::Error) -> Error { Error::Psbt(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SighashError> for Error {
|
|
||||||
fn from(e: SighashError) -> Error { Error::PsbtSighash(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<hex::Error> for Error {
|
|
||||||
fn from(e: hex::Error) -> Error { Error::Hex(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<address::Error> for Error {
|
|
||||||
fn from(e: address::Error) -> Error { Error::Address(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ParseAmountError> 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<C: Signing>(
|
|
||||||
psbt: &mut Psbt,
|
|
||||||
sk: &PrivateKey,
|
|
||||||
input_index: usize,
|
|
||||||
secp: &Secp256k1<C>,
|
|
||||||
) -> 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<T: Deref<Target = Transaction>>(
|
|
||||||
psbt: &Psbt,
|
|
||||||
input_index: usize,
|
|
||||||
cache: &mut SighashCache<T>,
|
|
||||||
tapleaf_hash: Option<TapLeafHash>,
|
|
||||||
) -> 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<Vec<&TxOut>, 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<T: Deref<Target = Transaction>>(
|
|
||||||
input: &Input,
|
|
||||||
prevouts: Vec<&TxOut>,
|
|
||||||
input_index: usize,
|
|
||||||
cache: &mut SighashCache<T>,
|
|
||||||
tapleaf_hash: Option<TapLeafHash>,
|
|
||||||
) -> 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<sighash::Error> for SighashError {
|
|
||||||
fn from(e: sighash::Error) -> Self { SighashError::SighashComputation(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcdsaSigError> 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,32 +7,40 @@
|
||||||
//! except we define PSBTs containing non-standard sighash types as invalid.
|
//! 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 core::{fmt, cmp};
|
||||||
use crate::blockdata::transaction::{ TxOut, Transaction};
|
use core::ops::Deref;
|
||||||
use crate::consensus::{encode, Encodable, Decodable};
|
|
||||||
pub use crate::util::sighash::Prevouts;
|
use secp256k1::{Message, Secp256k1, Signing};
|
||||||
|
use bitcoin_internals::write_err;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::io;
|
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]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
pub mod raw;
|
||||||
pub mod serialize;
|
pub mod serialize;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use self::error::Error;
|
||||||
|
|
||||||
mod map;
|
mod map;
|
||||||
pub use self::map::{Input, Output, TapTree, PsbtSighashType, IncompleteTapTree};
|
pub use self::map::{Input, Output, TapTree, PsbtSighashType, IncompleteTapTree};
|
||||||
use self::map::Map;
|
use self::map::Map;
|
||||||
|
|
||||||
use crate::util::bip32::{ExtendedPubKey, KeySource};
|
|
||||||
|
|
||||||
/// Partially signed transaction, commonly referred to as a PSBT.
|
/// Partially signed transaction, commonly referred to as a PSBT.
|
||||||
pub type Psbt = PartiallySignedTransaction;
|
pub type Psbt = PartiallySignedTransaction;
|
||||||
|
|
||||||
|
@ -198,6 +206,522 @@ impl PartiallySignedTransaction {
|
||||||
|
|
||||||
Ok(())
|
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<C, K>(
|
||||||
|
&mut self,
|
||||||
|
k: &K,
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
) -> Result<SigningKeys, (SigningKeys, SigningErrors)>
|
||||||
|
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<C, K, T>(
|
||||||
|
&mut self,
|
||||||
|
k: &K,
|
||||||
|
input_index: usize,
|
||||||
|
cache: &mut SighashCache<T>,
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
) -> Result<Vec<PublicKey>, SignError>
|
||||||
|
where
|
||||||
|
C: Signing,
|
||||||
|
T: Deref<Target=Transaction>,
|
||||||
|
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<T: Deref<Target=Transaction>>(
|
||||||
|
&self,
|
||||||
|
input_index: usize,
|
||||||
|
cache: &mut SighashCache<T>,
|
||||||
|
) -> 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<SigningAlgorithm, SignError> {
|
||||||
|
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<OutputType, SignError> {
|
||||||
|
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<C: Signing>(&self, key_request: KeyRequest, secp: &Secp256k1<C>) -> Result<Option<PrivateKey>, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetKey for ExtendedPrivKey {
|
||||||
|
type Error = GetKeyError;
|
||||||
|
|
||||||
|
fn get_key<C: Signing>(&self, key_request: KeyRequest, secp: &Secp256k1<C>) -> Result<Option<PrivateKey>, 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<usize, Vec<PublicKey>>;
|
||||||
|
|
||||||
|
/// Map of input index -> the error encountered while attempting to sign that input.
|
||||||
|
pub type SigningErrors = BTreeMap<usize, SignError>;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
macro_rules! impl_get_key_for_set {
|
||||||
|
($set:ident) => {
|
||||||
|
|
||||||
|
impl GetKey for $set<ExtendedPrivKey> {
|
||||||
|
type Error = GetKeyError;
|
||||||
|
|
||||||
|
fn get_key<C: Signing>(
|
||||||
|
&self,
|
||||||
|
key_request: KeyRequest,
|
||||||
|
secp: &Secp256k1<C>
|
||||||
|
) -> Result<Option<PrivateKey>, 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<PublicKey, PrivateKey> {
|
||||||
|
type Error = GetKeyError;
|
||||||
|
|
||||||
|
fn get_key<C: Signing>(
|
||||||
|
&self,
|
||||||
|
key_request: KeyRequest,
|
||||||
|
_: &Secp256k1<C>,
|
||||||
|
) -> Result<Option<PrivateKey>, 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<bip32::Error> 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<sighash::Error> for SignError {
|
||||||
|
fn from(e: sighash::Error) -> Self {
|
||||||
|
SignError::SighashComputation(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EcdsaSigError> for SignError {
|
||||||
|
fn from(e: EcdsaSigError) -> Self {
|
||||||
|
SignError::EcdsaSig(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "base64")]
|
#[cfg(feature = "base64")]
|
||||||
|
@ -341,6 +865,8 @@ mod tests {
|
||||||
use crate::hash_types::Txid;
|
use crate::hash_types::Txid;
|
||||||
|
|
||||||
use secp256k1::{Secp256k1, self};
|
use secp256k1::{Secp256k1, self};
|
||||||
|
#[cfg(feature = "rand")]
|
||||||
|
use secp256k1::{All, SecretKey};
|
||||||
|
|
||||||
use crate::blockdata::script::Script;
|
use crate::blockdata::script::Script;
|
||||||
use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence};
|
use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence};
|
||||||
|
@ -1137,4 +1663,74 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(psbt1, psbt2);
|
assert_eq!(psbt1, psbt2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rand")]
|
||||||
|
fn gen_keys() -> (PrivateKey, PublicKey, Secp256k1<All>) {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue