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:
parent
2ffb0d374c
commit
ca128b1356
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
# ===
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user