diff --git a/core/.changelog.d/1656.added b/core/.changelog.d/1656.added new file mode 100644 index 0000000000..08d61672ec --- /dev/null +++ b/core/.changelog.d/1656.added @@ -0,0 +1 @@ +Support sending to Taproot addresses. diff --git a/core/src/apps/bitcoin/addresses.py b/core/src/apps/bitcoin/addresses.py index b3f09f719c..9c904fc25e 100644 --- a/core/src/apps/bitcoin/addresses.py +++ b/core/src/apps/bitcoin/addresses.py @@ -10,7 +10,7 @@ from apps.common.coininfo import CoinInfo from .common import ecdsa_hash_pubkey, encode_bech32_address 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: 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: 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() return address_p2sh(redeem_script_hash, coin) 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() return address_p2sh(redeem_script_hash, coin) diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index d0a934e0dc..2189c70e28 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -7,6 +7,7 @@ from trezor.enums import InputScriptType, OutputScriptType from trezor.utils import ensure if False: + from typing import Tuple from apps.common.coininfo import CoinInfo from trezor.messages import TxInput @@ -84,12 +85,12 @@ def encode_bech32_address(prefix: str, witver: int, script: bytes) -> str: 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) if witver not in _BECH32_WITVERS: raise wire.ProcessError("Invalid address witness program") assert raw is not None - return bytes(raw) + return witver, bytes(raw) def input_is_segwit(txi: TxInput) -> bool: diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index 46771babe3..a37d4360aa 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -60,9 +60,9 @@ def write_input_script_prefixed( write_input_script_p2wpkh_in_p2sh( w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True ) - elif script_type == InputScriptType.SPENDWITNESS: - # native p2wpkh or p2wsh - script_sig = input_script_native_p2wpkh_or_p2wsh() + elif script_type in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDTAPROOT): + # native p2wpkh or p2wsh or p2tr + script_sig = input_script_native_segwit() write_bytes_prefixed(w, script_sig) elif script_type == InputScriptType.SPENDMULTISIG: # p2sh multisig @@ -77,9 +77,9 @@ def write_input_script_prefixed( def output_derive_script(address: str, coin: CoinInfo) -> bytes: if coin.bech32_prefix and address.startswith(coin.bech32_prefix): - # p2wpkh or p2wsh - witprog = common.decode_bech32_address(coin.bech32_prefix, address) - return output_script_native_p2wpkh_or_p2wsh(witprog) + # p2wpkh or p2wsh or p2tr + witver, witprog = common.decode_bech32_address(coin.bech32_prefix, address) + return output_script_native_segwit(witver, witprog) if ( not utils.BITCOIN_ONLY @@ -205,34 +205,39 @@ def output_script_p2sh(scripthash: bytes) -> bytearray: 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 +# +# 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 # -# P2WPKH (Pay-to-Witness-Public-Key-Hash) is the segwit native P2PKH. -# Not backwards compatible. -# -# P2WSH (Pay-to-Witness-Script-Hash) is segwit native P2SH. +# P2TR (Pay-to-Taproot) is native SegWit version 1. # 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. return bytearray(0) -def output_script_native_p2wpkh_or_p2wsh(witprog: bytes) -> bytearray: +def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray: # Either: # 00 14 <20-byte-key-hash> # 00 20 <32-byte-script-hash> + # 51 20 <32-byte-taproot-output-key> 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.append(0x00) # witness version byte - w.append(length) # pub key hash length is 20 (P2WPKH) or 32 (P2WSH) bytes - write_bytes_fixed(w, witprog, length) # pub key hash + w = utils.empty_bytearray(2 + length) + w.append(witver + 0x50 if witver else 0) # witness version byte (OP_witver) + w.append(length) # witness program length is 20 (P2WPKH) or 32 (P2WSH, P2TR) bytes + write_bytes_fixed(w, witprog, length) return w diff --git a/core/src/apps/bitcoin/verification.py b/core/src/apps/bitcoin/verification.py index cf52ccd168..b86b6780f4 100644 --- a/core/src/apps/bitcoin/verification.py +++ b/core/src/apps/bitcoin/verification.py @@ -5,7 +5,7 @@ from trezor.crypto.hashlib import sha256 from .common import ecdsa_hash_pubkey from .scripts import ( - output_script_native_p2wpkh_or_p2wsh, + output_script_native_segwit, output_script_p2pkh, output_script_p2sh, parse_input_script_multisig, @@ -40,14 +40,14 @@ class SignatureVerifier: if len(script_pubkey) == 22: # P2WPKH public_key, signature, hash_type = parse_witness_p2wpkh(witness) 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") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 34: # P2WSH script, self.signatures = parse_witness_multisig(witness) 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") self.public_keys, self.threshold = parse_output_script_multisig(script) else: