From d1fc478c978de3a28ff5135d342516d44ee01f3e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 4 Oct 2023 12:24:19 +0200 Subject: [PATCH] psbt: Bip340 sig data type and parsing --- psbt/src/coders.rs | 8 ++--- psbt/src/data.rs | 13 ++++---- psbt/src/lib.rs | 4 ++- psbt/src/sigtypes.rs | 79 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/psbt/src/coders.rs b/psbt/src/coders.rs index 67e0d04..037dd7b 100644 --- a/psbt/src/coders.rs +++ b/psbt/src/coders.rs @@ -36,7 +36,7 @@ use bp::{ use crate::keys::KeyValue; use crate::{ - EcdsaSig, GlobalKey, Input, InputKey, KeyData, KeyMap, KeyPair, KeyType, LockHeight, + GlobalKey, Input, InputKey, KeyData, KeyMap, KeyPair, KeyType, LegacySig, LockHeight, LockTimestamp, Map, ModifiableFlags, NonStandardSighashType, Output, OutputKey, PropKey, Psbt, PsbtUnsupportedVer, PsbtVer, SighashType, ValueData, }; @@ -629,7 +629,7 @@ impl Decode for KeyOrigin { } } -impl Encode for EcdsaSig { +impl Encode for LegacySig { fn encode(&self, writer: &mut impl Write) -> Result { let sig = self.sig.serialize_der(); writer.write_all(sig.as_ref())?; @@ -638,14 +638,14 @@ impl Encode for EcdsaSig { } } -impl Decode for EcdsaSig { +impl Decode for LegacySig { fn decode(reader: &mut impl Read) -> Result { let mut buf = Vec::with_capacity(78); reader.read_to_end(&mut buf)?; let (sighash, sig) = buf.split_last().ok_or(PsbtError::EmptySig)?; let sig = ecdsa::Signature::from_der(&sig).map_err(PsbtError::InvalidSig)?; let sighash_type = SighashType::from_psbt_u8(*sighash)?; - Ok(EcdsaSig { sig, sighash_type }) + Ok(LegacySig { sig, sighash_type }) } } diff --git a/psbt/src/data.rs b/psbt/src/data.rs index ef71562..be90c6a 100644 --- a/psbt/src/data.rs +++ b/psbt/src/data.rs @@ -20,8 +20,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; - use amplify::confinement::Confined; use amplify::num::u5; use amplify::{Bytes20, Bytes32}; @@ -33,7 +31,8 @@ use bp::{ use indexmap::IndexMap; use crate::{ - EcdsaSig, KeyData, LockHeight, LockTimestamp, PropKey, PsbtVer, SighashType, ValueData, + Bip340Sig, KeyData, LegacySig, LockHeight, LockTimestamp, PropKey, PsbtVer, SighashType, + ValueData, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] @@ -370,7 +369,7 @@ pub struct Input { /// A map from public keys to their corresponding signature as would be /// pushed to the stack from a scriptSig or witness for a non-taproot /// inputs. - pub partial_sigs: IndexMap, + pub partial_sigs: IndexMap, /// The sighash type to be used for this input. Signatures for this input /// must use the sighash type. @@ -414,14 +413,14 @@ pub struct Input { /// SHA256 algorithm twice. pub hash360: IndexMap, - // TODO: Add taproot data structures /// The 64 or 65 byte Schnorr signature for key path spending a Taproot output. Finalizers /// should remove this field after `PSBT_IN_FINAL_SCRIPTWITNESS` is constructed. - pub tap_key_sig: Option, + pub tap_key_sig: Option, + // TODO: Add taproot data structures: ControlBlock etc /// The 64 or 65 byte Schnorr signature for this pubkey and leaf combination. Finalizers /// should remove this field after `PSBT_IN_FINAL_SCRIPTWITNESS` is constructed. - pub tap_script_sig: IndexMap, + pub tap_script_sig: IndexMap, /// The script for this leaf as would be provided in the witness stack followed by the single /// byte leaf version. Note that the leaves included in this field should be those that the diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index 05bdb3b..afec252 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -37,7 +37,9 @@ pub use coders::{Decode, DecodeError, Encode, PsbtError}; pub use data::{Input, ModifiableFlags, Output, Prevout, Psbt}; pub use keys::{GlobalKey, InputKey, KeyPair, KeyType, OutputKey, PropKey}; pub use maps::{KeyData, KeyMap, Map, ValueData}; -pub use sigtypes::{EcdsaSig, EcdsaSigError, NonStandardSighashType, SighashFlag, SighashType}; +pub use sigtypes::{ + Bip340Sig, LegacySig, NonStandardSighashType, SigError, SighashFlag, SighashType, +}; pub use timelocks::{InvalidTimelock, LockHeight, LockTimestamp, TimelockParseError}; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] diff --git a/psbt/src/sigtypes.rs b/psbt/src/sigtypes.rs index c38fd85..7a3dc8c 100644 --- a/psbt/src/sigtypes.rs +++ b/psbt/src/sigtypes.rs @@ -22,8 +22,7 @@ use std::iter; -use bp::secp256k1; -use bp::secp256k1::ecdsa; +use bp::secp256k1::{ecdsa, schnorr}; /// This type is consensus valid but an input including it would prevent the transaction from /// being relayed on today's Bitcoin network. @@ -175,7 +174,7 @@ impl SighashType { /// An ECDSA signature-related error. #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] #[display(doc_comments)] -pub enum EcdsaSigError { +pub enum SigError { /// Non-standard sighash type. #[display(inner)] #[from] @@ -185,8 +184,13 @@ pub enum EcdsaSigError { EmptySignature, /// invalid signature DER encoding. - #[from(secp256k1::Error)] DerEncoding, + + /// invalid BIP340 signature length ({0}). + Bip340Encoding(usize), + + /// invalid BIP340 signature. + InvalidSignature, } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] @@ -195,33 +199,33 @@ pub enum EcdsaSigError { derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -pub struct EcdsaSig { +pub struct LegacySig { /// The underlying ECDSA Signature pub sig: ecdsa::Signature, /// The corresponding hash type pub sighash_type: SighashType, } -impl EcdsaSig { +impl LegacySig { /// Constructs an ECDSA bitcoin signature for [`SighashType::All`]. - pub fn sighash_all(sig: ecdsa::Signature) -> EcdsaSig { - EcdsaSig { + pub fn sighash_all(sig: ecdsa::Signature) -> LegacySig { + LegacySig { sig, sighash_type: SighashType::all(), } } /// Deserializes from slice following the standardness rules for [`SighashType`]. - pub fn from_bytes(bytes: &[u8]) -> Result { - let (hash_ty, sig) = bytes.split_last().ok_or(EcdsaSigError::EmptySignature)?; + pub fn from_bytes(bytes: &[u8]) -> Result { + let (hash_ty, sig) = bytes.split_last().ok_or(SigError::EmptySignature)?; let sighash_type = SighashType::from_standard(*hash_ty as u32)?; - let sig = ecdsa::Signature::from_der(sig)?; - Ok(EcdsaSig { sig, sighash_type }) + let sig = ecdsa::Signature::from_der(sig).map_err(|_| SigError::DerEncoding)?; + Ok(LegacySig { sig, sighash_type }) } - /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) into `Vec`. + /// Serializes an Legacy signature (inner secp256k1 signature in DER format) into `Vec`. + // TODO: add support to serialize to a writer to SerializedSig pub fn to_vec(self) -> Vec { - // TODO: add support to serialize to a writer to SerializedSig self.sig .serialize_der() .iter() @@ -230,3 +234,50 @@ impl EcdsaSig { .collect() } } + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Bip340Sig { + /// The underlying ECDSA Signature + pub sig: schnorr::Signature, + /// The corresponding hash type + pub sighash_type: Option, +} + +impl Bip340Sig { + /// Constructs an ECDSA bitcoin signature for [`SighashType::All`]. + pub fn sighash_default(sig: schnorr::Signature) -> Self { + Bip340Sig { + sig, + sighash_type: None, + } + } + + /// Deserializes from slice following the standardness rules for [`SighashType`]. + pub fn from_bytes(bytes: &[u8]) -> Result { + let (hash_ty, sig) = match bytes.len() { + 0 => return Err(SigError::EmptySignature), + 64 => (None, bytes), + 65 => (Some(bytes[64] as u32), &bytes[..64]), + invalid => return Err(SigError::Bip340Encoding(invalid)), + }; + let sighash_type = hash_ty.map(SighashType::from_standard).transpose()?; + let sig = schnorr::Signature::from_slice(sig).map_err(|_| SigError::InvalidSignature)?; + Ok(Bip340Sig { sig, sighash_type }) + } + + /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) into `Vec`. + // TODO: add support to serialize to a writer to SerializedSig + pub fn to_vec(self) -> Vec { + let mut ser = Vec::::with_capacity(65); + ser.extend_from_slice(&self.sig[..]); + if let Some(sighash_type) = self.sighash_type { + ser.push(sighash_type.into_u8()) + } + ser + } +}