Skip to content

Commit 2d6c4db

Browse files
committed
Merge #797: Add musig string and serde support
1d8b862 Add tests for MuSig2 de/serialization (Daniel Roberts) 6100c6d Add serialization and deserialization for relevant MuSig2 types (Daniel Roberts) c668f8e Fix `musig::ParseError` docs (Daniel Roberts) Pull request description: This addresses #796 to add serialization and deserialization for musig types that make sense to transmit as part of a protocol. There might be an argument to also include every type with a `serialize()` but IMHO this change implements serialization for the types that that make the most sense. The test vectors come from https://github.com/Ademan/rust-secp256k1/tree/musig-serde-vectors because I wasn't sure if/what the licensing implications are of taking the vectors from bip-0327 (which is BSD licensed). However, they could be trivially dropped in if desired (convert to lowercase first). The actual implementations are heavily "borrowed" from the serialization+deserialization of `schnorr::Signature` ACKs for top commit: apoelstra: ACK 1d8b862; successfully ran local tests Tree-SHA512: 85fe69347b73761d1ac3c18a7496d2734d7ca5be9d819e1c0d576bfe183f1be3c1710621beee1df86b1ab78da5437cd798684801d5d1a831806702982a6ff9a3
2 parents e77f33f + 1d8b862 commit 2d6c4db

File tree

2 files changed

+245
-3
lines changed

2 files changed

+245
-3
lines changed

src/musig.rs

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use std;
1313

1414
use crate::ffi::{self, CPtr};
1515
use crate::{
16-
schnorr, Error, Keypair, Message, PublicKey, Scalar, Secp256k1, SecretKey, Signing,
16+
from_hex, schnorr, Error, Keypair, Message, PublicKey, Scalar, Secp256k1, SecretKey, Signing,
1717
Verification, XOnlyPublicKey,
1818
};
1919

