1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-09 15:00:58 +00:00

feat(BTC): add taproot multisig address generation

This commit is contained in:
Andrew Toth 2024-09-05 13:48:41 -04:00
parent 2ffb0d374c
commit ca128b1356
No known key found for this signature in database
GPG Key ID: 60007AFC8938B018
4 changed files with 111 additions and 5 deletions

View File

@ -7,8 +7,16 @@ from trezor.wire import ProcessError
from apps.common import address_type
from .common import ecdsa_hash_pubkey, encode_bech32_address
from .scripts import output_script_native_segwit, write_output_script_multisig
from .common import (
ecdsa_hash_pubkey,
encode_bech32_address,
p2tr_multisig_tweaked_pubkey,
)
from .multisig import multisig_get_dummy_pubkey
from .scripts import (
output_script_native_segwit,
write_output_script_multisig,
)
if TYPE_CHECKING:
from trezor.crypto import bip32
@ -69,7 +77,11 @@ def get_address(
raise ProcessError("Taproot not enabled on this coin")
if multisig is not None:
raise ProcessError("Multisig not supported for taproot")
pubkeys = multisig_get_pubkeys(multisig)
dummy_pubkey = multisig_get_dummy_pubkey(multisig)
return _address_multisig_p2tr(
pubkeys, dummy_pubkey, multisig.m, coin.bech32_prefix
)
return _address_p2tr(node_public_key, coin)
@ -116,6 +128,13 @@ def _address_multisig_p2wsh(pubkeys: list[bytes], m: int, hrp: str) -> str:
return _address_p2wsh(witness_script_h.get_digest(), hrp)
def _address_multisig_p2tr(
pubkeys: list[bytes], dummy_pubkey: bytes, m: int, hrp: str
) -> str:
_, output_pubkey = p2tr_multisig_tweaked_pubkey(pubkeys, dummy_pubkey, m)
return encode_bech32_address(hrp, 1, output_pubkey)
def address_pkh(pubkey: bytes, coin: CoinInfo) -> str:
s = address_type.tobytes(coin.address_type) + coin.script_hash(pubkey).digest()
return base58.encode_check(bytes(s), coin.b58_hash)

View File

@ -60,6 +60,9 @@ BIP32_WALLET_DEPTH = const(2)
# Bitcoin opcodes
OP_0 = const(0x00)
OP_1 = const(0x51)
OP_CHECKSIG = const(0xAC)
OP_CHECKSIGADD = const(0xBA)
OP_NUMEQUAL = const(0x9C)
# supported witness versions for bech32 addresses
_BECH32_WITVERS = (0, 1)
@ -103,6 +106,8 @@ NONSEGWIT_INPUT_SCRIPT_TYPES = (
InputScriptType.SPENDMULTISIG,
)
LEAF_VERSION = const(0xC0)
def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
from trezor.crypto import der
@ -119,6 +124,25 @@ def bip340_sign(node: bip32.HDNode, digest: bytes) -> bytes:
return bip340.sign(output_private_key, digest)
def p2tr_multisig_leaf_hash(pubkeys: list[bytes], m: int) -> bytes:
from .scripts import write_output_script_multisig_taproot
hash_writer = tagged_hashwriter(b"TapLeaf")
hash_writer.append(LEAF_VERSION)
write_output_script_multisig_taproot(hash_writer, pubkeys, m)
return hash_writer.get_digest()
def p2tr_multisig_tweaked_pubkey(
pubkeys: list[bytes], internal_pubkey: bytes, m: int
) -> tuple[int, bytes]:
leaf_hash = p2tr_multisig_leaf_hash(pubkeys, m)
return bip340.tweak_public_key(internal_pubkey[1:], leaf_hash)
def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes:
from trezor.utils import ensure

View File

@ -85,6 +85,27 @@ def multisig_get_pubkey(n: HDNodeType, p: paths.Bip32Path) -> bytes:
return node.public_key()
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.
# 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",
public_key=b"\x02P\x92\x9bt\xc1\xa0IT\xb7\x8bK`5\xe9z^\x07\x8aZ\x0f(\xec\x96\xd5G\xbf\xee\x9a\xce\x80:\xc0",
)
for i in multisig.address_n:
node.derive(i, True)
return node.public_key()
def multisig_get_pubkeys(multisig: MultisigRedeemScriptType) -> list[bytes]:
validate_multisig(multisig)
if multisig.nodes:

View File

@ -9,8 +9,16 @@ from apps.common.readers import read_compact_size
from apps.common.writers import write_compact_size
from . import common
from .common import SigHashType
from .multisig import multisig_get_pubkeys, multisig_pubkey_index
from .common import (
SigHashType,
OP_CHECKSIG,
OP_CHECKSIGADD,
OP_NUMEQUAL,
)
from .multisig import (
multisig_get_pubkeys,
multisig_pubkey_index,
)
from .readers import read_memoryview_prefixed, read_op_push
from .writers import (
write_bytes_fixed,
@ -591,6 +599,40 @@ def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]:
return public_keys, threshold
# Taproot Multisig
# ===
def write_output_script_multisig_taproot(
w: Writer,
pubkeys: Sequence[bytes | memoryview],
m: int,
) -> None:
n = len(pubkeys)
if n < 1 or n > 15 or m < 1 or m > 15 or m > n:
raise DataError("Invalid multisig parameters")
for pubkey in pubkeys:
if len(pubkey) != 33:
raise DataError("Invalid multisig parameters")
write_compact_size(w, output_script_multisig_taproot_length(pubkeys))
iterator = iter(pubkeys)
append_pubkey(w, next(iterator)[1:])
w.append(OP_CHECKSIG)
for p in iterator:
append_pubkey(w, p[1:])
w.append(OP_CHECKSIGADD)
w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value
w.append(OP_NUMEQUAL)
def output_script_multisig_taproot_length(pubkeys: Sequence[bytes | memoryview]) -> int:
return (
len(pubkeys) * (1 + 32 + 1) + 1 + 1
) # see write_output_script_multisig_taproot
# OP_RETURN
# ===