mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-19 21:08:07 +00:00
decred: Add input_derive_script.
Decred shares code with bitcoin and a recent change broke signing stake based inputs. Add decred specific script construction and fix tests.
This commit is contained in:
parent
d1da8aa9e3
commit
d2a2ac2178
1
core/.changelog.d/4161.fixed
Normal file
1
core/.changelog.d/4161.fixed
Normal file
@ -0,0 +1 @@
|
|||||||
|
[T2T1] Fix spending decred stake outputs.
|
@ -1,8 +1,10 @@
|
|||||||
|
from micropython import const
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from trezor import utils
|
from trezor import utils
|
||||||
from trezor.crypto import base58
|
from trezor.crypto import base58
|
||||||
from trezor.crypto.base58 import blake256d_32
|
from trezor.crypto.base58 import blake256d_32
|
||||||
|
from trezor.enums import DecredStakingSpendType
|
||||||
from trezor.wire import DataError
|
from trezor.wire import DataError
|
||||||
|
|
||||||
from . import scripts
|
from . import scripts
|
||||||
@ -13,6 +15,14 @@ from .scripts import ( # noqa: F401
|
|||||||
)
|
)
|
||||||
from .writers import write_compact_size
|
from .writers import write_compact_size
|
||||||
|
|
||||||
|
# These are decred specific opcodes related to staking.
|
||||||
|
_OP_SSTX = const(0xBA)
|
||||||
|
_OP_SSGEN = const(0xBB)
|
||||||
|
_OP_SSRTX = const(0xBC)
|
||||||
|
_OP_SSTXCHANGE = const(0xBD)
|
||||||
|
|
||||||
|
_STAKE_TREE = const(1)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.enums import InputScriptType
|
from trezor.enums import InputScriptType
|
||||||
from trezor.messages import MultisigRedeemScriptType
|
from trezor.messages import MultisigRedeemScriptType
|
||||||
@ -98,7 +108,7 @@ def output_script_sstxsubmissionpkh(addr: str) -> bytearray:
|
|||||||
raise DataError("Invalid address")
|
raise DataError("Invalid address")
|
||||||
|
|
||||||
w = utils.empty_bytearray(26)
|
w = utils.empty_bytearray(26)
|
||||||
w.append(0xBA) # OP_SSTX
|
w.append(_OP_SSTX)
|
||||||
scripts.write_output_script_p2pkh(w, raw_address[2:])
|
scripts.write_output_script_p2pkh(w, raw_address[2:])
|
||||||
return w
|
return w
|
||||||
|
|
||||||
@ -111,7 +121,7 @@ def output_script_sstxchange(addr: str) -> bytearray:
|
|||||||
raise DataError("Invalid address")
|
raise DataError("Invalid address")
|
||||||
|
|
||||||
w = utils.empty_bytearray(26)
|
w = utils.empty_bytearray(26)
|
||||||
w.append(0xBD) # OP_SSTXCHANGE
|
w.append(_OP_SSTXCHANGE)
|
||||||
scripts.write_output_script_p2pkh(w, raw_address[2:])
|
scripts.write_output_script_p2pkh(w, raw_address[2:])
|
||||||
return w
|
return w
|
||||||
|
|
||||||
@ -120,7 +130,7 @@ def output_script_sstxchange(addr: str) -> bytearray:
|
|||||||
def write_output_script_ssrtx_prefixed(w: Writer, pkh: bytes) -> None:
|
def write_output_script_ssrtx_prefixed(w: Writer, pkh: bytes) -> None:
|
||||||
utils.ensure(len(pkh) == 20)
|
utils.ensure(len(pkh) == 20)
|
||||||
write_compact_size(w, 26)
|
write_compact_size(w, 26)
|
||||||
w.append(0xBC) # OP_SSRTX
|
w.append(_OP_SSRTX)
|
||||||
scripts.write_output_script_p2pkh(w, pkh)
|
scripts.write_output_script_p2pkh(w, pkh)
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +138,7 @@ def write_output_script_ssrtx_prefixed(w: Writer, pkh: bytes) -> None:
|
|||||||
def write_output_script_ssgen_prefixed(w: Writer, pkh: bytes) -> None:
|
def write_output_script_ssgen_prefixed(w: Writer, pkh: bytes) -> None:
|
||||||
utils.ensure(len(pkh) == 20)
|
utils.ensure(len(pkh) == 20)
|
||||||
write_compact_size(w, 26)
|
write_compact_size(w, 26)
|
||||||
w.append(0xBB) # OP_SSGEN
|
w.append(_OP_SSGEN)
|
||||||
scripts.write_output_script_p2pkh(w, pkh)
|
scripts.write_output_script_p2pkh(w, pkh)
|
||||||
|
|
||||||
|
|
||||||
@ -141,3 +151,64 @@ def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes:
|
|||||||
write_uint64_le(w, amount)
|
write_uint64_le(w, amount)
|
||||||
write_bytes_fixed(w, b"\x00\x58", 2) # standard fee limits
|
write_bytes_fixed(w, b"\x00\x58", 2) # standard fee limits
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def output_script_p2pkh(pubkeyhash: bytes) -> bytearray:
|
||||||
|
s = utils.empty_bytearray(25)
|
||||||
|
scripts.write_output_script_p2pkh(s, pubkeyhash)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def output_script_p2sh(scripthash: bytes) -> bytearray:
|
||||||
|
# A9 14 <scripthash> 87
|
||||||
|
utils.ensure(len(scripthash) == 20)
|
||||||
|
s = bytearray(23)
|
||||||
|
s[0] = 0xA9 # OP_HASH_160
|
||||||
|
s[1] = 0x14 # pushing 20 bytes
|
||||||
|
s[2:22] = scripthash
|
||||||
|
s[22] = 0x87 # OP_EQUAL
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def output_derive_script(
|
||||||
|
tree: int | None, stakeType: int | None, addr: str, coin: CoinInfo
|
||||||
|
) -> bytes:
|
||||||
|
from trezor.crypto import base58
|
||||||
|
|
||||||
|
from apps.common import address_type
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_address = base58.decode_check(addr, blake256d_32)
|
||||||
|
except ValueError:
|
||||||
|
raise DataError("Invalid address")
|
||||||
|
|
||||||
|
isStakeOutput = False
|
||||||
|
if tree is not None:
|
||||||
|
if stakeType is not None:
|
||||||
|
if tree == _STAKE_TREE:
|
||||||
|
isStakeOutput = True
|
||||||
|
|
||||||
|
if isStakeOutput:
|
||||||
|
assert stakeType is not None
|
||||||
|
if stakeType == DecredStakingSpendType.SSGen:
|
||||||
|
script = utils.empty_bytearray(26)
|
||||||
|
script.append(_OP_SSGEN)
|
||||||
|
scripts.write_output_script_p2pkh(script, raw_address[2:])
|
||||||
|
return script
|
||||||
|
elif stakeType == DecredStakingSpendType.SSRTX:
|
||||||
|
script = utils.empty_bytearray(26)
|
||||||
|
script.append(_OP_SSRTX)
|
||||||
|
scripts.write_output_script_p2pkh(script, raw_address[2:])
|
||||||
|
return script
|
||||||
|
|
||||||
|
elif address_type.check(coin.address_type, raw_address):
|
||||||
|
# p2pkh
|
||||||
|
pubkeyhash = address_type.strip(coin.address_type, raw_address)
|
||||||
|
script = output_script_p2pkh(pubkeyhash)
|
||||||
|
return script
|
||||||
|
elif address_type.check(coin.address_type_p2sh, raw_address):
|
||||||
|
scripthash = address_type.strip(coin.address_type_p2sh, raw_address)
|
||||||
|
script = output_script_p2sh(scripthash)
|
||||||
|
return script
|
||||||
|
|
||||||
|
raise DataError("Invalid address type")
|
||||||
|
@ -9,8 +9,8 @@ from trezor.wire import DataError, ProcessError
|
|||||||
from apps.bitcoin.sign_tx.tx_weight import TxWeightCalculator
|
from apps.bitcoin.sign_tx.tx_weight import TxWeightCalculator
|
||||||
from apps.common.writers import write_compact_size
|
from apps.common.writers import write_compact_size
|
||||||
|
|
||||||
from .. import scripts_decred, writers
|
from .. import addresses, scripts_decred, writers
|
||||||
from ..common import ecdsa_hash_pubkey
|
from ..common import ecdsa_hash_pubkey, input_is_external
|
||||||
from ..writers import write_uint32
|
from ..writers import write_uint32
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from .approvers import BasicApprover
|
from .approvers import BasicApprover
|
||||||
@ -423,3 +423,21 @@ class Decred(Bitcoin):
|
|||||||
pubkey,
|
pubkey,
|
||||||
signature,
|
signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# scriptPubKey derivation
|
||||||
|
# ===
|
||||||
|
|
||||||
|
def input_derive_script(
|
||||||
|
self, txi: TxInput, node: bip32.HDNode | None = None
|
||||||
|
) -> bytes:
|
||||||
|
if input_is_external(txi):
|
||||||
|
assert txi.script_pubkey is not None # checked in _sanitize_tx_input
|
||||||
|
return txi.script_pubkey
|
||||||
|
|
||||||
|
if node is None:
|
||||||
|
node = self.keychain.derive(txi.address_n)
|
||||||
|
|
||||||
|
address = addresses.get_address(txi.script_type, self.coin, node, txi.multisig)
|
||||||
|
return scripts_decred.output_derive_script(
|
||||||
|
txi.decred_tree, txi.decred_staking_spend, address, self.coin
|
||||||
|
)
|
||||||
|
@ -43,11 +43,11 @@ FAKE_TXHASH_9ac7d2 = bytes.fromhex(
|
|||||||
FAKE_TXHASH_48f5b8 = bytes.fromhex(
|
FAKE_TXHASH_48f5b8 = bytes.fromhex(
|
||||||
"48f5b85f8b1cf796d0d07388ced491f154e2d26b0615529d2d6ba9c170542df3"
|
"48f5b85f8b1cf796d0d07388ced491f154e2d26b0615529d2d6ba9c170542df3"
|
||||||
)
|
)
|
||||||
FAKE_TXHASH_f8e2f2 = bytes.fromhex(
|
FAKE_TXHASH_8b6890 = bytes.fromhex(
|
||||||
"f8e2f2b4eab772f6e3743cba92db341f64b84d9c16ae375c7690fbf0bf02fc7b"
|
"8b6890c10a3764fe6f378bc5b7e438148df176e9be1dde704ce866361149e254"
|
||||||
)
|
)
|
||||||
FAKE_TXHASH_51bc9c = bytes.fromhex(
|
FAKE_TXHASH_1f00fc = bytes.fromhex(
|
||||||
"51bc9c71f10a81eef3caedb5333062eb4b1f70998adf02916fe98fdc04c572e8"
|
"1f00fc54530d7c4877f5032e91b6c507f6a1531861dede2ab134e5c0b5dfe8c8"
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark = [
|
pytestmark = [
|
||||||
@ -174,7 +174,7 @@ def test_spend_from_stake_generation_and_revocation_decred(client: Client):
|
|||||||
|
|
||||||
inp1 = messages.TxInputType(
|
inp1 = messages.TxInputType(
|
||||||
address_n=parse_path("m/44h/1h/0h/0/0"),
|
address_n=parse_path("m/44h/1h/0h/0/0"),
|
||||||
prev_hash=FAKE_TXHASH_f8e2f2,
|
prev_hash=FAKE_TXHASH_8b6890,
|
||||||
prev_index=2,
|
prev_index=2,
|
||||||
amount=200_000_000,
|
amount=200_000_000,
|
||||||
script_type=messages.InputScriptType.SPENDADDRESS,
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
||||||
@ -184,7 +184,7 @@ def test_spend_from_stake_generation_and_revocation_decred(client: Client):
|
|||||||
|
|
||||||
inp2 = messages.TxInputType(
|
inp2 = messages.TxInputType(
|
||||||
address_n=parse_path("m/44h/1h/0h/0/0"),
|
address_n=parse_path("m/44h/1h/0h/0/0"),
|
||||||
prev_hash=FAKE_TXHASH_51bc9c,
|
prev_hash=FAKE_TXHASH_1f00fc,
|
||||||
prev_index=0,
|
prev_index=0,
|
||||||
amount=200_000_000,
|
amount=200_000_000,
|
||||||
script_type=messages.InputScriptType.SPENDADDRESS,
|
script_type=messages.InputScriptType.SPENDADDRESS,
|
||||||
@ -208,16 +208,16 @@ def test_spend_from_stake_generation_and_revocation_decred(client: Client):
|
|||||||
(is_core(client), messages.ButtonRequest(code=B.ConfirmOutput)),
|
(is_core(client), messages.ButtonRequest(code=B.ConfirmOutput)),
|
||||||
messages.ButtonRequest(code=B.SignTx),
|
messages.ButtonRequest(code=B.SignTx),
|
||||||
request_input(0),
|
request_input(0),
|
||||||
request_meta(FAKE_TXHASH_f8e2f2),
|
request_meta(FAKE_TXHASH_8b6890),
|
||||||
request_input(0, FAKE_TXHASH_f8e2f2),
|
request_input(0, FAKE_TXHASH_8b6890),
|
||||||
request_input(1, FAKE_TXHASH_f8e2f2),
|
request_input(1, FAKE_TXHASH_8b6890),
|
||||||
request_output(0, FAKE_TXHASH_f8e2f2),
|
request_output(0, FAKE_TXHASH_8b6890),
|
||||||
request_output(1, FAKE_TXHASH_f8e2f2),
|
request_output(1, FAKE_TXHASH_8b6890),
|
||||||
request_output(2, FAKE_TXHASH_f8e2f2),
|
request_output(2, FAKE_TXHASH_8b6890),
|
||||||
request_input(1),
|
request_input(1),
|
||||||
request_meta(FAKE_TXHASH_51bc9c),
|
request_meta(FAKE_TXHASH_1f00fc),
|
||||||
request_input(0, FAKE_TXHASH_51bc9c),
|
request_input(0, FAKE_TXHASH_1f00fc),
|
||||||
request_output(0, FAKE_TXHASH_51bc9c),
|
request_output(0, FAKE_TXHASH_1f00fc),
|
||||||
request_input(0),
|
request_input(0),
|
||||||
request_input(1),
|
request_input(1),
|
||||||
request_finished(),
|
request_finished(),
|
||||||
@ -229,7 +229,7 @@ def test_spend_from_stake_generation_and_revocation_decred(client: Client):
|
|||||||
|
|
||||||
assert (
|
assert (
|
||||||
serialized_tx.hex()
|
serialized_tx.hex()
|
||||||
== "01000000027bfc02bff0fb90765c37ae169c4db8641f34db92ba3c74e3f672b7eab4f2e2f80200000001ffffffffe872c504dc8fe96f9102df8a99701f4beb623033b5edcaf3ee810af1719cbc510000000001ffffffff0160fdd5170000000000001976a914819d291a2f7fbf770e784bfd78b5ce92c58e95ea88ac00000000000000000200c2eb0b0000000000000000ffffffff6b483045022100f74f652a073bdaf2197ede47b4df0d90609bbfd0dc8a94199d36ebb1429de09b022040366292a8812135ec7572a94eb6e969fa1fa97a52c03f08a337f20bc4fb71de0121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd000c2eb0b0000000000000000ffffffff6b483045022100ca385c05a008239c038e107989bbc30eec1ecd5a66e4973265eb21df034c77a9022070c3dceb24b39cb6e9f8c973572b955b37a4754e9caa704cdd37113c46e2b2970121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
|
== "010000000254e249113666e84c70de1dbee976f18d1438e4b7c58b376ffe64370ac190688b0200000001ffffffffc8e8dfb5c0e534b12adede611853a1f607c5b6912e03f577487c0d5354fc001f0000000001ffffffff0160fdd5170000000000001976a914819d291a2f7fbf770e784bfd78b5ce92c58e95ea88ac00000000000000000200c2eb0b0000000000000000ffffffff6b483045022100bdcb877c97d72db74eca06fefa21a7f7b00afcd5d916fce2155ed7df1ca5546102201e1f9efd7d652b449474c2c70171bfc4535544927bed62021f7334447d1ea4740121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd000c2eb0b0000000000000000ffffffff6a473044022030c5743c442bd696d19dcf73d54e95526e726de965c2e2b4b9fd70248eaae21d02201305a3bcc2bb0e33122277763990e3b48f317d61264a68d190fb8acfc004cc640121030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
"amount": 200000000,
|
"amount": 200000000,
|
||||||
"decred_script_version": 0,
|
"decred_script_version": 0,
|
||||||
"script_pubkey": "76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac",
|
"script_pubkey": "bc76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac",
|
||||||
"decred_tree": 1
|
"decred_tree": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
"amount": 200000000,
|
"amount": 200000000,
|
||||||
"decred_script_version": 0,
|
"decred_script_version": 0,
|
||||||
"script_pubkey": "76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac"
|
"script_pubkey": "bb76a914dc1a98d791735eb9a8715a2a219c23680edcedad88ac"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"expiry": 0,
|
"expiry": 0,
|
Loading…
Reference in New Issue
Block a user