Skip to content

Commit 5e0b86d

Browse files
committed
Merge rust-bitcoin#4440: Add support for de/serializing PSBT_{IN,OUT}_MUSIG2_PARTICIPANT_PUBKEYS
2481695 Add tests for BIP-373 PSBT_{IN,OUT}_MUSIG2_PARTICIPANT_PUBKEYS serialization and deserialization (Daniel Roberts) 3e8e6d9 Add BIP-373 PSBT_{IN,OUT}_MUSIG2_PARTICIPANT_PUBKEYS serialization and deserialization (Daniel Roberts) Pull request description: This change adds support for serializing and deserializing two PSBT keys from BIP-373: `PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS` and `PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS` This is a part of rust-bitcoin#4207 that can be implemented independently of the rest (which depends on rust-bitcoin/rust-secp256k1#716). I believe this satisfactorily avoids changing things multiple times on end users, *however* it's not *completely* transparent to end users, since any code that currently accesses these fields through `unknown` will need to be updated. Later, when `PSBT_IN_MUSIG2_PUB_NONCE` and `PSBT_IN_MUSIG2_PARTIAL_SIG` are supported, code will need to be updated a second time to retrieve them from the correct place instead of `unknown`. I'm of the opinion that this imposes a very minor maintenance burden, only consisting of *removing* deserialization code. ### Notes/Requests for feedback - For the most part I used my judgement rather than `cargo fmt` since `cargo fmt` already had a lot of other complaints, but of course I'll update if I need to. - To satisfy the requirement that every commit pass tests, the commit updating the psbt serde regression test should probably be squashed into the first commit, but I just wanted to confirm that before I did it. I suppose similarly, the test commit could be squashed as well? - I waffled between `musig2_participants` and `musig2_participant_pubkeys`, but I've decided to go with `musig2_participant_pubkeys` because that is consistent with Bitcoin Core ACKs for top commit: tcharding: ACK 2481695 apoelstra: ACK 2481695; successfully ran local tests Tree-SHA512: af884923593c9cbb24ff3f1f08219458538592fabde85d5d65bc2d9bc7bf0b1a73dac38d2c56303b4f3162088db129ea7e879c3d4b324e965933c121ef939a07
2 parents 9371018 + 2481695 commit 5e0b86d

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

bitcoin/src/psbt/map/input.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const PSBT_IN_TAP_BIP32_DERIVATION: u64 = 0x16;
5959
const PSBT_IN_TAP_INTERNAL_KEY: u64 = 0x17;
6060
/// Type: Taproot Merkle Root PSBT_IN_TAP_MERKLE_ROOT = 0x18
6161
const PSBT_IN_TAP_MERKLE_ROOT: u64 = 0x18;
62+
/// Type: MuSig2 Public Keys Participating in Aggregate Input PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a
63+
const PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x1a;
6264
/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC
6365
const PSBT_IN_PROPRIETARY: u64 = 0xFC;
6466

