Skip to content

Commit 53eb559

Browse files
committed
Merge bitcoin/bitcoin#32305: test: add test for decoding PSBT with MuSig2 PSBT key types (BIP 373)
4b24186 test: add test for decoding PSBT with MuSig2 PSBT key types (BIP 373) (Sebastian Falbesoner) 8ba245c test: add constants for MuSig2 PSBT key types (BIP 373) (Sebastian Falbesoner) Pull request description: This PR is a follow-up to #31247 (see bitcoin/bitcoin#31247 (review)) and adds a functional test for decoding PSBTs (using the `decodepsbt` RPC) with MuSig2 per-input and per-output types. The first commit adds the new MuSig2 key types to the test frameworks and extends the PSBT serialization to cope with lists of bytestrings. ACKs for top commit: achow101: ACK 4b24186 rkrux: re-ACK 4b24186 Tree-SHA512: f12919f71b3fff74df1d7ddaa8db455b1b139f7abd51d7f3fa5d750fc7dd613454b438c4e0dedad679476d414fa1da43ef1121e486b0bdfd97d5ef8bdf37f060
2 parents e7a9372 + 4b24186 commit 53eb559

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

test/functional/rpc_psbt.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@
2929
PSBT_IN_SHA256,
3030
PSBT_IN_HASH160,
3131
PSBT_IN_HASH256,
32+
PSBT_IN_MUSIG2_PARTIAL_SIG,
33+
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS,
34+
PSBT_IN_MUSIG2_PUB_NONCE,
3235
PSBT_IN_NON_WITNESS_UTXO,
3336
PSBT_IN_WITNESS_UTXO,
37+
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS,
3438
PSBT_OUT_TAP_TREE,
3539
)
3640
from test_framework.script import CScript, OP_TRUE
@@ -199,6 +203,75 @@ def test_input_confs_control(self):
199203

200204
wallet.unloadwallet()
201205

