diff --git a/src/musig.rs b/src/musig.rs index 7fd28b84f..ac298eb12 100644 --- a/src/musig.rs +++ b/src/musig.rs @@ -13,11 +13,11 @@ use std; use crate::ffi::{self, CPtr}; use crate::{ - schnorr, Error, Keypair, Message, PublicKey, Scalar, Secp256k1, SecretKey, Signing, + from_hex, schnorr, Error, Keypair, Message, PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification, XOnlyPublicKey, }; -/// Musig partial signature parsing errors +/// Musig parsing errors #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum ParseError { /// Parse Argument is malformed. This might occur if the point is on the secp order, @@ -222,6 +222,62 @@ impl CPtr for PartialSignature { fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() } } +impl fmt::LowerHex for PartialSignature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for b in self.serialize() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl fmt::Display for PartialSignature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } +} + +impl core::str::FromStr for PartialSignature { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let mut res = [0u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN]; + match from_hex(s, &mut res) { + Ok(ffi::MUSIG_PART_SIG_SERIALIZED_LEN) => PartialSignature::from_byte_array(&res), + _ => Err(ParseError::MalformedArg), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for PartialSignature { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(self) + } else { + s.serialize_bytes(&self.serialize()[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for PartialSignature { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + d.deserialize_str(super::serde_util::FromStrVisitor::new( + "a hex string representing a MuSig2 partial signature", + )) + } else { + d.deserialize_bytes(super::serde_util::BytesVisitor::new( + "a raw MuSig2 partial signature", + |slice| { + let bytes: &[u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN] = + slice.try_into().map_err(|_| ParseError::MalformedArg)?; + + Self::from_byte_array(bytes) + }, + )) + } + } +} + impl PartialSignature { /// Serialize a PartialSignature as a byte array. pub fn serialize(&self) -> [u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN] { @@ -635,6 +691,62 @@ impl CPtr for PublicNonce { fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() } } +impl fmt::LowerHex for PublicNonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for b in self.serialize() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl fmt::Display for PublicNonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } +} + +impl core::str::FromStr for PublicNonce { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let mut res = [0u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN]; + match from_hex(s, &mut res) { + Ok(ffi::MUSIG_PUBNONCE_SERIALIZED_LEN) => PublicNonce::from_byte_array(&res), + _ => Err(ParseError::MalformedArg), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for PublicNonce { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(self) + } else { + s.serialize_bytes(&self.serialize()[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for PublicNonce { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + d.deserialize_str(super::serde_util::FromStrVisitor::new( + "a hex string representing a MuSig2 public nonce", + )) + } else { + d.deserialize_bytes(super::serde_util::BytesVisitor::new( + "a raw MuSig2 public nonce", + |slice| { + let bytes: &[u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN] = + slice.try_into().map_err(|_| ParseError::MalformedArg)?; + + Self::from_byte_array(bytes) + }, + )) + } + } +} + impl PublicNonce { /// Serialize a PublicNonce pub fn serialize(&self) -> [u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN] { @@ -696,6 +808,62 @@ impl CPtr for AggregatedNonce { fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() } } +impl fmt::LowerHex for AggregatedNonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for b in self.serialize() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl fmt::Display for AggregatedNonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } +} + +impl core::str::FromStr for AggregatedNonce { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let mut res = [0u8; ffi::MUSIG_AGGNONCE_SERIALIZED_LEN]; + match from_hex(s, &mut res) { + Ok(ffi::MUSIG_AGGNONCE_SERIALIZED_LEN) => AggregatedNonce::from_byte_array(&res), + _ => Err(ParseError::MalformedArg), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for AggregatedNonce { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(self) + } else { + s.serialize_bytes(&self.serialize()[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for AggregatedNonce { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + d.deserialize_str(super::serde_util::FromStrVisitor::new( + "a hex string representing a MuSig2 aggregated nonce", + )) + } else { + d.deserialize_bytes(super::serde_util::BytesVisitor::new( + "a raw MuSig2 aggregated nonce", + |slice| { + let bytes: &[u8; ffi::MUSIG_AGGNONCE_SERIALIZED_LEN] = + slice.try_into().map_err(|_| ParseError::MalformedArg)?; + + Self::from_byte_array(bytes) + }, + )) + } + } +} + impl AggregatedNonce { /// Combine received public nonces into a single aggregated nonce /// @@ -1520,4 +1688,21 @@ mod tests { let _agg_sig = session.partial_sig_agg(&[]); } + + #[test] + fn de_serialization() { + const MUSIG_PUBLIC_NONCE_HEX: &str = "03f4a361abd3d50535be08421dbc73b0a8f595654ae3238afcaf2599f94e25204c036ba174214433e21f5cd0fcb14b038eb40b05b7e7c820dd21aa568fdb0a9de4d7"; + let pubnonce: PublicNonce = MUSIG_PUBLIC_NONCE_HEX.parse().unwrap(); + + assert_eq!(pubnonce.to_string(), MUSIG_PUBLIC_NONCE_HEX); + + const MUSIG_AGGREGATED_NONCE_HEX: &str = "0218c30fe0f567a4a9c05eb4835e2735419cf30f834c9ce2fe3430f021ba4eacd503112e97bcf6a022d236d71a9357824a2b19515f980131b3970b087cadf94cc4a7"; + let aggregated_nonce: AggregatedNonce = MUSIG_AGGREGATED_NONCE_HEX.parse().unwrap(); + assert_eq!(aggregated_nonce.to_string(), MUSIG_AGGREGATED_NONCE_HEX); + + const MUSIG_PARTIAL_SIGNATURE_HEX: &str = + "289eeb2f5efc314aa6d87bf58125043c96d15a007db4b6aaaac7d18086f49a99"; + let partial_signature: PartialSignature = MUSIG_PARTIAL_SIGNATURE_HEX.parse().unwrap(); + assert_eq!(partial_signature.to_string(), MUSIG_PARTIAL_SIGNATURE_HEX); + } } diff --git a/tests/serde.rs b/tests/serde.rs index 3b8df3915..b46d1e919 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -4,9 +4,9 @@ extern crate bincode; extern crate secp256k1; extern crate serde_cbor; +use secp256k1::{musig, PublicKey, SecretKey, XOnlyPublicKey}; #[cfg(feature = "global-context")] use secp256k1::{Keypair, Secp256k1}; -use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey}; // Arbitrary key data. @@ -35,6 +35,43 @@ static XONLY_PK_BYTES: [u8; 32] = [ 0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66, ]; +#[rustfmt::skip] +static MUSIG_PUBLIC_NONCE_BYTES: [u8; 74] = [ + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xf4, 0xa3, 0x61, 0xab, 0xd3, 0xd5, 0x05, + 0x35, 0xbe, 0x08, 0x42, 0x1d, 0xbc, 0x73, 0xb0, + 0xa8, 0xf5, 0x95, 0x65, 0x4a, 0xe3, 0x23, 0x8a, + 0xfc, 0xaf, 0x25, 0x99, 0xf9, 0x4e, 0x25, 0x20, + 0x4c, 0x03, 0x6b, 0xa1, 0x74, 0x21, 0x44, 0x33, + 0xe2, 0x1f, 0x5c, 0xd0, 0xfc, 0xb1, 0x4b, 0x03, + 0x8e, 0xb4, 0x0b, 0x05, 0xb7, 0xe7, 0xc8, 0x20, + 0xdd, 0x21, 0xaa, 0x56, 0x8f, 0xdb, 0x0a, 0x9d, + 0xe4, 0xd7, +]; + +#[rustfmt::skip] +static MUSIG_AGGREGATED_NONCE_BYTES: [u8; 74] = [ + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x18, 0xc3, 0x0f, 0xe0, 0xf5, 0x67, 0xa4, + 0xa9, 0xc0, 0x5e, 0xb4, 0x83, 0x5e, 0x27, 0x35, + 0x41, 0x9c, 0xf3, 0x0f, 0x83, 0x4c, 0x9c, 0xe2, + 0xfe, 0x34, 0x30, 0xf0, 0x21, 0xba, 0x4e, 0xac, + 0xd5, 0x03, 0x11, 0x2e, 0x97, 0xbc, 0xf6, 0xa0, + 0x22, 0xd2, 0x36, 0xd7, 0x1a, 0x93, 0x57, 0x82, + 0x4a, 0x2b, 0x19, 0x51, 0x5f, 0x98, 0x01, 0x31, + 0xb3, 0x97, 0x0b, 0x08, 0x7c, 0xad, 0xf9, 0x4c, + 0xc4, 0xa7, +]; + +#[rustfmt::skip] +static MUSIG_PARTIAL_SIG_BYTES: [u8; 40] = [ + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x9e, 0xeb, 0x2f, 0x5e, 0xfc, 0x31, 0x4a, + 0xa6, 0xd8, 0x7b, 0xf5, 0x81, 0x25, 0x04, 0x3c, + 0x96, 0xd1, 0x5a, 0x00, 0x7d, 0xb4, 0xb6, 0xaa, + 0xaa, 0xc7, 0xd1, 0x80, 0x86, 0xf4, 0x9a, 0x99, +]; + fn secret_key() -> SecretKey { SecretKey::from_slice(&SK_BYTES).expect("failed to create sk from slice") } @@ -85,3 +122,23 @@ fn cbor() { // It also adds a 1-byte length prefix and a byte of metadata for the whole vector. assert_eq!(e.len(), 54); } + +#[test] +fn musig() { + let public_nonce: musig::PublicNonce = bincode::deserialize(&MUSIG_PUBLIC_NONCE_BYTES).unwrap(); + let ser = bincode::serialize(&public_nonce).unwrap(); + + assert_eq!(ser, MUSIG_PUBLIC_NONCE_BYTES); + + let aggregated_nonce: musig::AggregatedNonce = + bincode::deserialize(&MUSIG_AGGREGATED_NONCE_BYTES).unwrap(); + let ser = bincode::serialize(&aggregated_nonce).unwrap(); + + assert_eq!(ser, MUSIG_AGGREGATED_NONCE_BYTES); + + let partial_sig: musig::PartialSignature = + bincode::deserialize(&MUSIG_PARTIAL_SIG_BYTES).unwrap(); + let ser = bincode::serialize(&partial_sig).unwrap(); + + assert_eq!(ser, MUSIG_PARTIAL_SIG_BYTES); +}