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

feat(core/sdbackup): improve recovery

- recovery does not need filesystem
- so far not possible to use SD with shamir
- export first backup block as a C constant
- WIP
This commit is contained in:
obrusvit 2023-12-06 11:20:27 +01:00
parent 83b4066f55
commit 9df4b5e24f
12 changed files with 166 additions and 82 deletions

View File

@ -21,6 +21,7 @@
#include "py/mperrno.h" #include "py/mperrno.h"
#include "py/obj.h" #include "py/obj.h"
#include "py/objstr.h" #include "py/objstr.h"
#include "stdio.h"
// clang-format off // clang-format off
#include "ff.h" #include "ff.h"
@ -559,10 +560,10 @@ STATIC mp_obj_t mod_trezorio_fatfs_mkfs(size_t n_args, const mp_obj_t *args) {
// create partition // create partition
if (n_args > 0 && args[0] == mp_const_true) { if (n_args > 0 && args[0] == mp_const_true) {
// for SD card backup: we make a small partition and keep the rest // for SD card backup: we make a small FAT32 partition and keep the rest
// unallocated // unallocated, FatFS allows smallest size as 0xFFF5 + 550. Windows needs
// TODO: seems like not big enough for Windows (problem detected popup) // two more clusters not to complain. MAX_FAT16 + 1 + 551
const int n_clusters = 0xFFF5 + 1 + 549; // MAX_FAT16 + 1 + overhead const int n_clusters = 0xFFF5 + 552;
make_partition(n_clusters); make_partition(n_clusters);
} else { } else {
// for other use (SD salt): make the partition over the whole space. // for other use (SD salt): make the partition over the whole space.
@ -589,14 +590,14 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorio_fatfs_mkfs_obj, 0, 1,
/// """ /// """
STATIC mp_obj_t mod_trezorio_fatfs_get_capacity() { STATIC mp_obj_t mod_trezorio_fatfs_get_capacity() {
FATFS_ONLY_MOUNTED; FATFS_ONLY_MOUNTED;
/* printf("csize: %d\n", fs_instance.csize); */ printf("csize: %d\n", fs_instance.csize);
/* printf("fatent: %d\n", fs_instance.n_fatent); */ printf("fatent: %d\n", fs_instance.n_fatent);
/* printf("free clusters: %d\n", fs_instance.free_clst); */ printf("free clusters: %d\n", fs_instance.free_clst);
/* printf("volbase: %d\n", fs_instance.volbase); */ printf("volbase: %d\n", fs_instance.volbase);
/* printf("fatbase: %d\n", fs_instance.fatbase); */ printf("fatbase: %d\n", fs_instance.fatbase);
/* printf("dirbase: %d\n", fs_instance.dirbase); */ printf("dirbase: %d\n", fs_instance.dirbase);
/* printf("database: %d\n", fs_instance.database); */ printf("database: %d\n", fs_instance.database);
/* printf("winsect: %d\n", fs_instance.winsect); */ printf("winsect: %d\n", fs_instance.winsect);
// total number of clusters in the filesystem // total number of clusters in the filesystem
DWORD total_clusters = fs_instance.n_fatent - 2; DWORD total_clusters = fs_instance.n_fatent - 2;
// size of each cluster in bytes // size of each cluster in bytes

View File

@ -25,6 +25,7 @@
/// package: trezorio.sdcard /// package: trezorio.sdcard
/// BLOCK_SIZE: int # size of SD card block /// BLOCK_SIZE: int # size of SD card block
/// BACKUP_BLOCK_START: int # first sector for SD seed backup
/// def is_present() -> bool: /// def is_present() -> bool:
/// """ /// """
@ -78,7 +79,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorio_sdcard_capacity_obj,
/// """ /// """
/// Reads blocks starting with block_num from the SD card into buf. /// Reads blocks starting with block_num from the SD card into buf.
/// Number of bytes read is length of buf rounded down to multiply of /// Number of bytes read is length of buf rounded down to multiply of
/// SDCARD_BLOCK_SIZE. /// SDCARD_BLOCK_SIZE.
/// """ /// """
STATIC mp_obj_t mod_trezorio_sdcard_read(mp_obj_t block_num, mp_obj_t buf) { STATIC mp_obj_t mod_trezorio_sdcard_read(mp_obj_t block_num, mp_obj_t buf) {
uint32_t block = trezor_obj_get_uint(block_num); uint32_t block = trezor_obj_get_uint(block_num);
@ -97,7 +98,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_sdcard_read_obj,
/// """ /// """
/// Writes blocks starting with block_num from buf to the SD card. /// Writes blocks starting with block_num from buf to the SD card.
/// Number of bytes written is length of buf rounded down to multiply of /// Number of bytes written is length of buf rounded down to multiply of
/// SDCARD_BLOCK_SIZE. /// SDCARD_BLOCK_SIZE.
/// """ /// """
STATIC mp_obj_t mod_trezorio_sdcard_write(mp_obj_t block_num, mp_obj_t buf) { STATIC mp_obj_t mod_trezorio_sdcard_write(mp_obj_t block_num, mp_obj_t buf) {
uint32_t block = trezor_obj_get_uint(block_num); uint32_t block = trezor_obj_get_uint(block_num);
@ -124,6 +125,7 @@ STATIC const mp_rom_map_elem_t mod_trezorio_sdcard_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_capacity), {MP_ROM_QSTR(MP_QSTR_capacity),
MP_ROM_PTR(&mod_trezorio_sdcard_capacity_obj)}, MP_ROM_PTR(&mod_trezorio_sdcard_capacity_obj)},
{MP_ROM_QSTR(MP_QSTR_BLOCK_SIZE), MP_ROM_INT(SDCARD_BLOCK_SIZE)}, {MP_ROM_QSTR(MP_QSTR_BLOCK_SIZE), MP_ROM_INT(SDCARD_BLOCK_SIZE)},
{MP_ROM_QSTR(MP_QSTR_BACKUP_BLOCK_START), MP_ROM_INT(SDCARD_BACKUP_BLOCK_START)},
{MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mod_trezorio_sdcard_read_obj)}, {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mod_trezorio_sdcard_read_obj)},
{MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_sdcard_write_obj)}, {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_sdcard_write_obj)},
}; };

View File

@ -50,6 +50,9 @@
// this is a fixed size and should not be changed // this is a fixed size and should not be changed
#define SDCARD_BLOCK_SIZE (512) #define SDCARD_BLOCK_SIZE (512)
// fixed offset for SD seed backup:
// Maximal size for FAT16 + overhead + start offset (65525 + 552 + 63)
#define SDCARD_BACKUP_BLOCK_START (66140)
void sdcard_init(void); void sdcard_init(void);
secbool __wur sdcard_power_on(void); secbool __wur sdcard_power_on(void);

View File

@ -1,5 +1,6 @@
from typing import * from typing import *
BLOCK_SIZE: int # size of SD card block BLOCK_SIZE: int # size of SD card block
BACKUP_BLOCK_START: int # first sector for SD seed backup
# extmod/modtrezorio/modtrezorio-sdcard.h # extmod/modtrezorio/modtrezorio-sdcard.h
@ -37,7 +38,7 @@ def read(block_num: int, buf: bytearray) -> None:
""" """
Reads blocks starting with block_num from the SD card into buf. Reads blocks starting with block_num from the SD card into buf.
Number of bytes read is length of buf rounded down to multiply of Number of bytes read is length of buf rounded down to multiply of
SDCARD_BLOCK_SIZE. SDCARD_BLOCK_SIZE.
""" """
@ -46,5 +47,5 @@ def write(block_num: int, buf: bytes) -> None:
""" """
Writes blocks starting with block_num from buf to the SD card. Writes blocks starting with block_num from buf to the SD card.
Number of bytes written is length of buf rounded down to multiply of Number of bytes written is length of buf rounded down to multiply of
SDCARD_BLOCK_SIZE. SDCARD_BLOCK_SIZE.
""" """

View File

@ -24,7 +24,6 @@ async def recovery_device(msg: RecoveryDevice) -> Success:
from trezor.ui.layouts import ( from trezor.ui.layouts import (
confirm_action, confirm_action,
confirm_reset_device, confirm_reset_device,
choose_backup_medium,
) )
from apps.common.request_pin import ( from apps.common.request_pin import (

View File

@ -41,39 +41,57 @@ async def recovery_process() -> Success:
raise wire.ActionCancelled raise wire.ActionCancelled
async def _choose_backup_medium() -> str:
from trezor import utils
if utils.USE_SD_CARD:
from apps.management.sd_backup import bip39_choose_backup_medium
# ask the user for backup type (words/SD card)
backup_medium: str = await bip39_choose_backup_medium(recovery=True)
else:
backup_medium: str = "words"
return backup_medium
async def _continue_recovery_process() -> Success: async def _continue_recovery_process() -> Success:
from trezor import utils from trezor import utils
from trezor.errors import MnemonicError from trezor.errors import MnemonicError
from trezor.ui.layouts import choose_backup_medium
if utils.USE_SD_CARD:
from apps.management.sd_backup import (
sdcard_recover_seed,
)
# gather the current recovery state from storage # gather the current recovery state from storage
dry_run = storage_recovery.is_dry_run() dry_run = storage_recovery.is_dry_run()
word_count, backup_type = recover.load_slip39_state() word_count, backup_type = recover.load_slip39_state()
# ask the user for backup type (words/SD card) # Both word_count and backup_type are derived from the same data. Both will be
backup_medium: str = await choose_backup_medium(recovery=True) # either set or unset. We use 'backup_type is None' to detect status of both.
if backup_medium == "sdcard": # The following variable indicates that we are (re)starting the first recovery step,
from apps.management.sd_backup import sdcard_recover_seed # which includes word count selection.
is_first_step = backup_type is None
words = await sdcard_recover_seed() if not is_first_step:
if not words: assert word_count is not None
raise wire.ProcessError("SD card backup could not be recovered.") # If we continue recovery, show starting screen with word count immediately.
secret, backup_type = await _process_words(words) await _request_share_first_screen(word_count)
else:
# Both word_count and backup_type are derived from the same data. Both will be
# either set or unset. We use 'backup_type is None' to detect status of both.
# The following variable indicates that we are (re)starting the first recovery step,
# which includes word count selection.
is_first_step = backup_type is None
if not is_first_step: secret = None
assert word_count is not None while secret is None:
# If we continue recovery, show starting screen with word count immediately. if is_first_step:
await _request_share_first_screen(word_count) backup_medium: str = await _choose_backup_medium()
if backup_medium == "sdcard":
secret = None # attempt to recover words from sd card
while secret is None: words = await sdcard_recover_seed()
if is_first_step: if words is None:
continue
word_count = len(words.split())
if word_count not in (12, 24):
await show_warning("Shamir not yet supported for SD")
raise wire.ProcessError("Attempt to recover Shamir from SD card.")
else:
# If we are starting recovery, ask for word count first... # If we are starting recovery, ask for word count first...
# _request_word_count # _request_word_count
# For TT, just continuing straight to word count keyboard # For TT, just continuing straight to word count keyboard
@ -85,23 +103,24 @@ async def _continue_recovery_process() -> Success:
word_count = await layout.request_word_count(dry_run) word_count = await layout.request_word_count(dry_run)
# ...and only then show the starting screen with word count. # ...and only then show the starting screen with word count.
await _request_share_first_screen(word_count) await _request_share_first_screen(word_count)
assert word_count is not None assert word_count is not None
if backup_medium == "words":
# ask for mnemonic words one by one # ask for mnemonic words one by one
words = await layout.request_mnemonic(word_count, backup_type) words = await layout.request_mnemonic(word_count, backup_type)
# if they were invalid or some checks failed we continue and request them again # if they were invalid or some checks failed we continue and request them again
if not words: if not words:
continue continue
try: try:
secret, backup_type = await _process_words(words) secret, backup_type = await _process_words(words)
# If _process_words succeeded, we now have both backup_type (from # If _process_words succeeded, we now have both backup_type (from
# its result) and word_count (from _request_word_count earlier), which means # its result) and word_count (from _request_word_count earlier), which means
# that the first step is complete. # that the first step is complete.
is_first_step = False is_first_step = False
except MnemonicError: except MnemonicError:
await layout.show_invalid_mnemonic(word_count) await layout.show_invalid_mnemonic(word_count)
assert backup_type is not None assert backup_type is not None
if dry_run: if dry_run:

View File

@ -215,18 +215,26 @@ def _compute_secret_from_entropy(
async def _backup_bip39_sdcard(mnemonic: str) -> None: async def _backup_bip39_sdcard(mnemonic: str) -> None:
from apps.management.sd_backup import sdcard_backup_seed from apps.management.sd_backup import sdcard_backup_seed
backup_success: bool = await sdcard_backup_seed(mnemonic) backup_success: bool = await sdcard_backup_seed(mnemonic)
if not backup_success: if not backup_success:
raise ProcessError("SD Card backup could not be verified.") raise ProcessError("SD Card backup could not be verified.")
async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None: async def backup_seed(backup_type: BackupType, mnemonic_secret: bytes) -> None:
from trezor import utils
if backup_type == BAK_T_SLIP39_BASIC: if backup_type == BAK_T_SLIP39_BASIC:
await _backup_slip39_basic(mnemonic_secret) await _backup_slip39_basic(mnemonic_secret)
elif backup_type == BAK_T_SLIP39_ADVANCED: elif backup_type == BAK_T_SLIP39_ADVANCED:
await _backup_slip39_advanced(mnemonic_secret) await _backup_slip39_advanced(mnemonic_secret)
else: else:
backup_medium: str = await layout.bip39_choose_backup_medium() if utils.USE_SD_CARD:
from apps.management.sd_backup import bip39_choose_backup_medium
backup_medium: str = await bip39_choose_backup_medium()
else:
backup_medium: str = "words"
if backup_medium == "sdcard": if backup_medium == "sdcard":
await _backup_bip39_sdcard(mnemonic_secret) await _backup_bip39_sdcard(mnemonic_secret)
elif backup_medium == "words": elif backup_medium == "words":

View File

@ -158,12 +158,6 @@ async def show_backup_success() -> None:
# BIP39 # BIP39
# === # ===
async def bip39_choose_backup_medium() -> str:
# TODO this will be general, not only for BIP39
from trezor.ui.layouts import choose_backup_medium
return await choose_backup_medium()
async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None: async def bip39_show_and_confirm_mnemonic(mnemonic: str) -> None:
# warn user about mnemonic safety # warn user about mnemonic safety

View File

@ -5,6 +5,12 @@ from storage.sd_seed_backup import store_seed_on_sdcard, recover_seed_from_sdcar
if utils.USE_SD_CARD: if utils.USE_SD_CARD:
fatfs = io.fatfs # global_import_cache fatfs = io.fatfs # global_import_cache
async def bip39_choose_backup_medium(recovery: bool = False) -> str:
# TODO this will be general, not only for BIP39
from trezor.ui.layouts import choose_backup_medium
return await choose_backup_medium(recovery)
async def sdcard_backup_seed(mnemonic_secret: bytes) -> bool: async def sdcard_backup_seed(mnemonic_secret: bytes) -> bool:
from apps.common.sdcard import ensure_sdcard from apps.common.sdcard import ensure_sdcard

View File

@ -3,20 +3,19 @@ from typing import TYPE_CHECKING
import storage.device import storage.device
from trezor import io, utils from trezor import io, utils
from trezor.sdcard import with_filesystem from trezor.sdcard import with_filesystem, with_sdcard
from trezorcrypto import crc from trezorcrypto import crc
if utils.USE_SD_CARD: if utils.USE_SD_CARD:
fatfs = io.fatfs # global_import_cache fatfs = io.fatfs # global_import_cache
sdcard = io.sdcard # global_import_cache sdcard = io.sdcard # global_import_cache
SDCARD_BLOCK_SIZE_B = sdcard.BLOCK_SIZE # global_import_cache SDCARD_BLOCK_SIZE_B = sdcard.BLOCK_SIZE # global_import_cache
SDBACKUP_BLOCK_START = 66_138 SDBACKUP_BLOCK_START = sdcard.BACKUP_BLOCK_START # global_import_cache
SDBACKUP_BLOCK_OFFSET = 130 # TODO arbitrary for now SDBACKUP_BLOCK_OFFSET = 130 # TODO arbitrary for now
SDBACKUP_MAGIC = b"TRZS" SDBACKUP_N_WRITINGS = 100 # TODO decide between offset/writings
SDBACKUP_MAGIC = b"TRZM"
SDBACKUP_VERSION = b"00" SDBACKUP_VERSION = b"00"
# TODO with_filesystem can be just with_sdcard, unnecessary to mount FS for recovery
@with_filesystem @with_filesystem
def store_seed_on_sdcard(mnemonic_secret: bytes) -> bool: def store_seed_on_sdcard(mnemonic_secret: bytes) -> bool:
@ -29,12 +28,11 @@ def store_seed_on_sdcard(mnemonic_secret: bytes) -> bool:
return False return False
@with_filesystem @with_sdcard
def recover_seed_from_sdcard() -> str | None: def recover_seed_from_sdcard() -> str | None:
return _read_seed_unalloc() return _read_seed_unalloc()
@with_filesystem
def _verify_backup(mnemonic_secret: bytes) -> bool: def _verify_backup(mnemonic_secret: bytes) -> bool:
mnemonic_read_plain = _read_seed_plain_text() mnemonic_read_plain = _read_seed_plain_text()
mnemonic_read_unalloc = _read_seed_unalloc() mnemonic_read_unalloc = _read_seed_unalloc()
@ -49,15 +47,12 @@ def _verify_backup(mnemonic_secret: bytes) -> bool:
) )
@with_filesystem
def _write_seed_unalloc(mnemonic_secret: bytes) -> None: def _write_seed_unalloc(mnemonic_secret: bytes) -> None:
block_to_write = _encode_mnemonic_to_backup_block(mnemonic_secret) block_to_write = _encode_mnemonic_to_backup_block(mnemonic_secret)
for block_idx in _storage_blocks_gen(): for block_idx in _storage_blocks_gen():
# print(f"block_idx: {block_idx}, writing: {block_to_write[10:10+4]}")
sdcard.write(block_idx, block_to_write) sdcard.write(block_idx, block_to_write)
@with_filesystem
def _read_seed_unalloc() -> str | None: def _read_seed_unalloc() -> str | None:
block_buffer = bytearray(SDCARD_BLOCK_SIZE_B) block_buffer = bytearray(SDCARD_BLOCK_SIZE_B)
for block_idx in _storage_blocks_gen(): for block_idx in _storage_blocks_gen():
@ -66,28 +61,45 @@ def _read_seed_unalloc() -> str | None:
mnemonic_read = _decode_mnemonic_from_backup_block(block_buffer) mnemonic_read = _decode_mnemonic_from_backup_block(block_buffer)
if mnemonic_read is not None: if mnemonic_read is not None:
break break
except fatfs.FatFSError: except Exception:
return None return None
# print(f"_read_seed_unalloc: block_read: {block_read}") if mnemonic_read is None:
return None
mnemonic_read_decoded = mnemonic_read.decode("utf-8").rstrip("\x00") mnemonic_read_decoded = mnemonic_read.decode("utf-8").rstrip("\x00")
# print(f"_read_seed_unalloc: mnemonic_read_decoded: {mnemonic_read_decoded}")
return mnemonic_read_decoded return mnemonic_read_decoded
def _storage_blocks_gen() -> Generator: def _storage_blocks_gen() -> Generator:
return _storage_blocks_gen_by_n()
def _storage_blocks_gen_by_offset() -> Generator:
cap = sdcard.capacity() cap = sdcard.capacity()
if cap == 0: if cap == 0:
raise ProcessError raise ProcessError
BLOCK_END = cap // SDCARD_BLOCK_SIZE_B - 1 BLOCK_END = cap // SDCARD_BLOCK_SIZE_B - 1
return range(SDBACKUP_BLOCK_START, BLOCK_END, SDBACKUP_BLOCK_OFFSET) return range(SDBACKUP_BLOCK_START, BLOCK_END, SDBACKUP_BLOCK_OFFSET)
def _storage_blocks_gen_by_n() -> Generator:
cap = sdcard.capacity()
if cap == 0:
raise ProcessError
BLOCK_END = cap // SDCARD_BLOCK_SIZE_B - 1
return (
SDBACKUP_BLOCK_START
+ n * (BLOCK_END - SDBACKUP_BLOCK_START) // (SDBACKUP_N_WRITINGS - 1)
for n in range(SDBACKUP_N_WRITINGS)
)
""" """
Backup Memory Block Layout: Backup Memory Block Layout:
+----------------------+----------------------+----------------------+-------------------------------+ +----------------------+----------------------+----------------------+-------------------------------+
| SDBACKUP_MAGIC (4B) | SDBACKUP_VERSION (2B)| SEED_LENGTH (4B) | MNEMONIC (variable length) | | SDBACKUP_MAGIC (4B) | SDBACKUP_VERSION (2B)| SEED_LENGTH (4B) | MNEMONIC (variable length) |
+----------------------+----------------------+----------------------+-------------------------------+ +----------------------+----------------------+----------------------+-------------------------------+
| CHECKSUM (4B) | Padding (variable) | | CHECKSUM (4B) | Padding (variable) |
+----------------------------------------------------------------------------+----------------------------+ +-----------------------------------------------------------------------+----------------------------+
- SDBACKUP_MAGIC: 4 bytes magic number identifying the backup block - SDBACKUP_MAGIC: 4 bytes magic number identifying the backup block
- SDBACKUP_VERSION: 2 bytes representing the version of the backup format - SDBACKUP_VERSION: 2 bytes representing the version of the backup format
@ -147,13 +159,11 @@ def _decode_mnemonic_from_backup_block(block: bytes) -> bytes | None:
return None return None
@with_filesystem
def _write_readme() -> None: def _write_readme() -> None:
with fatfs.open("README.txt", "w") as f: with fatfs.open("README.txt", "w") as f:
f.write("This is a Trezor backup SD card.") f.write("This is a Trezor backup SD card.")
@with_filesystem
def _write_seed_plain_text(mnemonic_secret: bytes) -> None: def _write_seed_plain_text(mnemonic_secret: bytes) -> None:
# TODO to be removed, just for testing purposes # TODO to be removed, just for testing purposes
fatfs.mkdir("/trezor", True) fatfs.mkdir("/trezor", True)
@ -161,7 +171,6 @@ def _write_seed_plain_text(mnemonic_secret: bytes) -> None:
f.write(mnemonic_secret) f.write(mnemonic_secret)
@with_filesystem
def _read_seed_plain_text() -> str | None: def _read_seed_plain_text() -> str | None:
# TODO to be removed, just for testing purposes # TODO to be removed, just for testing purposes
mnemonic_read = bytearray(SDCARD_BLOCK_SIZE_B) mnemonic_read = bytearray(SDCARD_BLOCK_SIZE_B)
@ -170,5 +179,4 @@ def _read_seed_plain_text() -> str | None:
f.read(mnemonic_read) f.read(mnemonic_read)
except fatfs.FatFSError: except fatfs.FatFSError:
return None return None
# print(f"_read_seed_plain_text: mnemonic_read: {mnemonic_read}")
return mnemonic_read.decode("utf-8").rstrip("\x00") return mnemonic_read.decode("utf-8").rstrip("\x00")

View File

@ -26,6 +26,11 @@ if TYPE_CHECKING:
R = TypeVar("R") R = TypeVar("R")
def is_trz_card() -> bool:
sdcard.capacity()
pass
class FilesystemWrapper: class FilesystemWrapper:
_INSTANCE: "FilesystemWrapper" | None = None _INSTANCE: "FilesystemWrapper" | None = None
@ -78,3 +83,11 @@ def with_filesystem(func: Callable[P, R]) -> Callable[P, R]:
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapped_func return wrapped_func
def with_sdcard(func: Callable[P, R]) -> Callable[P, R]:
def wrapped_func(*args: P.args, **kwargs: P.kwargs) -> R:
with filesystem(mounted=False):
return func(*args, **kwargs)
return wrapped_func

View File

@ -1,11 +1,11 @@
from common import * from common import *
from trezorio import sdcard, fatfs
from storage.sd_seed_backup import * from storage.sd_seed_backup import *
from trezor import io, sdcard
class TestStorageSdSeedBackup(unittest.TestCase): class TestStorageSdSeedBackup(unittest.TestCase):
# TODO add more tests, also with partly damaged backup # TODO add more tests, also for repairing the backup card
def setUp(self): def setUp(self):
self.mnemonic = ( self.mnemonic = (
@ -16,14 +16,44 @@ class TestStorageSdSeedBackup(unittest.TestCase):
# with self.assertRaises(fatfs.FatFSError): # with self.assertRaises(fatfs.FatFSError):
# store_seed_on_sdcard(self.mnemonic.encode("utf-8")) # store_seed_on_sdcard(self.mnemonic.encode("utf-8"))
sdcard.power_on() io.sdcard.power_on()
fatfs.mkfs(True) io.fatfs.mkfs(True)
io.fatfs.mount()
success = store_seed_on_sdcard(self.mnemonic.encode("utf-8")) success = store_seed_on_sdcard(self.mnemonic.encode("utf-8"))
self.assertTrue(success) self.assertTrue(success)
restored = recover_seed_from_sdcard() restored = recover_seed_from_sdcard()
self.assertEqual(self.mnemonic, restored) self.assertEqual(self.mnemonic, restored)
io.fatfs.unmount()
io.sdcard.power_off()
def test_backup_partlywipe_restore(self):
with sdcard.filesystem(mounted=True):
success = store_seed_on_sdcard(self.mnemonic.encode("utf-8"))
self.assertTrue(success)
# wipe half of the card, restore must succeed
block_buffer = bytearray(SDCARD_BLOCK_SIZE_B)
with sdcard.filesystem(mounted=False):
for block_num in range((io.sdcard.capacity() // 2) // io.sdcard.BLOCK_SIZE):
io.sdcard.write(block_num, block_buffer)
with sdcard.filesystem(mounted=False):
restored = recover_seed_from_sdcard()
self.assertEqual(self.mnemonic, restored)
# remove everything, restore fails
with sdcard.filesystem(mounted=False):
for block_num in range(io.sdcard.capacity() // io.sdcard.BLOCK_SIZE):
io.sdcard.write(block_num, block_buffer)
with sdcard.filesystem(mounted=False):
restored = recover_seed_from_sdcard()
self.assertEqual(None, restored)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()