1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 07:28:10 +00:00

fix(core/ui): clarify transaction replacement screens

[no changelog]
This commit is contained in:
Martin Milata 2023-05-22 21:07:18 +02:00
parent 46f9e898ca
commit 611d4edc32
11 changed files with 283 additions and 155 deletions

View File

@ -61,6 +61,7 @@ static void _librust_qstrs(void) {
MP_QSTR_fee_label;
MP_QSTR_fee_rate;
MP_QSTR_fee_rate_amount;
MP_QSTR_fee_rate_title;
MP_QSTR_hold;
MP_QSTR_hold_danger;
MP_QSTR_icon_name;

View File

@ -715,25 +715,29 @@ extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs:
extern "C" fn new_show_spending_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let account: StrBuffer = kwargs.get(Qstr::MP_QSTR_account)?.try_into()?;
let title: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_title, "INFORMATION".into())?;
let account: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let fee_rate: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_fee_rate)?.try_into_option()?;
let fee_rate_title: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_fee_rate_title, "Fee rate:".into())?;
let mut paragraphs = ParagraphVecShort::new();
paragraphs.add(Paragraph::new(
&theme::TEXT_NORMAL,
"Sending from account:".into(),
));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, account));
if let Some(a) = account {
paragraphs.add(Paragraph::new(
&theme::TEXT_NORMAL,
"Sending from account:".into(),
));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, a));
}
if let Some(f) = fee_rate {
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, "Fee rate:".into()));
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, fee_rate_title));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, f));
}
let obj = LayoutObj::new(
Frame::left_aligned(
theme::label_title(),
"INFORMATION",
title,
SwipePage::new(paragraphs.into_paragraphs(), Empty, theme::BG).with_swipe_right(),
)
.with_cancel_button(),
@ -750,7 +754,7 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
let description: Option<StrBuffer> =
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
let value: Obj = kwargs.get(Qstr::MP_QSTR_value)?;
let info_button: bool = unwrap!(kwargs.get_or(Qstr::MP_QSTR_info_button, false));
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?;
let verb: Option<StrBuffer> = kwargs
.get(Qstr::MP_QSTR_verb)
@ -774,7 +778,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false).unwrap();
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?;
let mut paragraphs = ParagraphVecShort::new();
@ -802,7 +806,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
let amount_change: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?;
let amount_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?;
@ -814,24 +817,17 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
};
let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, "Address:".into()),
Paragraph::new(&theme::TEXT_MONO, address).break_after(),
Paragraph::new(&theme::TEXT_NORMAL, description.into()),
Paragraph::new(&theme::TEXT_MONO, amount_change),
Paragraph::new(&theme::TEXT_NORMAL, "New amount:".into()),
Paragraph::new(&theme::TEXT_MONO, amount_new),
]);
let buttons = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL),
Button::with_text("CONFIRM").styled(theme::button_confirm()),
true,
);
let buttons = Button::cancel_confirm_text(Some("^"), Some("CONTINUE"));
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
"MODIFY AMOUNT",
SwipePage::new(paragraphs, buttons, theme::BG).with_cancel_on_first_page(),
SwipePage::new(paragraphs, buttons, theme::BG),
))?;
Ok(obj.into())
};
@ -840,32 +836,36 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
let user_fee_change: StrBuffer = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?;
let total_fee_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?;
// TODO: use this, most probably in "i" icon
let _fee_rate_amount: Option<StrBuffer> = kwargs
.get(Qstr::MP_QSTR_fee_rate_amount)?
.try_into_option()?;
let (description, change) = match sign {
s if s < 0 => ("Decrease your fee by:", user_fee_change),
s if s > 0 => ("Increase your fee by:", user_fee_change),
_ => ("Your fee did not change.", StrBuffer::empty()),
let (description, change, total_label) = match sign {
s if s < 0 => ("Decrease fee by:", user_fee_change, "New transaction fee:"),
s if s > 0 => ("Increase fee by:", user_fee_change, "New transaction fee:"),
_ => (
"Fee did not change.\r",
StrBuffer::empty(),
"Transaction fee:",
),
};
let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, description.into()),
Paragraph::new(&theme::TEXT_MONO, change),
Paragraph::new(&theme::TEXT_NORMAL, "\nTransaction fee:".into()),
Paragraph::new(&theme::TEXT_NORMAL, total_label.into()),
Paragraph::new(&theme::TEXT_MONO, total_fee_new),
]);
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
"MODIFY FEE",
SwipeHoldPage::new(paragraphs, theme::BG),
))?;
let obj = LayoutObj::new(
Frame::left_aligned(
theme::label_title(),
title,
SwipeHoldPage::new(paragraphs, theme::BG).with_swipe_left(),
)
.with_info_button(),
)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1660,8 +1660,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def show_spending_details(
/// *,
/// account: str,
/// title: str = "INFORMATION",
/// account: str | None,
/// fee_rate: str | None,
/// fee_rate_title: str = "Fee rate:",
/// ) -> object:
/// """Show metadata when for outgoing transaction."""
Qstr::MP_QSTR_show_spending_details => obj_fn_kw!(0, new_show_spending_details).as_obj(),
@ -1691,7 +1693,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def confirm_modify_output(
/// *,
/// address: str,
/// sign: int,
/// amount_change: str,
/// amount_new: str,
@ -1701,10 +1702,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def confirm_modify_fee(
/// *,
/// title: str,
/// sign: int,
/// user_fee_change: str,
/// total_fee_new: str,
/// fee_rate_amount: str | None,
/// ) -> object:
/// """Decrease or increase transaction fee."""
Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(),

View File

@ -469,8 +469,10 @@ def show_address_details(
# rust/src/ui/model_tt/layout.rs
def show_spending_details(
*,
account: str,
title: str = "INFORMATION",
account: str | None,
fee_rate: str | None,
fee_rate_title: str = "Fee rate:",
) -> object:
"""Show metadata when for outgoing transaction."""
@ -503,7 +505,6 @@ def confirm_total(
# rust/src/ui/model_tt/layout.rs
def confirm_modify_output(
*,
address: str,
sign: int,
amount_change: str,
amount_new: str,
@ -514,10 +515,10 @@ def confirm_modify_output(
# rust/src/ui/model_tt/layout.rs
def confirm_modify_fee(
*,
title: str,
sign: int,
user_fee_change: str,
total_fee_new: str,
fee_rate_amount: str | None,
) -> object:
"""Decrease or increase transaction fee."""

View File

@ -245,19 +245,23 @@ class BasicApprover(Approver):
if not orig_txs:
return
title = self._replacement_title(tx_info, orig_txs)
for orig in orig_txs:
await helpers.confirm_replacement(title, orig.orig_hash)
def _replacement_title(
self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]
) -> str:
if self.is_payjoin():
description = "PayJoin"
return "PayJoin"
elif tx_info.rbf_disabled() and any(
not orig.rbf_disabled() for orig in orig_txs
):
description = "Finalize transaction"
return "Finalize transaction"
elif len(orig_txs) > 1:
description = "Meld transactions"
return "Meld transactions"
else:
description = "Update transaction"
for orig in orig_txs:
await helpers.confirm_replacement(description, orig.orig_hash)
return "Update transaction"
async def approve_tx(self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]) -> None:
from trezor.wire import NotEnoughFunds
@ -322,18 +326,20 @@ class BasicApprover(Approver):
)
if not self.is_payjoin():
title = self._replacement_title(tx_info, orig_txs)
# Not a PayJoin: Show the actual fee difference, since any difference in the fee is
# coming entirely from the user's own funds and from decreases of external outputs.
# We consider the decreases as belonging to the user.
await helpers.confirm_modify_fee(
fee - orig_fee, fee, fee_rate, coin, amount_unit
title, fee - orig_fee, fee, fee_rate, coin, amount_unit
)
elif spending > orig_spending:
title = self._replacement_title(tx_info, orig_txs)
# PayJoin and user is spending more: Show the increase in the user's contribution
# to the fee, ignoring any contribution from external inputs. Decreasing of
# external outputs is not allowed in PayJoin, so there is no need to handle those.
await helpers.confirm_modify_fee(
spending - orig_spending, fee, fee_rate, coin, amount_unit
title, spending - orig_spending, fee, fee_rate, coin, amount_unit
)
else:
# PayJoin and user is not spending more: When new external inputs are involved and

View File

@ -92,12 +92,12 @@ class UiConfirmPaymentRequest(UiConfirm):
class UiConfirmReplacement(UiConfirm):
def __init__(self, description: str, txid: bytes):
self.description = description
def __init__(self, title: str, txid: bytes):
self.title = title
self.txid = txid
def confirm_dialog(self, ctx: Context) -> Awaitable[Any]:
return layout.confirm_replacement(ctx, self.description, self.txid)
return layout.confirm_replacement(ctx, self.title, self.txid)
class UiConfirmModifyOutput(UiConfirm):
@ -122,12 +122,14 @@ class UiConfirmModifyOutput(UiConfirm):
class UiConfirmModifyFee(UiConfirm):
def __init__(
self,
title: str,
user_fee_change: int,
total_fee_new: int,
fee_rate: float,
coin: CoinInfo,
amount_unit: AmountUnit,
):
self.title = title
self.user_fee_change = user_fee_change
self.total_fee_new = total_fee_new
self.fee_rate = fee_rate
@ -137,6 +139,7 @@ class UiConfirmModifyFee(UiConfirm):
def confirm_dialog(self, ctx: Context) -> Awaitable[Any]:
return layout.confirm_modify_fee(
ctx,
self.title,
self.user_fee_change,
self.total_fee_new,
self.fee_rate,
@ -255,10 +258,10 @@ def confirm_modify_output(txo: TxOutput, orig_txo: TxOutput, coin: CoinInfo, amo
return (yield UiConfirmModifyOutput(txo, orig_txo, coin, amount_unit))
def confirm_modify_fee(user_fee_change: int, total_fee_new: int, fee_rate: float, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[Any]: # type: ignore [awaitable-is-generator]
def confirm_modify_fee(title: str, user_fee_change: int, total_fee_new: int, fee_rate: float, coin: CoinInfo, amount_unit: AmountUnit) -> Awaitable[Any]: # type: ignore [awaitable-is-generator]
return (
yield UiConfirmModifyFee(
user_fee_change, total_fee_new, fee_rate, coin, amount_unit
title, user_fee_change, total_fee_new, fee_rate, coin, amount_unit
)
)

View File

@ -175,12 +175,12 @@ async def confirm_payment_request(
)
async def confirm_replacement(ctx: Context, description: str, txid: bytes) -> None:
async def confirm_replacement(ctx: Context, title: str, txid: bytes) -> None:
from ubinascii import hexlify
await layouts.confirm_replacement(
ctx,
description,
title,
hexlify(txid).decode(),
)
@ -206,6 +206,7 @@ async def confirm_modify_output(
async def confirm_modify_fee(
ctx: Context,
title: str,
user_fee_change: int,
total_fee_new: int,
fee_rate: float,
@ -214,6 +215,7 @@ async def confirm_modify_fee(
) -> None:
await layouts.confirm_modify_fee(
ctx,
title,
user_fee_change,
format_coin_amount(abs(user_fee_change), coin, amount_unit),
format_coin_amount(total_fee_new, coin, amount_unit),

View File

@ -728,6 +728,7 @@ async def confirm_blob(
title: str,
data: bytes | str,
description: str | None = None,
verb: str = "CONFIRM",
hold: bool = False,
br_code: ButtonRequestType = BR_TYPE_OTHER,
ask_pagination: bool = False,
@ -741,7 +742,7 @@ async def confirm_blob(
data=data,
extra=None,
hold=hold,
verb="CONFIRM",
verb=verb,
)
)
@ -897,41 +898,23 @@ async def confirm_total(
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
layout = RustLayout(
total_layout = RustLayout(
trezorui2.confirm_total(
title=title,
items=[
(total_label, total_amount),
(fee_label, fee_amount),
],
info_button=account_label is not None,
info_button=bool(account_label or fee_rate_amount),
)
)
await button_request(
ctx,
br_type,
br_code,
pages=layout.page_count(),
info_layout = RustLayout(
trezorui2.show_spending_details(
account=account_label or "",
fee_rate=fee_rate_amount or "",
)
)
while True:
result = await ctx.wait(layout)
if result is CONFIRMED:
return
elif result is INFO and account_label is not None:
result = await ctx.wait(
RustLayout(
trezorui2.show_spending_details(
account=account_label, fee_rate=fee_rate_amount
)
)
)
assert result is CANCELLED
layout.request_complete_repaint()
continue
raise ActionCancelled
await with_info(ctx, total_layout, info_layout, br_type, br_code)
async def confirm_joint_total(
@ -978,12 +961,13 @@ async def confirm_metadata(
)
async def confirm_replacement(ctx: GenericContext, description: str, txid: str) -> None:
async def confirm_replacement(ctx: GenericContext, title: str, txid: str) -> None:
await confirm_blob(
ctx,
title=description.upper(),
title=title.upper(),
data=txid,
description="Confirm transaction ID:",
description="Transaction ID:",
verb="CONTINUE",
br_type="confirm_replacement",
br_code=ButtonRequestType.SignTx,
)
@ -996,45 +980,101 @@ async def confirm_modify_output(
amount_change: str,
amount_new: str,
) -> None:
await raise_if_not_confirmed(
interact(
ctx,
send_button_request = True
while True:
if send_button_request:
await button_request(
ctx,
"modify_output",
ButtonRequestType.ConfirmOutput,
)
await raise_if_not_confirmed(
ctx.wait(
RustLayout(
trezorui2.confirm_blob(
title="MODIFY AMOUNT",
data=address,
verb="CONTINUE",
verb_cancel=None,
description="Address:",
extra=None,
)
)
)
)
if send_button_request:
send_button_request = False
await button_request(
ctx,
"modify_output",
ButtonRequestType.ConfirmOutput,
)
result = await ctx.wait(
RustLayout(
trezorui2.confirm_modify_output(
address=address,
sign=sign,
amount_change=amount_change,
amount_new=amount_new,
)
),
"modify_output",
ButtonRequestType.ConfirmOutput,
)
)
if result is CONFIRMED:
break
async def with_info(
ctx: GenericContext,
main_layout: RustLayout,
info_layout: RustLayout,
br_type: str,
br_code: ButtonRequestType,
) -> None:
await button_request(ctx, br_type, br_code, pages=main_layout.page_count())
while True:
result = await ctx.wait(main_layout)
if result is CONFIRMED:
return
elif result is INFO:
info_layout.request_complete_repaint()
result = await ctx.wait(info_layout)
assert result is CANCELLED
main_layout.request_complete_repaint()
continue
raise ActionCancelled
async def confirm_modify_fee(
ctx: GenericContext,
title: str,
sign: int,
user_fee_change: str,
total_fee_new: str,
fee_rate_amount: str | None = None,
) -> None:
await raise_if_not_confirmed(
interact(
ctx,
RustLayout(
trezorui2.confirm_modify_fee(
sign=sign,
user_fee_change=user_fee_change,
total_fee_new=total_fee_new,
fee_rate_amount=fee_rate_amount,
)
),
"modify_fee",
ButtonRequestType.SignTx,
fee_layout = RustLayout(
trezorui2.confirm_modify_fee(
title=title.upper(),
sign=sign,
user_fee_change=user_fee_change,
total_fee_new=total_fee_new,
)
)
info_layout = RustLayout(
trezorui2.show_spending_details(
account=None,
title="FEE INFORMATION",
fee_rate=fee_rate_amount,
fee_rate_title="New fee rate:",
)
)
await with_info(
ctx, fee_layout, info_layout, "modify_fee", ButtonRequestType.SignTx
)
async def confirm_coinjoin(

View File

@ -28,6 +28,7 @@ def _split_share_into_pages(share_words: Sequence[str], per_page: int = 4) -> li
pages.append(current)
current = ""
# Align numbers to the right.
lastnum = i + per_page + 1
fill = 1 if lastnum < 10 else 2
else:

View File

@ -93,7 +93,15 @@ TXHASH_25fee5 = bytes.fromhex(
TXHASH_1f326f = bytes.fromhex(
"1f326f65768d55ef146efbb345bd87abe84ac7185726d0457a026fc347a26ef3"
)
TXHASH_334cd7 = bytes.fromhex(
"334cd7ad982b3b15d07dd1c84e939e95efb0803071648048a7f289492e7b4c8a"
)
TXHASH_5e7667 = bytes.fromhex(
"5e7667690076ae4737e2f872005de6f6b57592f32108ed9b301eeece6de24ad6"
)
TXHASH_efaa41 = bytes.fromhex(
"efaa41ff3e67edf508846c1a1ed56894cfd32725c590300108f40c9edc1aac35"
)
CORNER_BUTTON = (215, 25)
@ -1671,3 +1679,67 @@ def test_information_cancel(client: Client):
[out1],
prev_txes=TX_CACHE_MAINNET,
)
@pytest.mark.skip_t1(reason="Cannot test layouts on T1")
@pytest.mark.skip_tr(reason="Input flow different on TR")
def test_information_replacement(client: Client):
# Use the change output and an external output to bump the fee.
# Originally fee was 3780, now 108060 (94280 from change and 10000 from external).
inp1 = messages.TxInputType(
address_n=parse_path("m/49h/1h/0h/0/4"),
amount=100_000,
script_type=messages.InputScriptType.SPENDP2SHWITNESS,
prev_hash=TXHASH_5e7667,
prev_index=1,
orig_hash=TXHASH_334cd7,
orig_index=0,
)
inp2 = messages.TxInputType(
address_n=parse_path("m/49h/1h/0h/0/3"),
amount=998_060,
script_type=messages.InputScriptType.SPENDP2SHWITNESS,
prev_hash=TXHASH_efaa41,
prev_index=0,
orig_hash=TXHASH_334cd7,
orig_index=1,
)
out1 = messages.TxOutputType(
# Actually m/49'/1'/0'/0/5.
address="2MvUUSiQZDSqyeSdofKX9KrSCio1nANPDTe",
amount=990_000,
orig_hash=TXHASH_334cd7,
orig_index=0,
)
def input_flow():
yield # confirm txid
client.debug.press_yes()
yield # confirm address
client.debug.press_yes()
# go back to address
client.debug.press_no()
# confirm address
client.debug.press_yes()
yield # confirm amount
client.debug.press_yes()
yield # transaction summary, press info
client.debug.press_info(wait=True)
client.debug.click(CORNER_BUTTON, wait=True)
client.debug.press_yes()
with client:
client.set_input_flow(input_flow)
client.watch_layout(True)
btc.sign_tx(
client,
"Testnet",
[inp1, inp2],
[out1],
prev_txes=TX_CACHE_TESTNET,
)

View File

@ -600,7 +600,7 @@ def test_p2wpkh_in_p2sh_fee_bump_from_external(client: Client):
orig_index=0,
)
t1 = client.features.model == "1"
tr = client.features.model == "R"
with client:
client.set_expected_responses(
[
@ -613,7 +613,7 @@ def test_p2wpkh_in_p2sh_fee_bump_from_external(client: Client):
request_output(0),
request_orig_output(0, TXHASH_334cd7),
messages.ButtonRequest(code=B.ConfirmOutput),
(t1, messages.ButtonRequest(code=B.ConfirmOutput)),
(not tr, messages.ButtonRequest(code=B.ConfirmOutput)),
request_orig_output(1, TXHASH_334cd7),
messages.ButtonRequest(code=B.SignTx),
request_input(0),

View File

@ -1919,13 +1919,13 @@
"TT_test_pin.py::test_pin_short": "b5377990e4a1f324133601e3ca4726cde7af50b3e2c3f53738022ed9108a6a79",
"TT_test_pin.py::test_wipe_code_same_as_pin": "cb8aa7d781b689452e863a3e704c1df06d217cbf8761b698cd912e0417da1513",
"TT_test_pin.py::test_wipe_code_setup": "353855511c20bcf4f8ec01d141c11a108472dc64e3dac06c5f567b42bddbaba9",
"TT_test_recovery.py::test_recovery_bip39": "6ff84bfab19bec7db2d5672a2059a69dbae56cc1e06464700024e431c9a1349f",
"TT_test_recovery.py::test_recovery_slip39_basic": "e038348233dc15169cd2678482f4512ed863fedca5cd70d94e571d88fa40c0a7",
"TT_test_reset_bip39.py::test_reset_bip39": "a37589806445d80b11edc57f3c80241fa14d538dbcd08f382bad7d010c3b708a",
"TT_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "67207985c09645772e42512a6cc3389e9389247d2164bb8e5731f86e707bfef7",
"TT_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "62f22bb7ec26ee3c12560bdf29496b7d4ca34b83083a6e4889c7d412c5a3e9ab",
"TT_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "ff60196c3efff9f8f9fc25d29d7e5241521d3c80a7ee43ead5f48e4914fac099",
"TT_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "35de95c810ac552b82d3af5b581dd3ade06f12b72a11cb45894d8a83c6e26be2"
"TT_test_recovery.py::test_recovery_bip39": "85f92b26902f1ce64913d6ca9874a508fcb21fff8411145c438de69077a4989a",
"TT_test_recovery.py::test_recovery_slip39_basic": "53fa3abfe1ce7b11ec189fa359f463763b53802ae5925e84a6593271fe4e8f9a",
"TT_test_reset_bip39.py::test_reset_bip39": "21633fd88045835f5ee6ca8e37f10e994345ccc0031662adbd58434db218c590",
"TT_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "34680359511117bce691dc377169f55f515c07ab1d958a15757a81bee3314aed",
"TT_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "a5e043a743d239181ecf73d2e6c48ae05485cee7cdcd4626ac8599709bcded47",
"TT_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "ed849cb2b3b0c3917f78289e415f0d89fdba34821b8ed8126ee5653abfa8169b",
"TT_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "309c4136c0621f4ffb3c1ff64796736111e95544165fa5bfd4147d9a75d1c71b"
},
"device_tests": {
"TT_binance-test_get_address.py::test_binance_get_address[m-44h-714h-0h-0-0-bnb1hgm0p7khfk85zpz-68e2cb5a": "483ff25f0ff24de80631dfb202ac681c38dfbf44c5905505281c2d0719a94fc6",
@ -2205,6 +2205,7 @@
"TT_bitcoin-test_signtx.py::test_information": "54c4b3a4deff8fcbbcf7fa06b7eb594ec191ec64ee5f1d69bb3692cbfd902a8c",
"TT_bitcoin-test_signtx.py::test_information_cancel": "d0989971cdaea07efc154c1508ce02eb20d05718443f3be7c34184f14621c2ec",
"TT_bitcoin-test_signtx.py::test_information_mixed": "bcc21fdced7033efbdc307650f5e766ab030ae9b2f3c1d1a19f30be1951d0173",
"TT_bitcoin-test_signtx.py::test_information_replacement": "bacaa02b0fa60db7410411932f0936a5c8bbf343597f8497b3285c7cbc6e11bf",
"TT_bitcoin-test_signtx.py::test_lock_time[1-4294967295]": "940c512b4932e8da6a27507d1aa1fa499d074b5105ebe88627e8c20d2138a65f",
"TT_bitcoin-test_signtx.py::test_lock_time[499999999-4294967294]": "29911873931f3e312bc9794cb57e507af02c7a1324717df59507fe1ce088e3cd",
"TT_bitcoin-test_signtx.py::test_lock_time[500000000-4294967294]": "fb6bded1f891884c9a82c582126fc7e883a8c70a513ca177d92c47d1256f8f5f",
@ -2293,21 +2294,21 @@
"TT_bitcoin-test_signtx_replacement.py::test_attack_fake_ext_input_amount": "f97a9ce841c7ceddbe8e1fd4c39a68c40b4606058eab4cc2c02c1d2143c60a38",
"TT_bitcoin-test_signtx_replacement.py::test_attack_fake_int_input_amount": "f97a9ce841c7ceddbe8e1fd4c39a68c40b4606058eab4cc2c02c1d2143c60a38",
"TT_bitcoin-test_signtx_replacement.py::test_attack_false_internal": "f97a9ce841c7ceddbe8e1fd4c39a68c40b4606058eab4cc2c02c1d2143c60a38",
"TT_bitcoin-test_signtx_replacement.py::test_attack_steal_change": "11f992c1ee9aebb9c672be1ff0b6606368b84ecb340cfdde2a085c89db712657",
"TT_bitcoin-test_signtx_replacement.py::test_p2pkh_fee_bump": "fbf869ddec563f34f168eaa42a7e4c2c6764d21541d3199ce38660639d5ca094",
"TT_bitcoin-test_signtx_replacement.py::test_p2tr_fee_bump": "036353f716b4cc3dfe8159896e5fbbda99083428d20529a0e0bdadee3b065f9b",
"TT_bitcoin-test_signtx_replacement.py::test_p2tr_invalid_signature": "036353f716b4cc3dfe8159896e5fbbda99083428d20529a0e0bdadee3b065f9b",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_finalize": "b6cc31733a28894d9a9e4399f544ca87a3d36b80aa4eb405bba87c93f366f533",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_in_p2sh_fee_bump_from_external": "b713534ff7d9daab50ed23497cc40f24d9f97d967980045f37e8f61c1c282813",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_in_p2sh_remove_change": "bd04e0c7d675c9fdb92cbd35e6fff88ecfc99900e6f2e1aaa71ee73e74da1ad6",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_invalid_signature": "b6cc31733a28894d9a9e4399f544ca87a3d36b80aa4eb405bba87c93f366f533",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_op_return_fee_bump": "5ba37ea0a488085f84a977ab26bc892c4ef592a7ef16e7b248781637e493d9bb",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909659-90000-02483045022100aa1b91-c9b963ae": "86967f42e1e91312cf364a9e9b0b99d9c3cf01ceb7d5c6eb94c0925fd7220fc8",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909718-90000-024730440220753f5304-ecb983d1": "86967f42e1e91312cf364a9e9b0b99d9c3cf01ceb7d5c6eb94c0925fd7220fc8",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909800-89859-0248304502210097a42b-7a89e474": "86967f42e1e91312cf364a9e9b0b99d9c3cf01ceb7d5c6eb94c0925fd7220fc8",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909859-89800-02483045022100af3a87-80428fad": "30f6706f52ef795d528909cab43fd9e07dd9382b8bf2d77228c998ad4d0be62c",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909859-89859-02483045022100eb74ab-881c7bef": "86967f42e1e91312cf364a9e9b0b99d9c3cf01ceb7d5c6eb94c0925fd7220fc8",
"TT_bitcoin-test_signtx_replacement.py::test_tx_meld": "1d4c6f043bfff920d09b8da2201ddffe84d970756a603ff54b9f9b59696c3ef6",
"TT_bitcoin-test_signtx_replacement.py::test_attack_steal_change": "5010e2df48c6250924a26345336beb0cd945f1a37c628572f70fbaad405ee7e2",
"TT_bitcoin-test_signtx_replacement.py::test_p2pkh_fee_bump": "4a8efb3ca3927cb6353ed86deea6240c16f27f41a493e9420b123dd09fcb6f13",
"TT_bitcoin-test_signtx_replacement.py::test_p2tr_fee_bump": "0c608c9cae88cde8cb3901fbfa69c511c10b88d81021d2134c5a77ce08553070",
"TT_bitcoin-test_signtx_replacement.py::test_p2tr_invalid_signature": "0c608c9cae88cde8cb3901fbfa69c511c10b88d81021d2134c5a77ce08553070",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_finalize": "8e01e2115bb0d48548797013cb74ba1ee50908945846b2e22eeee282a97bee98",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_in_p2sh_fee_bump_from_external": "57e1e2ab76ed05ddc9833b63999e846e94ff40046d0d9a88323b0eb82cd10fe1",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_in_p2sh_remove_change": "0a8cf9e7f974b5b827fe347379d26bbf5ffba68a7be040c77d6a86e2ea0765bc",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_invalid_signature": "8e01e2115bb0d48548797013cb74ba1ee50908945846b2e22eeee282a97bee98",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_op_return_fee_bump": "644ae548f9be2447a6ff6021f1495e58e337e67f15016ad6e18f764a7b5fc8d4",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909659-90000-02483045022100aa1b91-c9b963ae": "a055056fba39475606357ee9b8379c58b8d44577cb85d1a611cf77e7f205cfd9",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909718-90000-024730440220753f5304-ecb983d1": "a055056fba39475606357ee9b8379c58b8d44577cb85d1a611cf77e7f205cfd9",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909800-89859-0248304502210097a42b-7a89e474": "a055056fba39475606357ee9b8379c58b8d44577cb85d1a611cf77e7f205cfd9",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909859-89800-02483045022100af3a87-80428fad": "5355a4deff79234a6d6d1e4456bc7c9123fb7f0a0037c9dff52bc82f419cdc97",
"TT_bitcoin-test_signtx_replacement.py::test_p2wpkh_payjoin[19909859-89859-02483045022100eb74ab-881c7bef": "a055056fba39475606357ee9b8379c58b8d44577cb85d1a611cf77e7f205cfd9",
"TT_bitcoin-test_signtx_replacement.py::test_tx_meld": "e3d55c8342b34b161bac785a5e41c0b09104cfadd2c8473e4fed57c2eb321edd",
"TT_bitcoin-test_signtx_segwit.py::test_attack_change_input_address": "64771558b8dfb4f6317f95a1bc87c77e8461a7fd7ef6fee39770330984b55373",
"TT_bitcoin-test_signtx_segwit.py::test_attack_mixed_inputs": "b1230b100c552e5be052c169f46a5cdd5a2fe37c4e9e8051600fed6d91399cd1",
"TT_bitcoin-test_signtx_segwit.py::test_send_multisig_1": "4f66563032c2db963ad141655c8f362d54360902bc401d5d1101fa243b67dbc6",
@ -2876,24 +2877,24 @@
"TT_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "be1dd4ecbfc29a767dd3f1faf6ee12282422f0504175b996afa352b39a7f540b",
"TT_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "87718399c69d11e8656d4af3af448f542970fbfca524a53955adcd911903efd0",
"TT_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "0fbabbe652adc6a4e0d7285b165687dce0ad617762ed228f6a21d56de409d799",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "774d71f7654fa0fd89198359c96cfac655a21f4410c056f40e12b9309874b196",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "8410e57b746339b2a35078412fc2b83d26ddffbf73a8545f52dcda07b052b7b3",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "6dedfe69fd4f29b5c0fc65e85ef51cb0294b95c30021b401655c0516409a09ad",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "1aa3ce14a715dfaf9bba07c613bd96473bcfa57e48888f959d40a37fd8cda4c6",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "a5fc539ca06049a3753b767a2dc409e0dacefd5878cfeda0b2c20c8ed87036b5",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "12b002d816c964295d1f49318871555ab7eb71f42af04fab94e7978f05b25d9e",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "0109cf31e69f4bd666644b866ca26dfd47e5cc3cb211d4b7863dcbfd94f5a542",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "c631c45a838fd1f167fd2149886fc54c28cf0fe032fe846a7f7e0f27dea7270a",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "1180763b4f38e2836fef413f7107483375d1c4c851ee825528f9c6278b6ed992",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "2f8a27fa1fb5c5454a74885d361aa5507b029679e35032611e5bb032e2195bcf",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "aa094ca5d4658b33cd08260b8a465ebbe4862d1f22e76d34cd576d57f1027908",
"TT_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "eb31a91d2d8f3be95aff992c6ba69e38b3db5cbb7355194aebb6ad4f042f587a",
"TT_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "cb7f55d17ebf06888ff02ab3db0a5bd2b5355dcc465eab5fc009089845623c72",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "8f08225079c56dc3514277913a3fd409fd9ca68fb899b1b04fe94c31eba20da0",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "dcdb1b242a1747766fcb3f91d95226c47ad68e31c1a68c15b3dad219d4bf733a",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "0fb3df94993fe08bb219ff293329a07e367e6dc2e659a8817a3a9b039835e5b9",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "e447e861244e3ee749842f503ac344192fa6bb7fee499317f53985579c5c840e",
"TT_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "28187d0bd07df432025468ed1ec5dc96ad5a8df7eceb5013e66cead01db76524",
"TT_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "0b6c7b04b3c71326d6849d2d93b52f49fae67393fcd967d16fd64062bab23142",
"TT_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "a43eb7f0c6fe3f42f7086efd984c78c02994798dcc134cd8e683c1b53f394d1f",
"TT_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "bdf31edf5ed0384dfd429427d6c8362f6e6862ac8424a2e04bdc0603c9167e64",
"TT_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "ec94b714938f999d301198681e22eecaca59cfbc5ca3499fc58268f7d52bab89",
"TT_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "f5e65eeadaa2a3adc45c6425135434d84131d81056f7426bded9503b98e2517c",
"TT_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "3ec95417936f90e01b92ab89b50d1a5acb98befb51c5767914f501298e1a794c",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "36f6689acdafb02680f061608792295b0136a9ab5789ab82f2952266d5823107",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "e6d944b6e94a8810354026a454c3d7b918865e230897d4027b453011105eb5ad",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "7c8a9725873c105663ed7b78cd9f01c236850de356012d4e99f24d90dfc66bb1",
"TT_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "12b3e2b0f6df93af5ce1e88b9c6d99d0be18c60e6b67e42a172badbd2cd9c100",
"TT_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "51944865c548c77adc948bfa068bc9631d2e6f5f6df3af1089cf53bc5b43a28b",
"TT_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "822dc0f5e2dd89d5d7c1aca7d0b932de459f400622c59c8fd4a56e28022c02c1",
"TT_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "ee89833730540c24f204fc1fad392393b3177d12bacd2d602b1b7434350ea0a8",
"TT_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "f472b076ffdec128622e39b121f05f9bcb29733ea751da968c51a6dfe4942d7e",
"TT_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "390c996a1d11b47696a6a03c019a2e4ec37485b795c1dcc9885679201003cb83",
"TT_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "c91bf595acacba296a3fcb9db979a0475a86cfba67f92f260ef3827a00472bc0",
"TT_ripple-test_get_address.py::test_ripple_get_address": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_ripple-test_get_address.py::test_ripple_get_address_other": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_ripple-test_sign_tx.py::test_ripple_sign_invalid_fee": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
@ -2978,12 +2979,12 @@
"TT_test_msg_applysettings.py::test_experimental_features": "e7bdcfa8708fc0fbe88f5f4cc7f93497c94fa5ae9e08282496c04e993123674c",
"TT_test_msg_applysettings.py::test_label_too_long": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_test_msg_applysettings.py::test_safety_checks": "4ec93f34a54d13317c2d5434ed5853d0edf0f6d91772aab2a15acf7e3de75458",
"TT_test_msg_backup_device.py::test_backup_bip39": "f893431c45c12b0cab6695ad1ed3740bef8a546ea77c000e02287acc51f49396",
"TT_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "6e0456552d918a2bfeff518f664bf217ff69fb78585b9bedb786e453a602e0ed",
"TT_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "58261eb9d6e99256ceb5ebbea9fb91529bc9fd3bdb178787aa4e1e7ad85fc4f3",
"TT_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "7977be97fb539b1f0706fb792a3ba603148e656e6d1f1dcf599fc033aa8e02e2",
"TT_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "5e1e10a652aac8c95a771e873a1823ca35811df7fdb2356c15d1c2ab06ae7d3c",
"TT_test_msg_backup_device.py::test_interrupt_backup_fails": "ae147498028d68aa71c7337544e4a5049c4c943897f905c6fe29e88e5c3ab056",
"TT_test_msg_backup_device.py::test_backup_bip39": "443cbf213767f7152abe6ca70dbc09f8a8b1e325bbb866805b404dc345232d67",
"TT_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "1a04ac093951e611863a6a52a6ea14360eb13caeabfda438b5ac7c379f66cb8a",
"TT_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "1e2d27cf6d2634293b2a971065e809498f4ade47530d4f1d6aa04f01d87a6349",
"TT_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "999e1072a46dd9576d1ac51aede71c0ae650e8d0bb5cd140c68bad3826592601",
"TT_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "ff61a3e19bfde5105e727484fccb919febb0bf02c61c92dbad548439bdd0339d",
"TT_test_msg_backup_device.py::test_interrupt_backup_fails": "cdf801e16b046079569e4055f43a86a45d7eace58793427ef5433f71e75961f9",
"TT_test_msg_backup_device.py::test_no_backup_fails": "fada9d38ec099b3c6a4fd8bf994bb1f3431e40085128b4e0cd9deb8344dec53e",
"TT_test_msg_backup_device.py::test_no_backup_show_entropy_fails": "001377ce61dcd189e6a9d17e20dcd71130e951dc3314b40ff26f816bd9355bdd",
"TT_test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "b3abe56cbc85b9012e1fa27dce0e869f0a01de7a72db24e44177690a57392b52",