Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,34 @@ jobs:
cat $GITHUB_OUTPUT

core_firmware:
name: Build firmware (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }})
name: Build firmware (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}${{matrix.n4w1 && ', n4w1' || '' }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
model: ${{ fromJSON(github.event_name == 'push' && '["T2B1", "T2T1", "T3B1", "T3T1", "T3W1"]' || '["T2T1", "T3B1", "T3T1", "T3W1"]') }}
coins: [universal, btconly]
type: ${{ fromJSON(github.event_name == 'schedule' && '["normal", "debuglink", "production"]' || '["normal", "debuglink"]') }}
n4w1: [false]
include:
- model: D001
coins: universal
type: normal
- model: T2B1
coins: universal
type: normal
- model: T3W1
coins: universal
type: debuglink
n4w1: true # currently N4W1 is not supported for normal builds
exclude:
- model: T3W1
type: production
env:
TREZOR_MODEL: ${{ matrix.model }}
BITCOIN_ONLY: ${{ matrix.coins == 'universal' && '0' || '1' }}
PYOPT: ${{ matrix.type == 'debuglink' && '0' || '1' }}
N4W1: ${{ matrix.n4w1 && '1' || '0' }}
PRODUCTION: ${{ matrix.type == 'production' && '1' || '0' }}
BOOTLOADER_DEVEL: ${{ matrix.model == 'T3W1' && '1' || '0' }}
QUIET_MODE: 1
Expand Down Expand Up @@ -96,7 +102,7 @@ jobs:
if: matrix.coins == 'btconly' && matrix.type != 'debuglink'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # actions/upload-artifact@v7.0.0
with:
name: core-firmware-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}
name: core-firmware-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}${{ matrix.n4w1 && '-n4w1' || '' }}
path: |
core/build/boardloader/*.bin
core/build/bootloader/*.bin
Expand All @@ -109,7 +115,7 @@ jobs:
retention-days: 7

core_emu:
name: Build emu (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}, ${{ matrix.asan }})
name: Build emu (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}, ${{ matrix.asan }}${{matrix.n4w1 && ', n4w1' || '' }})
runs-on: ubuntu-latest
needs: param
strategy:
Expand All @@ -120,13 +126,21 @@ jobs:
# type: [normal, debuglink]
type: ${{ fromJSON(github.event_name == 'schedule' && '["normal", "debuglink"]' || '["debuglink"]') }}
asan: ${{ fromJSON(needs.param.outputs.asan) }}
n4w1: [false]
exclude:
- type: normal
asan: asan
include:
- model: T3W1
coins: universal
type: debuglink
asan: noasan
n4w1: true # currently N4W1 is not supported for normal builds
env:
TREZOR_MODEL: ${{ matrix.model }}
BITCOIN_ONLY: ${{ matrix.coins == 'universal' && '0' || '1' }}
PYOPT: ${{ matrix.type == 'debuglink' && '0' || '1' }}
N4W1: ${{ matrix.n4w1 && '1' || '0' }}
ADDRESS_SANITIZER: ${{ matrix.asan == 'asan' && '1' || '0' }}
LSAN_OPTIONS: "suppressions=../../asan_suppressions.txt"
QUIET_MODE: 1
Expand All @@ -141,10 +155,10 @@ jobs:
- run: nix-shell --run "uv run make -C core build_unix_frozen"
- run: nix-shell --arg fullDeps true --run "cd vendor/ts-tvl && poetry env use 3.12 && poetry install && poetry run model_server tcp -c ../../tests/tropic_model/config.yml > ../../tests/trezor-tropic-model.log 2>&1 &"
- run: nix-shell --run "uv run make -C core test_emu_sanity"
- run: cp core/build/unix/trezor-emu-core core/build/unix/trezor-emu-core-${{ matrix.model }}-${{ matrix.coins }}
- run: cp core/build/unix/trezor-emu-core core/build/unix/trezor-emu-core-${{ matrix.model }}-${{ matrix.coins }}${{ matrix.n4w1 && '-n4w1' || '' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # actions/upload-artifact@v7.0.0
with:
name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }}
name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}
path: |
core/build/unix/trezor-emu-core*
core/build/bootloader_emu/bootloader.elf
Expand All @@ -153,7 +167,7 @@ jobs:

core_emu_arm:
if: github.event_name == 'schedule'
name: Build emu arm
name: Build emu ARM (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}, ${{ matrix.asan }}${{matrix.n4w1 && ', n4w1' || '' }})
runs-on: ubuntu-latest-arm64
needs: param
strategy:
Expand All @@ -163,10 +177,18 @@ jobs:
coins: [universal]
type: [debuglink]
asan: [noasan]
n4w1: [false]
include:
- model: T3W1
coins: universal
type: debuglink
asan: noasan
n4w1: true # currently N4W1 is not supported for normal builds
env:
TREZOR_MODEL: ${{ matrix.model }}
BITCOIN_ONLY: ${{ matrix.coins == 'universal' && '0' || '1' }}
PYOPT: ${{ matrix.type == 'debuglink' && '0' || '1' }}
N4W1: ${{ matrix.n4w1 && '1' || '0' }}
ADDRESS_SANITIZER: ${{ matrix.asan == 'asan' && '1' || '0' }}
LSAN_OPTIONS: "suppressions=../../asan_suppressions.txt"
QUIET_MODE: 1
Expand All @@ -178,10 +200,10 @@ jobs:
- run: nix-shell --run "uv run make -C core build_bootloader_emu"
if: matrix.coins == 'universal'
- run: nix-shell --run "uv run make -C core build_unix_frozen"
- run: mv core/build/unix/trezor-emu-core core/build/unix/trezor-emu-arm-core-${{ matrix.model }}-${{ matrix.coins }}
- run: mv core/build/unix/trezor-emu-core core/build/unix/trezor-emu-arm-core-${{ matrix.model }}-${{ matrix.coins }}${{ matrix.n4w1 && '-n4w1' || '' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # actions/upload-artifact@v7.0.0
with:
name: core-emu-arm-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }}
name: core-emu-arm-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}
path: |
core/build/unix/trezor-emu-*
core/build/bootloader_emu/bootloader.elf
Expand Down Expand Up @@ -268,7 +290,7 @@ jobs:
# See artifacts for a comprehensive report of UI.
# See [docs/tests/ui-tests](../tests/ui-tests.md) for more info.
core_device_test:
name: Device tests (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.asan }}, ${{ matrix.lang }})
name: Device tests (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.asan }}, ${{ matrix.lang }}${{ matrix.n4w1 && ', n4w1' || '' }})
runs-on: ubuntu-latest
needs:
- param
Expand All @@ -280,6 +302,13 @@ jobs:
coins: [universal, btconly]
asan: ${{ fromJSON(needs.param.outputs.asan) }}
lang: ${{ fromJSON(needs.param.outputs.test_lang) }}
n4w1: [false]
include:
- model: T3W1
coins: universal
asan: noasan
lang: en
n4w1: true
env:
TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }}
TREZOR_MODEL: ${{ matrix.model }}
Expand All @@ -295,19 +324,24 @@ jobs:
submodules: recursive
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # actions/download-artifact@v8.0.0
with:
name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}
name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}
path: core/build
- run: chmod +x core/build/unix/trezor-emu-core*
- uses: ./.github/actions/environment
- name: Start Tropic model
if: ${{ env.TREZOR_MODEL == 'T3W1' && env.ACTIONS_DO_UI_TEST != 'true' }} # ACTIONS_DO_UI_TEST refers to the test_emu_ui_multicore below which uses --control-emulators and starts tvl internally
run: nix-shell --arg fullDeps true --run "cd vendor/ts-tvl && poetry env use 3.12 && poetry install && poetry run model_server tcp -c ../../tests/tropic_model/config.yml > ../../tests/trezor-tropic-model.log 2>&1 &"
- run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_ui_multicore' || 'test_emu' }}"
- name: Run device tests
if: ${{ !matrix.n4w1 }}
run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_ui_multicore' || 'test_emu' }}"
- name: Run device tests (N4W1)
if: ${{ matrix.n4w1 }} # TODO: test N4W1 UI fixtures as well
run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_multicore' || 'test_emu' }}"
- run: tail -v -n50 tests/trezor*.log || true
if: failure()
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # actions/upload-artifact@v7.0.0
with:
name: core-test-device-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.lang }}-${{ matrix.asan }}
name: core-test-device-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.lang }}-${{ matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}
path: tests/trezor*.log
retention-days: 7
if: always()
Expand All @@ -316,7 +350,7 @@ jobs:
model: ${{ matrix.model }}
lang: ${{ matrix.lang }}
status: ${{ job.status }}
if: ${{ always() && env.ACTIONS_DO_UI_TEST == 'true' }}
if: ${{ always() && env.ACTIONS_DO_UI_TEST == 'true' && !matrix.n4w1 }}
continue-on-error: true
- uses: ./.github/actions/upload-coverage

Expand Down Expand Up @@ -769,7 +803,7 @@ jobs:
steps:
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # actions/download-artifact@v8.0.0
with:
pattern: core-emu*debuglink-noasan
pattern: core-emu*debuglink-noasan*
merge-multiple: true
- name: Configure aws credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # aws-actions/configure-aws-credentials@v6.0.0
Expand All @@ -792,7 +826,7 @@ jobs:
steps:
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # actions/download-artifact@v8.0.0
with:
pattern: core-emu*debuglink-noasan
pattern: core-emu*debuglink-noasan*
merge-multiple: true
- name: Configure aws credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # aws-actions/configure-aws-credentials@v6.0.0
Expand Down
145 changes: 138 additions & 7 deletions core/src/apps/management/recovery_device/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import storage.recovery as storage_recovery
import storage.recovery_shares as storage_recovery_shares
from trezor import TR
from trezor import TR, utils
from trezor.ui.layouts.recovery import ( # noqa: F401
request_word_count,
show_already_added,
Expand Down Expand Up @@ -278,12 +278,143 @@ async def request_mnemonic(self) -> str | None:
return None


async def choose_handler(method: BackupMethod | None) -> type[RecoveryHandler]:
from trezor.enums import BackupMethod
if not utils.USE_N4W1:

if method is not BackupMethod.Display and __debug__:
from trezor import log
async def choose_handler(method: BackupMethod | None) -> type[RecoveryHandler]:

log.warning(__name__, "Unsupported backup method: %s", method)
if __debug__:
from trezor.enums import BackupMethod

return _DisplayHandler
if method not in (None, BackupMethod.Display):
from trezor import log

log.warning(__name__, "Unsupported backup method: %s", method)

return _DisplayHandler

else:

if TYPE_CHECKING:
from trezor.messages import BackupMethod

from .recover import Slip39State

async def choose_handler(method: BackupMethod | None) -> type[RecoveryHandler]:
from trezor.enums import BackupMethod

if method is None:
from trezor.ui.layouts.recovery import choose_method

method = await choose_method(TR.recovery__title, TR.backup__type_have)

if method is BackupMethod.N4W1:
return _N4W1Handler

if method not in (None, BackupMethod.Display):
from trezor import log

if __debug__:
log.warning(__name__, "Unsupported backup method: %s", method)

return _DisplayHandler

class RetryRead(Exception):
def __init__(self, msg: str) -> None:
self.msg = msg

async def _read_share() -> str:
from apps.debug import n4w1_mock

with n4w1_mock.ctx as ctx:
# returns `None` on cancellation or retriable error.
await ctx.confirm_connect(
title=TR.recovery__title,
description=TR.n4w1__hold_next,
button=TR.n4w1__footer_next,
br_name="backup_read",
)
# continue N4W1 communication (the tag is connected)

# TODO: animate during read?
if (blob := await ctx.read(key="mnemonic")) is None:
raise RetryRead(TR.n4w1__err_empty)

# TODO: use protobuf?
blob = bytes(blob)
try:
return blob.decode()
except ValueError:
raise RetryRead(TR.n4w1__err_damaged)

class _N4W1Handler:
def __init__(
self,
recovery_type: RecoveryType,
slip39_state: Slip39State | None,
) -> None:
super().__init__()
self.recovery_type = recovery_type
# `slip39_state is None` indicates that we are (re)starting the first recovery step.
self.slip39_state = slip39_state

@classmethod
async def load(cls, recovery_type: RecoveryType) -> "RecoveryHandler":
return cls(recovery_type, load_slip39_state())

async def show_state(self, is_retry: bool) -> None:
if is_retry or self.slip39_state is None:
# don't show recovery state on retries and before the first share is entered
return
word_count = self.slip39_state[0]
await _request_share_first_screen(word_count, self.recovery_type)

async def request_mnemonic(self) -> str | None:
"""Return the mnemonic or `None` on cancellation/validation error."""
import trezorui_api
from trezor.ui.layouts.common import raise_if_not_confirmed

while True:
try:
share = await _read_share()
break
except RetryRead as exc:
await raise_if_not_confirmed(
trezorui_api.show_warning(
title=TR.words__important,
button=TR.buttons__continue,
description=exc.msg,
danger=True,
),
br_name="recovery_retry",
)
# wait for a new N4W1 tag
continue

return await self.check_words(share)

async def check_words(self, share: str) -> str | None:
from trezor.ui.layouts.progress import progress

from .word_validity import WordValidityResult, check

# Can be `None` when checking the first share.
backup_type = self.slip39_state and self.slip39_state[1]
share_words = share.split(" ")

progress_obj = progress(description=TR.n4w1__reading)
progress_obj.start()

try:
# Re-verify mnemonic prefixes:
steps = len(share_words)
for prefix_len in range(1, 1 + steps):
progress_obj.report((1000 * prefix_len) // steps)
check(backup_type, partial_mnemonic=share_words[:prefix_len])

return share
except WordValidityResult as exc:
# if they were invalid or some checks failed we continue and request them again
await exc.show_error()
return None
finally:
progress_obj.stop()
Loading
Loading