From 7834d06aac60bd37af9981a4f5b914ba1555b455 Mon Sep 17 00:00:00 2001 From: Matheus Degiovani Date: Thu, 20 Sep 2018 17:01:26 -0300 Subject: [PATCH] src/apps/wallet/sign_tx: add support for Decred --- src/apps/common/coininfo.py | 5 +- src/apps/common/coininfo.py.mako | 5 +- src/apps/common/writers.py | 7 ++ src/apps/wallet/sign_tx/signing.py | 145 ++++++++++++++++++++++++++++- src/apps/wallet/sign_tx/writers.py | 20 ++++ src/trezor/crypto/base58.py | 6 ++ 6 files changed, 181 insertions(+), 7 deletions(-) diff --git a/src/apps/common/coininfo.py b/src/apps/common/coininfo.py index 5bfad8e4e..a60819337 100644 --- a/src/apps/common/coininfo.py +++ b/src/apps/common/coininfo.py @@ -1,6 +1,6 @@ # generated from coininfo.py.mako # do not edit manually! -from trezor.crypto.base58 import groestl512d_32, sha256d_32 +from trezor.crypto.base58 import blake256_32, groestl512d_32, sha256d_32 class CoinInfo: @@ -48,6 +48,9 @@ class CoinInfo: if curve_name == "secp256k1-groestl": self.b58_hash = groestl512d_32 self.sign_hash_double = False + elif curve_name == "secp256k1-decred": + self.b58_hash = blake256_32 + self.sign_hash_double = False else: self.b58_hash = sha256d_32 self.sign_hash_double = True diff --git a/src/apps/common/coininfo.py.mako b/src/apps/common/coininfo.py.mako index ca15c932b..75f4e3276 100644 --- a/src/apps/common/coininfo.py.mako +++ b/src/apps/common/coininfo.py.mako @@ -1,6 +1,6 @@ # generated from coininfo.py.mako # do not edit manually! -from trezor.crypto.base58 import groestl512d_32, sha256d_32 +from trezor.crypto.base58 import blake256_32, groestl512d_32, sha256d_32 class CoinInfo: @@ -48,6 +48,9 @@ class CoinInfo: if curve_name == "secp256k1-groestl": self.b58_hash = groestl512d_32 self.sign_hash_double = False + elif curve_name == "secp256k1-decred": + self.b58_hash = blake256_32 + self.sign_hash_double = False else: self.b58_hash = sha256d_32 self.sign_hash_double = True diff --git a/src/apps/common/writers.py b/src/apps/common/writers.py index 8d7215ec3..868e5e4ac 100644 --- a/src/apps/common/writers.py +++ b/src/apps/common/writers.py @@ -14,6 +14,13 @@ def write_uint8(w: bytearray, n: int) -> int: return 1 +def write_uint16_le(w: bytearray, n: int) -> int: + assert 0 <= n <= 0xFFFF + w.append(n & 0xFF) + w.append((n >> 8) & 0xFF) + return 2 + + def write_uint32_le(w: bytearray, n: int) -> int: assert 0 <= n <= 0xFFFFFFFF w.append(n & 0xFF) diff --git a/src/apps/wallet/sign_tx/signing.py b/src/apps/wallet/sign_tx/signing.py index dda4bc58e..cf3abaf09 100644 --- a/src/apps/wallet/sign_tx/signing.py +++ b/src/apps/wallet/sign_tx/signing.py @@ -2,7 +2,7 @@ from micropython import const from trezor.crypto import base58, bip32, cashaddr, der from trezor.crypto.curve import secp256k1 -from trezor.crypto.hashlib import sha256 +from trezor.crypto.hashlib import blake256, sha256 from trezor.messages import OutputScriptType from trezor.messages.TxRequestDetailsType import TxRequestDetailsType from trezor.messages.TxRequestSerializedType import TxRequestSerializedType @@ -143,6 +143,14 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode): elif not await confirm_output(txo, coin): raise SigningError(FailureType.ActionCancelled, "Output cancelled") + if coin.decred: + if 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 + write_tx_output(h_first, txo_bin) hash143.add_output(txo_bin) total_out += txo_bin.amount @@ -183,6 +191,20 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode): tx_req.details = TxRequestDetailsType() tx_req.serialized = None + h_prefix_sign = None + if coin.decred: + h_prefix_sign = HashWriter(blake256) + + # used to validate no changes between check_tx_fee and phase 2 + h_second = HashWriter(sha256) + + # used to validate no changes between phase 2 and decred witness + h_third = HashWriter(sha256) + h_fourth = HashWriter(sha256) + write_uint16(h_prefix_sign, tx.version) + write_uint16(h_prefix_sign, 1) # serType + write_varint(h_prefix_sign, tx.inputs_count) + for i_sign in range(tx.inputs_count): progress.advance() txi_sign = None @@ -259,6 +281,22 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode): tx_req.serialized = tx_ser + elif coin.decred: + txi_sign = await request_tx_input(tx_req, i_sign) + + input_check_wallet_path(txi_sign, wallet_path) + + w_txi = empty_bytearray(7 + len(txi_sign.prev_hash) + 9) + if i_sign == 0: # serializing first input => prepend headers + write_bytes(w_txi, get_tx_header(coin, tx, False)) + + write_tx_input_decred(w_txi, txi_sign) + write_tx_input_decred(h_prefix_sign, txi_sign) + tx_ser.serialized_tx = w_txi + tx_req.serialized = tx_ser + write_tx_input_check(h_second, txi_sign) + write_tx_input_check(h_third, txi_sign) + else: # hash of what we are signing with this input h_sign = HashWriter(sha256) @@ -360,13 +398,19 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode): # STAGE_REQUEST_5_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount + txo_bin.decred_script_version = txo.decred_script_version txo_bin.script_pubkey = output_derive_script(txo, coin, root) # serialize output w_txo_bin = empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) + if coin.decred: + write_varint(h_prefix_sign, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) + if coin.decred: + write_tx_output(h_prefix_sign, txo_bin) + write_tx_output(h_second, txo_bin) tx_ser.signature_index = None tx_ser.signature = None @@ -376,6 +420,18 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode): any_segwit = True in segwit.values() + prefix_hash = None + if coin.decred: + if get_tx_hash(h_first, False) != get_tx_hash(h_second): + raise SigningError( + FailureType.ProcessError, "Transaction has changed during signing" + ) + write_uint32(h_prefix_sign, tx.lock_time) + write_uint32(h_prefix_sign, tx.expiry) + prefix_hash = get_tx_hash( + h_prefix_sign, double=coin.sign_hash_double, reverse=False + ) + for i in range(tx.inputs_count): progress.advance() if segwit[i]: @@ -416,10 +472,75 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode): tx_ser.serialized_tx = bytearray(1) # empty witness for non-segwit inputs tx_ser.signature_index = None tx_ser.signature = None + elif coin.decred: + txi = await request_tx_input(tx_req, i) + input_check_wallet_path(txi, wallet_path) + + write_tx_input_check(h_fourth, txi) + + if txi.amount > authorized_in: + raise SigningError( + FailureType.ProcessError, "Transaction has changed during signing" + ) + authorized_in -= txi.amount + + key_sign = node_derive(root, txi.address_n) + key_sign_pub = key_sign.public_key() + prev_txo = TxOutputType( + address_n=txi.address_n, script_type=OutputScriptType.PAYTOADDRESS + ) + prev_pkscript = output_derive_script(prev_txo, coin, root) + + h_witness = HashWriter(blake256) + write_uint16(h_witness, tx.version) + write_uint16(h_witness, 3) # serType serializeWitness + write_varint(h_witness, tx.inputs_count) + + for ii in range(tx.inputs_count): + if ii == i: + write_varint(h_witness, len(prev_pkscript)) + write_bytes(h_witness, prev_pkscript) + else: + write_varint(h_witness, 0) + + witness_hash = get_tx_hash( + h_witness, double=coin.sign_hash_double, reverse=False + ) + + h_sign = HashWriter(blake256) + write_uint32(h_sign, 1) # SIGHASHALL + write_bytes(h_sign, prefix_hash) + write_bytes(h_sign, witness_hash) + + sig_hash = get_tx_hash(h_sign, double=coin.sign_hash_double) + signature = ecdsa_sign(key_sign, sig_hash) + tx_ser.signature_index = i_sign + tx_ser.signature = signature + + # serialize input with correct signature + txi.script_sig = input_derive_script(coin, txi, key_sign_pub, signature) + w_txi_sign = empty_bytearray( + 10 + len(txi.prev_hash) + 18 + len(txi.script_sig) + ) + + if i == 0: + write_uint32(w_txi_sign, tx.lock_time) + write_uint32(w_txi_sign, tx.expiry) + write_varint(w_txi_sign, tx.inputs_count) + + write_tx_input_decred_witness(w_txi_sign, txi) + tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser - write_uint32(tx_ser.serialized_tx, tx.lock_time) + if coin.decred: + if get_tx_hash(h_third, False) != get_tx_hash(h_fourth): + raise SigningError( + FailureType.ProcessError, "Transaction has changed during signing" + ) + else: + write_uint32(tx_ser.serialized_tx, tx.lock_time) + if tx.overwintered: write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight write_varint(tx_ser.serialized_tx, 0) # nJoinSplit @@ -435,11 +556,17 @@ async def get_prevtx_output_value( # STAGE_REQUEST_2_PREV_META tx = await request_tx_meta(tx_req, prev_hash) - txh = HashWriter(sha256) + if coin.decred: + txh = HashWriter(blake256) + else: + txh = HashWriter(sha256) if tx.overwintered: write_uint32(txh, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(txh, coin.version_group_id) # nVersionGroupId + elif coin.decred: + write_uint16(txh, tx.version) + write_uint16(txh, 1) # serType else: write_uint32(txh, tx.version) # nVersion @@ -448,7 +575,10 @@ async def get_prevtx_output_value( for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await request_tx_input(tx_req, i, prev_hash) - write_tx_input(txh, txi) + if coin.decred: + write_tx_input_decred(txh, txi) + else: + write_tx_input(txh, txi) write_varint(txh, tx.outputs_cnt) @@ -458,10 +588,15 @@ async def get_prevtx_output_value( write_tx_output(txh, txo_bin) if o == prev_index: total_out += txo_bin.amount + if coin.decred and txo_bin.decred_script_version != 0: + raise SigningError( + FailureType.ProcessError, + "Cannot use utxo that has script_version != 0", + ) write_uint32(txh, tx.lock_time) - if tx.overwintered: + if tx.overwintered or coin.decred: write_uint32(txh, tx.expiry) ofs = 0 diff --git a/src/apps/wallet/sign_tx/writers.py b/src/apps/wallet/sign_tx/writers.py index 7c56a0b04..c22acf764 100644 --- a/src/apps/wallet/sign_tx/writers.py +++ b/src/apps/wallet/sign_tx/writers.py @@ -5,10 +5,13 @@ from trezor.messages.TxOutputBinType import TxOutputBinType from apps.common.writers import ( write_bytes, write_bytes_reversed, + write_uint8, + write_uint16_le, write_uint32_le, write_uint64_le, ) +write_uint16 = write_uint16_le write_uint32 = write_uint32_le write_uint64 = write_uint64_le @@ -32,8 +35,25 @@ def write_tx_input_check(w, i: TxInputType): write_uint32(w, i.amount or 0) +def write_tx_input_decred(w, i: TxInputType): + write_bytes_reversed(w, i.prev_hash) + write_uint32(w, i.prev_index) + write_uint8(w, i.decred_tree) + write_uint32(w, i.sequence) + + +def write_tx_input_decred_witness(w, i: TxInputType): + write_uint64(w, i.amount) + write_uint32(w, 0) # block height fraud proof + write_uint32(w, 0xFFFFFFFF) # block index fraud proof + write_varint(w, len(i.script_sig)) + write_bytes(w, i.script_sig) + + def write_tx_output(w, o: TxOutputBinType): write_uint64(w, o.amount) + if o.decred_script_version is not None: + write_uint16_le(w, o.decred_script_version) write_varint(w, len(o.script_pubkey)) write_bytes(w, o.script_pubkey) diff --git a/src/trezor/crypto/base58.py b/src/trezor/crypto/base58.py index 10bc0147f..a9b560aa5 100644 --- a/src/trezor/crypto/base58.py +++ b/src/trezor/crypto/base58.py @@ -71,6 +71,12 @@ def groestl512d_32(data: bytes) -> bytes: return groestl512(groestl512(data).digest()).digest()[:4] +def blake256_32(data: bytes) -> bytes: + from .hashlib import blake256 + + return blake256(blake256(data).digest()).digest()[:4] + + def encode_check(data: bytes, digestfunc=sha256d_32) -> str: """ Convert bytes to base58 encoded string, append checksum.