mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-12 07:39:41 +02:00
Merge bitcoin/bitcoin#35269: musig: Include pubnonce in session id
2ef6679c2ctest: Check that MuSig2 signing does not reuse nonces (Ava Chow)bb05986c0amusig: Include pubnonce in session id (Ava Chow) Pull request description: It is safe to have multiple musig signing sessions over the same message so long as the nonces used are different. Including the pubnonce in the session id allows for multiple simultaneous signing sessions over the same message, rather than asserting when the user tries to do this. The second commit tests this behavior, both ensuring that there is no crash, and verifying that both sessions produce unique nonces and signatures to verify that no reuse is occurring. Lastly, the assertion in `SetMuSig2SecNonce` is retained as hitting it now would indicate that a nonce has been reused. We prefer to assert and crash rather than do something that is highly likely to leak a private key. Fixes #35250 ACKs for top commit: rkrux: lgtm ACK2ef6679c2cjunbyjun1238: utACK2ef6679c2ctheStack: ACK2ef6679c2cTree-SHA512: 9fb60b68ebe0ea9656408afb65b9ec9f280632e1bb84a4821b074c8d8569847845f7c29da800c757b9ddf3aa31aa890dd9e3646cf119917a714e7daf20be2198
This commit is contained in:
@@ -12,6 +12,7 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_greater_than,
|
||||
assert_not_equal,
|
||||
)
|
||||
|
||||
PRIVKEY_RE = re.compile(r"^tr\((.+?)/.+\)#.{8}$")
|
||||
@@ -111,6 +112,31 @@ class WalletMuSigTest(BitcoinTestFramework):
|
||||
|
||||
return wallets, psbt
|
||||
|
||||
def assert_musig_signer_data(self, first, second, different_key):
|
||||
assert_equal(first["participant_pubkey"], second["participant_pubkey"])
|
||||
assert_equal(first["aggregate_pubkey"], second["aggregate_pubkey"])
|
||||
if "leaf_hash" in first:
|
||||
assert_equal(first["leaf_hash"], second["leaf_hash"])
|
||||
else:
|
||||
assert "leaf_hash" not in second
|
||||
|
||||
assert_not_equal(first[different_key], second[different_key])
|
||||
|
||||
def assert_musig_aggregate_in_script(self, signer_data, pattern, psbtin):
|
||||
pubkey = signer_data["aggregate_pubkey"][2:]
|
||||
if "pkh" in pattern or "pk_h" in pattern:
|
||||
pubkey = hash160(bytes.fromhex(pubkey)).hex()
|
||||
if pubkey in psbtin["witness_utxo"]["scriptPubKey"]["hex"]:
|
||||
return
|
||||
elif "taproot_scripts" in psbtin:
|
||||
for leaf_scripts in psbtin["taproot_scripts"]:
|
||||
if pubkey in leaf_scripts["script"]:
|
||||
break
|
||||
else:
|
||||
assert False, "Aggregate pubkey not seen as output key, or in any scripts"
|
||||
else:
|
||||
assert False, "Aggregate pubkey not seen as output key or internal key"
|
||||
|
||||
def test_failure_case_1(self, comment, pat):
|
||||
self.log.info(f"Testing {comment}")
|
||||
wallets, psbt = self.setup_musig_scenario(pat)
|
||||
@@ -247,66 +273,57 @@ class WalletMuSigTest(BitcoinTestFramework):
|
||||
part_pks.remove(deriv_path["pubkey"])
|
||||
assert_equal(len(part_pks), 0)
|
||||
|
||||
# Run 2 signing sessions simultaneously to verify no nonce reuse
|
||||
# Add pubnonces
|
||||
nonce_psbts = []
|
||||
nonce_psbts2 = []
|
||||
for i, wallet in enumerate(wallets):
|
||||
if nosign_wallets and i in nosign_wallets:
|
||||
continue
|
||||
proc = wallet.walletprocesspsbt(psbt=psbt, sighashtype=sighash_type)
|
||||
assert_equal(proc["complete"], False)
|
||||
nonce_psbts.append(proc["psbt"])
|
||||
for psbt_list in [nonce_psbts, nonce_psbts2]:
|
||||
proc = wallet.walletprocesspsbt(psbt=psbt, sighashtype=sighash_type)
|
||||
assert_equal(proc["complete"], False)
|
||||
psbt_list.append(proc["psbt"])
|
||||
|
||||
comb_nonce_psbt = self.nodes[0].combinepsbt(nonce_psbts)
|
||||
comb_nonce_psbt2 = self.nodes[0].combinepsbt(nonce_psbts2)
|
||||
|
||||
dec_psbt = self.nodes[0].decodepsbt(comb_nonce_psbt)
|
||||
dec_psbt2 = self.nodes[0].decodepsbt(comb_nonce_psbt2)
|
||||
assert_equal(len(dec_psbt["inputs"][0]["musig2_pubnonces"]), expected_pubnonces)
|
||||
for pn in dec_psbt["inputs"][0]["musig2_pubnonces"]:
|
||||
pubkey = pn["aggregate_pubkey"][2:]
|
||||
if "pkh" in pattern or "pk_h" in pattern:
|
||||
pubkey = hash160(bytes.fromhex(pubkey)).hex()
|
||||
if pubkey in dec_psbt["inputs"][0]["witness_utxo"]["scriptPubKey"]["hex"]:
|
||||
continue
|
||||
elif "taproot_scripts" in dec_psbt["inputs"][0]:
|
||||
for leaf_scripts in dec_psbt["inputs"][0]["taproot_scripts"]:
|
||||
if pubkey in leaf_scripts["script"]:
|
||||
break
|
||||
else:
|
||||
assert False, "Aggregate pubkey for pubnonce not seen as output key, or in any scripts"
|
||||
else:
|
||||
assert False, "Aggregate pubkey for pubnonce not seen as output key or internal key"
|
||||
assert_equal(len(dec_psbt2["inputs"][0]["musig2_pubnonces"]), expected_pubnonces)
|
||||
for pn, pn2 in zip(dec_psbt["inputs"][0]["musig2_pubnonces"], dec_psbt2["inputs"][0]["musig2_pubnonces"]):
|
||||
self.assert_musig_signer_data(pn, pn2, "pubnonce")
|
||||
self.assert_musig_aggregate_in_script(pn, pattern, dec_psbt["inputs"][0])
|
||||
|
||||
# Add partial sigs
|
||||
psig_psbts = []
|
||||
psig_psbts2 = []
|
||||
for i, wallet in enumerate(wallets):
|
||||
if nosign_wallets and i in nosign_wallets:
|
||||
continue
|
||||
proc = wallet.walletprocesspsbt(psbt=comb_nonce_psbt, sighashtype=sighash_type)
|
||||
assert_equal(proc["complete"], False)
|
||||
psig_psbts.append(proc["psbt"])
|
||||
for psbt, psbt_list in [(comb_nonce_psbt, psig_psbts), (comb_nonce_psbt2, psig_psbts2)]:
|
||||
proc = wallet.walletprocesspsbt(psbt=psbt, sighashtype=sighash_type)
|
||||
assert_equal(proc["complete"], False)
|
||||
psbt_list.append(proc["psbt"])
|
||||
|
||||
comb_psig_psbt = self.nodes[0].combinepsbt(psig_psbts)
|
||||
comb_psig_psbt2 = self.nodes[0].combinepsbt(psig_psbts2)
|
||||
|
||||
dec_psbt = self.nodes[0].decodepsbt(comb_psig_psbt)
|
||||
dec_psbt2 = self.nodes[0].decodepsbt(comb_psig_psbt2)
|
||||
assert_equal(len(dec_psbt["inputs"][0]["musig2_partial_sigs"]), expected_partial_sigs)
|
||||
for ps in dec_psbt["inputs"][0]["musig2_partial_sigs"]:
|
||||
pubkey = ps["aggregate_pubkey"][2:]
|
||||
if "pkh" in pattern or "pk_h" in pattern:
|
||||
pubkey = hash160(bytes.fromhex(pubkey)).hex()
|
||||
if pubkey in dec_psbt["inputs"][0]["witness_utxo"]["scriptPubKey"]["hex"]:
|
||||
continue
|
||||
elif "taproot_scripts" in dec_psbt["inputs"][0]:
|
||||
for leaf_scripts in dec_psbt["inputs"][0]["taproot_scripts"]:
|
||||
if pubkey in leaf_scripts["script"]:
|
||||
break
|
||||
else:
|
||||
assert False, "Aggregate pubkey for partial sig not seen as output key or in any scripts"
|
||||
else:
|
||||
assert False, "Aggregate pubkey for partial sig not seen as output key"
|
||||
assert_equal(len(dec_psbt2["inputs"][0]["musig2_partial_sigs"]), expected_partial_sigs)
|
||||
for ps, ps2 in zip(dec_psbt["inputs"][0]["musig2_partial_sigs"], dec_psbt2["inputs"][0]["musig2_partial_sigs"]):
|
||||
self.assert_musig_signer_data(ps, ps2, "partial_sig")
|
||||
self.assert_musig_aggregate_in_script(ps, pattern, dec_psbt["inputs"][0])
|
||||
|
||||
# Non-participant aggregates partial sigs and send
|
||||
finalized = self.nodes[0].finalizepsbt(psbt=comb_psig_psbt, extract=False)
|
||||
assert_equal(finalized["complete"], True)
|
||||
finalized2 = self.nodes[0].finalizepsbt(psbt=comb_psig_psbt2, extract=False)
|
||||
assert_equal(finalized["complete"], finalized2["complete"], True)
|
||||
witness = self.nodes[0].decodepsbt(finalized["psbt"])["inputs"][0]["final_scriptwitness"]
|
||||
assert_not_equal(witness, self.nodes[0].decodepsbt(finalized2["psbt"])["inputs"][0]["final_scriptwitness"])
|
||||
if scriptpath:
|
||||
assert_greater_than(len(witness), 1)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user