1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00

src/apps/wallet/sign_tx: add support for Decred

This commit is contained in:
Matheus Degiovani 2018-09-20 17:01:26 -03:00 committed by Pavol Rusnak
parent 9e3ae93796
commit 7834d06aac
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
6 changed files with 181 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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.