1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-05-30 04:38:44 +00:00

feat(tests): Support non-bitcoinlike coins in make_payment_request.

This commit is contained in:
Andrew Kozlik 2025-04-10 14:02:28 +02:00
parent 5dd2405a86
commit 6aecf5e20e
6 changed files with 97 additions and 61 deletions

View File

@ -106,6 +106,7 @@ Q(apps.common.keychain)
Q(apps.common.passphrase) Q(apps.common.passphrase)
Q(apps.common.paths) Q(apps.common.paths)
Q(apps.common.payment_request) Q(apps.common.payment_request)
Q(payment_request)
Q(apps.common.readers) Q(apps.common.readers)
Q(apps.common.request_pin) Q(apps.common.request_pin)
Q(apps.common.safety_checks) Q(apps.common.safety_checks)

View File

@ -27,7 +27,7 @@ from trezorlib.tools import parse_path
from .. import common from .. import common
from .. import translations as TR from .. import translations as TR
from ..device_tests.bitcoin.payment_req import make_coinjoin_request from ..device_tests.bitcoin.coinjoin_req import make_coinjoin_request
from ..tx_cache import TxCache from ..tx_cache import TxCache
from . import recovery from . import recovery
from .common import go_next, unlock_gesture from .common import go_next, unlock_gesture

View File

@ -0,0 +1,26 @@
from trezorlib import messages
def make_coinjoin_request(
coordinator_name,
inputs,
input_script_pubkeys,
outputs,
output_script_pubkeys,
no_fee_indices,
fee_rate=500_000, # 0.5 %
no_fee_threshold=1_000_000,
min_registrable_amount=5_000,
):
# Process inputs.
for i, txi in enumerate(inputs):
# Set no_fee flag in coinjoin_flags.
txi.coinjoin_flags |= (i in no_fee_indices) << 1
return messages.CoinJoinRequest(
fee_rate=fee_rate,
no_fee_threshold=no_fee_threshold,
min_registrable_amount=min_registrable_amount,
mask_public_key=b"",
signature=b"",
)

View File