20-
/// Musig partial signature parsing errors
20+
/// Musig parsing errors
2121
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
2222
pub enum ParseError {
2323
/// Parse Argument is malformed. This might occur if the point is on the secp order,
@@ -222,6 +222,62 @@ impl CPtr for PartialSignature {
222222
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
223223
}
224224

225+
impl fmt::LowerHex for PartialSignature {
226+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227+
for b in self.serialize() {
228+
write!(f, "{:02x}", b)?;
229+
}
230+
Ok(())
231+
}
232+
}
233+
234+
impl fmt::Display for PartialSignature {
235+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
236+
}
237+
238+
impl core::str::FromStr for PartialSignature {
239+
type Err = ParseError;
240+
fn from_str(s: &str) -> Result<Self, Self::Err> {
241+
let mut res = [0u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN];
242+
match from_hex(s, &mut res) {
243+
Ok(ffi::MUSIG_PART_SIG_SERIALIZED_LEN) => PartialSignature::from_byte_array(&res),
244+
_ => Err(ParseError::MalformedArg),
245+
}
246+
}
247+
}
248+
249+
#[cfg(feature = "serde")]
250+
impl serde::Serialize for PartialSignature {
251+
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
252+
if s.is_human_readable() {
253+
s.collect_str(self)
254+
} else {
255+
s.serialize_bytes(&self.serialize()[..])
256+
}
257+
}
258+
}
259+
260+
#[cfg(feature = "serde")]
261+
impl<'de> serde::Deserialize<'de> for PartialSignature {
262+
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
263+
if d.is_human_readable() {
264+
d.deserialize_str(super::serde_util::FromStrVisitor::new(
265+
"a hex string representing a MuSig2 partial signature",
266+
))
267+
} else {
268+
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
269+
"a raw MuSig2 partial signature",
270+
|slice| {
271+
let bytes: &[u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN] =
272+
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
273+
274+
Self::from_byte_array(bytes)
275+
},
276+
))
277+
}
278+
}
279+
}
280+
225281
impl PartialSignature {
226282
/// Serialize a PartialSignature as a byte array.
227283
pub fn serialize(&self) -> [u8; ffi::MUSIG_PART_SIG_SERIALIZED_LEN] {
@@ -635,6 +691,62 @@ impl CPtr for PublicNonce {
635691
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
636692
}
637693

694+
impl fmt::LowerHex for PublicNonce {
695+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
696+
for b in self.serialize() {
697+
write!(f, "{:02x}", b)?;
698+
}
699+
Ok(())
700+
}
701+
}
702+
703+
impl fmt::Display for PublicNonce {
704+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
705+
}
706+
707+
impl core::str::FromStr for PublicNonce {
708+
type Err = ParseError;
709+
fn from_str(s: &str) -> Result<Self, Self::Err> {
710+
let mut res = [0u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN];
711+
match from_hex(s, &mut res) {
712+
Ok(ffi::MUSIG_PUBNONCE_SERIALIZED_LEN) => PublicNonce::from_byte_array(&res),
713+
_ => Err(ParseError::MalformedArg),
714+
}
715+
}
716+
}
717+
718+
#[cfg(feature = "serde")]
719+
impl serde::Serialize for PublicNonce {
720+
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
721+
if s.is_human_readable() {
722+
s.collect_str(self)
723+
} else {
724+
s.serialize_bytes(&self.serialize()[..])
725+
}
726+
}
727+
}
728+
729+
#[cfg(feature = "serde")]
730+
impl<'de> serde::Deserialize<'de> for PublicNonce {
731+
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
732+
if d.is_human_readable() {
733+
d.deserialize_str(super::serde_util::FromStrVisitor::new(
734+
"a hex string representing a MuSig2 public nonce",
735+
))
736+
} else {
737+
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
738+
"a raw MuSig2 public nonce",
739+
|slice| {
740+
let bytes: &[u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN] =
741+
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
742+
743+
Self::from_byte_array(bytes)
744+
},
745+
))
746+
}
747+
}
748+
}
749+
638750
impl PublicNonce {
639751
/// Serialize a PublicNonce
640752
pub fn serialize(&self) -> [u8; ffi::MUSIG_PUBNONCE_SERIALIZED_LEN] {
@@ -696,6 +808,62 @@ impl CPtr for AggregatedNonce {
696808
fn as_mut_c_ptr(&mut self) -> *mut Self::Target { self.as_mut_ptr() }
697809
}
698810

811+
impl fmt::LowerHex for AggregatedNonce {
812+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
813+
for b in self.serialize() {
814+
write!(f, "{:02x}", b)?;
815+
}
816+
Ok(())
817+
}
818+
}
819+
820+
impl fmt::Display for AggregatedNonce {
821+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
822+
}
823+
824+
impl core::str::FromStr for AggregatedNonce {
825+
type Err = ParseError;
826+
fn from_str(s: &str) -> Result<Self, Self::Err> {
827+
let mut res = [0u8; ffi::MUSIG_AGGNONCE_SERIALIZED_LEN];
828+
match from_hex(s, &mut res) {
829+
Ok(ffi::MUSIG_AGGNONCE_SERIALIZED_LEN) => AggregatedNonce::from_byte_array(&res),
830+
_ => Err(ParseError::MalformedArg),
831+
}
832+
}
833+
}
834+
835+
#[cfg(feature = "serde")]
836+
impl serde::Serialize for AggregatedNonce {
837+
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
838+
if s.is_human_readable() {
839+
s.collect_str(self)
840+
} else {
841+
s.serialize_bytes(&self.serialize()[..])
842+
}
843+
}
844+
}
845+
846+
#[cfg(feature = "serde")]
847+
impl<'de> serde::Deserialize<'de> for AggregatedNonce {
848+
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
849+
if d.is_human_readable() {
850+
d.deserialize_str(super::serde_util::FromStrVisitor::new(
851+
"a hex string representing a MuSig2 aggregated nonce",
852+
))
853+
} else {
854+
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
855+
"a raw MuSig2 aggregated nonce",
856+
|slice| {
857+
let bytes: &[u8; ffi::MUSIG_AGGNONCE_SERIALIZED_LEN] =
858+
slice.try_into().map_err(|_| ParseError::MalformedArg)?;
859+
860+
Self::from_byte_array(bytes)
861+
},
862+
))
863+
}
864+
}
865+
}
866+
699867
impl AggregatedNonce {
700868
/// Combine received public nonces into a single aggregated nonce
701869
///
@@ -1520,4 +1688,21 @@ mod tests {
15201688

15211689
let _agg_sig = session.partial_sig_agg(&[]);
15221690
}
1691+
1692+
#[test]
1693+
fn de_serialization() {
1694+
const MUSIG_PUBLIC_NONCE_HEX: &str = "03f4a361abd3d50535be08421dbc73b0a8f595654ae3238afcaf2599f94e25204c036ba174214433e21f5cd0fcb14b038eb40b05b7e7c820dd21aa568fdb0a9de4d7";
1695+
let pubnonce: PublicNonce = MUSIG_PUBLIC_NONCE_HEX.parse().unwrap();
1696+
1697+
assert_eq!(pubnonce.to_string(), MUSIG_PUBLIC_NONCE_HEX);
1698+
1699+
const MUSIG_AGGREGATED_NONCE_HEX: &str = "0218c30fe0f567a4a9c05eb4835e2735419cf30f834c9ce2fe3430f021ba4eacd503112e97bcf6a022d236d71a9357824a2b19515f980131b3970b087cadf94cc4a7";
1700+
let aggregated_nonce: AggregatedNonce = MUSIG_AGGREGATED_NONCE_HEX.parse().unwrap();
1701+
assert_eq!(aggregated_nonce.to_string(), MUSIG_AGGREGATED_NONCE_HEX);
1702+
1703+
const MUSIG_PARTIAL_SIGNATURE_HEX: &str =
1704+
"289eeb2f5efc314aa6d87bf58125043c96d15a007db4b6aaaac7d18086f49a99";
1705+
let partial_signature: PartialSignature = MUSIG_PARTIAL_SIGNATURE_HEX.parse().unwrap();
1706+
assert_eq!(partial_signature.to_string(), MUSIG_PARTIAL_SIGNATURE_HEX);
1707+
}
15231708
}

