src/apps/wallet/sign_tx: re-enable Decred

pull/25/head
Pavol Rusnak 6 years ago
parent de12ad705c
commit 2277a9c754
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D

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

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

@ -1,13 +1,16 @@
from ubinascii import hexlify
from trezor.crypto.hashlib import sha256
from trezor.crypto.hashlib import blake256, sha256
from trezor.utils import HashWriter
from apps.wallet.sign_tx.signing import write_varint
def message_digest(coin, message):
h = HashWriter(sha256)
if coin.decred:
h = HashWriter(blake256)
else:
h = HashWriter(sha256)
write_varint(h, len(coin.signed_message_header))
h.extend(coin.signed_message_header)
write_varint(h, len(message))

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

@ -9,6 +9,7 @@ from apps.common import address_type
from apps.common.coininfo import CoinInfo
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys, multisig_pubkey_index
from apps.wallet.sign_tx.scripts import (
blake256_ripemd160_digest,
output_script_multisig,
output_script_native_p2wpkh_or_p2wsh,
sha256_ripemd160_digest,
@ -65,7 +66,7 @@ def get_address(
return address_multisig_p2wsh(pubkeys, multisig.m, coin.bech32_prefix)
# native p2wpkh
return address_p2wpkh(node.public_key(), coin.bech32_prefix)
return address_p2wpkh(node.public_key(), coin)
elif (
script_type == InputScriptType.SPENDP2SHWITNESS
@ -92,7 +93,10 @@ def address_multisig_p2sh(pubkeys: bytes, m: int, coin: CoinInfo):
FailureType.ProcessError, "Multisig not enabled on this coin"
)
redeem_script = output_script_multisig(pubkeys, m)
redeem_script_hash = sha256_ripemd160_digest(redeem_script)
if coin.decred:
redeem_script_hash = blake256_ripemd160_digest(redeem_script)
else:
redeem_script_hash = sha256_ripemd160_digest(redeem_script)
return address_p2sh(redeem_script_hash, coin)
@ -127,7 +131,7 @@ def address_p2sh(redeem_script_hash: bytes, coin: CoinInfo) -> str:
def address_p2wpkh_in_p2sh(pubkey: bytes, coin: CoinInfo) -> str:
pubkey_hash = ecdsa_hash_pubkey(pubkey)
pubkey_hash = ecdsa_hash_pubkey(pubkey, coin)
redeem_script = output_script_native_p2wpkh_or_p2wsh(pubkey_hash)
redeem_script_hash = sha256_ripemd160_digest(redeem_script)
return address_p2sh(redeem_script_hash, coin)
@ -139,9 +143,9 @@ def address_p2wsh_in_p2sh(witness_script_hash: bytes, coin: CoinInfo) -> str:
return address_p2sh(redeem_script_hash, coin)
def address_p2wpkh(pubkey: bytes, hrp: str) -> str:
pubkeyhash = ecdsa_hash_pubkey(pubkey)
address = bech32.encode(hrp, _BECH32_WITVER, pubkeyhash)
def address_p2wpkh(pubkey: bytes, coin: CoinInfo) -> str:
pubkeyhash = ecdsa_hash_pubkey(pubkey, coin)
address = bech32.encode(coin.bech32_prefix, _BECH32_WITVER, pubkeyhash)
if address is None:
raise AddressError(FailureType.ProcessError, "Invalid address")
return address
@ -173,13 +177,17 @@ def address_to_cashaddr(address: str, coin: CoinInfo) -> str:
return cashaddr.encode(coin.cashaddr_prefix, version, data)
def ecdsa_hash_pubkey(pubkey: bytes) -> bytes:
def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes:
if pubkey[0] == 0x04:
ensure(len(pubkey) == 65) # uncompressed format
elif pubkey[0] == 0x00:
ensure(len(pubkey) == 1) # point at infinity
else:
ensure(len(pubkey) == 33) # compresssed format
if coin.decred:
return blake256_ripemd160_digest(pubkey)
h = sha256(pubkey).digest()
h = ripemd160(h).digest()
return h

@ -0,0 +1,65 @@
from micropython import const
from trezor.crypto.hashlib import blake256
from trezor.messages.SignTx import SignTx
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.utils import HashWriter
from apps.wallet.sign_tx.writers import (
write_tx_input_decred,
write_tx_output,
write_uint32,
write_varint,
)
DECRED_SERIALIZE_FULL = const(0 << 16)
DECRED_SERIALIZE_NO_WITNESS = const(1 << 16)
DECRED_SERIALIZE_WITNESS_SIGNING = const(3 << 16)
DECRED_SIGHASHALL = const(1)
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.
"""
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)
def add_prevouts(self, txi: TxInputType):
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)
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
def add_locktime_expiry(self, tx: SignTx):
write_uint32(self.h_prefix, tx.lock_time)
write_uint32(self.h_prefix, tx.expiry)
def prefix_hash(self) -> bytes:
return self.h_prefix.get_digest()

@ -1,6 +1,7 @@
from trezor.crypto.hashlib import ripemd160, sha256
from trezor.crypto.hashlib import blake256, ripemd160, sha256
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
from apps.common.coininfo import CoinInfo
from apps.common.writers import empty_bytearray
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys
from apps.wallet.sign_tx.writers import (
@ -192,6 +193,7 @@ def input_script_multisig(
signature: bytes,
signature_index: int,
sighash: int,
coin: CoinInfo,
):
signatures = multisig.signatures # other signatures
if len(signatures[signature_index]) > 0:
@ -199,10 +201,12 @@ def input_script_multisig(
signatures[signature_index] = signature # our signature
w = bytearray()
# 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
w.append(0x00)
if 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
w.append(0x00)
for s in signatures:
if len(s):
@ -267,3 +271,9 @@ def sha256_ripemd160_digest(b: bytes) -> bytes:
h = sha256(b).digest()
h = ripemd160(h).digest()
return h
def blake256_ripemd160_digest(b: bytes) -> bytes:
h = blake256(b).digest()
h = ripemd160(h).digest()
return h

@ -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
@ -13,6 +13,12 @@ from apps.common.coininfo import CoinInfo
from apps.common.writers import empty_bytearray
from apps.wallet.sign_tx import progress
from apps.wallet.sign_tx.addresses import *
from apps.wallet.sign_tx.decred_prefix_hasher import (
DECRED_SERIALIZE_NO_WITNESS,
DECRED_SERIALIZE_WITNESS_SIGNING,
DECRED_SIGHASHALL,
DecredPrefixHasher,
)
from apps.wallet.sign_tx.helpers import *
from apps.wallet.sign_tx.multisig import *
from apps.wallet.sign_tx.overwinter_zip143 import ( # noqa:F401
@ -59,7 +65,10 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
# tx, as the SignTx info is streamed only once
h_first = HashWriter(sha256) # not a real tx hash
if tx.overwintered:
if coin.decred:
hash143 = DecredPrefixHasher(tx) # pseudo bip143 prefix hashing
tx_ser = TxRequestSerializedType()
elif tx.overwintered:
hash143 = Zip143() # zip143 transaction hashing
else:
hash143 = Bip143() # bip143 transaction hashing
@ -130,6 +139,17 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
else:
raise SigningError(FailureType.DataError, "Wrong input script type")
if coin.decred:
w_txi = empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash))
if i == 0: # serializing first input => prepend headers
write_bytes(w_txi, get_tx_header(coin, tx))
write_tx_input_decred(w_txi, txi)
tx_ser.serialized_tx = w_txi
tx_req.serialized = tx_ser
if coin.decred:
hash143.add_output_count(tx)
for o in range(tx.outputs_count):
# STAGE_REQUEST_3_OUTPUT
txo = await request_tx_output(tx_req, o)
@ -143,6 +163,22 @@ 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 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 = empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey))
if o == 0: # serializing first output => prepend outputs count
write_varint(w_txo_bin, tx.outputs_count)
write_tx_output(w_txo_bin, txo_bin)
tx_ser.serialized_tx = w_txo_bin
tx_req.serialized = tx_ser
hash143.set_last_output_bytes(w_txo_bin)
write_tx_output(h_first, txo_bin)
hash143.add_output(txo_bin)
total_out += txo_bin.amount
@ -159,6 +195,9 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
if not await confirm_total(total_in - change_out, fee, coin):
raise SigningError(FailureType.ActionCancelled, "Total cancelled")
if coin.decred:
hash143.add_locktime_expiry(tx)
return h_first, hash143, segwit, total_in, wallet_path
@ -183,6 +222,9 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
tx_req.details = TxRequestDetailsType()
tx_req.serialized = None
if coin.decred:
prefix_hash = hash143.prefix_hash()
for i_sign in range(tx.inputs_count):
progress.advance()
txi_sign = None
@ -234,7 +276,11 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
key_sign = node_derive(root, txi_sign.address_n)
key_sign_pub = key_sign.public_key()
hash143_hash = hash143.preimage_hash(
coin, tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)
coin,
tx,
txi_sign,
ecdsa_hash_pubkey(key_sign_pub, coin),
get_hash_type(coin),
)
# if multisig, check if singing with a key that is included in multisig
@ -259,6 +305,70 @@ 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)
key_sign = node_derive(root, txi_sign.address_n)
key_sign_pub = key_sign.public_key()
if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
prev_pkscript = output_script_multisig(
multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
prev_pkscript = output_script_p2pkh(
ecdsa_hash_pubkey(key_sign_pub, coin)
)
else:
raise ValueError("Unknown input script type")
h_witness = HashWriter(blake256)
write_uint32(h_witness, tx.version | DECRED_SERIALIZE_WITNESS_SIGNING)
write_varint(h_witness, tx.inputs_count)
for ii in range(tx.inputs_count):
if ii == i_sign:
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, DECRED_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_sign.script_sig = input_derive_script(
coin, txi_sign, key_sign_pub, signature
)
w_txi_sign = empty_bytearray(
8 + 4 + len(hash143.get_last_output_bytes())
if i_sign == 0
else 0 + 16 + 4 + len(txi_sign.script_sig)
)
if i_sign == 0:
write_bytes(w_txi_sign, hash143.get_last_output_bytes())
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_sign)
tx_ser.serialized_tx = w_txi_sign
tx_req.serialized = tx_ser
else:
# hash of what we are signing with this input
h_sign = HashWriter(sha256)
@ -292,7 +402,7 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
txi_sign.script_sig = output_script_p2pkh(
ecdsa_hash_pubkey(key_sign_pub)
ecdsa_hash_pubkey(key_sign_pub, coin)
)
if coin.bip115:
txi_sign.script_sig += script_replay_protection_bip115(
@ -355,6 +465,9 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
tx_req.serialized = tx_ser
if coin.decred:
return await request_tx_finish(tx_req)
for o in range(tx.outputs_count):
progress.advance()
# STAGE_REQUEST_5_OUTPUT
@ -396,7 +509,11 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
key_sign = node_derive(root, txi.address_n)
key_sign_pub = key_sign.public_key()
hash143_hash = hash143.preimage_hash(
coin, tx, txi, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)
coin,
tx,
txi,
ecdsa_hash_pubkey(key_sign_pub, coin),
get_hash_type(coin),
)
signature = ecdsa_sign(key_sign, hash143_hash)
@ -420,6 +537,7 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
tx_req.serialized = tx_ser
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 +553,16 @@ 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_uint32(txh, tx.version | DECRED_SERIALIZE_NO_WITNESS)
else:
write_uint32(txh, tx.version) # nVersion
@ -448,7 +571,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 +584,19 @@ 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 is not None
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
@ -626,7 +761,7 @@ def input_derive_script(
return input_script_p2wsh_in_p2sh(witness_script_hash)
# p2wpkh in p2sh
return input_script_p2wpkh_in_p2sh(ecdsa_hash_pubkey(pubkey))
return input_script_p2wpkh_in_p2sh(ecdsa_hash_pubkey(pubkey, coin))
elif i.script_type == InputScriptType.SPENDWITNESS:
# native p2wpkh or p2wsh
@ -636,7 +771,7 @@ def input_derive_script(
# p2sh multisig
signature_index = multisig_pubkey_index(i.multisig, pubkey)
return input_script_multisig(
i.multisig, signature, signature_index, get_hash_type(coin)
i.multisig, signature, signature_index, get_hash_type(coin), coin
)
else:

@ -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 or 0)
write_uint8(w, i.decred_tree or 0)
write_uint32(w, i.sequence)
def write_tx_input_decred_witness(w, i: TxInputType):
write_uint64(w, i.amount or 0)
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(w, o.decred_script_version)
write_varint(w, len(o.script_pubkey))
write_bytes(w, o.script_pubkey)

@ -51,7 +51,7 @@ async def verify_message(ctx, msg):
elif script_type == SPENDP2SHWITNESS:
addr = address_p2wpkh_in_p2sh(pubkey, coin)
elif script_type == SPENDWITNESS:
addr = address_p2wpkh(pubkey, coin.bech32_prefix)
addr = address_p2wpkh(pubkey, coin)
else:
raise wire.ProcessError("Invalid signature")

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

@ -41,7 +41,7 @@ class TestAddress(unittest.TestCase):
coin = coins.by_name('Testnet')
address = address_p2wpkh(
unhexlify('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
coin.bech32_prefix
coin
)
self.assertEqual(address, 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx')

@ -60,15 +60,15 @@ class TestAddressGRS(unittest.TestCase):
root = bip32.from_seed(seed, coin.curve_name)
node = node_derive(root, [84 | 0x80000000, 17 | 0x80000000, 0 | 0x80000000, 1, 0])
address = address_p2wpkh(node.public_key(), coin.bech32_prefix)
address = address_p2wpkh(node.public_key(), coin)
self.assertEqual(address, 'grs1qzfpwn55tvkxcw0xwfa0g8k2gtlzlgkcq3z000e')
node = node_derive(root, [84 | 0x80000000, 17 | 0x80000000, 0 | 0x80000000, 1, 1])
address = address_p2wpkh(node.public_key(), coin.bech32_prefix)
address = address_p2wpkh(node.public_key(), coin)
self.assertEqual(address, 'grs1qxsgwl66tx7tsuwfm4kk5c5dh6tlfpr4qjqg6gg')
node = node_derive(root, [84 | 0x80000000, 17 | 0x80000000, 0 | 0x80000000, 0, 0])
address = address_p2wpkh(node.public_key(), coin.bech32_prefix)
address = address_p2wpkh(node.public_key(), coin)
self.assertEqual(address, 'grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne')

Loading…
Cancel
Save