Compare commits

...

13 Commits

@ -396,6 +396,12 @@ message ResetDevice {
* @next Success
*/
message BackupDevice {
optional uint32 group_threshold = 1;
message Slip39Group {
required uint32 member_threshold = 1;
required uint32 member_count = 2;
}
repeated Slip39Group groups = 2;
}
/**

@ -0,0 +1 @@
Added ability to request Shamir backups with any number of groups/shares.

@ -353,8 +353,8 @@ static void _librust_qstrs(void) {
MP_QSTR_recovery__enter_share_from_diff_group;
MP_QSTR_recovery__group_num_template;
MP_QSTR_recovery__group_threshold_reached;
MP_QSTR_recovery__invalid_seed_entered;
MP_QSTR_recovery__invalid_share_entered;
MP_QSTR_recovery__invalid_wallet_backup_entered;
MP_QSTR_recovery__more_shares_needed;
MP_QSTR_recovery__num_of_words;
MP_QSTR_recovery__only_first_n_letters;
@ -393,13 +393,14 @@ static void _librust_qstrs(void) {
MP_QSTR_reset__by_continuing;
MP_QSTR_reset__check_backup_title;
MP_QSTR_reset__check_group_share_title_template;
MP_QSTR_reset__check_seed_title;
MP_QSTR_reset__check_share_title_template;
MP_QSTR_reset__check_wallet_backup_title;
MP_QSTR_reset__continue_with_next_share;
MP_QSTR_reset__continue_with_share_template;
MP_QSTR_reset__create_x_of_y_shamir_backup_template;
MP_QSTR_reset__finished_verifying_group_template;
MP_QSTR_reset__finished_verifying_seed;
MP_QSTR_reset__finished_verifying_shares;
MP_QSTR_reset__finished_verifying_wallet_backup;
MP_QSTR_reset__group_description;
MP_QSTR_reset__group_info;
MP_QSTR_reset__group_share_checked_successfully_template;
@ -417,8 +418,8 @@ static void _librust_qstrs(void) {
MP_QSTR_reset__number_of_shares_info;
MP_QSTR_reset__one_share;
MP_QSTR_reset__only_one_share_will_be_created;
MP_QSTR_reset__recovery_seed_title;
MP_QSTR_reset__recovery_share_title_template;
MP_QSTR_reset__recovery_wallet_backup_title;
MP_QSTR_reset__required_number_of_groups;
MP_QSTR_reset__select_correct_word;
MP_QSTR_reset__select_word_template;
@ -448,6 +449,7 @@ static void _librust_qstrs(void) {
MP_QSTR_reset__title_set_number_of_groups;
MP_QSTR_reset__title_set_number_of_shares;
MP_QSTR_reset__title_set_threshold;
MP_QSTR_reset__title_shamir_backup;
MP_QSTR_reset__to_form_group_template;
MP_QSTR_reset__tos_link;
MP_QSTR_reset__total_number_of_shares_in_group_template;

@ -783,8 +783,8 @@ pub enum TranslatedString {
recovery__cancel_dry_run = 495, // "Cancel backup check"
recovery__check_dry_run = 496, // "Check your backup?"
recovery__cursor_will_change = 497, // "Position of the cursor will change between entries for enhanced security."
recovery__dry_run_bip39_valid_match = 498, // "The entered recovery seed is valid and matches the one in the device."
recovery__dry_run_bip39_valid_mismatch = 499, // "The entered recovery seed is valid but does not match the one in the device."
recovery__dry_run_bip39_valid_match = 498, // "The entered recovery wallet backup is valid and matches the one in the device."
recovery__dry_run_bip39_valid_mismatch = 499, // "The entered wallet backup is valid but does not match the one in the device."
recovery__dry_run_slip39_valid_match = 500, // "The entered recovery shares are valid and match what is currently in the device."
recovery__dry_run_slip39_valid_mismatch = 501, // "The entered recovery shares are valid but do not match what is currently in the device."
recovery__enter_any_share = 502, // "Enter any share"
@ -793,7 +793,7 @@ pub enum TranslatedString {
recovery__enter_share_from_diff_group = 505, // "Enter share from a different group."
recovery__group_num_template = 506, // "Group {0}"
recovery__group_threshold_reached = 507, // "Group threshold reached."
recovery__invalid_seed_entered = 508, // "Invalid recovery seed entered."
recovery__invalid_wallet_backup_entered = 508, // "Invalid wallet backup entered."
recovery__invalid_share_entered = 509, // "Invalid recovery share entered."
recovery__more_shares_needed = 510, // "More shares needed"
recovery__num_of_words = 511, // "Select the number of words in your backup."
@ -827,12 +827,12 @@ pub enum TranslatedString {
reset__by_continuing = 539, // "By continuing you agree to Trezor Company's terms and conditions."
reset__check_backup_title = 540, // "CHECK BACKUP"
reset__check_group_share_title_template = 541, // "CHECK G{0} - SHARE {1}"
reset__check_seed_title = 542, // "CHECK SEED"
reset__check_wallet_backup_title = 542, // "CHECK WALLET BACKUP"
reset__check_share_title_template = 543, // "CHECK SHARE #{0}"
reset__continue_with_next_share = 544, // "Continue with the next share."
reset__continue_with_share_template = 545, // "Continue with share #{0}."
reset__finished_verifying_group_template = 546, // "You have finished verifying your recovery shares for group {0}."
reset__finished_verifying_seed = 547, // "You have finished verifying your recovery seed."
reset__finished_verifying_wallet_backup = 547, // "You have finished verifying your wallet backup."
reset__finished_verifying_shares = 548, // "You have finished verifying your recovery shares."
reset__group_description = 549, // "A group is made up of recovery shares."
reset__group_info = 550, // "Each group has a set number of shares and its own threshold. In the next steps you will set the numbers of shares and the thresholds."
@ -843,7 +843,7 @@ pub enum TranslatedString {
reset__need_any_share_template = 555, // "For recovery you need any {0} of the shares."
reset__needed_to_form_a_group = 556, // "needed to form a group. "
reset__needed_to_recover_your_wallet = 557, // "needed to recover your wallet. "
reset__never_make_digital_copy = 558, // "Never make a digital copy of your backup or upload it online!"
reset__never_make_digital_copy = 558, // "Never put your backup anywhere digital."
reset__num_of_share_holders_template = 559, // "{0} people or locations will each hold one share."
reset__num_of_shares_advanced_info_template = 560, // "Each recovery share is a sequence of 20 words. Next you will choose the threshold number of shares needed to form Group {0}."
reset__num_of_shares_basic_info = 561, // "Each recovery share is a sequence of 20 words. Next you will choose how many shares you need to recover your wallet."
@ -851,7 +851,7 @@ pub enum TranslatedString {
reset__number_of_shares_info = 563, // "= total number of unique word lists used for wallet backup."
reset__one_share = 564, // "1 share"
reset__only_one_share_will_be_created = 565, // "Only one share will be created."
reset__recovery_seed_title = 566, // "RECOVERY SEED"
reset__recovery_wallet_backup_title = 566, // "WALLET BACKUP"
reset__recovery_share_title_template = 567, // "RECOVERY SHARE #{0}"
reset__required_number_of_groups = 568, // "The required number of groups for recovery."
reset__select_correct_word = 569, // "Select the correct word for each position."
@ -1237,6 +1237,8 @@ pub enum TranslatedString {
storage_msg__starting = 842, // "STARTING UP"
storage_msg__verifying_pin = 843, // "VERIFYING PIN"
storage_msg__wrong_pin = 844, // "WRONG PIN"
reset__create_x_of_y_shamir_backup_template = 845, // "Do you want to create a {0} of {1} Shamir backup?"
reset__title_shamir_backup = 846, // "SHAMIR BACKUP"
}
impl TranslatedString {
@ -2015,8 +2017,8 @@ impl TranslatedString {
Self::recovery__cancel_dry_run => "Cancel backup check",
Self::recovery__check_dry_run => "Check your backup?",
Self::recovery__cursor_will_change => "Position of the cursor will change between entries for enhanced security.",
Self::recovery__dry_run_bip39_valid_match => "The entered recovery seed is valid and matches the one in the device.",
Self::recovery__dry_run_bip39_valid_mismatch => "The entered recovery seed is valid but does not match the one in the device.",
Self::recovery__dry_run_bip39_valid_match => "The entered recovery wallet backup is valid and matches the one in the device.",
Self::recovery__dry_run_bip39_valid_mismatch => "The entered wallet backup is valid but does not match the one in the device.",
Self::recovery__dry_run_slip39_valid_match => "The entered recovery shares are valid and match what is currently in the device.",
Self::recovery__dry_run_slip39_valid_mismatch => "The entered recovery shares are valid but do not match what is currently in the device.",
Self::recovery__enter_any_share => "Enter any share",
@ -2025,7 +2027,7 @@ impl TranslatedString {
Self::recovery__enter_share_from_diff_group => "Enter share from a different group.",
Self::recovery__group_num_template => "Group {0}",
Self::recovery__group_threshold_reached => "Group threshold reached.",
Self::recovery__invalid_seed_entered => "Invalid recovery seed entered.",
Self::recovery__invalid_wallet_backup_entered => "Invalid wallet backup entered.",
Self::recovery__invalid_share_entered => "Invalid recovery share entered.",
Self::recovery__more_shares_needed => "More shares needed",
Self::recovery__num_of_words => "Select the number of words in your backup.",
@ -2059,12 +2061,12 @@ impl TranslatedString {
Self::reset__by_continuing => "By continuing you agree to Trezor Company's terms and conditions.",
Self::reset__check_backup_title => "CHECK BACKUP",
Self::reset__check_group_share_title_template => "CHECK G{0} - SHARE {1}",
Self::reset__check_seed_title => "CHECK SEED",
Self::reset__check_wallet_backup_title => "CHECK WALLET BACKUP",
Self::reset__check_share_title_template => "CHECK SHARE #{0}",
Self::reset__continue_with_next_share => "Continue with the next share.",
Self::reset__continue_with_share_template => "Continue with share #{0}.",
Self::reset__finished_verifying_group_template => "You have finished verifying your recovery shares for group {0}.",
Self::reset__finished_verifying_seed => "You have finished verifying your recovery seed.",
Self::reset__finished_verifying_wallet_backup => "You have finished verifying your wallet backup.",
Self::reset__finished_verifying_shares => "You have finished verifying your recovery shares.",
Self::reset__group_description => "A group is made up of recovery shares.",
Self::reset__group_info => "Each group has a set number of shares and its own threshold. In the next steps you will set the numbers of shares and the thresholds.",
@ -2075,7 +2077,7 @@ impl TranslatedString {
Self::reset__need_any_share_template => "For recovery you need any {0} of the shares.",
Self::reset__needed_to_form_a_group => "needed to form a group. ",
Self::reset__needed_to_recover_your_wallet => "needed to recover your wallet. ",
Self::reset__never_make_digital_copy => "Never make a digital copy of your backup or upload it online!",
Self::reset__never_make_digital_copy => "Never put your backup anywhere digital.",
Self::reset__num_of_share_holders_template => "{0} people or locations will each hold one share.",
Self::reset__num_of_shares_advanced_info_template => "Each recovery share is a sequence of 20 words. Next you will choose the threshold number of shares needed to form Group {0}.",
Self::reset__num_of_shares_basic_info => "Each recovery share is a sequence of 20 words. Next you will choose how many shares you need to recover your wallet.",
@ -2083,7 +2085,7 @@ impl TranslatedString {
Self::reset__number_of_shares_info => "= total number of unique word lists used for wallet backup.",
Self::reset__one_share => "1 share",
Self::reset__only_one_share_will_be_created => "Only one share will be created.",
Self::reset__recovery_seed_title => "RECOVERY SEED",
Self::reset__recovery_wallet_backup_title => "WALLET BACKUP",
Self::reset__recovery_share_title_template => "RECOVERY SHARE #{0}",
Self::reset__required_number_of_groups => "The required number of groups for recovery.",
Self::reset__select_correct_word => "Select the correct word for each position.",
@ -2469,6 +2471,8 @@ impl TranslatedString {
Self::storage_msg__starting => "STARTING UP",
Self::storage_msg__verifying_pin => "VERIFYING PIN",
Self::storage_msg__wrong_pin => "WRONG PIN",
Self::reset__create_x_of_y_shamir_backup_template => "Do you want to create a {0} of {1} Shamir backup?",
Self::reset__title_shamir_backup => "SHAMIR BACKUP",
}
}
@ -3258,7 +3262,7 @@ impl TranslatedString {
Qstr::MP_QSTR_recovery__enter_share_from_diff_group => Some(Self::recovery__enter_share_from_diff_group),
Qstr::MP_QSTR_recovery__group_num_template => Some(Self::recovery__group_num_template),
Qstr::MP_QSTR_recovery__group_threshold_reached => Some(Self::recovery__group_threshold_reached),
Qstr::MP_QSTR_recovery__invalid_seed_entered => Some(Self::recovery__invalid_seed_entered),
Qstr::MP_QSTR_recovery__invalid_wallet_backup_entered => Some(Self::recovery__invalid_wallet_backup_entered),
Qstr::MP_QSTR_recovery__invalid_share_entered => Some(Self::recovery__invalid_share_entered),
Qstr::MP_QSTR_recovery__more_shares_needed => Some(Self::recovery__more_shares_needed),
Qstr::MP_QSTR_recovery__num_of_words => Some(Self::recovery__num_of_words),
@ -3292,12 +3296,12 @@ impl TranslatedString {
Qstr::MP_QSTR_reset__by_continuing => Some(Self::reset__by_continuing),
Qstr::MP_QSTR_reset__check_backup_title => Some(Self::reset__check_backup_title),
Qstr::MP_QSTR_reset__check_group_share_title_template => Some(Self::reset__check_group_share_title_template),
Qstr::MP_QSTR_reset__check_seed_title => Some(Self::reset__check_seed_title),
Qstr::MP_QSTR_reset__check_wallet_backup_title => Some(Self::reset__check_wallet_backup_title),
Qstr::MP_QSTR_reset__check_share_title_template => Some(Self::reset__check_share_title_template),
Qstr::MP_QSTR_reset__continue_with_next_share => Some(Self::reset__continue_with_next_share),
Qstr::MP_QSTR_reset__continue_with_share_template => Some(Self::reset__continue_with_share_template),
Qstr::MP_QSTR_reset__finished_verifying_group_template => Some(Self::reset__finished_verifying_group_template),
Qstr::MP_QSTR_reset__finished_verifying_seed => Some(Self::reset__finished_verifying_seed),
Qstr::MP_QSTR_reset__finished_verifying_wallet_backup => Some(Self::reset__finished_verifying_wallet_backup),
Qstr::MP_QSTR_reset__finished_verifying_shares => Some(Self::reset__finished_verifying_shares),
Qstr::MP_QSTR_reset__group_description => Some(Self::reset__group_description),
Qstr::MP_QSTR_reset__group_info => Some(Self::reset__group_info),
@ -3316,7 +3320,7 @@ impl TranslatedString {
Qstr::MP_QSTR_reset__number_of_shares_info => Some(Self::reset__number_of_shares_info),
Qstr::MP_QSTR_reset__one_share => Some(Self::reset__one_share),
Qstr::MP_QSTR_reset__only_one_share_will_be_created => Some(Self::reset__only_one_share_will_be_created),
Qstr::MP_QSTR_reset__recovery_seed_title => Some(Self::reset__recovery_seed_title),
Qstr::MP_QSTR_reset__recovery_wallet_backup_title => Some(Self::reset__recovery_wallet_backup_title),
Qstr::MP_QSTR_reset__recovery_share_title_template => Some(Self::reset__recovery_share_title_template),
Qstr::MP_QSTR_reset__required_number_of_groups => Some(Self::reset__required_number_of_groups),
Qstr::MP_QSTR_reset__select_correct_word => Some(Self::reset__select_correct_word),
@ -3702,6 +3706,8 @@ impl TranslatedString {
Qstr::MP_QSTR_storage_msg__starting => Some(Self::storage_msg__starting),
Qstr::MP_QSTR_storage_msg__verifying_pin => Some(Self::storage_msg__verifying_pin),
Qstr::MP_QSTR_storage_msg__wrong_pin => Some(Self::storage_msg__wrong_pin),
Qstr::MP_QSTR_reset__create_x_of_y_shamir_backup_template => Some(Self::reset__create_x_of_y_shamir_backup_template),
Qstr::MP_QSTR_reset__title_shamir_backup => Some(Self::reset__title_shamir_backup),
_ => None,
}
}

@ -509,8 +509,8 @@ class TR:
recovery__cancel_dry_run: str = "Cancel backup check"
recovery__check_dry_run: str = "Check your backup?"
recovery__cursor_will_change: str = "Position of the cursor will change between entries for enhanced security."
recovery__dry_run_bip39_valid_match: str = "The entered recovery seed is valid and matches the one in the device."
recovery__dry_run_bip39_valid_mismatch: str = "The entered recovery seed is valid but does not match the one in the device."
recovery__dry_run_bip39_valid_match: str = "The entered recovery wallet backup is valid and matches the one in the device."
recovery__dry_run_bip39_valid_mismatch: str = "The entered wallet backup is valid but does not match the one in the device."
recovery__dry_run_slip39_valid_match: str = "The entered recovery shares are valid and match what is currently in the device."
recovery__dry_run_slip39_valid_mismatch: str = "The entered recovery shares are valid but do not match what is currently in the device."
recovery__enter_any_share: str = "Enter any share"
@ -519,8 +519,8 @@ class TR:
recovery__enter_share_from_diff_group: str = "Enter share from a different group."
recovery__group_num_template: str = "Group {0}"
recovery__group_threshold_reached: str = "Group threshold reached."
recovery__invalid_seed_entered: str = "Invalid recovery seed entered."
recovery__invalid_share_entered: str = "Invalid recovery share entered."
recovery__invalid_wallet_backup_entered: str = "Invalid wallet backup entered."
recovery__more_shares_needed: str = "More shares needed"
recovery__num_of_words: str = "Select the number of words in your backup."
recovery__only_first_n_letters: str = "You'll only have to select the first 2-4 letters of each word."
@ -553,13 +553,14 @@ class TR:
reset__by_continuing: str = "By continuing you agree to Trezor Company's terms and conditions."
reset__check_backup_title: str = "CHECK BACKUP"
reset__check_group_share_title_template: str = "CHECK G{0} - SHARE {1}"
reset__check_seed_title: str = "CHECK SEED"
reset__check_share_title_template: str = "CHECK SHARE #{0}"
reset__check_wallet_backup_title: str = "CHECK WALLET BACKUP"
reset__continue_with_next_share: str = "Continue with the next share."
reset__continue_with_share_template: str = "Continue with share #{0}."
reset__create_x_of_y_shamir_backup_template: str = "Do you want to create a {0} of {1} Shamir backup?"
reset__finished_verifying_group_template: str = "You have finished verifying your recovery shares for group {0}."
reset__finished_verifying_seed: str = "You have finished verifying your recovery seed."
reset__finished_verifying_shares: str = "You have finished verifying your recovery shares."
reset__finished_verifying_wallet_backup: str = "You have finished verifying your wallet backup."
reset__group_description: str = "A group is made up of recovery shares."
reset__group_info: str = "Each group has a set number of shares and its own threshold. In the next steps you will set the numbers of shares and the thresholds."
reset__group_share_checked_successfully_template: str = "Group {0} - Share {1} checked successfully."
@ -569,7 +570,7 @@ class TR:
reset__need_any_share_template: str = "For recovery you need any {0} of the shares."
reset__needed_to_form_a_group: str = "needed to form a group. "
reset__needed_to_recover_your_wallet: str = "needed to recover your wallet. "
reset__never_make_digital_copy: str = "Never make a digital copy of your backup or upload it online!"
reset__never_make_digital_copy: str = "Never put your backup anywhere digital."
reset__num_of_share_holders_template: str = "{0} people or locations will each hold one share."
reset__num_of_shares_advanced_info_template: str = "Each recovery share is a sequence of 20 words. Next you will choose the threshold number of shares needed to form Group {0}."
reset__num_of_shares_basic_info: str = "Each recovery share is a sequence of 20 words. Next you will choose how many shares you need to recover your wallet."
@ -577,8 +578,8 @@ class TR:
reset__number_of_shares_info: str = "= total number of unique word lists used for wallet backup."
reset__one_share: str = "1 share"
reset__only_one_share_will_be_created: str = "Only one share will be created."
reset__recovery_seed_title: str = "RECOVERY SEED"
reset__recovery_share_title_template: str = "RECOVERY SHARE #{0}"
reset__recovery_wallet_backup_title: str = "WALLET BACKUP"
reset__required_number_of_groups: str = "The required number of groups for recovery."
reset__select_correct_word: str = "Select the correct word for each position."
reset__select_word_template: str = "SELECT {0} WORD"
@ -608,6 +609,7 @@ class TR:
reset__title_set_number_of_groups: str = "SET NUMBER OF GROUPS"
reset__title_set_number_of_shares: str = "SET NUMBER OF SHARES"
reset__title_set_threshold: str = "SET THRESHOLD"
reset__title_shamir_backup: str = "SHAMIR BACKUP"
reset__to_form_group_template: str = "to form Group {0}."
reset__tos_link: str = "trezor.io/tos"
reset__total_number_of_shares_in_group_template: str = "Set the total number of shares in Group {0}."

@ -18,14 +18,19 @@ async def backup_device(msg: BackupDevice) -> Success:
if not storage_device.needs_backup():
raise wire.ProcessError("Seed already backed up")
mnemonic_secret, mnemonic_type = mnemonic.get()
mnemonic_secret, backup_type = mnemonic.get()
if mnemonic_secret is None:
raise RuntimeError
storage_device.set_unfinished_backup(True)
storage_device.set_backed_up()
await backup_seed(mnemonic_type, mnemonic_secret)
await backup_seed(
backup_type,
mnemonic_secret,
msg.group_threshold,
[(g.member_threshold, g.member_count) for g in msg.groups],
)
storage_device.set_unfinished_backup(False)

@ -135,7 +135,7 @@ async def show_invalid_mnemonic(word_count: int) -> None:
else:
await show_recovery_warning(
"warning_invalid_seed",
TR.recovery__invalid_seed_entered,
TR.recovery__invalid_wallet_backup_entered,
TR.words__please_try_again,
)

@ -1,9 +1,11 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence
import storage
import storage.device as storage_device
from trezor import TR
from trezor.crypto import slip39
from trezor.enums import BackupType
from trezor.ui.layouts import confirm_action
from trezor.wire import ProcessError
from . import layout
@ -111,31 +113,23 @@ async def reset_device(msg: ResetDevice) -> Success:
async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
group_threshold = 1
# get number of shares
await layout.slip39_show_checklist(0, BAK_T_SLIP39_BASIC)
shares_count = await layout.slip39_prompt_number_of_shares()
share_count = await layout.slip39_prompt_number_of_shares()
# get threshold
await layout.slip39_show_checklist(1, BAK_T_SLIP39_BASIC)
threshold = await layout.slip39_prompt_threshold(shares_count)
identifier = storage_device.get_slip39_identifier()
iteration_exponent = storage_device.get_slip39_iteration_exponent()
if identifier is None or iteration_exponent is None:
raise ValueError
share_threshold = await layout.slip39_prompt_threshold(share_count)
# generate the mnemonics
mnemonics = slip39.split_ems(
1, # Single Group threshold
[(threshold, shares_count)], # Single Group threshold/count
identifier,
iteration_exponent,
encrypted_master_secret,
)[0]
mnemonics = _get_slip39_mnemonics(
encrypted_master_secret, group_threshold, [(share_threshold, share_count)]
)
# show and confirm individual shares
await layout.slip39_show_checklist(2, BAK_T_SLIP39_BASIC)
await layout.slip39_basic_show_and_confirm_shares(mnemonics)
await layout.slip39_basic_show_and_confirm_shares(mnemonics[0])
async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
@ -155,13 +149,50 @@ async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
share_threshold = await layout.slip39_prompt_threshold(share_count, i)
groups.append((share_threshold, share_count))
mnemonics = _get_slip39_mnemonics(encrypted_master_secret, group_threshold, groups)
# show and confirm individual shares
await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
async def _backup_slip39_custom(
encrypted_master_secret: bytes,
group_threshold: int,
groups: Sequence[tuple[int, int]],
):
mnemonics = _get_slip39_mnemonics(encrypted_master_secret, group_threshold, groups)
# show and confirm individual shares
if len(groups) == 1 and groups[0][0] == 1 and groups[0][1] == 1:
# for a single 1-of-1 group, we use the same layouts as for BIP39
await layout.show_and_confirm_mnemonic(mnemonics[0][0])
else:
await confirm_action(
"warning_shamir_backup",
TR.reset__title_shamir_backup,
description=TR.reset__create_x_of_y_shamir_backup_template.format(
groups[0][0], groups[0][1]
),
verb=TR.buttons__continue,
)
if len(groups) == 1:
await layout.slip39_basic_show_and_confirm_shares(mnemonics[0])
else:
await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
def _get_slip39_mnemonics(
encrypted_master_secret: bytes,
group_threshold: int,
groups: Sequence[tuple[int, int]],
):
identifier = storage_device.get_slip39_identifier()
iteration_exponent = storage_device.get_slip39_iteration_exponent()
if identifier is None or iteration_exponent is None:
raise ValueError
# generate the mnemonics
mnemonics = slip39.split_ems(
return slip39.split_ems(
group_threshold,
groups,
identifier,
@ -169,9 +200,6 @@ async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
encrypted_master_secret,
)
# show and confirm individual shares
await layout.slip39_advanced_show_and_confirm_shares(mnemonics)
def _validate_reset_device(msg: ResetDevice) -> None:
from trezor.wire import UnexpectedMessage
@ -184,7 +212,7 @@ def _validate_reset_device(msg: ResetDevice) -> None:
BAK_T_SLIP39_BASIC,
BAK_T_SLIP39_ADVANCED,
):
raise ProcessError("Backup type not implemented.")
raise ProcessError("Backup type not implemented")
if backup_types.is_slip39_backup_type(backup_type):
if msg.strength not in (128, 256):
raise ProcessError("Invalid strength (has to be 128 or 256 bits)")
@ -213,10 +241,22 @@ def _compute_secret_from_entropy(
return secret
async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
if backup_type == BAK_T_SLIP39_BASIC:
async def backup_seed(
backup_type: BackupType,
mnemonic_secret: bytes,
group_threshold: int | None = None,
groups: Sequence[tuple[int, int]] = (),
) -> None:
# Either both should be defined or both should be missing: group_threshold, groups
assert (group_threshold is None) == (len(groups) == 0)
assert backup_type != BAK_T_BIP39 or group_threshold is None
if group_threshold is not None:
await _backup_slip39_custom(mnemonic_secret, group_threshold, groups)
elif backup_type == BAK_T_SLIP39_BASIC:
await _backup_slip39_basic(mnemonic_secret)
elif backup_type == BAK_T_SLIP39_ADVANCED:
await _backup_slip39_advanced(mnemonic_secret)
else:
await layout.bip39_show_and_confirm_mnemonic(mnemonic_secret.decode())
await layout.show_and_confirm_mnemonic(mnemonic_secret.decode())

@ -110,8 +110,9 @@ async def _show_confirmation_success(
num_of_shares: int | None = None,
group_index: int | None = None,
) -> None:
if share_index is None or num_of_shares is None: # it is a BIP39 backup
subheader = TR.reset__finished_verifying_seed
if share_index is None or num_of_shares is None:
# it is a BIP39 or a 1-of-1 SLIP39 backup
subheader = TR.reset__finished_verifying_wallet_backup
text = ""
elif share_index == num_of_shares - 1:
@ -150,10 +151,10 @@ async def _show_confirmation_failure() -> None:
)
async def show_backup_warning(slip39: bool = False) -> None:
async def show_backup_warning() -> None:
from trezor.ui.layouts.reset import show_warning_backup
await show_warning_backup(slip39)
await show_warning_backup()
async def show_backup_success() -> None:
@ -162,11 +163,11 @@ async def show_backup_success() -> None:
await show_success_backup()
# BIP39
# Simple setups: BIP39 or SLIP39 1-of-1
# ===
async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None:
async def show_and_confirm_mnemonic(mnemonic: str) -> None:
# warn user about mnemonic safety
await show_backup_warning()
@ -181,13 +182,13 @@ async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None:
break # mnemonic is confirmed, go next
# SLIP39
# Complex setups: SLIP39, except 1-of-1
# ===
async def slip39_basic_show_and_confirm_shares(shares: Sequence[str]) -> None:
# warn user about mnemonic safety
await show_backup_warning(True)
await show_backup_warning()
for index, share in enumerate(shares):
share_words = share.split(" ")
@ -204,7 +205,7 @@ async def slip39_advanced_show_and_confirm_shares(
shares: Sequence[Sequence[str]],
) -> None:
# warn user about mnemonic safety
await show_backup_warning(True)
await show_backup_warning()
for group_index, group in enumerate(shares):
for share_index, share in enumerate(group):

@ -38,7 +38,7 @@ from trezor.crypto import random
from trezor.errors import MnemonicError
if TYPE_CHECKING:
from typing import Callable, Iterable
from typing import Callable, Collection, Iterable
Indices = tuple[int, ...]
MnemonicGroups = dict[int, tuple[int, set[tuple[int, bytes]]]]
@ -174,7 +174,9 @@ def generate_random_identifier() -> int:
def split_ems(
group_threshold: int, # The number of groups required to reconstruct the master secret.
groups: list[tuple[int, int]], # A list of (member_threshold, member_count).
groups: Collection[
tuple[int, int]
], # A collection of (member_threshold, member_count).
identifier: int,
iteration_exponent: int,
encrypted_master_secret: bytes, # The encrypted master secret to split.

@ -2526,6 +2526,16 @@ if TYPE_CHECKING:
return isinstance(msg, cls)
class BackupDevice(protobuf.MessageType):
group_threshold: "int | None"
groups: "list[Slip39Group]"
def __init__(
self,
*,
groups: "list[Slip39Group] | None" = None,
group_threshold: "int | None" = None,
) -> None:
pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["BackupDevice"]:
@ -2739,6 +2749,22 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: Any) -> TypeGuard["UnlockBootloader"]:
return isinstance(msg, cls)
class Slip39Group(protobuf.MessageType):
member_threshold: "int"
member_count: "int"
def __init__(
self,
*,
member_threshold: "int",
member_count: "int",
) -> None:
pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["Slip39Group"]:
return isinstance(msg, cls)
class DebugLinkDecision(protobuf.MessageType):
button: "DebugButton | None"
swipe: "DebugSwipeDirection | None"

@ -258,7 +258,7 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
)
async def show_warning_backup(slip39: bool) -> None:
async def show_warning_backup() -> None:
await show_warning(
"backup_warning",
TR.words__title_remember,

@ -48,7 +48,7 @@ async def show_share_words(
group_index: int | None = None,
) -> None:
if share_index is None:
title = TR.reset__recovery_seed_title
title = TR.reset__recovery_wallet_backup_title
elif group_index is None:
title = TR.reset__recovery_share_title_template.format(share_index + 1)
else:
@ -80,7 +80,7 @@ async def select_word(
group_index: int | None = None,
) -> str:
if share_index is None:
title: str = TR.reset__check_seed_title
title: str = TR.reset__check_wallet_backup_title
elif group_index is None:
title = TR.reset__check_share_title_template.format(share_index + 1)
else:
@ -319,7 +319,7 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
)
async def show_warning_backup(slip39: bool) -> None:
async def show_warning_backup() -> None:
result = await interact(
RustLayout(
trezorui2.show_info(

@ -510,7 +510,7 @@
"recovery__enter_share_from_diff_group": "text,2",
"recovery__group_num_template": "text,1",
"recovery__group_threshold_reached": "text,2",
"recovery__invalid_seed_entered": "text,3",
"recovery__invalid_wallet_backup_entered": "text,3",
"recovery__invalid_share_entered": "text,3",
"recovery__more_shares_needed": "text,2",
"recovery__num_of_words": "text,2",
@ -544,12 +544,12 @@
"reset__by_continuing": "text,3",
"reset__check_backup_title": "title,1",
"reset__check_group_share_title_template": "title,1",
"reset__check_seed_title": "title,1",
"reset__check_wallet_backup_title": "title,1",
"reset__check_share_title_template": "title,1",
"reset__continue_with_next_share": "text,2",
"reset__continue_with_share_template": "text,2",
"reset__finished_verifying_group_template": "text,4",
"reset__finished_verifying_seed": "text,4",
"reset__finished_verifying_wallet_backup": "text,4",
"reset__finished_verifying_shares": "text,4",
"reset__group_description": "text,2",
"reset__group_info": "text,7",
@ -568,7 +568,7 @@
"reset__number_of_shares_info": "text,3",
"reset__one_share": "text,1",
"reset__only_one_share_will_be_created": "text,2",
"reset__recovery_seed_title": "title,1",
"reset__recovery_wallet_backup_title": "title,1",
"reset__recovery_share_title_template": "title,1",
"reset__required_number_of_groups": "text,3",
"reset__select_correct_word": "text,3",

@ -544,7 +544,7 @@
"recovery__enter_share_from_diff_group": "Zadejte část z jiné skupiny.",
"recovery__group_num_template": "Skupina {0}",
"recovery__group_threshold_reached": "Dosaženo počtu částí zálohy skupiny.",
"recovery__invalid_seed_entered": "Zadali jste neplatnou zálohu.",
"recovery__invalid_wallet_backup_entered": "Zadali jste neplatnou zálohu.",
"recovery__invalid_share_entered": "Zadali jste neplatnou část zálohy.",
"recovery__more_shares_needed": "Je potřeba více částí",
"recovery__num_of_words": "Vyberte počet slov v záloze.",
@ -578,12 +578,12 @@
"reset__by_continuing": "Pokračováním přijímáte smluvní podmínky společnosti Trezor.",
"reset__check_backup_title": "ZKONTR. ZÁLOHU",
"reset__check_group_share_title_template": "ZKONT. G{0}-ČÁST{1}",
"reset__check_seed_title": "ZKONTROLOVAT SEED",
"reset__check_wallet_backup_title": "ZKONTROLOVAT SEED",
"reset__check_share_title_template": "ZKONTROL. ČÁST #{0}",
"reset__continue_with_next_share": "Pokračujte další částí.",
"reset__continue_with_share_template": "Pokračujte částí #{0}.",
"reset__finished_verifying_group_template": "Ověřili jste části zálohy skupiny {0}.",
"reset__finished_verifying_seed": "Dokončili jste ověření zálohy.",
"reset__finished_verifying_wallet_backup": "Dokončili jste ověření zálohy.",
"reset__finished_verifying_shares": "Dokončili jste ověření částí zálohy.",
"reset__group_description": "Skupina se skládá z částí zálohy.",
"reset__group_info": "Každá skupina má stanovený počet částí a vlastní počet částí zálohy pro obnovu. V dalších krocích nastavíte počty částí a počet pro obnovu.",
@ -602,7 +602,7 @@
"reset__number_of_shares_info": "= počet seznamů jedinečných slov u zálohy peněženky.",
"reset__one_share": "1 část",
"reset__only_one_share_will_be_created": "Bude vytvořena pouze jedna část.",
"reset__recovery_seed_title": "ZÁLOHA",
"reset__recovery_wallet_backup_title": "ZÁLOHA",
"reset__recovery_share_title_template": "ČÁST ZÁLOHY Č. {0}",
"reset__required_number_of_groups": "Požadovaný počet skupin pro obnovení.",
"reset__select_correct_word": "Pro každou pozici vyberte správné slovo.",

@ -544,7 +544,7 @@
"recovery__enter_share_from_diff_group": "Share von anderer Gruppe eingeben.",
"recovery__group_num_template": "Gruppe {0}",
"recovery__group_threshold_reached": "Gruppenschwelle wurde erreicht.",
"recovery__invalid_seed_entered": "Eingegebener Recovery Seed ist ungültig.",
"recovery__invalid_wallet_backup_entered": "Eingegebener Recovery Seed ist ungültig.",
"recovery__invalid_share_entered": "Eingegebener Recovery Share ist ungültig.",
"recovery__more_shares_needed": "Weitere Shares erforderlich",
"recovery__num_of_words": "Wörteranzahl in deinem Backup auswählen.",
@ -578,12 +578,12 @@
"reset__by_continuing": "Du stimmst den Geschäftsbedingungen von Trezor Company zu.",
"reset__check_backup_title": "BACKUP ÜBERPRÜFEN",
"reset__check_group_share_title_template": "G{0}-SHARE {1} PRÜFEN",
"reset__check_seed_title": "SEED ÜBERPRÜFEN",
"reset__check_wallet_backup_title": "SEED ÜBERPRÜFEN",
"reset__check_share_title_template": "SHARE #{0} PRÜFEN",
"reset__continue_with_next_share": "Fahre mit dem nächsten Share fort.",
"reset__continue_with_share_template": "Fahre mit Share #{0} fort.",
"reset__finished_verifying_group_template": "Verifizierung deiner Recovery Shares für Gruppe {0} ist abgeschlossen.",
"reset__finished_verifying_seed": "Die Verifizierung deines Recovery Seeds ist abgeschlossen.",
"reset__finished_verifying_wallet_backup": "Die Verifizierung deines Recovery Seeds ist abgeschlossen.",
"reset__finished_verifying_shares": "Die Verifizierung deiner Recovery Shares ist abgeschlossen.",
"reset__group_description": "Eine Gruppe besteht aus Recovery Shares.",
"reset__group_info": "Jede Gruppe hat eine feste Share-Anzahl und eine eigene Schwelle. Als nächstes legst du die Anzahl der Shares und die Schwellen fest. In den nächsten Schritten legst du die Anzahl der Shares und die Schwellen fest.",
@ -602,7 +602,7 @@
"reset__number_of_shares_info": "= Gesamtzahl eindeutiger Wortlisten für Wallet-Backup.",
"reset__one_share": "1 Share",
"reset__only_one_share_will_be_created": "Es wird nur ein Share erstellt.",
"reset__recovery_seed_title": "RECOVERY SEED",
"reset__recovery_wallet_backup_title": "RECOVERY SEED",
"reset__recovery_share_title_template": "RECOVERY SHARE #{0}",
"reset__required_number_of_groups": "Zur Wiederherstellung erforderliche Anzahl von Gruppen.",
"reset__select_correct_word": "Wähle für jede Position das richtige Wort.",

@ -511,8 +511,8 @@
"recovery__cancel_dry_run": "Cancel backup check",
"recovery__check_dry_run": "Check your backup?",
"recovery__cursor_will_change": "Position of the cursor will change between entries for enhanced security.",
"recovery__dry_run_bip39_valid_match": "The entered recovery seed is valid and matches the one in the device.",
"recovery__dry_run_bip39_valid_mismatch": "The entered recovery seed is valid but does not match the one in the device.",
"recovery__dry_run_bip39_valid_match": "The entered recovery wallet backup is valid and matches the one in the device.",
"recovery__dry_run_bip39_valid_mismatch": "The entered wallet backup is valid but does not match the one in the device.",
"recovery__dry_run_slip39_valid_match": "The entered recovery shares are valid and match what is currently in the device.",
"recovery__dry_run_slip39_valid_mismatch": "The entered recovery shares are valid but do not match what is currently in the device.",
"recovery__enter_any_share": "Enter any share",
@ -521,7 +521,7 @@
"recovery__enter_share_from_diff_group": "Enter share from a different group.",
"recovery__group_num_template": "Group {0}",
"recovery__group_threshold_reached": "Group threshold reached.",
"recovery__invalid_seed_entered": "Invalid recovery seed entered.",
"recovery__invalid_wallet_backup_entered": "Invalid wallet backup entered.",
"recovery__invalid_share_entered": "Invalid recovery share entered.",
"recovery__more_shares_needed": "More shares needed",
"recovery__num_of_words": "Select the number of words in your backup.",
@ -555,12 +555,13 @@
"reset__by_continuing": "By continuing you agree to Trezor Company's terms and conditions.",
"reset__check_backup_title": "CHECK BACKUP",
"reset__check_group_share_title_template": "CHECK G{0} - SHARE {1}",
"reset__check_seed_title": "CHECK SEED",
"reset__check_wallet_backup_title": "CHECK WALLET BACKUP",
"reset__check_share_title_template": "CHECK SHARE #{0}",
"reset__continue_with_next_share": "Continue with the next share.",
"reset__continue_with_share_template": "Continue with share #{0}.",
"reset__create_x_of_y_shamir_backup_template": "Do you want to create a {0} of {1} Shamir backup?",
"reset__finished_verifying_group_template": "You have finished verifying your recovery shares for group {0}.",
"reset__finished_verifying_seed": "You have finished verifying your recovery seed.",
"reset__finished_verifying_wallet_backup": "You have finished verifying your wallet backup.",
"reset__finished_verifying_shares": "You have finished verifying your recovery shares.",
"reset__group_description": "A group is made up of recovery shares.",
"reset__group_info": "Each group has a set number of shares and its own threshold. In the next steps you will set the numbers of shares and the thresholds.",
@ -571,7 +572,7 @@
"reset__need_any_share_template": "For recovery you need any {0} of the shares.",
"reset__needed_to_form_a_group": "needed to form a group. ",
"reset__needed_to_recover_your_wallet": "needed to recover your wallet. ",
"reset__never_make_digital_copy": "Never make a digital copy of your backup or upload it online!",
"reset__never_make_digital_copy": "Never put your backup anywhere digital.",
"reset__num_of_share_holders_template": "{0} people or locations will each hold one share.",
"reset__num_of_shares_advanced_info_template": "Each recovery share is a sequence of 20 words. Next you will choose the threshold number of shares needed to form Group {0}.",
"reset__num_of_shares_basic_info": "Each recovery share is a sequence of 20 words. Next you will choose how many shares you need to recover your wallet.",
@ -579,7 +580,7 @@
"reset__number_of_shares_info": "= total number of unique word lists used for wallet backup.",
"reset__one_share": "1 share",
"reset__only_one_share_will_be_created": "Only one share will be created.",
"reset__recovery_seed_title": "RECOVERY SEED",
"reset__recovery_wallet_backup_title": "WALLET BACKUP",
"reset__recovery_share_title_template": "RECOVERY SHARE #{0}",
"reset__required_number_of_groups": "The required number of groups for recovery.",
"reset__select_correct_word": "Select the correct word for each position.",
@ -610,6 +611,7 @@
"reset__title_set_number_of_groups": "SET NUMBER OF GROUPS",
"reset__title_set_number_of_shares": "SET NUMBER OF SHARES",
"reset__title_set_threshold": "SET THRESHOLD",
"reset__title_shamir_backup": "SHAMIR BACKUP",
"reset__to_form_group_template": "to form Group {0}.",
"reset__tos_link": "trezor.io/tos",
"reset__total_number_of_shares_in_group_template": "Set the total number of shares in Group {0}.",

@ -544,7 +544,7 @@
"recovery__enter_share_from_diff_group": "Intro. rec. comp. de otro grupo.",
"recovery__group_num_template": "Grupo {0}",
"recovery__group_threshold_reached": "Se ha alcanzado el umbral de grupo.",
"recovery__invalid_seed_entered": "Se ha introducido una semilla de recuperación no válida.",
"recovery__invalid_wallet_backup_entered": "Se ha introducido una semilla de recuperación no válida.",
"recovery__invalid_share_entered": "Se ha introducido un recurso compartido de recuperación no válido.",
"recovery__more_shares_needed": "Se necesitan más recursos compartidos.",
"recovery__num_of_words": "Elige el nro.de p. de la copia seg.",
@ -578,12 +578,12 @@
"reset__by_continuing": "Si continúas, aceptas los términos de Trezor Company.",
"reset__check_backup_title": "REVISAR C. SEG.",
"reset__check_group_share_title_template": "REVISAR REC. {0} G {1}",
"reset__check_seed_title": "REVISAR SEMILLA",
"reset__check_wallet_backup_title": "REVISAR SEMILLA",
"reset__check_share_title_template": "REVISAR REC. nro.{0}",
"reset__continue_with_next_share": "Continuar con el próximo rec. comp.",
"reset__continue_with_share_template": "Continuar con el recurso nro.{0}.",
"reset__finished_verifying_group_template": "Fin de la verificación de los rec. comp. de recuperación del grupo {0}.",
"reset__finished_verifying_seed": "Fin de la verificación de la semilla de recuperación.",
"reset__finished_verifying_wallet_backup": "Fin de la verificación de la semilla de recuperación.",
"reset__finished_verifying_shares": "Fin de la verificación de los rec. comp. de recuperación.",
"reset__group_description": "Un grupo consta de rec. comp. de recuperación.",
"reset__group_info": "Cada grupo tiene un nro.de rec. comp. y su propio umbral. En los siguientes pasos, ajustarás el nro.de rec. comp. y los umbrales.",
@ -602,7 +602,7 @@
"reset__number_of_shares_info": "= total de listas de palabras únicas para la copia de seguridad.",
"reset__one_share": "1 recurso compartido",
"reset__only_one_share_will_be_created": "Solo se creará un recurso compartido.",
"reset__recovery_seed_title": "SEMILLA RECUP.",
"reset__recovery_wallet_backup_title": "SEMILLA RECUP.",
"reset__recovery_share_title_template": "REC. RECUP. nro.{0}",
"reset__required_number_of_groups": "El nro.de grupos necesario para la recuperación.",
"reset__select_correct_word": "Selecciona la palabra correcta para cada posición.",

@ -544,7 +544,7 @@
"recovery__enter_share_from_diff_group": "Saisissez un fragm. d'un autre groupe.",
"recovery__group_num_template": "Groupe {0}",
"recovery__group_threshold_reached": "Seuil de groupe atteint.",
"recovery__invalid_seed_entered": "Seed de récup. saisie non valide.",
"recovery__invalid_wallet_backup_entered": "Seed de récup. saisie non valide.",
"recovery__invalid_share_entered": "Fragm. de récup. saisi non valide.",
"recovery__more_shares_needed": "Plus de fragm. requis",
"recovery__num_of_words": "Sélect. le nbre de mots dans votre sauv.",
@ -578,12 +578,12 @@
"reset__by_continuing": "En continuant, vous acceptez les conditions générales de Trezor.",
"reset__check_backup_title": "VÉRIF. SAUVEGARDE",
"reset__check_group_share_title_template": "VÉR. G{0}- FRAGM. {1}",
"reset__check_seed_title": "VÉRIFIER LA SEED",
"reset__check_wallet_backup_title": "VÉRIFIER LA SEED",
"reset__check_share_title_template": "VÉRIFIER FRAGM. #{0}",
"reset__continue_with_next_share": "Continuez avec le fragm. suivant.",
"reset__continue_with_share_template": "Continuez avec le fragm. #{0}.",
"reset__finished_verifying_group_template": "Vous avez terminé la vér. de vos fragm. de récup. pour le groupe {0}.",
"reset__finished_verifying_seed": "Votre seed de récup. est vérifiée.",
"reset__finished_verifying_wallet_backup": "Votre seed de récup. est vérifiée.",
"reset__finished_verifying_shares": "Vous avez terminé la vér. de vos fragm. de récup.",
"reset__group_description": "Un groupe est composé de fragm. de récup.",
"reset__group_info": "Chaque groupe a un nbre défini de fragm. et un seuil spécifique. Dans les étapes suivantes, vous allez déf. le nbre de fragm. et les seuils.",
@ -602,7 +602,7 @@
"reset__number_of_shares_info": "= nbre total de listes de mots uniques pour la sauv. du portef.",
"reset__one_share": "1 fragm.",
"reset__only_one_share_will_be_created": "Un seul fragm. sera créé.",
"reset__recovery_seed_title": "SEED DE RÉCUP.",
"reset__recovery_wallet_backup_title": "SEED DE RÉCUP.",
"reset__recovery_share_title_template": "FRAGM. DE RÉCUP. #{0}",
"reset__required_number_of_groups": "Le nbre de groupes requis pour la récup.",
"reset__select_correct_word": "Sélect. le mot correct par emplacement.",

@ -507,7 +507,7 @@
"505": "recovery__enter_share_from_diff_group",
"506": "recovery__group_num_template",
"507": "recovery__group_threshold_reached",
"508": "recovery__invalid_seed_entered",
"508": "recovery__invalid_wallet_backup_entered",
"509": "recovery__invalid_share_entered",
"510": "recovery__more_shares_needed",
"511": "recovery__num_of_words",
@ -541,12 +541,12 @@
"539": "reset__by_continuing",
"540": "reset__check_backup_title",
"541": "reset__check_group_share_title_template",
"542": "reset__check_seed_title",
"542": "reset__check_wallet_backup_title",
"543": "reset__check_share_title_template",
"544": "reset__continue_with_next_share",
"545": "reset__continue_with_share_template",
"546": "reset__finished_verifying_group_template",
"547": "reset__finished_verifying_seed",
"547": "reset__finished_verifying_wallet_backup",
"548": "reset__finished_verifying_shares",
"549": "reset__group_description",
"550": "reset__group_info",
@ -565,7 +565,7 @@
"563": "reset__number_of_shares_info",
"564": "reset__one_share",
"565": "reset__only_one_share_will_be_created",
"566": "reset__recovery_seed_title",
"566": "reset__recovery_wallet_backup_title",
"567": "reset__recovery_share_title_template",
"568": "reset__required_number_of_groups",
"569": "reset__select_correct_word",
@ -843,5 +843,7 @@
"841": "ethereum__staking_unstake_intro",
"842": "storage_msg__starting",
"843": "storage_msg__verifying_pin",
"844": "storage_msg__wrong_pin"
"844": "storage_msg__wrong_pin",
"845": "reset__create_x_of_y_shamir_backup_template",
"846": "reset__title_shamir_backup"
}

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "83f62541670df59f347bfdf577f36c1ef504f769fd8a498c0f76aa78f9021cda",
"datetime": "2024-03-30T11:28:31.004037",
"commit": "1c36c0927c33fd05b4eff4b3a020940e33074b54"
"merkle_root": "fe1f2942f8723f3f65fe7972de93e1dcdd214cc14be8251c44b7047808c10e11",
"datetime": "2024-04-24T15:46:21.632186",
"commit": "c52db3a8deae36eb2becc96f4917ea7164656f76"
},
"history": [
{

@ -26,6 +26,8 @@ LoadDevice.label max_size:33
ResetDevice.language max_size:17
ResetDevice.label max_size:33
BackupDevice.groups type:FT_IGNORE
Entropy.entropy max_size:1024
EntropyAck.entropy max_size:128

@ -0,0 +1 @@
Added ability to request Shamir backups with any number of groups/shares.

@ -16,7 +16,7 @@
import secrets
import sys
from typing import TYPE_CHECKING, Optional, Sequence
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
import click
@ -237,10 +237,17 @@ def setup(
@cli.command()
@click.option("-t", "--group-threshold", type=int)
@click.option("-g", "--group", "groups", type=(int, int), multiple=True, metavar="T N")
@with_client
def backup(client: "TrezorClient") -> str:
def backup(
client: "TrezorClient",
group_threshold: Optional[int] = None,
groups: Sequence[Tuple[int, int]] = (),
) -> str:
"""Perform device seed backup."""
return device.backup(client)
return device.backup(client, group_threshold, groups)
@cli.command()

@ -19,7 +19,7 @@ from __future__ import annotations
import os
import time
import warnings
from typing import TYPE_CHECKING, Callable, Optional
from typing import TYPE_CHECKING, Callable, Iterable, Optional
from . import messages
from .exceptions import Cancelled, TrezorException
@ -264,8 +264,20 @@ def reset(
@expect(messages.Success, field="message", ret_type=str)
@session
def backup(client: "TrezorClient") -> "MessageType":
ret = client.call(messages.BackupDevice())
def backup(
client: "TrezorClient",
group_threshold: Optional[int] = None,
groups: Iterable[tuple[int, int]] = (),
) -> "MessageType":
ret = client.call(
messages.BackupDevice(
group_threshold=group_threshold,
groups=[
messages.Slip39Group(member_threshold=t, member_count=c)
for t, c in groups
],
)
)
client.refresh_features()
return ret

@ -3685,6 +3685,19 @@ class ResetDevice(protobuf.MessageType):
class BackupDevice(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 34
FIELDS = {
1: protobuf.Field("group_threshold", "uint32", repeated=False, required=False, default=None),
2: protobuf.Field("groups", "Slip39Group", repeated=True, required=False, default=None),
}
def __init__(
self,
*,
groups: Optional[Sequence["Slip39Group"]] = None,
group_threshold: Optional["int"] = None,
) -> None:
self.groups: Sequence["Slip39Group"] = groups if groups is not None else []
self.group_threshold = group_threshold
class EntropyRequest(protobuf.MessageType):
@ -3892,6 +3905,23 @@ class UnlockBootloader(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 96
class Slip39Group(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {
1: protobuf.Field("member_threshold", "uint32", repeated=False, required=True),
2: protobuf.Field("member_count", "uint32", repeated=False, required=True),
}
def __init__(
self,
*,
member_threshold: "int",
member_count: "int",
) -> None:
self.member_threshold = member_threshold
self.member_count = member_count
class DebugLinkDecision(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 100
FIELDS = {

@ -7012,6 +7012,11 @@ impl ::protobuf::reflect::ProtobufValue for ResetDevice {
// @@protoc_insertion_point(message:hw.trezor.messages.management.BackupDevice)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct BackupDevice {
// message fields
// @@protoc_insertion_point(field:hw.trezor.messages.management.BackupDevice.group_threshold)
pub group_threshold: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.BackupDevice.groups)
pub groups: ::std::vec::Vec<backup_device::Slip39Group>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.management.BackupDevice.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@ -7028,9 +7033,38 @@ impl BackupDevice {
::std::default::Default::default()
}
// optional uint32 group_threshold = 1;
pub fn group_threshold(&self) -> u32 {
self.group_threshold.unwrap_or(0)
}
pub fn clear_group_threshold(&mut self) {
self.group_threshold = ::std::option::Option::None;
}
pub fn has_group_threshold(&self) -> bool {
self.group_threshold.is_some()
}
// Param is passed by value, moved
pub fn set_group_threshold(&mut self, v: u32) {
self.group_threshold = ::std::option::Option::Some(v);
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(0);
let mut fields = ::std::vec::Vec::with_capacity(2);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"group_threshold",
|m: &BackupDevice| { &m.group_threshold },
|m: &mut BackupDevice| { &mut m.group_threshold },
));
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
"groups",
|m: &BackupDevice| { &m.groups },
|m: &mut BackupDevice| { &mut m.groups },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<BackupDevice>(
"BackupDevice",
fields,
@ -7043,12 +7077,23 @@ impl ::protobuf::Message for BackupDevice {
const NAME: &'static str = "BackupDevice";
fn is_initialized(&self) -> bool {
for v in &self.groups {
if !v.is_initialized() {
return false;
}
};
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
8 => {
self.group_threshold = ::std::option::Option::Some(is.read_uint32()?);
},
18 => {
self.groups.push(is.read_message()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@ -7061,12 +7106,25 @@ impl ::protobuf::Message for BackupDevice {
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if let Some(v) = self.group_threshold {
my_size += ::protobuf::rt::uint32_size(1, v);
}
for value in &self.groups {
let len = value.compute_size();
my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len;
};
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if let Some(v) = self.group_threshold {
os.write_uint32(1, v)?;
}
for v in &self.groups {
::protobuf::rt::write_message_field_with_cached_size(2, v, os)?;
};
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@ -7084,11 +7142,15 @@ impl ::protobuf::Message for BackupDevice {
}
fn clear(&mut self) {
self.group_threshold = ::std::option::Option::None;
self.groups.clear();
self.special_fields.clear();
}
fn default_instance() -> &'static BackupDevice {
static instance: BackupDevice = BackupDevice {
group_threshold: ::std::option::Option::None,
groups: ::std::vec::Vec::new(),
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
@ -7112,6 +7174,193 @@ impl ::protobuf::reflect::ProtobufValue for BackupDevice {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
/// Nested message and enums of message `BackupDevice`
pub mod backup_device {
// @@protoc_insertion_point(message:hw.trezor.messages.management.BackupDevice.Slip39Group)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct Slip39Group {
// message fields
// @@protoc_insertion_point(field:hw.trezor.messages.management.BackupDevice.Slip39Group.member_threshold)
pub member_threshold: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.BackupDevice.Slip39Group.member_count)
pub member_count: ::std::option::Option<u32>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.management.BackupDevice.Slip39Group.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a Slip39Group {
fn default() -> &'a Slip39Group {
<Slip39Group as ::protobuf::Message>::default_instance()
}
}
impl Slip39Group {
pub fn new() -> Slip39Group {
::std::default::Default::default()
}
// required uint32 member_threshold = 1;
pub fn member_threshold(&self) -> u32 {
self.member_threshold.unwrap_or(0)
}
pub fn clear_member_threshold(&mut self) {
self.member_threshold = ::std::option::Option::None;
}
pub fn has_member_threshold(&self) -> bool {
self.member_threshold.is_some()
}
// Param is passed by value, moved
pub fn set_member_threshold(&mut self, v: u32) {
self.member_threshold = ::std::option::Option::Some(v);
}
// required uint32 member_count = 2;
pub fn member_count(&self) -> u32 {
self.member_count.unwrap_or(0)
}
pub fn clear_member_count(&mut self) {
self.member_count = ::std::option::Option::None;
}
pub fn has_member_count(&self) -> bool {
self.member_count.is_some()
}
// Param is passed by value, moved
pub fn set_member_count(&mut self, v: u32) {
self.member_count = ::std::option::Option::Some(v);
}
pub(in super) fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(2);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"member_threshold",
|m: &Slip39Group| { &m.member_threshold },
|m: &mut Slip39Group| { &mut m.member_threshold },
));
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"member_count",
|m: &Slip39Group| { &m.member_count },
|m: &mut Slip39Group| { &mut m.member_count },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<Slip39Group>(
"BackupDevice.Slip39Group",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for Slip39Group {
const NAME: &'static str = "Slip39Group";
fn is_initialized(&self) -> bool {
if self.member_threshold.is_none() {
return false;
}
if self.member_count.is_none() {
return false;
}
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> {
while let Some(tag) = is.read_raw_tag_or_eof()? {
match tag {
8 => {
self.member_threshold = ::std::option::Option::Some(is.read_uint32()?);
},
16 => {
self.member_count = ::std::option::Option::Some(is.read_uint32()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u64 {
let mut my_size = 0;
if let Some(v) = self.member_threshold {
my_size += ::protobuf::rt::uint32_size(1, v);
}
if let Some(v) = self.member_count {
my_size += ::protobuf::rt::uint32_size(2, v);
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> {
if let Some(v) = self.member_threshold {
os.write_uint32(1, v)?;
}
if let Some(v) = self.member_count {
os.write_uint32(2, v)?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
fn special_fields(&self) -> &::protobuf::SpecialFields {
&self.special_fields
}
fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields {
&mut self.special_fields
}
fn new() -> Slip39Group {
Slip39Group::new()
}
fn clear(&mut self) {
self.member_threshold = ::std::option::Option::None;
self.member_count = ::std::option::Option::None;
self.special_fields.clear();
}
fn default_instance() -> &'static Slip39Group {
static instance: Slip39Group = Slip39Group {
member_threshold: ::std::option::Option::None,
member_count: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for Slip39Group {
fn descriptor() -> ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new();
descriptor.get(|| super::file_descriptor().message_by_package_relative_name("BackupDevice.Slip39Group").unwrap()).clone()
}
}
impl ::std::fmt::Display for Slip39Group {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for Slip39Group {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
}
// @@protoc_insertion_point(message:hw.trezor.messages.management.EntropyRequest)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct EntropyRequest {
@ -10420,45 +10669,50 @@ static file_descriptor_proto_data: &'static [u8] = b"\
l\x12\x1f\n\x0bu2f_counter\x18\x07\x20\x01(\rR\nu2fCounter\x12\x1f\n\x0b\
skip_backup\x18\x08\x20\x01(\x08R\nskipBackup\x12\x1b\n\tno_backup\x18\t\
\x20\x01(\x08R\x08noBackup\x12Q\n\x0bbackup_type\x18\n\x20\x01(\x0e2).hw\
.trezor.messages.management.BackupType:\x05Bip39R\nbackupType\"\x0e\n\
\x0cBackupDevice\"\x10\n\x0eEntropyRequest\"&\n\nEntropyAck\x12\x18\n\
\x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\"\xd8\x03\n\x0eRecoveryDevi\
ce\x12\x1d\n\nword_count\x18\x01\x20\x01(\rR\twordCount\x123\n\x15passph\
rase_protection\x18\x02\x20\x01(\x08R\x14passphraseProtection\x12%\n\x0e\
pin_protection\x18\x03\x20\x01(\x08R\rpinProtection\x12\x1e\n\x08languag\
e\x18\x04\x20\x01(\tR\x08languageB\x02\x18\x01\x12\x14\n\x05label\x18\
\x05\x20\x01(\tR\x05label\x12)\n\x10enforce_wordlist\x18\x06\x20\x01(\
\x08R\x0fenforceWordlist\x12T\n\x04type\x18\x08\x20\x01(\x0e2@.hw.trezor\
.messages.management.RecoveryDevice.RecoveryDeviceTypeR\x04type\x12\x1f\
\n\x0bu2f_counter\x18\t\x20\x01(\rR\nu2fCounter\x12\x17\n\x07dry_run\x18\
\n\x20\x01(\x08R\x06dryRun\"Z\n\x12RecoveryDeviceType\x12%\n!RecoveryDev\
iceType_ScrambledWords\x10\0\x12\x1d\n\x19RecoveryDeviceType_Matrix\x10\
\x01\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\x18\x01\x20\x02(\x0e2:.hw\
.trezor.messages.management.WordRequest.WordRequestTypeR\x04type\"f\n\
\x0fWordRequestType\x12\x19\n\x15WordRequestType_Plain\x10\0\x12\x1b\n\
\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17WordRequestType_Matrix6\
\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\x01\x20\x02(\tR\x04wor\
d\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fC\
ounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNextU2FCounter\x12\x1f\n\x0b\
u2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\x11\n\x0fDoPreauthorized\"\
\x16\n\x14PreauthorizedRequest\"\x15\n\x13CancelAuthorization\"\x9a\x02\
\n\x12RebootToBootloader\x12o\n\x0cboot_command\x18\x01\x20\x01(\x0e2=.h\
w.trezor.messages.management.RebootToBootloader.BootCommand:\rSTOP_AND_W\
AITR\x0bbootCommand\x12'\n\x0ffirmware_header\x18\x02\x20\x01(\x0cR\x0ef\
irmwareHeader\x123\n\x14language_data_length\x18\x03\x20\x01(\r:\x010R\
\x12languageDataLength\"5\n\x0bBootCommand\x12\x11\n\rSTOP_AND_WAIT\x10\
\0\x12\x13\n\x0fINSTALL_UPGRADE\x10\x01\"\x10\n\x08GetNonce:\x04\x88\xb2\
\x19\x01\"#\n\x05Nonce\x12\x14\n\x05nonce\x18\x01\x20\x02(\x0cR\x05nonce\
:\x04\x88\xb2\x19\x01\";\n\nUnlockPath\x12\x1b\n\taddress_n\x18\x01\x20\
\x03(\rR\x08addressN\x12\x10\n\x03mac\x18\x02\x20\x01(\x0cR\x03mac\"'\n\
\x13UnlockedPathRequest\x12\x10\n\x03mac\x18\x01\x20\x01(\x0cR\x03mac\"\
\x14\n\x12ShowDeviceTutorial\"\x12\n\x10UnlockBootloader*>\n\nBackupType\
\x12\t\n\x05Bip39\x10\0\x12\x10\n\x0cSlip39_Basic\x10\x01\x12\x13\n\x0fS\
lip39_Advanced\x10\x02*G\n\x10SafetyCheckLevel\x12\n\n\x06Strict\x10\0\
\x12\x10\n\x0cPromptAlways\x10\x01\x12\x15\n\x11PromptTemporarily\x10\
\x02*0\n\x10HomescreenFormat\x12\x08\n\x04Toif\x10\x01\x12\x08\n\x04Jpeg\
\x10\x02\x12\x08\n\x04ToiG\x10\x03BB\n#com.satoshilabs.trezor.lib.protob\
ufB\x17TrezorMessageManagement\x80\xa6\x1d\x01\
.trezor.messages.management.BackupType:\x05Bip39R\nbackupType\"\xe5\x01\
\n\x0cBackupDevice\x12'\n\x0fgroup_threshold\x18\x01\x20\x01(\rR\x0egrou\
pThreshold\x12O\n\x06groups\x18\x02\x20\x03(\x0b27.hw.trezor.messages.ma\
nagement.BackupDevice.Slip39GroupR\x06groups\x1a[\n\x0bSlip39Group\x12)\
\n\x10member_threshold\x18\x01\x20\x02(\rR\x0fmemberThreshold\x12!\n\x0c\
member_count\x18\x02\x20\x02(\rR\x0bmemberCount\"\x10\n\x0eEntropyReques\
t\"&\n\nEntropyAck\x12\x18\n\x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\
\"\xd8\x03\n\x0eRecoveryDevice\x12\x1d\n\nword_count\x18\x01\x20\x01(\rR\
\twordCount\x123\n\x15passphrase_protection\x18\x02\x20\x01(\x08R\x14pas\
sphraseProtection\x12%\n\x0epin_protection\x18\x03\x20\x01(\x08R\rpinPro\
tection\x12\x1e\n\x08language\x18\x04\x20\x01(\tR\x08languageB\x02\x18\
\x01\x12\x14\n\x05label\x18\x05\x20\x01(\tR\x05label\x12)\n\x10enforce_w\
ordlist\x18\x06\x20\x01(\x08R\x0fenforceWordlist\x12T\n\x04type\x18\x08\
\x20\x01(\x0e2@.hw.trezor.messages.management.RecoveryDevice.RecoveryDev\
iceTypeR\x04type\x12\x1f\n\x0bu2f_counter\x18\t\x20\x01(\rR\nu2fCounter\
\x12\x17\n\x07dry_run\x18\n\x20\x01(\x08R\x06dryRun\"Z\n\x12RecoveryDevi\
ceType\x12%\n!RecoveryDeviceType_ScrambledWords\x10\0\x12\x1d\n\x19Recov\
eryDeviceType_Matrix\x10\x01\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\
\x18\x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordRe\
questTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_\
Plain\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17Wo\
rdRequestType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\
\x01\x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\
\x18\x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNe\
xtU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\
\x11\n\x0fDoPreauthorized\"\x16\n\x14PreauthorizedRequest\"\x15\n\x13Can\
celAuthorization\"\x9a\x02\n\x12RebootToBootloader\x12o\n\x0cboot_comman\
d\x18\x01\x20\x01(\x0e2=.hw.trezor.messages.management.RebootToBootloade\
r.BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\n\x0ffirmware_header\
\x18\x02\x20\x01(\x0cR\x0efirmwareHeader\x123\n\x14language_data_length\
\x18\x03\x20\x01(\r:\x010R\x12languageDataLength\"5\n\x0bBootCommand\x12\
\x11\n\rSTOP_AND_WAIT\x10\0\x12\x13\n\x0fINSTALL_UPGRADE\x10\x01\"\x10\n\
\x08GetNonce:\x04\x88\xb2\x19\x01\"#\n\x05Nonce\x12\x14\n\x05nonce\x18\
\x01\x20\x02(\x0cR\x05nonce:\x04\x88\xb2\x19\x01\";\n\nUnlockPath\x12\
\x1b\n\taddress_n\x18\x01\x20\x03(\rR\x08addressN\x12\x10\n\x03mac\x18\
\x02\x20\x01(\x0cR\x03mac\"'\n\x13UnlockedPathRequest\x12\x10\n\x03mac\
\x18\x01\x20\x01(\x0cR\x03mac\"\x14\n\x12ShowDeviceTutorial\"\x12\n\x10U\
nlockBootloader*>\n\nBackupType\x12\t\n\x05Bip39\x10\0\x12\x10\n\x0cSlip\
39_Basic\x10\x01\x12\x13\n\x0fSlip39_Advanced\x10\x02*G\n\x10SafetyCheck\
Level\x12\n\n\x06Strict\x10\0\x12\x10\n\x0cPromptAlways\x10\x01\x12\x15\
\n\x11PromptTemporarily\x10\x02*0\n\x10HomescreenFormat\x12\x08\n\x04Toi\
f\x10\x01\x12\x08\n\x04Jpeg\x10\x02\x12\x08\n\x04ToiG\x10\x03BB\n#com.sa\
toshilabs.trezor.lib.protobufB\x17TrezorMessageManagement\x80\xa6\x1d\
\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
@ -10477,7 +10731,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
let generated_file_descriptor = generated_file_descriptor_lazy.get(|| {
let mut deps = ::std::vec::Vec::with_capacity(1);
deps.push(super::messages::file_descriptor().clone());
let mut messages = ::std::vec::Vec::with_capacity(44);
let mut messages = ::std::vec::Vec::with_capacity(45);
messages.push(Initialize::generated_message_descriptor_data());
messages.push(GetFeatures::generated_message_descriptor_data());
messages.push(Features::generated_message_descriptor_data());
@ -10522,6 +10776,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
messages.push(UnlockedPathRequest::generated_message_descriptor_data());
messages.push(ShowDeviceTutorial::generated_message_descriptor_data());
messages.push(UnlockBootloader::generated_message_descriptor_data());
messages.push(backup_device::Slip39Group::generated_message_descriptor_data());
let mut enums = ::std::vec::Vec::with_capacity(8);
enums.push(BackupType::generated_enum_descriptor_data());
enums.push(SafetyCheckLevel::generated_enum_descriptor_data());

@ -38,6 +38,13 @@ def confirm_read(debug: "DebugLink", middle_r: bool = False) -> None:
debug.press_right(wait=True)
def cancel_backup(debug: "DebugLink", middle_r: bool = False) -> None:
if debug.model in (models.T2T1, models.T3T1):
debug.click(buttons.CANCEL, wait=True)
elif debug.model in (models.T2B1,):
debug.press_left(wait=True)
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
if debug.model in (models.T2T1, models.T3T1):
assert "NumberInputDialog" in debug.read_layout().all_components()

@ -0,0 +1,109 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2024 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from typing import TYPE_CHECKING
import pytest
from trezorlib import device, messages
from ..common import EXTERNAL_ENTROPY, WITH_MOCK_URANDOM, generate_entropy
from . import reset
if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
pytestmark = [pytest.mark.skip_t1b1]
@pytest.mark.parametrize(
"group_threshold, share_threshold, share_count",
[
pytest.param(1, 1, 1, id="1of1"),
],
)
@pytest.mark.setup_client(uninitialized=True)
@WITH_MOCK_URANDOM
def test_backup_slip39_single(
device_handler: "BackgroundDeviceHandler",
group_threshold: int,
share_threshold: int,
share_count: int,
):
features = device_handler.features()
debug = device_handler.debuglink()
assert features.initialized is False
device_handler.run(
device.reset,
strength=128,
backup_type=messages.BackupType.Slip39_Basic,
pin_protection=False,
)
# confirm new wallet
reset.confirm_new_wallet(debug)
# cancel back up
reset.cancel_backup(debug)
# confirm cancel
reset.cancel_backup(debug)
assert device_handler.result() == "Initialized"
device_handler.run(
device.backup,
group_threshold=group_threshold,
groups=[(share_threshold, share_count)],
)
# confirm backup warning
reset.confirm_read(debug, middle_r=True)
all_words: list[str] = []
for _ in range(share_count):
# read words
words = reset.read_words(debug, messages.BackupType.Slip39_Basic)
# confirm words
reset.confirm_words(debug, words)
# confirm share checked
reset.confirm_read(debug)
all_words.append(" ".join(words))
# confirm backup done
reset.confirm_read(debug)
# generate secret locally
internal_entropy = debug.state().reset_entropy
assert internal_entropy is not None
secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY)
# validate that all combinations will result in the correct master secret
reset.validate_mnemonics(all_words, secret)
assert device_handler.result() == "Seed successfully backed up"
features = device_handler.features()
assert features.initialized is True
assert features.needs_backup is False
assert features.pin_protection is False
assert features.passphrase_protection is False
assert features.backup_type is messages.BackupType.Slip39_Basic

@ -31,6 +31,7 @@ from ..input_flows import (
InputFlowBip39Backup,
InputFlowSlip39AdvancedBackup,
InputFlowSlip39BasicBackup,
InputFlowSlip39CustomBackup,
)
@ -111,6 +112,37 @@ def test_backup_slip39_advanced(client: Client, click_info: bool):
assert expected_ms == actual_ms
@pytest.mark.skip_t1b1
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC_SLIP39_ADVANCED_20)
@pytest.mark.parametrize(
"share_threshold,share_count",
[(1, 1), (2, 2), (3, 5)],
ids=["1_of_1", "2_of_2", "3_of_5"],
)
def test_backup_slip39_custom(client: Client, share_threshold, share_count):
assert client.features.needs_backup is True
with client:
IF = InputFlowSlip39CustomBackup(client, share_count)
client.set_input_flow(IF.get())
device.backup(
client, group_threshold=1, groups=[(share_threshold, share_count)]
)
client.init_device()
assert client.features.initialized is True
assert client.features.needs_backup is False
assert client.features.unfinished_backup is False
assert client.features.no_backup is False
assert client.features.backup_type is messages.BackupType.Slip39_Advanced
expected_ms = shamir.combine_mnemonics(MNEMONIC_SLIP39_ADVANCED_20)
actual_ms = shamir.combine_mnemonics(
IF.mnemonics[:3] + IF.mnemonics[5:8] + IF.mnemonics[10:13]
)
assert expected_ms == actual_ms
# we only test this with bip39 because the code path is always the same
@pytest.mark.setup_client(no_backup=True)
def test_no_backup_fails(client: Client):

@ -1211,12 +1211,13 @@ class InputFlowBip39ResetFailedCheck(InputFlowBase):
self.debug.press_yes()
def load_5_shares(
def load_N_shares(
debug: DebugLink,
n: int,
) -> Generator[None, "messages.ButtonRequest", list[str]]:
mnemonics: list[str] = []
for _ in range(5):
for _ in range(n):
# Phrase screen
mnemonic = yield from read_and_confirm_mnemonic(debug)
assert mnemonic is not None
@ -1254,7 +1255,7 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # Confirm backup
assert br.code == B.Success
@ -1279,7 +1280,7 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # Confirm backup
assert br.code == B.Success
@ -1304,7 +1305,7 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # Confirm backup
assert br.code == B.Success
@ -1328,7 +1329,7 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase):
yield from click_through(self.debug, screens=8, code=B.ResetDevice)
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # safety warning
assert br.code == B.Success
@ -1357,7 +1358,7 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase):
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # Confirm backup
assert br.code == B.Success
@ -1375,13 +1376,65 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase):
yield from click_through(self.debug, screens=8, code=B.ResetDevice)
# Mnemonic phrases
self.mnemonics = yield from load_5_shares(self.debug)
self.mnemonics = yield from load_N_shares(self.debug, 5)
br = yield # safety warning
assert br.code == B.Success
self.debug.press_yes()
class InputFlowSlip39CustomBackup(InputFlowBase):
def __init__(self, client: Client, share_count: int):
super().__init__(client)
self.mnemonics: list[str] = []
self.share_count = share_count
def input_flow_tt(self) -> BRGeneratorType:
if self.share_count > 1:
yield # Checklist
self.debug.press_yes()
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, self.share_count)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def input_flow_tr(self) -> BRGeneratorType:
if self.share_count > 1:
yield # Checklist
self.debug.press_yes()
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, self.share_count)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def input_flow_t3t1(self) -> BRGeneratorType:
if self.share_count > 1:
yield # Checklist
self.debug.press_yes()
yield # Confirm show seeds
self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, self.share_count)
br = yield # Confirm backup
assert br.code == B.Success
self.debug.press_yes()
def load_5_groups_5_shares(
debug: DebugLink,
) -> Generator[None, "messages.ButtonRequest", list[str]]:

@ -137,7 +137,7 @@ class RecoveryFlow:
def warning_invalid_recovery_seed(self) -> BRGeneratorType:
br = yield
assert br.code == B.Warning
TR.assert_in(self._text_content(), "recovery__invalid_seed_entered")
TR.assert_in(self._text_content(), "recovery__invalid_wallet_backup_entered")
self.debug.press_yes()
def warning_invalid_recovery_share(self) -> BRGeneratorType:

@ -835,6 +835,7 @@
"T3T1_en_test_autolock.py::test_dryrun_enter_word_slowly": "be557c3e4e0492d0b884d4ed549d3ae18e6175b74b5945e7ab6f2f96aab58748",
"T3T1_en_test_autolock.py::test_dryrun_locks_at_number_of_words": "700aa42142055535b4123d84f6d307a0589b43600c2dec525312d06c2af9aa18",
"T3T1_en_test_autolock.py::test_dryrun_locks_at_word_entry": "736652b5298a7a4ee2e51282323d1150c9844b6f7b738f421ac4ad3a83d0788d",
"T3T1_en_test_backup_slip39_single.py::test_backup_slip39_single[1of1]": "b13e22bddd80296bb0d08dff4bc94dad287c40247045eb779b88ba5c53050507",
"T3T1_en_test_lock.py::test_hold_to_lock": "9d60d7aa2fbe6a0de14379e02ea825fbf9e21471596498f7be686f2538391f1d",
"T3T1_en_test_passphrase_tt.py::test_cycle_through_last_character": "2a8d54c8014cc0c1bf46c0e4b58d6a002009b62aa8b92db663f88af0ad2f5e19",
"T3T1_en_test_passphrase_tt.py::test_passphrase_click_same_button_many_times": "6a579067b4395a260d173e78643b67ac701304ea833a112cb2da1bce94cbb102",
@ -867,11 +868,11 @@
"T3T1_en_test_recovery.py::test_recovery_bip39": "62947c5512e7ba33f77af5d67d2063918dcccea5d5530db89f04bffa0c309ae6",
"T3T1_en_test_recovery.py::test_recovery_bip39_previous_word": "35c67f93da34ba0f3c72a09076391bbc1ef24661d7ff8102f0394ee50250b29b",
"T3T1_en_test_recovery.py::test_recovery_slip39_basic": "a9f1ab4971b614ec67fdaa9602f72ba5eebe54204b3735f8aa2b91ddeee41a19",
"T3T1_en_test_reset_bip39.py::test_reset_bip39": "04c37505a7d8d88f47541728039d64ef674f0f70df68e60245ab9e3bb670b407",
"T3T1_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "94856f6643fac58182ae281c2341b4807e13baac286b5ba7af02a2019e30e0f6",
"T3T1_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "3253896eb301d76a3ef81c54ad7517f469919685b3b548c524b552e278712051",
"T3T1_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "a55360836e6ca450e5819dfe5a4f765cd9fd7fa57c5736fcd3bb3441c75c1624",
"T3T1_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "16ac65a2a4059e5effa52df529f482f03a2d39117964cd5edd529705b6d55709",
"T3T1_en_test_reset_bip39.py::test_reset_bip39": "beab50474b19fedaf4487c64071529e4217288942e4695ed795632559835b20b",
"T3T1_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "ef7f63ddb82bcd7ca92d739865cf466741b531b86946b1524b8440382823a56a",
"T3T1_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "5ac9a51693538d546a37d7c6c8c3be260849bdccd84d4f92a2c48ac036914100",
"T3T1_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "08a2e9747cf0cc71d6fff582b7972e5c6b5a63bb667c6a63b2110aa7dc675b84",
"T3T1_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "5b61a5331e62c21419c8ffdd4ba8ff41b1ef18f9dcb575b3ad4aa5359062d5c1",
"T3T1_es_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "f5dd586173207ad0c2ca10288d9b0459bcd9c47b322a762bf69a48152a8d6884",
"T3T1_es_test_autolock.py::test_autolock_does_not_interrupt_signing": "31873e72e779156bc1ef9045b2ac30ba25568692032850f71d1a16dae9e93a7f",
"T3T1_es_test_autolock.py::test_autolock_interrupts_passphrase": "a2da4e7cd30e51e3a8587286660adfce5d6ca3dd0528a6c75c3f1c321ea6586c",
@ -4846,9 +4847,9 @@
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[u2f_counter-1]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "2d9bad062846c0b389f6c04dae3aaaeeb25a5cd83b36226842a4ccd400e6a039",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "864dc829401d036c222fc8f6b594c2cea5432652f5aa0048226473cbd1457a89",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "a9e0cbc9f0c47ea74e7e2387d369cbf41363440542eba80e2804b4d6cfa66d82",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "81058e35c06ce19b4e398af7ae6ec27c4123c295b5ef030d00ff96ab8999d1e2",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "5444fb8a8ab2092036224dd5bdaed2272d9673ae9da485ce7b3e3abbefb9e23c",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "a1d7f9b4248b4bc32ba4b76149862a46c3259579ddb9386da0ac1d451c666722",
"T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_uninitialized": "c47cfc38ee8a29b79808336a6f99b037f4a760b907bb9c680a554f92a194d262",
"T3T1_en_reset_recovery-test_recovery_bip39_t2.py::test_already_initialized": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_reset_recovery-test_recovery_bip39_t2.py::test_tt_nopin_nopassphrase": "1cc45f55b9a213d0aa67110c27852c403bcd6206e319e0f4b0bf341ec9f14127",
@ -4878,24 +4879,24 @@
"T3T1_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "963f0067311df44ec9e62f3c859469783bf2f4d7710b4c653fa9edf19d75d8a7",
"T3T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "28398ee2bfc0e8774613a51155d78039657531b4a34209c457bf364c11e7a13d",
"T3T1_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "7a2afcb4d00fab2004cdec95f2bc3d6c5a37c2f2e9b1749982b1571b265b51a2",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "c7ffe5259e79f05e9a537d8ccc4a8cc9fb9002dd5721ebad0da4d8bb731f08fd",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "0c3a5aa933478a149a32edfd7d467466f765053c70e7786c9ce9826ec98eb799",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "bc49fa0ef6b32c839177cb9db22fe7649e2311d43ec55ad5b3c0653b4f0962f4",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "2e5b9fb0155b7e674bb961773485be1558fd81c2f4adbe562352a15b618abdc3",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "004ff705e2cbd815431b99c06ffdb1b36f3d452a943b25431fec653741fb2967",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "10320310c32ae202781ca3561ed7976dcf0ef69bba3a3ed9b9d4efeddaf95c87",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "64626ac12c65af4f17b3654ea2cb3fd487ccf62855792c5a7cf43709996024b7",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "04ca60ba989435647e3dc7aedfc9471e858ec767028c35dbfc8cdf99b8b2b507",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "ef43bf6d25f93115145ef0688dc4fd31870499a4bcc52a050c516228e2e9d641",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "6c0f3ab10c3a403526b39326834ba7059d7053eb22cd9403a0cc760e2b62c0e5",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "a91f60fd8b0b1f06cf83d2de541502dc7dcdb9debfa684de6d5d76270435e2d7",
"T3T1_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "e68140d2edc1227e6f7591bd0f89615cb8e68880e74948c42461860f170e9d64",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "87bcddf21ffbf1a850b9c4480329498c4148fc7457144959214e037e6b137f87",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "4b354469a2600422f473d3453edde53109de8a39a70460f5fa99e5bc86d8c87d",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "55c94846461878560fd80ede303482296d2daf47e1a4ce111dfe39df4338f61d",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "d857c232229a26ff2f1b0b0142f60e8f4b0d26e94dead507da2ea6d77b383453",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "f8d4aa788dc312496b150f29dbf0c8fe756d9e8a7a3515f8209905130fce456e",
"T3T1_en_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "b0b91b3936e31b23ea962fe2ee8e9dcb5484bfda491925006e9b02ba65a8142f",
"T3T1_en_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "1ad7de5834e4bd900644dc8f51835f8c755dad2af3968eebd6d8113936c70cce",
"T3T1_en_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "d38b00501cc3d07fc0ff9442f4a9590be0f19e07308039590524bbd70f621f01",
"T3T1_en_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "d4d690a3305feabda0122282c46bce4bbf22de1be7400d5e134a21beaea6b406",
"T3T1_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "ec5282b537c2a53b01fdfc301eb57154b1d4687c7461cd8a9687c079b30f7ebd",
"T3T1_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "826a820a3d9265d14df2effee8d78fb1d8d15c63399913aee39b46902261ba4a",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "2164a72e04396209768bfcb81d77e842863f801600db3a6cceebada0c9834d24",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "c605309a6c6161e1fffd10c2a61f6fee862f08eda5a6276dcae2ae4b7a0e812e",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "d7596291af2da445c465c00e4810302b8b7ab67743efed9d69d790947f894a85",
"T3T1_en_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "18b4e40f7e3581e3cdbc959f2a9f666064b435a3af8da5ef30d40634ecb169eb",
"T3T1_en_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "f6f62e8228d3fbae8b64bce061dd77e49fa6c5c40e0b499bdcc01eb2a9603a59",
"T3T1_en_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "198b0c574bfa13aa6b16807c7cea469144bedc941f806b893ee98c924a0efff8",
"T3T1_en_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "031c302ae2f839862b4fb6ae65da7ac3a6ee84fd3fbf90707d09f907914e0488",
"T3T1_en_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "c51f8b6743401a55153b76374d4a038a7f7ec89df5a9618e90130dd3d84e44cb",
"T3T1_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "f09745b93295d89d0f1660b1cb88cb2cc1c7fa9542954c898b8696f36ab4af75",
"T3T1_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "6de4e189036e5f94e0aeed958f9a3d64796b2cfd1d4f318ab7440a33119af4b4",
"T3T1_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-0h-0-0-rNaqKtKrMSwpwZSzRckPf-3321e5d1": "e1714dd0d8e33bc7d38c8a8f0a24082c89846497a98fcfec5136743007da9171",
"T3T1_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-0h-0-1-rBKz5MC2iXdoS3XgnNSYm-fd75b415": "6b8a98bb658c617c435cf541e35c1fdb055f86c983add4df01b714836239c1b8",
"T3T1_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-1h-0-0-rJX2KwzaLJDyFhhtXKi3h-af5daf0f": "f2b9355b112c77ea457568adb7375dd8f2338f5170ba26758751ce3f8cee285b",
@ -5096,11 +5097,14 @@
"T3T1_en_test_msg_applysettings.py::test_experimental_features": "e774f4de022f48caacc2445b1994ac19dce7a6155b4224ae7eb727ffc8975ca9",
"T3T1_en_test_msg_applysettings.py::test_label_too_long": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"T3T1_en_test_msg_applysettings.py::test_safety_checks": "b129e0ba2e3c7ac836b3bb4b0bc8ded1c922eba054b42b434d10499fc780ea2b",
"T3T1_en_test_msg_backup_device.py::test_backup_bip39": "dfb5ff96727714c4d8115f9dee8d76d9cb40c46a6a5e693118cb8e332e0124ab",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "24ef4ff564a1d024deb4efa7b2fff91fa69892a6373f566cc5c4d86045c0c853",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "ca4836dd39f2398401fe2001a6cb7bf389f83fcd8e7770a65ca79b6840fe176a",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "01af0780579b900211698fdaa68a34f4339fa516f152dfd013e210f96b02f5e1",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "efd5fa4a4a87e6ac6e6b6b2c819d3d40711e7aff0340d1bab27794c333caa8d0",
"T3T1_en_test_msg_backup_device.py::test_backup_bip39": "429a141f97ad5a7b857985fcba60a7ac35e09e6ea54013203d80cb63deef8638",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "75f8fb63ed22a2e9983a9d60488d6cb87bde792ed7b00b67f1754ef5bfa8910a",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "31f143785472dad5adc46df21cf80ae3210c30c22fc719d9f56446d1915a36e8",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "63db4c01cc1abddf73ece3c61d9d0de6bfd6499559e01bc58c2981f416a922f8",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "f1cab327533697803793725144e42200106e05f1d81b75ff0a8eb55453a37dfa",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_custom[2_of_2]": "02784e05be37613bbd41836a18e57378dbd4b1e031864c4da0f63c72eb1cb156",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_custom[1_of_1]": "4a8e597346e174c32b2e6a3667c6c236b076f1ff28400586ab3b069ac6c7ce0f",
"T3T1_en_test_msg_backup_device.py::test_backup_slip39_custom[3_of_5]": "0a4d6ee5ad699483dfd7ecef83829c64e95b958e9ad8be04e76ce0c2db5cdd9a",
"T3T1_en_test_msg_backup_device.py::test_interrupt_backup_fails": "ae147498028d68aa71c7337544e4a5049c4c943897f905c6fe29e88e5c3ab056",
"T3T1_en_test_msg_backup_device.py::test_no_backup_fails": "fada9d38ec099b3c6a4fd8bf994bb1f3431e40085128b4e0cd9deb8344dec53e",
"T3T1_en_test_msg_backup_device.py::test_no_backup_show_entropy_fails": "c47cfc38ee8a29b79808336a6f99b037f4a760b907bb9c680a554f92a194d262",
@ -8130,6 +8134,7 @@
"TR_en_test_autolock.py::test_dryrun_enter_word_slowly": "f35d159c13b36c428e68969bbeb87fb4bdbfa6c21eb98985b5832849938d6934",
"TR_en_test_autolock.py::test_dryrun_locks_at_number_of_words": "df7a17ab9cd2c617ce22a615e1da9bedee41f978459cbe103f2eecb8dfe8db12",
"TR_en_test_autolock.py::test_dryrun_locks_at_word_entry": "53b39b3ff0548a91e87ac3391462850e4f6060fa1a19ae873eca6fc5cce8dbb2",
"TR_en_test_backup_slip39_single.py::test_backup_slip39_single[1of1]": "9e620acfff4ef1442996b81c1fd9c73d20490c2deecc6bc05aa0b665d9cd217c",
"TR_en_test_lock.py::test_hold_to_lock": "83e2d055215b03150069d9fcb3aee6dc3f78a0d2bc43d8133425bf6b000c191d",
"TR_en_test_passphrase_tr.py::test_cancel": "4a79e82b3ddf23c087e70767f2c4d4720f980370e22be209189d4ed42a453ee8",
"TR_en_test_passphrase_tr.py::test_passphrase_delete": "28468562292a54f2d8cc954129c6b1859c267bc5a9b94f9b406352e71a4c8036",
@ -8156,11 +8161,11 @@
"TR_en_test_recovery.py::test_recovery_bip39": "632e414cf9886064a0c5a72cb8d676a2a3755f4db19e5c8aff1c3d2833374a84",
"TR_en_test_recovery.py::test_recovery_bip39_previous_word": "6689558320ffceecb9c8a5eccaf1f83ab1e63f01d3387a580ec2c412f047286e",
"TR_en_test_recovery.py::test_recovery_slip39_basic": "5f3c58aea5c8ad3c1c32c2fe303a0aa91b0805cce5d4dcd23473f72120109458",
"TR_en_test_reset_bip39.py::test_reset_bip39": "5ae502b3041376c4dac8de8b315242e20c29e76cbd93acc81790b61a65bb2401",
"TR_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "53763c7177a26069cc164a84d93c6b6b53bfe3f58a770913b4b62b537fd9301d",
"TR_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "ae94835bc49ebce6fdaa05671734701f6d2911cdee54e99e083edddbb070ffa7",
"TR_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "74d81c033b35aab5d88af1562be47b3c25e8fc14114bf4e5f817713943f563a2",
"TR_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "ffb556aaa947bb5a91a613f46d7a4275d9a6b94b5aa2f50296a86391f61b2f74",
"TR_en_test_reset_bip39.py::test_reset_bip39": "818f7de91f7d73b4e287c9f6bd205c95c3b568d0a269e1084ec976a1885d7037",
"TR_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "38ba7436ccf4d969f4821d9ff0f65646478376e254d4eda80cbce4034fc5a4e4",
"TR_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "a96bd0f6bbf9bcf8f20f7832f0c99c3631d6d2f6bb50d26ea3a681c44973b875",
"TR_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "f873d72ecc201e998274001516f6da8ec28e5d39fd0a0e6fb86a365be1b19fd2",
"TR_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "cfaff71fd9595c4600b8c8ced20775ecf2dadb2785fcb9774eb9402f35e7d28a",
"TR_en_test_tutorial.py::test_tutorial_again_and_skip": "74eccb209aa5c644da6e8de9689972841b566ef51a8073acf5d7770661c8f833",
"TR_en_test_tutorial.py::test_tutorial_finish": "945c3e89653817fe59016b4698a488c7905a745f49eafb974b6d130936a11289",
"TR_en_test_tutorial.py::test_tutorial_skip": "d95088af526a3b18cc6eba7d84014f2eaf5757411f7b36e3305c41980640ecc3",
@ -15206,6 +15211,7 @@
"TT_en_test_autolock.py::test_dryrun_enter_word_slowly": "140ff1c01d0d27ade29e88af481a9a24385fbe01058bdbf35f2fa20c19e0c386",
"TT_en_test_autolock.py::test_dryrun_locks_at_number_of_words": "f9a5c8f92ca3b0b9545a9a5b3cf8df4de9943fbe45de113aa6f970d60b3b9b49",
"TT_en_test_autolock.py::test_dryrun_locks_at_word_entry": "2ea54adc6df443f758a6395b6b443fbfe5931cbd62a321504de9ae453aff8ca8",
"TT_en_test_backup_slip39_single.py::test_backup_slip39_single[1of1]": "6c33232fdff24175a489c83507bcde9bd859cc2d7f1ca687e154befd0cd2b883",
"TT_en_test_lock.py::test_hold_to_lock": "a5739f92ae28fc57769e444408ce5b58223d0d33b368022ef78ea68e0f8c9b80",
"TT_en_test_passphrase_tt.py::test_cycle_through_last_character": "2a8d54c8014cc0c1bf46c0e4b58d6a002009b62aa8b92db663f88af0ad2f5e19",
"TT_en_test_passphrase_tt.py::test_passphrase_click_same_button_many_times": "6a579067b4395a260d173e78643b67ac701304ea833a112cb2da1bce94cbb102",
@ -15238,11 +15244,11 @@
"TT_en_test_recovery.py::test_recovery_bip39": "65a138f634806c6483c55c6ce5365b8a7a4073a3c0c340b1826042262faa8545",
"TT_en_test_recovery.py::test_recovery_bip39_previous_word": "a009899ccd3305cb6737c8fa645cc9eedf4e46d6669a621a07d8cd9447d80f2f",
"TT_en_test_recovery.py::test_recovery_slip39_basic": "9b0f5a7b8d2ab0fed1e5389076bc035e24dce377d275824220f1aa61e9bb4810",
"TT_en_test_reset_bip39.py::test_reset_bip39": "19fd9f6233d72224696c547528a0079934a86cb41539b6f7149aab57b0aaec42",
"TT_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "1d604f1766ce861e616745dcb5c89122165eb26ecc7f40039e50b8fe8c61a861",
"TT_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "58fe6639315e549576be1bf8141ca01cea306c94b21e9e03067ba4ade6065d27",
"TT_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "ffe9e0d38c068f2af90dfe9f63d912492b8eeeba8e4d5105d9df86e31e0dbbd6",
"TT_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "1db5982fc3b0795517144fcb3a30da7e2a6226c223912f6d88647f6715fd872b",
"TT_en_test_reset_bip39.py::test_reset_bip39": "1feb3fcae2c593ea9193bcab23d8e64accd6fdbd2b05e2d2403ff89f8f94e4d8",
"TT_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[16of16]": "6dc6b1ed736a1073c05067f37b817bf139e832a41db7d7804622d4a452db7b2a",
"TT_en_test_reset_slip39_advanced.py::test_reset_slip39_advanced[2of2]": "50b553055335d639728e075c6757ae8cecea4887a0508876ce36501f81fcf7dc",
"TT_en_test_reset_slip39_basic.py::test_reset_slip39_basic[16of16]": "a407656fc025984edf491d8ff04adc55e7e1b683e3bdc39b74172aba1e419777",
"TT_en_test_reset_slip39_basic.py::test_reset_slip39_basic[1of1]": "3cf750b01cea54c96b7f5e1fee535af8e42e02447e2da62bf7b9b167a9cc835e",
"TT_es_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "a6c27b94205c9dd53a817612fe0abd85743079be6ea00206626be171e1aec455",
"TT_es_test_autolock.py::test_autolock_does_not_interrupt_signing": "e32e681c59dddd9e0b52d559d2f843dad022662fc56507c1ef76c1491aa8728d",
"TT_es_test_autolock.py::test_autolock_interrupts_passphrase": "d3a25e5f32fff10662159ae1d42a057db28ebf7c331d8b1910ae8e0e4e74f29f",
@ -19343,9 +19349,9 @@
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[u2f_counter-1]": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "6ba7675c1d397a6978a8f8025d8a80024ee862887d4a586e139d250a7476c665",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "025e49a30d18066ab4d4cd73af9ca5864b74e2cba625fd56a6905fec9a7017bd",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "dfe87991c0ec4150084bc3453145decc3531e21b41e81202c8ca1211929309c6",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_dry_run": "93856fdb06fd1c0a151734a34e40d04b8c792ba87491219394fa58eeab40529a",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_invalid_seed_core": "33d58b3cbae9f0455791e0130d250e3093a3c2e6110cc8b7f407c5a3a4f3526d",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_seed_mismatch": "8d29cf9e1e28de42b19b7c90c3e1242ca012bce923eb8c1f96b27386eb280935",
"TT_en_reset_recovery-test_recovery_bip39_dryrun.py::test_uninitialized": "001377ce61dcd189e6a9d17e20dcd71130e951dc3314b40ff26f816bd9355bdd",
"TT_en_reset_recovery-test_recovery_bip39_t2.py::test_already_initialized": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_reset_recovery-test_recovery_bip39_t2.py::test_tt_nopin_nopassphrase": "e11ced4e4950c59d52bca1045770606dd08c648f96d5b4426c36048d740e0b45",
@ -19375,24 +19381,24 @@
"TT_en_reset_recovery-test_recovery_slip39_basic.py::test_wrong_nth_word[2]": "aea2334f29f8f4af160e238d79cac41e1b3b33feb08b71c2ab0cd867b8e8a20b",
"TT_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_dryrun": "5f3413af77cc76413fe1ec3e00e17850f8de504499b5bf9d753ed4d9351931e5",
"TT_en_reset_recovery-test_recovery_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "2b866e19489d98b9924398080478e2126fe9769b057ea725e7ba795584adb1ad",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "965dcb5a1cbc4c61790e3ed9cef705962d2ed24046c5a5733e64faf5c8bce543",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "ebb906d66f842651cc25cda333d71ddcca0d1fdf023f9a66cf026482bfe95a8d",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "4bae92f24e04731197ab0a7051458551ea041bcf483c8c3b740203f9fbb84564",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "389ae72b9576b5d8c317fd8fad2656db72c9397ed52014e722aa26048587518f",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "2655a385df8ae04636f9193f3aea9078963aaa00c24c56d446f56faa71ad74ad",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "20f6295e76a45bce8679780c2eb93f99e7c2cf2e59db7afa81b53ad9a997cdab",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Bip39-backup_flow_bip39]": "e5468c5e24463f2e7f0bd7eec981bb7e942cce7dbadcdbd7c5cf3bd1df229a4f",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Advanced-bac-f67baa1c": "7d5bcc38644dbf0b0b3e10659c1f37ea0614518db63480c9dc1c7ad1dd1210f7",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_manual[BackupType.Slip39_Basic-backup-6348e7fe": "313ac8fb4b3da9cfbdf19bf90617ff8fb69aa894485f682eb32a4db2d9e08f30",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Bip39-backup_flow_bip39]": "7846f566faafc178e190e2bf320b4028847ca58993c80fe4865dab6efd453f17",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Advanced-backup-dcbda5cf": "d224375841a21ce86f7abeac502b3baf9d1ac94f1c9868b85c9326ec4ee4505b",
"TT_en_reset_recovery-test_reset_backup.py::test_skip_backup_msg[BackupType.Slip39_Basic-backup_fl-1577de4d": "1d2b0fb041b91bf6b405bce8e6ab34b6db89f6c5a6b0680a7e4eb7fb258d38d9",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_already_initialized": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_failed_pin": "550ef3ed1a280dfee383119ee41329956c47ae38162721cd3ad53f00d8423937",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "9aa728ab73a36dbe45d91e6406e5e9543ba1627565a901ffded3fe3f4b2031d5",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "7927c0cf3eacd16033590e38382618c408d5c67b9b589161de73aa1a0bacec12",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "f04dc9251b9eea9dec562072972822617f1449f33bee4f38f4720ba9e1ddc027",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "80545ae63bc78a9b2268caf0d19e5d5c0f9073db7b5d92406c738244feed70ee",
"TT_en_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "20af3c7e83478b63e469cde6654119d3de7aa860363ef14aa120b97a1a8475e0",
"TT_en_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "ddf840544d8580d6b057047e9880267670cbe967e5e2ec9817c0c8689b265238",
"TT_en_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "d90e3bf0498d1f8db2ebc99dd2d516f0f7b532627c7a48ef76a96aa8c844786f",
"TT_en_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "7fb8ebc2b2809bec7cb5ed0cb043d2d5f154d2051936427d20503f1f9f7d66f9",
"TT_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "68920bb747d646fe5f274a73a64935f4c79a1974c56b1ab5e87a949d1db8cc89",
"TT_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "192a0d8f6306929e294266aa8ce91e7c805511673ed2982538005fd74c131086",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device": "adfc882ee9368382c280d2d80a358e362d555f6dd72e645dcc6cb7bcb1a5bf56",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_192": "f4ba7a0b808092c49a97dda6e514259de4cc8c40f79d814d6dc16db6fdf3e648",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_device_pin": "9a091e24dd9e1a4f000f7b457c6c868d5a839ae5da56022c94dc8ad90900ee13",
"TT_en_reset_recovery-test_reset_bip39_t2.py::test_reset_failed_check": "426b7fea50e15767f8038324ff1e6aac01f8cab1cf57a998b5c65951daa74ef6",
"TT_en_reset_recovery-test_reset_recovery_bip39.py::test_reset_recovery": "13c73bb3ea55849f7aa918f142942265ece4863f9f9645ffcdfb93fca5ab26f7",
"TT_en_reset_recovery-test_reset_recovery_slip39_advanced.py::test_reset_recovery": "d128d28108e9c9cde11ee0028d3bd8ed34a866b148efbcf3f89e316e401f2c6b",
"TT_en_reset_recovery-test_reset_recovery_slip39_basic.py::test_reset_recovery": "ece7afb1567c31ac7edf380f402f24cd3c2cedc5d8c93d7bb0b140a8c3fcfc85",
"TT_en_reset_recovery-test_reset_slip39_advanced.py::test_reset_device_slip39_advanced": "0290b12f3902f6315505cfa9b582adec9b3421773191ce7401f48b8f554ad842",
"TT_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic": "f034c92bdd7946eefb2402976df7406dc2048f35040fc8d7b685d59ac3ec4f88",
"TT_en_reset_recovery-test_reset_slip39_basic.py::test_reset_device_slip39_basic_256": "182a4be3493bc23999bffa969f3e2d236ecf478ffa23fc83236f0e162e503399",
"TT_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-0h-0-0-rNaqKtKrMSwpwZSzRckPf-3321e5d1": "e1714dd0d8e33bc7d38c8a8f0a24082c89846497a98fcfec5136743007da9171",
"TT_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-0h-0-1-rBKz5MC2iXdoS3XgnNSYm-fd75b415": "6b8a98bb658c617c435cf541e35c1fdb055f86c983add4df01b714836239c1b8",
"TT_en_ripple-test_get_address.py::test_ripple_get_address[m-44h-144h-1h-0-0-rJX2KwzaLJDyFhhtXKi3h-af5daf0f": "f2b9355b112c77ea457568adb7375dd8f2338f5170ba26758751ce3f8cee285b",
@ -19593,11 +19599,14 @@
"TT_en_test_msg_applysettings.py::test_experimental_features": "523f74db7f660c261507dfdd92285981869af72c9ba391c4dfedb3f06ccf40ad",
"TT_en_test_msg_applysettings.py::test_label_too_long": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_test_msg_applysettings.py::test_safety_checks": "b129e0ba2e3c7ac836b3bb4b0bc8ded1c922eba054b42b434d10499fc780ea2b",
"TT_en_test_msg_backup_device.py::test_backup_bip39": "3b90a1c3383b3afa2c321109e1088b2e33d976a9f75db3b013456d52c85ae5d2",
"TT_en_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "2738a2e8fb890f7f2f5857cd9af0d3f6eac9fbf71dc299c829b1ba6a1209b712",
"TT_en_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "503c667f02e099102be7c9bf14e771b5e27f41eabebdf7269389c9d9b33016ae",
"TT_en_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "65b98eb420a3b4a5128dc686f491a55b460205db7e1e1bb2828a3d6998af5466",
"TT_en_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "a3c26090183f9b150cd0caccfa2a4ba9833de718868777cc1e6f255ddda8a94f",
"TT_en_test_msg_backup_device.py::test_backup_bip39": "09c32059eea15ce17dd07bc34f09ca335e8a6cee573fef0095cf2105932a078a",
"TT_en_test_msg_backup_device.py::test_backup_slip39_advanced[click_info]": "5007dfedc1b4420b096f76d33acc9bcf77ec6083d54ca59571f5d225dfb2a33b",
"TT_en_test_msg_backup_device.py::test_backup_slip39_advanced[no_click_info]": "4f13e0550f8e06481fd4f59d722d3893ea904f85fb20ec8018344347492aff1c",
"TT_en_test_msg_backup_device.py::test_backup_slip39_basic[click_info]": "b432bf3671b15fdcfb92113fcef0e95671c3d7beaae70b25a87d090d30f9a079",
"TT_en_test_msg_backup_device.py::test_backup_slip39_basic[no_click_info]": "4a6f3fdd5d7ae36e6b5ad84faa5385565fd13f4ce3f7d915fa16c2184f99ebbd",
"TT_en_test_msg_backup_device.py::test_backup_slip39_custom[2_of_2]": "eab39f646e9e45d4c6db01bd89f3b0ffbb651910e11786fb98b85b9b2d341f6b",
"TT_en_test_msg_backup_device.py::test_backup_slip39_custom[1_of_1]": "d0aae400aee9b006e98d0302bf5175cda95dc5302b80d83bf199e1b619ad3621",
"TT_en_test_msg_backup_device.py::test_backup_slip39_custom[3_of_5]": "bf6defdfed3a06c0ae27d88ae2c8ea1196af45527cf3207515fa06b8dea672b2",
"TT_en_test_msg_backup_device.py::test_interrupt_backup_fails": "ae147498028d68aa71c7337544e4a5049c4c943897f905c6fe29e88e5c3ab056",
"TT_en_test_msg_backup_device.py::test_no_backup_fails": "fada9d38ec099b3c6a4fd8bf994bb1f3431e40085128b4e0cd9deb8344dec53e",
"TT_en_test_msg_backup_device.py::test_no_backup_show_entropy_fails": "001377ce61dcd189e6a9d17e20dcd71130e951dc3314b40ff26f816bd9355bdd",

Loading…
Cancel
Save