@@ -113,6 +115,8 @@ pub struct Input {
113115
pub tap_internal_key: Option<XOnlyPublicKey>,
114116
/// Taproot Merkle root.
115117
pub tap_merkle_root: Option<TapNodeHash>,
118+
/// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated.
119+
pub musig2_participant_pubkeys: BTreeMap<secp256k1::PublicKey, Vec<secp256k1::PublicKey>>,
116120
/// Proprietary key-value pairs for this input.
117121
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
118122
/// Unknown key-value pairs for this input.
@@ -352,6 +356,11 @@ impl Input {
352356
self.tap_merkle_root <= <raw_key: _>|< raw_value: TapNodeHash>
353357
}
354358
}
359+
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS => {
360+
impl_psbt_insert_pair! {
361+
self.musig2_participant_pubkeys <= <raw_key: secp256k1::PublicKey>|< raw_value: Vec<secp256k1::PublicKey> >
362+
}
363+
}
355364
PSBT_IN_PROPRIETARY => {
356365
let key = raw::ProprietaryKey::try_from(raw_key.clone())?;
357366
match self.proprietary.entry(key) {
@@ -390,6 +399,7 @@ impl Input {
390399
self.tap_script_sigs.extend(other.tap_script_sigs);
391400
self.tap_scripts.extend(other.tap_scripts);
392401
self.tap_key_origins.extend(other.tap_key_origins);
402+
self.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys);
393403
self.proprietary.extend(other.proprietary);
394404
self.unknown.extend(other.unknown);
395405

@@ -482,6 +492,11 @@ impl Map for Input {
482492
impl_psbt_get_pair! {
483493
rv.push(self.tap_merkle_root, PSBT_IN_TAP_MERKLE_ROOT)
484494
}
495+
496+
impl_psbt_get_pair! {
497+
rv.push_map(self.musig2_participant_pubkeys, PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS)
498+
}
499+
485500
for (key, value) in self.proprietary.iter() {
486501
rv.push(raw::Pair { key: key.to_key(), value: value.clone() });
487502
}

bitcoin/src/psbt/map/output.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const PSBT_OUT_TAP_INTERNAL_KEY: u64 = 0x05;
2020
const PSBT_OUT_TAP_TREE: u64 = 0x06;
2121
/// Type: Taproot Key BIP 32 Derivation Path PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
2222
const PSBT_OUT_TAP_BIP32_DERIVATION: u64 = 0x07;
23+
/// Type: MuSig2 Public Keys Participating in Aggregate Output PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
24+
const PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x08;
2325
/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC
2426
const PSBT_OUT_PROPRIETARY: u64 = 0xFC;
2527

@@ -40,6 +42,8 @@ pub struct Output {
4042
pub tap_tree: Option<TapTree>,
4143
/// Map of tap root x only keys to origin info and leaf hashes contained in it.
4244
pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>,
45+
/// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated.
46+
pub musig2_participant_pubkeys: BTreeMap<secp256k1::PublicKey, Vec<secp256k1::PublicKey>>,
4347
/// Proprietary key-value pairs for this output.
4448
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
4549
/// Unknown key-value pairs for this output.
@@ -90,6 +94,11 @@ impl Output {
9094
self.tap_key_origins <= <raw_key: XOnlyPublicKey>|< raw_value: (Vec<TapLeafHash>, KeySource)>
9195
}
9296
}
97+
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS => {
98+
impl_psbt_insert_pair! {
99+
self.musig2_participant_pubkeys <= <raw_key: secp256k1::PublicKey>|< raw_value: Vec<secp256k1::PublicKey> >
100+
}
101+
}
93102
_ => match self.unknown.entry(raw_key) {
94103
btree_map::Entry::Vacant(empty_key) => {
95104
empty_key.insert(raw_value);
@@ -107,6 +116,7 @@ impl Output {
107116
self.proprietary.extend(other.proprietary);
108117
self.unknown.extend(other.unknown);
109118
self.tap_key_origins.extend(other.tap_key_origins);
119+
self.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys);
110120

111121
combine!(redeem_script, self, other);
112122
combine!(witness_script, self, other);
@@ -143,6 +153,10 @@ impl Map for Output {
143153
rv.push_map(self.tap_key_origins, PSBT_OUT_TAP_BIP32_DERIVATION)
144154
}
145155

156+
impl_psbt_get_pair! {
157+
rv.push_map(self.musig2_participant_pubkeys, PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS)
158+
}
159+
146160
for (key, value) in self.proprietary.iter() {
147161
rv.push(raw::Pair { key: key.to_key(), value: value.clone() });
148162
}

bitcoin/src/psbt/mod.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,8 @@ mod tests {
13391339
secp256k1::{All, SecretKey},
13401340
};
13411341

1342+
use std::str::FromStr;
1343+
13421344
use super::*;
13431345
use crate::address::script_pubkey::ScriptExt as _;
13441346
use crate::bip32::ChildNumber;
@@ -2233,6 +2235,49 @@ mod tests {
22332235
assert!(!rtt.proprietary.is_empty());
22342236
}
22352237

2238+
// Deserialize MuSig2 PSBT participant keys according to BIP-373
2239+
#[test]
2240+
fn serialize_and_deserialize_musig2_participants() {
2241+
// XXX: Does not cover PSBT_IN_MUSIG2_PUB_NONCE, PSBT_IN_MUSIG2_PARTIAL_SIG (yet)
2242+
2243+
let expected_in_agg_pk = secp256k1::PublicKey::from_str("021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00").unwrap();
2244+
let expected_in_pubkeys = vec![
2245+
secp256k1::PublicKey::from_str("02bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a").unwrap(),
2246+
secp256k1::PublicKey::from_str("0355212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e2").unwrap(),
2247+
];
2248+
2249+
let expected_out_agg_pk = secp256k1::PublicKey::from_str("0364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5").unwrap();
2250+
2251+
let expected_out_pubkeys = vec![
2252+
secp256k1::PublicKey::from_str("02841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b525").unwrap(),
2253+
secp256k1::PublicKey::from_str("02bad833849a98cdfb0a0749609ddccab16ad54485ecc67f828df4bdc4f2b90d4c").unwrap(),
2254+
];
2255+
2256+
const PSBT_HEX: &str = "70736274ff01005e02000000017b42be5ea467afe0d0571dc4a91bef97ff9605a590c0b8d5892323946414d1810000000000ffffffff01f0b9f50500000000225120bc7e18f55e2c7a28d78cadac1bc72c248372375d269bafe6b315bc40505d07e5000000000001012b00e1f50500000000225120de564ebf8ff7bd9bb41bd88264c04b1713ebb9dc8df36319091d2eabb16cda6221161401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e000500eb4cbe62211655212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20500755abbf92116bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a05002a33dfd90117201401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00221a021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e004202bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a0355212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20001052064934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5210764934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba50500fa4c6afa22080364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba54202841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b52502bad833849a98cdfb0a0749609ddccab16ad54485ecc67f828df4bdc4f2b90d4c00";
2257+
2258+
let psbt = hex_psbt(PSBT_HEX).unwrap();
2259+
2260+
assert_eq!(psbt.inputs[0].musig2_participant_pubkeys.len(), 1);
2261+
assert_eq!(
2262+
psbt.inputs[0].musig2_participant_pubkeys.iter().next().unwrap(),
2263+
(&expected_in_agg_pk, &expected_in_pubkeys)
2264+
);
2265+
2266+
assert_eq!(psbt.outputs[0].musig2_participant_pubkeys.len(), 1);
2267+
assert_eq!(
2268+
psbt.outputs[0].musig2_participant_pubkeys.iter().next().unwrap(),
2269+
(&expected_out_agg_pk, &expected_out_pubkeys)
2270+
);
2271+
2272+
// Check round trip de/serialization
2273+
assert_eq!(psbt.serialize_hex(), PSBT_HEX);
2274+
2275+
const PSBT_TRUNCATED_MUSIG_PARTICIPANTS_HEX: &str = "70736274ff01005e0200000001f034711ce319b1db76ce73440f2cb64a7e3a02e75c936b8d8a4958a024ea8d870000000000ffffffff01f0b9f50500000000225120bc7e18f55e2c7a28d78cadac1bc72c248372375d269bafe6b315bc40505d07e5000000000001012b00e1f50500000000225120de564ebf8ff7bd9bb41bd88264c04b1713ebb9dc8df36319091d2eabb16cda6221161401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e000500eb4cbe62211655212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20500755abbf92116bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a05002a33dfd90117201401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00221a021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e002a02bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a0355212dff7b3d7e810001052064934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5210764934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba50500fa4c6afa22080364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba52a02841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b52502bad833849a98cdfb00";
2276+
2277+
hex_psbt(PSBT_TRUNCATED_MUSIG_PARTICIPANTS_HEX)
2278+
.expect_err("Deserializing PSBT with truncated musig participants should error");
2279+
}
2280+
22362281
// PSBTs taken from BIP 174 test vectors.
22372282
#[test]
22382283
fn combine_psbts() {

bitcoin/src/psbt/serialize.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,28 @@ impl Deserialize for secp256k1::PublicKey {
172172
}
173173
}
174174

175+
impl Serialize for Vec<secp256k1::PublicKey> {
176+
fn serialize(&self) -> Vec<u8> {
177+
let mut result: Vec<u8> = Vec::with_capacity(secp256k1::constants::PUBLIC_KEY_SIZE * self.len());
178+
179+
for pubkey in self.iter() {
180+
result.extend(Serialize::serialize(pubkey));
181+
}
182+
183+
result
184+
}
185+
}
186+
187+
impl Deserialize for Vec<secp256k1::PublicKey> {
188+
fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
189+
bytes.chunks(secp256k1::constants::PUBLIC_KEY_SIZE)
190+
.map(|pubkey_bytes| {
191+
secp256k1::PublicKey::deserialize(pubkey_bytes)
192+
})
193+
.collect()
194+
}
195+
}
196+
175197
impl Serialize for ecdsa::Signature {
176198
fn serialize(&self) -> Vec<u8> { self.to_vec() }
177199
}

0 commit comments

Comments
 (0)