@ -25,7 +25,7 @@ from trezorlib.tools import parse_path
from ...common import is_core from ...common import is_core
from ...tx_cache import TxCache from ...tx_cache import TxCache
from .payment_req import make_coinjoin_request from .coinjoin_req import make_coinjoin_request
from .signtx import ( from .signtx import (
assert_tx_matches, assert_tx_matches,
request_finished, request_finished,

View File

@ -24,7 +24,7 @@ from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path from trezorlib.tools import parse_path
from ...input_flows import InputFlowPaymentRequestDetails from ...input_flows import InputFlowPaymentRequestDetails
from .payment_req import CoinPurchaseMemo, RefundMemo, TextMemo, make_payment_request from ..payment_req import CoinPurchaseMemo, RefundMemo, TextMemo, make_payment_request
from .signtx import forge_prevtx from .signtx import forge_prevtx
# address at seed "all all all..." path m/84h/1h/0h/0/0 # address at seed "all all all..." path m/84h/1h/0h/0/0
@ -146,13 +146,24 @@ def test_payment_request(client: Client, payment_request_params):
for i, params in enumerate(payment_request_params): for i, params in enumerate(payment_request_params):
request_outputs = [] request_outputs = []
for txo_index in params.txo_indices: for txo_index in params.txo_indices:
outputs[txo_index].payment_req_index = i output = outputs[txo_index]
request_outputs.append(outputs[txo_index]) output.payment_req_index = i
request_outputs.append((output.amount, output.address))
nonce = misc.get_nonce(client) if params.get_nonce else None nonce = misc.get_nonce(client) if params.get_nonce else None
for memo in params.memos:
if isinstance(memo, RefundMemo):
memo.address_resp = btc.get_authenticated_address(
client, "Testnet", memo.address_n
)
elif isinstance(memo, CoinPurchaseMemo):
memo.address_resp = btc.get_authenticated_address(
client, memo.coin_name, memo.address_n
)
payment_reqs.append( payment_reqs.append(
make_payment_request( make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
slip44=1,
outputs=request_outputs, outputs=request_outputs,
change_addresses=["tb1qkvwu9g3k2pdxewfqr7syz89r3gj557l3uuf9r9"], change_addresses=["tb1qkvwu9g3k2pdxewfqr7syz89r3gj557l3uuf9r9"],
memos=params.memos, memos=params.memos,
@ -194,7 +205,8 @@ def test_payment_request_details(client: Client):
make_payment_request( make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
outputs=outputs[:2], slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[TextMemo("Invoice #87654321.")], memos=[TextMemo("Invoice #87654321.")],
nonce=nonce, nonce=nonce,
) )
@ -224,7 +236,8 @@ def test_payment_req_wrong_amount(client: Client):
payment_req = make_payment_request( payment_req = make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
outputs=outputs[:2], slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
nonce=misc.get_nonce(client), nonce=misc.get_nonce(client),
) )
@ -245,13 +258,15 @@ def test_payment_req_wrong_amount(client: Client):
def test_payment_req_wrong_mac_refund(client: Client): def test_payment_req_wrong_mac_refund(client: Client):
# Test wrong MAC in payment request memo. # Test wrong MAC in payment request memo.
memo = RefundMemo(parse_path("m/44h/1h/0h/1/0")) memo = RefundMemo(parse_path("m/44h/1h/0h/1/0"))
memo.address_resp = btc.get_authenticated_address(client, "Testnet", memo.address_n)
outputs[0].payment_req_index = 0 outputs[0].payment_req_index = 0
outputs[1].payment_req_index = 0 outputs[1].payment_req_index = 0
outputs[2].payment_req_index = None outputs[2].payment_req_index = None
payment_req = make_payment_request( payment_req = make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
outputs=outputs[:2], slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[memo], memos=[memo],
nonce=misc.get_nonce(client), nonce=misc.get_nonce(client),
) )
@ -282,13 +297,17 @@ def test_payment_req_wrong_mac_purchase(client: Client):
slip44=5, slip44=5,
address_n=parse_path("m/44h/5h/0h/1/0"), address_n=parse_path("m/44h/5h/0h/1/0"),
) )
memo.address_resp = btc.get_authenticated_address(
client, memo.coin_name, memo.address_n
)
outputs[0].payment_req_index = 0 outputs[0].payment_req_index = 0
outputs[1].payment_req_index = 0 outputs[1].payment_req_index = 0
outputs[2].payment_req_index = None outputs[2].payment_req_index = None
payment_req = make_payment_request( payment_req = make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
outputs=outputs[:2], slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[memo], memos=[memo],
nonce=misc.get_nonce(client), nonce=misc.get_nonce(client),
) )
@ -317,7 +336,8 @@ def test_payment_req_wrong_output(client: Client):
payment_req = make_payment_request( payment_req = make_payment_request(
client, client,
recipient_name="trezor.io", recipient_name="trezor.io",
outputs=outputs[:2], slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
nonce=misc.get_nonce(client), nonce=misc.get_nonce(client),
) )

View File

