1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 07:18:09 +00:00

feat(core): Implement sending to taproot addresses.

This commit is contained in:
Andrew Kozlik 2021-10-14 13:39:47 +02:00 committed by Andrew Kozlik
parent ea0fb08fed
commit 9d03112846
5 changed files with 33 additions and 26 deletions

View File

@ -0,0 +1 @@
Support sending to Taproot addresses.

View File

@ -10,7 +10,7 @@ from apps.common.coininfo import CoinInfo
from .common import ecdsa_hash_pubkey, encode_bech32_address from .common import ecdsa_hash_pubkey, encode_bech32_address
from .multisig import multisig_get_pubkeys, multisig_pubkey_index from .multisig import multisig_get_pubkeys, multisig_pubkey_index
from .scripts import output_script_native_p2wpkh_or_p2wsh, write_output_script_multisig from .scripts import output_script_native_segwit, write_output_script_multisig
if False: if False:
from trezor.crypto import bip32 from trezor.crypto import bip32
@ -109,13 +109,13 @@ def address_p2sh(redeem_script_hash: bytes, coin: CoinInfo) -> str:
def address_p2wpkh_in_p2sh(pubkey: bytes, coin: CoinInfo) -> str: def address_p2wpkh_in_p2sh(pubkey: bytes, coin: CoinInfo) -> str:
pubkey_hash = ecdsa_hash_pubkey(pubkey, coin) pubkey_hash = ecdsa_hash_pubkey(pubkey, coin)
redeem_script = output_script_native_p2wpkh_or_p2wsh(pubkey_hash) redeem_script = output_script_native_segwit(0, pubkey_hash)
redeem_script_hash = coin.script_hash(redeem_script).digest() redeem_script_hash = coin.script_hash(redeem_script).digest()
return address_p2sh(redeem_script_hash, coin) return address_p2sh(redeem_script_hash, coin)
def address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str: def address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str:
redeem_script = output_script_native_p2wpkh_or_p2wsh(witness_script_hash) redeem_script = output_script_native_segwit(0, witness_script_hash)
redeem_script_hash = coin.script_hash(redeem_script).digest() redeem_script_hash = coin.script_hash(redeem_script).digest()
return address_p2sh(redeem_script_hash, coin) return address_p2sh(redeem_script_hash, coin)

View File

@ -7,6 +7,7 @@ from trezor.enums import InputScriptType, OutputScriptType
from trezor.utils import ensure from trezor.utils import ensure
if False: if False:
from typing import Tuple
from apps.common.coininfo import CoinInfo from apps.common.coininfo import CoinInfo
from trezor.messages import TxInput from trezor.messages import TxInput
@ -84,12 +85,12 @@ def encode_bech32_address(prefix: str, witver: int, script: bytes) -> str:
return address return address
def decode_bech32_address(prefix: str, address: str) -> bytes: def decode_bech32_address(prefix: str, address: str) -> Tuple[int, bytes]:
witver, raw = bech32.decode(prefix, address) witver, raw = bech32.decode(prefix, address)
if witver not in _BECH32_WITVERS: if witver not in _BECH32_WITVERS:
raise wire.ProcessError("Invalid address witness program") raise wire.ProcessError("Invalid address witness program")
assert raw is not None assert raw is not None
return bytes(raw) return witver, bytes(raw)
def input_is_segwit(txi: TxInput) -> bool: def input_is_segwit(txi: TxInput) -> bool:

View File

