Merge rust-bitcoin/rust-bitcoin#669: PSBT: partial sig data type

14ace92666 Fix SchnorrSig type references in PSBT serialization macros (Dr Maxim Orlovsky)
2b530000d3 Use EcdsaSig in PSBT partial signatures instead of Vec<u8> (Dr Maxim Orlovsky)
141dbbd1b9 Add serde impl for EcdsaSig (Dr Maxim Orlovsky)
c92057d98f PSBT serialize/deserialize impl for EcdsaSig type (Dr Maxim Orlovsky)
0af1c3f320 Add Display and FromStr for EcdsaSig (Dr Maxim Orlovsky)
daf0eacf3d Improve NonStandardSigHashType (Dr Maxim Orlovsky)
c36a3da6f0 Add EcdsaSig::sighash_all convenience constructor (Dr Maxim Orlovsky)

Pull request description:

  Previously signatures were kept in PSBT as raw byte vec, without processing. This adds specific data type, capable of holding & serializing/deserializing partial signature with sighash flag information.

ACKs for top commit:
  apoelstra:
    ACK 14ace92666
  Kixunil:
    ACK 14ace92666

Tree-SHA512: f505df9d1990735fe3941092174a61e067a4e3db30d2d1b94b136da4607865b3b78b274e674b2cfb877aaf0ab58e007c1ab1738f3927d60a4a3ecc12cadc5a48
This commit is contained in:
Andrew Poelstra 2022-01-08 22:11:51 +00:00
commit 2ec9af3d35
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
5 changed files with 91 additions and 23 deletions

View File

@ -666,11 +666,11 @@ impl Decodable for Transaction {
/// This type is consensus valid but an input including it would prevent the transaction from /// This type is consensus valid but an input including it would prevent the transaction from
/// being relayed on today's Bitcoin network. /// being relayed on today's Bitcoin network.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonStandardSigHashType; pub struct NonStandardSigHashType(pub u32);
impl fmt::Display for NonStandardSigHashType { impl fmt::Display for NonStandardSigHashType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Non standard sighash type") write!(f, "Non standard sighash type {}", self.0)
} }
} }
@ -789,7 +789,7 @@ impl EcdsaSigHashType {
0x81 => Ok(EcdsaSigHashType::AllPlusAnyoneCanPay), 0x81 => Ok(EcdsaSigHashType::AllPlusAnyoneCanPay),
0x82 => Ok(EcdsaSigHashType::NonePlusAnyoneCanPay), 0x82 => Ok(EcdsaSigHashType::NonePlusAnyoneCanPay),
0x83 => Ok(EcdsaSigHashType::SinglePlusAnyoneCanPay), 0x83 => Ok(EcdsaSigHashType::SinglePlusAnyoneCanPay),
_ => Err(NonStandardSigHashType) non_standard => Err(NonStandardSigHashType(non_standard))
} }
} }
@ -1151,7 +1151,7 @@ mod tests {
assert_eq!(EcdsaSigHashType::from_u32(nonstandard_hashtype), EcdsaSigHashType::All); assert_eq!(EcdsaSigHashType::from_u32(nonstandard_hashtype), EcdsaSigHashType::All);
assert_eq!(EcdsaSigHashType::from_u32_consensus(nonstandard_hashtype), EcdsaSigHashType::All); assert_eq!(EcdsaSigHashType::from_u32_consensus(nonstandard_hashtype), EcdsaSigHashType::All);
// But it's policy-invalid to use it! // But it's policy-invalid to use it!
assert_eq!(EcdsaSigHashType::from_u32_standard(nonstandard_hashtype), Err(NonStandardSigHashType)); assert_eq!(EcdsaSigHashType::from_u32_standard(nonstandard_hashtype), Err(NonStandardSigHashType(0x04)));
} }
// These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT // These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT

View File

