Skip to content

Commit

Permalink
psbt: Bip340 sig data type and parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Oct 4, 2023
1 parent ab637d3 commit d1fc478
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 26 deletions.
8 changes: 4 additions & 4 deletions psbt/src/coders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -629,7 +629,7 @@ impl Decode for KeyOrigin {
}
}

impl Encode for EcdsaSig {
impl Encode for LegacySig {
fn encode(&self, writer: &mut impl Write) -> Result<usize, IoError> {
let sig = self.sig.serialize_der();
writer.write_all(sig.as_ref())?;
Expand All @@ -638,14 +638,14 @@ impl Encode for EcdsaSig {
}
}

impl Decode for EcdsaSig {
impl Decode for LegacySig {
fn decode(reader: &mut impl Read) -> Result<Self, DecodeError> {
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 })
}
}

Expand Down
13 changes: 6 additions & 7 deletions psbt/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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)]
Expand Down Expand Up @@ -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<LegacyPubkey, EcdsaSig>,
pub partial_sigs: IndexMap<LegacyPubkey, LegacySig>,

/// The sighash type to be used for this input. Signatures for this input
/// must use the sighash type.
Expand Down Expand Up @@ -414,14 +413,14 @@ pub struct Input {
/// SHA256 algorithm twice.
pub hash360: IndexMap<Bytes32, ValueData>,

// 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<ValueData>,
pub tap_key_sig: Option<Bip340Sig>,

// 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<KeyData, ValueData>,
pub tap_script_sig: IndexMap<KeyData, Bip340Sig>,

/// 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
Expand Down
4 changes: 3 additions & 1 deletion psbt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
79 changes: 65 additions & 14 deletions psbt/src/sigtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand All @@ -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)]
Expand All @@ -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<Self, EcdsaSigError> {
let (hash_ty, sig) = bytes.split_last().ok_or(EcdsaSigError::EmptySignature)?;
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SigError> {
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<u8> {
// TODO: add support to serialize to a writer to SerializedSig
self.sig
.serialize_der()
.iter()
Expand All @@ -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<SighashType>,
}

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<Self, SigError> {
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<u8> {
let mut ser = Vec::<u8>::with_capacity(65);
ser.extend_from_slice(&self.sig[..]);
if let Some(sighash_type) = self.sighash_type {
ser.push(sighash_type.into_u8())
}
ser
}
}

0 comments on commit d1fc478

Please sign in to comment.