1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-29 09:38:08 +00:00

multi: Add decred staking.

Add two new input and four output script types.

Decred ticket purchases consist of a stake submission, op returns, and
change addresses. Although change addresses are allowed by consensus,
they are no longer used in practice and so have been given the
restrictions of a null pubkey and no value. Stake scripts are almost
identical to p2pkh or p2sh except for an extra opcode in front. Inputs
are currently only used in the form of one input three outputs with the
first output, or stake submission, paying to a public key hash, or with
two inputs and five outputs with the stake submission paying to a
multisig script hash. The op returns are directed to the user in the
case of one and the voting service provider and user in the case of two.

One of the sstx commitment for a ticket must pay back to the trezor
wallet. This is checked and an error is thrown if we don't find the
expected public key hash.

Because this adds the ability to create new types of outputs once the
ticket votes, two new input script types are also needed. A successful
vote will lead to a stake generation script that must be spent, and an
unsuccessful vote will lead to a revocation script that must be spent.
If we allowed stake change scripts to have a valid pubkey, that too
would require another op code, but we disallow those for output.
This commit is contained in:
JoeGruff 2020-09-26 18:30:56 +09:00 committed by Andrew Kozlik
parent 6c11bc60d7
commit e3ea32a986
25 changed files with 841 additions and 24 deletions

View File