@ -25,11 +25,12 @@ use io;
use secp256k1::{self, Secp256k1}; use secp256k1::{self, Secp256k1};
use network::constants::Network; use network::constants::Network;
use hashes::{Hash, hash160}; use hashes::{Hash, hash160, hex};
use hashes::hex::FromHex;
use hash_types::{PubkeyHash, WPubkeyHash}; use hash_types::{PubkeyHash, WPubkeyHash};
use util::base58; use util::base58;
use util::key::Error; use util::key::Error;
use blockdata::transaction::EcdsaSigHashType; use blockdata::transaction::{EcdsaSigHashType, NonStandardSigHashType};
/// A Bitcoin ECDSA public key /// A Bitcoin ECDSA public key
@ -417,6 +418,7 @@ impl<'de> ::serde::Deserialize<'de> for PublicKey {
/// An ECDSA signature with the corresponding hash type. /// An ECDSA signature with the corresponding hash type.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EcdsaSig { pub struct EcdsaSig {
/// The underlying ECDSA Signature /// The underlying ECDSA Signature
pub sig: secp256k1::ecdsa::Signature, pub sig: secp256k1::ecdsa::Signature,
@ -425,13 +427,20 @@ pub struct EcdsaSig {
} }
impl EcdsaSig { impl EcdsaSig {
/// Constructs ECDSA bitcoin signature for [`EcdsaSigHashType::All`]
pub fn sighash_all(sig: secp256k1::ecdsa::Signature) -> EcdsaSig {
EcdsaSig {
sig,
hash_ty: EcdsaSigHashType::All
}
}
/// Deserialize from slice /// Deserialize from slice
pub fn from_slice(sl: &[u8]) -> Result<Self, EcdsaSigError> { pub fn from_slice(sl: &[u8]) -> Result<Self, EcdsaSigError> {
let (hash_ty, sig) = sl.split_last() let (hash_ty, sig) = sl.split_last()
.ok_or(EcdsaSigError::EmptySignature)?; .ok_or(EcdsaSigError::EmptySignature)?;
let hash_ty = EcdsaSigHashType::from_u32_standard(*hash_ty as u32) let hash_ty = EcdsaSigHashType::from_u32_standard(*hash_ty as u32)
.map_err(|_| EcdsaSigError::NonStandardSigHashType(*hash_ty))?; .map_err(|_| EcdsaSigError::NonStandardSigHashType(*hash_ty as u32))?;
let sig = secp256k1::ecdsa::Signature::from_der(sig) let sig = secp256k1::ecdsa::Signature::from_der(sig)
.map_err(EcdsaSigError::Secp256k1)?; .map_err(EcdsaSigError::Secp256k1)?;
Ok(EcdsaSig { sig, hash_ty }) Ok(EcdsaSig { sig, hash_ty })
@ -446,11 +455,34 @@ impl EcdsaSig {
} }
} }
impl fmt::Display for EcdsaSig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex::format_hex(&self.sig.serialize_der(), f)?;
hex::format_hex(&[self.hash_ty as u8], f)
}
}
impl FromStr for EcdsaSig {
type Err = EcdsaSigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = Vec::from_hex(s)?;
let (sighash_byte, signature) = bytes.split_last()
.ok_or(EcdsaSigError::EmptySignature)?;
Ok(EcdsaSig {
sig: secp256k1::ecdsa::Signature::from_der(signature)?,
hash_ty: EcdsaSigHashType::from_u32_standard(*sighash_byte as u32)?
})
}
}
/// A key-related error. /// A key-related error.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum EcdsaSigError { pub enum EcdsaSigError {
/// Hex encoding error
HexEncoding(hex::Error),
/// Base58 encoding error /// Base58 encoding error
NonStandardSigHashType(u8), NonStandardSigHashType(u32),
/// Empty Signature /// Empty Signature
EmptySignature, EmptySignature,
/// secp256k1-related error /// secp256k1-related error
@ -467,6 +499,7 @@ impl fmt::Display for EcdsaSigError {
write!(f, "Invalid Ecdsa signature: {}", e), write!(f, "Invalid Ecdsa signature: {}", e),
EcdsaSigError::EmptySignature => EcdsaSigError::EmptySignature =>
write!(f, "Empty ECDSA signature"), write!(f, "Empty ECDSA signature"),
EcdsaSigError::HexEncoding(e) => write!(f, "EcdsaSig hex encoding error: {}", e)
} }
} }
} }
@ -481,6 +514,18 @@ impl From<secp256k1::Error> for EcdsaSigError {
} }
} }
impl From<NonStandardSigHashType> for EcdsaSigError {
fn from(err: NonStandardSigHashType) -> Self {
EcdsaSigError::NonStandardSigHashType(err.0)
}
}
impl From<hex::Error> for EcdsaSigError {
fn from(err: hex::Error) -> Self {
EcdsaSigError::HexEncoding(err)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use io; use io;

View File

@ -14,7 +14,7 @@
use prelude::*; use prelude::*;
use io; use ::{EcdsaSig, io};
use blockdata::script::Script; use blockdata::script::Script;
use blockdata::transaction::{EcdsaSigHashType, Transaction, TxOut}; use blockdata::transaction::{EcdsaSigHashType, Transaction, TxOut};
@ -29,7 +29,7 @@ use util::psbt::raw;
use util::psbt::serialize::Deserialize; use util::psbt::serialize::Deserialize;
use util::psbt::{Error, error}; use util::psbt::{Error, error};
use schnorr; use ::{SchnorrSig};
use util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapBranchHash}; use util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapBranchHash};
/// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00 /// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00
@ -87,9 +87,8 @@ pub struct Input {
/// including P2SH embedded ones. /// including P2SH embedded ones.
pub witness_utxo: Option<TxOut>, pub witness_utxo: Option<TxOut>,
/// A map from public keys to their corresponding signature as would be /// A map from public keys to their corresponding signature as would be
/// pushed to the stack from a scriptSig or witness. /// pushed to the stack from a scriptSig or witness for a non-taproot inputs.
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_byte_values"))] pub partial_sigs: BTreeMap<PublicKey, EcdsaSig>,
pub partial_sigs: BTreeMap<PublicKey, Vec<u8>>,
/// The sighash type to be used for this input. Signatures for this input /// The sighash type to be used for this input. Signatures for this input
/// must use the sighash type. /// must use the sighash type.
pub sighash_type: Option<EcdsaSigHashType>, pub sighash_type: Option<EcdsaSigHashType>,
@ -121,10 +120,10 @@ pub struct Input {
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_byte_values"))] #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_byte_values"))]
pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>, pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>,
/// Serialized schnorr signature with sighash type for key spend /// Serialized schnorr signature with sighash type for key spend
pub tap_key_sig: Option<schnorr::SchnorrSig>, pub tap_key_sig: Option<SchnorrSig>,
/// Map of <xonlypubkey>|<leafhash> with signature /// Map of <xonlypubkey>|<leafhash> with signature
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq"))] #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq"))]
pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), schnorr::SchnorrSig>, pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), SchnorrSig>,
/// Map of Control blocks to Script version pair /// Map of Control blocks to Script version pair
#[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq"))] #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq"))]
pub tap_scripts: BTreeMap<ControlBlock, (Script, LeafVersion)>, pub tap_scripts: BTreeMap<ControlBlock, (Script, LeafVersion)>,
@ -163,7 +162,7 @@ impl Map for Input {
} }
PSBT_IN_PARTIAL_SIG => { PSBT_IN_PARTIAL_SIG => {
impl_psbt_insert_pair! { impl_psbt_insert_pair! {
self.partial_sigs <= <raw_key: PublicKey>|<raw_value: Vec<u8>> self.partial_sigs <= <raw_key: PublicKey>|<raw_value: EcdsaSig>
} }
} }
PSBT_IN_SIGHASH_TYPE => { PSBT_IN_SIGHASH_TYPE => {
@ -210,12 +209,12 @@ impl Map for Input {
} }
PSBT_IN_TAP_KEY_SIG => { PSBT_IN_TAP_KEY_SIG => {
impl_psbt_insert_pair! { impl_psbt_insert_pair! {
self.tap_key_sig <= <raw_key: _>|<raw_value: schnorr::SchnorrSig> self.tap_key_sig <= <raw_key: _>|<raw_value: SchnorrSig>
} }
} }
PSBT_IN_TAP_SCRIPT_SIG => { PSBT_IN_TAP_SCRIPT_SIG => {
impl_psbt_insert_pair! { impl_psbt_insert_pair! {
self.tap_script_sigs <= <raw_key: (XOnlyPublicKey, TapLeafHash)>|<raw_value: schnorr::SchnorrSig> self.tap_script_sigs <= <raw_key: (XOnlyPublicKey, TapLeafHash)>|<raw_value: SchnorrSig>
} }
} }
PSBT_IN_TAP_LEAF_SCRIPT=> { PSBT_IN_TAP_LEAF_SCRIPT=> {
@ -267,7 +266,7 @@ impl Map for Input {
} }
impl_psbt_get_pair! { impl_psbt_get_pair! {
rv.push(self.partial_sigs as <PSBT_IN_PARTIAL_SIG, PublicKey>|<Vec<u8>>) rv.push(self.partial_sigs as <PSBT_IN_PARTIAL_SIG, PublicKey>|<EcdsaSig>)
} }
impl_psbt_get_pair! { impl_psbt_get_pair! {
@ -311,11 +310,11 @@ impl Map for Input {
} }
impl_psbt_get_pair! { impl_psbt_get_pair! {
rv.push(self.tap_key_sig as <PSBT_IN_TAP_KEY_SIG, _>|<Vec<u8>>) rv.push(self.tap_key_sig as <PSBT_IN_TAP_KEY_SIG, _>|<SchnorrSig>)
} }
impl_psbt_get_pair! { impl_psbt_get_pair! {
rv.push(self.tap_script_sigs as <PSBT_IN_TAP_SCRIPT_SIG, (XOnlyPublicKey, TapLeafHash)>|<Vec<u8>>) rv.push(self.tap_script_sigs as <PSBT_IN_TAP_SCRIPT_SIG, (XOnlyPublicKey, TapLeafHash)>|<SchnorrSig>)
} }
impl_psbt_get_pair! { impl_psbt_get_pair! {

View File

@ -477,7 +477,7 @@ mod tests {
witness_script: None, witness_script: None,
partial_sigs: vec![( partial_sigs: vec![(
"0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(), "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(),
vec![8, 5, 4], "304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe701".parse().unwrap(),
)].into_iter().collect(), )].into_iter().collect(),
bip32_derivation: keypaths.clone(), bip32_derivation: keypaths.clone(),
final_script_witness: Some(vec![vec![1, 3], vec![5]]), final_script_witness: Some(vec![vec![1, 3], vec![5]]),

View File

@ -28,7 +28,7 @@ use consensus::encode::{self, serialize, Decodable, Encodable, deserialize_parti
use secp256k1::{self, XOnlyPublicKey}; use secp256k1::{self, XOnlyPublicKey};
use util::bip32::{ChildNumber, Fingerprint, KeySource}; use util::bip32::{ChildNumber, Fingerprint, KeySource};
use hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use hashes::{hash160, ripemd160, sha256, sha256d, Hash};
use util::ecdsa::PublicKey; use util::ecdsa::{PublicKey, EcdsaSig};
use util::psbt; use util::psbt;
use util::taproot::{TapBranchHash, TapLeafHash, ControlBlock, LeafVersion}; use util::taproot::{TapBranchHash, TapLeafHash, ControlBlock, LeafVersion};
use schnorr; use schnorr;
@ -89,6 +89,30 @@ impl Deserialize for PublicKey {
} }
} }
impl Serialize for EcdsaSig {
fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(72);
buf.extend(self.sig.serialize_der().iter());
buf.push(self.hash_ty as u8);
buf
}
}
impl Deserialize for EcdsaSig {
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
let (sighash_byte, signature) = bytes.split_last()
.ok_or(encode::Error::ParseFailed("empty partial signature data"))?;
Ok(EcdsaSig {
sig: secp256k1::ecdsa::Signature::from_der(signature)
.map_err(|_| encode::Error::ParseFailed("non-DER encoded signature"))?,
// NB: Since BIP-174 says "the signature as would be pushed to the stack from
// a scriptSig or witness" we should use a consensus deserialization and do
// not error on a non-standard values.
hash_ty: EcdsaSigHashType::from_u32_consensus(*sighash_byte as u32)
})
}
}
impl Serialize for KeySource { impl Serialize for KeySource {
fn serialize(&self) -> Vec<u8> { fn serialize(&self) -> Vec<u8> {
let mut rv: Vec<u8> = Vec::with_capacity(key_source_len(&self)); let mut rv: Vec<u8> = Vec::with_capacity(key_source_len(&self));