tests/serde.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ extern crate bincode;
44
extern crate secp256k1;
55
extern crate serde_cbor;
66

7+
use secp256k1::{musig, PublicKey, SecretKey, XOnlyPublicKey};
78
#[cfg(feature = "global-context")]
89
use secp256k1::{Keypair, Secp256k1};
9-
use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey};
1010

1111
// Arbitrary key data.
1212

@@ -35,6 +35,43 @@ static XONLY_PK_BYTES: [u8; 32] = [
3535
0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
3636
];
3737

38+
#[rustfmt::skip]
39+
static MUSIG_PUBLIC_NONCE_BYTES: [u8; 74] = [
40+
0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41+
0x03, 0xf4, 0xa3, 0x61, 0xab, 0xd3, 0xd5, 0x05,
42+
0x35, 0xbe, 0x08, 0x42, 0x1d, 0xbc, 0x73, 0xb0,
43+
0xa8, 0xf5, 0x95, 0x65, 0x4a, 0xe3, 0x23, 0x8a,
44+
0xfc, 0xaf, 0x25, 0x99, 0xf9, 0x4e, 0x25, 0x20,
45+
0x4c, 0x03, 0x6b, 0xa1, 0x74, 0x21, 0x44, 0x33,
46+
0xe2, 0x1f, 0x5c, 0xd0, 0xfc, 0xb1, 0x4b, 0x03,
47+
0x8e, 0xb4, 0x0b, 0x05, 0xb7, 0xe7, 0xc8, 0x20,
48+
0xdd, 0x21, 0xaa, 0x56, 0x8f, 0xdb, 0x0a, 0x9d,
49+
0xe4, 0xd7,
50+
];
51+
52+
#[rustfmt::skip]
53+
static MUSIG_AGGREGATED_NONCE_BYTES: [u8; 74] = [
54+
0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
55+
0x02, 0x18, 0xc3, 0x0f, 0xe0, 0xf5, 0x67, 0xa4,
56+
0xa9, 0xc0, 0x5e, 0xb4, 0x83, 0x5e, 0x27, 0x35,
57+
0x41, 0x9c, 0xf3, 0x0f, 0x83, 0x4c, 0x9c, 0xe2,
58+
0xfe, 0x34, 0x30, 0xf0, 0x21, 0xba, 0x4e, 0xac,
59+
0xd5, 0x03, 0x11, 0x2e, 0x97, 0xbc, 0xf6, 0xa0,
60+
0x22, 0xd2, 0x36, 0xd7, 0x1a, 0x93, 0x57, 0x82,
61+
0x4a, 0x2b, 0x19, 0x51, 0x5f, 0x98, 0x01, 0x31,
62+
0xb3, 0x97, 0x0b, 0x08, 0x7c, 0xad, 0xf9, 0x4c,
63+
0xc4, 0xa7,
64+
];
65+
66+
#[rustfmt::skip]
67+
static MUSIG_PARTIAL_SIG_BYTES: [u8; 40] = [
68+
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
69+
0x28, 0x9e, 0xeb, 0x2f, 0x5e, 0xfc, 0x31, 0x4a,
70+
0xa6, 0xd8, 0x7b, 0xf5, 0x81, 0x25, 0x04, 0x3c,
71+
0x96, 0xd1, 0x5a, 0x00, 0x7d, 0xb4, 0xb6, 0xaa,
72+
0xaa, 0xc7, 0xd1, 0x80, 0x86, 0xf4, 0x9a, 0x99,
73+
];
74+
3875
fn secret_key() -> SecretKey {
3976
SecretKey::from_slice(&SK_BYTES).expect("failed to create sk from slice")
4077
}
@@ -85,3 +122,23 @@ fn cbor() {
85122
// It also adds a 1-byte length prefix and a byte of metadata for the whole vector.
86123
assert_eq!(e.len(), 54);
87124
}
125+
126+
#[test]
127+
fn musig() {
128+
let public_nonce: musig::PublicNonce = bincode::deserialize(&MUSIG_PUBLIC_NONCE_BYTES).unwrap();
129+
let ser = bincode::serialize(&public_nonce).unwrap();
130+
131+
assert_eq!(ser, MUSIG_PUBLIC_NONCE_BYTES);
132+
133+
let aggregated_nonce: musig::AggregatedNonce =
134+
bincode::deserialize(&MUSIG_AGGREGATED_NONCE_BYTES).unwrap();
135+
let ser = bincode::serialize(&aggregated_nonce).unwrap();
136+
137+
assert_eq!(ser, MUSIG_AGGREGATED_NONCE_BYTES);
138+
139+
let partial_sig: musig::PartialSignature =
140+
bincode::deserialize(&MUSIG_PARTIAL_SIG_BYTES).unwrap();
141+
let ser = bincode::serialize(&partial_sig).unwrap();
142+
143+
assert_eq!(ser, MUSIG_PARTIAL_SIG_BYTES);
144+
}

0 commit comments

Comments
 (0)