From c36a3da6f073b07afa2d618786a61b13507a1ee2 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 12:00:29 +0100 Subject: [PATCH 1/7] Add EcdsaSig::sighash_all convenience constructor --- src/util/ecdsa.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs index 9cd4bb2c..5456cc03 100644 --- a/src/util/ecdsa.rs +++ b/src/util/ecdsa.rs @@ -425,6 +425,13 @@ pub struct EcdsaSig { } impl EcdsaSig { + /// Constructs ECDSA bitcoin signature for [`EcdsaSigHashType::All`] + pub fn sighash_all(sig: secp256k1::Signature) -> EcdsaSig { + EcdsaSig { + sig, + hash_ty: EcdsaSigHashType::All + } + } /// Deserialize from slice pub fn from_slice(sl: &[u8]) -> Result { From daf0eacf3df3e9da609da704eee592156cb593b5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 11:58:08 +0100 Subject: [PATCH 2/7] Improve NonStandardSigHashType --- src/blockdata/transaction.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index e7eafabe..5e92eea6 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -666,11 +666,11 @@ impl Decodable for Transaction { /// This type is consensus valid but an input including it would prevent the transaction from /// being relayed on today's Bitcoin network. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct NonStandardSigHashType; +pub struct NonStandardSigHashType(pub u32); impl fmt::Display for NonStandardSigHashType { 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), 0x82 => Ok(EcdsaSigHashType::NonePlusAnyoneCanPay), 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_consensus(nonstandard_hashtype), EcdsaSigHashType::All); // 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 From 0af1c3f320dd1b341dbf8ea722c7312df1dec593 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 12:01:05 +0100 Subject: [PATCH 3/7] Add Display and FromStr for EcdsaSig --- src/util/ecdsa.rs | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs index 5456cc03..54dd2692 100644 --- a/src/util/ecdsa.rs +++ b/src/util/ecdsa.rs @@ -25,11 +25,12 @@ use io; use secp256k1::{self, Secp256k1}; use network::constants::Network; -use hashes::{Hash, hash160}; +use hashes::{Hash, hash160, hex}; +use hashes::hex::FromHex; use hash_types::{PubkeyHash, WPubkeyHash}; use util::base58; use util::key::Error; -use blockdata::transaction::EcdsaSigHashType; +use blockdata::transaction::{EcdsaSigHashType, NonStandardSigHashType}; /// A Bitcoin ECDSA public key @@ -426,7 +427,7 @@ pub struct EcdsaSig { impl EcdsaSig { /// Constructs ECDSA bitcoin signature for [`EcdsaSigHashType::All`] - pub fn sighash_all(sig: secp256k1::Signature) -> EcdsaSig { + pub fn sighash_all(sig: secp256k1::ecdsa::Signature) -> EcdsaSig { EcdsaSig { sig, hash_ty: EcdsaSigHashType::All @@ -438,7 +439,7 @@ impl EcdsaSig { let (hash_ty, sig) = sl.split_last() .ok_or(EcdsaSigError::EmptySignature)?; 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) .map_err(EcdsaSigError::Secp256k1)?; Ok(EcdsaSig { sig, hash_ty }) @@ -453,11 +454,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 { + 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. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum EcdsaSigError { + /// Hex encoding error + HexEncoding(hex::Error), /// Base58 encoding error - NonStandardSigHashType(u8), + NonStandardSigHashType(u32), /// Empty Signature EmptySignature, /// secp256k1-related error @@ -474,6 +498,7 @@ impl fmt::Display for EcdsaSigError { write!(f, "Invalid Ecdsa signature: {}", e), EcdsaSigError::EmptySignature => write!(f, "Empty ECDSA signature"), + EcdsaSigError::HexEncoding(e) => write!(f, "EcdsaSig hex encoding error: {}", e) } } } @@ -488,6 +513,18 @@ impl From for EcdsaSigError { } } +impl From for EcdsaSigError { + fn from(err: NonStandardSigHashType) -> Self { + EcdsaSigError::NonStandardSigHashType(err.0) + } +} + +impl From for EcdsaSigError { + fn from(err: hex::Error) -> Self { + EcdsaSigError::HexEncoding(err) + } +} + #[cfg(test)] mod tests { use io; From c92057d98f1d04a95dcc5b3ccd3624e96b1be4f5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 11:51:33 +0100 Subject: [PATCH 4/7] PSBT serialize/deserialize impl for EcdsaSig type --- src/util/psbt/serialize.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/util/psbt/serialize.rs b/src/util/psbt/serialize.rs index e1a5cfed..467cba84 100644 --- a/src/util/psbt/serialize.rs +++ b/src/util/psbt/serialize.rs @@ -20,7 +20,7 @@ use prelude::*; -use io; +use ::{EcdsaSig, io}; use blockdata::script::Script; use blockdata::transaction::{EcdsaSigHashType, Transaction, TxOut}; @@ -89,6 +89,30 @@ impl Deserialize for PublicKey { } } +impl Serialize for EcdsaSig { + fn serialize(&self) -> Vec { + 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 { + 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 { fn serialize(&self) -> Vec { let mut rv: Vec = Vec::with_capacity(key_source_len(&self)); From 141dbbd1b9eb97613a380879c943d092544472e3 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 12:06:29 +0100 Subject: [PATCH 5/7] Add serde impl for EcdsaSig --- src/util/ecdsa.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs index 54dd2692..bdd8800c 100644 --- a/src/util/ecdsa.rs +++ b/src/util/ecdsa.rs @@ -418,6 +418,7 @@ impl<'de> ::serde::Deserialize<'de> for PublicKey { /// An ECDSA signature with the corresponding hash type. #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EcdsaSig { /// The underlying ECDSA Signature pub sig: secp256k1::ecdsa::Signature, From 2b530000d3f41b4ff278b955c0fd57f22832b587 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 12:06:49 +0100 Subject: [PATCH 6/7] Use EcdsaSig in PSBT partial signatures instead of Vec --- src/util/psbt/map/input.rs | 11 +++++------ src/util/psbt/mod.rs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index 4a593b21..4c9f0506 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -14,7 +14,7 @@ use prelude::*; -use io; +use ::{EcdsaSig, io}; use blockdata::script::Script; use blockdata::transaction::{EcdsaSigHashType, Transaction, TxOut}; @@ -87,9 +87,8 @@ pub struct Input { /// including P2SH embedded ones. pub witness_utxo: Option, /// A map from public keys to their corresponding signature as would be - /// pushed to the stack from a scriptSig or witness. - #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_byte_values"))] - pub partial_sigs: BTreeMap>, + /// pushed to the stack from a scriptSig or witness for a non-taproot inputs. + pub partial_sigs: BTreeMap, /// The sighash type to be used for this input. Signatures for this input /// must use the sighash type. pub sighash_type: Option, @@ -163,7 +162,7 @@ impl Map for Input { } PSBT_IN_PARTIAL_SIG => { impl_psbt_insert_pair! { - self.partial_sigs <= |> + self.partial_sigs <= | } } PSBT_IN_SIGHASH_TYPE => { @@ -267,7 +266,7 @@ impl Map for Input { } impl_psbt_get_pair! { - rv.push(self.partial_sigs as |>) + rv.push(self.partial_sigs as |) } impl_psbt_get_pair! { diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 6e3b4a03..159b3b96 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -477,7 +477,7 @@ mod tests { witness_script: None, partial_sigs: vec![( "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(), - vec![8, 5, 4], + "304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe701".parse().unwrap(), )].into_iter().collect(), bip32_derivation: keypaths.clone(), final_script_witness: Some(vec![vec![1, 3], vec![5]]), From 14ace9266680f31628e54615275f6ba8d52fcfbe Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 6 Jan 2022 12:09:39 +0100 Subject: [PATCH 7/7] Fix SchnorrSig type references in PSBT serialization macros --- src/util/psbt/map/input.rs | 14 +++++++------- src/util/psbt/serialize.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index 4c9f0506..926bf899 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -29,7 +29,7 @@ use util::psbt::raw; use util::psbt::serialize::Deserialize; use util::psbt::{Error, error}; -use schnorr; +use ::{SchnorrSig}; use util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapBranchHash}; /// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00 @@ -120,10 +120,10 @@ pub struct Input { #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_byte_values"))] pub hash256_preimages: BTreeMap>, /// Serialized schnorr signature with sighash type for key spend - pub tap_key_sig: Option, + pub tap_key_sig: Option, /// Map of | with signature #[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 #[cfg_attr(feature = "serde", serde(with = "::serde_utils::btreemap_as_seq"))] pub tap_scripts: BTreeMap, @@ -209,12 +209,12 @@ impl Map for Input { } PSBT_IN_TAP_KEY_SIG => { impl_psbt_insert_pair! { - self.tap_key_sig <= | + self.tap_key_sig <= | } } PSBT_IN_TAP_SCRIPT_SIG => { impl_psbt_insert_pair! { - self.tap_script_sigs <= | + self.tap_script_sigs <= | } } PSBT_IN_TAP_LEAF_SCRIPT=> { @@ -310,11 +310,11 @@ impl Map for Input { } impl_psbt_get_pair! { - rv.push(self.tap_key_sig as |>) + rv.push(self.tap_key_sig as |) } impl_psbt_get_pair! { - rv.push(self.tap_script_sigs as |>) + rv.push(self.tap_script_sigs as |) } impl_psbt_get_pair! { diff --git a/src/util/psbt/serialize.rs b/src/util/psbt/serialize.rs index 467cba84..aa0e4035 100644 --- a/src/util/psbt/serialize.rs +++ b/src/util/psbt/serialize.rs @@ -20,7 +20,7 @@ use prelude::*; -use ::{EcdsaSig, io}; +use io; use blockdata::script::Script; use blockdata::transaction::{EcdsaSigHashType, Transaction, TxOut}; @@ -28,7 +28,7 @@ use consensus::encode::{self, serialize, Decodable, Encodable, deserialize_parti use secp256k1::{self, XOnlyPublicKey}; use util::bip32::{ChildNumber, Fingerprint, KeySource}; use hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use util::ecdsa::PublicKey; +use util::ecdsa::{PublicKey, EcdsaSig}; use util::psbt; use util::taproot::{TapBranchHash, TapLeafHash, ControlBlock, LeafVersion}; use schnorr;