core: Refactor signing.

pull/985/head
Andrew Kozlik 4 years ago committed by Andrew Kozlik
parent 3e07382a0e
commit ba8b34b2d7

@ -3,7 +3,7 @@ from trezor.messages.RequestType import TXFINISHED
from trezor.messages.TxAck import TxAck
from trezor.messages.TxRequest import TxRequest
from apps.common import paths
from apps.common import coins, paths
from apps.wallet.sign_tx import (
addresses,
helpers,
@ -15,9 +15,17 @@ from apps.wallet.sign_tx import (
signing,
)
if not utils.BITCOIN_ONLY:
from apps.wallet.sign_tx import decred
async def sign_tx(ctx, msg, keychain):
signer = signing.sign_tx(msg, keychain)
coin_name = msg.coin_name if msg.coin_name is not None else "Bitcoin"
coin = coins.by_name(coin_name)
if not utils.BITCOIN_ONLY and coin.decred:
signer = decred.Decred().signer(msg, keychain, coin)
else:
signer = signing.Bitcoin().signer(msg, keychain, coin)
res = None
while True:

@ -1,16 +1,23 @@
import gc
from micropython import const
from trezor.crypto.hashlib import blake256
from trezor.messages import FailureType, InputScriptType
from trezor.messages.SignTx import SignTx
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.messages.TxOutputType import TxOutputType
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
from trezor.utils import HashWriter
from apps.wallet.sign_tx.writers import (
write_tx_input_decred,
write_tx_output,
write_uint32,
write_varint,
from apps.common import coininfo, seed
from apps.wallet.sign_tx import addresses, helpers, multisig, progress, scripts, writers
from apps.wallet.sign_tx.signing import (
Bitcoin,
SigningError,
ecdsa_sign,
input_check_multisig_fingerprint,
input_check_wallet_path,
)
DECRED_SERIALIZE_FULL = const(0 << 16)
@ -24,42 +31,202 @@ class DecredPrefixHasher:
"""
While Decred does not have the exact same implementation as bip143/zip143,
the semantics for using the prefix hash of transactions are close enough
that a pseudo-bip143 class can be used to store the prefix hash during the
check_fee stage of transaction signature to then reuse it at the sign_tx
stage without having to request the inputs again.
that a pseudo-bip143 class can be used.
"""
def __init__(self, tx: SignTx):
self.h_prefix = HashWriter(blake256())
self.last_output_bytes = None
write_uint32(self.h_prefix, tx.version | DECRED_SERIALIZE_NO_WITNESS)
write_varint(self.h_prefix, tx.inputs_count)
writers.write_uint32(self.h_prefix, tx.version | DECRED_SERIALIZE_NO_WITNESS)
writers.write_varint(self.h_prefix, tx.inputs_count)
def add_prevouts(self, txi: TxInputType):
write_tx_input_decred(self.h_prefix, txi)
writers.write_tx_input_decred(self.h_prefix, txi)
def add_sequence(self, txi: TxInputType):
pass
def add_output_count(self, tx: SignTx):
write_varint(self.h_prefix, tx.outputs_count)
writers.write_varint(self.h_prefix, tx.outputs_count)
def add_output(self, txo_bin: TxOutputBinType):
write_tx_output(self.h_prefix, txo_bin)
def set_last_output_bytes(self, w_txo_bin: bytearray):
"""
This is required because the last serialized output obtained in
`check_fee` will only be sent to the client in `sign_tx`
"""
self.last_output_bytes = w_txo_bin
def get_last_output_bytes(self):
return self.last_output_bytes
writers.write_tx_output(self.h_prefix, txo_bin)
def add_locktime_expiry(self, tx: SignTx):
write_uint32(self.h_prefix, tx.lock_time)
write_uint32(self.h_prefix, tx.expiry)
writers.write_uint32(self.h_prefix, tx.lock_time)
writers.write_uint32(self.h_prefix, tx.expiry)
def prefix_hash(self) -> bytes:
return self.h_prefix.get_digest()
class Decred(Bitcoin):
def initialize(self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo):
super().initialize(tx, keychain, coin)
# This is required because the last serialized output obtained in
# `check_fee` will only be sent to the client in `sign_tx`
self.last_output_bytes = None # type: bytearray
def init_hash143(self):
self.hash143 = DecredPrefixHasher(self.tx) # pseudo BIP-0143 prefix hashing
async def phase1(self):
await super().phase1()
self.hash143.add_locktime_expiry(self.tx)
async def phase1_process_input(self, i: int, txi: TxInputType):
await super().phase1_process_input(i, txi)
w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash))
if i == 0: # serializing first input => prepend headers
self.write_tx_header(w_txi)
writers.write_tx_input_decred(w_txi, txi)
self.tx_req.serialized = TxRequestSerializedType(None, None, w_txi)
async def phase1_confirm_output(
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
):
if txo.decred_script_version is not None and txo.decred_script_version != 0:
raise SigningError(
FailureType.ActionCancelled,
"Cannot send to output with script version != 0",
)
txo_bin.decred_script_version = txo.decred_script_version
w_txo_bin = writers.empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey))
if i == 0: # serializing first output => prepend outputs count
writers.write_varint(w_txo_bin, self.tx.outputs_count)
self.hash143.add_output_count(self.tx)
writers.write_tx_output(w_txo_bin, txo_bin)
self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_txo_bin)
self.last_output_bytes = w_txo_bin
await super().phase1_confirm_output(i, txo, txo_bin)
async def phase2(self):
self.tx_req.serialized = None
prefix_hash = self.hash143.prefix_hash()
for i_sign in range(self.tx.inputs_count):
progress.advance()
txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin)
input_check_wallet_path(txi_sign, self.wallet_path)
input_check_multisig_fingerprint(txi_sign, self.multisig_fp)
key_sign = self.keychain.derive(txi_sign.address_n, self.coin.curve_name)
key_sign_pub = key_sign.public_key()
if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
prev_pkscript = scripts.output_script_multisig(
multisig.multisig_get_pubkeys(txi_sign.multisig),
txi_sign.multisig.m,
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
prev_pkscript = scripts.output_script_p2pkh(
addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
else:
raise SigningError("Unsupported input script type")
h_witness = HashWriter(blake256())
writers.write_uint32(
h_witness, self.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING
)
writers.write_varint(h_witness, self.tx.inputs_count)
for ii in range(self.tx.inputs_count):
if ii == i_sign:
writers.write_bytes_prefixed(h_witness, prev_pkscript)
else:
writers.write_varint(h_witness, 0)
witness_hash = writers.get_tx_hash(
h_witness, double=self.coin.sign_hash_double, reverse=False
)
h_sign = HashWriter(blake256())
writers.write_uint32(h_sign, DECRED_SIGHASHALL)
writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE)
writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE)
sig_hash = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double)
signature = ecdsa_sign(key_sign, sig_hash)
# serialize input with correct signature
gc.collect()
txi_sign.script_sig = self.input_derive_script(
txi_sign, key_sign_pub, signature
)
w_txi_sign = writers.empty_bytearray(
8 + 4 + len(self.last_output_bytes)
if i_sign == 0
else 0 + 16 + 4 + len(txi_sign.script_sig)
)
if i_sign == 0:
writers.write_bytes_unchecked(w_txi_sign, self.last_output_bytes)
writers.write_uint32(w_txi_sign, self.tx.lock_time)
writers.write_uint32(w_txi_sign, self.tx.expiry)
writers.write_varint(w_txi_sign, self.tx.inputs_count)
writers.write_tx_input_decred_witness(w_txi_sign, txi_sign)
self.tx_req.serialized = TxRequestSerializedType(
i_sign, signature, w_txi_sign
)
return await helpers.request_tx_finish(self.tx_req)
async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
total_out = 0 # sum of output amounts
# STAGE_REQUEST_2_PREV_META
tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)
if tx.outputs_cnt <= prev_index:
raise SigningError(
FailureType.ProcessError, "Not enough outputs in previous transaction."
)
txh = HashWriter(blake256())
writers.write_uint32(txh, tx.version | DECRED_SERIALIZE_NO_WITNESS)
writers.write_varint(txh, tx.inputs_cnt)
for i in range(tx.inputs_cnt):
# STAGE_REQUEST_2_PREV_INPUT
txi = await helpers.request_tx_input(self.tx_req, i, self.coin, prev_hash)
writers.write_tx_input_decred(txh, txi)
writers.write_varint(txh, tx.outputs_cnt)
for o in range(tx.outputs_cnt):
# STAGE_REQUEST_2_PREV_OUTPUT
txo_bin = await helpers.request_tx_output(
self.tx_req, o, self.coin, prev_hash
)
writers.write_tx_output(txh, txo_bin)
if o == prev_index:
total_out += txo_bin.amount
if (
txo_bin.decred_script_version is not None
and txo_bin.decred_script_version != 0
):
raise SigningError(
FailureType.ProcessError,
"Cannot use utxo that has script_version != 0",
)
writers.write_uint32(txh, tx.lock_time)
writers.write_uint32(txh, tx.expiry)
if (
writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True)
!= prev_hash
):
raise SigningError(
FailureType.ProcessError, "Encountered invalid prev_hash"
)
return total_out

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save