From 144a4e812d807772008b335b85b41d9d99eda30f Mon Sep 17 00:00:00 2001 From: obrusvit Date: Tue, 16 Jan 2024 11:01:04 +0100 Subject: [PATCH] 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 --- common/protob/messages-debug.proto | 10 +- .../generated/trezorio/sdcard_switcher.pyi | 2 +- core/src/apps/debug/__init__.py | 19 +- core/src/trezor/messages.py | 18 ++ core/src/trezor/sdcard.py | 2 +- python/src/trezorlib/debuglink.py | 2 + python/src/trezorlib/messages.py | 20 ++ .../src/protos/generated/messages_debug.rs | 251 +++++++++++++++++- tests/conftest.py | 1 + .../test_recovery_bip39_sdcard.py | 50 ++-- .../test_reset_recovery_bip39_sdcard.py | 63 +++++ ...test_reset_recovery_slip39_basic_sdcard.py | 70 +++++ .../device_tests/test_debug_sdcard_insert.py | 2 - tests/input_flows.py | 134 +++++++++- 14 files changed, 598 insertions(+), 46 deletions(-) create mode 100644 tests/device_tests/reset_recovery/test_reset_recovery_bip39_sdcard.py create mode 100644 tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic_sdcard.py diff --git a/common/protob/messages-debug.proto b/common/protob/messages-debug.proto index 43c75a5643..b9fe2488f8 100644 --- a/common/protob/messages-debug.proto +++ b/common/protob/messages-debug.proto @@ -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 diff --git a/core/mocks/generated/trezorio/sdcard_switcher.pyi b/core/mocks/generated/trezorio/sdcard_switcher.pyi index 70de1b2899..b4256892e2 100644 --- a/core/mocks/generated/trezorio/sdcard_switcher.pyi +++ b/core/mocks/generated/trezorio/sdcard_switcher.pyi @@ -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. diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py index 8c289acc36..5b4b120334 100644 --- a/core/src/apps/debug/__init__.py +++ b/core/src/apps/debug/__init__.py @@ -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() diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index dc64f83ac0..bc1a3d23cc 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -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" diff --git a/core/src/trezor/sdcard.py b/core/src/trezor/sdcard.py index 6ae31653c4..e09abf2f5f 100644 --- a/core/src/trezor/sdcard.py +++ b/core/src/trezor/sdcard.py @@ -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: diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py index a93ff4e951..0ee3a8fe8a 100644 --- a/python/src/trezorlib/debuglink.py +++ b/python/src/trezorlib/debuglink.py @@ -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, ) ) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index e8655968a7..885b4be486 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -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 = { diff --git a/rust/trezor-client/src/protos/generated/messages_debug.rs b/rust/trezor-client/src/protos/generated/messages_debug.rs index 7dc9af08de..f499224085 100644 --- a/rust/trezor-client/src/protos/generated/messages_debug.rs +++ b/rust/trezor-client/src/protos/generated/messages_debug.rs @@ -3220,6 +3220,8 @@ pub struct DebugLinkInsertSdCard { pub capacity_bytes: ::std::option::Option, // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.manuf_ID) pub manuf_ID: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.data_blocks) + pub data_blocks: ::std::vec::Vec, // 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", 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; } +/// 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, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkInsertSdCard.DebugLinkSdCardDataBlock.data) + pub data: ::std::option::Option<::std::vec::Vec>, + // 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 { + ::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) { + 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 { + 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 { + 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::( + "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; + } +} + // @@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()); diff --git a/tests/conftest.py b/tests/conftest.py index dae9f903a6..fe909c9bd6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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) diff --git a/tests/device_tests/reset_recovery/test_recovery_bip39_sdcard.py b/tests/device_tests/reset_recovery/test_recovery_bip39_sdcard.py index 92d67da41e..e4bb5c13aa 100644 --- a/tests/device_tests/reset_recovery/test_recovery_bip39_sdcard.py +++ b/tests/device_tests/reset_recovery/test_recovery_bip39_sdcard.py @@ -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" diff --git a/tests/device_tests/reset_recovery/test_reset_recovery_bip39_sdcard.py b/tests/device_tests/reset_recovery/test_reset_recovery_bip39_sdcard.py new file mode 100644 index 0000000000..0d913b6ac2 --- /dev/null +++ b/tests/device_tests/reset_recovery/test_reset_recovery_bip39_sdcard.py @@ -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 diff --git a/tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic_sdcard.py b/tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic_sdcard.py new file mode 100644 index 0000000000..918fbcef0c --- /dev/null +++ b/tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic_sdcard.py @@ -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 diff --git a/tests/device_tests/test_debug_sdcard_insert.py b/tests/device_tests/test_debug_sdcard_insert.py index e1d19342cf..3f2abfdf07 100644 --- a/tests/device_tests/test_debug_sdcard_insert.py +++ b/tests/device_tests/test_debug_sdcard_insert.py @@ -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) diff --git a/tests/input_flows.py b/tests/input_flows.py index f4fcd37bcd..5bf2764546 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -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)