@ -31,6 +31,14 @@ enum OutputScriptType {
PAYTOP2SHWITNESS = 5; // only for change output
}
/**
* Type of script which will be used for decred stake transaction input
*/
enum DecredStakingSpendType {
SSGen = 0;
SSRTX = 1;
}
/**
* Unit to be used when showing amounts on the display
*/
@ -171,17 +179,18 @@ message VerifyMessage {
* @next Failure
*/
message SignTx {
required uint32 outputs_count = 1; // number of transaction outputs
required uint32 inputs_count = 2; // number of transaction inputs
optional string coin_name = 3 [default='Bitcoin']; // coin to use
optional uint32 version = 4 [default=1]; // transaction version
optional uint32 lock_time = 5 [default=0]; // transaction lock_time
optional uint32 expiry = 6; // only for Decred and Zcash
optional bool overwintered = 7 [deprecated=true]; // deprecated in 2.3.2, the field is not needed as it can be derived from `version`
optional uint32 version_group_id = 8; // only for Zcash, nVersionGroupId
optional uint32 timestamp = 9; // only for Peercoin
optional uint32 branch_id = 10; // only for Zcash, BRANCH_ID
optional AmountUnit amount_unit = 11 [default=BITCOIN]; // show amounts in
required uint32 outputs_count = 1; // number of transaction outputs
required uint32 inputs_count = 2; // number of transaction inputs
optional string coin_name = 3 [default='Bitcoin']; // coin to use
optional uint32 version = 4 [default=1]; // transaction version
optional uint32 lock_time = 5 [default=0]; // transaction lock_time
optional uint32 expiry = 6; // only for Decred and Zcash
optional bool overwintered = 7 [deprecated=true]; // deprecated in 2.3.2, the field is not needed as it can be derived from `version`
optional uint32 version_group_id = 8; // only for Zcash, nVersionGroupId
optional uint32 timestamp = 9; // only for Peercoin
optional uint32 branch_id = 10; // only for Zcash, BRANCH_ID
optional AmountUnit amount_unit = 11 [default=BITCOIN]; // show amounts in
optional bool decred_staking_ticket = 12 [default=false]; // only for Decred, this is signing a ticket purchase
}
/**
@ -277,7 +286,7 @@ message TxAck {
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
optional uint64 amount = 8; // amount of previous transaction output (for segwit only)
optional uint32 decred_tree = 9; // only for Decred
optional uint32 decred_tree = 9; // only for Decred, 0 is a normal transaction while 1 is a stake transaction
// optional uint32 decred_script_version = 10; // only for Decred // deprecated -> only 0 is supported
// optional bytes prev_block_hash_bip115 = 11; // BIP-115 support dropped
// optional uint32 prev_block_height_bip115 = 12; // BIP-115 support dropped
@ -286,6 +295,7 @@ message TxAck {
optional bytes commitment_data = 15; // optional commitment data for the SLIP-0019 proof of ownership
optional bytes orig_hash = 16; // tx_hash of the original transaction where this input was spent (used when creating a replacement transaction)
optional uint32 orig_index = 17; // index of the input in the original transaction (used when creating a replacement transaction)
optional DecredStakingSpendType decred_staking_spend = 18; // if not None this holds the type of stake spend: revocation or stake generation
}
/**
* Structure representing compiled transaction output
@ -293,7 +303,7 @@ message TxAck {
message TxOutputBinType {
required uint64 amount = 1;
required bytes script_pubkey = 2;
optional uint32 decred_script_version = 3; // only for Decred
optional uint32 decred_script_version = 3; // only for Decred, currently only 0 is supported
}
/**
* Structure representing transaction output
@ -329,13 +339,14 @@ message TxInput {
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
required uint64 amount = 8; // amount of previous transaction output
optional uint32 decred_tree = 9; // only for Decred
optional uint32 decred_tree = 9; // only for Decred, 0 is a normal transaction while 1 is a stake transaction
reserved 10, 11, 12; // fields which are in use, or have been in the past, in TxInputType
optional bytes witness = 13; // witness data, only set for EXTERNAL inputs
optional bytes ownership_proof = 14; // SLIP-0019 proof of ownership, only set for EXTERNAL inputs
optional bytes commitment_data = 15; // optional commitment data for the SLIP-0019 proof of ownership
optional bytes orig_hash = 16; // tx_hash of the original transaction where this input was spent (used when creating a replacement transaction)
optional uint32 orig_index = 17; // index of the input in the original transaction (used when creating a replacement transaction)
optional DecredStakingSpendType decred_staking_spend = 18; // if not None this holds the type of stake spend: revocation or stake generation
}
/** Data type for transaction output to be signed.

View File

@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Locking the device by holding finger on the homescreen for 2.5 seconds. [#1404]
- Public key to ECDHSessionKey. [#1518]
- Decred staking. [#1249]
### Changed
@ -360,6 +361,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[#1193]: https://github.com/trezor/trezor-firmware/issues/1193
[#1206]: https://github.com/trezor/trezor-firmware/issues/1206
[#1246]: https://github.com/trezor/trezor-firmware/issues/1246
[#1249]: https://github.com/trezor/trezor-firmware/issues/1249
[#1271]: https://github.com/trezor/trezor-firmware/issues/1271
[#1292]: https://github.com/trezor/trezor-firmware/issues/1292
[#1322]: https://github.com/trezor/trezor-firmware/issues/1322

View File

@ -27,10 +27,7 @@ def get_address(
# Ensure that our public key is included in the multisig.
multisig_pubkey_index(multisig, node.public_key())
if (
script_type == InputScriptType.SPENDADDRESS
or script_type == InputScriptType.SPENDMULTISIG
):
if script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG):
if multisig: # p2sh multisig
if coin.address_type_p2sh is None:
raise wire.ProcessError("Multisig not enabled on this coin")

View File

@ -43,6 +43,7 @@ CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES: Dict[
OutputScriptType.PAYTOP2SHWITNESS: InputScriptType.SPENDP2SHWITNESS,
OutputScriptType.PAYTOWITNESS: InputScriptType.SPENDWITNESS,
}
INTERNAL_INPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.values())
CHANGE_OUTPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.keys())

View File

@ -22,7 +22,7 @@ async def get_public_key(ctx: wire.Context, msg: GetPublicKey) -> PublicKey:
node = keychain.derive(msg.address_n)
if (
script_type in [InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG]
script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG)
and coin.xpub_magic is not None
):
node_xpub = node.serialize_public(coin.xpub_magic)

View File

@ -0,0 +1,78 @@
from trezor import utils, wire
from trezor.crypto import base58
from trezor.crypto.base58 import blake256d_32
from apps.common.writers import empty_bytearray, write_bytes_fixed, write_uint64_le
# A ticket purchase submission for an address hash.
def output_script_sstxsubmissionpkh(addr: str) -> bytearray:
try:
raw_address = base58.decode_check(addr, blake256d_32)
except ValueError:
raise wire.DataError("Invalid address")
w = empty_bytearray(26)
w.append(0xBA) # OP_SSTX
w.append(0x76) # OP_DUP
w.append(0xA9) # OP_HASH160
w.append(0x14) # OP_DATA_20
write_bytes_fixed(w, raw_address[2:], 20)
w.append(0x88) # OP_EQUALVERIFY
w.append(0xAC) # OP_CHECKSIG
return w
# Ticket purchase change script.
def output_script_sstxchange(addr: str) -> bytearray:
try:
raw_address = base58.decode_check(addr, blake256d_32)
except ValueError:
raise wire.DataError("Invalid address")
w = empty_bytearray(26)
w.append(0xBD) # OP_SSTXCHANGE
w.append(0x76) # OP_DUP
w.append(0xA9) # OP_HASH160
w.append(0x14) # OP_DATA_20
write_bytes_fixed(w, raw_address[2:], 20)
w.append(0x88) # OP_EQUALVERIFY
w.append(0xAC) # OP_CHECKSIG
return w
# Spend from a stake revocation.
def output_script_ssrtx(pkh: bytes) -> bytearray:
utils.ensure(len(pkh) == 20)
s = bytearray(26)
s[0] = 0xBC # OP_SSRTX
s[1] = 0x76 # OP_DUP
s[2] = 0xA9 # OP_HASH160
s[3] = 0x14 # OP_DATA_20
s[4:24] = pkh
s[24] = 0x88 # OP_EQUALVERIFY
s[25] = 0xAC # OP_CHECKSIG
return s
# Spend from a stake generation.
def output_script_ssgen(pkh: bytes) -> bytearray:
utils.ensure(len(pkh) == 20)
s = bytearray(26)
s[0] = 0xBB # OP_SSGEN
s[1] = 0x76 # OP_DUP
s[2] = 0xA9 # OP_HASH160
s[3] = 0x14 # OP_DATA_20
s[4:24] = pkh
s[24] = 0x88 # OP_EQUALVERIFY
s[25] = 0xAC # OP_CHECKSIG
return s
# Retrieve pkh bytes from a stake commitment OPRETURN.
def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes:
w = empty_bytearray(30)
write_bytes_fixed(w, pkh, 20)
write_uint64_le(w, amount)
write_bytes_fixed(w, b"\x00\x58", 2) # standard fee limits
return w

View File

@ -2,6 +2,7 @@ from micropython import const
from trezor import wire
from trezor.messages import OutputScriptType
from trezor.utils import ensure
from apps.common import safety_checks
@ -76,6 +77,11 @@ class Approver:
self.weight.add_output(script_pubkey)
self.total_out += txo.amount
async def add_decred_sstx_submission(
self, txo: TxOutput, script_pubkey: bytes
) -> None:
raise NotImplementedError
def add_orig_external_output(self, txo: TxOutput) -> None:
self.orig_total_out += txo.amount
@ -125,6 +131,13 @@ class BasicApprover(Approver):
else:
await helpers.confirm_output(txo, self.coin, self.amount_unit)
async def add_decred_sstx_submission(
self, txo: TxOutput, script_pubkey: bytes
) -> None:
ensure(self.coin.decred)
await super().add_external_output(txo, script_pubkey, None)
await helpers.confirm_decred_sstx_submission(txo, self.coin, self.amount_unit)
async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> None:
fee = self.total_in - self.total_out

View File

@ -2,13 +2,13 @@ from micropython import const
from trezor import wire
from trezor.crypto.hashlib import blake256
from trezor.messages import InputScriptType
from trezor.messages import DecredStakingSpendType, InputScriptType
from trezor.messages.PrevOutput import PrevOutput
from trezor.utils import HashWriter, ensure
from apps.common.writers import write_bitcoin_varint
from .. import multisig, scripts, writers
from .. import multisig, scripts, scripts_decred, writers
from ..common import ecdsa_hash_pubkey, ecdsa_sign
from . import approvers, helpers, progress
from .bitcoin import Bitcoin
@ -17,8 +17,10 @@ DECRED_SERIALIZE_FULL = const(0 << 16)
DECRED_SERIALIZE_NO_WITNESS = const(1 << 16)
DECRED_SERIALIZE_WITNESS_SIGNING = const(3 << 16)
DECRED_SCRIPT_VERSION = const(0)
DECRED_SIGHASH_ALL = const(1)
OUTPUT_SCRIPT_NULL_SSTXCHANGE = (
b"\xBD\x76\xA9\x14\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x88\xAC"
)
if False:
from typing import Optional, Union, List
@ -87,7 +89,12 @@ class Decred(Bitcoin):
async def step2_approve_outputs(self) -> None:
write_bitcoin_varint(self.serialized_tx, self.tx_info.tx.outputs_count)
write_bitcoin_varint(self.h_prefix, self.tx_info.tx.outputs_count)
await super().step2_approve_outputs()
if self.tx_info.tx.decred_staking_ticket:
await self.approve_staking_ticket()
else:
await super().step2_approve_outputs()
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
self.write_tx_footer(self.h_prefix, self.tx_info.tx)
@ -127,7 +134,15 @@ class Decred(Bitcoin):
key_sign = self.keychain.derive(txi_sign.address_n)
key_sign_pub = key_sign.public_key()
if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX:
prev_pkscript = scripts_decred.output_script_ssrtx(
ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen:
prev_pkscript = scripts_decred.output_script_ssgen(
ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.script_type == InputScriptType.SPENDMULTISIG:
assert txi_sign.multisig is not None
prev_pkscript = scripts.output_script_multisig(
multisig.multisig_get_pubkeys(txi_sign.multisig),
@ -208,6 +223,53 @@ class Decred(Bitcoin):
writers.write_uint16(w, DECRED_SCRIPT_VERSION)
writers.write_bytes_prefixed(w, script_pubkey)
def process_sstx_commitment_owned(self, txo: TxOutput) -> bytearray:
if not self.tx_info.output_is_change(txo):
raise wire.DataError("Invalid sstxcommitment path.")
node = self.keychain.derive(txo.address_n)
pkh = ecdsa_hash_pubkey(node.public_key(), self.coin)
op_return_data = scripts_decred.sstxcommitment_pkh(pkh, txo.amount)
txo.amount = 0 # Clear the amount, since this is an OP_RETURN.
return scripts_decred.output_script_paytoopreturn(op_return_data)
async def approve_staking_ticket(self) -> None:
if self.tx_info.tx.outputs_count != 3:
raise wire.DataError("Ticket has wrong number of outputs.")
# SSTX submission
txo = await helpers.request_tx_output(self.tx_req, 0, self.coin)
if txo.address is None:
raise wire.DataError("Missing address.")
script_pubkey = scripts_decred.output_script_sstxsubmissionpkh(txo.address)
await self.approver.add_decred_sstx_submission(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
# SSTX commitment
txo = await helpers.request_tx_output(self.tx_req, 1, self.coin)
if txo.amount != self.approver.total_in:
raise wire.DataError("Wrong sstxcommitment amount.")
script_pubkey = self.process_sstx_commitment_owned(txo)
self.approver.add_change_output(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
# SSTX change
txo = await helpers.request_tx_output(self.tx_req, 2, self.coin)
if txo.address is None:
raise wire.DataError("Missing address.")
script_pubkey = scripts_decred.output_script_sstxchange(txo.address)
# Using change addresses is no longer common practice. Inputs are split
# beforehand and should be exact. SSTX change should pay zero amount to
# a zeroed hash.
if txo.amount != 0:
raise wire.DataError("Only value of 0 allowed for sstx change.")
if script_pubkey != OUTPUT_SCRIPT_NULL_SSTXCHANGE:
raise wire.DataError("Only zeroed addresses accepted for sstx change.")
self.approver.add_change_output(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
def write_tx_header(
self,
w: writers.Writer,

View File

@ -58,6 +58,22 @@ class UiConfirmOutput(UiConfirm):
__eq__ = utils.obj_eq
class UiConfirmDecredSSTXSubmission(UiConfirm):
def __init__(
self, output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
):
self.output = output
self.coin = coin
self.amount_unit = amount_unit
def confirm_dialog(self, ctx: wire.Context) -> Awaitable[Any]:
return layout.confirm_decred_sstx_submission(
ctx, self.output, self.coin, self.amount_unit
)
__eq__ = utils.obj_eq
class UiConfirmReplacement(UiConfirm):
def __init__(self, description: str, txid: bytes):
self.description = description
@ -175,6 +191,10 @@ def confirm_output(output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmount
return (yield UiConfirmOutput(output, coin, amount_unit))
def confirm_decred_sstx_submission(output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit) -> Awaitable[None]: # type: ignore
return (yield UiConfirmDecredSSTXSubmission(output, coin, amount_unit))
def confirm_replacement(description: str, txid: bytes) -> Awaitable[Any]: # type: ignore
return (yield UiConfirmReplacement(description, txid))

View File

@ -73,6 +73,19 @@ async def confirm_output(
await require(layout)
async def confirm_decred_sstx_submission(
ctx: wire.Context, output: TxOutput, coin: CoinInfo, amount_unit: EnumTypeAmountUnit
) -> None:
assert output.address is not None
address_short = addresses.address_short(coin, output.address)
await require(
layouts.confirm_decred_sstx_submission(
ctx, address_short, format_coin_amount(output.amount, coin, amount_unit)
)
)
async def confirm_replacement(ctx: wire.Context, description: str, txid: bytes) -> None:
await require(
layouts.confirm_replacement(

View File

@ -0,0 +1,10 @@
# Automatically generated by pb2py
# fmt: off
if __debug__:
try:
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
SSGen: Literal[0] = 0
SSRTX: Literal[1] = 1

View File

@ -27,6 +27,7 @@ class SignTx(p.MessageType):
timestamp: int = None,
branch_id: int = None,
amount_unit: EnumTypeAmountUnit = 0,
decred_staking_ticket: bool = False,
) -> None:
self.outputs_count = outputs_count
self.inputs_count = inputs_count
@ -38,6 +39,7 @@ class SignTx(p.MessageType):
self.timestamp = timestamp
self.branch_id = branch_id
self.amount_unit = amount_unit
self.decred_staking_ticket = decred_staking_ticket
@classmethod
def get_fields(cls) -> Dict:
@ -52,4 +54,5 @@ class SignTx(p.MessageType):
9: ('timestamp', p.UVarintType, None),
10: ('branch_id', p.UVarintType, None),
11: ('amount_unit', p.EnumType("AmountUnit", (0, 1, 2, 3)), 0), # default=BITCOIN
12: ('decred_staking_ticket', p.BoolType, False), # default=false
}

View File

@ -9,6 +9,7 @@ if __debug__:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
EnumTypeInputScriptType = Literal[0, 1, 2, 3, 4]
EnumTypeDecredStakingSpendType = Literal[0, 1]
except ImportError:
pass
@ -32,6 +33,7 @@ class TxInput(p.MessageType):
commitment_data: bytes = None,
orig_hash: bytes = None,
orig_index: int = None,
decred_staking_spend: EnumTypeDecredStakingSpendType = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
@ -47,6 +49,7 @@ class TxInput(p.MessageType):
self.commitment_data = commitment_data
self.orig_hash = orig_hash
self.orig_index = orig_index
self.decred_staking_spend = decred_staking_spend
@classmethod
def get_fields(cls) -> Dict:
@ -65,4 +68,5 @@ class TxInput(p.MessageType):
15: ('commitment_data', p.BytesType, None),
16: ('orig_hash', p.BytesType, None),
17: ('orig_index', p.UVarintType, None),
18: ('decred_staking_spend', p.EnumType("DecredStakingSpendType", (0, 1)), None),
}

View File

@ -9,6 +9,7 @@ if __debug__:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
EnumTypeInputScriptType = Literal[0, 1, 2, 3, 4]
EnumTypeDecredStakingSpendType = Literal[0, 1]
except ImportError:
pass
@ -32,6 +33,7 @@ class TxInputType(p.MessageType):
commitment_data: bytes = None,
orig_hash: bytes = None,
orig_index: int = None,
decred_staking_spend: EnumTypeDecredStakingSpendType = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
@ -47,6 +49,7 @@ class TxInputType(p.MessageType):
self.commitment_data = commitment_data
self.orig_hash = orig_hash
self.orig_index = orig_index
self.decred_staking_spend = decred_staking_spend
@classmethod
def get_fields(cls) -> Dict:
@ -65,4 +68,5 @@ class TxInputType(p.MessageType):
15: ('commitment_data', p.BytesType, None),
16: ('orig_hash', p.BytesType, None),
17: ('orig_index', p.UVarintType, None),
18: ('decred_staking_spend', p.EnumType("DecredStakingSpendType", (0, 1)), None),
}

View File

@ -44,6 +44,7 @@ __all__ = (
"show_xpub",
"show_warning",
"confirm_output",
"confirm_decred_sstx_submission",
"confirm_hex",
"confirm_total",
"confirm_joint_total",
@ -327,6 +328,23 @@ def confirm_output(
)
def confirm_decred_sstx_submission(
ctx: wire.GenericContext,
address: str,
amount: str,
) -> LayoutType:
text = Text("Purchase ticket", ui.ICON_SEND, ui.GREEN)
text.normal(amount)
text.normal("with voting rights to")
text.mono(*_split_address(address))
return interact(
ctx,
Confirm(text),
"confirm_decred_sstx_submission",
ButtonRequestType.ConfirmOutput,
)
def confirm_hex(
ctx: wire.GenericContext,
br_type: str,

View File

@ -0,0 +1,371 @@
from common import *
from trezor.utils import chunks
from trezor.crypto import bip32, bip39
from trezor.messages.SignTx import SignTx
from trezor.messages.TxAckInput import TxAckInput
from trezor.messages.TxAckInputWrapper import TxAckInputWrapper
from trezor.messages.TxInput import TxInput
from trezor.messages.TxAckOutput import TxAckOutput
from trezor.messages.TxAckOutputWrapper import TxAckOutputWrapper
from trezor.messages.TxOutput import TxOutput
from trezor.messages.TxAckPrevMeta import TxAckPrevMeta
from trezor.messages.PrevTx import PrevTx
from trezor.messages.TxAckPrevInput import TxAckPrevInput
from trezor.messages.TxAckPrevInputWrapper import TxAckPrevInputWrapper
from trezor.messages.PrevInput import PrevInput
from trezor.messages.TxAckPrevOutput import TxAckPrevOutput
from trezor.messages.TxAckPrevOutputWrapper import TxAckPrevOutputWrapper
from trezor.messages.PrevOutput import PrevOutput
from trezor.messages.TxRequest import TxRequest
from trezor.messages.RequestType import TXINPUT, TXOUTPUT, TXMETA, TXFINISHED
from trezor.messages.TxRequestDetailsType import TxRequestDetailsType
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
from trezor.messages import AmountUnit
from trezor.messages import OutputScriptType
from apps.common import coins
from apps.common.keychain import Keychain
from apps.bitcoin.keychain import get_schemas_for_coin
from apps.bitcoin.sign_tx import decred, helpers
from apps.bitcoin.sign_tx.approvers import BasicApprover
EMPTY_SERIALIZED = TxRequestSerializedType(serialized_tx=bytearray())
coin_decred = coins.by_name("Decred")
ptx1 = PrevTx(version=1, lock_time=0, inputs_count=2, outputs_count=1, extra_data_len=0)
pinp1 = PrevInput(
script_sig=unhexlify(
"483045022072ba61305fe7cb542d142b8f3299a7b10f9ea61f6ffaab5dca8142601869d53c0221009a8027ed79eb3b9bc13577ac2853269323434558528c6b6a7e542be46e7e9a820141047a2d177c0f3626fc68c53610b0270fa6156181f46586c679ba6a88b34c6f4874686390b4d92e5769fbb89c8050b984f4ec0b257a0e5c4ff8bd3b035a51709503"
),
prev_hash=unhexlify(
"c16a03f1cf8f99f6b5297ab614586cacec784c2d259af245909dedb0e39eddcf"
),
prev_index=1,
sequence=0xFFFF_FFFF,
)
pinp2 = PrevInput(
script_sig=unhexlify(
"48304502200fd63adc8f6cb34359dc6cca9e5458d7ea50376cbd0a74514880735e6d1b8a4c0221008b6ead7fe5fbdab7319d6dfede3a0bc8e2a7c5b5a9301636d1de4aa31a3ee9b101410486ad608470d796236b003635718dfc07c0cac0cfc3bfc3079e4f491b0426f0676e6643a39198e8e7bdaffb94f4b49ea21baa107ec2e237368872836073668214"
),
prev_hash=unhexlify(
"1ae39a2f8d59670c8fc61179148a8e61e039d0d9e8ab08610cb69b4a19453eaf"
),
prev_index=1,
sequence=0xFFFF_FFFF,
)
pout1 = PrevOutput(
script_pubkey=unhexlify("76a91424a56db43cf6f2b02e838ea493f95d8d6047423188ac"),
amount=200000 + 200000 - 10000,
decred_script_version=0,
)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestSignTxDecred(unittest.TestCase):
# pylint: disable=C0301
def test_one_one_fee(self):
inp1 = TxInput(
address_n=[44 | 0x80000000, 42 | 0x80000000, 0 | 0x80000000, 0, 0],
prev_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
prev_index=0,
amount=390000,
multisig=None,
sequence=0xFFFF_FFFF,
)
out1 = TxOutput(
address="DsaHnKa418BeeQmyhpQEGG4cxGAPrneydfv",
amount=390000 - 10000,
script_type=OutputScriptType.PAYTOADDRESS,
multisig=None,
)
tx = SignTx(
coin_name="Decred", version=1, lock_time=0, inputs_count=1, outputs_count=1
)
messages = [
None,
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("0100000001")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("4ac247307a054c37525169a78d690a7a7f87d709bef7d722aae85584f59c8fdf0000000000ffffffff01")
),
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmOutput(out1, coin_decred, AmountUnit.BITCOIN),
True,
helpers.UiConfirmTotal(380000 + 10000, 10000, coin_decred, AmountUnit.BITCOIN),
True,
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("60cc05000000000000001976a914664b0cd46741a695a38f8ed37db2a20327471beb88ac0000000000000000")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXMETA,
details=TxRequestDetailsType(
request_index=None,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevMeta(tx=ptx1),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(
request_index=0,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevInput(tx=TxAckPrevInputWrapper(input=pinp1)),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(
request_index=1,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevInput(tx=TxAckPrevInputWrapper(input=pinp2)),
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(
request_index=0,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevOutput(tx=TxAckPrevOutputWrapper(output=pout1)),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("01")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXFINISHED,
details=TxRequestDetailsType(request_index=None, tx_hash=None),
serialized=TxRequestSerializedType(
signature_index=0,
signature=unhexlify(
"3044022078a5c388838796562eb9dad176b00e6d9425bc360083f633a14948685ca8a5ce02202a1b49cd44104a9d40aee8f988281a8aac94a497b5bc7337c77cc7ddbab16f23"
),
serialized_tx=unhexlify("70f305000000000000000000ffffffff6a473044022078a5c388838796562eb9dad176b00e6d9425bc360083f633a14948685ca8a5ce02202a1b49cd44104a9d40aee8f988281a8aac94a497b5bc7337c77cc7ddbab16f23012103fc15aa2f684457332c0ef1fe44d908ab97208102a1792caa13bcc5e886c4b321"),
),
),
]
seed = bip39.seed(
"alcohol woman abuse must during monitor noble actual mixed trade anger aisle",
"",
)
ns = get_schemas_for_coin(coin_decred)
keychain = Keychain(seed, coin_decred.curve_name, ns)
approver = BasicApprover(tx, coin_decred)
signer = decred.Decred(tx, keychain, coin_decred, approver).signer()
for request, response in chunks(messages, 2):
res = signer.send(request)
if isinstance(res, tuple):
_, res = res
self.assertEqual(res, response)
with self.assertRaises(StopIteration):
signer.send(None)
def test_purchase_ticket(self):
inp1 = TxInput(
address_n=[44 | 0x80000000, 42 | 0x80000000, 0 | 0x80000000, 0, 0],
prev_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
prev_index=0,
amount=390000,
multisig=None,
sequence=0xFFFF_FFFF,
)
out1 = TxOutput(
address="DsaHnKa418BeeQmyhpQEGG4cxGAPrneydfv",
amount=390000 - 10000,
script_type=OutputScriptType.PAYTOADDRESS,
multisig=None,
)
out2 = TxOutput(
address_n=[44 | 0x80000000, 42 | 0x80000000, 0 | 0x80000000, 0, 0],
amount=390000,
script_type=OutputScriptType.PAYTOADDRESS,
multisig=None,
)
out3 = TxOutput(
address="DsQxuVRvS4eaJ42dhQEsCXauMWjvopWgrVg",
amount=0,
script_type=OutputScriptType.PAYTOADDRESS,
multisig=None,
)
tx = SignTx(
coin_name="Decred", version=1, lock_time=0, inputs_count=1, outputs_count=3, decred_staking_ticket=True
)
messages = [
None,
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("0100000001")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("4ac247307a054c37525169a78d690a7a7f87d709bef7d722aae85584f59c8fdf0000000000ffffffff03")
),
),
TxAckOutput(tx=TxAckOutputWrapper(output=out1)),
helpers.UiConfirmDecredSSTXSubmission(out1, coin_decred, AmountUnit.BITCOIN),
True,
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(request_index=1, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("60cc05000000000000001aba76a914664b0cd46741a695a38f8ed37db2a20327471beb88ac")
),
),
TxAckOutput(tx=TxAckOutputWrapper(output=out2)),
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(request_index=2, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("00000000000000000000206a1e762e46655536d93ad13f88a49bde9a2df45fe62e70f30500000000000058")
),
),
TxAckOutput(tx=TxAckOutputWrapper(output=out3)),
helpers.UiConfirmTotal(380000 + 10000, 10000, coin_decred, AmountUnit.BITCOIN),
True,
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("000000000000000000001abd76a914000000000000000000000000000000000000000088ac0000000000000000")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXMETA,
details=TxRequestDetailsType(
request_index=None,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevMeta(tx=ptx1),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(
request_index=0,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevInput(tx=TxAckPrevInputWrapper(input=pinp1)),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(
request_index=1,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevInput(tx=TxAckPrevInputWrapper(input=pinp2)),
TxRequest(
request_type=TXOUTPUT,
details=TxRequestDetailsType(
request_index=0,
tx_hash=unhexlify(
"df8f9cf58455e8aa22d7f7be09d7877f7a0a698da7695152374c057a3047c24a"
),
),
serialized=EMPTY_SERIALIZED,
),
TxAckPrevOutput(tx=TxAckPrevOutputWrapper(output=pout1)),
TxRequest(
request_type=TXINPUT,
details=TxRequestDetailsType(request_index=0, tx_hash=None),
serialized=TxRequestSerializedType(
serialized_tx=unhexlify("01")
),
),
TxAckInput(tx=TxAckInputWrapper(input=inp1)),
TxRequest(
request_type=TXFINISHED,
details=TxRequestDetailsType(),
serialized=TxRequestSerializedType(
signature_index=0,
signature=unhexlify(
"3045022100d2a6baadc88ea67ec94a1f6dca70882e647e9af68d24e1bc72f9c27359e5e6ff02207b8a939e7cf82e79e2947e8fe59a14c11ee0b3a9cd1ff084d9bd54e23291b6be"
),
serialized_tx=unhexlify("70f305000000000000000000ffffffff6b483045022100d2a6baadc88ea67ec94a1f6dca70882e647e9af68d24e1bc72f9c27359e5e6ff02207b8a939e7cf82e79e2947e8fe59a14c11ee0b3a9cd1ff084d9bd54e23291b6be012103fc15aa2f684457332c0ef1fe44d908ab97208102a1792caa13bcc5e886c4b321")
),
),
]
seed = bip39.seed(
"alcohol woman abuse must during monitor noble actual mixed trade anger aisle",
"",
)
ns = get_schemas_for_coin(coin_decred)
keychain = Keychain(seed, coin_decred.curve_name, ns)
approver = BasicApprover(tx, coin_decred)
signer = decred.Decred(tx, keychain, coin_decred, approver).signer()
for request, response in chunks(messages, 2):
res = signer.send(request)
if isinstance(res, tuple):
_, res = res
self.assertEqual(res, response)
with self.assertRaises(StopIteration):
signer.send(None)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,10 @@
# Automatically generated by pb2py
# fmt: off
if __debug__:
try:
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
SSGen: Literal[0] = 0
SSRTX: Literal[1] = 1

View File

@ -28,6 +28,7 @@ class SignTx(p.MessageType):
timestamp: int = None,
branch_id: int = None,
amount_unit: EnumTypeAmountUnit = 0,
decred_staking_ticket: bool = False,
) -> None:
self.outputs_count = outputs_count
self.inputs_count = inputs_count
@ -40,6 +41,7 @@ class SignTx(p.MessageType):
self.timestamp = timestamp
self.branch_id = branch_id
self.amount_unit = amount_unit
self.decred_staking_ticket = decred_staking_ticket
@classmethod
def get_fields(cls) -> Dict:
@ -55,4 +57,5 @@ class SignTx(p.MessageType):
9: ('timestamp', p.UVarintType, None),
10: ('branch_id', p.UVarintType, None),
11: ('amount_unit', p.EnumType("AmountUnit", (0, 1, 2, 3)), 0), # default=BITCOIN
12: ('decred_staking_ticket', p.BoolType, False), # default=false
}

View File

@ -9,6 +9,7 @@ if __debug__:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
EnumTypeInputScriptType = Literal[0, 1, 2, 3, 4]
EnumTypeDecredStakingSpendType = Literal[0, 1]
except ImportError:
pass
@ -32,6 +33,7 @@ class TxInput(p.MessageType):
commitment_data: bytes = None,
orig_hash: bytes = None,
orig_index: int = None,
decred_staking_spend: EnumTypeDecredStakingSpendType = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
@ -47,6 +49,7 @@ class TxInput(p.MessageType):
self.commitment_data = commitment_data
self.orig_hash = orig_hash
self.orig_index = orig_index
self.decred_staking_spend = decred_staking_spend
@classmethod
def get_fields(cls) -> Dict:
@ -65,4 +68,5 @@ class TxInput(p.MessageType):
15: ('commitment_data', p.BytesType, None),
16: ('orig_hash', p.BytesType, None),
17: ('orig_index', p.UVarintType, None),
18: ('decred_staking_spend', p.EnumType("DecredStakingSpendType", (0, 1)), None),
}

View File

@ -9,6 +9,7 @@ if __debug__:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
EnumTypeInputScriptType = Literal[0, 1, 2, 3, 4]
EnumTypeDecredStakingSpendType = Literal[0, 1]
except ImportError:
pass
@ -32,6 +33,7 @@ class TxInputType(p.MessageType):
commitment_data: bytes = None,
orig_hash: bytes = None,
orig_index: int = None,
decred_staking_spend: EnumTypeDecredStakingSpendType = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
@ -47,6 +49,7 @@ class TxInputType(p.MessageType):
self.commitment_data = commitment_data
self.orig_hash = orig_hash
self.orig_index = orig_index
self.decred_staking_spend = decred_staking_spend
@classmethod
def get_fields(cls) -> Dict:
@ -65,4 +68,5 @@ class TxInputType(p.MessageType):
15: ('commitment_data', p.BytesType, None),
16: ('orig_hash', p.BytesType, None),
17: ('orig_index', p.UVarintType, None),
18: ('decred_staking_spend', p.EnumType("DecredStakingSpendType", (0, 1)), None),
}

View File

@ -314,6 +314,7 @@ from . import CardanoCertificateType
from . import CardanoPoolRelayType
from . import DebugLinkShowTextStyle
from . import DebugSwipeDirection
from . import DecredStakingSpendType
from . import FailureType
from . import InputScriptType
from . import LiskTransactionType

View File

@ -44,6 +44,12 @@ TXHASH_3f7c39 = bytes.fromhex(
TXHASH_16da18 = bytes.fromhex(
"16da185052740d85a630e79c140558215b64e26c500212b90e16b55d13ca06a8"
)
TXHASH_8b6890 = bytes.fromhex(
"8b6890c10a3764fe6f378bc5b7e438148df176e9be1dde704ce866361149e254"
)
TXHASH_1f00fc = bytes.fromhex(
"1f00fc54530d7c4877f5032e91b6c507f6a1531861dede2ab134e5c0b5dfe8c8"
)
@pytest.mark.altcoin
@ -92,6 +98,126 @@ class TestMsgSigntxDecred:
== "0100000001edd579e9462ee0e80127a817e0500d4f942a4cf8f2d6530e0c0a9ab3f04862e10100000000ffffffff01802b530b0000000000001976a914819d291a2f7fbf770e784bfd78b5ce92c58e95ea88ac00000000000000000100c2eb0b0000000000000000ffffffff6a473044022009e394c7dec76ab6988270b467839b1462ad781556bce37383b76e026418ce6302204f7f6ef535d2986b095d7c96232a0990a0b9ce3004894b39c167bb18e5833ac30121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
)
@pytest.mark.skip_t1
def test_purchase_ticket_decred(self, client):
inp1 = proto.TxInputType(
address_n=parse_path("m/44'/1'/0'/0/0"),
prev_hash=TXHASH_e16248,
prev_index=1,
amount=200000000,
decred_tree=0,
script_type=proto.InputScriptType.SPENDADDRESS,
)
out1 = proto.TxOutputType(
address="TscqTv1he8MZrV321SfRghw7LFBCJDKB3oz",
script_type=proto.OutputScriptType.PAYTOADDRESS,
amount=199900000,
)
out2 = proto.TxOutputType(
address_n=parse_path("m/44'/1'/0'/0/0"),
amount=200000000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
out3 = proto.TxOutputType(
address="TsR28UZRprhgQQhzWns2M6cAwchrNVvbYq2",
amount=0,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
with client:
client.set_expected_responses(
[
request_input(0),
request_output(0),
proto.ButtonRequest(code=B.ConfirmOutput),
request_output(1),
request_output(2),
proto.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_e16248),
request_input(0, TXHASH_e16248),
request_output(0, TXHASH_e16248),
request_output(1, TXHASH_e16248),
request_input(0),
request_finished(),
]
)
_, serialized_tx = btc.sign_tx(
client,
"Decred Testnet",
[inp1],
[out1, out2, out3],
prev_txes=TX_API,
decred_staking_ticket=True,
)
assert (
serialized_tx.hex()
== "0100000001edd579e9462ee0e80127a817e0500d4f942a4cf8f2d6530e0c0a9ab3f04862e10100000000ffffffff03603bea0b0000000000001aba76a914819d291a2f7fbf770e784bfd78b5ce92c58e95ea88ac00000000000000000000206a1edc1a98d791735eb9a8715a2a219c23680edcedad00c2eb0b000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000000000000100c2eb0b0000000000000000ffffffff6b4830450221008ced5411a6d92b761bdd8b9f7fbc5bfae3c31f9369050c218977f4540ab1ec9602206e89c821878ebfd959d1c4a63100eec5b1154c8d9508c039bb78e333498a73b40121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
)
@pytest.mark.skip_t1
def test_spend_from_stake_generation_and_revocation_decred(self, client):
inp1 = proto.TxInputType(
address_n=parse_path("m/44'/1'/0'/0/0"),
prev_hash=TXHASH_8b6890,
prev_index=2,
amount=200000000,
script_type=proto.InputScriptType.SPENDADDRESS,
decred_staking_spend=proto.DecredStakingSpendType.SSGen,
decred_tree=1,
)
inp2 = proto.TxInputType(
address_n=parse_path("m/44'/1'/0'/0/0"),
prev_hash=TXHASH_1f00fc,
prev_index=0,
amount=200000000,
script_type=proto.InputScriptType.SPENDADDRESS,
decred_staking_spend=proto.DecredStakingSpendType.SSRTX,
decred_tree=1,
)
out1 = proto.TxOutputType(
address="TscqTv1he8MZrV321SfRghw7LFBCJDKB3oz",
amount=399900000,
script_type=proto.OutputScriptType.PAYTOADDRESS,
)
with client:
client.set_expected_responses(
[
request_input(0),
request_input(1),
request_output(0),
proto.ButtonRequest(code=B.ConfirmOutput),
proto.ButtonRequest(code=B.SignTx),
request_input(0),
request_meta(TXHASH_8b6890),
request_input(0, TXHASH_8b6890),
request_input(1, TXHASH_8b6890),
request_output(0, TXHASH_8b6890),
request_output(1, TXHASH_8b6890),
request_output(2, TXHASH_8b6890),
request_input(1),
request_meta(TXHASH_1f00fc),
request_input(0, TXHASH_1f00fc),
request_output(0, TXHASH_1f00fc),
request_input(0),
request_input(1),
request_finished(),
]
)
_, serialized_tx = btc.sign_tx(
client, "Decred Testnet", [inp1, inp2], [out1], prev_txes=TX_API
)
assert (
serialized_tx.hex()
== "010000000254e249113666e84c70de1dbee976f18d1438e4b7c58b376ffe64370ac190688b0200000001ffffffffc8e8dfb5c0e534b12adede611853a1f607c5b6912e03f577487c0d5354fc001f0000000001ffffffff0160fdd5170000000000001976a914819d291a2f7fbf770e784bfd78b5ce92c58e95ea88ac00000000000000000200c2eb0b0000000000000000ffffffff6b483045022100bdcb877c97d72db74eca06fefa21a7f7b00afcd5d916fce2155ed7df1ca5546102201e1f9efd7d652b449474c2c70171bfc4535544927bed62021f7334447d1ea4740121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd000c2eb0b0000000000000000ffffffff6a473044022030c5743c442bd696d19dcf73d54e95526e726de965c2e2b4b9fd70248eaae21d02201305a3bcc2bb0e33122277763990e3b48f317d61264a68d190fb8acfc004cc640121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
)
def test_send_decred_change(self, client):
inp1 = proto.TxInputType(
# TscqTv1he8MZrV321SfRghw7LFBCJDKB3oz

View File

@ -0,0 +1,22 @@
{
"bin_outputs": [
{
"amount": 200000000,
"decred_script_version": 0,
"script_pubkey": "bc76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac",
"decred_tree": 1
}
],
"expiry": 0,
"inputs": [
{
"decred_tree": 1,
"prev_hash": "569e3a8fe2354abd9ff309dc5f469dd091cbd1de6f394cc212be73fb52bb6fed",
"prev_index": 0,
"script_sig": "483045022100da17695ed60f17cc9e8a40d1199654a326e541ed77130983d5c281a21e4d743b022058420d3d905d542f290fd793176e02186e31ba8f294b52d922c5aad5ffd3d2c90121026233f32eb24b0571c703f9a89a7b581921f0379a8bca8f63fa23c033bafa0b48",
"sequence": 4294967295
}
],
"lock_time": 0,
"version": 1
}

View File

@ -0,0 +1,38 @@
{
"bin_outputs": [
{
"amount": 0,
"decred_script_version": 0,
"script_pubkey": "6a24f1dc4222fe139509d84614643c7b24c469c6738b19e8094ba6e69fe315000000cd770800"
},
{
"amount": 0,
"decred_script_version": 0,
"script_pubkey": "6a06010009000000"
},
{
"amount": 200000000,
"decred_script_version": 0,
"script_pubkey": "bb76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac"
}
],
"expiry": 0,
"inputs": [
{
"decred_tree": 1,
"prev_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"prev_index": 0,
"script_sig": "0000",
"sequence": 4294967295
},
{
"decred_tree": 1,
"prev_hash": "7f87fb8c64578ec18658e6f5860ed6187d161f284da4bacc4d2b57e5fe43567b",
"prev_index": 0,
"script_sig": "473044022006b7b98c8a0d7670264552321507fc977812a6f8fe4326f224b07cb8507dc11b0220573f7c0c9c37c1ffd0a952a72b963d47cc6884b82f7eeca7fd47161652dc9aec01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"sequence": 4294967295
}
],
"lock_time": 0,
"version": 1
}

View File

@ -460,8 +460,10 @@
"test_msg_signtx_dash.py-test_send_dash": "291f1a3ace22877641494a1a470a1a4a8dab6e363fc4402dadaeb52c1288c72b",
"test_msg_signtx_dash.py-test_send_dash_dip2_input": "cf7fc7e6fe3a9e4063e743da6fc44c27dac013917bc00cfc63d13a183c091d91",
"test_msg_signtx_decred.py-test_decred_multisig_change": "9a7e9e1adcb0ba6770e3965df8324f2b7bc46d6bcd866db9289e8e1d62ef486e",
"test_msg_signtx_decred.py-test_purchase_ticket_decred": "4d39aac92f68bd28a19077eb3447cf2fda79be5ec88940009df7c34a6aedfe25",
"test_msg_signtx_decred.py-test_send_decred": "862f30f42b35d29e0cc25205621eef2c20ce40816da4fe171725905d05867194",
"test_msg_signtx_decred.py-test_send_decred_change": "6b44d98d39753a65e4aee69185d7dcecaafd405403f47835d0706ce52083b2ca",
"test_msg_signtx_decred.py-test_spend_from_stake_generation_and_revocation_decred": "338e788a0f042dbc378c8ad7a2c7dfdeca9341f4fe5bfe0eda6e838f776260fc",
"test_msg_signtx_external.py::test_p2pkh_presigned": "075b9a41516faba90ddd8a6ed894ed4b60de1c11dd96400a57d37e64adbc73c4",
"test_msg_signtx_external.py::test_p2pkh_with_proof": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_signtx_external.py::test_p2wpkh_in_p2sh_presigned": "f88ace4e725d81fbe79bc243d427f4d2284c478cc605b32c17336226bacb7600",