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:
parent
6c11bc60d7
commit
e3ea32a986
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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)
|
||||
|
78
core/src/apps/bitcoin/scripts_decred.py
Normal file
78
core/src/apps/bitcoin/scripts_decred.py
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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(
|
||||
|
10
core/src/trezor/messages/DecredStakingSpendType.py
Normal file
10
core/src/trezor/messages/DecredStakingSpendType.py
Normal 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
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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,
|
||||
|
371
core/tests/test_apps.bitcoin.signtx_decred.py
Normal file
371
core/tests/test_apps.bitcoin.signtx_decred.py
Normal 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()
|
10
python/src/trezorlib/messages/DecredStakingSpendType.py
Normal file
10
python/src/trezorlib/messages/DecredStakingSpendType.py
Normal 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
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user