1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-30 18:38:27 +00:00

test(core/sdbackup): reset, recovery

- BIP39 recovery test (MNEMONIC12 backup block written directly to
  mocked SD card)
- BIP39 reset-recovery test
- 3-of-5 shamir reset-recovery test
- WIP: InputFlows will be updated and polished with UX improvements later
This commit is contained in:
obrusvit 2024-01-16 11:01:04 +01:00
parent a0ef80baeb
commit 144a4e812d
14 changed files with 598 additions and 46 deletions

View File

@ -190,9 +190,14 @@ message DebugLinkEraseSdCard {
* @next Success
*/
message DebugLinkInsertSdCard {
optional uint32 serial_number = 1; // mocked serial number of the card (e.g.: 1, 2, 3,...)
optional uint32 capacity_bytes = 2; // capacity of the card in bytes
optional uint32 serial_number = 1; // mocked serial number of the card (range 1-16)
optional uint32 capacity_bytes = 2; // capacity of the mocked card in bytes
optional uint32 manuf_ID = 3; // mocked manufacturer ID
repeated DebugLinkSdCardDataBlock data_blocks = 4; // data written directly to individual memory blocks
message DebugLinkSdCardDataBlock {
required uint32 number = 1;
required bytes data = 2;
}
}
/**
@ -205,7 +210,6 @@ message DebugLinkWatchLayout {
// if false, stop.
}
/**
* Request: Remove all the previous debug event state
* @start

View File

@ -5,7 +5,7 @@ from typing import *
def insert(
card_sn: int,
capacity_bytes: int | None = 122_945_536,
manuf_id: int | None = 27,
manuf_id: int | None = 39,
) -> None:
"""
Inserts SD card to the emulator.

View File

@ -241,15 +241,14 @@ if __debug__:
try:
sdcard.power_on()
# trash the whole card
assert sdcard.capacity() >= sdcard.BLOCK_SIZE
empty_block = bytes([0xFF] * sdcard.BLOCK_SIZE)
for i in range(sdcard.capacity() // sdcard.BLOCK_SIZE):
sdcard.write(i, empty_block)
# make filesystem
if msg.format:
io.fatfs.mkfs()
else:
# trash first 1 MB of data to destroy the FAT filesystem
assert sdcard.capacity() >= 1024 * 1024
empty_block = bytes([0xFF] * sdcard.BLOCK_SIZE)
for i in range(1024 * 1024 // sdcard.BLOCK_SIZE):
sdcard.write(i, empty_block)
except OSError:
raise wire.ProcessError("SD card operation failed")
finally:
@ -268,6 +267,12 @@ if __debug__:
capacity_bytes=msg.capacity_bytes,
manuf_id=msg.manuf_ID,
)
if msg.data_blocks is not None:
sdcard = io.sdcard
sdcard.power_on()
for block in msg.data_blocks:
sdcard.write(block.number, block.data)
sdcard.power_off()
return Success()

View File

@ -2927,10 +2927,12 @@ if TYPE_CHECKING:
serial_number: "int | None"
capacity_bytes: "int | None"
manuf_ID: "int | None"
data_blocks: "list[DebugLinkSdCardDataBlock]"
def __init__(
self,
*,
data_blocks: "list[DebugLinkSdCardDataBlock] | None" = None,
serial_number: "int | None" = None,
capacity_bytes: "int | None" = None,
manuf_ID: "int | None" = None,
@ -2961,6 +2963,22 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkResetDebugEvents"]:
return isinstance(msg, cls)
class DebugLinkSdCardDataBlock(protobuf.MessageType):
number: "int"
data: "bytes"
def __init__(
self,
*,
number: "int",
data: "bytes",
) -> None:
pass
@classmethod
def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkSdCardDataBlock"]:
return isinstance(msg, cls)
class EosGetPublicKey(protobuf.MessageType):
address_n: "list[int]"
show_display: "bool | None"

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
P = ParamSpec("P")
R = TypeVar("R")
SD_CARD_HOT_SWAPPABLE = False
SD_CARD_HOT_SWAPPABLE = True
class FilesystemWrapper:

View File

@ -746,6 +746,7 @@ class DebugLink:
serial_number: int = 1,
capacity_bytes: Optional[int] = None,
manuf_ID: Optional[int] = None,
data_blocks: Optional[List[messages.DebugLinkSdCardDataBlock]] = None,
) -> messages.Success:
if not self.model == "T":
raise RuntimeError("SD card not supported by this device.")
@ -756,6 +757,7 @@ class DebugLink:
serial_number=serial_number,
capacity_bytes=capacity_bytes,
manuf_ID=manuf_ID,
data_blocks=data_blocks,
)
)

View File

@ -4091,15 +4091,18 @@ class DebugLinkInsertSdCard(protobuf.MessageType):
1: protobuf.Field("serial_number", "uint32", repeated=False, required=False, default=None),
2: protobuf.Field("capacity_bytes", "uint32", repeated=False, required=False, default=None),
3: protobuf.Field("manuf_ID", "uint32", repeated=False, required=False, default=None),
4: protobuf.Field("data_blocks", "DebugLinkSdCardDataBlock", repeated=True, required=False, default=None),
}
def __init__(
self,
*,
data_blocks: Optional[Sequence["DebugLinkSdCardDataBlock"]] = None,
serial_number: Optional["int"] = None,
capacity_bytes: Optional["int"] = None,
manuf_ID: Optional["int"] = None,
) -> None:
self.data_blocks: Sequence["DebugLinkSdCardDataBlock"] = data_blocks if data_blocks is not None else []
self.serial_number = serial_number
self.capacity_bytes = capacity_bytes
self.manuf_ID = manuf_ID
@ -4123,6 +4126,23 @@ class DebugLinkResetDebugEvents(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 9008
class DebugLinkSdCardDataBlock(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {
1: protobuf.Field("number", "uint32", repeated=False, required=True),
2: protobuf.Field("data", "bytes", repeated=False, required=True),
}
def __init__(
self,
*,
number: "int",
data: "bytes",
) -> None:
self.number = number
self.data = data
class EosGetPublicKey(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 600
FIELDS = {

View File

@ -3220,6 +3220,8 @@ pub struct DebugLinkInsertSdCard {
pub capacity_bytes: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.manuf_ID)
pub manuf_ID: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.data_blocks)
pub data_blocks: ::std::vec::Vec<debug_link_insert_sd_card::DebugLinkSdCardDataBlock>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkInsertSdCard.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@ -3294,7 +3296,7 @@ impl DebugLinkInsertSdCard {
}
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
let mut fields = ::std::vec::Vec::with_capacity(3);
let mut fields = ::std::vec::Vec::with_capacity(4);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"serial_number",
@ -3311,6 +3313,11 @@ impl DebugLinkInsertSdCard {
|m: &DebugLinkInsertSdCard| { &m.manuf_ID },
|m: &mut DebugLinkInsertSdCard| { &mut m.manuf_ID },
));
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
"data_blocks",
|m: &DebugLinkInsertSdCard| { &m.data_blocks },
|m: &mut DebugLinkInsertSdCard| { &mut m.data_blocks },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<DebugLinkInsertSdCard>(
"DebugLinkInsertSdCard",
fields,
@ -3323,6 +3330,11 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
const NAME: &'static str = "DebugLinkInsertSdCard";
fn is_initialized(&self) -> bool {
for v in &self.data_blocks {
if !v.is_initialized() {
return false;
}
};
true
}
@ -3338,6 +3350,9 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
24 => {
self.manuf_ID = ::std::option::Option::Some(is.read_uint32()?);
},
34 => {
self.data_blocks.push(is.read_message()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@ -3359,6 +3374,10 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
if let Some(v) = self.manuf_ID {
my_size += ::protobuf::rt::uint32_size(3, v);
}
for value in &self.data_blocks {
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
@ -3374,6 +3393,9 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
if let Some(v) = self.manuf_ID {
os.write_uint32(3, v)?;
}
for v in &self.data_blocks {
::protobuf::rt::write_message_field_with_cached_size(4, v, os)?;
};
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@ -3394,6 +3416,7 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
self.serial_number = ::std::option::Option::None;
self.capacity_bytes = ::std::option::Option::None;
self.manuf_ID = ::std::option::Option::None;
self.data_blocks.clear();
self.special_fields.clear();
}
@ -3402,6 +3425,7 @@ impl ::protobuf::Message for DebugLinkInsertSdCard {
serial_number: ::std::option::Option::None,
capacity_bytes: ::std::option::Option::None,
manuf_ID: ::std::option::Option::None,
data_blocks: ::std::vec::Vec::new(),
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
@ -3425,6 +3449,210 @@ impl ::protobuf::reflect::ProtobufValue for DebugLinkInsertSdCard {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
/// Nested message and enums of message `DebugLinkInsertSdCard`
pub mod debug_link_insert_sd_card {
// @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkInsertSdCard.DebugLinkSdCardDataBlock)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct DebugLinkSdCardDataBlock {
// message fields
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.DebugLinkSdCardDataBlock.number)
pub number: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.DebugLinkSdCardDataBlock.data)
pub data: ::std::option::Option<::std::vec::Vec<u8>>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkInsertSdCard.DebugLinkSdCardDataBlock.special_fields)
pub special_fields: ::protobuf::SpecialFields,
}
impl<'a> ::std::default::Default for &'a DebugLinkSdCardDataBlock {
fn default() -> &'a DebugLinkSdCardDataBlock {
<DebugLinkSdCardDataBlock as ::protobuf::Message>::default_instance()
}
}
impl DebugLinkSdCardDataBlock {
pub fn new() -> DebugLinkSdCardDataBlock {
::std::default::Default::default()
}
// required uint32 number = 1;
pub fn number(&self) -> u32 {
self.number.unwrap_or(0)
}
pub fn clear_number(&mut self) {
self.number = ::std::option::Option::None;
}
pub fn has_number(&self) -> bool {
self.number.is_some()
}
// Param is passed by value, moved
pub fn set_number(&mut self, v: u32) {
self.number = ::std::option::Option::Some(v);
}
// required bytes data = 2;
pub fn data(&self) -> &[u8] {
match self.data.as_ref() {
Some(v) => v,
None => &[],
}
}
pub fn clear_data(&mut self) {
self.data = ::std::option::Option::None;
}
pub fn has_data(&self) -> bool {
self.data.is_some()
}
// Param is passed by value, moved
pub fn set_data(&mut self, v: ::std::vec::Vec<u8>) {
self.data = ::std::option::Option::Some(v);
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_data(&mut self) -> &mut ::std::vec::Vec<u8> {
if self.data.is_none() {
self.data = ::std::option::Option::Some(::std::vec::Vec::new());
}
self.data.as_mut().unwrap()
}
// Take field
pub fn take_data(&mut self) -> ::std::vec::Vec<u8> {
self.data.take().unwrap_or_else(|| ::std::vec::Vec::new())
}
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::<_, _>(
"number",
|m: &DebugLinkSdCardDataBlock| { &m.number },
|m: &mut DebugLinkSdCardDataBlock| { &mut m.number },
));
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"data",
|m: &DebugLinkSdCardDataBlock| { &m.data },
|m: &mut DebugLinkSdCardDataBlock| { &mut m.data },
));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<DebugLinkSdCardDataBlock>(
"DebugLinkInsertSdCard.DebugLinkSdCardDataBlock",
fields,
oneofs,
)
}
}
impl ::protobuf::Message for DebugLinkSdCardDataBlock {
const NAME: &'static str = "DebugLinkSdCardDataBlock";
fn is_initialized(&self) -> bool {
if self.number.is_none() {
return false;
}
if self.data.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.number = ::std::option::Option::Some(is.read_uint32()?);
},
18 => {
self.data = ::std::option::Option::Some(is.read_bytes()?);
},
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.number {
my_size += ::protobuf::rt::uint32_size(1, v);
}
if let Some(v) = self.data.as_ref() {
my_size += ::protobuf::rt::bytes_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.number {
os.write_uint32(1, v)?;
}
if let Some(v) = self.data.as_ref() {
os.write_bytes(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() -> DebugLinkSdCardDataBlock {
DebugLinkSdCardDataBlock::new()
}
fn clear(&mut self) {
self.number = ::std::option::Option::None;
self.data = ::std::option::Option::None;
self.special_fields.clear();
}
fn default_instance() -> &'static DebugLinkSdCardDataBlock {
static instance: DebugLinkSdCardDataBlock = DebugLinkSdCardDataBlock {
number: ::std::option::Option::None,
data: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
}
}
impl ::protobuf::MessageFull for DebugLinkSdCardDataBlock {
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("DebugLinkInsertSdCard.DebugLinkSdCardDataBlock").unwrap()).clone()
}
}
impl ::std::fmt::Display for DebugLinkSdCardDataBlock {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for DebugLinkSdCardDataBlock {
type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage<Self>;
}
}
// @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkWatchLayout)
#[derive(PartialEq,Clone,Default,Debug)]
pub struct DebugLinkWatchLayout {
@ -3714,13 +3942,17 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\x01(\rR\x07address\x12\x16\n\x06memory\x18\x02\x20\x01(\x0cR\x06memory\
\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\x05flash\"-\n\x13DebugLinkFlas\
hErase\x12\x16\n\x06sector\x18\x01\x20\x01(\rR\x06sector\".\n\x14DebugLi\
nkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\x08R\x06format\"~\n\
\x15DebugLinkInsertSdCard\x12#\n\rserial_number\x18\x01\x20\x01(\rR\x0cs\
erialNumber\x12%\n\x0ecapacity_bytes\x18\x02\x20\x01(\rR\rcapacityBytes\
\x12\x19\n\x08manuf_ID\x18\x03\x20\x01(\rR\x07manufID\",\n\x14DebugLinkW\
atchLayout\x12\x14\n\x05watch\x18\x01\x20\x01(\x08R\x05watch\"\x1b\n\x19\
DebugLinkResetDebugEventsB=\n#com.satoshilabs.trezor.lib.protobufB\x12Tr\
ezorMessageDebug\x80\xa6\x1d\x01\
nkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\x08R\x06format\"\xb1\
\x02\n\x15DebugLinkInsertSdCard\x12#\n\rserial_number\x18\x01\x20\x01(\r\
R\x0cserialNumber\x12%\n\x0ecapacity_bytes\x18\x02\x20\x01(\rR\rcapacity\
Bytes\x12\x19\n\x08manuf_ID\x18\x03\x20\x01(\rR\x07manufID\x12i\n\x0bdat\
a_blocks\x18\x04\x20\x03(\x0b2H.hw.trezor.messages.debug.DebugLinkInsert\
SdCard.DebugLinkSdCardDataBlockR\ndataBlocks\x1aF\n\x18DebugLinkSdCardDa\
taBlock\x12\x16\n\x06number\x18\x01\x20\x02(\rR\x06number\x12\x12\n\x04d\
ata\x18\x02\x20\x02(\x0cR\x04data\",\n\x14DebugLinkWatchLayout\x12\x14\n\
\x05watch\x18\x01\x20\x01(\x08R\x05watch\"\x1b\n\x19DebugLinkResetDebugE\
ventsB=\n#com.satoshilabs.trezor.lib.protobufB\x12TrezorMessageDebug\x80\
\xa6\x1d\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
@ -3741,7 +3973,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
deps.push(super::messages::file_descriptor().clone());
deps.push(super::messages_common::file_descriptor().clone());
deps.push(super::messages_management::file_descriptor().clone());
let mut messages = ::std::vec::Vec::with_capacity(16);
let mut messages = ::std::vec::Vec::with_capacity(17);
messages.push(DebugLinkDecision::generated_message_descriptor_data());
messages.push(DebugLinkLayout::generated_message_descriptor_data());
messages.push(DebugLinkReseedRandom::generated_message_descriptor_data());
@ -3758,6 +3990,7 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor {
messages.push(DebugLinkInsertSdCard::generated_message_descriptor_data());
messages.push(DebugLinkWatchLayout::generated_message_descriptor_data());
messages.push(DebugLinkResetDebugEvents::generated_message_descriptor_data());
messages.push(debug_link_insert_sd_card::DebugLinkSdCardDataBlock::generated_message_descriptor_data());
let mut enums = ::std::vec::Vec::with_capacity(3);
enums.push(debug_link_decision::DebugSwipeDirection::generated_enum_descriptor_data());
enums.push(debug_link_decision::DebugButton::generated_enum_descriptor_data());

View File

@ -215,6 +215,7 @@ def client(
# we need to reseed before the wipe
_raw_client.debug.reseed(0)
# Insert SD card if needed
sd_marker = request.node.get_closest_marker("sd_card")
if sd_marker:
_raw_client.debug.insert_sd_card(1)

View File

@ -1,34 +1,44 @@
import pytest
from trezorlib import device
from trezorlib import device, messages
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.messages import BackupType
from ...common import MNEMONIC12
from ...input_flows import InputFlowBip39RecoverySdCard
pytestmark = [pytest.mark.skip_t1, pytest.mark.skip_tr]
def prepare_data_for_sdcard() -> bytes:
# MNEMONIC12 backup block
backup_block_str = "54525A4D000000004C616C636F686F6C20776F6D616E206162757365206D75737420647572696E67206D6F6E69746F72206E6F626C652061637475616C206D6978656420747261646520616E676572206169736C654B1118DAD99C3A21E85AC1CBAE3D41F8BA02BE5E6B8422B3225C9DB53C316D8A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
return bytes.fromhex(backup_block_str)
@pytest.mark.setup_client(uninitialized=True)
@pytest.mark.sd_card(formatted=False)
def test_sd_backup_end_to_end(client: Client):
def test_sdrecover_tt_nopin_nopassphrase(client: Client):
with client:
device.reset(client, pin_protection=False, label="SD card")
# put seed writing directly to the first backup block
mnemonic_data_bytes = prepare_data_for_sdcard()
backup_block = messages.DebugLinkSdCardDataBlock(
number=65525 + 552 + 63, data=mnemonic_data_bytes
)
client.debug.insert_sd_card(serial_number=1, data_blocks=[backup_block])
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 BackupType.Bip39
IF = InputFlowBip39RecoverySdCard(client)
client.set_input_flow(IF.get())
device.recover(
client,
pin_protection=False,
passphrase_protection=False,
label="SD recovery",
)
with client:
device.wipe(client)
assert client.debug.state().mnemonic_secret.decode() == MNEMONIC12
# assert client.features.initialized is False
# assert client.features.no_backup is True
with client:
device.recover(client, pin_protection=False)
state = client.debug.state()
print(f"mnemonic is {state.mnemonic_secret}")
# assert state.mnemonic_type is backup_type
# assert state.mnemonic_secret == secret
assert client.features.pin_protection is False
assert client.features.passphrase_protection is False
assert client.features.backup_type is messages.BackupType.Bip39
assert client.features.label == "SD recovery"

View File

@ -0,0 +1,63 @@
import pytest
from trezorlib import btc, device, messages
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.messages import BackupType
from trezorlib.tools import parse_path
from ...common import WITH_MOCK_URANDOM
from ...input_flows import InputFlowBip39RecoverySdCard, InputFlowBip39ResetBackupSdCard
pytestmark = [pytest.mark.skip_t1, pytest.mark.skip_tr]
# NOTE: Test adapted from test_reset_recovery_bip39.py
@pytest.mark.setup_client(uninitialized=True)
@pytest.mark.sd_card(formatted=False)
def test_reset_recovery_sdcard(client: Client):
reset(client)
address_before = btc.get_address(client, "Bitcoin", parse_path("m/44h/0h/0h/0/0"))
device.wipe(client)
recover(client)
address_after = btc.get_address(client, "Bitcoin", parse_path("m/44h/0h/0h/0/0"))
assert address_before == address_after
def reset(client: Client, strength: int = 128, skip_backup: bool = False) -> None:
with WITH_MOCK_URANDOM, client:
IF = InputFlowBip39ResetBackupSdCard(client)
client.set_input_flow(IF.get())
# No PIN, no passphrase, don't display random
device.reset(
client,
display_random=False,
strength=strength,
passphrase_protection=False,
pin_protection=False,
label="test",
language="en-US",
backup_type=BackupType.Bip39,
)
# Check if device is properly initialized
assert client.features.initialized is True
assert client.features.needs_backup is False
assert client.features.pin_protection is False
assert client.features.passphrase_protection is False
def recover(client: Client):
with client:
IF = InputFlowBip39RecoverySdCard(client)
client.set_input_flow(IF.get())
client.watch_layout()
ret = device.recover(client, pin_protection=False, label="label")
# Workflow successfully ended
assert ret == messages.Success(message="Device recovered")
assert client.features.pin_protection is False
assert client.features.passphrase_protection is False

View File

@ -0,0 +1,70 @@
import itertools
import pytest
from trezorlib import btc, device, messages
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.messages import BackupType
from trezorlib.tools import parse_path
from ...common import WITH_MOCK_URANDOM
from ...input_flows import (
InputFlowSlip39BasicRecoverySdCard,
InputFlowSlip39BasicResetRecoverySdCard,
)
# NOTE: Test adapted from test_reset_recovery_slip39_basic.py
pytestmark = [pytest.mark.skip_t1, pytest.mark.skip_tr]
sdcard_serial_numbers = [1, 2, 3, 4, 5]
@pytest.mark.setup_client(uninitialized=True)
@WITH_MOCK_URANDOM
def test_reset_recovery(client: Client):
reset(client)
address_before = btc.get_address(client, "Bitcoin", parse_path("m/44h/0h/0h/0/0"))
for selected_sdcards in itertools.combinations(sdcard_serial_numbers, 3):
device.wipe(client)
recover(client, selected_sdcards)
address_after = btc.get_address(
client, "Bitcoin", parse_path("m/44h/0h/0h/0/0")
)
assert address_before == address_after
def reset(client: Client, strength: int = 128) -> list[str]:
with client:
IF = InputFlowSlip39BasicResetRecoverySdCard(client, sdcard_serial_numbers)
client.set_input_flow(IF.get())
# No PIN, no passphrase, don't display random
device.reset(
client,
display_random=False,
strength=strength,
passphrase_protection=False,
pin_protection=False,
label="test",
language="en-US",
backup_type=BackupType.Slip39_Basic,
)
# Check if device is properly initialized
assert client.features.initialized is True
assert client.features.needs_backup is False
assert client.features.pin_protection is False
assert client.features.passphrase_protection is False
def recover(client: Client, sdcards: list[int]):
with client:
IF = InputFlowSlip39BasicRecoverySdCard(client, sdcards)
client.set_input_flow(IF.get())
ret = device.recover(client, pin_protection=False, label="label")
# Workflow successfully ended
assert ret == messages.Success(message="Device recovered")
assert client.features.pin_protection is False
assert client.features.passphrase_protection is False

View File

@ -7,12 +7,10 @@ pytestmark = [pytest.mark.skip_t1, pytest.mark.skip_tr]
@pytest.mark.sd_card(formatted=True)
def test_sd_eject(client: Client):
print(client.features)
assert client.features.sd_card_present is True
client.debug.eject_sd_card()
client.refresh_features()
print(client.features)
assert client.features.sd_card_present is False
client.debug.insert_sd_card(2)

View File

@ -913,13 +913,14 @@ class InputFlowBip39Backup(InputFlowBase):
self.mnemonic = None
def input_flow_common(self) -> BRGeneratorType:
# choose Words
yield
self.debug.press_no()
# 1. Confirm Reset
yield from click_through(self.debug, screens=1, code=B.ResetDevice)
# 2. Choose Words
yield
self.debug.press_no()
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
@ -935,11 +936,39 @@ class InputFlowBip39ResetBackup(InputFlowBase):
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
# 4. Choose Words
yield
self.debug.press_no()
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
class InputFlowBip39ResetBackupSdCard(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
# 1. Confirm Reset
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
# 2. Choose SD Card
yield
self.debug.press_yes()
# 3. Go through format screens
yield from click_through(self.debug, screens=2, code=B.Other)
br = yield # confirm recovery seed check
assert br.code == B.Success
self.debug.press_yes()
br = yield # confirm success
assert br.code == B.Success
self.debug.press_yes()
class InputFlowBip39ResetPIN(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
@ -1014,6 +1043,9 @@ def load_5_shares(
mnemonics: list[str] = []
for _ in range(5):
# Choose Words
yield
debug.press_no()
# Phrase screen
mnemonic = yield from read_and_confirm_mnemonic(debug)
assert mnemonic is not None
@ -1143,6 +1175,9 @@ def load_5_groups_5_shares(
for _g in range(5):
for _s in range(5):
# Choose Words
yield
debug.press_no()
# Phrase screen
mnemonic = yield from read_and_confirm_mnemonic(debug)
assert mnemonic is not None
@ -1154,6 +1189,52 @@ def load_5_groups_5_shares(
return mnemonics
class InputFlowSlip39BasicResetRecoverySdCard(InputFlowBase):
def __init__(self, client: Client, sdcard_numbers):
super().__init__(client)
self.sdcard_numbers = sdcard_numbers
def input_flow_tt(self) -> BRGeneratorType:
# 1. Confirm Reset
# 2. Backup your seed
# 3. Confirm warning
# 4. shares info
# 5. Set & Confirm number of shares
# 6. threshold info
# 7. Set & confirm threshold value
# 8. Confirm show seeds
yield from click_through(self.debug, screens=8, code=B.ResetDevice)
# Mnemonic phrases
yield from load_5_shares_to_sdcards(self.debug, self.sdcard_numbers)
br = yield # safety warning
assert br.code == B.Success
self.debug.press_yes()
def load_5_shares_to_sdcards(debug: DebugLink, sdcard_numbers: list[int]) -> None:
for n in sdcard_numbers:
# Insert the card and erase (i.e. assume empty card)
debug.insert_sd_card(n)
debug.erase_sd_card(format=False)
# Choose SD card
yield
debug.press_yes()
# Go through format screens
yield from click_through(debug, screens=2, code=B.Other)
# Confirm continue
br = yield
assert br.code == B.Success
debug.press_yes()
# Eject the card
debug.eject_sd_card()
class InputFlowSlip39AdvancedBackup(InputFlowBase):
def __init__(self, client: Client, click_info: bool):
super().__init__(client)
@ -1328,11 +1409,29 @@ class InputFlowBip39Recovery(InputFlowBase):
yield from self.REC.confirm_recovery()
if self.pin is not None:
yield from self.PIN.setup_new_pin(self.pin)
# Choose Words
yield
self.debug.press_no()
yield from self.REC.setup_bip39_recovery(len(self.mnemonic))
yield from self.REC.input_mnemonic(self.mnemonic)
yield from self.REC.success_wallet_recovered()
class InputFlowBip39RecoverySdCard(InputFlowBase):
def __init__(self, client: Client, pin: str | None = None):
super().__init__(client)
self.pin = pin
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.pin is not None:
yield from self.PIN.setup_new_pin(self.pin)
# Choose "SD card"
yield
self.debug.press_yes()
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39AdvancedRecoveryDryRun(InputFlowBase):
def __init__(self, client: Client, shares: list[str], mismatch: bool = False):
super().__init__(client)
@ -1478,6 +1577,35 @@ class InputFlowSlip39BasicRecovery(InputFlowBase):
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39BasicRecoverySdCard(InputFlowBase):
def __init__(self, client: Client, sdcard_numbers: list[int], pin: str | None = None):
super().__init__(client)
self.sdcard_numbers = sdcard_numbers
self.pin = pin
def input_flow_common(self) -> BRGeneratorType:
yield from self.REC.confirm_recovery()
if self.pin is not None:
yield from self.PIN.setup_new_pin(self.pin)
# "Words" counterpart:
# yield from self.REC.setup_slip39_recovery(self.word_count)
# yield from self.REC.input_all_slip39_shares(self.shares)
# choose SD card
for n in self.sdcard_numbers:
self.debug.eject_sd_card()
self.debug.insert_sd_card(n)
# choose SD card
yield
self.debug.press_yes()
# enter next share
yield
self.debug.press_yes()
yield from self.REC.success_wallet_recovered()
class InputFlowSlip39BasicRecoveryAbort(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)