206+
def test_decodepsbt_musig2_input_output_types(self):
207+
self.log.info("Test decoding PSBT with MuSig2 per-input and per-output types")
208+
# create 2-of-2 musig2 using fake aggregate key, leaf hash, pubnonce, and partial sig
209+
# TODO: actually implement MuSig2 aggregation (for decoding only it doesn't matter though)
210+
_, in_pubkey1 = generate_keypair()
211+
_, in_pubkey2 = generate_keypair()
212+
_, in_fake_agg_pubkey = generate_keypair()
213+
fake_leaf_hash = randbytes(32)
214+
fake_pubnonce = randbytes(66)
215+
fake_partialsig = randbytes(32)
216+
tx = CTransaction()
217+
tx.vin = [CTxIn(outpoint=COutPoint(hash=int('ee' * 32, 16), n=0), scriptSig=b"")]
218+
tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]
219+
psbt = PSBT()
220+
psbt.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})
221+
participant1_keydata = in_pubkey1 + in_fake_agg_pubkey + fake_leaf_hash
222+
psbt.i = [PSBTMap({
223+
bytes([PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS]) + in_fake_agg_pubkey: [in_pubkey1, in_pubkey2],
224+
bytes([PSBT_IN_MUSIG2_PUB_NONCE]) + participant1_keydata: fake_pubnonce,
225+
bytes([PSBT_IN_MUSIG2_PARTIAL_SIG]) + participant1_keydata: fake_partialsig,
226+
})]
227+
_, out_pubkey1 = generate_keypair()
228+
_, out_pubkey2 = generate_keypair()
229+
_, out_fake_agg_pubkey = generate_keypair()
230+
psbt.o = [PSBTMap({
231+
bytes([PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS]) + out_fake_agg_pubkey: [out_pubkey1, out_pubkey2],
232+
})]
233+
res = self.nodes[0].decodepsbt(psbt.to_base64())
234+
assert_equal(len(res["inputs"]), 1)
235+
res_input = res["inputs"][0]
236+
assert_equal(len(res["outputs"]), 1)
237+
res_output = res["outputs"][0]
238+
239+
assert "musig2_participant_pubkeys" in res_input
240+
in_participant_pks = res_input["musig2_participant_pubkeys"][0]
241+
assert "aggregate_pubkey" in in_participant_pks
242+
assert_equal(in_participant_pks["aggregate_pubkey"], in_fake_agg_pubkey.hex())
243+
assert "participant_pubkeys" in in_participant_pks
244+
assert_equal(in_participant_pks["participant_pubkeys"], [in_pubkey1.hex(), in_pubkey2.hex()])
245+
246+
assert "musig2_pubnonces" in res_input
247+
in_pubnonce = res_input["musig2_pubnonces"][0]
248+
assert "participant_pubkey" in in_pubnonce
249+
assert_equal(in_pubnonce["participant_pubkey"], in_pubkey1.hex())
250+
assert "aggregate_pubkey" in in_pubnonce
251+
assert_equal(in_pubnonce["aggregate_pubkey"], in_fake_agg_pubkey.hex())
252+
assert "leaf_hash" in in_pubnonce
253+
assert_equal(in_pubnonce["leaf_hash"], fake_leaf_hash.hex())
254+
assert "pubnonce" in in_pubnonce
255+
assert_equal(in_pubnonce["pubnonce"], fake_pubnonce.hex())
256+
257+
assert "musig2_partial_sigs" in res_input
258+
in_partialsig = res_input["musig2_partial_sigs"][0]
259+
assert "participant_pubkey" in in_partialsig
260+
assert_equal(in_partialsig["participant_pubkey"], in_pubkey1.hex())
261+
assert "aggregate_pubkey" in in_partialsig
262+
assert_equal(in_partialsig["aggregate_pubkey"], in_fake_agg_pubkey.hex())
263+
assert "leaf_hash" in in_partialsig
264+
assert_equal(in_partialsig["leaf_hash"], fake_leaf_hash.hex())
265+
assert "partial_sig" in in_partialsig
266+
assert_equal(in_partialsig["partial_sig"], fake_partialsig.hex())
267+
268+
assert "musig2_participant_pubkeys" in res_output
269+
out_participant_pks = res_output["musig2_participant_pubkeys"][0]
270+
assert "aggregate_pubkey" in out_participant_pks
271+
assert_equal(out_participant_pks["aggregate_pubkey"], out_fake_agg_pubkey.hex())
272+
assert "participant_pubkeys" in out_participant_pks
273+
assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])
274+
202275
def assert_change_type(self, psbtx, expected_type):
203276
"""Assert that the given PSBT has a change output with the given type."""
204277

@@ -959,6 +1032,8 @@ def test_psbt_input_keys(psbt_input, keys):
9591032
assert hash.hex() in res_input[preimage_key]
9601033
assert_equal(res_input[preimage_key][hash.hex()], preimage.hex())
9611034

1035+
self.test_decodepsbt_musig2_input_output_types()
1036+
9621037
self.log.info("Test that combining PSBTs with different transactions fails")
9631038
tx = CTransaction()
9641039
tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]

test/functional/test_framework/psbt.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
PSBT_IN_TAP_BIP32_DERIVATION = 0x16
5151
PSBT_IN_TAP_INTERNAL_KEY = 0x17
5252
PSBT_IN_TAP_MERKLE_ROOT = 0x18
53+
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a
54+
PSBT_IN_MUSIG2_PUB_NONCE = 0x1b
55+
PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c
5356
PSBT_IN_PROPRIETARY = 0xfc
5457

5558
# per-output types
@@ -61,6 +64,7 @@
6164
PSBT_OUT_TAP_INTERNAL_KEY = 0x05
6265
PSBT_OUT_TAP_TREE = 0x06
6366
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
67+
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
6468
PSBT_OUT_PROPRIETARY = 0xfc
6569

6670

@@ -88,6 +92,9 @@ def serialize(self):
8892
for k,v in self.map.items():
8993
if isinstance(k, int) and 0 <= k and k <= 255:
9094
k = bytes([k])
95+
if isinstance(v, list):
96+
assert all(type(elem) is bytes for elem in v)
97+
v = b"".join(v) # simply concatenate the byte-strings w/o size prefixes
9198
m += ser_compact_size(len(k)) + k
9299
m += ser_compact_size(len(v)) + v
93100
m += b"\x00"

0 commit comments

Comments
 (0)