core/sign_tx: Move script derivation to scripts module.

pull/985/head
Andrew Kozlik 4 years ago committed by Andrew Kozlik
parent d0b80bddc8
commit c2a0f83558

@ -2,9 +2,8 @@ import gc
from micropython import const
from trezor import utils
from trezor.crypto import base58
from trezor.crypto.hashlib import sha256
from trezor.messages import FailureType, InputScriptType, OutputScriptType
from trezor.messages import FailureType, InputScriptType
from trezor.messages.SignTx import SignTx
from trezor.messages.TransactionType import TransactionType
from trezor.messages.TxInputType import TxInputType
@ -14,7 +13,7 @@ from trezor.messages.TxRequest import TxRequest
from trezor.messages.TxRequestDetailsType import TxRequestDetailsType
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
from apps.common import address_type, coininfo, seed
from apps.common import coininfo, seed
from apps.wallet.sign_tx import (
addresses,
helpers,
@ -426,6 +425,7 @@ class Bitcoin:
return amount_out
def check_prevtx_output(self, txo_bin: TxOutputBinType) -> None:
# Validations to perform on the UTXO when checking the previous transaction output amount.
pass
# TX Helpers
@ -462,51 +462,20 @@ class Bitcoin:
# ===
def output_derive_script(self, txo: TxOutputType) -> bytes:
if txo.script_type == OutputScriptType.PAYTOOPRETURN:
return scripts.output_script_paytoopreturn(txo.op_return_data)
if txo.address_n:
# change output
txo.address = self.get_address_for_change(txo)
if self.coin.bech32_prefix and txo.address.startswith(self.coin.bech32_prefix):
# p2wpkh or p2wsh
witprog = addresses.decode_bech32_address(
self.coin.bech32_prefix, txo.address
try:
input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[
txo.script_type
]
except KeyError:
raise SigningError(FailureType.DataError, "Invalid script type")
node = self.keychain.derive(txo.address_n, self.coin.curve_name)
txo.address = addresses.get_address(
input_script_type, self.coin, node, txo.multisig
)
return scripts.output_script_native_p2wpkh_or_p2wsh(witprog)
raw_address = self.get_raw_address(txo)
if address_type.check(self.coin.address_type, raw_address):
# p2pkh
pubkeyhash = address_type.strip(self.coin.address_type, raw_address)
script = scripts.output_script_p2pkh(pubkeyhash)
return script
elif address_type.check(self.coin.address_type_p2sh, raw_address):
# p2sh
scripthash = address_type.strip(self.coin.address_type_p2sh, raw_address)
script = scripts.output_script_p2sh(scripthash)
return script
raise SigningError(FailureType.DataError, "Invalid address type")
def get_raw_address(self, txo: TxOutputType) -> bytes:
try:
return base58.decode_check(txo.address, self.coin.b58_hash)
except ValueError:
raise SigningError(FailureType.DataError, "Invalid address")
def get_address_for_change(self, txo: TxOutputType) -> str:
try:
input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[
txo.script_type
]
except KeyError:
raise SigningError(FailureType.DataError, "Invalid script type")
node = self.keychain.derive(txo.address_n, self.coin.curve_name)
return addresses.get_address(input_script_type, self.coin, node, txo.multisig)
return scripts.output_derive_script(txo, self.coin)
def output_is_change(self, txo: TxOutputType) -> bool:
if txo.script_type not in helpers.CHANGE_OUTPUT_SCRIPT_TYPES:
@ -525,44 +494,9 @@ class Bitcoin:
def input_derive_script(
self, txi: TxInputType, pubkey: bytes, signature: bytes = None
) -> bytes:
if txi.script_type == InputScriptType.SPENDADDRESS:
# p2pkh or p2sh
return scripts.input_script_p2pkh_or_p2sh(
pubkey, signature, self.get_hash_type()
)
if txi.script_type == InputScriptType.SPENDP2SHWITNESS:
# p2wpkh or p2wsh using p2sh
if txi.multisig:
# p2wsh in p2sh
pubkeys = multisig.multisig_get_pubkeys(txi.multisig)
witness_script_hasher = self.create_hash_writer()
scripts.write_output_script_multisig(
witness_script_hasher, pubkeys, txi.multisig.m
)
witness_script_hash = witness_script_hasher.get_digest()
return scripts.input_script_p2wsh_in_p2sh(witness_script_hash)
# p2wpkh in p2sh
return scripts.input_script_p2wpkh_in_p2sh(
addresses.ecdsa_hash_pubkey(pubkey, self.coin)
)
elif txi.script_type == InputScriptType.SPENDWITNESS:
# native p2wpkh or p2wsh
return scripts.input_script_native_p2wpkh_or_p2wsh()
elif txi.script_type == InputScriptType.SPENDMULTISIG:
# p2sh multisig
signature_index = multisig.multisig_pubkey_index(txi.multisig, pubkey)
return scripts.input_script_multisig(
txi.multisig,
signature,
signature_index,
self.get_hash_type(),
self.coin,
)
else:
raise SigningError(FailureType.ProcessError, "Invalid script type")
return scripts.input_derive_script(
txi, self.coin, self.get_hash_type(), pubkey, signature
)
def input_is_segwit(txi: TxInputType) -> bool:

@ -1,12 +1,10 @@
import gc
from micropython import const
from trezor.crypto import cashaddr
from trezor.messages import FailureType, InputScriptType
from trezor.messages.SignTx import SignTx
from trezor.messages.TransactionType import TransactionType
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputType import TxOutputType
from apps.wallet.sign_tx import addresses, helpers, multisig, writers
from apps.wallet.sign_tx.bitcoin import Bitcoin
@ -87,22 +85,6 @@ class Bitcoinlike(Bitcoin):
if not self.coin.negative_fee:
super().on_negative_fee()
def get_raw_address(self, txo: TxOutputType) -> bytes:
if self.coin.cashaddr_prefix is not None and txo.address.startswith(
self.coin.cashaddr_prefix + ":"
):
prefix, addr = txo.address.split(":")
version, data = cashaddr.decode(prefix, addr)
if version == cashaddr.ADDRESS_TYPE_P2KH:
version = self.coin.address_type
elif version == cashaddr.ADDRESS_TYPE_P2SH:
version = self.coin.address_type_p2sh
else:
raise SigningError("Unknown cashaddr address type")
return bytes([version]) + data
else:
return super().get_raw_address(txo)
def get_hash_type(self) -> int:
SIGHASH_FORKID = const(0x40)
hashtype = super().get_hash_type()

@ -1,9 +1,20 @@
from trezor import utils
from trezor.crypto import base58, cashaddr
from trezor.crypto.hashlib import sha256
from trezor.messages import FailureType, InputScriptType, OutputScriptType
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputType import TxOutputType
from apps.common import address_type
from apps.common.coininfo import CoinInfo
from apps.common.writers import empty_bytearray
from apps.wallet.sign_tx.multisig import multisig_get_pubkey_count, multisig_get_pubkeys
from apps.wallet.sign_tx import addresses
from apps.wallet.sign_tx.multisig import (
multisig_get_pubkey_count,
multisig_get_pubkeys,
multisig_pubkey_index,
)
from apps.wallet.sign_tx.writers import (
write_bytes_fixed,
write_bytes_unchecked,
@ -12,7 +23,7 @@ from apps.wallet.sign_tx.writers import (
)
if False:
from typing import List
from typing import List, Optional
from apps.wallet.sign_tx.writers import Writer
@ -20,6 +31,86 @@ class ScriptsError(ValueError):
pass
def input_derive_script(
txi: TxInputType,
coin: CoinInfo,
hash_type: int,
pubkey: bytes,
signature: Optional[bytes],
) -> bytes:
if txi.script_type == InputScriptType.SPENDADDRESS:
# p2pkh or p2sh
return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type)
if txi.script_type == InputScriptType.SPENDP2SHWITNESS:
# p2wpkh or p2wsh using p2sh
if txi.multisig:
# p2wsh in p2sh
pubkeys = multisig_get_pubkeys(txi.multisig)
witness_script_hasher = utils.HashWriter(sha256())
write_output_script_multisig(witness_script_hasher, pubkeys, txi.multisig.m)
witness_script_hash = witness_script_hasher.get_digest()
return input_script_p2wsh_in_p2sh(witness_script_hash)
# p2wpkh in p2sh
return input_script_p2wpkh_in_p2sh(addresses.ecdsa_hash_pubkey(pubkey, coin))
elif txi.script_type == InputScriptType.SPENDWITNESS:
# native p2wpkh or p2wsh
return input_script_native_p2wpkh_or_p2wsh()
elif txi.script_type == InputScriptType.SPENDMULTISIG:
# p2sh multisig
signature_index = multisig_pubkey_index(txi.multisig, pubkey)
return input_script_multisig(
txi.multisig, signature, signature_index, hash_type, coin
)
else:
raise ScriptsError(FailureType.ProcessError, "Invalid script type")
def output_derive_script(txo: TxOutputType, coin: CoinInfo) -> bytes:
if txo.script_type == OutputScriptType.PAYTOOPRETURN:
return output_script_paytoopreturn(txo.op_return_data)
if coin.bech32_prefix and txo.address.startswith(coin.bech32_prefix):
# p2wpkh or p2wsh
witprog = addresses.decode_bech32_address(coin.bech32_prefix, txo.address)
return output_script_native_p2wpkh_or_p2wsh(witprog)
if (
not utils.BITCOIN_ONLY
and coin.cashaddr_prefix is not None
and txo.address.startswith(coin.cashaddr_prefix + ":")
):
prefix, addr = txo.address.split(":")
version, data = cashaddr.decode(prefix, addr)
if version == cashaddr.ADDRESS_TYPE_P2KH:
version = coin.address_type
elif version == cashaddr.ADDRESS_TYPE_P2SH:
version = coin.address_type_p2sh
else:
raise ScriptsError("Unknown cashaddr address type")
raw_address = bytes([version]) + data
else:
try:
raw_address = base58.decode_check(txo.address, coin.b58_hash)
except ValueError:
raise ScriptsError(FailureType.DataError, "Invalid address")
if address_type.check(coin.address_type, raw_address):
# p2pkh
pubkeyhash = address_type.strip(coin.address_type, raw_address)
script = output_script_p2pkh(pubkeyhash)
return script
elif address_type.check(coin.address_type_p2sh, raw_address):
# p2sh
scripthash = address_type.strip(coin.address_type_p2sh, raw_address)
script = output_script_p2sh(scripthash)
return script
raise ScriptsError(FailureType.DataError, "Invalid address type")
# P2PKH, P2SH
# ===
# https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
@ -225,7 +316,7 @@ def input_script_multisig(
w = empty_bytearray(total_length)
if not coin.decred:
if utils.BITCOIN_ONLY or not coin.decred:
# Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
# consumes one additional item on the stack:
# https://bitcoin.org/en/developer-guide#standard-transactions
@ -242,7 +333,7 @@ def input_script_multisig(
return w
def output_script_multisig(pubkeys: List[bytes], m: int, w: Writer = None) -> bytearray:
def output_script_multisig(pubkeys: List[bytes], m: int) -> bytearray:
w = empty_bytearray(output_script_multisig_length(pubkeys, m))
write_output_script_multisig(w, pubkeys, m)
return w

@ -1,6 +1,6 @@
from common import *
from apps.wallet.sign_tx import bitcoin
from apps.wallet.sign_tx.scripts import output_derive_script
from apps.wallet.sign_tx.segwit_bip143 import *
from apps.common import coins
from trezor.messages.SignTx import SignTx
@ -61,17 +61,14 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase):
def test_outputs(self):
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
root = bip32.from_seed(seed, 'secp256k1')
coin = coins.by_name(self.tx.coin_name)
coinsig = bitcoin.Bitcoin()
coinsig.initialize(self.tx, root, coin)
bip143 = Bip143()
for txo in [self.out1, self.out2]:
txo_bin = TxOutputBinType()
txo_bin.amount = txo.amount
txo_bin.script_pubkey = coinsig.output_derive_script(txo)
txo_bin.script_pubkey = output_derive_script(txo, coin)
bip143.add_output(txo_bin)
self.assertEqual(hexlify(bip143.get_outputs_hash(coin)),
@ -80,10 +77,7 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase):
def test_preimage_testdata(self):
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
root = bip32.from_seed(seed, 'secp256k1')
coin = coins.by_name(self.tx.coin_name)
coinsig = bitcoin.Bitcoin()
coinsig.initialize(self.tx, root, coin)
bip143 = Bip143()
bip143.add_prevouts(self.inp1)
@ -94,7 +88,7 @@ class TestSegwitBip143NativeP2WPKH(unittest.TestCase):
for txo in [self.out1, self.out2]:
txo_bin = TxOutputBinType()
txo_bin.amount = txo.amount
txo_bin.script_pubkey = coinsig.output_derive_script(txo)
txo_bin.script_pubkey = output_derive_script(txo, coin)
bip143.add_output(txo_bin)
# test data public key hash

@ -1,6 +1,6 @@
from common import *
from apps.wallet.sign_tx import bitcoin
from apps.wallet.sign_tx.scripts import output_derive_script
from apps.wallet.sign_tx.segwit_bip143 import *
from apps.common import coins
from trezor.messages.SignTx import SignTx
@ -51,17 +51,14 @@ class TestSegwitBip143(unittest.TestCase):
def test_bip143_outputs(self):
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
root = bip32.from_seed(seed, 'secp256k1')
coin = coins.by_name(self.tx.coin_name)
coinsig = bitcoin.Bitcoin()
coinsig.initialize(self.tx, root, coin)
bip143 = Bip143()
for txo in [self.out1, self.out2]:
txo_bin = TxOutputBinType()
txo_bin.amount = txo.amount
txo_bin.script_pubkey = coinsig.output_derive_script(txo)
txo_bin.script_pubkey = output_derive_script(txo, coin)
bip143.add_output(txo_bin)
self.assertEqual(hexlify(bip143.get_outputs_hash(coin)),
@ -70,10 +67,7 @@ class TestSegwitBip143(unittest.TestCase):
def test_bip143_preimage_testdata(self):
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
root = bip32.from_seed(seed, 'secp256k1')
coin = coins.by_name(self.tx.coin_name)
coinsig = bitcoin.Bitcoin()
coinsig.initialize(self.tx, root, coin)
bip143 = Bip143()
bip143.add_prevouts(self.inp1)
@ -81,7 +75,7 @@ class TestSegwitBip143(unittest.TestCase):
for txo in [self.out1, self.out2]:
txo_bin = TxOutputBinType()
txo_bin.amount = txo.amount
txo_bin.script_pubkey = coinsig.output_derive_script(txo)
txo_bin.script_pubkey = output_derive_script(txo, coin)
bip143.add_output(txo_bin)
# test data public key hash

@ -17,7 +17,7 @@ from trezor.messages import OutputScriptType
from apps.common import coins
from apps.common.seed import Keychain
from apps.wallet.sign_tx import helpers, bitcoin
from apps.wallet.sign_tx.common import SigningError
from apps.wallet.sign_tx.scripts import ScriptsError
EMPTY_SERIALIZED = TxRequestSerializedType(serialized_tx=bytearray())
@ -271,7 +271,7 @@ class TestSignSegwitTxNativeP2WPKH(unittest.TestCase):
signer = coinsig.signer(tx, keychain, coin)
for request, response in chunks(messages, 2):
if response is None:
with self.assertRaises(SigningError):
with self.assertRaises(ScriptsError):
signer.send(request)
else:
self.assertEqual(signer.send(request), response)

@ -7,7 +7,7 @@ from trezor.crypto import bip32, bip39
from apps.common import coins
from apps.wallet.sign_tx.tx_weight import *
from apps.wallet.sign_tx import bitcoin
from apps.wallet.sign_tx.scripts import output_derive_script
class TestCalculateTxWeight(unittest.TestCase):
@ -16,11 +16,7 @@ class TestCalculateTxWeight(unittest.TestCase):
def test_p2pkh_txweight(self):
coin = coins.by_name('Bitcoin')
coinsig = bitcoin.Bitcoin()
seed = bip39.seed(' '.join(['all'] * 12), '')
root = bip32.from_seed(seed, 'secp256k1')
coinsig.initialize(SignTx(), root, coin)
inp1 = TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e
# amount=390000,
@ -38,7 +34,7 @@ class TestCalculateTxWeight(unittest.TestCase):
calculator = TxWeightCalculator(1, 1)
calculator.add_input(inp1)
calculator.add_output(coinsig.output_derive_script(out1))
calculator.add_output(output_derive_script(out1, coin))
serialized_tx = '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000'
tx_weight = len(serialized_tx) / 2 * 4 # non-segwit tx's weight is simple length*4
@ -48,11 +44,7 @@ class TestCalculateTxWeight(unittest.TestCase):
def test_p2wpkh_in_p2sh_txweight(self):
coin = coins.by_name('Testnet')
coinsig = bitcoin.Bitcoin()
seed = bip39.seed(' '.join(['all'] * 12), '')
root = bip32.from_seed(seed, 'secp256k1')
coinsig.initialize(SignTx(), root, coin)
inp1 = TxInputType(
# 49'/1'/0'/1/0" - 2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX
@ -81,8 +73,8 @@ class TestCalculateTxWeight(unittest.TestCase):
calculator = TxWeightCalculator(1, 2)
calculator.add_input(inp1)
calculator.add_output(coinsig.output_derive_script(out1))
calculator.add_output(coinsig.output_derive_script(out2))
calculator.add_output(output_derive_script(out1, coin))
calculator.add_output(output_derive_script(out2, coin))
self.assertEqual(calculator.get_total(), 670)
# non-segwit: header, inputs, outputs, locktime 4*(4+65+67+4) = 560
@ -92,11 +84,7 @@ class TestCalculateTxWeight(unittest.TestCase):
def test_native_p2wpkh_txweight(self):
coin = coins.by_name('Testnet')
coinsig = bitcoin.Bitcoin()
seed = bip39.seed(' '.join(['all'] * 12), '')
root = bip32.from_seed(seed, 'secp256k1')
coinsig.initialize(SignTx(), root, coin)
inp1 = TxInputType(
# 49'/1'/0'/0/0" - tb1qqzv60m9ajw8drqulta4ld4gfx0rdh82un5s65s
@ -125,8 +113,8 @@ class TestCalculateTxWeight(unittest.TestCase):
calculator = TxWeightCalculator(1, 2)
calculator.add_input(inp1)
calculator.add_output(coinsig.output_derive_script(out1))
calculator.add_output(coinsig.output_derive_script(out2))
calculator.add_output(output_derive_script(out1, coin))
calculator.add_output(output_derive_script(out2, coin))
self.assertEqual(calculator.get_total(), 566)
# non-segwit: header, inputs, outputs, locktime 4*(4+42+64+4) = 456

Loading…
Cancel
Save