From d06623218f75e87a1f27a904cf3d476e5be57a37 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Tue, 17 Sep 2024 10:52:52 -0400 Subject: [PATCH] multisig: compute dummy chaincode based on pubkeys Compute a dummy xpub to use as the internal pubkey for taproot multisig. Sort and remove duplicates of all xpub pubkeys, concatenate and compute sha256 hash. The result is the chaincode, and the pubkey is the NUMS point. --- core/src/apps/bitcoin/multisig.py | 43 +++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/core/src/apps/bitcoin/multisig.py b/core/src/apps/bitcoin/multisig.py index 563dbf9ec3..6c8c6f5551 100644 --- a/core/src/apps/bitcoin/multisig.py +++ b/core/src/apps/bitcoin/multisig.py @@ -59,14 +59,10 @@ def validate_multisig(multisig: MultisigRedeemScriptType) -> None: def multisig_pubkey_index(multisig: MultisigRedeemScriptType, pubkey: bytes) -> int: validate_multisig(multisig) - if multisig.nodes: - for i, hd_node in enumerate(multisig.nodes): - if multisig_get_pubkey(hd_node, multisig.address_n) == pubkey: - return i - else: - for i, hd in enumerate(multisig.pubkeys): - if multisig_get_pubkey(hd.node, hd.address_n) == pubkey: - return i + pubkeys = multisig_get_pubkeys(multisig) + for i, derived_pubkey in enumerate(pubkeys): + if derived_pubkey == pubkey: + return i raise DataError("Pubkey not found in multisig script") @@ -85,19 +81,44 @@ def multisig_get_pubkey(n: HDNodeType, p: paths.Bip32Path) -> bytes: return node.public_key() +def compute_taproot_dummy_chaincode(multisig: MultisigRedeemScriptType) -> bytes: + from trezor.crypto.hashlib import sha256 + from trezor.utils import HashWriter + + from .writers import write_bytes_fixed + + if len(multisig.address_n) != 2: + raise DataError("Taproot multisig must use xpub derivation depth of 2") + + if multisig.nodes: + pubkeys = [hd.public_key for hd in multisig.nodes] + else: + pubkeys = [hd.public_key for hd in multisig.pubkeys] + pubkeys.sort() + h = HashWriter(sha256()) + prev = None + for pubkey in pubkeys: + if prev == pubkey: + continue + prev = pubkey + write_bytes_fixed(h, pubkey, 33) + + return h.get_digest() + + def multisig_get_dummy_pubkey(multisig: MultisigRedeemScriptType) -> bytes: from trezor.crypto import bip32 # The following encodes this xpub into an HDNode. It is the NUMS point suggested - # in BIP341, with a chaincode of 32 0 bytes. Deriving a pubkey from this node - # results in a provably unspendable pubkey. + # in BIP341, with a chaincode derived from the sha256 of the sorted public keys with duplicates removed. + # Deriving a pubkey from this node results in a provably unspendable pubkey. # https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304 # xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6QgnecKFpJFPpdzxKrwoaZoV44qAJewsc4kX9vGaCaBExuvJH57 node = bip32.HDNode( depth=0, fingerprint=2084970077, child_num=0, - chain_code=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + chain_code=compute_taproot_dummy_chaincode(multisig), public_key=b"\x02P\x92\x9bt\xc1\xa0IT\xb7\x8bK`5\xe9z^\x07\x8aZ\x0f(\xec\x96\xd5G\xbf\xee\x9a\xce\x80:\xc0", )