mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-14 11:39:03 +00:00
Dash: Support spending DIP2 special transaction inputs (#351)
This implements support for spending of TX outputs which are part of a [DIP2](https://github.com/dashpay/dips/blob/master/dip-0002.md) special transaction, especially [DIP4 coinbases](https://github.com/dashpay/dips/blob/master/dip-0004.md#coinbase-special-transaction). This is for Dash only and thus required the addition of `is_dash` in `tx_api.py`. Support on the firmware side is not required for this to work as it reuses the logic from Zcash's `extra_data` and `extra_data_len` fields.
This commit is contained in:
parent
6fa8ccfeed
commit
d131b70bed
208
trezorlib/tests/device_tests/test_msg_signtx_dash.py
Normal file
208
trezorlib/tests/device_tests/test_msg_signtx_dash.py
Normal file
@ -0,0 +1,208 @@
|
||||
# This file is part of the Trezor project.
|
||||
#
|
||||
# Copyright (C) 2012-2018 SatoshiLabs and contributors
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
from trezorlib import btc, messages as proto
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ..support.tx_cache import tx_cache
|
||||
from .common import TrezorTest
|
||||
|
||||
TX_API = tx_cache("Dash")
|
||||
|
||||
|
||||
class TestMsgSigntxDash(TrezorTest):
|
||||
def test_send_dash(self):
|
||||
self.setup_mnemonic_allallall()
|
||||
inp1 = proto.TxInputType(
|
||||
address_n=parse_path("44'/5'/0'/0/0"),
|
||||
# dash:XdTw4G5AWW4cogGd7ayybyBNDbuB45UpgH
|
||||
amount=1000000000,
|
||||
prev_hash=bytes.fromhex(
|
||||
"5579eaa64b2a0233e7d8d037f5a5afc957cedf48f1c4067e9e33ca6df22ab04f"
|
||||
),
|
||||
prev_index=1,
|
||||
script_type=proto.InputScriptType.SPENDADDRESS,
|
||||
)
|
||||
out1 = proto.TxOutputType(
|
||||
address="XpTc36DPAeWmaueNBA9JqCg2GC8XDLKSYe",
|
||||
amount=999999000,
|
||||
script_type=proto.OutputScriptType.PAYTOADDRESS,
|
||||
)
|
||||
with self.client:
|
||||
self.client.set_expected_responses(
|
||||
[
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXMETA,
|
||||
details=proto.TxRequestDetailsType(tx_hash=inp1.prev_hash),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=0, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=1, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=0, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=1, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput),
|
||||
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(request_type=proto.RequestType.TXFINISHED),
|
||||
]
|
||||
)
|
||||
_, serialized_tx = btc.sign_tx(
|
||||
self.client, "Dash", [inp1], [out1], prev_txes=TX_API
|
||||
)
|
||||
|
||||
assert (
|
||||
serialized_tx.hex()
|
||||
== "01000000014fb02af26dca339e7e06c4f148dfce57c9afa5f537d0d8e733022a4ba6ea7955010000006a4730440220387be4d1e4b5e355614091416373e99e1a3532b8cc9a8629368060aff2681bdb02200a0c4a5e9eb2ce6adb6c2e01ec8f954463dcc04f531ed8a89a2b40019d5aeb0b012102936f80cac2ba719ddb238646eb6b78a170a55a52a9b9f08c43523a4a6bd5c896ffffffff0118c69a3b000000001976a9149710d6545407e78c326aa8c8ae386ec7f883b0af88ac00000000"
|
||||
)
|
||||
|
||||
def test_send_dash_dip2_input(self):
|
||||
self.setup_mnemonic_allallall()
|
||||
inp1 = proto.TxInputType(
|
||||
address_n=parse_path("44'/5'/0'/0/0"),
|
||||
# dash:XdTw4G5AWW4cogGd7ayybyBNDbuB45UpgH
|
||||
amount=4095000260,
|
||||
prev_hash=bytes.fromhex(
|
||||
"15575a1c874bd60a819884e116c42e6791c8283ce1fc3b79f0d18531a61bbb8a"
|
||||
),
|
||||
prev_index=1,
|
||||
script_type=proto.InputScriptType.SPENDADDRESS,
|
||||
)
|
||||
out1 = proto.TxOutputType(
|
||||
address_n=parse_path("44'/5'/0'/1/0"),
|
||||
amount=4000000000,
|
||||
script_type=proto.OutputScriptType.PAYTOADDRESS,
|
||||
)
|
||||
out2 = proto.TxOutputType(
|
||||
address="XrEFMNkxeipYHgEQKiJuqch8XzwrtfH5fm",
|
||||
amount=95000000,
|
||||
script_type=proto.OutputScriptType.PAYTOADDRESS,
|
||||
)
|
||||
with self.client:
|
||||
self.client.set_expected_responses(
|
||||
[
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXMETA,
|
||||
details=proto.TxRequestDetailsType(tx_hash=inp1.prev_hash),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=0, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=0, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(
|
||||
request_index=1, tx_hash=inp1.prev_hash
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXEXTRADATA,
|
||||
details=proto.TxRequestDetailsType(
|
||||
extra_data_len=39,
|
||||
extra_data_offset=0,
|
||||
tx_hash=inp1.prev_hash,
|
||||
),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=1),
|
||||
),
|
||||
proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput),
|
||||
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXINPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=1),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=0),
|
||||
),
|
||||
proto.TxRequest(
|
||||
request_type=proto.RequestType.TXOUTPUT,
|
||||
details=proto.TxRequestDetailsType(request_index=1),
|
||||
),
|
||||
proto.TxRequest(request_type=proto.RequestType.TXFINISHED),
|
||||
]
|
||||
)
|
||||
_, serialized_tx = btc.sign_tx(
|
||||
self.client, "Dash", [inp1], [out1, out2], prev_txes=TX_API
|
||||
)
|
||||
|
||||
assert (
|
||||
serialized_tx.hex()
|
||||
== "01000000018abb1ba63185d1f0793bfce13c28c891672ec416e18498810ad64b871c5a5715010000006b483045022100f0442b6d9c7533cd6f74afa993b280ed9475276d69df4dac631bc3b5591ba71b022051daf125372c1c477681bbd804a6445d8ff6840901854fb0b485b1c6c7866c44012102936f80cac2ba719ddb238646eb6b78a170a55a52a9b9f08c43523a4a6bd5c896ffffffff0200286bee000000001976a914fd61dd017dad1f505c0511142cc9ac51ef3a5beb88acc095a905000000001976a914aa7a6a1f43dfc34d17e562ce1845b804b73fc31e88ac00000000"
|
||||
)
|
@ -0,0 +1 @@
|
||||
{"txid":"15575a1c874bd60a819884e116c42e6791c8283ce1fc3b79f0d18531a61bbb8a","version":3,"type":5,"locktime":0,"extraPayloadSize":38,"extraPayload":"01003a5200009e8ecb69a1493e3d573cc9ce8460b9a0d7b05e77ca17878c9faf73959392cef3","vin":[{"coinbase":"023a52042ab0355c08fabe6d6d0000000000000000000000000000000000000000000000000000000000000000010000000000000010000017cd0000000d2f6e6f64655374726174756d2f","sequence":0,"n":0}],"vout":[{"value":"40.95000262","n":0,"scriptPubKey":{"hex":"76a914cb594917ad4e5849688ec63f29a0f7f3badb5da688ac","asm":"OP_DUP OP_HASH160 cb594917ad4e5849688ec63f29a0f7f3badb5da6 OP_EQUALVERIFY OP_CHECKSIG","addresses":["yereyozxENB9jbhqpbg1coE5c39ExqLSaG"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"40.95000260","n":1,"scriptPubKey":{"hex":"76a9141e7754b21a4afe7d229b8ff46e613d1117a6515588ac","asm":"OP_DUP OP_HASH160 1e7754b21a4afe7d229b8ff46e613d1117a65155 OP_EQUALVERIFY OP_CHECKSIG","addresses":["yP6Y5D9bx3ih9RCAgSJNdzbiVtPYXNUE8v"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null}],"blockhash":"000000000c1f70ed43e42571dc18c6930de267d63f82640aa13ecee1b2508ed0","blockheight":21050,"confirmations":-635,"time":1547022378,"blocktime":1547022378,"isCoinBase":true,"valueOut":81.90000522,"size":233,"txlock":false,"cbTx":{"version":1,"height":21050,"merkleRootMNList":"f3ce92939573af9f8c8717ca775eb0d7a0b96084cec93c573d3e49a169cb8e9e"}}
|
@ -0,0 +1 @@
|
||||
{"txid":"5579eaa64b2a0233e7d8d037f5a5afc957cedf48f1c4067e9e33ca6df22ab04f","version":3,"locktime":21064,"vin":[{"txid":"4f5a33d74e1d2aea6a0d6c374e382a9bc4d5a045dff1969ed62b887558f7a230","vout":0,"sequence":4294967294,"n":0,"scriptSig":{"hex":"473044022003057fa600b72bf970fe56cf8fbaba613ec705563adcb34c4a18eb6a268d722f022070fa9e1f999dacf2b16de9887d5e37374b09fddb7e8856f250de918def31fd0d01210380137151f0cdacdd072ec5fd3b11c68c4509be9d5676d24ca2ba852173c6a899","asm":"3044022003057fa600b72bf970fe56cf8fbaba613ec705563adcb34c4a18eb6a268d722f022070fa9e1f999dacf2b16de9887d5e37374b09fddb7e8856f250de918def31fd0d[ALL] 0380137151f0cdacdd072ec5fd3b11c68c4509be9d5676d24ca2ba852173c6a899"},"addr":"yY8hmtiBVyw74vKmBhdhNxvCfiakuUUBkb","valueSat":1000593,"value":0.01000593,"doubleSpentTxID":null},{"txid":"4f5a33d74e1d2aea6a0d6c374e382a9bc4d5a045dff1969ed62b887558f7a230","vout":1,"sequence":4294967294,"n":1,"scriptSig":{"hex":"483045022100babd26c449eb9718d3906328a392873630df77c5cafd715cfdb880f832f5470c02201a8dce2a0e784696a2bcbd64415e523e8c545773881764e7100354eaddd7337401210219a6894f4d5dd3e78c2215f9feb0d57fcce287bd0f0078ae0d246a58b3e554fb","asm":"3045022100babd26c449eb9718d3906328a392873630df77c5cafd715cfdb880f832f5470c02201a8dce2a0e784696a2bcbd64415e523e8c545773881764e7100354eaddd73374[ALL] 0219a6894f4d5dd3e78c2215f9feb0d57fcce287bd0f0078ae0d246a58b3e554fb"},"addr":"ySCCMgodnW999ehuQt8pe4qivwhfZaduSy","valueSat":1000000000,"value":10,"doubleSpentTxID":null}],"vout":[{"value":"0.01000210","n":0,"scriptPubKey":{"hex":"76a9144f61517912cf3fb9a19a14912bea23792b18bdcc88ac","asm":"OP_DUP OP_HASH160 4f61517912cf3fb9a19a14912bea23792b18bdcc OP_EQUALVERIFY OP_CHECKSIG","addresses":["yTZAp9ZhVcNDVa8ZUiJByTKKeTNHwFpasG"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null},{"value":"10.00000000","n":1,"scriptPubKey":{"hex":"76a9141e7754b21a4afe7d229b8ff46e613d1117a6515588ac","asm":"OP_DUP OP_HASH160 1e7754b21a4afe7d229b8ff46e613d1117a65155 OP_EQUALVERIFY OP_CHECKSIG","addresses":["yP6Y5D9bx3ih9RCAgSJNdzbiVtPYXNUE8v"],"type":"pubkeyhash"},"spentTxId":null,"spentIndex":null,"spentHeight":null}],"blockheight":-1,"confirmations":0,"time":1547025147,"valueOut":10.0100021,"size":373,"valueIn":10.01000593,"fees":0.00000383,"txlock":false}
|
File diff suppressed because one or more lines are too long
@ -23,6 +23,7 @@ from ..support.tx_cache import tx_cache
|
||||
TxApiBitcoin = coins.tx_api["Bitcoin"]
|
||||
TxApiTestnet = tx_cache("Testnet", allow_fetch=False)
|
||||
TxApiZencash = coins.tx_api["Zencash"]
|
||||
TxApiDash = tx_cache("Dash", allow_fetch=False)
|
||||
|
||||
tests_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@ -82,3 +83,26 @@ def test_tx_api_get_block_hash():
|
||||
assert (
|
||||
hash.hex() == "000000003f5d6ba1385c6cd2d4f836dfc5adf7f98834309ad67e26faef462454"
|
||||
)
|
||||
|
||||
|
||||
def test_tx_api_dash_dip2():
|
||||
# Test if pre-DIP2 TXs are still working as expected
|
||||
tx = TxApiDash.get_tx(
|
||||
"acb3b7f259429989fc9c51ae4a5e3e3eab0723dceb21577533ac7c4b4ba4db5d"
|
||||
)
|
||||
assert tx.version == 2 # pre-DIP2
|
||||
assert tx.extra_data is None and tx.extra_data_len is None
|
||||
|
||||
# Test if version 3 TX with type=0 is treated as normal TX
|
||||
tx = TxApiDash.get_tx(
|
||||
"5579eaa64b2a0233e7d8d037f5a5afc957cedf48f1c4067e9e33ca6df22ab04f"
|
||||
)
|
||||
assert tx.version == 3
|
||||
assert tx.extra_data is None and tx.extra_data_len is None
|
||||
|
||||
# Test if DIP2 payloads are initialized correctly
|
||||
tx = TxApiDash.get_tx(
|
||||
"15575a1c874bd60a819884e116c42e6791c8283ce1fc3b79f0d18531a61bbb8a"
|
||||
)
|
||||
assert tx.version == (3 | (5 << 16)) # DIP2 type 1 (ProRegTx)
|
||||
assert len(tx.extra_data) == (38 + 1) # real length + varint size
|
||||
|
@ -15,6 +15,7 @@
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import random
|
||||
import struct
|
||||
from decimal import Decimal
|
||||
|
||||
import requests
|
||||
@ -32,6 +33,21 @@ def is_capricoin(coin):
|
||||
return coin["coin_name"].lower().startswith("capricoin")
|
||||
|
||||
|
||||
def is_dash(coin):
|
||||
return coin["coin_name"].lower().startswith("dash")
|
||||
|
||||
|
||||
def pack_varint(n):
|
||||
if n < 253:
|
||||
return struct.pack("<B", n)
|
||||
elif n <= 0xFFFF:
|
||||
return struct.pack("<BH", 253, n)
|
||||
elif n <= 0xFFFFFFFF:
|
||||
return struct.pack("<BL", 254, n)
|
||||
else:
|
||||
return struct.pack("<BQ", 255, n)
|
||||
|
||||
|
||||
def _json_to_input(coin, vin):
|
||||
i = messages.TxInputType()
|
||||
if "coinbase" in vin:
|
||||
@ -104,6 +120,25 @@ def json_to_tx(coin, data):
|
||||
extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64
|
||||
t.extra_data = rawtx[-extra_data_len:]
|
||||
|
||||
if is_dash(coin):
|
||||
dip2_type = data.get("type", 0)
|
||||
|
||||
if t.version == 3 and dip2_type != 0:
|
||||
# It's a DIP2 special TX with payload
|
||||
|
||||
if "extraPayloadSize" not in data or "extraPayload" not in data:
|
||||
raise ValueError("Payload data missing in DIP2 transaction")
|
||||
|
||||
if data["extraPayloadSize"] * 2 != len(data["extraPayload"]):
|
||||
raise ValueError("length mismatch")
|
||||
t.extra_data = pack_varint(data["extraPayloadSize"]) + bytes.fromhex(
|
||||
data["extraPayload"]
|
||||
)
|
||||
|
||||
# Trezor firmware doesn't understand the split of version and type, so let's mimic the
|
||||
# old serialization format
|
||||
t.version |= dip2_type << 16
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user