From a17cdb7cfa77a027d7c6fdb9d9e1f48bdc397e74 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Fri, 29 Oct 2021 21:48:49 +0200 Subject: [PATCH] feat(core): Implement Taproot signing. --- core/.changelog.d/1656.added.1 | 1 + core/src/apps/bitcoin/common.py | 8 ++- core/src/apps/bitcoin/sign_tx/bitcoin.py | 77 ++++++++++++++++++------ 3 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 core/.changelog.d/1656.added.1 diff --git a/core/.changelog.d/1656.added.1 b/core/.changelog.d/1656.added.1 new file mode 100644 index 0000000000..aae521d9f1 --- /dev/null +++ b/core/.changelog.d/1656.added.1 @@ -0,0 +1 @@ +Support spending from Taproot UTXOs. diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index a14ca1e258..3c68b68150 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -2,7 +2,7 @@ from micropython import const from trezor import wire from trezor.crypto import bech32, bip32, der -from trezor.crypto.curve import secp256k1 +from trezor.crypto.curve import bip340, secp256k1 from trezor.crypto.hashlib import sha256 from trezor.enums import InputScriptType, OutputScriptType from trezor.utils import HashWriter, ensure @@ -78,6 +78,12 @@ def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes: return sigder +def bip340_sign(node: bip32.HDNode, digest: bytes) -> bytes: + internal_private_key = node.private_key() + output_private_key = bip340.tweak_secret_key(internal_private_key) + return bip340.sign(output_private_key, digest) + + def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes: if pubkey[0] == 0x04: ensure(len(pubkey) == 65) # uncompressed format diff --git a/core/src/apps/bitcoin/sign_tx/bitcoin.py b/core/src/apps/bitcoin/sign_tx/bitcoin.py index bff4a472c0..7074e71923 100644 --- a/core/src/apps/bitcoin/sign_tx/bitcoin.py +++ b/core/src/apps/bitcoin/sign_tx/bitcoin.py @@ -9,7 +9,14 @@ from trezor.utils import HashWriter, empty_bytearray, ensure from apps.common.writers import write_bitcoin_varint from .. import addresses, common, multisig, scripts, writers -from ..common import SIGHASH_ALL, ecdsa_sign, input_is_external, input_is_segwit +from ..common import ( + SIGHASH_ALL, + SIGHASH_ALL_TAPROOT, + bip340_sign, + ecdsa_sign, + input_is_external, + input_is_segwit, +) from ..ownership import verify_nonownership from ..verification import SignatureVerifier from . import approvers, helpers, progress @@ -459,8 +466,13 @@ class Bitcoin: raise wire.ProcessError("Transaction has changed during signing") self.tx_info.check_input(txi) - node = self.keychain.derive(txi.address_n) - key_sign_pub = node.public_key() + if txi.script_type == InputScriptType.SPENDP2SHWITNESS: + node = self.keychain.derive(txi.address_n) + key_sign_pub = node.public_key() + else: + # Native SegWit has an empty scriptSig. Public key is not needed. + key_sign_pub = b"" + self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"") def sign_bip143_input(self, txi: TxInput) -> tuple[bytes, bytes]: @@ -489,29 +501,53 @@ class Bitcoin: return public_key, signature + def sign_taproot_input(self, i: int, txi: TxInput) -> bytes: + self.tx_info.check_input(txi) + sigmsg_digest = self.tx_info.hash143.preimage_hash( + i, + txi, + [], + 1, + self.tx_info.tx, + self.coin, + self.get_sighash_type(txi), + ) + + node = self.keychain.derive(txi.address_n) + return bip340_sign(node, sigmsg_digest) + async def sign_segwit_input(self, i: int) -> None: # STAGE_REQUEST_SEGWIT_WITNESS in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin) if txi.script_type not in common.SEGWIT_INPUT_SCRIPT_TYPES: raise wire.ProcessError("Transaction has changed during signing") - public_key, signature = self.sign_bip143_input(txi) - - self.set_serialized_signature(i, signature) - if txi.multisig: - # find out place of our signature based on the pubkey - signature_index = multisig.multisig_pubkey_index(txi.multisig, public_key) - scripts.write_witness_multisig( - self.serialized_tx, - txi.multisig, - signature, - signature_index, - self.get_hash_type(txi), + if txi.script_type == InputScriptType.SPENDTAPROOT: + signature = self.sign_taproot_input(i, txi) + scripts.write_witness_p2tr( + self.serialized_tx, signature, self.get_hash_type(txi) ) else: - scripts.write_witness_p2wpkh( - self.serialized_tx, signature, public_key, self.get_hash_type(txi) - ) + public_key, signature = self.sign_bip143_input(txi) + + if txi.multisig: + # find out place of our signature based on the pubkey + signature_index = multisig.multisig_pubkey_index( + txi.multisig, public_key + ) + scripts.write_witness_multisig( + self.serialized_tx, + txi.multisig, + signature, + signature_index, + self.get_hash_type(txi), + ) + else: + scripts.write_witness_p2wpkh( + self.serialized_tx, signature, public_key, self.get_hash_type(txi) + ) + + self.set_serialized_signature(i, signature) async def get_legacy_tx_digest( self, @@ -654,7 +690,10 @@ class Bitcoin: # === def get_sighash_type(self, txi: TxInput) -> int: - return SIGHASH_ALL + if common.input_is_taproot(txi): + return SIGHASH_ALL_TAPROOT + else: + return SIGHASH_ALL def get_hash_type(self, txi: TxInput) -> int: """ Return the nHashType flags."""