@ -60,9 +60,9 @@ def write_input_script_prefixed(
write_input_script_p2wpkh_in_p2sh( write_input_script_p2wpkh_in_p2sh(
w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True
) )
elif script_type == InputScriptType.SPENDWITNESS: elif script_type in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDTAPROOT):
# native p2wpkh or p2wsh # native p2wpkh or p2wsh or p2tr
script_sig = input_script_native_p2wpkh_or_p2wsh() script_sig = input_script_native_segwit()
write_bytes_prefixed(w, script_sig) write_bytes_prefixed(w, script_sig)
elif script_type == InputScriptType.SPENDMULTISIG: elif script_type == InputScriptType.SPENDMULTISIG:
# p2sh multisig # p2sh multisig
@ -77,9 +77,9 @@ def write_input_script_prefixed(
def output_derive_script(address: str, coin: CoinInfo) -> bytes: def output_derive_script(address: str, coin: CoinInfo) -> bytes:
if coin.bech32_prefix and address.startswith(coin.bech32_prefix): if coin.bech32_prefix and address.startswith(coin.bech32_prefix):
# p2wpkh or p2wsh # p2wpkh or p2wsh or p2tr
witprog = common.decode_bech32_address(coin.bech32_prefix, address) witver, witprog = common.decode_bech32_address(coin.bech32_prefix, address)
return output_script_native_p2wpkh_or_p2wsh(witprog) return output_script_native_segwit(witver, witprog)
if ( if (
not utils.BITCOIN_ONLY not utils.BITCOIN_ONLY
@ -205,34 +205,39 @@ def output_script_p2sh(scripthash: bytes) -> bytearray:
return s return s
# SegWit: Native P2WPKH or P2WSH # SegWit: Native P2WPKH or P2WSH or P2TR
# === # ===
#
# P2WPKH (Pay-to-Witness-Public-Key-Hash) is native SegWit version 0 P2PKH.
# Not backwards compatible.
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh # https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
#
# P2WSH (Pay-to-Witness-Script-Hash) is native SegWit version 0 P2SH.
# Not backwards compatible.
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wsh # https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wsh
# #
# P2WPKH (Pay-to-Witness-Public-Key-Hash) is the segwit native P2PKH. # P2TR (Pay-to-Taproot) is native SegWit version 1.
# Not backwards compatible.
#
# P2WSH (Pay-to-Witness-Script-Hash) is segwit native P2SH.
# Not backwards compatible. # Not backwards compatible.
# https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules
def input_script_native_p2wpkh_or_p2wsh() -> bytearray: def input_script_native_segwit() -> bytearray:
# Completely replaced by the witness and therefore empty. # Completely replaced by the witness and therefore empty.
return bytearray(0) return bytearray(0)
def output_script_native_p2wpkh_or_p2wsh(witprog: bytes) -> bytearray: def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray:
# Either: # Either:
# 00 14 <20-byte-key-hash> # 00 14 <20-byte-key-hash>
# 00 20 <32-byte-script-hash> # 00 20 <32-byte-script-hash>
# 51 20 <32-byte-taproot-output-key>
length = len(witprog) length = len(witprog)
utils.ensure(length == 20 or length == 32) utils.ensure((length == 20 and witver == 0) or length == 32)
w = utils.empty_bytearray(3 + length) w = utils.empty_bytearray(2 + length)
w.append(0x00) # witness version byte w.append(witver + 0x50 if witver else 0) # witness version byte (OP_witver)
w.append(length) # pub key hash length is 20 (P2WPKH) or 32 (P2WSH) bytes w.append(length) # witness program length is 20 (P2WPKH) or 32 (P2WSH, P2TR) bytes
write_bytes_fixed(w, witprog, length) # pub key hash write_bytes_fixed(w, witprog, length)
return w return w

View File

@ -5,7 +5,7 @@ from trezor.crypto.hashlib import sha256
from .common import ecdsa_hash_pubkey from .common import ecdsa_hash_pubkey
from .scripts import ( from .scripts import (
output_script_native_p2wpkh_or_p2wsh, output_script_native_segwit,
output_script_p2pkh, output_script_p2pkh,
output_script_p2sh, output_script_p2sh,
parse_input_script_multisig, parse_input_script_multisig,
@ -40,14 +40,14 @@ class SignatureVerifier:
if len(script_pubkey) == 22: # P2WPKH if len(script_pubkey) == 22: # P2WPKH
public_key, signature, hash_type = parse_witness_p2wpkh(witness) public_key, signature, hash_type = parse_witness_p2wpkh(witness)
pubkey_hash = ecdsa_hash_pubkey(public_key, coin) pubkey_hash = ecdsa_hash_pubkey(public_key, coin)
if output_script_native_p2wpkh_or_p2wsh(pubkey_hash) != script_pubkey: if output_script_native_segwit(0, pubkey_hash) != script_pubkey:
raise wire.DataError("Invalid public key hash") raise wire.DataError("Invalid public key hash")
self.public_keys = [public_key] self.public_keys = [public_key]
self.signatures = [(signature, hash_type)] self.signatures = [(signature, hash_type)]
elif len(script_pubkey) == 34: # P2WSH elif len(script_pubkey) == 34: # P2WSH
script, self.signatures = parse_witness_multisig(witness) script, self.signatures = parse_witness_multisig(witness)
script_hash = sha256(script).digest() script_hash = sha256(script).digest()
if output_script_native_p2wpkh_or_p2wsh(script_hash) != script_pubkey: if output_script_native_segwit(0, script_hash) != script_pubkey:
raise wire.DataError("Invalid script hash") raise wire.DataError("Invalid script hash")
self.public_keys, self.threshold = parse_output_script_multisig(script) self.public_keys, self.threshold = parse_output_script_multisig(script)
else: else: