feat(core): Implement Taproot signing.

pull/1918/head
Andrew Kozlik 3 years ago committed by Andrew Kozlik
parent 381e8bc85a
commit a17cdb7cfa

@ -0,0 +1 @@
Support spending from Taproot UTXOs.

@ -2,7 +2,7 @@ from micropython import const
from trezor import wire from trezor import wire
from trezor.crypto import bech32, bip32, der 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.crypto.hashlib import sha256
from trezor.enums import InputScriptType, OutputScriptType from trezor.enums import InputScriptType, OutputScriptType
from trezor.utils import HashWriter, ensure from trezor.utils import HashWriter, ensure
@ -78,6 +78,12 @@ def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
return sigder 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: def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes:
if pubkey[0] == 0x04: if pubkey[0] == 0x04:
ensure(len(pubkey) == 65) # uncompressed format ensure(len(pubkey) == 65) # uncompressed format

@ -9,7 +9,14 @@ from trezor.utils import HashWriter, empty_bytearray, ensure
from apps.common.writers import write_bitcoin_varint from apps.common.writers import write_bitcoin_varint
from .. import addresses, common, multisig, scripts, writers 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 ..ownership import verify_nonownership
from ..verification import SignatureVerifier from ..verification import SignatureVerifier
from . import approvers, helpers, progress from . import approvers, helpers, progress
@ -459,8 +466,13 @@ class Bitcoin:
raise wire.ProcessError("Transaction has changed during signing") raise wire.ProcessError("Transaction has changed during signing")
self.tx_info.check_input(txi) self.tx_info.check_input(txi)
node = self.keychain.derive(txi.address_n) if txi.script_type == InputScriptType.SPENDP2SHWITNESS:
key_sign_pub = node.public_key() 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"") self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"")
def sign_bip143_input(self, txi: TxInput) -> tuple[bytes, bytes]: def sign_bip143_input(self, txi: TxInput) -> tuple[bytes, bytes]:
@ -489,29 +501,53 @@ class Bitcoin:
return public_key, signature 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: async def sign_segwit_input(self, i: int) -> None:
# STAGE_REQUEST_SEGWIT_WITNESS in legacy # STAGE_REQUEST_SEGWIT_WITNESS in legacy
txi = await helpers.request_tx_input(self.tx_req, i, self.coin) txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
if txi.script_type not in common.SEGWIT_INPUT_SCRIPT_TYPES: if txi.script_type not in common.SEGWIT_INPUT_SCRIPT_TYPES:
raise wire.ProcessError("Transaction has changed during signing") raise wire.ProcessError("Transaction has changed during signing")
public_key, signature = self.sign_bip143_input(txi) if txi.script_type == InputScriptType.SPENDTAPROOT:
signature = self.sign_taproot_input(i, txi)
self.set_serialized_signature(i, signature) scripts.write_witness_p2tr(
if txi.multisig: self.serialized_tx, signature, self.get_hash_type(txi)
# 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: else:
scripts.write_witness_p2wpkh( public_key, signature = self.sign_bip143_input(txi)
self.serialized_tx, signature, public_key, self.get_hash_type(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( async def get_legacy_tx_digest(
self, self,
@ -654,7 +690,10 @@ class Bitcoin:
# === # ===
def get_sighash_type(self, txi: TxInput) -> int: 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: def get_hash_type(self, txi: TxInput) -> int:
""" Return the nHashType flags.""" """ Return the nHashType flags."""

Loading…
Cancel
Save