1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-20 17:19:01 +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 17e9515ee2
commit faf09b48d0
6 changed files with 97 additions and 62 deletions

View File

@ -85,7 +85,6 @@ Q(apps.bitcoin.sign_tx.helpers)
Q(apps.bitcoin.sign_tx.layout)
Q(apps.bitcoin.sign_tx.matchcheck)
Q(apps.bitcoin.sign_tx.omni)
Q(apps.bitcoin.sign_tx.payment_request)
Q(apps.bitcoin.sign_tx.progress)
Q(apps.bitcoin.sign_tx.sig_hasher)
Q(apps.bitcoin.sign_tx.tx_info)
@ -107,6 +106,7 @@ Q(apps.common.keychain)
Q(apps.common.passphrase)
Q(apps.common.paths)
Q(apps.common.payment_request)
Q(payment_request)
Q(apps.common.readers)
Q(apps.common.request_pin)
Q(apps.common.safety_checks)

View File

@ -27,7 +27,7 @@ from trezorlib.tools import parse_path
from .. import common
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 . import recovery
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 ...tx_cache import TxCache
from .payment_req import make_coinjoin_request
from .coinjoin_req import make_coinjoin_request
from .signtx import (
assert_tx_matches,
request_finished,

View File

@ -24,7 +24,7 @@ from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path
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
# 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):
request_outputs = []
for txo_index in params.txo_indices:
outputs[txo_index].payment_req_index = i
request_outputs.append(outputs[txo_index])
output = 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
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(
make_payment_request(
client,
recipient_name="trezor.io",
slip44=1,
outputs=request_outputs,
change_addresses=["tb1qkvwu9g3k2pdxewfqr7syz89r3gj557l3uuf9r9"],
memos=params.memos,
@ -194,7 +205,8 @@ def test_payment_request_details(client: Client):
make_payment_request(
client,
recipient_name="trezor.io",
outputs=outputs[:2],
slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[TextMemo("Invoice #87654321.")],
nonce=nonce,
)
@ -224,7 +236,8 @@ def test_payment_req_wrong_amount(client: Client):
payment_req = make_payment_request(
client,
recipient_name="trezor.io",
outputs=outputs[:2],
slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
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):
# Test wrong MAC in payment request memo.
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[1].payment_req_index = 0
outputs[2].payment_req_index = None
payment_req = make_payment_request(
client,
recipient_name="trezor.io",
outputs=outputs[:2],
slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[memo],
nonce=misc.get_nonce(client),
)
@ -282,13 +297,17 @@ def test_payment_req_wrong_mac_purchase(client: Client):
slip44=5,
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[1].payment_req_index = 0
outputs[2].payment_req_index = None
payment_req = make_payment_request(
client,
recipient_name="trezor.io",
outputs=outputs[:2],
slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
memos=[memo],
nonce=misc.get_nonce(client),
)
@ -317,7 +336,8 @@ def test_payment_req_wrong_output(client: Client):
payment_req = make_payment_request(
client,
recipient_name="trezor.io",
outputs=outputs[:2],
slip44=1,
outputs=[(txo.amount, txo.address) for txo in outputs[:2]],
nonce=misc.get_nonce(client),
)

View File

@ -1,19 +1,32 @@
from collections import namedtuple
from dataclasses import dataclass
from hashlib import sha256
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")
RefundMemo = namedtuple("RefundMemo", "address_n")
CoinPurchaseMemo = namedtuple(
"CoinPurchaseMemo", "amount, coin_name, slip44, address_n"
)
@dataclass
class TextMemo:
text: str
@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(
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(
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")
@ -51,25 +70,19 @@ def make_payment_request(
h_pr.update(memo_type.to_bytes(4, "little"))
hash_bytes_prefixed(h_pr, memo.text.encode())
elif isinstance(memo, RefundMemo):
address_resp = btc.get_authenticated_address(
client, "Testnet", memo.address_n
)
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))
memo_type = 2
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):
address_resp = btc.get_authenticated_address(
client, memo.coin_name, memo.address_n
)
msg_memo = messages.CoinPurchaseMemo(
coin_type=memo.slip44,
amount=memo.amount,
address=address_resp.address,
mac=address_resp.mac,
address=memo.address_resp.address,
mac=memo.address_resp.mac,
)
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.slip44.to_bytes(4, "little"))
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:
raise ValueError
h_pr.update(SLIP44.to_bytes(4, "little"))
h_pr.update(slip44.to_bytes(4, "little"))
change_address = iter(change_addresses or [])
h_outputs = sha256()
for txo in outputs:
h_outputs.update(txo.amount.to_bytes(8, "little"))
address = txo.address or next(change_address)
for amount, address in outputs:
h_outputs.update(amount.to_bytes(8, "little"))
if not address:
address = next(change_address)
h_outputs.update(len(address).to_bytes(1, "little"))
h_outputs.update(address.encode())
@ -95,33 +109,8 @@ def make_payment_request(
return messages.PaymentRequest(
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,
nonce=nonce,
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"",
)