mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-10 07:20:56 +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 apps.common import address_type
|
||||||
|
|
||||||
from .common import ecdsa_hash_pubkey, encode_bech32_address
|
from .common import (
|
||||||
from .scripts import output_script_native_segwit, write_output_script_multisig
|
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:
|
if TYPE_CHECKING:
|
||||||
from trezor.crypto import bip32
|
from trezor.crypto import bip32
|
||||||
@ -69,7 +77,11 @@ def get_address(
|
|||||||
raise ProcessError("Taproot not enabled on this coin")
|
raise ProcessError("Taproot not enabled on this coin")
|
||||||
|
|
||||||
if multisig is not None:
|
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)
|
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)
|
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:
|
def address_pkh(pubkey: bytes, coin: CoinInfo) -> str:
|
||||||
s = address_type.tobytes(coin.address_type) + coin.script_hash(pubkey).digest()
|
s = address_type.tobytes(coin.address_type) + coin.script_hash(pubkey).digest()
|
||||||
return base58.encode_check(bytes(s), coin.b58_hash)
|
return base58.encode_check(bytes(s), coin.b58_hash)
|
||||||
|
@ -60,6 +60,9 @@ BIP32_WALLET_DEPTH = const(2)
|
|||||||
# Bitcoin opcodes
|
# Bitcoin opcodes
|
||||||
OP_0 = const(0x00)
|
OP_0 = const(0x00)
|
||||||
OP_1 = const(0x51)
|
OP_1 = const(0x51)
|
||||||
|
OP_CHECKSIG = const(0xAC)
|
||||||
|
OP_CHECKSIGADD = const(0xBA)
|
||||||
|
OP_NUMEQUAL = const(0x9C)
|
||||||
|
|
||||||
# supported witness versions for bech32 addresses
|
# supported witness versions for bech32 addresses
|
||||||
_BECH32_WITVERS = (0, 1)
|
_BECH32_WITVERS = (0, 1)
|
||||||
@ -103,6 +106,8 @@ NONSEGWIT_INPUT_SCRIPT_TYPES = (
|
|||||||
InputScriptType.SPENDMULTISIG,
|
InputScriptType.SPENDMULTISIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LEAF_VERSION = const(0xC0)
|
||||||
|
|
||||||
|
|
||||||
def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
|
def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
|
||||||
from trezor.crypto import der
|
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)
|
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:
|
def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes:
|
||||||
from trezor.utils import ensure
|
from trezor.utils import ensure
|
||||||
|
|
||||||
|
@ -85,6 +85,27 @@ def multisig_get_pubkey(n: HDNodeType, p: paths.Bip32Path) -> bytes:
|
|||||||
return node.public_key()
|
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]:
|
def multisig_get_pubkeys(multisig: MultisigRedeemScriptType) -> list[bytes]:
|
||||||
validate_multisig(multisig)
|
validate_multisig(multisig)
|
||||||
if multisig.nodes:
|
if multisig.nodes:
|
||||||
|
@ -9,8 +9,16 @@ from apps.common.readers import read_compact_size
|
|||||||
from apps.common.writers import write_compact_size
|
from apps.common.writers import write_compact_size
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
from .common import SigHashType
|
from .common import (
|
||||||
from .multisig import multisig_get_pubkeys, multisig_pubkey_index
|
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 .readers import read_memoryview_prefixed, read_op_push
|
||||||
from .writers import (
|
from .writers import (
|
||||||
write_bytes_fixed,
|
write_bytes_fixed,
|
||||||
@ -591,6 +599,40 @@ def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]:
|
|||||||
return public_keys, threshold
|
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
|
# OP_RETURN
|
||||||
# ===
|
# ===
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user