@ -1,19 +1,32 @@
from collections import namedtuple from dataclasses import dataclass
from hashlib import sha256 from hashlib import sha256
from ecdsa import SECP256k1, SigningKey from ecdsa import SECP256k1, SigningKey
from trezorlib import btc, messages from trezorlib import messages
from ...common import compact_size from ..common import compact_size
SLIP44 = 1 # Testnet
TextMemo = namedtuple("TextMemo", "text") @dataclass
RefundMemo = namedtuple("RefundMemo", "address_n") class TextMemo:
CoinPurchaseMemo = namedtuple( text: str
"CoinPurchaseMemo", "amount, coin_name, slip44, address_n"
)
@dataclass
class RefundMemo:
address_n: list[int]
address_resp: messages.Address | messages.EthereumAddress | None = None
@dataclass
class CoinPurchaseMemo:
amount: int
coin_name: str
slip44: int
address_n: list[int]
address_resp: messages.Address | messages.EthereumAddress | None = None
payment_req_signer = SigningKey.from_string( payment_req_signer = SigningKey.from_string(
b"?S\ti\x8b\xc5o{,\xab\x03\x194\xea\xa8[_:\xeb\xdf\xce\xef\xe50\xf17D\x98`\xb9dj", b"?S\ti\x8b\xc5o{,\xab\x03\x194\xea\xa8[_:\xeb\xdf\xce\xef\xe50\xf17D\x98`\xb9dj",
@ -27,7 +40,13 @@ def hash_bytes_prefixed(hasher, data):
def make_payment_request( def make_payment_request(
client, recipient_name, outputs, change_addresses=None, memos=None, nonce=None client,
recipient_name,
slip44,
outputs,
change_addresses=None,
memos=None,
nonce=None,
): ):
h_pr = sha256(b"SL\x00\x24") h_pr = sha256(b"SL\x00\x24")
@ -51,25 +70,19 @@ def make_payment_request(
h_pr.update(memo_type.to_bytes(4, "little")) h_pr.update(memo_type.to_bytes(4, "little"))
hash_bytes_prefixed(h_pr, memo.text.encode()) hash_bytes_prefixed(h_pr, memo.text.encode())
elif isinstance(memo, RefundMemo): elif isinstance(memo, RefundMemo):
address_resp = btc.get_authenticated_address(
client, "Testnet", memo.address_n
)
msg_memo = messages.RefundMemo( msg_memo = messages.RefundMemo(
address=address_resp.address, mac=address_resp.mac address=memo.address_resp.address, mac=memo.address_resp.mac
) )
msg_memos.append(messages.PaymentRequestMemo(refund_memo=msg_memo)) msg_memos.append(messages.PaymentRequestMemo(refund_memo=msg_memo))
memo_type = 2 memo_type = 2
h_pr.update(memo_type.to_bytes(4, "little")) h_pr.update(memo_type.to_bytes(4, "little"))
hash_bytes_prefixed(h_pr, address_resp.address.encode()) hash_bytes_prefixed(h_pr, memo.address_resp.address.encode())
elif isinstance(memo, CoinPurchaseMemo): elif isinstance(memo, CoinPurchaseMemo):
address_resp = btc.get_authenticated_address(
client, memo.coin_name, memo.address_n
)
msg_memo = messages.CoinPurchaseMemo( msg_memo = messages.CoinPurchaseMemo(
coin_type=memo.slip44, coin_type=memo.slip44,
amount=memo.amount, amount=memo.amount,
address=address_resp.address, address=memo.address_resp.address,
mac=address_resp.mac, mac=memo.address_resp.mac,
) )
msg_memos.append(messages.PaymentRequestMemo(coin_purchase_memo=msg_memo)) msg_memos.append(messages.PaymentRequestMemo(coin_purchase_memo=msg_memo))
@ -77,17 +90,18 @@ def make_payment_request(
h_pr.update(memo_type.to_bytes(4, "little")) h_pr.update(memo_type.to_bytes(4, "little"))
h_pr.update(memo.slip44.to_bytes(4, "little")) h_pr.update(memo.slip44.to_bytes(4, "little"))
hash_bytes_prefixed(h_pr, memo.amount.encode()) hash_bytes_prefixed(h_pr, memo.amount.encode())
hash_bytes_prefixed(h_pr, address_resp.address.encode()) hash_bytes_prefixed(h_pr, memo.address_resp.address.encode())
else: else:
raise ValueError raise ValueError
h_pr.update(SLIP44.to_bytes(4, "little")) h_pr.update(slip44.to_bytes(4, "little"))
change_address = iter(change_addresses or []) change_address = iter(change_addresses or [])
h_outputs = sha256() h_outputs = sha256()
for txo in outputs: for amount, address in outputs:
h_outputs.update(txo.amount.to_bytes(8, "little")) h_outputs.update(amount.to_bytes(8, "little"))
address = txo.address or next(change_address) if not address:
address = next(change_address)
h_outputs.update(len(address).to_bytes(1, "little")) h_outputs.update(len(address).to_bytes(1, "little"))
h_outputs.update(address.encode()) h_outputs.update(address.encode())
@ -95,33 +109,8 @@ def make_payment_request(
return messages.PaymentRequest( return messages.PaymentRequest(
recipient_name=recipient_name, recipient_name=recipient_name,
amount=sum(txo.amount for txo in outputs if txo.address), amount=sum(amount for amount, address in outputs if address),
memos=msg_memos, memos=msg_memos,
nonce=nonce, nonce=nonce,
signature=payment_req_signer.sign_digest_deterministic(h_pr.digest()), signature=payment_req_signer.sign_digest_deterministic(h_pr.digest()),
) )
def make_coinjoin_request(
coordinator_name,
inputs,
input_script_pubkeys,
outputs,
output_script_pubkeys,
no_fee_indices,
fee_rate=500_000, # 0.5 %
no_fee_threshold=1_000_000,
min_registrable_amount=5_000,
):
# Process inputs.
for i, txi in enumerate(inputs):
# Set no_fee flag in coinjoin_flags.
txi.coinjoin_flags |= (i in no_fee_indices) << 1
return messages.CoinJoinRequest(
fee_rate=fee_rate,
no_fee_threshold=no_fee_threshold,
min_registrable_amount=min_registrable_amount,
mask_public_key=b"",
signature=b"",
)