mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 15:38:11 +00:00
commit
2c0504ad1c
@ -62,11 +62,12 @@ message ButtonRequest {
|
|||||||
ButtonRequest_PublicKey = 11;
|
ButtonRequest_PublicKey = 11;
|
||||||
ButtonRequest_MnemonicWordCount = 12;
|
ButtonRequest_MnemonicWordCount = 12;
|
||||||
ButtonRequest_MnemonicInput = 13;
|
ButtonRequest_MnemonicInput = 13;
|
||||||
ButtonRequest_PassphraseType = 14;
|
// ButtonRequest_PassphraseType = 14; DEPRECATED
|
||||||
ButtonRequest_UnknownDerivationPath = 15;
|
ButtonRequest_UnknownDerivationPath = 15;
|
||||||
ButtonRequest_RecoveryHomepage = 16;
|
ButtonRequest_RecoveryHomepage = 16;
|
||||||
ButtonRequest_Success = 17;
|
ButtonRequest_Success = 17;
|
||||||
ButtonRequest_Warning = 18;
|
ButtonRequest_Warning = 18;
|
||||||
|
ButtonRequest_PassphraseEntry = 19;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,31 +111,36 @@ message PinMatrixAck {
|
|||||||
* @next PassphraseAck
|
* @next PassphraseAck
|
||||||
*/
|
*/
|
||||||
message PassphraseRequest {
|
message PassphraseRequest {
|
||||||
optional bool on_device = 1; // passphrase is being entered on the device
|
optional bool _on_device = 1 [deprecated=true]; // <2.3.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Send passphrase back
|
* Request: Send passphrase back
|
||||||
* @next PassphraseStateRequest
|
* @auxend
|
||||||
*/
|
*/
|
||||||
message PassphraseAck {
|
message PassphraseAck {
|
||||||
optional string passphrase = 1;
|
optional string passphrase = 1;
|
||||||
optional bytes state = 2; // expected device state
|
optional bytes _state = 2 [deprecated=true]; // <2.3.0
|
||||||
|
optional bool on_device = 3; // user wants to enter passphrase on the device
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response: Device awaits passphrase state
|
* Response: Device awaits passphrase state
|
||||||
* @next PassphraseStateAck
|
* Deprecated in 2.3.0
|
||||||
|
* @next Deprecated_PassphraseStateAck
|
||||||
*/
|
*/
|
||||||
message PassphraseStateRequest {
|
message Deprecated_PassphraseStateRequest {
|
||||||
|
option deprecated = true;
|
||||||
optional bytes state = 1; // actual device state
|
optional bytes state = 1; // actual device state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Send passphrase state back
|
* Request: Send passphrase state back
|
||||||
|
* Deprecated in 2.3.0
|
||||||
* @auxend
|
* @auxend
|
||||||
*/
|
*/
|
||||||
message PassphraseStateAck {
|
message Deprecated_PassphraseStateAck {
|
||||||
|
option deprecated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,8 +20,7 @@ enum BackupType {
|
|||||||
* @next Features
|
* @next Features
|
||||||
*/
|
*/
|
||||||
message Initialize {
|
message Initialize {
|
||||||
optional bytes state = 1; // assumed device state, clear session if set and different
|
optional bytes session_id = 1; // assumed device session id; Trezor clears caches if it is different or empty
|
||||||
optional bool skip_passphrase = 2; // this session should always assume empty passphrase
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +51,7 @@ message Features {
|
|||||||
optional bytes bootloader_hash = 14; // hash of the bootloader
|
optional bytes bootloader_hash = 14; // hash of the bootloader
|
||||||
optional bool imported = 15; // was storage imported from an external source?
|
optional bool imported = 15; // was storage imported from an external source?
|
||||||
optional bool pin_cached = 16; // is PIN already cached in session?
|
optional bool pin_cached = 16; // is PIN already cached in session?
|
||||||
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
// optional bool passphrase_cached = 17; // is passphrase already cached in session? DEPRECATED
|
||||||
optional bool firmware_present = 18; // is valid firmware loaded?
|
optional bool firmware_present = 18; // is valid firmware loaded?
|
||||||
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
||||||
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
||||||
@ -83,11 +82,14 @@ message Features {
|
|||||||
Capability_U2F = 14;
|
Capability_U2F = 14;
|
||||||
Capability_Shamir = 15;
|
Capability_Shamir = 15;
|
||||||
Capability_ShamirGroups = 16;
|
Capability_ShamirGroups = 16;
|
||||||
|
Capability_PassphraseEntry = 17; // the device is capable of passphrase entry directly on the device
|
||||||
}
|
}
|
||||||
optional BackupType backup_type = 31; // type of device backup (BIP-39 / SLIP-39 basic / SLIP-39 advanced)
|
optional BackupType backup_type = 31; // type of device backup (BIP-39 / SLIP-39 basic / SLIP-39 advanced)
|
||||||
optional bool sd_card_present = 32; // is SD card present
|
optional bool sd_card_present = 32; // is SD card present
|
||||||
optional bool sd_protection = 33; // is SD Protect enabled
|
optional bool sd_protection = 33; // is SD Protect enabled
|
||||||
optional bool wipe_code_protection = 34; // is wipe code protection enabled
|
optional bool wipe_code_protection = 34; // is wipe code protection enabled
|
||||||
|
optional bytes session_id = 35;
|
||||||
|
optional bool passphrase_always_on_device = 36; // device enforces passphrase entry on Trezor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,17 +111,10 @@ message ApplySettings {
|
|||||||
optional string label = 2;
|
optional string label = 2;
|
||||||
optional bool use_passphrase = 3;
|
optional bool use_passphrase = 3;
|
||||||
optional bytes homescreen = 4;
|
optional bytes homescreen = 4;
|
||||||
optional PassphraseSourceType passphrase_source = 5;
|
// optional PassphraseSourceType passphrase_source = 5; DEPRECATED
|
||||||
optional uint32 auto_lock_delay_ms = 6;
|
optional uint32 auto_lock_delay_ms = 6;
|
||||||
optional uint32 display_rotation = 7; // in degrees from North
|
optional uint32 display_rotation = 7; // in degrees from North
|
||||||
/**
|
optional bool passphrase_always_on_device = 8; // do not prompt for passphrase, enforce device entry
|
||||||
* Structure representing passphrase source
|
|
||||||
*/
|
|
||||||
enum PassphraseSourceType {
|
|
||||||
ASK = 0;
|
|
||||||
DEVICE = 1;
|
|
||||||
HOST = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,8 +173,6 @@ message SdProtect {
|
|||||||
message Ping {
|
message Ping {
|
||||||
optional string message = 1; // message to send back in Success message
|
optional string message = 1; // message to send back in Success message
|
||||||
optional bool button_protection = 2; // ask for button press
|
optional bool button_protection = 2; // ask for button press
|
||||||
optional bool pin_protection = 3; // ask for PIN if set in device
|
|
||||||
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,8 +54,6 @@ enum MessageType {
|
|||||||
MessageType_EntropyAck = 36 [(wire_in) = true];
|
MessageType_EntropyAck = 36 [(wire_in) = true];
|
||||||
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
||||||
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
||||||
MessageType_PassphraseStateRequest = 77 [(wire_out) = true];
|
|
||||||
MessageType_PassphraseStateAck = 78 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
|
|
||||||
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
||||||
MessageType_WordRequest = 46 [(wire_out) = true];
|
MessageType_WordRequest = 46 [(wire_out) = true];
|
||||||
MessageType_WordAck = 47 [(wire_in) = true];
|
MessageType_WordAck = 47 [(wire_in) = true];
|
||||||
@ -66,6 +64,11 @@ enum MessageType {
|
|||||||
MessageType_NextU2FCounter = 81 [(wire_out) = true];
|
MessageType_NextU2FCounter = 81 [(wire_out) = true];
|
||||||
MessageType_ChangeWipeCode = 82 [(wire_in) = true];
|
MessageType_ChangeWipeCode = 82 [(wire_in) = true];
|
||||||
|
|
||||||
|
// Deprecated messages, kept for protobuf compatibility.
|
||||||
|
// Both are marked wire_out so that we don't need to implement incoming handler for legacy
|
||||||
|
MessageType_Deprecated_PassphraseStateRequest = 77 [deprecated = true];
|
||||||
|
MessageType_Deprecated_PassphraseStateAck = 78 [deprecated = true];
|
||||||
|
|
||||||
// Bootloader
|
// Bootloader
|
||||||
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
@ -2,6 +2,9 @@ Version 2.x.x [not yet released]
|
|||||||
* SD card protection
|
* SD card protection
|
||||||
* Upgrade MicroPython to 1.12
|
* Upgrade MicroPython to 1.12
|
||||||
|
|
||||||
|
Version 2.3.0 [not yet released]
|
||||||
|
* Passphrase redesign
|
||||||
|
|
||||||
Version 2.2.0 [Jan 2020]
|
Version 2.2.0 [Jan 2020]
|
||||||
* Remove unused ButtonRequest.data field
|
* Remove unused ButtonRequest.data field
|
||||||
* Rework Recovery persistence internally
|
* Rework Recovery persistence internally
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#define VERSION_MAJOR 2
|
#define VERSION_MAJOR 2
|
||||||
#define VERSION_MINOR 2
|
#define VERSION_MINOR 3
|
||||||
#define VERSION_PATCH 1
|
#define VERSION_PATCH 0
|
||||||
#define VERSION_BUILD 0
|
#define VERSION_BUILD 0
|
||||||
|
|
||||||
#define FIX_VERSION_MAJOR 2
|
#define FIX_VERSION_MAJOR 2
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import storage
|
import storage
|
||||||
import storage.cache
|
from storage import cache
|
||||||
from trezor import wire
|
from trezor import wire
|
||||||
from trezor.crypto import bip32
|
from trezor.crypto import bip32
|
||||||
|
|
||||||
from apps.cardano import CURVE, SEED_NAMESPACE
|
from apps.cardano import CURVE, SEED_NAMESPACE
|
||||||
from apps.common import mnemonic
|
from apps.common import mnemonic
|
||||||
from apps.common.request_passphrase import protect_by_passphrase
|
from apps.common.passphrase import get as get_passphrase
|
||||||
|
|
||||||
|
|
||||||
class Keychain:
|
class Keychain:
|
||||||
@ -30,34 +30,27 @@ class Keychain:
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
async def _get_passphrase(ctx: wire.Context) -> bytes:
|
|
||||||
passphrase = storage.cache.get_passphrase()
|
|
||||||
if passphrase is None:
|
|
||||||
passphrase = await protect_by_passphrase(ctx)
|
|
||||||
storage.cache.set_passphrase(passphrase)
|
|
||||||
|
|
||||||
return passphrase
|
|
||||||
|
|
||||||
|
|
||||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||||
|
root = cache.get(cache.APP_CARDANO_ROOT)
|
||||||
|
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise wire.NotInitialized("Device is not initialized")
|
raise wire.NotInitialized("Device is not initialized")
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
passphrase = await get_passphrase(ctx)
|
||||||
if mnemonic.is_bip39():
|
if mnemonic.is_bip39():
|
||||||
# derive the root node from mnemonic and passphrase
|
# derive the root node from mnemonic and passphrase
|
||||||
passphrase = await _get_passphrase(ctx)
|
root = bip32.from_mnemonic_cardano(
|
||||||
root = bip32.from_mnemonic_cardano(mnemonic.get_secret().decode(), passphrase)
|
mnemonic.get_secret().decode(), passphrase
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
seed = storage.cache.get_seed()
|
|
||||||
if seed is None:
|
|
||||||
passphrase = await _get_passphrase(ctx)
|
|
||||||
seed = mnemonic.get_seed(passphrase)
|
seed = mnemonic.get_seed(passphrase)
|
||||||
storage.cache.set_seed(seed)
|
|
||||||
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
||||||
|
|
||||||
# derive the namespaced root node
|
# derive the namespaced root node
|
||||||
for i in SEED_NAMESPACE:
|
for i in SEED_NAMESPACE:
|
||||||
root.derive_cardano(i)
|
root.derive_cardano(i)
|
||||||
|
storage.cache.set(cache.APP_CARDANO_ROOT, root)
|
||||||
|
|
||||||
keychain = Keychain(SEED_NAMESPACE, root)
|
keychain = Keychain(SEED_NAMESPACE, root)
|
||||||
return keychain
|
return keychain
|
||||||
|
@ -2,6 +2,7 @@ import storage.device
|
|||||||
from trezor import ui, utils, workflow
|
from trezor import ui, utils, workflow
|
||||||
from trezor.crypto import bip39, slip39
|
from trezor.crypto import bip39, slip39
|
||||||
from trezor.messages import BackupType
|
from trezor.messages import BackupType
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
@ -59,11 +60,8 @@ def _start_progress() -> None:
|
|||||||
# should make sure that no other layout is running. At this point, only
|
# should make sure that no other layout is running. At this point, only
|
||||||
# the homescreen should be on, so shut it down.
|
# the homescreen should be on, so shut it down.
|
||||||
workflow.kill_default()
|
workflow.kill_default()
|
||||||
ui.backlight_fade(ui.BACKLIGHT_DIM)
|
t = Text("Please wait", ui.ICON_CONFIG)
|
||||||
ui.display.clear()
|
ui.draw_simple(t)
|
||||||
ui.header("Please wait")
|
|
||||||
ui.refresh()
|
|
||||||
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
|
||||||
|
|
||||||
|
|
||||||
def _render_progress(progress: int, total: int) -> None:
|
def _render_progress(progress: int, total: int) -> None:
|
||||||
|
78
core/src/apps/common/passphrase.py
Normal file
78
core/src/apps/common/passphrase.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
|
import storage.device
|
||||||
|
from trezor import wire
|
||||||
|
from trezor.messages import ButtonRequestType
|
||||||
|
from trezor.messages.ButtonAck import ButtonAck
|
||||||
|
from trezor.messages.ButtonRequest import ButtonRequest
|
||||||
|
from trezor.messages.PassphraseAck import PassphraseAck
|
||||||
|
from trezor.messages.PassphraseRequest import PassphraseRequest
|
||||||
|
from trezor.ui import ICON_CONFIG, draw_simple
|
||||||
|
from trezor.ui.passphrase import CANCELLED, PassphraseKeyboard
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
from apps.debug import input_signal
|
||||||
|
|
||||||
|
_MAX_PASSPHRASE_LEN = const(50)
|
||||||
|
|
||||||
|
|
||||||
|
def is_enabled() -> bool:
|
||||||
|
return storage.device.is_passphrase_enabled()
|
||||||
|
|
||||||
|
|
||||||
|
async def get(ctx: wire.Context) -> str:
|
||||||
|
if is_enabled():
|
||||||
|
return await _request_from_user(ctx)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def _request_from_user(ctx: wire.Context) -> str:
|
||||||
|
if storage.device.get_passphrase_always_on_device():
|
||||||
|
passphrase = await _request_on_device(ctx)
|
||||||
|
else:
|
||||||
|
passphrase = await _request_on_host(ctx)
|
||||||
|
if len(passphrase) > _MAX_PASSPHRASE_LEN:
|
||||||
|
raise wire.DataError("Maximum passphrase length is %d" % _MAX_PASSPHRASE_LEN)
|
||||||
|
|
||||||
|
return passphrase
|
||||||
|
|
||||||
|
|
||||||
|
async def _request_on_host(ctx: wire.Context) -> str:
|
||||||
|
_entry_dialog()
|
||||||
|
|
||||||
|
request = PassphraseRequest()
|
||||||
|
ack = await ctx.call(request, PassphraseAck)
|
||||||
|
if ack.on_device:
|
||||||
|
if ack.passphrase is not None:
|
||||||
|
raise wire.DataError("Passphrase provided when it should not be")
|
||||||
|
return await _request_on_device(ctx)
|
||||||
|
|
||||||
|
if ack.passphrase is None:
|
||||||
|
raise wire.DataError(
|
||||||
|
"Passphrase not provided and on_device is False. Use empty string to set an empty passphrase."
|
||||||
|
)
|
||||||
|
return ack.passphrase
|
||||||
|
|
||||||
|
|
||||||
|
async def _request_on_device(ctx: wire.Context) -> str:
|
||||||
|
await ctx.call(ButtonRequest(code=ButtonRequestType.PassphraseEntry), ButtonAck)
|
||||||
|
|
||||||
|
keyboard = PassphraseKeyboard("Enter passphrase", _MAX_PASSPHRASE_LEN)
|
||||||
|
if __debug__:
|
||||||
|
passphrase = await ctx.wait(keyboard, input_signal())
|
||||||
|
else:
|
||||||
|
passphrase = await ctx.wait(keyboard)
|
||||||
|
if passphrase is CANCELLED:
|
||||||
|
raise wire.ActionCancelled("Passphrase entry cancelled")
|
||||||
|
|
||||||
|
assert isinstance(passphrase, str)
|
||||||
|
|
||||||
|
return passphrase
|
||||||
|
|
||||||
|
|
||||||
|
def _entry_dialog() -> None:
|
||||||
|
text = Text("Passphrase entry", ICON_CONFIG)
|
||||||
|
text.normal("Please type your", "passphrase on the", "connected host.")
|
||||||
|
draw_simple(text)
|
@ -1,86 +0,0 @@
|
|||||||
from micropython import const
|
|
||||||
|
|
||||||
import storage.device
|
|
||||||
from storage import cache
|
|
||||||
from trezor import ui, wire
|
|
||||||
from trezor.messages import ButtonRequestType, PassphraseSourceType
|
|
||||||
from trezor.messages.ButtonAck import ButtonAck
|
|
||||||
from trezor.messages.ButtonRequest import ButtonRequest
|
|
||||||
from trezor.messages.PassphraseAck import PassphraseAck
|
|
||||||
from trezor.messages.PassphraseRequest import PassphraseRequest
|
|
||||||
from trezor.messages.PassphraseStateAck import PassphraseStateAck
|
|
||||||
from trezor.messages.PassphraseStateRequest import PassphraseStateRequest
|
|
||||||
from trezor.ui.passphrase import CANCELLED, PassphraseKeyboard, PassphraseSource
|
|
||||||
from trezor.ui.popup import Popup
|
|
||||||
from trezor.ui.text import Text
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
from apps.debug import input_signal
|
|
||||||
|
|
||||||
_MAX_PASSPHRASE_LEN = const(50)
|
|
||||||
|
|
||||||
|
|
||||||
async def protect_by_passphrase(ctx: wire.Context) -> str:
|
|
||||||
if storage.device.has_passphrase():
|
|
||||||
return await request_passphrase(ctx)
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
async def request_passphrase(ctx: wire.Context) -> str:
|
|
||||||
source = storage.device.get_passphrase_source()
|
|
||||||
if source == PassphraseSourceType.ASK:
|
|
||||||
source = await request_passphrase_source(ctx)
|
|
||||||
passphrase = await request_passphrase_ack(
|
|
||||||
ctx, source == PassphraseSourceType.DEVICE
|
|
||||||
)
|
|
||||||
if len(passphrase) > _MAX_PASSPHRASE_LEN:
|
|
||||||
raise wire.DataError("Maximum passphrase length is %d" % _MAX_PASSPHRASE_LEN)
|
|
||||||
return passphrase
|
|
||||||
|
|
||||||
|
|
||||||
async def request_passphrase_source(ctx: wire.Context) -> int:
|
|
||||||
req = ButtonRequest(code=ButtonRequestType.PassphraseType)
|
|
||||||
await ctx.call(req, ButtonAck)
|
|
||||||
|
|
||||||
text = Text("Enter passphrase", ui.ICON_CONFIG)
|
|
||||||
text.normal("Where do you want to", "enter your passphrase?")
|
|
||||||
source = PassphraseSource(text)
|
|
||||||
|
|
||||||
response = await ctx.wait(source)
|
|
||||||
assert isinstance(response, int)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
async def request_passphrase_ack(ctx: wire.Context, on_device: bool) -> str:
|
|
||||||
if not on_device:
|
|
||||||
text = Text("Passphrase entry", ui.ICON_CONFIG)
|
|
||||||
text.normal("Please type your", "passphrase on the", "connected host.")
|
|
||||||
await Popup(text)
|
|
||||||
|
|
||||||
passphrase_request = PassphraseRequest(on_device=on_device)
|
|
||||||
ack = await ctx.call(passphrase_request, PassphraseAck)
|
|
||||||
|
|
||||||
if on_device:
|
|
||||||
if ack.passphrase is not None:
|
|
||||||
raise wire.ProcessError("Passphrase provided when it should not be")
|
|
||||||
|
|
||||||
keyboard = PassphraseKeyboard("Enter passphrase", _MAX_PASSPHRASE_LEN)
|
|
||||||
if __debug__:
|
|
||||||
passphrase = await ctx.wait(keyboard, input_signal())
|
|
||||||
else:
|
|
||||||
passphrase = await ctx.wait(keyboard)
|
|
||||||
if passphrase is CANCELLED:
|
|
||||||
raise wire.ActionCancelled("Passphrase cancelled")
|
|
||||||
else:
|
|
||||||
if ack.passphrase is None:
|
|
||||||
raise wire.ProcessError("Passphrase not provided")
|
|
||||||
passphrase = ack.passphrase
|
|
||||||
|
|
||||||
assert isinstance(passphrase, str)
|
|
||||||
|
|
||||||
state = cache.get_state(prev_state=ack.state, passphrase=passphrase)
|
|
||||||
state_request = PassphraseStateRequest(state=state)
|
|
||||||
await ctx.call(state_request, PassphraseStateAck)
|
|
||||||
|
|
||||||
return passphrase
|
|
@ -1,11 +1,11 @@
|
|||||||
import storage
|
import storage
|
||||||
import storage.cache
|
from storage import cache
|
||||||
from trezor import wire
|
from trezor import wire
|
||||||
from trezor.crypto import bip32, hashlib, hmac
|
from trezor.crypto import bip32, hashlib, hmac
|
||||||
from trezor.crypto.curve import secp256k1
|
from trezor.crypto.curve import secp256k1
|
||||||
|
|
||||||
from apps.common import HARDENED, mnemonic
|
from apps.common import HARDENED, mnemonic
|
||||||
from apps.common.request_passphrase import protect_by_passphrase
|
from apps.common.passphrase import get as get_passphrase
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
@ -112,14 +112,11 @@ class Keychain:
|
|||||||
async def get_keychain(ctx: wire.Context, namespaces: list) -> Keychain:
|
async def get_keychain(ctx: wire.Context, namespaces: list) -> Keychain:
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise wire.NotInitialized("Device is not initialized")
|
raise wire.NotInitialized("Device is not initialized")
|
||||||
seed = storage.cache.get_seed()
|
seed = cache.get(cache.APP_COMMON_SEED)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
passphrase = storage.cache.get_passphrase()
|
passphrase = await get_passphrase(ctx)
|
||||||
if passphrase is None:
|
|
||||||
passphrase = await protect_by_passphrase(ctx)
|
|
||||||
storage.cache.set_passphrase(passphrase)
|
|
||||||
seed = mnemonic.get_seed(passphrase)
|
seed = mnemonic.get_seed(passphrase)
|
||||||
storage.cache.set_seed(seed)
|
cache.set(cache.APP_COMMON_SEED, seed)
|
||||||
keychain = Keychain(seed, namespaces)
|
keychain = Keychain(seed, namespaces)
|
||||||
return keychain
|
return keychain
|
||||||
|
|
||||||
@ -129,10 +126,10 @@ def derive_node_without_passphrase(
|
|||||||
) -> bip32.HDNode:
|
) -> bip32.HDNode:
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise Exception("Device is not initialized")
|
raise Exception("Device is not initialized")
|
||||||
seed = storage.cache.get_seed_without_passphrase()
|
seed = cache.get(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
seed = mnemonic.get_seed(progress_bar=False)
|
seed = mnemonic.get_seed(progress_bar=False)
|
||||||
storage.cache.set_seed_without_passphrase(seed)
|
cache.set(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE, seed)
|
||||||
node = bip32.from_seed(seed, curve_name)
|
node = bip32.from_seed(seed, curve_name)
|
||||||
node.derive_path(path)
|
node.derive_path(path)
|
||||||
return node
|
return node
|
||||||
@ -141,10 +138,10 @@ def derive_node_without_passphrase(
|
|||||||
def derive_slip21_node_without_passphrase(path: list) -> Slip21Node:
|
def derive_slip21_node_without_passphrase(path: list) -> Slip21Node:
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise Exception("Device is not initialized")
|
raise Exception("Device is not initialized")
|
||||||
seed = storage.cache.get_seed_without_passphrase()
|
seed = cache.get(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
seed = mnemonic.get_seed(progress_bar=False)
|
seed = mnemonic.get_seed(progress_bar=False)
|
||||||
storage.cache.set_seed_without_passphrase(seed)
|
cache.set(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE, seed)
|
||||||
node = Slip21Node(seed)
|
node = Slip21Node(seed)
|
||||||
node.derive_path(path)
|
node.derive_path(path)
|
||||||
return node
|
return node
|
||||||
|
@ -97,13 +97,12 @@ if __debug__:
|
|||||||
ctx: wire.Context, msg: DebugLinkGetState
|
ctx: wire.Context, msg: DebugLinkGetState
|
||||||
) -> DebugLinkState:
|
) -> DebugLinkState:
|
||||||
from trezor.messages.DebugLinkState import DebugLinkState
|
from trezor.messages.DebugLinkState import DebugLinkState
|
||||||
from storage.device import has_passphrase
|
from apps.common import mnemonic, passphrase
|
||||||
from apps.common import mnemonic
|
|
||||||
|
|
||||||
m = DebugLinkState()
|
m = DebugLinkState()
|
||||||
m.mnemonic_secret = mnemonic.get_secret()
|
m.mnemonic_secret = mnemonic.get_secret()
|
||||||
m.mnemonic_type = mnemonic.get_type()
|
m.mnemonic_type = mnemonic.get_type()
|
||||||
m.passphrase_protection = has_passphrase()
|
m.passphrase_protection = passphrase.is_enabled()
|
||||||
m.reset_entropy = reset_internal_entropy
|
m.reset_entropy = reset_internal_entropy
|
||||||
|
|
||||||
if msg.wait_layout or current_content is None:
|
if msg.wait_layout or current_content is None:
|
||||||
|
@ -34,8 +34,7 @@ def get_features() -> Features:
|
|||||||
f.initialized = storage.is_initialized()
|
f.initialized = storage.is_initialized()
|
||||||
f.pin_protection = config.has_pin()
|
f.pin_protection = config.has_pin()
|
||||||
f.pin_cached = config.has_pin()
|
f.pin_cached = config.has_pin()
|
||||||
f.passphrase_protection = storage.device.has_passphrase()
|
f.passphrase_protection = storage.device.is_passphrase_enabled()
|
||||||
f.passphrase_cached = cache.has_passphrase()
|
|
||||||
f.needs_backup = storage.device.needs_backup()
|
f.needs_backup = storage.device.needs_backup()
|
||||||
f.unfinished_backup = storage.device.unfinished_backup()
|
f.unfinished_backup = storage.device.unfinished_backup()
|
||||||
f.no_backup = storage.device.no_backup()
|
f.no_backup = storage.device.no_backup()
|
||||||
@ -48,6 +47,7 @@ def get_features() -> Features:
|
|||||||
Capability.Crypto,
|
Capability.Crypto,
|
||||||
Capability.Shamir,
|
Capability.Shamir,
|
||||||
Capability.ShamirGroups,
|
Capability.ShamirGroups,
|
||||||
|
Capability.PassphraseEntry,
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
f.capabilities = [
|
f.capabilities = [
|
||||||
@ -67,18 +67,19 @@ def get_features() -> Features:
|
|||||||
Capability.U2F,
|
Capability.U2F,
|
||||||
Capability.Shamir,
|
Capability.Shamir,
|
||||||
Capability.ShamirGroups,
|
Capability.ShamirGroups,
|
||||||
|
Capability.PassphraseEntry,
|
||||||
]
|
]
|
||||||
f.sd_card_present = io.SDCard().present()
|
f.sd_card_present = io.SDCard().present()
|
||||||
f.sd_protection = storage.sd_salt.is_enabled()
|
f.sd_protection = storage.sd_salt.is_enabled()
|
||||||
f.wipe_code_protection = config.has_wipe_code()
|
f.wipe_code_protection = config.has_wipe_code()
|
||||||
|
f.session_id = cache.get_session_id()
|
||||||
|
f.passphrase_always_on_device = storage.device.get_passphrase_always_on_device()
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
async def handle_Initialize(ctx: wire.Context, msg: Initialize) -> Features:
|
async def handle_Initialize(ctx: wire.Context, msg: Initialize) -> Features:
|
||||||
if msg.state is None or msg.state != cache.get_state(prev_state=bytes(msg.state)):
|
if msg.session_id is None or msg.session_id != cache.get_session_id():
|
||||||
cache.clear()
|
cache.clear()
|
||||||
if msg.skip_passphrase:
|
|
||||||
cache.set_passphrase("")
|
|
||||||
return get_features()
|
return get_features()
|
||||||
|
|
||||||
|
|
||||||
@ -91,8 +92,12 @@ async def handle_Cancel(ctx: wire.Context, msg: Cancel) -> NoReturn:
|
|||||||
|
|
||||||
|
|
||||||
async def handle_ClearSession(ctx: wire.Context, msg: ClearSession) -> Success:
|
async def handle_ClearSession(ctx: wire.Context, msg: ClearSession) -> Success:
|
||||||
cache.clear(keep_passphrase=True)
|
"""
|
||||||
return Success(message="Session cleared")
|
This is currently a no-op on T. This should be called LockSession/LockDevice
|
||||||
|
and lock the device. In other words the cache should stay but the PIN should
|
||||||
|
be forgotten and required again.
|
||||||
|
"""
|
||||||
|
return Success()
|
||||||
|
|
||||||
|
|
||||||
async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success:
|
async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success:
|
||||||
@ -102,10 +107,6 @@ async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success:
|
|||||||
from trezor.ui.text import Text
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
await require_confirm(ctx, Text("Confirm"), ProtectCall)
|
await require_confirm(ctx, Text("Confirm"), ProtectCall)
|
||||||
if msg.passphrase_protection:
|
|
||||||
from apps.common.request_passphrase import protect_by_passphrase
|
|
||||||
|
|
||||||
await protect_by_passphrase(ctx)
|
|
||||||
return Success(message=msg.message)
|
return Success(message=msg.message)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import storage.device
|
import storage.device
|
||||||
from trezor import ui, wire
|
from trezor import ui, wire
|
||||||
from trezor.messages import ButtonRequestType, PassphraseSourceType
|
from trezor.messages import ButtonRequestType
|
||||||
from trezor.messages.Success import Success
|
from trezor.messages.Success import Success
|
||||||
from trezor.ui.text import Text
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ async def apply_settings(ctx, msg):
|
|||||||
msg.homescreen is None
|
msg.homescreen is None
|
||||||
and msg.label is None
|
and msg.label is None
|
||||||
and msg.use_passphrase is None
|
and msg.use_passphrase is None
|
||||||
and msg.passphrase_source is None
|
and msg.passphrase_always_on_device is None
|
||||||
and msg.display_rotation is None
|
and msg.display_rotation is None
|
||||||
):
|
):
|
||||||
raise wire.ProcessError("No setting provided")
|
raise wire.ProcessError("No setting provided")
|
||||||
@ -28,8 +28,10 @@ async def apply_settings(ctx, msg):
|
|||||||
if msg.use_passphrase is not None:
|
if msg.use_passphrase is not None:
|
||||||
await require_confirm_change_passphrase(ctx, msg.use_passphrase)
|
await require_confirm_change_passphrase(ctx, msg.use_passphrase)
|
||||||
|
|
||||||
if msg.passphrase_source is not None:
|
if msg.passphrase_always_on_device is not None:
|
||||||
await require_confirm_change_passphrase_source(ctx, msg.passphrase_source)
|
await require_confirm_change_passphrase_source(
|
||||||
|
ctx, msg.passphrase_always_on_device
|
||||||
|
)
|
||||||
|
|
||||||
if msg.display_rotation is not None:
|
if msg.display_rotation is not None:
|
||||||
await require_confirm_change_display_rotation(ctx, msg.display_rotation)
|
await require_confirm_change_display_rotation(ctx, msg.display_rotation)
|
||||||
@ -38,7 +40,7 @@ async def apply_settings(ctx, msg):
|
|||||||
label=msg.label,
|
label=msg.label,
|
||||||
use_passphrase=msg.use_passphrase,
|
use_passphrase=msg.use_passphrase,
|
||||||
homescreen=msg.homescreen,
|
homescreen=msg.homescreen,
|
||||||
passphrase_source=msg.passphrase_source,
|
passphrase_always_on_device=msg.passphrase_always_on_device,
|
||||||
display_rotation=msg.display_rotation,
|
display_rotation=msg.display_rotation,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,16 +71,16 @@ async def require_confirm_change_passphrase(ctx, use):
|
|||||||
await require_confirm(ctx, text, ButtonRequestType.ProtectCall)
|
await require_confirm(ctx, text, ButtonRequestType.ProtectCall)
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_change_passphrase_source(ctx, source):
|
async def require_confirm_change_passphrase_source(
|
||||||
if source == PassphraseSourceType.DEVICE:
|
ctx, passphrase_always_on_device: bool
|
||||||
desc = "ON DEVICE"
|
):
|
||||||
elif source == PassphraseSourceType.HOST:
|
|
||||||
desc = "ON HOST"
|
|
||||||
else:
|
|
||||||
desc = "ASK"
|
|
||||||
text = Text("Passphrase source", ui.ICON_CONFIG)
|
text = Text("Passphrase source", ui.ICON_CONFIG)
|
||||||
text.normal("Do you really want to", "change the passphrase", "source to")
|
if passphrase_always_on_device:
|
||||||
text.bold("ALWAYS %s?" % desc)
|
text.normal(
|
||||||
|
"Do you really want to", "enter passphrase always", "on the device?"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text.normal("Do you want to revoke", "the passphrase on device", "setting?")
|
||||||
await require_confirm(ctx, text, ButtonRequestType.ProtectCall)
|
await require_confirm(ctx, text, ButtonRequestType.ProtectCall)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ async def recovery_homescreen() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# recovery process does not communicate on the wire
|
# recovery process does not communicate on the wire
|
||||||
ctx = wire.DummyContext()
|
ctx = wire.DUMMY_CONTEXT
|
||||||
await recovery_process(ctx)
|
await recovery_process(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ from trezor.messages import MessageType
|
|||||||
from apps.common import HARDENED
|
from apps.common import HARDENED
|
||||||
|
|
||||||
CURVE = "ed25519"
|
CURVE = "ed25519"
|
||||||
_LIVE_REFRESH_TOKEN = None # live-refresh permission token
|
|
||||||
|
|
||||||
|
|
||||||
def boot() -> None:
|
def boot() -> None:
|
||||||
@ -20,11 +19,3 @@ def boot() -> None:
|
|||||||
|
|
||||||
if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"):
|
if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"):
|
||||||
wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag")
|
wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag")
|
||||||
|
|
||||||
|
|
||||||
def live_refresh_token(token: bytes = None) -> None:
|
|
||||||
global _LIVE_REFRESH_TOKEN
|
|
||||||
if token is None:
|
|
||||||
return _LIVE_REFRESH_TOKEN
|
|
||||||
else:
|
|
||||||
_LIVE_REFRESH_TOKEN = token
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import gc
|
import gc
|
||||||
|
|
||||||
from storage.cache import get_passphrase_fprint
|
import storage.cache
|
||||||
from trezor import log
|
from trezor import log
|
||||||
from trezor.messages import MessageType
|
from trezor.messages import MessageType
|
||||||
from trezor.messages.MoneroLiveRefreshFinalAck import MoneroLiveRefreshFinalAck
|
from trezor.messages.MoneroLiveRefreshFinalAck import MoneroLiveRefreshFinalAck
|
||||||
@ -10,7 +10,7 @@ from trezor.messages.MoneroLiveRefreshStepAck import MoneroLiveRefreshStepAck
|
|||||||
from trezor.messages.MoneroLiveRefreshStepRequest import MoneroLiveRefreshStepRequest
|
from trezor.messages.MoneroLiveRefreshStepRequest import MoneroLiveRefreshStepRequest
|
||||||
|
|
||||||
from apps.common import paths
|
from apps.common import paths
|
||||||
from apps.monero import CURVE, live_refresh_token, misc
|
from apps.monero import CURVE, misc
|
||||||
from apps.monero.layout import confirms
|
from apps.monero.layout import confirms
|
||||||
from apps.monero.xmr import crypto, key_image, monero
|
from apps.monero.xmr import crypto, key_image, monero
|
||||||
from apps.monero.xmr.crypto import chacha_poly
|
from apps.monero.xmr.crypto import chacha_poly
|
||||||
@ -49,10 +49,9 @@ async def _init_step(
|
|||||||
ctx, misc.validate_full_path, keychain, msg.address_n, CURVE
|
ctx, misc.validate_full_path, keychain, msg.address_n, CURVE
|
||||||
)
|
)
|
||||||
|
|
||||||
passphrase_fprint = get_passphrase_fprint()
|
if not storage.cache.get(storage.cache.APP_MONERO_LIVE_REFRESH):
|
||||||
if live_refresh_token() != passphrase_fprint:
|
|
||||||
await confirms.require_confirm_live_refresh(ctx)
|
await confirms.require_confirm_live_refresh(ctx)
|
||||||
live_refresh_token(passphrase_fprint)
|
storage.cache.set(storage.cache.APP_MONERO_LIVE_REFRESH, True)
|
||||||
|
|
||||||
s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
||||||
|
|
||||||
|
@ -1,75 +1,36 @@
|
|||||||
from storage.device import get_device_id
|
from trezor.crypto import random
|
||||||
from trezor.crypto import hashlib, hmac, random
|
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
_cached_seed = None # type: Optional[bytes]
|
APP_COMMON_SEED = 0
|
||||||
_cached_seed_without_passphrase = None # type: Optional[bytes]
|
APP_COMMON_SEED_WITHOUT_PASSPHRASE = 1
|
||||||
_cached_passphrase = None # type: Optional[str]
|
APP_CARDANO_ROOT = 2
|
||||||
_cached_passphrase_fprint = b"\x00\x00\x00\x00" # type: bytes
|
APP_MONERO_LIVE_REFRESH = 3
|
||||||
|
|
||||||
|
_cache_session_id = None # type: Optional[bytes]
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def get_state(prev_state: bytes = None, passphrase: str = None) -> Optional[bytes]:
|
def get_session_id() -> bytes:
|
||||||
if prev_state is None:
|
global _cache_session_id
|
||||||
salt = random.bytes(32) # generate a random salt if no state provided
|
if not _cache_session_id:
|
||||||
else:
|
_cache_session_id = random.bytes(32)
|
||||||
salt = prev_state[:32] # use salt from provided state
|
return _cache_session_id
|
||||||
if len(salt) != 32:
|
|
||||||
return None # invalid state
|
|
||||||
if passphrase is None:
|
|
||||||
if _cached_passphrase is None:
|
|
||||||
return None # we don't have any passphrase to compute the state
|
|
||||||
else:
|
|
||||||
passphrase = _cached_passphrase # use cached passphrase
|
|
||||||
return _compute_state(salt, passphrase)
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_state(salt: bytes, passphrase: str) -> bytes:
|
def set(key: int, value: Any) -> None:
|
||||||
# state = HMAC(passphrase, salt || device_id)
|
_cache[key] = value
|
||||||
message = salt + get_device_id().encode()
|
|
||||||
state = hmac.new(passphrase.encode(), message, hashlib.sha256).digest()
|
|
||||||
return salt + state
|
|
||||||
|
|
||||||
|
|
||||||
def get_seed() -> Optional[bytes]:
|
def get(key: int) -> Any:
|
||||||
return _cached_seed
|
return _cache.get(key)
|
||||||
|
|
||||||
|
|
||||||
def get_seed_without_passphrase() -> Optional[bytes]:
|
def clear() -> None:
|
||||||
return _cached_seed_without_passphrase
|
global _cache_session_id
|
||||||
|
_cache_session_id = None
|
||||||
|
_cache.clear()
|
||||||
def get_passphrase() -> Optional[str]:
|
|
||||||
return _cached_passphrase
|
|
||||||
|
|
||||||
|
|
||||||
def get_passphrase_fprint() -> bytes:
|
|
||||||
return _cached_passphrase_fprint
|
|
||||||
|
|
||||||
|
|
||||||
def has_passphrase() -> bool:
|
|
||||||
return _cached_passphrase is not None
|
|
||||||
|
|
||||||
|
|
||||||
def set_seed(seed: Optional[bytes]) -> None:
|
|
||||||
global _cached_seed
|
|
||||||
_cached_seed = seed
|
|
||||||
|
|
||||||
|
|
||||||
def set_seed_without_passphrase(seed: Optional[bytes]) -> None:
|
|
||||||
global _cached_seed_without_passphrase
|
|
||||||
_cached_seed_without_passphrase = seed
|
|
||||||
|
|
||||||
|
|
||||||
def set_passphrase(passphrase: Optional[str]) -> None:
|
|
||||||
global _cached_passphrase, _cached_passphrase_fprint
|
|
||||||
_cached_passphrase = passphrase
|
|
||||||
_cached_passphrase_fprint = _compute_state(b"FPRINT", passphrase or "")[:4]
|
|
||||||
|
|
||||||
|
|
||||||
def clear(keep_passphrase: bool = False) -> None:
|
|
||||||
set_seed(None)
|
|
||||||
set_seed_without_passphrase(None)
|
|
||||||
if not keep_passphrase:
|
|
||||||
set_passphrase(None)
|
|
||||||
|
@ -24,7 +24,7 @@ _HOMESCREEN = const(0x06) # bytes
|
|||||||
_NEEDS_BACKUP = const(0x07) # bool (0x01 or empty)
|
_NEEDS_BACKUP = const(0x07) # bool (0x01 or empty)
|
||||||
_FLAGS = const(0x08) # int
|
_FLAGS = const(0x08) # int
|
||||||
U2F_COUNTER = const(0x09) # int
|
U2F_COUNTER = const(0x09) # int
|
||||||
_PASSPHRASE_SOURCE = const(0x0A) # int
|
_PASSPHRASE_ALWAYS_ON_DEVICE = const(0x0A) # bool (0x01 or empty)
|
||||||
_UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
|
_UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
|
||||||
_AUTOLOCK_DELAY_MS = const(0x0C) # int
|
_AUTOLOCK_DELAY_MS = const(0x0C) # int
|
||||||
_NO_BACKUP = const(0x0D) # bool (0x01 or empty)
|
_NO_BACKUP = const(0x0D) # bool (0x01 or empty)
|
||||||
@ -101,7 +101,7 @@ def get_backup_type() -> EnumTypeBackupType:
|
|||||||
return backup_type # type: ignore
|
return backup_type # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def has_passphrase() -> bool:
|
def is_passphrase_enabled() -> bool:
|
||||||
return common.get_bool(_NAMESPACE, _USE_PASSPHRASE)
|
return common.get_bool(_NAMESPACE, _USE_PASSPHRASE)
|
||||||
|
|
||||||
|
|
||||||
@ -143,21 +143,21 @@ def no_backup() -> bool:
|
|||||||
return common.get_bool(_NAMESPACE, _NO_BACKUP)
|
return common.get_bool(_NAMESPACE, _NO_BACKUP)
|
||||||
|
|
||||||
|
|
||||||
def get_passphrase_source() -> int:
|
def get_passphrase_always_on_device() -> bool:
|
||||||
b = common.get(_NAMESPACE, _PASSPHRASE_SOURCE)
|
"""
|
||||||
if b == b"\x01":
|
This is backwards compatible with _PASSPHRASE_SOURCE:
|
||||||
return 1
|
- If ASK(0) => returns False, the check against b"\x01" in get_bool fails.
|
||||||
elif b == b"\x02":
|
- If DEVICE(1) => returns True, the check against b"\x01" in get_bool succeeds.
|
||||||
return 2
|
- If HOST(2) => returns False, the check against b"\x01" in get_bool fails.
|
||||||
else:
|
"""
|
||||||
return 0
|
return common.get_bool(_NAMESPACE, _PASSPHRASE_ALWAYS_ON_DEVICE)
|
||||||
|
|
||||||
|
|
||||||
def load_settings(
|
def load_settings(
|
||||||
label: str = None,
|
label: str = None,
|
||||||
use_passphrase: bool = None,
|
use_passphrase: bool = None,
|
||||||
homescreen: bytes = None,
|
homescreen: bytes = None,
|
||||||
passphrase_source: int = None,
|
passphrase_always_on_device: bool = None,
|
||||||
display_rotation: int = None,
|
display_rotation: int = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if label is not None:
|
if label is not None:
|
||||||
@ -170,9 +170,10 @@ def load_settings(
|
|||||||
common.set(_NAMESPACE, _HOMESCREEN, homescreen, True) # public
|
common.set(_NAMESPACE, _HOMESCREEN, homescreen, True) # public
|
||||||
else:
|
else:
|
||||||
common.set(_NAMESPACE, _HOMESCREEN, b"", True) # public
|
common.set(_NAMESPACE, _HOMESCREEN, b"", True) # public
|
||||||
if passphrase_source is not None:
|
if passphrase_always_on_device is not None:
|
||||||
if passphrase_source in (0, 1, 2):
|
common.set_bool(
|
||||||
common.set(_NAMESPACE, _PASSPHRASE_SOURCE, bytes([passphrase_source]))
|
_NAMESPACE, _PASSPHRASE_ALWAYS_ON_DEVICE, passphrase_always_on_device
|
||||||
|
)
|
||||||
if display_rotation is not None:
|
if display_rotation is not None:
|
||||||
if display_rotation not in (0, 90, 180, 270):
|
if display_rotation not in (0, 90, 180, 270):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -6,7 +6,6 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypePassphraseSourceType = Literal[0, 1, 2]
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -20,17 +19,17 @@ class ApplySettings(p.MessageType):
|
|||||||
label: str = None,
|
label: str = None,
|
||||||
use_passphrase: bool = None,
|
use_passphrase: bool = None,
|
||||||
homescreen: bytes = None,
|
homescreen: bytes = None,
|
||||||
passphrase_source: EnumTypePassphraseSourceType = None,
|
|
||||||
auto_lock_delay_ms: int = None,
|
auto_lock_delay_ms: int = None,
|
||||||
display_rotation: int = None,
|
display_rotation: int = None,
|
||||||
|
passphrase_always_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.language = language
|
self.language = language
|
||||||
self.label = label
|
self.label = label
|
||||||
self.use_passphrase = use_passphrase
|
self.use_passphrase = use_passphrase
|
||||||
self.homescreen = homescreen
|
self.homescreen = homescreen
|
||||||
self.passphrase_source = passphrase_source
|
|
||||||
self.auto_lock_delay_ms = auto_lock_delay_ms
|
self.auto_lock_delay_ms = auto_lock_delay_ms
|
||||||
self.display_rotation = display_rotation
|
self.display_rotation = display_rotation
|
||||||
|
self.passphrase_always_on_device = passphrase_always_on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -39,7 +38,7 @@ class ApplySettings(p.MessageType):
|
|||||||
2: ('label', p.UnicodeType, 0),
|
2: ('label', p.UnicodeType, 0),
|
||||||
3: ('use_passphrase', p.BoolType, 0),
|
3: ('use_passphrase', p.BoolType, 0),
|
||||||
4: ('homescreen', p.BytesType, 0),
|
4: ('homescreen', p.BytesType, 0),
|
||||||
5: ('passphrase_source', p.EnumType("PassphraseSourceType", (0, 1, 2)), 0),
|
|
||||||
6: ('auto_lock_delay_ms', p.UVarintType, 0),
|
6: ('auto_lock_delay_ms', p.UVarintType, 0),
|
||||||
7: ('display_rotation', p.UVarintType, 0),
|
7: ('display_rotation', p.UVarintType, 0),
|
||||||
|
8: ('passphrase_always_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypeButtonRequestType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
|
EnumTypeButtonRequestType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -23,5 +23,5 @@ class ButtonRequest(p.MessageType):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('code', p.EnumType("ButtonRequestType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)), 0),
|
1: ('code', p.EnumType("ButtonRequestType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19)), 0),
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ Address = 10 # type: Literal[10]
|
|||||||
PublicKey = 11 # type: Literal[11]
|
PublicKey = 11 # type: Literal[11]
|
||||||
MnemonicWordCount = 12 # type: Literal[12]
|
MnemonicWordCount = 12 # type: Literal[12]
|
||||||
MnemonicInput = 13 # type: Literal[13]
|
MnemonicInput = 13 # type: Literal[13]
|
||||||
PassphraseType = 14 # type: Literal[14]
|
|
||||||
UnknownDerivationPath = 15 # type: Literal[15]
|
UnknownDerivationPath = 15 # type: Literal[15]
|
||||||
RecoveryHomepage = 16 # type: Literal[16]
|
RecoveryHomepage = 16 # type: Literal[16]
|
||||||
Success = 17 # type: Literal[17]
|
Success = 17 # type: Literal[17]
|
||||||
Warning = 18 # type: Literal[18]
|
Warning = 18 # type: Literal[18]
|
||||||
|
PassphraseEntry = 19 # type: Literal[19]
|
||||||
|
@ -23,3 +23,4 @@ if not utils.BITCOIN_ONLY:
|
|||||||
U2F = 14 # type: Literal[14]
|
U2F = 14 # type: Literal[14]
|
||||||
Shamir = 15 # type: Literal[15]
|
Shamir = 15 # type: Literal[15]
|
||||||
ShamirGroups = 16 # type: Literal[16]
|
ShamirGroups = 16 # type: Literal[16]
|
||||||
|
PassphraseEntry = 17 # type: Literal[17]
|
||||||
|
@ -10,5 +10,5 @@ if __debug__:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PassphraseStateAck(p.MessageType):
|
class Deprecated_PassphraseStateAck(p.MessageType):
|
||||||
MESSAGE_WIRE_TYPE = 78
|
MESSAGE_WIRE_TYPE = 78
|
@ -10,7 +10,7 @@ if __debug__:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PassphraseStateRequest(p.MessageType):
|
class Deprecated_PassphraseStateRequest(p.MessageType):
|
||||||
MESSAGE_WIRE_TYPE = 77
|
MESSAGE_WIRE_TYPE = 77
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
@ -6,7 +6,7 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypeCapability = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
EnumTypeCapability = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
|
||||||
EnumTypeBackupType = Literal[0, 1, 2]
|
EnumTypeBackupType = Literal[0, 1, 2]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -32,7 +32,6 @@ class Features(p.MessageType):
|
|||||||
bootloader_hash: bytes = None,
|
bootloader_hash: bytes = None,
|
||||||
imported: bool = None,
|
imported: bool = None,
|
||||||
pin_cached: bool = None,
|
pin_cached: bool = None,
|
||||||
passphrase_cached: bool = None,
|
|
||||||
firmware_present: bool = None,
|
firmware_present: bool = None,
|
||||||
needs_backup: bool = None,
|
needs_backup: bool = None,
|
||||||
flags: int = None,
|
flags: int = None,
|
||||||
@ -50,6 +49,8 @@ class Features(p.MessageType):
|
|||||||
sd_card_present: bool = None,
|
sd_card_present: bool = None,
|
||||||
sd_protection: bool = None,
|
sd_protection: bool = None,
|
||||||
wipe_code_protection: bool = None,
|
wipe_code_protection: bool = None,
|
||||||
|
session_id: bytes = None,
|
||||||
|
passphrase_always_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.vendor = vendor
|
self.vendor = vendor
|
||||||
self.major_version = major_version
|
self.major_version = major_version
|
||||||
@ -66,7 +67,6 @@ class Features(p.MessageType):
|
|||||||
self.bootloader_hash = bootloader_hash
|
self.bootloader_hash = bootloader_hash
|
||||||
self.imported = imported
|
self.imported = imported
|
||||||
self.pin_cached = pin_cached
|
self.pin_cached = pin_cached
|
||||||
self.passphrase_cached = passphrase_cached
|
|
||||||
self.firmware_present = firmware_present
|
self.firmware_present = firmware_present
|
||||||
self.needs_backup = needs_backup
|
self.needs_backup = needs_backup
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
@ -84,6 +84,8 @@ class Features(p.MessageType):
|
|||||||
self.sd_card_present = sd_card_present
|
self.sd_card_present = sd_card_present
|
||||||
self.sd_protection = sd_protection
|
self.sd_protection = sd_protection
|
||||||
self.wipe_code_protection = wipe_code_protection
|
self.wipe_code_protection = wipe_code_protection
|
||||||
|
self.session_id = session_id
|
||||||
|
self.passphrase_always_on_device = passphrase_always_on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -103,7 +105,6 @@ class Features(p.MessageType):
|
|||||||
14: ('bootloader_hash', p.BytesType, 0),
|
14: ('bootloader_hash', p.BytesType, 0),
|
||||||
15: ('imported', p.BoolType, 0),
|
15: ('imported', p.BoolType, 0),
|
||||||
16: ('pin_cached', p.BoolType, 0),
|
16: ('pin_cached', p.BoolType, 0),
|
||||||
17: ('passphrase_cached', p.BoolType, 0),
|
|
||||||
18: ('firmware_present', p.BoolType, 0),
|
18: ('firmware_present', p.BoolType, 0),
|
||||||
19: ('needs_backup', p.BoolType, 0),
|
19: ('needs_backup', p.BoolType, 0),
|
||||||
20: ('flags', p.UVarintType, 0),
|
20: ('flags', p.UVarintType, 0),
|
||||||
@ -116,9 +117,11 @@ class Features(p.MessageType):
|
|||||||
27: ('unfinished_backup', p.BoolType, 0),
|
27: ('unfinished_backup', p.BoolType, 0),
|
||||||
28: ('no_backup', p.BoolType, 0),
|
28: ('no_backup', p.BoolType, 0),
|
||||||
29: ('recovery_mode', p.BoolType, 0),
|
29: ('recovery_mode', p.BoolType, 0),
|
||||||
30: ('capabilities', p.EnumType("Capability", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)), p.FLAG_REPEATED),
|
30: ('capabilities', p.EnumType("Capability", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)), p.FLAG_REPEATED),
|
||||||
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
||||||
32: ('sd_card_present', p.BoolType, 0),
|
32: ('sd_card_present', p.BoolType, 0),
|
||||||
33: ('sd_protection', p.BoolType, 0),
|
33: ('sd_protection', p.BoolType, 0),
|
||||||
34: ('wipe_code_protection', p.BoolType, 0),
|
34: ('wipe_code_protection', p.BoolType, 0),
|
||||||
|
35: ('session_id', p.BytesType, 0),
|
||||||
|
36: ('passphrase_always_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,12 @@ class Initialize(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
state: bytes = None,
|
session_id: bytes = None,
|
||||||
skip_passphrase: bool = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.state = state
|
self.session_id = session_id
|
||||||
self.skip_passphrase = skip_passphrase
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('state', p.BytesType, 0),
|
1: ('session_id', p.BytesType, 0),
|
||||||
2: ('skip_passphrase', p.BoolType, 0),
|
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,6 @@ EntropyRequest = 35 # type: Literal[35]
|
|||||||
EntropyAck = 36 # type: Literal[36]
|
EntropyAck = 36 # type: Literal[36]
|
||||||
PassphraseRequest = 41 # type: Literal[41]
|
PassphraseRequest = 41 # type: Literal[41]
|
||||||
PassphraseAck = 42 # type: Literal[42]
|
PassphraseAck = 42 # type: Literal[42]
|
||||||
PassphraseStateRequest = 77 # type: Literal[77]
|
|
||||||
PassphraseStateAck = 78 # type: Literal[78]
|
|
||||||
RecoveryDevice = 45 # type: Literal[45]
|
RecoveryDevice = 45 # type: Literal[45]
|
||||||
WordRequest = 46 # type: Literal[46]
|
WordRequest = 46 # type: Literal[46]
|
||||||
WordAck = 47 # type: Literal[47]
|
WordAck = 47 # type: Literal[47]
|
||||||
@ -40,6 +38,8 @@ SdProtect = 79 # type: Literal[79]
|
|||||||
GetNextU2FCounter = 80 # type: Literal[80]
|
GetNextU2FCounter = 80 # type: Literal[80]
|
||||||
NextU2FCounter = 81 # type: Literal[81]
|
NextU2FCounter = 81 # type: Literal[81]
|
||||||
ChangeWipeCode = 82 # type: Literal[82]
|
ChangeWipeCode = 82 # type: Literal[82]
|
||||||
|
Deprecated_PassphraseStateRequest = 77 # type: Literal[77]
|
||||||
|
Deprecated_PassphraseStateAck = 78 # type: Literal[78]
|
||||||
FirmwareErase = 6 # type: Literal[6]
|
FirmwareErase = 6 # type: Literal[6]
|
||||||
FirmwareUpload = 7 # type: Literal[7]
|
FirmwareUpload = 7 # type: Literal[7]
|
||||||
FirmwareRequest = 8 # type: Literal[8]
|
FirmwareRequest = 8 # type: Literal[8]
|
||||||
|
@ -16,14 +16,17 @@ class PassphraseAck(p.MessageType):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
passphrase: str = None,
|
passphrase: str = None,
|
||||||
state: bytes = None,
|
_state: bytes = None,
|
||||||
|
on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.passphrase = passphrase
|
self.passphrase = passphrase
|
||||||
self.state = state
|
self._state = _state
|
||||||
|
self.on_device = on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('passphrase', p.UnicodeType, 0),
|
1: ('passphrase', p.UnicodeType, 0),
|
||||||
2: ('state', p.BytesType, 0),
|
2: ('_state', p.BytesType, 0),
|
||||||
|
3: ('on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ class PassphraseRequest(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
on_device: bool = None,
|
_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.on_device = on_device
|
self._on_device = _on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('on_device', p.BoolType, 0),
|
1: ('_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
# Automatically generated by pb2py
|
|
||||||
# fmt: off
|
|
||||||
if False:
|
|
||||||
from typing_extensions import Literal
|
|
||||||
|
|
||||||
ASK = 0 # type: Literal[0]
|
|
||||||
DEVICE = 1 # type: Literal[1]
|
|
||||||
HOST = 2 # type: Literal[2]
|
|
@ -17,19 +17,13 @@ class Ping(p.MessageType):
|
|||||||
self,
|
self,
|
||||||
message: str = None,
|
message: str = None,
|
||||||
button_protection: bool = None,
|
button_protection: bool = None,
|
||||||
pin_protection: bool = None,
|
|
||||||
passphrase_protection: bool = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message = message
|
self.message = message
|
||||||
self.button_protection = button_protection
|
self.button_protection = button_protection
|
||||||
self.pin_protection = pin_protection
|
|
||||||
self.passphrase_protection = passphrase_protection
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('message', p.UnicodeType, 0),
|
1: ('message', p.UnicodeType, 0),
|
||||||
2: ('button_protection', p.BoolType, 0),
|
2: ('button_protection', p.BoolType, 0),
|
||||||
3: ('pin_protection', p.BoolType, 0),
|
|
||||||
4: ('passphrase_protection', p.BoolType, 0),
|
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,14 @@ def header_error(message: str, clear: bool = True) -> None:
|
|||||||
display.bar(0, 30, WIDTH, HEIGHT - 30, style.BG)
|
display.bar(0, 30, WIDTH, HEIGHT - 30, style.BG)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_simple(t: Component) -> None: # noqa: F405
|
||||||
|
backlight_fade(style.BACKLIGHT_DIM)
|
||||||
|
display.clear()
|
||||||
|
t.on_render()
|
||||||
|
refresh()
|
||||||
|
backlight_fade(style.BACKLIGHT_NORMAL)
|
||||||
|
|
||||||
|
|
||||||
def grid(
|
def grid(
|
||||||
i: int, # i-th cell of the table of which we wish to return Area (snake-like starting with 0)
|
i: int, # i-th cell of the table of which we wish to return Area (snake-like starting with 0)
|
||||||
n_x: int = 3, # number of rows in the table
|
n_x: int = 3, # number of rows in the table
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from trezor import io, loop, res, ui
|
from trezor import io, loop, res, ui
|
||||||
from trezor.messages import PassphraseSourceType
|
|
||||||
from trezor.ui import display
|
from trezor.ui import display
|
||||||
from trezor.ui.button import Button, ButtonClear, ButtonConfirm
|
from trezor.ui.button import Button, ButtonClear, ButtonConfirm
|
||||||
from trezor.ui.swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, Swipe
|
from trezor.ui.swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, Swipe
|
||||||
@ -246,25 +245,3 @@ class PassphraseKeyboard(ui.Layout):
|
|||||||
|
|
||||||
def create_tasks(self) -> Tuple[loop.Task, ...]:
|
def create_tasks(self) -> Tuple[loop.Task, ...]:
|
||||||
return self.handle_input(), self.handle_rendering(), self.handle_paging()
|
return self.handle_input(), self.handle_rendering(), self.handle_paging()
|
||||||
|
|
||||||
|
|
||||||
class PassphraseSource(ui.Layout):
|
|
||||||
def __init__(self, content: ui.Component) -> None:
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), "Device")
|
|
||||||
self.device.on_click = self.on_device # type: ignore
|
|
||||||
|
|
||||||
self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), "Host")
|
|
||||||
self.host.on_click = self.on_host # type: ignore
|
|
||||||
|
|
||||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
|
||||||
self.content.dispatch(event, x, y)
|
|
||||||
self.device.dispatch(event, x, y)
|
|
||||||
self.host.dispatch(event, x, y)
|
|
||||||
|
|
||||||
def on_device(self) -> None:
|
|
||||||
raise ui.Result(PassphraseSourceType.DEVICE)
|
|
||||||
|
|
||||||
def on_host(self) -> None:
|
|
||||||
raise ui.Result(PassphraseSourceType.HOST)
|
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.append('../src')
|
sys.path.append("../src")
|
||||||
|
|
||||||
from ubinascii import hexlify, unhexlify # noqa: F401
|
from ubinascii import hexlify, unhexlify # noqa: F401
|
||||||
|
|
||||||
import unittest # noqa: F401
|
import unittest # noqa: F401
|
||||||
|
|
||||||
from trezor import utils # noqa: F401
|
from trezor import utils # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
def await_result(task: Awaitable) -> Any:
|
||||||
|
value = None
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
result = task.send(value)
|
||||||
|
except StopIteration as e:
|
||||||
|
return e.value
|
||||||
|
|
||||||
|
if result:
|
||||||
|
value = await_result(result)
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
@ -44,28 +44,14 @@ class ByteArrayWriter:
|
|||||||
return len(buf)
|
return len(buf)
|
||||||
|
|
||||||
|
|
||||||
def run_until_complete(task: Awaitable) -> Any:
|
|
||||||
value = None
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
result = task.send(value)
|
|
||||||
except StopIteration as e:
|
|
||||||
return e.value
|
|
||||||
|
|
||||||
if result:
|
|
||||||
value = run_until_complete(result)
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
|
|
||||||
|
|
||||||
def load_uvarint(data: bytes) -> int:
|
def load_uvarint(data: bytes) -> int:
|
||||||
reader = ByteReader(data)
|
reader = ByteReader(data)
|
||||||
return run_until_complete(protobuf.load_uvarint(reader))
|
return await_result(protobuf.load_uvarint(reader))
|
||||||
|
|
||||||
|
|
||||||
def dump_uvarint(value: int) -> bytearray:
|
def dump_uvarint(value: int) -> bytearray:
|
||||||
writer = ByteArrayWriter()
|
writer = ByteArrayWriter()
|
||||||
run_until_complete(protobuf.dump_uvarint(writer, value))
|
await_result(protobuf.dump_uvarint(writer, value))
|
||||||
return writer.buf
|
return writer.buf
|
||||||
|
|
||||||
|
|
||||||
@ -106,9 +92,9 @@ class TestProtobuf(unittest.TestCase):
|
|||||||
# ok message:
|
# ok message:
|
||||||
msg = Message(-42, 5)
|
msg = Message(-42, 5)
|
||||||
writer = ByteArrayWriter()
|
writer = ByteArrayWriter()
|
||||||
run_until_complete(protobuf.dump_message(writer, msg))
|
await_result(protobuf.dump_message(writer, msg))
|
||||||
reader = ByteReader(bytes(writer.buf))
|
reader = ByteReader(bytes(writer.buf))
|
||||||
nmsg = run_until_complete(protobuf.load_message(reader, Message))
|
nmsg = await_result(protobuf.load_message(reader, Message))
|
||||||
|
|
||||||
self.assertEqual(msg.sint_field, nmsg.sint_field)
|
self.assertEqual(msg.sint_field, nmsg.sint_field)
|
||||||
self.assertEqual(msg.enum_field, nmsg.enum_field)
|
self.assertEqual(msg.enum_field, nmsg.enum_field)
|
||||||
@ -116,10 +102,10 @@ class TestProtobuf(unittest.TestCase):
|
|||||||
# bad enum value:
|
# bad enum value:
|
||||||
msg = Message(-42, 42)
|
msg = Message(-42, 42)
|
||||||
writer = ByteArrayWriter()
|
writer = ByteArrayWriter()
|
||||||
run_until_complete(protobuf.dump_message(writer, msg))
|
await_result(protobuf.dump_message(writer, msg))
|
||||||
reader = ByteReader(bytes(writer.buf))
|
reader = ByteReader(bytes(writer.buf))
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
run_until_complete(protobuf.load_message(reader, Message))
|
await_result(protobuf.load_message(reader, Message))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
81
core/tests/test_storage.cache.py
Normal file
81
core/tests/test_storage.cache.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
from common import *
|
||||||
|
from mock import patch
|
||||||
|
from mock_storage import mock_storage
|
||||||
|
|
||||||
|
import storage
|
||||||
|
from storage import cache
|
||||||
|
from trezor.messages.Initialize import Initialize
|
||||||
|
from trezor.messages.ClearSession import ClearSession
|
||||||
|
from trezor.wire import DUMMY_CONTEXT
|
||||||
|
|
||||||
|
from apps.homescreen import handle_Initialize, handle_ClearSession
|
||||||
|
|
||||||
|
KEY = 99
|
||||||
|
|
||||||
|
|
||||||
|
class TestStorageCache(unittest.TestCase):
|
||||||
|
def test_session_id(self):
|
||||||
|
session_id_a = cache.get_session_id()
|
||||||
|
self.assertIsNotNone(session_id_a)
|
||||||
|
session_id_b = cache.get_session_id()
|
||||||
|
self.assertEqual(session_id_a, session_id_b)
|
||||||
|
|
||||||
|
cache.clear()
|
||||||
|
session_id_c = cache.get_session_id()
|
||||||
|
self.assertIsNotNone(session_id_c)
|
||||||
|
self.assertNotEqual(session_id_a, session_id_c)
|
||||||
|
|
||||||
|
def test_get_set(self):
|
||||||
|
value = cache.get(KEY)
|
||||||
|
self.assertIsNone(value)
|
||||||
|
|
||||||
|
cache.set(KEY, "hello")
|
||||||
|
value = cache.get(KEY)
|
||||||
|
self.assertEqual(value, "hello")
|
||||||
|
|
||||||
|
cache.clear()
|
||||||
|
value = cache.get(KEY)
|
||||||
|
self.assertIsNone(value)
|
||||||
|
|
||||||
|
@mock_storage
|
||||||
|
def test_Initialize(self):
|
||||||
|
def call_Initialize(**kwargs):
|
||||||
|
msg = Initialize(**kwargs)
|
||||||
|
return await_result(handle_Initialize(DUMMY_CONTEXT, msg))
|
||||||
|
|
||||||
|
# calling Initialize without an ID allocates a new one
|
||||||
|
session_id = cache.get_session_id()
|
||||||
|
features = call_Initialize()
|
||||||
|
new_session_id = cache.get_session_id()
|
||||||
|
self.assertNotEqual(session_id, new_session_id)
|
||||||
|
self.assertEqual(new_session_id, features.session_id)
|
||||||
|
|
||||||
|
# calling Initialize with the current ID does not allocate a new one
|
||||||
|
features = call_Initialize(session_id=new_session_id)
|
||||||
|
same_session_id = cache.get_session_id()
|
||||||
|
self.assertEqual(new_session_id, same_session_id)
|
||||||
|
self.assertEqual(same_session_id, features.session_id)
|
||||||
|
|
||||||
|
call_Initialize()
|
||||||
|
# calling Initialize with a non-current ID returns a different one
|
||||||
|
features = call_Initialize(session_id=new_session_id)
|
||||||
|
self.assertNotEqual(new_session_id, features.session_id)
|
||||||
|
|
||||||
|
# allocating a new session ID clears the cache
|
||||||
|
cache.set(KEY, "hello")
|
||||||
|
features = call_Initialize()
|
||||||
|
self.assertIsNone(cache.get(KEY))
|
||||||
|
|
||||||
|
# resuming a session does not clear the cache
|
||||||
|
cache.set(KEY, "hello")
|
||||||
|
call_Initialize(session_id=features.session_id)
|
||||||
|
self.assertEqual(cache.get(KEY), "hello")
|
||||||
|
|
||||||
|
# supplying a different session ID clears the cache
|
||||||
|
self.assertNotEqual(new_session_id, features.session_id)
|
||||||
|
call_Initialize(session_id=new_session_id)
|
||||||
|
self.assertIsNone(cache.get(KEY))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -1,5 +0,0 @@
|
|||||||
# Communication
|
|
||||||
|
|
||||||
We use [Protobuf v2](https://developers.google.com/protocol-buffers/) for host-device communication. The communication cycle is very simple, Trezor receives a message, acts on it and responds with another message. Trezor on its own is incapable of initiating the communication.
|
|
||||||
|
|
||||||
The Protobuf definitions can be found in `common/protob`.
|
|
9
docs/common/communication/index.md
Normal file
9
docs/common/communication/index.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Communication
|
||||||
|
|
||||||
|
_Note: In this section we describe the internal functioning of the communication protocol. If you wish to implement Trezor support you should use [Connect](https://github.com/trezor/connect/) or [python-trezor](https://pypi.org/project/trezor/), which will do all this hard work for you._
|
||||||
|
|
||||||
|
We use [Protobuf v2](https://developers.google.com/protocol-buffers/) for host-device communication. The communication cycle is very simple, Trezor receives a message (request), acts on it and responds with another one (response). Trezor on its own is incapable of initiating the communication.
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
Protobuf messages are defined in the [Common](https://github.com/trezor/trezor-firmware/tree/master/common) project, which is part of this monorepo. This repository is also exported to [trezor/trezor-common](https://github.com/trezor/trezor-common) to be used by third parties, which prefer not to include the whole monorepo. That copy is read-only mirror and all changes are happening in this monorepo.
|
64
docs/common/communication/passphrase.md
Normal file
64
docs/common/communication/passphrase.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Passphrase
|
||||||
|
|
||||||
|
As of \[versions TBD\] we have changed how [passphrase](https://wiki.trezor.io/Passphrase) is communicated between the Host and the Device.
|
||||||
|
|
||||||
|
Passphrase is very tightly coupled with _sessions_. The reader is encouraged to read on that topic first in the [sessions.md](sessions.md) section.
|
||||||
|
|
||||||
|
## Scheme
|
||||||
|
|
||||||
|
As soon as Trezor needs the passphrase to do BIP-39/SLIP-39 derivations it prompts the user for passphrase.
|
||||||
|
|
||||||
|
```
|
||||||
|
GetAddress(...)
|
||||||
|
---------> PassphraseRequest()
|
||||||
|
<---------
|
||||||
|
PassphraseAck
|
||||||
|
(str passphrase, bool on_device)
|
||||||
|
---------> Address(...)
|
||||||
|
<---------
|
||||||
|
```
|
||||||
|
|
||||||
|
In the default Trezor setting, the passphrase is obtained from the Host. Trezor sends a PassphraseRequest message and awaits PassphraseAck as a response. This message contains field `passphrase` to transmit it or it has `on_device` boolean flag to indicate that the user wishes to enter the passphrase on Trezor instead. Setting both `passphrase` and `on_device` to true is forbidden.
|
||||||
|
|
||||||
|
Note that this has changed as of TBD. In previous firmware versions the `on_device` flag was in the PassphraseRequest message, since this decision has been made on Trezor. We also had two additional messages PassphraseStateRequest and PassphraseStateAck which were removed.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
On an initialized device with passphrase enabled a common communication starts like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Initialize()
|
||||||
|
---------> Features(..., session_id)
|
||||||
|
<---------
|
||||||
|
GetAddress(...)
|
||||||
|
---------> PassphraseRequest()
|
||||||
|
<---------
|
||||||
|
PassphraseAck(...)
|
||||||
|
---------> Address(...)
|
||||||
|
<---------
|
||||||
|
```
|
||||||
|
|
||||||
|
The device requested the passphrase since the BIP-39/SLIP-39 seed is not yet cached. After this workflow the seed is cached and the passphrase will therefore never be requested again unless the session is cleared*.
|
||||||
|
|
||||||
|
Since we do not have sessions, the Host can not be sure that someone else has not used the device and applied another session id (e.g. changed the Passphrase). To work around this we send the session id again on every subsequent message. See more on that in [session.md]().
|
||||||
|
|
||||||
|
```
|
||||||
|
Initialize(session_id)
|
||||||
|
---------> Features(..., session_id)
|
||||||
|
<---------
|
||||||
|
GetPublicKey(...)
|
||||||
|
---------> PublicKey(...)
|
||||||
|
<---------
|
||||||
|
```
|
||||||
|
|
||||||
|
As long as the session_id in `Initialize` is the same as the one Trezor stores internally, Trezor guarantees the same passphrase is being used.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
\* There is one exception and that is Cardano. Because Cardano has a different BIP-39/SLIP-39 derivation scheme for passphrase we can not use the cached seed. As a workaround we prompt for the passphrase again in such case and cache the cardano seed in the cardano app directly.
|
||||||
|
|
||||||
|
## Passphrase always on device
|
||||||
|
|
||||||
|
User might want to enforce the passphrase entry on the device every time without the hassle of instructing the Host to do so.
|
||||||
|
|
||||||
|
For such cases the user may apply the *Passphrase always on device* setting. As the name suggests, with this setting the passphrase is prompted on the device right away and no PassphraseRequest/PassphraseAck messages are exchanged. Note that the passphrase is prompted only once for given session id. If the user wishes to enter another passphrase they need to either send Initialize(session_id=None) or replug the device.
|
36
docs/common/communication/sessions.md
Normal file
36
docs/common/communication/sessions.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Sessions (the lack of them)
|
||||||
|
|
||||||
|
Currently the communication protocol lacks sessions, which are planned to be introduced in the near future (see [#79](https://github.com/trezor/trezor-firmware/issues/79)).
|
||||||
|
|
||||||
|
To ensure the device is in the expected state we use something called _session_id_. Session id is a 32 bytes long random blob which identifies the internal device state (mainly its caches). This is primarily useful for passphrase to make sure the same passphrase is cached in the device as the one the user entered a few minutes ago. See [passphrase.md](passphrase.md) for more on that.
|
||||||
|
|
||||||
|
On first initialization the Host does not have a session id and starts the communication with an empty Initialize:
|
||||||
|
|
||||||
|
```
|
||||||
|
Initialize()
|
||||||
|
---------> Features(..., session_id)
|
||||||
|
<---------
|
||||||
|
```
|
||||||
|
|
||||||
|
After the first Features message is received the Host might store the session_id. To ensure the device state Host must send the Initialize message again including that particular session_id:
|
||||||
|
|
||||||
|
```
|
||||||
|
Initialize(session_id)
|
||||||
|
---------> Features(..., session_id)
|
||||||
|
<---------
|
||||||
|
Request
|
||||||
|
---------> Response
|
||||||
|
<---------
|
||||||
|
```
|
||||||
|
|
||||||
|
So to make sure the device state has not changed, the Host must send the Initialize message with the correctly stored session_id before each request. Yes, this is stupid.
|
||||||
|
|
||||||
|
As mentioned, sessions will be introduced soon™ and fix that. We will probably take the first few bytes of the session_id, declare it a session id, and the rest will remain, without the annoying requirement of sending Initialize before every message.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
The session is terminated and therefore the caches are cleared if:
|
||||||
|
- Initialize.session_id is empty.
|
||||||
|
- Initialize.session_id is different then the one cached in Trezor.
|
||||||
|
- Trezor is replugged (session is not persistent).
|
||||||
|
- ClearSession is received.
|
@ -1,8 +1,9 @@
|
|||||||
Version 1.x.x [not yet released]
|
Version 1.9.0 [not yet released]
|
||||||
* Disallow changing of settings via dry-run recovery
|
* Disallow changing of settings via dry-run recovery
|
||||||
* Wipe code
|
* Wipe code
|
||||||
* Make LoadDevice debug only and drop its XPRV feature
|
* Make LoadDevice debug only and drop its XPRV feature
|
||||||
* Add feature to retrieve the next U2F counter
|
* Add feature to retrieve the next U2F counter
|
||||||
|
* Passphrase redesign
|
||||||
|
|
||||||
Version 1.8.3 [Sep 2019]
|
Version 1.8.3 [Sep 2019]
|
||||||
* Small code improvements
|
* Small code improvements
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "curves.h"
|
#include "curves.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "fsm.h"
|
||||||
#include "gettext.h"
|
#include "gettext.h"
|
||||||
#include "hmac.h"
|
#include "hmac.h"
|
||||||
#include "layout2.h"
|
#include "layout2.h"
|
||||||
@ -119,11 +120,11 @@ be added to the storage u2f_counter to get the real counter value.
|
|||||||
* storage.u2f_counter + config_u2f_offset.
|
* storage.u2f_counter + config_u2f_offset.
|
||||||
* This corresponds to the number of cleared bits in the U2FAREA.
|
* This corresponds to the number of cleared bits in the U2FAREA.
|
||||||
*/
|
*/
|
||||||
static secbool sessionSeedCached, sessionSeedUsesPassphrase;
|
static secbool sessionSeedCached;
|
||||||
static uint8_t CONFIDENTIAL sessionSeed[64];
|
static uint8_t CONFIDENTIAL sessionSeed[64];
|
||||||
|
|
||||||
static secbool sessionPassphraseCached = secfalse;
|
static secbool sessionIdCached;
|
||||||
static char CONFIDENTIAL sessionPassphrase[51];
|
static uint8_t sessionId[32];
|
||||||
|
|
||||||
#define autoLockDelayMsDefault (10 * 60 * 1000U) // 10 minutes
|
#define autoLockDelayMsDefault (10 * 60 * 1000U) // 10 minutes
|
||||||
static secbool autoLockDelayMsCached = secfalse;
|
static secbool autoLockDelayMsCached = secfalse;
|
||||||
@ -407,8 +408,8 @@ void config_init(void) {
|
|||||||
void session_clear(bool lock) {
|
void session_clear(bool lock) {
|
||||||
sessionSeedCached = secfalse;
|
sessionSeedCached = secfalse;
|
||||||
memzero(&sessionSeed, sizeof(sessionSeed));
|
memzero(&sessionSeed, sizeof(sessionSeed));
|
||||||
sessionPassphraseCached = secfalse;
|
sessionIdCached = secfalse;
|
||||||
memzero(&sessionPassphrase, sizeof(sessionPassphrase));
|
memzero(&sessionId, sizeof(sessionId));
|
||||||
if (lock) {
|
if (lock) {
|
||||||
storage_lock();
|
storage_lock();
|
||||||
}
|
}
|
||||||
@ -527,8 +528,6 @@ void config_setLanguage(const char *lang) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void config_setPassphraseProtection(bool passphrase_protection) {
|
void config_setPassphraseProtection(bool passphrase_protection) {
|
||||||
sessionSeedCached = secfalse;
|
|
||||||
sessionPassphraseCached = secfalse;
|
|
||||||
config_set_bool(KEY_PASSPHRASE_PROTECTION, passphrase_protection);
|
config_set_bool(KEY_PASSPHRASE_PROTECTION, passphrase_protection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,18 +549,19 @@ static void get_root_node_callback(uint32_t iter, uint32_t total) {
|
|||||||
layoutProgress(_("Waking up"), 1000 * iter / total);
|
layoutProgress(_("Waking up"), 1000 * iter / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *config_getSeed(bool usePassphrase) {
|
const uint8_t *config_getSeed(void) {
|
||||||
// root node is properly cached
|
// root node is properly cached
|
||||||
if (usePassphrase == (sectrue == sessionSeedUsesPassphrase) &&
|
if (sectrue == sessionSeedCached) {
|
||||||
sectrue == sessionSeedCached) {
|
|
||||||
return sessionSeed;
|
return sessionSeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if storage has mnemonic, convert it to node and use it
|
// if storage has mnemonic, convert it to node and use it
|
||||||
char mnemonic[MAX_MNEMONIC_LEN + 1] = {0};
|
char mnemonic[MAX_MNEMONIC_LEN + 1] = {0};
|
||||||
if (config_getMnemonic(mnemonic, sizeof(mnemonic))) {
|
if (config_getMnemonic(mnemonic, sizeof(mnemonic))) {
|
||||||
if (usePassphrase && !protectPassphrase()) {
|
char passphrase[MAX_PASSPHRASE_LEN + 1] = {0};
|
||||||
|
if (!protectPassphrase(passphrase)) {
|
||||||
memzero(mnemonic, sizeof(mnemonic));
|
memzero(mnemonic, sizeof(mnemonic));
|
||||||
|
memzero(passphrase, sizeof(passphrase));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// if storage was not imported (i.e. it was properly generated or recovered)
|
// if storage was not imported (i.e. it was properly generated or recovered)
|
||||||
@ -575,13 +575,16 @@ const uint8_t *config_getSeed(bool usePassphrase) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
char oldTiny = usbTiny(1);
|
char oldTiny = usbTiny(1);
|
||||||
mnemonic_to_seed(mnemonic, usePassphrase ? sessionPassphrase : "",
|
mnemonic_to_seed(mnemonic, passphrase, sessionSeed,
|
||||||
sessionSeed, get_root_node_callback); // BIP-0039
|
get_root_node_callback); // BIP-0039
|
||||||
memzero(mnemonic, sizeof(mnemonic));
|
memzero(mnemonic, sizeof(mnemonic));
|
||||||
|
memzero(passphrase, sizeof(passphrase));
|
||||||
usbTiny(oldTiny);
|
usbTiny(oldTiny);
|
||||||
sessionSeedCached = sectrue;
|
sessionSeedCached = sectrue;
|
||||||
sessionSeedUsesPassphrase = usePassphrase ? sectrue : secfalse;
|
|
||||||
return sessionSeed;
|
return sessionSeed;
|
||||||
|
} else {
|
||||||
|
fsm_sendFailure(FailureType_Failure_NotInitialized,
|
||||||
|
_("Device not initialized"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -606,58 +609,16 @@ bool config_getU2FRoot(HDNode *node) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool config_getRootNode(HDNode *node, const char *curve, bool usePassphrase) {
|
bool config_getRootNode(HDNode *node, const char *curve) {
|
||||||
// if storage has node, decrypt and use it
|
const uint8_t *seed = config_getSeed();
|
||||||
StorageHDNode storageHDNode = {0};
|
|
||||||
uint16_t len = 0;
|
|
||||||
if (strcmp(curve, SECP256K1_NAME) == 0 &&
|
|
||||||
sectrue ==
|
|
||||||
storage_get(KEY_NODE, &storageHDNode, sizeof(storageHDNode), &len) &&
|
|
||||||
len == sizeof(StorageHDNode)) {
|
|
||||||
if (!protectPassphrase()) {
|
|
||||||
memzero(&storageHDNode, sizeof(storageHDNode));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!config_loadNode(&storageHDNode, curve, node)) {
|
|
||||||
memzero(&storageHDNode, sizeof(storageHDNode));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool passphrase_protection = false;
|
|
||||||
config_getPassphraseProtection(&passphrase_protection);
|
|
||||||
if (passphrase_protection && sectrue == sessionPassphraseCached &&
|
|
||||||
sessionPassphrase[0] != '\0') {
|
|
||||||
// decrypt hd node
|
|
||||||
uint8_t secret[64] = {0};
|
|
||||||
PBKDF2_HMAC_SHA512_CTX pctx = {0};
|
|
||||||
char oldTiny = usbTiny(1);
|
|
||||||
pbkdf2_hmac_sha512_Init(&pctx, (const uint8_t *)sessionPassphrase,
|
|
||||||
strlen(sessionPassphrase),
|
|
||||||
(const uint8_t *)"TREZORHD", 8, 1);
|
|
||||||
get_root_node_callback(0, BIP39_PBKDF2_ROUNDS);
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
pbkdf2_hmac_sha512_Update(&pctx, BIP39_PBKDF2_ROUNDS / 8);
|
|
||||||
get_root_node_callback((i + 1) * BIP39_PBKDF2_ROUNDS / 8,
|
|
||||||
BIP39_PBKDF2_ROUNDS);
|
|
||||||
}
|
|
||||||
pbkdf2_hmac_sha512_Final(&pctx, secret);
|
|
||||||
usbTiny(oldTiny);
|
|
||||||
aes_decrypt_ctx ctx = {0};
|
|
||||||
aes_decrypt_key256(secret, &ctx);
|
|
||||||
aes_cbc_decrypt(node->chain_code, node->chain_code, 32, secret + 32,
|
|
||||||
&ctx);
|
|
||||||
aes_cbc_decrypt(node->private_key, node->private_key, 32, secret + 32,
|
|
||||||
&ctx);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
memzero(&storageHDNode, sizeof(storageHDNode));
|
|
||||||
|
|
||||||
const uint8_t *seed = config_getSeed(usePassphrase);
|
|
||||||
if (seed == NULL) {
|
if (seed == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
int result = hdnode_from_seed(seed, 64, curve, node);
|
||||||
return hdnode_from_seed(seed, 64, curve, node);
|
if (result == 0) {
|
||||||
|
fsm_sendFailure(FailureType_Failure_NotInitialized, _("Unsupported curve"));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool config_getLabel(char *dest, uint16_t dest_size) {
|
bool config_getLabel(char *dest, uint16_t dest_size) {
|
||||||
@ -811,40 +772,12 @@ bool config_changeWipeCode(const char *pin, const char *wipe_code) {
|
|||||||
return sectrue == ret;
|
return sectrue == ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_cachePassphrase(const char *passphrase) {
|
const uint8_t *session_getSessionId(void) {
|
||||||
strlcpy(sessionPassphrase, passphrase, sizeof(sessionPassphrase));
|
if (!sessionIdCached) {
|
||||||
sessionPassphraseCached = sectrue;
|
random_buffer(sessionId, 32);
|
||||||
}
|
|
||||||
|
|
||||||
bool session_isPassphraseCached(void) {
|
|
||||||
return sectrue == sessionPassphraseCached;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool session_getState(const uint8_t *salt, uint8_t *state,
|
|
||||||
const char *passphrase) {
|
|
||||||
if (!passphrase && sectrue != sessionPassphraseCached) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
passphrase = sessionPassphrase;
|
|
||||||
}
|
}
|
||||||
if (!salt) {
|
sessionIdCached = sectrue;
|
||||||
// if salt is not provided fill the first half of the state with random data
|
return sessionId;
|
||||||
random_buffer(state, 32);
|
|
||||||
} else {
|
|
||||||
// if salt is provided fill the first half of the state with salt
|
|
||||||
memcpy(state, salt, 32);
|
|
||||||
}
|
|
||||||
// state[0:32] = salt
|
|
||||||
// state[32:64] = HMAC(passphrase, salt || device_id)
|
|
||||||
HMAC_SHA256_CTX ctx = {0};
|
|
||||||
hmac_sha256_Init(&ctx, (const uint8_t *)passphrase, strlen(passphrase));
|
|
||||||
hmac_sha256_Update(&ctx, state, 32);
|
|
||||||
hmac_sha256_Update(&ctx, (const uint8_t *)config_uuid, sizeof(config_uuid));
|
|
||||||
hmac_sha256_Final(&ctx, state + 32);
|
|
||||||
|
|
||||||
memzero(&ctx, sizeof(ctx));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool session_isUnlocked(void) { return sectrue == storage_is_unlocked(); }
|
bool session_isUnlocked(void) { return sectrue == storage_is_unlocked(); }
|
||||||
|
@ -91,10 +91,10 @@ void session_clear(bool lock);
|
|||||||
|
|
||||||
void config_loadDevice(const LoadDevice *msg);
|
void config_loadDevice(const LoadDevice *msg);
|
||||||
|
|
||||||
const uint8_t *config_getSeed(bool usePassphrase);
|
const uint8_t *config_getSeed(void);
|
||||||
|
|
||||||
bool config_getU2FRoot(HDNode *node);
|
bool config_getU2FRoot(HDNode *node);
|
||||||
bool config_getRootNode(HDNode *node, const char *curve, bool usePassphrase);
|
bool config_getRootNode(HDNode *node, const char *curve);
|
||||||
|
|
||||||
bool config_getLabel(char *dest, uint16_t dest_size);
|
bool config_getLabel(char *dest, uint16_t dest_size);
|
||||||
void config_setLabel(const char *label);
|
void config_setLabel(const char *label);
|
||||||
@ -108,10 +108,7 @@ bool config_getPassphraseProtection(bool *passphrase_protection);
|
|||||||
bool config_getHomescreen(uint8_t *dest, uint16_t dest_size);
|
bool config_getHomescreen(uint8_t *dest, uint16_t dest_size);
|
||||||
void config_setHomescreen(const uint8_t *data, uint32_t size);
|
void config_setHomescreen(const uint8_t *data, uint32_t size);
|
||||||
|
|
||||||
void session_cachePassphrase(const char *passphrase);
|
const uint8_t *session_getSessionId(void);
|
||||||
bool session_isPassphraseCached(void);
|
|
||||||
bool session_getState(const uint8_t *salt, uint8_t *state,
|
|
||||||
const char *passphrase);
|
|
||||||
|
|
||||||
bool config_setMnemonic(const char *mnemonic);
|
bool config_setMnemonic(const char *mnemonic);
|
||||||
bool config_containsMnemonic(const char *mnemonic);
|
bool config_containsMnemonic(const char *mnemonic);
|
||||||
|
@ -212,10 +212,7 @@ static HDNode *fsm_getDerivedNode(const char *curve, const uint32_t *address_n,
|
|||||||
if (fingerprint) {
|
if (fingerprint) {
|
||||||
*fingerprint = 0;
|
*fingerprint = 0;
|
||||||
}
|
}
|
||||||
if (!config_getRootNode(&node, curve, true)) {
|
if (!config_getRootNode(&node, curve)) {
|
||||||
fsm_sendFailure(FailureType_Failure_NotInitialized,
|
|
||||||
_("Device not initialized or passphrase request cancelled "
|
|
||||||
"or unsupported curve"));
|
|
||||||
layoutHome();
|
layoutHome();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,14 @@
|
|||||||
void fsm_msgInitialize(const Initialize *msg) {
|
void fsm_msgInitialize(const Initialize *msg) {
|
||||||
recovery_abort();
|
recovery_abort();
|
||||||
signing_abort();
|
signing_abort();
|
||||||
if (msg && msg->has_state && msg->state.size == 64) {
|
if (msg && msg->has_session_id && msg->session_id.size == 32) {
|
||||||
uint8_t i_state[64];
|
if (0 != memcmp(session_getSessionId(), msg->session_id.bytes, 32)) {
|
||||||
if (!session_getState(msg->state.bytes, i_state, NULL)) {
|
// If session id was specified but does not match -> clear the cache.
|
||||||
session_clear(false); // do not clear PIN
|
session_clear(false); // do not lock
|
||||||
} else {
|
|
||||||
if (0 != memcmp(msg->state.bytes, i_state, 64)) {
|
|
||||||
session_clear(false); // do not clear PIN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session_clear(false); // do not clear PIN
|
// If session id was not specified -> clear the cache.
|
||||||
|
session_clear(false); // do not lock
|
||||||
}
|
}
|
||||||
layoutHome();
|
layoutHome();
|
||||||
fsm_msgGetFeatures(0);
|
fsm_msgGetFeatures(0);
|
||||||
@ -39,6 +36,12 @@ void fsm_msgInitialize(const Initialize *msg) {
|
|||||||
void fsm_msgGetFeatures(const GetFeatures *msg) {
|
void fsm_msgGetFeatures(const GetFeatures *msg) {
|
||||||
(void)msg;
|
(void)msg;
|
||||||
RESP_INIT(Features);
|
RESP_INIT(Features);
|
||||||
|
|
||||||
|
resp->has_session_id = true;
|
||||||
|
memcpy(resp->session_id.bytes, session_getSessionId(),
|
||||||
|
sizeof(resp->session_id.bytes));
|
||||||
|
resp->session_id.size = sizeof(resp->session_id.bytes);
|
||||||
|
|
||||||
resp->has_vendor = true;
|
resp->has_vendor = true;
|
||||||
strlcpy(resp->vendor, "trezor.io", sizeof(resp->vendor));
|
strlcpy(resp->vendor, "trezor.io", sizeof(resp->vendor));
|
||||||
resp->has_major_version = true;
|
resp->has_major_version = true;
|
||||||
@ -71,8 +74,6 @@ void fsm_msgGetFeatures(const GetFeatures *msg) {
|
|||||||
resp->has_imported = config_getImported(&(resp->imported));
|
resp->has_imported = config_getImported(&(resp->imported));
|
||||||
resp->has_pin_cached = true;
|
resp->has_pin_cached = true;
|
||||||
resp->pin_cached = session_isUnlocked() && config_hasPin();
|
resp->pin_cached = session_isUnlocked() && config_hasPin();
|
||||||
resp->has_passphrase_cached = true;
|
|
||||||
resp->passphrase_cached = session_isPassphraseCached();
|
|
||||||
resp->has_needs_backup = true;
|
resp->has_needs_backup = true;
|
||||||
config_getNeedsBackup(&(resp->needs_backup));
|
config_getNeedsBackup(&(resp->needs_backup));
|
||||||
resp->has_unfinished_backup = true;
|
resp->has_unfinished_backup = true;
|
||||||
@ -120,17 +121,6 @@ void fsm_msgPing(const Ping *msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg->has_pin_protection && msg->pin_protection) {
|
|
||||||
CHECK_PIN
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg->has_passphrase_protection && msg->passphrase_protection) {
|
|
||||||
if (!protectPassphrase()) {
|
|
||||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg->has_message) {
|
if (msg->has_message) {
|
||||||
resp->has_message = true;
|
resp->has_message = true;
|
||||||
memcpy(&(resp->message), &(msg->message), sizeof(resp->message));
|
memcpy(&(resp->message), &(msg->message), sizeof(resp->message));
|
||||||
@ -359,6 +349,10 @@ void fsm_msgClearSession(const ClearSession *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fsm_msgApplySettings(const ApplySettings *msg) {
|
void fsm_msgApplySettings(const ApplySettings *msg) {
|
||||||
|
CHECK_PARAM(
|
||||||
|
!msg->has_passphrase_always_on_device,
|
||||||
|
_("This firmware is incapable of passphrase entry on the device."));
|
||||||
|
|
||||||
CHECK_PARAM(msg->has_label || msg->has_language || msg->has_use_passphrase ||
|
CHECK_PARAM(msg->has_label || msg->has_language || msg->has_use_passphrase ||
|
||||||
msg->has_homescreen || msg->has_auto_lock_delay_ms,
|
msg->has_homescreen || msg->has_auto_lock_delay_ms,
|
||||||
_("No setting provided"));
|
_("No setting provided"));
|
||||||
|
@ -350,10 +350,12 @@ bool protectChangeWipeCode(bool removal) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool protectPassphrase(void) {
|
bool protectPassphrase(char *passphrase) {
|
||||||
|
memzero(passphrase, MAX_PASSPHRASE_LEN + 1);
|
||||||
bool passphrase_protection = false;
|
bool passphrase_protection = false;
|
||||||
config_getPassphraseProtection(&passphrase_protection);
|
config_getPassphraseProtection(&passphrase_protection);
|
||||||
if (!passphrase_protection || session_isPassphraseCached()) {
|
if (!passphrase_protection) {
|
||||||
|
// passphrase already set to empty by memzero above
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,12 +371,24 @@ bool protectPassphrase(void) {
|
|||||||
bool result;
|
bool result;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
usbPoll();
|
usbPoll();
|
||||||
// TODO: correctly process PassphraseAck with state field set (mismatch =>
|
|
||||||
// Failure)
|
|
||||||
if (msg_tiny_id == MessageType_MessageType_PassphraseAck) {
|
if (msg_tiny_id == MessageType_MessageType_PassphraseAck) {
|
||||||
msg_tiny_id = 0xFFFF;
|
msg_tiny_id = 0xFFFF;
|
||||||
PassphraseAck *ppa = (PassphraseAck *)msg_tiny;
|
PassphraseAck *ppa = (PassphraseAck *)msg_tiny;
|
||||||
session_cachePassphrase(ppa->has_passphrase ? ppa->passphrase : "");
|
if (ppa->has_on_device && ppa->on_device == true) {
|
||||||
|
fsm_sendFailure(
|
||||||
|
FailureType_Failure_DataError,
|
||||||
|
_("This firmware is incapable of passphrase entry on the device."));
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!ppa->has_passphrase) {
|
||||||
|
fsm_sendFailure(FailureType_Failure_DataError,
|
||||||
|
_("No passphrase provided. Use empty string to set an "
|
||||||
|
"empty passphrase."));
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
strlcpy(passphrase, ppa->passphrase, sizeof(ppa->passphrase));
|
||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -383,8 +397,9 @@ bool protectPassphrase(void) {
|
|||||||
protectAbortedByInitialize =
|
protectAbortedByInitialize =
|
||||||
(msg_tiny_id == MessageType_MessageType_Initialize);
|
(msg_tiny_id == MessageType_MessageType_Initialize);
|
||||||
if (protectAbortedByCancel || protectAbortedByInitialize) {
|
if (protectAbortedByCancel || protectAbortedByInitialize) {
|
||||||
msg_tiny_id = 0xFFFF;
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||||
result = false;
|
result = false;
|
||||||
|
msg_tiny_id = 0xFFFF;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,15 @@
|
|||||||
#include "messages-common.pb.h"
|
#include "messages-common.pb.h"
|
||||||
#include "secbool.h"
|
#include "secbool.h"
|
||||||
|
|
||||||
|
#define MAX_PASSPHRASE_LEN 50
|
||||||
|
|
||||||
bool protectButton(ButtonRequestType type, bool confirm_only);
|
bool protectButton(ButtonRequestType type, bool confirm_only);
|
||||||
secbool protectPinUiCallback(uint32_t wait, uint32_t progress,
|
secbool protectPinUiCallback(uint32_t wait, uint32_t progress,
|
||||||
const char* message);
|
const char* message);
|
||||||
bool protectPin(bool use_cached);
|
bool protectPin(bool use_cached);
|
||||||
bool protectChangePin(bool removal);
|
bool protectChangePin(bool removal);
|
||||||
bool protectChangeWipeCode(bool removal);
|
bool protectChangeWipeCode(bool removal);
|
||||||
bool protectPassphrase(void);
|
bool protectPassphrase(char* passphrase);
|
||||||
|
|
||||||
extern bool protectAbortedByCancel;
|
extern bool protectAbortedByCancel;
|
||||||
extern bool protectAbortedByInitialize;
|
extern bool protectAbortedByInitialize;
|
||||||
|
@ -5,9 +5,6 @@ Failure.message max_size:256
|
|||||||
PinMatrixAck.pin max_size:10
|
PinMatrixAck.pin max_size:10
|
||||||
|
|
||||||
PassphraseAck.passphrase max_size:51
|
PassphraseAck.passphrase max_size:51
|
||||||
PassphraseAck.state max_size:64
|
|
||||||
|
|
||||||
PassphraseStateRequest.state max_size:64
|
|
||||||
|
|
||||||
HDNodeType.chain_code max_size:32
|
HDNodeType.chain_code max_size:32
|
||||||
HDNodeType.private_key max_size:32
|
HDNodeType.private_key max_size:32
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Initialize.state max_size:64
|
Initialize.session_id max_size:32
|
||||||
|
|
||||||
Features.vendor max_size:33
|
Features.vendor max_size:33
|
||||||
Features.device_id max_size:25
|
Features.device_id max_size:25
|
||||||
@ -10,6 +10,7 @@ Features.model max_size:17
|
|||||||
Features.fw_vendor max_size:256
|
Features.fw_vendor max_size:256
|
||||||
Features.fw_vendor_keys max_size:32
|
Features.fw_vendor_keys max_size:32
|
||||||
Features.capabilities max_count:32
|
Features.capabilities max_count:32
|
||||||
|
Features.session_id max_size:32
|
||||||
|
|
||||||
ApplySettings.language max_size:17
|
ApplySettings.language max_size:17
|
||||||
ApplySettings.label max_size:33
|
ApplySettings.label max_size:33
|
||||||
|
@ -109,6 +109,8 @@ fl.write(
|
|||||||
messages = defaultdict(list)
|
messages = defaultdict(list)
|
||||||
|
|
||||||
for message in MessageType.DESCRIPTOR.values:
|
for message in MessageType.DESCRIPTOR.values:
|
||||||
|
if message.GetOptions().deprecated:
|
||||||
|
continue
|
||||||
extension = get_wire_extension(message)
|
extension = get_wire_extension(message)
|
||||||
messages[extension].append(message)
|
messages[extension].append(message)
|
||||||
|
|
||||||
|
@ -1460,7 +1460,7 @@ const HDNode *stellar_deriveNode(const uint32_t *address_n,
|
|||||||
const char *curve = "ed25519";
|
const char *curve = "ed25519";
|
||||||
|
|
||||||
// Device not initialized, passphrase request cancelled, or unsupported curve
|
// Device not initialized, passphrase request cancelled, or unsupported curve
|
||||||
if (!config_getRootNode(&node, curve, true)) {
|
if (!config_getRootNode(&node, curve)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Failed to derive private key
|
// Failed to derive private key
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#define VERSION_MAJOR 1
|
#define VERSION_MAJOR 1
|
||||||
#define VERSION_MINOR 8
|
#define VERSION_MINOR 9
|
||||||
#define VERSION_PATCH 4
|
#define VERSION_PATCH 0
|
||||||
|
|
||||||
#define FIX_VERSION_MAJOR 1
|
#define FIX_VERSION_MAJOR 1
|
||||||
#define FIX_VERSION_MINOR 8
|
#define FIX_VERSION_MINOR 9
|
||||||
#define FIX_VERSION_PATCH 0
|
#define FIX_VERSION_PATCH 0
|
||||||
|
@ -6,13 +6,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
_At the moment, the project does **not** adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). That is expected to change with version 1.0._
|
_At the moment, the project does **not** adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). That is expected to change with version 1.0._
|
||||||
|
|
||||||
## [0.11.7] - Unreleased
|
## [0.12.0] - Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- support for firmwares 1.9.0 and 2.3.0
|
||||||
|
- Model T now defaults to entering passphrase on device. New trezorctl option `-P`
|
||||||
|
enforces entering passphrase on host.
|
||||||
|
- support for "passphrase always on device" mode on model T
|
||||||
|
- new trezorctl command `get-session` and option `-s` allows entering passphrase once
|
||||||
|
for multiple subsequent trezorctl operations
|
||||||
- built-in functionality of UdpTransport to wait until an emulator comes up, and the
|
- built-in functionality of UdpTransport to wait until an emulator comes up, and the
|
||||||
related command `trezorctl wait-for-emulator`
|
related command `trezorctl wait-for-emulator`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- API for `cosi` module was streamlined: `verify_m_of_n` is now `verify`, the old
|
||||||
|
`verify` is `verify_combined`
|
||||||
|
- internals of firmware parsing were reworked to support signing firmware headers
|
||||||
|
- `get_default_client` respects `TREZOR_PATH` environment variable
|
||||||
|
- UI callback `get_passphrase` has an additional argument `available_on_device`,
|
||||||
|
indicating that the connected Trezor is capable of on-device entry
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- trezorctl does not print empty line when there is no output
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `btc.sign_tx` will not preload transaction data from `prev_txes`, as usage with TxApi
|
||||||
|
is being phased out
|
||||||
|
- PIN protection and passphrase protection for `ping()` command was removed
|
||||||
|
|
||||||
## [0.11.6] - 2019-12-30
|
## [0.11.6] - 2019-12-30
|
||||||
[0.11.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.5...python/v0.11.6
|
[0.11.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.5...python/v0.11.6
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
__version__ = "0.11.6"
|
__version__ = "0.12.0"
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
MINIMUM_FIRMWARE_VERSION = {
|
MINIMUM_FIRMWARE_VERSION = {
|
||||||
|
@ -16,15 +16,9 @@
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from .. import device, messages
|
from .. import device
|
||||||
from . import ChoiceType
|
from . import ChoiceType
|
||||||
|
|
||||||
PASSPHRASE_SOURCE = {
|
|
||||||
"ask": messages.PassphraseSourceType.ASK,
|
|
||||||
"device": messages.PassphraseSourceType.DEVICE,
|
|
||||||
"host": messages.PassphraseSourceType.HOST,
|
|
||||||
}
|
|
||||||
|
|
||||||
ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270}
|
ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270}
|
||||||
|
|
||||||
|
|
||||||
@ -145,10 +139,13 @@ def passphrase():
|
|||||||
|
|
||||||
|
|
||||||
@passphrase.command(name="enabled")
|
@passphrase.command(name="enabled")
|
||||||
|
@click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def passphrase_enable(connect):
|
def passphrase_enable(connect, force_on_device: bool):
|
||||||
"""Enable passphrase."""
|
"""Enable passphrase."""
|
||||||
return device.apply_settings(connect(), use_passphrase=True)
|
return device.apply_settings(
|
||||||
|
connect(), use_passphrase=True, passphrase_always_on_device=force_on_device
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@passphrase.command(name="disabled")
|
@passphrase.command(name="disabled")
|
||||||
@ -156,19 +153,3 @@ def passphrase_enable(connect):
|
|||||||
def passphrase_disable(connect):
|
def passphrase_disable(connect):
|
||||||
"""Disable passphrase."""
|
"""Disable passphrase."""
|
||||||
return device.apply_settings(connect(), use_passphrase=False)
|
return device.apply_settings(connect(), use_passphrase=False)
|
||||||
|
|
||||||
|
|
||||||
@passphrase.command(name="source")
|
|
||||||
@click.argument("source", type=ChoiceType(PASSPHRASE_SOURCE))
|
|
||||||
@click.pass_obj
|
|
||||||
def passphrase_source(connect, source):
|
|
||||||
"""Set passphrase source.
|
|
||||||
|
|
||||||
Configure how to enter passphrase on Trezor Model T. The options are:
|
|
||||||
|
|
||||||
\b
|
|
||||||
ask - always ask where to enter passphrase
|
|
||||||
device - always enter passphrase on device
|
|
||||||
host - always enter passphrase on host
|
|
||||||
"""
|
|
||||||
return device.apply_settings(connect(), passphrase_source=source)
|
|
||||||
|
@ -52,7 +52,6 @@ COMMAND_ALIASES = {
|
|||||||
"change-pin": settings.pin,
|
"change-pin": settings.pin,
|
||||||
"enable-passphrase": settings.passphrase_enable,
|
"enable-passphrase": settings.passphrase_enable,
|
||||||
"disable-passphrase": settings.passphrase_disable,
|
"disable-passphrase": settings.passphrase_disable,
|
||||||
"set-passphrase-source": settings.passphrase_source,
|
|
||||||
"wipe-device": device.wipe,
|
"wipe-device": device.wipe,
|
||||||
"reset-device": device.setup,
|
"reset-device": device.setup,
|
||||||
"recovery-device": device.recover,
|
"recovery-device": device.recover,
|
||||||
@ -143,11 +142,26 @@ def configure_logging(verbose: int):
|
|||||||
@click.option(
|
@click.option(
|
||||||
"-j", "--json", "is_json", is_flag=True, help="Print result as JSON object"
|
"-j", "--json", "is_json", is_flag=True, help="Print result as JSON object"
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"-P", "--passphrase-on-host", is_flag=True, help="Enter passphrase on host.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-s",
|
||||||
|
"--session-id",
|
||||||
|
help="Resume given session ID.",
|
||||||
|
default=os.environ.get("TREZOR_SESSION_ID"),
|
||||||
|
)
|
||||||
@click.version_option()
|
@click.version_option()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, path, verbose, is_json):
|
def cli(ctx, path, verbose, is_json, passphrase_on_host, session_id):
|
||||||
configure_logging(verbose)
|
configure_logging(verbose)
|
||||||
|
|
||||||
|
if session_id:
|
||||||
|
try:
|
||||||
|
session_id = bytes.fromhex(session_id)
|
||||||
|
except ValueError:
|
||||||
|
raise click.ClickException("Not a valid session id: {}".format(session_id))
|
||||||
|
|
||||||
def get_device():
|
def get_device():
|
||||||
try:
|
try:
|
||||||
device = get_transport(path, prefix_search=False)
|
device = get_transport(path, prefix_search=False)
|
||||||
@ -159,13 +173,17 @@ def cli(ctx, path, verbose, is_json):
|
|||||||
if path is not None:
|
if path is not None:
|
||||||
click.echo("Using path: {}".format(path))
|
click.echo("Using path: {}".format(path))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return TrezorClient(transport=device, ui=ui.ClickUI())
|
return TrezorClient(
|
||||||
|
transport=device,
|
||||||
|
ui=ui.ClickUI(passphrase_on_host=passphrase_on_host),
|
||||||
|
session_id=session_id,
|
||||||
|
)
|
||||||
|
|
||||||
ctx.obj = get_device
|
ctx.obj = get_device
|
||||||
|
|
||||||
|
|
||||||
@cli.resultcallback()
|
@cli.resultcallback()
|
||||||
def print_result(res, path, verbose, is_json):
|
def print_result(res, is_json, **kwargs):
|
||||||
if is_json:
|
if is_json:
|
||||||
if isinstance(res, protobuf.MessageType):
|
if isinstance(res, protobuf.MessageType):
|
||||||
click.echo(json.dumps({res.__class__.__name__: res.__dict__}))
|
click.echo(json.dumps({res.__class__.__name__: res.__dict__}))
|
||||||
@ -202,7 +220,7 @@ def list_devices():
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
def version():
|
def version():
|
||||||
"""Show version of trezorctl/trezorlib."""
|
"""Show version of trezorctl/trezorlib."""
|
||||||
from trezorlib import __version__ as VERSION
|
from .. import __version__ as VERSION
|
||||||
|
|
||||||
return VERSION
|
return VERSION
|
||||||
|
|
||||||
@ -215,17 +233,33 @@ def version():
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument("message")
|
@click.argument("message")
|
||||||
@click.option("-b", "--button-protection", is_flag=True)
|
@click.option("-b", "--button-protection", is_flag=True)
|
||||||
@click.option("-p", "--pin-protection", is_flag=True)
|
|
||||||
@click.option("-r", "--passphrase-protection", is_flag=True)
|
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def ping(connect, message, button_protection, pin_protection, passphrase_protection):
|
def ping(connect, message, button_protection):
|
||||||
"""Send ping message."""
|
"""Send ping message."""
|
||||||
return connect().ping(
|
return connect().ping(message, button_protection=button_protection)
|
||||||
message,
|
|
||||||
button_protection=button_protection,
|
|
||||||
pin_protection=pin_protection,
|
@cli.command()
|
||||||
passphrase_protection=passphrase_protection,
|
@click.pass_obj
|
||||||
)
|
def get_session(connect):
|
||||||
|
"""Get a session ID for subsequent commands.
|
||||||
|
|
||||||
|
Unlocks Trezor with a passphrase and returns a session ID. Use this session ID with
|
||||||
|
`trezorctl -s SESSION_ID`, or set it to an environment variable `TREZOR_SESSION_ID`,
|
||||||
|
to avoid having to enter passphrase for subsequent commands.
|
||||||
|
|
||||||
|
The session ID is valid until another client starts using Trezor, until the next
|
||||||
|
get-session call, or until Trezor is disconnected.
|
||||||
|
"""
|
||||||
|
from ..btc import get_address
|
||||||
|
from ..client import PASSPHRASE_TEST_PATH
|
||||||
|
|
||||||
|
client = connect()
|
||||||
|
get_address(client, "Testnet", PASSPHRASE_TEST_PATH)
|
||||||
|
if client.session_id is None:
|
||||||
|
raise click.ClickException("Passphrase not enabled or firmware too old.")
|
||||||
|
else:
|
||||||
|
return client.session_id.hex()
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -23,6 +23,7 @@ from types import SimpleNamespace
|
|||||||
from mnemonic import Mnemonic
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
from . import MINIMUM_FIRMWARE_VERSION, exceptions, messages, tools
|
from . import MINIMUM_FIRMWARE_VERSION, exceptions, messages, tools
|
||||||
|
from .messages import Capability
|
||||||
|
|
||||||
if sys.version_info.major < 3:
|
if sys.version_info.major < 3:
|
||||||
raise Exception("Trezorlib does not support Python 2 anymore.")
|
raise Exception("Trezorlib does not support Python 2 anymore.")
|
||||||
@ -32,6 +33,9 @@ LOG = logging.getLogger(__name__)
|
|||||||
VENDORS = ("bitcointrezor.com", "trezor.io")
|
VENDORS = ("bitcointrezor.com", "trezor.io")
|
||||||
MAX_PASSPHRASE_LENGTH = 50
|
MAX_PASSPHRASE_LENGTH = 50
|
||||||
|
|
||||||
|
PASSPHRASE_ON_DEVICE = object()
|
||||||
|
PASSPHRASE_TEST_PATH = tools.parse_path("44h/1h/19h/0/1337")
|
||||||
|
|
||||||
DEPRECATION_ERROR = """
|
DEPRECATION_ERROR = """
|
||||||
Incompatible Trezor library detected.
|
Incompatible Trezor library detected.
|
||||||
|
|
||||||
@ -107,15 +111,17 @@ class TrezorClient:
|
|||||||
- passphrase request (ask the user to enter a passphrase)
|
- passphrase request (ask the user to enter a passphrase)
|
||||||
See `trezorlib.ui` for details.
|
See `trezorlib.ui` for details.
|
||||||
|
|
||||||
You can supply a `state` you saved in the previous session. If you do,
|
You can supply a `session_id` you might have saved in the previous session.
|
||||||
the user might not need to enter their passphrase again.
|
If you do, the user might not need to enter their passphrase again.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, transport, ui=_NO_UI_OBJECT, state=None):
|
def __init__(
|
||||||
|
self, transport, ui=_NO_UI_OBJECT, session_id=None,
|
||||||
|
):
|
||||||
LOG.info("creating client instance for device: {}".format(transport.get_path()))
|
LOG.info("creating client instance for device: {}".format(transport.get_path()))
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.ui = ui
|
self.ui = ui
|
||||||
self.state = state
|
self.session_id = session_id
|
||||||
|
|
||||||
# XXX remove when old Electrum has been cycled out.
|
# XXX remove when old Electrum has been cycled out.
|
||||||
# explanation: We changed the API in 0.11 and this broke older versions
|
# explanation: We changed the API in 0.11 and this broke older versions
|
||||||
@ -177,30 +183,47 @@ class TrezorClient:
|
|||||||
else:
|
else:
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def _callback_passphrase(self, msg):
|
def _callback_passphrase(self, msg: messages.PassphraseRequest):
|
||||||
if msg.on_device:
|
available_on_device = Capability.PassphraseEntry in self.features.capabilities
|
||||||
passphrase = None
|
|
||||||
|
def send_passphrase(passphrase=None, on_device=None):
|
||||||
|
if self.features.model == "1":
|
||||||
|
state = None
|
||||||
else:
|
else:
|
||||||
|
state = self.session_id
|
||||||
|
msg = messages.PassphraseAck(
|
||||||
|
_state=state, passphrase=passphrase, on_device=on_device
|
||||||
|
)
|
||||||
|
resp = self.call_raw(msg)
|
||||||
|
if isinstance(resp, messages.Deprecated_PassphraseStateRequest):
|
||||||
|
self.session_id = resp.state
|
||||||
|
resp = self.call_raw(messages.Deprecated_PassphraseStateAck())
|
||||||
|
return resp
|
||||||
|
|
||||||
|
# short-circuit old style entry
|
||||||
|
if msg._on_device is True:
|
||||||
|
return send_passphrase(None, None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
passphrase = self.ui.get_passphrase()
|
passphrase = self.ui.get_passphrase(available_on_device=available_on_device)
|
||||||
except exceptions.Cancelled:
|
except exceptions.Cancelled:
|
||||||
self.call_raw(messages.Cancel())
|
self.call_raw(messages.Cancel())
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if passphrase is PASSPHRASE_ON_DEVICE:
|
||||||
|
if not available_on_device:
|
||||||
|
self.call_raw(messages.Cancel())
|
||||||
|
raise RuntimeError("Device is not capable of entering passphrase")
|
||||||
|
else:
|
||||||
|
return send_passphrase(on_device=True)
|
||||||
|
|
||||||
|
# else process host-entered passphrase
|
||||||
passphrase = Mnemonic.normalize_string(passphrase)
|
passphrase = Mnemonic.normalize_string(passphrase)
|
||||||
if len(passphrase) > MAX_PASSPHRASE_LENGTH:
|
if len(passphrase) > MAX_PASSPHRASE_LENGTH:
|
||||||
self.call_raw(messages.Cancel())
|
self.call_raw(messages.Cancel())
|
||||||
raise ValueError("Passphrase too long")
|
raise ValueError("Passphrase too long")
|
||||||
|
|
||||||
resp = self.call_raw(
|
return send_passphrase(passphrase, on_device=False)
|
||||||
messages.PassphraseAck(passphrase=passphrase, state=self.state)
|
|
||||||
)
|
|
||||||
if isinstance(resp, messages.PassphraseStateRequest):
|
|
||||||
# TODO report to the user that the passphrase has changed?
|
|
||||||
self.state = resp.state
|
|
||||||
return self.call_raw(messages.PassphraseStateAck())
|
|
||||||
else:
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def _callback_button(self, msg):
|
def _callback_button(self, msg):
|
||||||
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
__tracebackhide__ = True # for pytest # pylint: disable=W0612
|
||||||
@ -229,7 +252,7 @@ class TrezorClient:
|
|||||||
|
|
||||||
@tools.session
|
@tools.session
|
||||||
def init_device(self):
|
def init_device(self):
|
||||||
resp = self.call_raw(messages.Initialize(state=self.state))
|
resp = self.call_raw(messages.Initialize(session_id=self.session_id))
|
||||||
if not isinstance(resp, messages.Features):
|
if not isinstance(resp, messages.Features):
|
||||||
raise exceptions.TrezorException("Unexpected initial response")
|
raise exceptions.TrezorException("Unexpected initial response")
|
||||||
else:
|
else:
|
||||||
@ -245,6 +268,8 @@ class TrezorClient:
|
|||||||
self.features.patch_version,
|
self.features.patch_version,
|
||||||
)
|
)
|
||||||
self.check_firmware_version(warn_only=True)
|
self.check_firmware_version(warn_only=True)
|
||||||
|
if self.features.session_id is not None:
|
||||||
|
self.session_id = self.features.session_id
|
||||||
|
|
||||||
def is_outdated(self):
|
def is_outdated(self):
|
||||||
if self.features.bootloader_mode:
|
if self.features.bootloader_mode:
|
||||||
@ -262,17 +287,13 @@ class TrezorClient:
|
|||||||
|
|
||||||
@tools.expect(messages.Success, field="message")
|
@tools.expect(messages.Success, field="message")
|
||||||
def ping(
|
def ping(
|
||||||
self,
|
self, msg, button_protection=False,
|
||||||
msg,
|
|
||||||
button_protection=False,
|
|
||||||
pin_protection=False,
|
|
||||||
passphrase_protection=False,
|
|
||||||
):
|
):
|
||||||
# We would like ping to work on any valid TrezorClient instance, but
|
# We would like ping to work on any valid TrezorClient instance, but
|
||||||
# due to the protection modes, we need to go through self.call, and that will
|
# due to the protection modes, we need to go through self.call, and that will
|
||||||
# raise an exception if the firmware is too old.
|
# raise an exception if the firmware is too old.
|
||||||
# So we short-circuit the simplest variant of ping with call_raw.
|
# So we short-circuit the simplest variant of ping with call_raw.
|
||||||
if not button_protection and not pin_protection and not passphrase_protection:
|
if not button_protection:
|
||||||
# XXX this should be: `with self:`
|
# XXX this should be: `with self:`
|
||||||
try:
|
try:
|
||||||
self.open()
|
self.open()
|
||||||
@ -280,12 +301,7 @@ class TrezorClient:
|
|||||||
finally:
|
finally:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
msg = messages.Ping(
|
msg = messages.Ping(message=msg, button_protection=button_protection,)
|
||||||
message=msg,
|
|
||||||
button_protection=button_protection,
|
|
||||||
pin_protection=pin_protection,
|
|
||||||
passphrase_protection=passphrase_protection,
|
|
||||||
)
|
|
||||||
return self.call(msg)
|
return self.call(msg)
|
||||||
|
|
||||||
def get_device_id(self):
|
def get_device_id(self):
|
||||||
@ -295,7 +311,7 @@ class TrezorClient:
|
|||||||
def clear_session(self):
|
def clear_session(self):
|
||||||
resp = self.call_raw(messages.ClearSession())
|
resp = self.call_raw(messages.ClearSession())
|
||||||
if isinstance(resp, messages.Success):
|
if isinstance(resp, messages.Success):
|
||||||
self.state = None
|
self.session_id = None
|
||||||
self.init_device()
|
self.init_device()
|
||||||
return resp.message
|
return resp.message
|
||||||
else:
|
else:
|
||||||
|
@ -226,7 +226,7 @@ class DebugUI:
|
|||||||
else:
|
else:
|
||||||
return self.debuglink.read_pin_encoded()
|
return self.debuglink.read_pin_encoded()
|
||||||
|
|
||||||
def get_passphrase(self):
|
def get_passphrase(self, available_on_device):
|
||||||
return self.passphrase
|
return self.passphrase
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ def apply_settings(
|
|||||||
language=None,
|
language=None,
|
||||||
use_passphrase=None,
|
use_passphrase=None,
|
||||||
homescreen=None,
|
homescreen=None,
|
||||||
passphrase_source=None,
|
passphrase_always_on_device=None,
|
||||||
auto_lock_delay_ms=None,
|
auto_lock_delay_ms=None,
|
||||||
display_rotation=None,
|
display_rotation=None,
|
||||||
):
|
):
|
||||||
@ -64,8 +64,8 @@ def apply_settings(
|
|||||||
settings.use_passphrase = use_passphrase
|
settings.use_passphrase = use_passphrase
|
||||||
if homescreen is not None:
|
if homescreen is not None:
|
||||||
settings.homescreen = homescreen
|
settings.homescreen = homescreen
|
||||||
if passphrase_source is not None:
|
if passphrase_always_on_device is not None:
|
||||||
settings.passphrase_source = passphrase_source
|
settings.passphrase_always_on_device = passphrase_always_on_device
|
||||||
if auto_lock_delay_ms is not None:
|
if auto_lock_delay_ms is not None:
|
||||||
settings.auto_lock_delay_ms = auto_lock_delay_ms
|
settings.auto_lock_delay_ms = auto_lock_delay_ms
|
||||||
if display_rotation is not None:
|
if display_rotation is not None:
|
||||||
|
@ -6,7 +6,6 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypePassphraseSourceType = Literal[0, 1, 2]
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -20,17 +19,17 @@ class ApplySettings(p.MessageType):
|
|||||||
label: str = None,
|
label: str = None,
|
||||||
use_passphrase: bool = None,
|
use_passphrase: bool = None,
|
||||||
homescreen: bytes = None,
|
homescreen: bytes = None,
|
||||||
passphrase_source: EnumTypePassphraseSourceType = None,
|
|
||||||
auto_lock_delay_ms: int = None,
|
auto_lock_delay_ms: int = None,
|
||||||
display_rotation: int = None,
|
display_rotation: int = None,
|
||||||
|
passphrase_always_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.language = language
|
self.language = language
|
||||||
self.label = label
|
self.label = label
|
||||||
self.use_passphrase = use_passphrase
|
self.use_passphrase = use_passphrase
|
||||||
self.homescreen = homescreen
|
self.homescreen = homescreen
|
||||||
self.passphrase_source = passphrase_source
|
|
||||||
self.auto_lock_delay_ms = auto_lock_delay_ms
|
self.auto_lock_delay_ms = auto_lock_delay_ms
|
||||||
self.display_rotation = display_rotation
|
self.display_rotation = display_rotation
|
||||||
|
self.passphrase_always_on_device = passphrase_always_on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -39,7 +38,7 @@ class ApplySettings(p.MessageType):
|
|||||||
2: ('label', p.UnicodeType, 0),
|
2: ('label', p.UnicodeType, 0),
|
||||||
3: ('use_passphrase', p.BoolType, 0),
|
3: ('use_passphrase', p.BoolType, 0),
|
||||||
4: ('homescreen', p.BytesType, 0),
|
4: ('homescreen', p.BytesType, 0),
|
||||||
5: ('passphrase_source', p.EnumType("PassphraseSourceType", (0, 1, 2)), 0),
|
|
||||||
6: ('auto_lock_delay_ms', p.UVarintType, 0),
|
6: ('auto_lock_delay_ms', p.UVarintType, 0),
|
||||||
7: ('display_rotation', p.UVarintType, 0),
|
7: ('display_rotation', p.UVarintType, 0),
|
||||||
|
8: ('passphrase_always_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypeButtonRequestType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
|
EnumTypeButtonRequestType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -23,5 +23,5 @@ class ButtonRequest(p.MessageType):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('code', p.EnumType("ButtonRequestType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)), 0),
|
1: ('code', p.EnumType("ButtonRequestType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19)), 0),
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ Address = 10 # type: Literal[10]
|
|||||||
PublicKey = 11 # type: Literal[11]
|
PublicKey = 11 # type: Literal[11]
|
||||||
MnemonicWordCount = 12 # type: Literal[12]
|
MnemonicWordCount = 12 # type: Literal[12]
|
||||||
MnemonicInput = 13 # type: Literal[13]
|
MnemonicInput = 13 # type: Literal[13]
|
||||||
PassphraseType = 14 # type: Literal[14]
|
|
||||||
UnknownDerivationPath = 15 # type: Literal[15]
|
UnknownDerivationPath = 15 # type: Literal[15]
|
||||||
RecoveryHomepage = 16 # type: Literal[16]
|
RecoveryHomepage = 16 # type: Literal[16]
|
||||||
Success = 17 # type: Literal[17]
|
Success = 17 # type: Literal[17]
|
||||||
Warning = 18 # type: Literal[18]
|
Warning = 18 # type: Literal[18]
|
||||||
|
PassphraseEntry = 19 # type: Literal[19]
|
||||||
|
@ -19,3 +19,4 @@ Tezos = 13 # type: Literal[13]
|
|||||||
U2F = 14 # type: Literal[14]
|
U2F = 14 # type: Literal[14]
|
||||||
Shamir = 15 # type: Literal[15]
|
Shamir = 15 # type: Literal[15]
|
||||||
ShamirGroups = 16 # type: Literal[16]
|
ShamirGroups = 16 # type: Literal[16]
|
||||||
|
PassphraseEntry = 17 # type: Literal[17]
|
||||||
|
@ -10,5 +10,5 @@ if __debug__:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PassphraseStateAck(p.MessageType):
|
class Deprecated_PassphraseStateAck(p.MessageType):
|
||||||
MESSAGE_WIRE_TYPE = 78
|
MESSAGE_WIRE_TYPE = 78
|
@ -10,7 +10,7 @@ if __debug__:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PassphraseStateRequest(p.MessageType):
|
class Deprecated_PassphraseStateRequest(p.MessageType):
|
||||||
MESSAGE_WIRE_TYPE = 77
|
MESSAGE_WIRE_TYPE = 77
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
@ -6,7 +6,7 @@ if __debug__:
|
|||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
from typing_extensions import Literal # noqa: F401
|
from typing_extensions import Literal # noqa: F401
|
||||||
EnumTypeCapability = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
EnumTypeCapability = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
|
||||||
EnumTypeBackupType = Literal[0, 1, 2]
|
EnumTypeBackupType = Literal[0, 1, 2]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -32,7 +32,6 @@ class Features(p.MessageType):
|
|||||||
bootloader_hash: bytes = None,
|
bootloader_hash: bytes = None,
|
||||||
imported: bool = None,
|
imported: bool = None,
|
||||||
pin_cached: bool = None,
|
pin_cached: bool = None,
|
||||||
passphrase_cached: bool = None,
|
|
||||||
firmware_present: bool = None,
|
firmware_present: bool = None,
|
||||||
needs_backup: bool = None,
|
needs_backup: bool = None,
|
||||||
flags: int = None,
|
flags: int = None,
|
||||||
@ -50,6 +49,8 @@ class Features(p.MessageType):
|
|||||||
sd_card_present: bool = None,
|
sd_card_present: bool = None,
|
||||||
sd_protection: bool = None,
|
sd_protection: bool = None,
|
||||||
wipe_code_protection: bool = None,
|
wipe_code_protection: bool = None,
|
||||||
|
session_id: bytes = None,
|
||||||
|
passphrase_always_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.vendor = vendor
|
self.vendor = vendor
|
||||||
self.major_version = major_version
|
self.major_version = major_version
|
||||||
@ -66,7 +67,6 @@ class Features(p.MessageType):
|
|||||||
self.bootloader_hash = bootloader_hash
|
self.bootloader_hash = bootloader_hash
|
||||||
self.imported = imported
|
self.imported = imported
|
||||||
self.pin_cached = pin_cached
|
self.pin_cached = pin_cached
|
||||||
self.passphrase_cached = passphrase_cached
|
|
||||||
self.firmware_present = firmware_present
|
self.firmware_present = firmware_present
|
||||||
self.needs_backup = needs_backup
|
self.needs_backup = needs_backup
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
@ -84,6 +84,8 @@ class Features(p.MessageType):
|
|||||||
self.sd_card_present = sd_card_present
|
self.sd_card_present = sd_card_present
|
||||||
self.sd_protection = sd_protection
|
self.sd_protection = sd_protection
|
||||||
self.wipe_code_protection = wipe_code_protection
|
self.wipe_code_protection = wipe_code_protection
|
||||||
|
self.session_id = session_id
|
||||||
|
self.passphrase_always_on_device = passphrase_always_on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -103,7 +105,6 @@ class Features(p.MessageType):
|
|||||||
14: ('bootloader_hash', p.BytesType, 0),
|
14: ('bootloader_hash', p.BytesType, 0),
|
||||||
15: ('imported', p.BoolType, 0),
|
15: ('imported', p.BoolType, 0),
|
||||||
16: ('pin_cached', p.BoolType, 0),
|
16: ('pin_cached', p.BoolType, 0),
|
||||||
17: ('passphrase_cached', p.BoolType, 0),
|
|
||||||
18: ('firmware_present', p.BoolType, 0),
|
18: ('firmware_present', p.BoolType, 0),
|
||||||
19: ('needs_backup', p.BoolType, 0),
|
19: ('needs_backup', p.BoolType, 0),
|
||||||
20: ('flags', p.UVarintType, 0),
|
20: ('flags', p.UVarintType, 0),
|
||||||
@ -116,9 +117,11 @@ class Features(p.MessageType):
|
|||||||
27: ('unfinished_backup', p.BoolType, 0),
|
27: ('unfinished_backup', p.BoolType, 0),
|
||||||
28: ('no_backup', p.BoolType, 0),
|
28: ('no_backup', p.BoolType, 0),
|
||||||
29: ('recovery_mode', p.BoolType, 0),
|
29: ('recovery_mode', p.BoolType, 0),
|
||||||
30: ('capabilities', p.EnumType("Capability", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)), p.FLAG_REPEATED),
|
30: ('capabilities', p.EnumType("Capability", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)), p.FLAG_REPEATED),
|
||||||
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
||||||
32: ('sd_card_present', p.BoolType, 0),
|
32: ('sd_card_present', p.BoolType, 0),
|
||||||
33: ('sd_protection', p.BoolType, 0),
|
33: ('sd_protection', p.BoolType, 0),
|
||||||
34: ('wipe_code_protection', p.BoolType, 0),
|
34: ('wipe_code_protection', p.BoolType, 0),
|
||||||
|
35: ('session_id', p.BytesType, 0),
|
||||||
|
36: ('passphrase_always_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,12 @@ class Initialize(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
state: bytes = None,
|
session_id: bytes = None,
|
||||||
skip_passphrase: bool = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.state = state
|
self.session_id = session_id
|
||||||
self.skip_passphrase = skip_passphrase
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('state', p.BytesType, 0),
|
1: ('session_id', p.BytesType, 0),
|
||||||
2: ('skip_passphrase', p.BoolType, 0),
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ EntropyRequest = 35 # type: Literal[35]
|
|||||||
EntropyAck = 36 # type: Literal[36]
|
EntropyAck = 36 # type: Literal[36]
|
||||||
PassphraseRequest = 41 # type: Literal[41]
|
PassphraseRequest = 41 # type: Literal[41]
|
||||||
PassphraseAck = 42 # type: Literal[42]
|
PassphraseAck = 42 # type: Literal[42]
|
||||||
PassphraseStateRequest = 77 # type: Literal[77]
|
|
||||||
PassphraseStateAck = 78 # type: Literal[78]
|
|
||||||
RecoveryDevice = 45 # type: Literal[45]
|
RecoveryDevice = 45 # type: Literal[45]
|
||||||
WordRequest = 46 # type: Literal[46]
|
WordRequest = 46 # type: Literal[46]
|
||||||
WordAck = 47 # type: Literal[47]
|
WordAck = 47 # type: Literal[47]
|
||||||
@ -38,6 +36,8 @@ SdProtect = 79 # type: Literal[79]
|
|||||||
GetNextU2FCounter = 80 # type: Literal[80]
|
GetNextU2FCounter = 80 # type: Literal[80]
|
||||||
NextU2FCounter = 81 # type: Literal[81]
|
NextU2FCounter = 81 # type: Literal[81]
|
||||||
ChangeWipeCode = 82 # type: Literal[82]
|
ChangeWipeCode = 82 # type: Literal[82]
|
||||||
|
Deprecated_PassphraseStateRequest = 77 # type: Literal[77]
|
||||||
|
Deprecated_PassphraseStateAck = 78 # type: Literal[78]
|
||||||
FirmwareErase = 6 # type: Literal[6]
|
FirmwareErase = 6 # type: Literal[6]
|
||||||
FirmwareUpload = 7 # type: Literal[7]
|
FirmwareUpload = 7 # type: Literal[7]
|
||||||
FirmwareRequest = 8 # type: Literal[8]
|
FirmwareRequest = 8 # type: Literal[8]
|
||||||
|
@ -16,14 +16,17 @@ class PassphraseAck(p.MessageType):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
passphrase: str = None,
|
passphrase: str = None,
|
||||||
state: bytes = None,
|
_state: bytes = None,
|
||||||
|
on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.passphrase = passphrase
|
self.passphrase = passphrase
|
||||||
self.state = state
|
self._state = _state
|
||||||
|
self.on_device = on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('passphrase', p.UnicodeType, 0),
|
1: ('passphrase', p.UnicodeType, 0),
|
||||||
2: ('state', p.BytesType, 0),
|
2: ('_state', p.BytesType, 0),
|
||||||
|
3: ('on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ class PassphraseRequest(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
on_device: bool = None,
|
_on_device: bool = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.on_device = on_device
|
self._on_device = _on_device
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('on_device', p.BoolType, 0),
|
1: ('_on_device', p.BoolType, 0),
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
# Automatically generated by pb2py
|
|
||||||
# fmt: off
|
|
||||||
if False:
|
|
||||||
from typing_extensions import Literal
|
|
||||||
|
|
||||||
ASK = 0 # type: Literal[0]
|
|
||||||
DEVICE = 1 # type: Literal[1]
|
|
||||||
HOST = 2 # type: Literal[2]
|
|
@ -17,19 +17,13 @@ class Ping(p.MessageType):
|
|||||||
self,
|
self,
|
||||||
message: str = None,
|
message: str = None,
|
||||||
button_protection: bool = None,
|
button_protection: bool = None,
|
||||||
pin_protection: bool = None,
|
|
||||||
passphrase_protection: bool = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message = message
|
self.message = message
|
||||||
self.button_protection = button_protection
|
self.button_protection = button_protection
|
||||||
self.pin_protection = pin_protection
|
|
||||||
self.passphrase_protection = passphrase_protection
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('message', p.UnicodeType, 0),
|
1: ('message', p.UnicodeType, 0),
|
||||||
2: ('button_protection', p.BoolType, 0),
|
2: ('button_protection', p.BoolType, 0),
|
||||||
3: ('pin_protection', p.BoolType, 0),
|
|
||||||
4: ('passphrase_protection', p.BoolType, 0),
|
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,8 @@ from .DebugLinkState import DebugLinkState
|
|||||||
from .DebugLinkStop import DebugLinkStop
|
from .DebugLinkStop import DebugLinkStop
|
||||||
from .DebugMoneroDiagAck import DebugMoneroDiagAck
|
from .DebugMoneroDiagAck import DebugMoneroDiagAck
|
||||||
from .DebugMoneroDiagRequest import DebugMoneroDiagRequest
|
from .DebugMoneroDiagRequest import DebugMoneroDiagRequest
|
||||||
|
from .Deprecated_PassphraseStateAck import Deprecated_PassphraseStateAck
|
||||||
|
from .Deprecated_PassphraseStateRequest import Deprecated_PassphraseStateRequest
|
||||||
from .ECDHSessionKey import ECDHSessionKey
|
from .ECDHSessionKey import ECDHSessionKey
|
||||||
from .Entropy import Entropy
|
from .Entropy import Entropy
|
||||||
from .EntropyAck import EntropyAck
|
from .EntropyAck import EntropyAck
|
||||||
@ -196,8 +198,6 @@ from .NEMTransfer import NEMTransfer
|
|||||||
from .NextU2FCounter import NextU2FCounter
|
from .NextU2FCounter import NextU2FCounter
|
||||||
from .PassphraseAck import PassphraseAck
|
from .PassphraseAck import PassphraseAck
|
||||||
from .PassphraseRequest import PassphraseRequest
|
from .PassphraseRequest import PassphraseRequest
|
||||||
from .PassphraseStateAck import PassphraseStateAck
|
|
||||||
from .PassphraseStateRequest import PassphraseStateRequest
|
|
||||||
from .PinMatrixAck import PinMatrixAck
|
from .PinMatrixAck import PinMatrixAck
|
||||||
from .PinMatrixRequest import PinMatrixRequest
|
from .PinMatrixRequest import PinMatrixRequest
|
||||||
from .Ping import Ping
|
from .Ping import Ping
|
||||||
@ -283,7 +283,6 @@ from . import NEMModificationType
|
|||||||
from . import NEMMosaicLevy
|
from . import NEMMosaicLevy
|
||||||
from . import NEMSupplyChangeType
|
from . import NEMSupplyChangeType
|
||||||
from . import OutputScriptType
|
from . import OutputScriptType
|
||||||
from . import PassphraseSourceType
|
|
||||||
from . import PinMatrixRequestType
|
from . import PinMatrixRequestType
|
||||||
from . import RecoveryDeviceType
|
from . import RecoveryDeviceType
|
||||||
from . import RequestType
|
from . import RequestType
|
||||||
|
@ -20,6 +20,7 @@ import click
|
|||||||
from mnemonic import Mnemonic
|
from mnemonic import Mnemonic
|
||||||
|
|
||||||
from . import device
|
from . import device
|
||||||
|
from .client import PASSPHRASE_ON_DEVICE
|
||||||
from .exceptions import Cancelled
|
from .exceptions import Cancelled
|
||||||
from .messages import PinMatrixRequestType, WordRequestType
|
from .messages import PinMatrixRequestType, WordRequestType
|
||||||
|
|
||||||
@ -58,10 +59,11 @@ def prompt(*args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class ClickUI:
|
class ClickUI:
|
||||||
def __init__(self, always_prompt=False):
|
def __init__(self, always_prompt=False, passphrase_on_host=False):
|
||||||
self.pinmatrix_shown = False
|
self.pinmatrix_shown = False
|
||||||
self.prompt_shown = False
|
self.prompt_shown = False
|
||||||
self.always_prompt = always_prompt
|
self.always_prompt = always_prompt
|
||||||
|
self.passphrase_on_host = passphrase_on_host
|
||||||
|
|
||||||
def button_request(self, code):
|
def button_request(self, code):
|
||||||
if not self.prompt_shown:
|
if not self.prompt_shown:
|
||||||
@ -98,7 +100,10 @@ class ClickUI:
|
|||||||
else:
|
else:
|
||||||
return pin
|
return pin
|
||||||
|
|
||||||
def get_passphrase(self):
|
def get_passphrase(self, available_on_device):
|
||||||
|
if available_on_device and not self.passphrase_on_host:
|
||||||
|
return PASSPHRASE_ON_DEVICE
|
||||||
|
|
||||||
if os.getenv("PASSPHRASE") is not None:
|
if os.getenv("PASSPHRASE") is not None:
|
||||||
echo("Passphrase required. Using PASSPHRASE environment variable.")
|
echo("Passphrase required. Using PASSPHRASE environment variable.")
|
||||||
return os.getenv("PASSPHRASE")
|
return os.getenv("PASSPHRASE")
|
||||||
|
1
tests/.gitignore
vendored
1
tests/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
junit.xml
|
junit.xml
|
||||||
|
trezor.log
|
||||||
|
@ -20,8 +20,7 @@ import pytest
|
|||||||
|
|
||||||
from trezorlib import debuglink, log
|
from trezorlib import debuglink, log
|
||||||
from trezorlib.debuglink import TrezorClientDebugLink
|
from trezorlib.debuglink import TrezorClientDebugLink
|
||||||
from trezorlib.device import apply_settings, wipe as wipe_device
|
from trezorlib.device import wipe as wipe_device
|
||||||
from trezorlib.messages.PassphraseSourceType import HOST as PASSPHRASE_ON_HOST
|
|
||||||
from trezorlib.transport import enumerate_devices, get_transport
|
from trezorlib.transport import enumerate_devices, get_transport
|
||||||
|
|
||||||
from . import ui_tests
|
from . import ui_tests
|
||||||
@ -36,8 +35,8 @@ def get_device():
|
|||||||
try:
|
try:
|
||||||
transport = get_transport(path)
|
transport = get_transport(path)
|
||||||
return TrezorClientDebugLink(transport, auto_interact=not interact)
|
return TrezorClientDebugLink(transport, auto_interact=not interact)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
raise RuntimeError("Failed to open debuglink for {}".format(path)) from e
|
pytest.exit("Failed to open debuglink for {}".format(path), 3)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
devices = enumerate_devices()
|
devices = enumerate_devices()
|
||||||
@ -47,7 +46,7 @@ def get_device():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("No debuggable device found")
|
pytest.exit("No debuggable device found", 3)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
@ -130,8 +129,6 @@ def client(request):
|
|||||||
needs_backup=setup_params["needs_backup"],
|
needs_backup=setup_params["needs_backup"],
|
||||||
no_backup=setup_params["no_backup"],
|
no_backup=setup_params["no_backup"],
|
||||||
)
|
)
|
||||||
if setup_params["passphrase"] and client.features.model != "1":
|
|
||||||
apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
|
|
||||||
if setup_params["pin"]:
|
if setup_params["pin"]:
|
||||||
# ClearSession locks the device. We only do that if the PIN is set.
|
# ClearSession locks the device. We only do that if the PIN is set.
|
||||||
|
@ -20,7 +20,7 @@ from trezorlib import device, messages
|
|||||||
class TestBasic:
|
class TestBasic:
|
||||||
def test_features(self, client):
|
def test_features(self, client):
|
||||||
f0 = client.features
|
f0 = client.features
|
||||||
f1 = client.call(messages.Initialize())
|
f1 = client.call(messages.Initialize(f0.session_id))
|
||||||
assert f0 == f1
|
assert f0 == f1
|
||||||
|
|
||||||
def test_ping(self, client):
|
def test_ping(self, client):
|
||||||
|
@ -34,7 +34,7 @@ class TestDebuglink:
|
|||||||
|
|
||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12, pin="1234", passphrase=True)
|
@pytest.mark.setup_client(mnemonic=MNEMONIC12, pin="1234", passphrase=True)
|
||||||
def test_pin(self, client):
|
def test_pin(self, client):
|
||||||
resp = client.call_raw(messages.Ping(message="test", pin_protection=True))
|
resp = client.call_raw(messages.GetAddress())
|
||||||
assert isinstance(resp, messages.PinMatrixRequest)
|
assert isinstance(resp, messages.PinMatrixRequest)
|
||||||
|
|
||||||
pin, matrix = client.debug.read_pin()
|
pin, matrix = client.debug.read_pin()
|
||||||
@ -43,4 +43,7 @@ class TestDebuglink:
|
|||||||
|
|
||||||
pin_encoded = client.debug.read_pin_encoded()
|
pin_encoded = client.debug.read_pin_encoded()
|
||||||
resp = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
resp = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||||
assert isinstance(resp, messages.Success)
|
assert isinstance(resp, messages.PassphraseRequest)
|
||||||
|
|
||||||
|
resp = client.call_raw(messages.PassphraseAck(passphrase=""))
|
||||||
|
assert isinstance(resp, messages.Address)
|
||||||
|
@ -18,7 +18,7 @@ import time
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import device, messages as proto
|
from trezorlib import btc, device, messages as proto
|
||||||
|
|
||||||
EXPECTED_RESPONSES_NOPIN = [proto.ButtonRequest(), proto.Success(), proto.Features()]
|
EXPECTED_RESPONSES_NOPIN = [proto.ButtonRequest(), proto.Success(), proto.Features()]
|
||||||
EXPECTED_RESPONSES_PIN = [proto.PinMatrixRequest()] + EXPECTED_RESPONSES_NOPIN
|
EXPECTED_RESPONSES_PIN = [proto.PinMatrixRequest()] + EXPECTED_RESPONSES_NOPIN
|
||||||
@ -91,13 +91,13 @@ class TestMsgApplysettings:
|
|||||||
time.sleep(0.1) # sleep less than auto-lock delay
|
time.sleep(0.1) # sleep less than auto-lock delay
|
||||||
with client:
|
with client:
|
||||||
# No PIN protection is required.
|
# No PIN protection is required.
|
||||||
client.set_expected_responses([proto.Success()])
|
client.set_expected_responses([proto.Address()])
|
||||||
client.ping(msg="", pin_protection=True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
time.sleep(10.1) # sleep more than auto-lock delay
|
time.sleep(10.1) # sleep more than auto-lock delay
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()])
|
client.set_expected_responses([proto.PinMatrixRequest(), proto.Address()])
|
||||||
client.ping(msg="", pin_protection=True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
@pytest.mark.skip_t2
|
@pytest.mark.skip_t2
|
||||||
def test_apply_minimal_auto_lock_delay(self, client):
|
def test_apply_minimal_auto_lock_delay(self, client):
|
||||||
@ -114,16 +114,16 @@ class TestMsgApplysettings:
|
|||||||
time.sleep(0.1) # sleep less than auto-lock delay
|
time.sleep(0.1) # sleep less than auto-lock delay
|
||||||
with client:
|
with client:
|
||||||
# No PIN protection is required.
|
# No PIN protection is required.
|
||||||
client.set_expected_responses([proto.Success()])
|
client.set_expected_responses([proto.Address()])
|
||||||
client.ping(msg="", pin_protection=True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
time.sleep(2) # sleep less than the minimal auto-lock delay
|
time.sleep(2) # sleep less than the minimal auto-lock delay
|
||||||
with client:
|
with client:
|
||||||
# No PIN protection is required.
|
# No PIN protection is required.
|
||||||
client.set_expected_responses([proto.Success()])
|
client.set_expected_responses([proto.Address()])
|
||||||
client.ping(msg="", pin_protection=True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
time.sleep(10.1) # sleep more than the minimal auto-lock delay
|
time.sleep(10.1) # sleep more than the minimal auto-lock delay
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()])
|
client.set_expected_responses([proto.PinMatrixRequest(), proto.Address()])
|
||||||
client.ping(msg="", pin_protection=True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
@ -119,10 +119,7 @@ def test_cardano_sign_tx(
|
|||||||
inputs = [cardano.create_input(i) for i in inputs]
|
inputs = [cardano.create_input(i) for i in inputs]
|
||||||
outputs = [cardano.create_output(o) for o in outputs]
|
outputs = [cardano.create_output(o) for o in outputs]
|
||||||
|
|
||||||
expected_responses = [
|
expected_responses = [messages.PassphraseRequest()]
|
||||||
messages.PassphraseRequest(),
|
|
||||||
messages.PassphraseStateRequest(),
|
|
||||||
]
|
|
||||||
expected_responses += [
|
expected_responses += [
|
||||||
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
|
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
|
||||||
]
|
]
|
||||||
|
@ -225,5 +225,5 @@ def test_set_pin_to_wipe_code(client):
|
|||||||
# Check that there is no PIN protection.
|
# Check that there is no PIN protection.
|
||||||
client.init_device()
|
client.init_device()
|
||||||
assert client.features.pin_protection is False
|
assert client.features.pin_protection is False
|
||||||
ret = client.call_raw(messages.Ping(pin_protection=True))
|
resp = client.call_raw(messages.GetAddress())
|
||||||
assert isinstance(ret, messages.Success)
|
assert isinstance(resp, messages.Address)
|
||||||
|
@ -29,8 +29,8 @@ class TestMsgChangepin:
|
|||||||
assert features.pin_protection is False
|
assert features.pin_protection is False
|
||||||
|
|
||||||
# Check that there's no PIN protection
|
# Check that there's no PIN protection
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.Success)
|
assert isinstance(ret, proto.Address)
|
||||||
|
|
||||||
# Let's set new PIN
|
# Let's set new PIN
|
||||||
ret = client.call_raw(proto.ChangePin())
|
ret = client.call_raw(proto.ChangePin())
|
||||||
@ -66,7 +66,7 @@ class TestMsgChangepin:
|
|||||||
assert features.pin_protection is True
|
assert features.pin_protection is True
|
||||||
|
|
||||||
# Check that there's PIN protection
|
# Check that there's PIN protection
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.PinMatrixRequest)
|
assert isinstance(ret, proto.PinMatrixRequest)
|
||||||
client.call_raw(proto.Cancel())
|
client.call_raw(proto.Cancel())
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class TestMsgChangepin:
|
|||||||
assert features.pin_protection is True
|
assert features.pin_protection is True
|
||||||
|
|
||||||
# Check that there's PIN protection
|
# Check that there's PIN protection
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.PinMatrixRequest)
|
assert isinstance(ret, proto.PinMatrixRequest)
|
||||||
client.call_raw(proto.Cancel())
|
client.call_raw(proto.Cancel())
|
||||||
|
|
||||||
@ -135,16 +135,16 @@ class TestMsgChangepin:
|
|||||||
# Check that there's no PIN protection now
|
# Check that there's no PIN protection now
|
||||||
features = client.call_raw(proto.Initialize())
|
features = client.call_raw(proto.Initialize())
|
||||||
assert features.pin_protection is False
|
assert features.pin_protection is False
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.Success)
|
assert isinstance(ret, proto.Address)
|
||||||
|
|
||||||
def test_set_failed(self, client):
|
def test_set_failed(self, client):
|
||||||
features = client.call_raw(proto.Initialize())
|
features = client.call_raw(proto.Initialize())
|
||||||
assert features.pin_protection is False
|
assert features.pin_protection is False
|
||||||
|
|
||||||
# Check that there's no PIN protection
|
# Check that there's no PIN protection
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.Success)
|
assert isinstance(ret, proto.Address)
|
||||||
|
|
||||||
# Let's set new PIN
|
# Let's set new PIN
|
||||||
ret = client.call_raw(proto.ChangePin())
|
ret = client.call_raw(proto.ChangePin())
|
||||||
@ -170,8 +170,8 @@ class TestMsgChangepin:
|
|||||||
# Check that there's still no PIN protection now
|
# Check that there's still no PIN protection now
|
||||||
features = client.call_raw(proto.Initialize())
|
features = client.call_raw(proto.Initialize())
|
||||||
assert features.pin_protection is False
|
assert features.pin_protection is False
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.Success)
|
assert isinstance(ret, proto.Address)
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=True)
|
@pytest.mark.setup_client(pin=True)
|
||||||
def test_set_failed_2(self, client):
|
def test_set_failed_2(self, client):
|
||||||
@ -211,8 +211,8 @@ class TestMsgChangepin:
|
|||||||
|
|
||||||
def check_pin(self, client, pin):
|
def check_pin(self, client, pin):
|
||||||
client.clear_session()
|
client.clear_session()
|
||||||
ret = client.call_raw(proto.Ping(pin_protection=True))
|
ret = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(ret, proto.PinMatrixRequest)
|
assert isinstance(ret, proto.PinMatrixRequest)
|
||||||
pin_encoded = client.debug.encode_pin(pin)
|
pin_encoded = client.debug.encode_pin(pin)
|
||||||
ret = client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
|
ret = client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
|
||||||
assert isinstance(ret, proto.Success)
|
assert isinstance(ret, proto.Address)
|
||||||
|
@ -16,78 +16,41 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import messages as proto
|
from trezorlib import messages
|
||||||
|
from trezorlib.btc import get_public_node
|
||||||
|
from trezorlib.tools import parse_path
|
||||||
|
|
||||||
|
ADDRESS_N = parse_path("44'/0'/0'")
|
||||||
|
XPUB = "xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t2
|
@pytest.mark.skip_ui
|
||||||
class TestMsgClearsession:
|
@pytest.mark.setup_client(pin=True, passphrase=True)
|
||||||
@pytest.mark.setup_client(pin=True, passphrase=True)
|
def test_clear_session(client):
|
||||||
def test_clearsession(self, client):
|
if client.features.model == "1":
|
||||||
|
init_responses = [messages.PinMatrixRequest(), messages.PassphraseRequest()]
|
||||||
|
else:
|
||||||
|
init_responses = [messages.PassphraseRequest()]
|
||||||
|
|
||||||
|
cached_responses = [messages.PublicKey()]
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(init_responses + cached_responses)
|
||||||
[
|
assert get_public_node(client, ADDRESS_N).xpub == XPUB
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.PinMatrixRequest(),
|
|
||||||
proto.PassphraseRequest(),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
# pin and passphrase are cached
|
# pin and passphrase are cached
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(cached_responses)
|
||||||
[
|
assert get_public_node(client, ADDRESS_N).xpub == XPUB
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
client.clear_session()
|
client.clear_session()
|
||||||
|
|
||||||
# session cache is cleared
|
# session cache is cleared
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(init_responses + cached_responses)
|
||||||
[
|
assert get_public_node(client, ADDRESS_N).xpub == XPUB
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.PinMatrixRequest(),
|
|
||||||
proto.PassphraseRequest(),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
# pin and passphrase are cached
|
# pin and passphrase are cached
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(cached_responses)
|
||||||
[
|
assert get_public_node(client, ADDRESS_N).xpub == XPUB
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
@ -18,7 +18,6 @@ import pytest
|
|||||||
|
|
||||||
from trezorlib import btc, debuglink, device
|
from trezorlib import btc, debuglink, device
|
||||||
from trezorlib.messages import BackupType
|
from trezorlib.messages import BackupType
|
||||||
from trezorlib.messages.PassphraseSourceType import HOST as PASSPHRASE_ON_HOST
|
|
||||||
|
|
||||||
from ..common import (
|
from ..common import (
|
||||||
MNEMONIC12,
|
MNEMONIC12,
|
||||||
@ -53,8 +52,6 @@ class TestDeviceLoad:
|
|||||||
passphrase_protection=True,
|
passphrase_protection=True,
|
||||||
label="test",
|
label="test",
|
||||||
)
|
)
|
||||||
if client.features.model == "T":
|
|
||||||
device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
client.set_passphrase("passphrase")
|
client.set_passphrase("passphrase")
|
||||||
state = client.debug.state()
|
state = client.debug.state()
|
||||||
assert state.mnemonic_secret == MNEMONIC12.encode()
|
assert state.mnemonic_secret == MNEMONIC12.encode()
|
||||||
@ -108,7 +105,6 @@ class TestDeviceLoad:
|
|||||||
u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko"
|
u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko"
|
||||||
)
|
)
|
||||||
|
|
||||||
device.wipe(client)
|
|
||||||
debuglink.load_device(
|
debuglink.load_device(
|
||||||
client,
|
client,
|
||||||
mnemonic=words_nfkd,
|
mnemonic=words_nfkd,
|
||||||
@ -118,8 +114,6 @@ class TestDeviceLoad:
|
|||||||
language="en-US",
|
language="en-US",
|
||||||
skip_checksum=True,
|
skip_checksum=True,
|
||||||
)
|
)
|
||||||
if client.features.model == "T":
|
|
||||||
device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
client.set_passphrase(passphrase_nfkd)
|
client.set_passphrase(passphrase_nfkd)
|
||||||
address_nfkd = btc.get_address(client, "Bitcoin", [])
|
address_nfkd = btc.get_address(client, "Bitcoin", [])
|
||||||
|
|
||||||
@ -133,8 +127,6 @@ class TestDeviceLoad:
|
|||||||
language="en-US",
|
language="en-US",
|
||||||
skip_checksum=True,
|
skip_checksum=True,
|
||||||
)
|
)
|
||||||
if client.features.model == "T":
|
|
||||||
device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
client.set_passphrase(passphrase_nfc)
|
client.set_passphrase(passphrase_nfc)
|
||||||
address_nfc = btc.get_address(client, "Bitcoin", [])
|
address_nfc = btc.get_address(client, "Bitcoin", [])
|
||||||
|
|
||||||
@ -148,8 +140,6 @@ class TestDeviceLoad:
|
|||||||
language="en-US",
|
language="en-US",
|
||||||
skip_checksum=True,
|
skip_checksum=True,
|
||||||
)
|
)
|
||||||
if client.features.model == "T":
|
|
||||||
device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
client.set_passphrase(passphrase_nfkc)
|
client.set_passphrase(passphrase_nfkc)
|
||||||
address_nfkc = btc.get_address(client, "Bitcoin", [])
|
address_nfkc = btc.get_address(client, "Bitcoin", [])
|
||||||
|
|
||||||
@ -163,8 +153,6 @@ class TestDeviceLoad:
|
|||||||
language="en-US",
|
language="en-US",
|
||||||
skip_checksum=True,
|
skip_checksum=True,
|
||||||
)
|
)
|
||||||
if client.features.model == "T":
|
|
||||||
device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST)
|
|
||||||
client.set_passphrase(passphrase_nfd)
|
client.set_passphrase(passphrase_nfd)
|
||||||
address_nfd = btc.get_address(client, "Bitcoin", [])
|
address_nfd = btc.get_address(client, "Bitcoin", [])
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ import pytest
|
|||||||
from trezorlib import messages as proto
|
from trezorlib import messages as proto
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t2
|
|
||||||
class TestMsgPing:
|
class TestMsgPing:
|
||||||
|
@pytest.mark.skip_ui
|
||||||
@pytest.mark.setup_client(pin=True, passphrase=True)
|
@pytest.mark.setup_client(pin=True, passphrase=True)
|
||||||
def test_ping(self, client):
|
def test_ping(self, client):
|
||||||
with client:
|
with client:
|
||||||
@ -37,48 +37,3 @@ class TestMsgPing:
|
|||||||
)
|
)
|
||||||
res = client.ping("random data", button_protection=True)
|
res = client.ping("random data", button_protection=True)
|
||||||
assert res == "random data"
|
assert res == "random data"
|
||||||
|
|
||||||
with client:
|
|
||||||
client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()])
|
|
||||||
res = client.ping("random data", pin_protection=True)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
with client:
|
|
||||||
client.set_expected_responses([proto.PassphraseRequest(), proto.Success()])
|
|
||||||
res = client.ping("random data", passphrase_protection=True)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=True, passphrase=True)
|
|
||||||
def test_ping_caching(self, client):
|
|
||||||
with client:
|
|
||||||
client.set_expected_responses(
|
|
||||||
[
|
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.PinMatrixRequest(),
|
|
||||||
proto.PassphraseRequest(),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
with client:
|
|
||||||
# pin and passphrase are cached
|
|
||||||
client.set_expected_responses(
|
|
||||||
[
|
|
||||||
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
res = client.ping(
|
|
||||||
"random data",
|
|
||||||
button_protection=True,
|
|
||||||
pin_protection=True,
|
|
||||||
passphrase_protection=True,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
@ -85,7 +85,7 @@ class TestMsgRecoverydevice:
|
|||||||
assert client.features.passphrase_protection is True
|
assert client.features.passphrase_protection is True
|
||||||
|
|
||||||
# Do passphrase-protected action, PassphraseRequest should be raised
|
# Do passphrase-protected action, PassphraseRequest should be raised
|
||||||
resp = client.call_raw(proto.Ping(passphrase_protection=True))
|
resp = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(resp, proto.PassphraseRequest)
|
assert isinstance(resp, proto.PassphraseRequest)
|
||||||
client.call_raw(proto.Cancel())
|
client.call_raw(proto.Cancel())
|
||||||
|
|
||||||
@ -136,13 +136,9 @@ class TestMsgRecoverydevice:
|
|||||||
assert client.features.pin_protection is False
|
assert client.features.pin_protection is False
|
||||||
assert client.features.passphrase_protection is False
|
assert client.features.passphrase_protection is False
|
||||||
|
|
||||||
# Do passphrase-protected action, PassphraseRequest should NOT be raised
|
# Do pin & passphrase-protected action, PassphraseRequest should NOT be raised
|
||||||
resp = client.call_raw(proto.Ping(passphrase_protection=True))
|
resp = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(resp, proto.Success)
|
assert isinstance(resp, proto.Address)
|
||||||
|
|
||||||
# Do PIN-protected action, PinRequest should NOT be raised
|
|
||||||
resp = client.call_raw(proto.Ping(pin_protection=True))
|
|
||||||
assert isinstance(resp, proto.Success)
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
def test_word_fail(self, client):
|
def test_word_fail(self, client):
|
||||||
|
@ -87,13 +87,9 @@ class TestMsgResetDevice:
|
|||||||
assert resp.pin_protection is False
|
assert resp.pin_protection is False
|
||||||
assert resp.passphrase_protection is False
|
assert resp.passphrase_protection is False
|
||||||
|
|
||||||
# Do passphrase-protected action, PassphraseRequest should NOT be raised
|
# Do pin & passphrase-protected action, PassphraseRequest should NOT be raised
|
||||||
resp = client.call_raw(proto.Ping(passphrase_protection=True))
|
resp = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(resp, proto.Success)
|
assert isinstance(resp, proto.Address)
|
||||||
|
|
||||||
# Do PIN-protected action, PinRequest should NOT be raised
|
|
||||||
resp = client.call_raw(proto.Ping(pin_protection=True))
|
|
||||||
assert isinstance(resp, proto.Success)
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
def test_reset_device_pin(self, client):
|
def test_reset_device_pin(self, client):
|
||||||
@ -180,7 +176,7 @@ class TestMsgResetDevice:
|
|||||||
assert resp.passphrase_protection is True
|
assert resp.passphrase_protection is True
|
||||||
|
|
||||||
# Do passphrase-protected action, PassphraseRequest should be raised
|
# Do passphrase-protected action, PassphraseRequest should be raised
|
||||||
resp = client.call_raw(proto.Ping(passphrase_protection=True))
|
resp = client.call_raw(proto.GetAddress())
|
||||||
assert isinstance(resp, proto.PassphraseRequest)
|
assert isinstance(resp, proto.PassphraseRequest)
|
||||||
client.call_raw(proto.Cancel())
|
client.call_raw(proto.Cancel())
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import time
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import messages as proto
|
from trezorlib import btc, messages as proto
|
||||||
from trezorlib.exceptions import PinException
|
from trezorlib.exceptions import PinException
|
||||||
|
|
||||||
# FIXME TODO Add passphrase tests
|
# FIXME TODO Add passphrase tests
|
||||||
@ -26,84 +26,36 @@ from trezorlib.exceptions import PinException
|
|||||||
|
|
||||||
@pytest.mark.skip_t2
|
@pytest.mark.skip_t2
|
||||||
class TestProtectCall:
|
class TestProtectCall:
|
||||||
def _some_protected_call(self, client, button, pin, passphrase):
|
def _some_protected_call(self, client):
|
||||||
# This method perform any call which have protection in the device
|
# This method perform any call which have protection in the device
|
||||||
res = client.ping(
|
res = btc.get_address(client, "Testnet", [0])
|
||||||
"random data",
|
assert res == "mndoQDWatQhfeQbprzZxD43mZ75Z94D6vz"
|
||||||
button_protection=button,
|
|
||||||
pin_protection=pin,
|
|
||||||
passphrase_protection=passphrase,
|
|
||||||
)
|
|
||||||
assert res == "random data"
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
|
||||||
def test_expected_responses(self, client):
|
|
||||||
# This is low-level test of set_expected_responses()
|
|
||||||
# feature of debugging client
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError), client:
|
|
||||||
# Scenario 1 - Received unexpected message
|
|
||||||
client.set_expected_responses([])
|
|
||||||
self._some_protected_call(client, True, True, True)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError), client:
|
|
||||||
# Scenario 2 - Received other than expected message
|
|
||||||
client.set_expected_responses([proto.Success()])
|
|
||||||
self._some_protected_call(client, True, True, True)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError), client:
|
|
||||||
# Scenario 3 - Not received expected message
|
|
||||||
client.set_expected_responses(
|
|
||||||
[proto.ButtonRequest(), proto.Success(), proto.Success()]
|
|
||||||
) # This is expected, but not received
|
|
||||||
self._some_protected_call(client, True, False, False)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError), client:
|
|
||||||
# Scenario 4 - Received what expected
|
|
||||||
client.set_expected_responses(
|
|
||||||
[
|
|
||||||
proto.ButtonRequest(),
|
|
||||||
proto.PinMatrixRequest(),
|
|
||||||
proto.PassphraseRequest(),
|
|
||||||
proto.Success(message="random data"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
self._some_protected_call(client, True, True, True)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError), client:
|
|
||||||
# Scenario 5 - Failed message by field filter
|
|
||||||
client.set_expected_responses(
|
|
||||||
[proto.ButtonRequest(), proto.Success(message="wrong data")]
|
|
||||||
)
|
|
||||||
self._some_protected_call(client, True, True, True)
|
|
||||||
|
|
||||||
def test_no_protection(self, client):
|
def test_no_protection(self, client):
|
||||||
with client:
|
with client:
|
||||||
assert client.debug.read_pin()[0] is None
|
assert client.debug.read_pin()[0] is None
|
||||||
client.set_expected_responses([proto.Success()])
|
client.set_expected_responses([proto.Address()])
|
||||||
self._some_protected_call(client, False, True, True)
|
self._some_protected_call(client)
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
@pytest.mark.setup_client(pin="1234")
|
||||||
def test_pin(self, client):
|
def test_pin(self, client):
|
||||||
with client:
|
with client:
|
||||||
assert client.debug.read_pin()[0] == "1234"
|
assert client.debug.read_pin()[0] == "1234"
|
||||||
client.setup_debuglink(button=True, pin_correct=True)
|
client.setup_debuglink(button=True, pin_correct=True)
|
||||||
client.set_expected_responses(
|
client.set_expected_responses([proto.PinMatrixRequest(), proto.Address()])
|
||||||
[proto.ButtonRequest(), proto.PinMatrixRequest(), proto.Success()]
|
self._some_protected_call(client)
|
||||||
)
|
|
||||||
self._some_protected_call(client, True, True, False)
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
@pytest.mark.setup_client(pin="1234")
|
||||||
def test_incorrect_pin(self, client):
|
def test_incorrect_pin(self, client):
|
||||||
client.setup_debuglink(button=True, pin_correct=False)
|
client.setup_debuglink(button=True, pin_correct=False)
|
||||||
with pytest.raises(PinException):
|
with pytest.raises(PinException):
|
||||||
self._some_protected_call(client, False, True, False)
|
self._some_protected_call(client)
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
@pytest.mark.setup_client(pin="1234")
|
||||||
def test_cancelled_pin(self, client):
|
def test_cancelled_pin(self, client):
|
||||||
client.setup_debuglink(button=True, pin_correct=False) # PIN cancel
|
client.setup_debuglink(button=True, pin_correct=False) # PIN cancel
|
||||||
with pytest.raises(PinException):
|
with pytest.raises(PinException):
|
||||||
self._some_protected_call(client, False, True, False)
|
self._some_protected_call(client)
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
@pytest.mark.setup_client(pin="1234", passphrase=True)
|
||||||
def test_exponential_backoff_with_reboot(self, client):
|
def test_exponential_backoff_with_reboot(self, client):
|
||||||
@ -126,5 +78,5 @@ class TestProtectCall:
|
|||||||
for attempt in range(1, 4):
|
for attempt in range(1, 4):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
with pytest.raises(PinException):
|
with pytest.raises(PinException):
|
||||||
self._some_protected_call(client, False, True, False)
|
self._some_protected_call(client)
|
||||||
test_backoff(attempt, start)
|
test_backoff(attempt, start)
|
||||||
|
@ -63,18 +63,10 @@ class TestProtectionLevels:
|
|||||||
)
|
)
|
||||||
device.change_pin(client)
|
device.change_pin(client)
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=True, passphrase=True)
|
|
||||||
def test_ping(self, client):
|
def test_ping(self, client):
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(
|
client.set_expected_responses([proto.ButtonRequest(), proto.Success()])
|
||||||
[
|
client.ping("msg", True)
|
||||||
proto.ButtonRequest(),
|
|
||||||
proto.PinMatrixRequest(),
|
|
||||||
proto.PassphraseRequest(),
|
|
||||||
proto.Success(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
client.ping("msg", True, True, True)
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=True, passphrase=True)
|
@pytest.mark.setup_client(pin=True, passphrase=True)
|
||||||
def test_get_entropy(self, client):
|
def test_get_entropy(self, client):
|
||||||
@ -269,29 +261,21 @@ class TestProtectionLevels:
|
|||||||
assert client.features.pin_cached is False
|
assert client.features.pin_cached is False
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(
|
client.set_expected_responses([proto.PinMatrixRequest(), proto.Address()])
|
||||||
[proto.ButtonRequest(), proto.PinMatrixRequest(), proto.Success()]
|
btc.get_address(client, "Testnet", [0])
|
||||||
)
|
|
||||||
client.ping("msg", True, True, True)
|
|
||||||
|
|
||||||
client.init_device()
|
client.init_device()
|
||||||
assert client.features.pin_cached is True
|
assert client.features.pin_cached is True
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses([proto.ButtonRequest(), proto.Success()])
|
client.set_expected_responses([proto.Address()])
|
||||||
client.ping("msg", True, True, True)
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
@pytest.mark.setup_client(passphrase=True)
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
def test_passphrase_cached(self, client):
|
def test_passphrase_cached(self, client):
|
||||||
assert client.features.passphrase_cached is False
|
with client:
|
||||||
|
client.set_expected_responses([proto.PassphraseRequest(), proto.Address()])
|
||||||
|
btc.get_address(client, "Testnet", [0])
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(
|
client.set_expected_responses([proto.Address()])
|
||||||
[proto.ButtonRequest(), proto.PassphraseRequest(), proto.Success()]
|
btc.get_address(client, "Testnet", [0])
|
||||||
)
|
|
||||||
client.ping("msg", True, True, True)
|
|
||||||
|
|
||||||
features = client.call(proto.GetFeatures())
|
|
||||||
assert features.passphrase_cached is True
|
|
||||||
with client:
|
|
||||||
client.set_expected_responses([proto.ButtonRequest(), proto.Success()])
|
|
||||||
client.ping("msg", True, True, True)
|
|
||||||
|
280
tests/device_tests/test_session_id_and_passphrase.py
Normal file
280
tests/device_tests/test_session_id_and_passphrase.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# This file is part of the Trezor project.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3
|
||||||
|
# as published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the License along with this library.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from trezorlib import messages
|
||||||
|
from trezorlib.messages import FailureType
|
||||||
|
from trezorlib.tools import parse_path
|
||||||
|
|
||||||
|
XPUB_PASSPHRASE_A = "xpub6CekxGcnqnJ6osfY4Rrq7W5ogFtR54KUvz4H16XzaQuukMFZCGebEpVznfq4yFcKEmYyShwj2UKjL7CazuNSuhdkofF4mHabHkLxCMVvsqG"
|
||||||
|
XPUB_PASSPHRASE_NONE = "xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy"
|
||||||
|
XPUB_CARDANO_PASSPHRASE_B = "d80e770f6dfc3edb58eaab68aa091b2c27b08a47583471e93437ac5f8baa61880c7af4938a941c084c19731e6e57a5710e6ad1196263291aea297ce0eec0f177"
|
||||||
|
|
||||||
|
ADDRESS_N = parse_path("44h/0h/0h")
|
||||||
|
XPUB_REQUEST = messages.GetPublicKey(address_n=ADDRESS_N, coin_name="Bitcoin")
|
||||||
|
|
||||||
|
|
||||||
|
def _init_session(client, session_id=None):
|
||||||
|
"""Call Initialize, check and return the session ID."""
|
||||||
|
response = client.call(messages.Initialize(session_id=session_id))
|
||||||
|
assert isinstance(response, messages.Features)
|
||||||
|
assert len(response.session_id) == 32
|
||||||
|
return response.session_id
|
||||||
|
|
||||||
|
|
||||||
|
def _get_xpub(client, passphrase=None):
|
||||||
|
"""Get XPUB and check that the appropriate passphrase flow has happened."""
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
if passphrase is not None:
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase=passphrase))
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
return response.xpub
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_session_with_passphrase(client):
|
||||||
|
# Let's start the communication by calling Initialize.
|
||||||
|
session_id = _init_session(client)
|
||||||
|
|
||||||
|
# GetPublicKey requires passphrase and since it is not cached,
|
||||||
|
# Trezor will prompt for it.
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Call Initialize again, this time with the received session id and then call
|
||||||
|
# GetPublicKey. The passphrase should be cached now so Trezor must
|
||||||
|
# not ask for it again, whilst returning the same xpub.
|
||||||
|
new_session_id = _init_session(client, session_id=session_id)
|
||||||
|
assert new_session_id == session_id
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# If we set session id in Initialize to None, the cache will be cleared
|
||||||
|
# and Trezor will ask for the passphrase again.
|
||||||
|
new_session_id = _init_session(client)
|
||||||
|
assert new_session_id != session_id
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Unknown session id is the same as setting it to None.
|
||||||
|
_init_session(client, session_id=b"X" * 32)
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
def test_session_enable_passphrase(client):
|
||||||
|
# Let's start the communication by calling Initialize.
|
||||||
|
session_id = _init_session(client)
|
||||||
|
|
||||||
|
# Trezor will not prompt for passphrase because it is turned off.
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_NONE
|
||||||
|
|
||||||
|
# Turn on passphrase.
|
||||||
|
# Emit the call explicitly to avoid ClearSession done by the library function
|
||||||
|
response = client.call(messages.ApplySettings(use_passphrase=True))
|
||||||
|
assert isinstance(response, messages.Success)
|
||||||
|
|
||||||
|
# The session id is unchanged, therefore we do not prompt for the passphrase.
|
||||||
|
new_session_id = _init_session(client, session_id=session_id)
|
||||||
|
assert session_id == new_session_id
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_NONE
|
||||||
|
|
||||||
|
# We clear the session id now, so the passphrase should be asked.
|
||||||
|
new_session_id = _init_session(client)
|
||||||
|
assert session_id != new_session_id
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_clear_session_passphrase(client):
|
||||||
|
# at first attempt, we are prompted for passphrase
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# now the passphrase is cached
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Erase the cached passphrase
|
||||||
|
response = client.call(messages.Initialize())
|
||||||
|
assert isinstance(response, messages.Features)
|
||||||
|
|
||||||
|
# we have to enter passphrase again
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_on_device(client):
|
||||||
|
_init_session(client)
|
||||||
|
|
||||||
|
# try to get xpub with passphrase on host:
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase="A", on_device=False))
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# try to get xpub again, passphrase should be cached
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# make a new session
|
||||||
|
_init_session(client)
|
||||||
|
|
||||||
|
# try to get xpub with passphrase on device:
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(on_device=True))
|
||||||
|
assert isinstance(response, messages.ButtonRequest)
|
||||||
|
client.debug.input("A")
|
||||||
|
response = client.call_raw(messages.ButtonAck())
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# try to get xpub again, passphrase should be cached
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_always_on_device(client):
|
||||||
|
# Let's start the communication by calling Initialize.
|
||||||
|
session_id = _init_session(client)
|
||||||
|
|
||||||
|
# Force passphrase entry on Trezor.
|
||||||
|
response = client.call(messages.ApplySettings(passphrase_always_on_device=True))
|
||||||
|
assert isinstance(response, messages.Success)
|
||||||
|
|
||||||
|
# Since we enabled the always_on_device setting, Trezor will send ButtonRequests and ask for it on the device.
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.ButtonRequest)
|
||||||
|
client.debug.input("") # Input empty passphrase.
|
||||||
|
response = client.call_raw(messages.ButtonAck())
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_NONE
|
||||||
|
|
||||||
|
# Passphrase will not be prompted. The session id stays the same and the passphrase is cached.
|
||||||
|
_init_session(client, session_id=session_id)
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_NONE
|
||||||
|
|
||||||
|
# In case we want to add a new passphrase we need to send session_id = None.
|
||||||
|
_init_session(client)
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.ButtonRequest)
|
||||||
|
client.debug.input("A") # Input empty passphrase.
|
||||||
|
response = client.call_raw(messages.ButtonAck())
|
||||||
|
assert isinstance(response, messages.PublicKey)
|
||||||
|
assert response.xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.skip_t2
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_on_device_not_possible_on_t1(client):
|
||||||
|
# This setting makes no sense on T1.
|
||||||
|
response = client.call_raw(messages.ApplySettings(passphrase_always_on_device=True))
|
||||||
|
assert isinstance(response, messages.Failure)
|
||||||
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
# T1 should not accept on_device request
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(on_device=True))
|
||||||
|
assert isinstance(response, messages.Failure)
|
||||||
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_ack_mismatch(client):
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase="A", on_device=True))
|
||||||
|
assert isinstance(response, messages.Failure)
|
||||||
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_passphrase_missing(client):
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase=None))
|
||||||
|
assert isinstance(response, messages.Failure)
|
||||||
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
response = client.call_raw(XPUB_REQUEST)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase=None, on_device=False))
|
||||||
|
assert isinstance(response, messages.Failure)
|
||||||
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
|
||||||
|
def _get_xpub_cardano(client, passphrase):
|
||||||
|
msg = messages.CardanoGetPublicKey(address_n=parse_path("44'/1815'/0'/0/0"))
|
||||||
|
response = client.call_raw(msg)
|
||||||
|
if passphrase is not None:
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase=passphrase))
|
||||||
|
assert isinstance(response, messages.CardanoPublicKey)
|
||||||
|
return response.xpub
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.altcoin
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_cardano_passphrase(client):
|
||||||
|
# Cardano uses a variation of BIP-39 so we need to ask for the passphrase again.
|
||||||
|
|
||||||
|
session_id = _init_session(client)
|
||||||
|
|
||||||
|
# GetPublicKey requires passphrase and since it is not cached,
|
||||||
|
# Trezor will prompt for it.
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# The passphrase is now cached for non-Cardano coins.
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Cardano will prompt for it again.
|
||||||
|
assert _get_xpub_cardano(client, passphrase="B") == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
|
||||||
|
# But now also Cardano has it cached.
|
||||||
|
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
|
||||||
|
# And others behaviour did not change.
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Initialize with the session id does not destroy the state
|
||||||
|
_init_session(client, session_id=session_id)
|
||||||
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||||
|
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
|
||||||
|
# New session will destroy the state
|
||||||
|
_init_session(client)
|
||||||
|
|
||||||
|
# GetPublicKey must ask for passphrase again
|
||||||
|
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Cardano must also ask for passphrase again
|
||||||
|
assert _get_xpub_cardano(client, passphrase="B") == XPUB_CARDANO_PASSPHRASE_B
|
@ -9,9 +9,9 @@
|
|||||||
"test_cancel.py::test_cancel_message_via_initialize[message1]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_cancel.py::test_cancel_message_via_initialize[message1]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_applysettings.py-test_apply_settings": "b698654871541258f97d58ada0f010b2d77b74829791566746cad619d3740a94",
|
"test_msg_applysettings.py-test_apply_settings": "b698654871541258f97d58ada0f010b2d77b74829791566746cad619d3740a94",
|
||||||
"test_msg_applysettings.py-test_apply_settings_passphrase": "fb38537b921f8064f7ea6e1a584e70a8be74968a3be6726b7d36cf57de0d7865",
|
"test_msg_applysettings.py-test_apply_settings_passphrase": "fb38537b921f8064f7ea6e1a584e70a8be74968a3be6726b7d36cf57de0d7865",
|
||||||
"test_msg_backup_device.py::test_backup_bip39": "68d22900d8f9130bf325342b6b32b1785f28cefd256ea1cee7fd32109c817c23",
|
"test_msg_backup_device.py::test_backup_bip39": "dfdbd0ae6774177d43f2f11d026c4d8679dd994b508c1d850d9cfee5dd1118ac",
|
||||||
"test_msg_backup_device.py::test_backup_slip39_advanced": "aec6a663f8f75ff73d2f2377d12347b062b831ce0d5fcfe9d7f3c975a05aef32",
|
"test_msg_backup_device.py::test_backup_slip39_advanced": "244b31044a25e44847a6efb79ea4cb67c246a971bf828d46aa79d14151934a04",
|
||||||
"test_msg_backup_device.py::test_backup_slip39_basic": "a4c44785873509bd4ebffa6ec82f2fe732349eb0bd23631dbc3876e956b567c7",
|
"test_msg_backup_device.py::test_backup_slip39_basic": "81a56c307342e46ad261d6a6c6da0a75224af8e72f40da7c630c1e03c0643e1c",
|
||||||
"test_msg_backup_device.py::test_interrupt_backup_fails": "225b3da1acac6e9a65106fcc4a01de8a44de035aedb4dcc21c09f439199fdf40",
|
"test_msg_backup_device.py::test_interrupt_backup_fails": "225b3da1acac6e9a65106fcc4a01de8a44de035aedb4dcc21c09f439199fdf40",
|
||||||
"test_msg_backup_device.py::test_no_backup_fails": "93039a9472cfc9058563bd56e4a3dbe2e41af64744a61f6ee3255a04bd3a9366",
|
"test_msg_backup_device.py::test_no_backup_fails": "93039a9472cfc9058563bd56e4a3dbe2e41af64744a61f6ee3255a04bd3a9366",
|
||||||
"test_msg_backup_device.py::test_no_backup_show_entropy_fails": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
"test_msg_backup_device.py::test_no_backup_show_entropy_fails": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
||||||
@ -24,16 +24,16 @@
|
|||||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPEZLCq3sFv4wVYx": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPEZLCq3sFv4wVYx": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPEZEY6pVJoyuNNd": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPEZEY6pVJoyuNNd": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPEZ3gZD1QeUHvAq": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPEZ3gZD1QeUHvAq": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPE": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPE": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPE": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPE": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPE": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_address_slip39_basic.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPE": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-0'-c0fce1839f1a84c4e7702": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-0'-c0fce1839f1a84c4e7702": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-3'-5f769380dc6fd17a4e0f2": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-3'-5f769380dc6fd17a4e0f2": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-0-bc04": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-0-bc04": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-1-24c4": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-1-24c4": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-2-831a": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
"test_msg_cardano_get_public_key_slip39_basic.py::test_cardano_get_public_key[m-44'-1815'-0'-0-2-831a": "d2d6aac0a4605f1a961580f0fc156e7b69b993f908906281313429fa3222f349",
|
||||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transactions2": "1a8eade03d9c0ecbbb01567e5e9d46187a2ffe7fa42d59eb711347a7fe3b5bb7",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transactions2": "1a8eade03d9c0ecbbb01567e5e9d46187a2ffe7fa42d59eb711347a7fe3b5bb7",
|
||||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactions0-": "07eceef966cb8069381dc5105b732bce6de8d207a1d27e56e8abddd57c307b22",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactions0-": "07eceef966cb8069381dc5105b732bce6de8d207a1d27e56e8abddd57c307b22",
|
||||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactions1-": "4c74546cacb2b99b9aeb6e134d99c0d1d6f0ba1818d1182364cfdb94b938ccc5",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactions1-": "4c74546cacb2b99b9aeb6e134d99c0d1d6f0ba1818d1182364cfdb94b938ccc5",
|
||||||
@ -43,16 +43,16 @@
|
|||||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transacti": "e6813a7fd973f49b02ef28cee15deb48d0389d1cb2696194848e4d690281361f",
|
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transacti": "e6813a7fd973f49b02ef28cee15deb48d0389d1cb2696194848e4d690281361f",
|
||||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactio": "bc88a23280c0234860ccbb5e96d5cc3a851e2f2f9928c400f6c0907c68172d39",
|
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactio": "bc88a23280c0234860ccbb5e96d5cc3a851e2f2f9928c400f6c0907c68172d39",
|
||||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactio": "6956bb359388186b4c127ae88f4d86527381caf8098f55b4fa05c343640c081f",
|
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactio": "6956bb359388186b4c127ae88f4d86527381caf8098f55b4fa05c343640c081f",
|
||||||
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "b294554434cd454b6c53845256d3637bb83f0f7f96e39608c91018bb345f359e",
|
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "9519b574e3f749810977ac55f851553f2051b51ef7799ffaa155410d89e1cd63",
|
||||||
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "74e8309c74ca9e599de91296cfcef63a744713acad3f3ea10b7b461eba69c30e",
|
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "82c0d1acbf5ff344189761f808d3cf0e632726341231c20b2c0925ab5549b6af",
|
||||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "fdbd58fb31323cfc32a9a7684d4dd45a5bd2b098156f2abf9cca5b5a481ecd10",
|
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "1642d2d15920a3bb2c666b39beca9943ba39adb59289ebc40b97d7088a4d7abf",
|
||||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "7b3599a5582b566c65c337b50fb6e38fc577b67310fc731ed756b7d143f4727d",
|
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "15289574ceb002b5161305b0595dcd20e437d1dd4e7561332e1aba4c1615e9ea",
|
||||||
"test_msg_change_wipe_code_t2.py::test_wipe_code_activate": "b25571c82be5f00b1f0c0bca95154dbae6a6ac092b249b79f805a126e1580037",
|
"test_msg_change_wipe_code_t2.py::test_wipe_code_activate": "348ff6811029253d7d520b37a2d3ff219516a7401c8b65ab088c7a7d39bd8b2b",
|
||||||
"test_msg_changepin_t2.py::test_change_failed": "d39ba207aab689c53adde211dc8e189809ffe4f3a3c547bd8c0422ad20663dc2",
|
"test_msg_changepin_t2.py::test_change_failed": "370c59da62a84aaefa242562c36a6facac89c7f819e37d1ae8cbe2c44a2de256",
|
||||||
"test_msg_changepin_t2.py::test_change_pin": "9e43d9f05c96f61e5ba7723e7d3ceb26ed813fd5be95d2cfa7e01573ab87b531",
|
"test_msg_changepin_t2.py::test_change_pin": "c42fca9bf8f3b4c330516d90231ae0cfa7419d83370be9cfcf6a81cca3f3b06c",
|
||||||
"test_msg_changepin_t2.py::test_remove_pin": "249a84500e2cac217f4b3c7cfbb4d4212781019191b21d1386f81317f6d699e7",
|
"test_msg_changepin_t2.py::test_remove_pin": "d049eaa6cd11e88b7af193b080cf868b62271266ad6f2973bfd82944b523741d",
|
||||||
"test_msg_changepin_t2.py::test_set_failed": "5aa994de8e571d08512705b04e0c55b60411d7d23491f75688f16d255369363e",
|
"test_msg_changepin_t2.py::test_set_failed": "59beeec1a00817f664a5fd93234012588613aac93c45d53c27550fe5d0ef8380",
|
||||||
"test_msg_changepin_t2.py::test_set_pin": "9e30570a9063a9820d423dd000d5ba70f30bf32de78cce9160643f60d5fe4eb2",
|
"test_msg_changepin_t2.py::test_set_pin": "3aafe16a451f928c9bfff2a3ff7e3c23ce4948c9a044ebd04834df045670183f",
|
||||||
"test_msg_cipherkeyvalue.py-test_decrypt": "166d85b1bf11aeaeb5b93ef5d047b6f8910c28b8fce1d853e6912d89d7bfca2f",
|
"test_msg_cipherkeyvalue.py-test_decrypt": "166d85b1bf11aeaeb5b93ef5d047b6f8910c28b8fce1d853e6912d89d7bfca2f",
|
||||||
"test_msg_cipherkeyvalue.py-test_decrypt_badlen": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cipherkeyvalue.py-test_decrypt_badlen": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cipherkeyvalue.py-test_encrypt": "3a37e4004c87bc6df6a8fa7c93b6fe3e3524986914709fda2f9c99ba0ff69775",
|
"test_msg_cipherkeyvalue.py-test_encrypt": "3a37e4004c87bc6df6a8fa7c93b6fe3e3524986914709fda2f9c99ba0ff69775",
|
||||||
@ -166,10 +166,10 @@
|
|||||||
"test_msg_lisk_verifymessage.py-test_verify": "5e9cf05f6ccf10f697cae9f780042db934892e1d7c68fb2f19a40319a687ea50",
|
"test_msg_lisk_verifymessage.py-test_verify": "5e9cf05f6ccf10f697cae9f780042db934892e1d7c68fb2f19a40319a687ea50",
|
||||||
"test_msg_lisk_verifymessage.py-test_verify_long": "26adab7e31f388e5b034a865f9c010d57e67fd855d44839d2f2600d8317bd98e",
|
"test_msg_lisk_verifymessage.py-test_verify_long": "26adab7e31f388e5b034a865f9c010d57e67fd855d44839d2f2600d8317bd98e",
|
||||||
"test_msg_loaddevice.py-test_load_device_1": "114d7e07b00f8a9fc60e0888ce3e39f79805c577b99f4d25967fcf7cf6367664",
|
"test_msg_loaddevice.py-test_load_device_1": "114d7e07b00f8a9fc60e0888ce3e39f79805c577b99f4d25967fcf7cf6367664",
|
||||||
"test_msg_loaddevice.py-test_load_device_2": "35797984a814cf7e48ee827993269b43524bf91462c9c694ad7dd7d6bb7e6bbb",
|
"test_msg_loaddevice.py-test_load_device_2": "9947760ad56ea110b6f3937883c37701c866dd57b6c342806bd8e8b3aa889887",
|
||||||
"test_msg_loaddevice.py-test_load_device_slip39_advanced": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
"test_msg_loaddevice.py-test_load_device_slip39_advanced": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
||||||
"test_msg_loaddevice.py-test_load_device_slip39_basic": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
"test_msg_loaddevice.py-test_load_device_slip39_basic": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
||||||
"test_msg_loaddevice.py-test_load_device_utf": "51fe82ea3081d06e69af1283bf9e32ea91ddb75ac87173a3d1f861c2b27a0635",
|
"test_msg_loaddevice.py-test_load_device_utf": "65d570c16a561831e017c447736749d855f0bf6e0c3cfe7a9b186493c01f187b",
|
||||||
"test_msg_monero_getaddress.py-test_monero_getaddress": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_monero_getaddress.py-test_monero_getaddress": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_monero_getwatchkey.py-test_monero_getwatchkey": "d77fa4d4322e145c41f1ce07526ff59f8b58d8854aeffaa5266e14cd572350e7",
|
"test_msg_monero_getwatchkey.py-test_monero_getwatchkey": "d77fa4d4322e145c41f1ce07526ff59f8b58d8854aeffaa5266e14cd572350e7",
|
||||||
"test_msg_nem_getaddress.py-test_nem_getaddress": "e726f99401a20eb74c33d755cecea2a3f69b7ae5b541302677ee05f80f5aef19",
|
"test_msg_nem_getaddress.py-test_nem_getaddress": "e726f99401a20eb74c33d755cecea2a3f69b7ae5b541302677ee05f80f5aef19",
|
||||||
@ -200,7 +200,7 @@
|
|||||||
"test_msg_recoverydevice_bip39_dryrun.py::test_uninitialized": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
"test_msg_recoverydevice_bip39_dryrun.py::test_uninitialized": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
||||||
"test_msg_recoverydevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_recoverydevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_recoverydevice_bip39_t2.py-test_nopin_nopassphrase": "9769cde3e3951a76364973ade753682e2acc67d1633a9f982f0604b5702aa895",
|
"test_msg_recoverydevice_bip39_t2.py-test_nopin_nopassphrase": "9769cde3e3951a76364973ade753682e2acc67d1633a9f982f0604b5702aa895",
|
||||||
"test_msg_recoverydevice_bip39_t2.py-test_pin_passphrase": "761b17d28d589eb4d4d6d73beb6061087828c469f60694893c94b301a37d34ad",
|
"test_msg_recoverydevice_bip39_t2.py-test_pin_passphrase": "d746fda5234be75cded559817d0fdfb4397ac757e9847d4dfbef44ac031381d1",
|
||||||
"test_msg_recoverydevice_slip39_advanced.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
"test_msg_recoverydevice_slip39_advanced.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
||||||
"test_msg_recoverydevice_slip39_advanced.py::test_extra_share_entered": "00a94e20b786346c45f987860b2465f299075d7c6de4971f42a4749e1cc8bfc0",
|
"test_msg_recoverydevice_slip39_advanced.py::test_extra_share_entered": "00a94e20b786346c45f987860b2465f299075d7c6de4971f42a4749e1cc8bfc0",
|
||||||
"test_msg_recoverydevice_slip39_advanced.py::test_group_threshold_reached": "3b075a276c4e0d53fbc51ce1f29594bbd474d25f47c0f6a32caac41ba0ba2138",
|
"test_msg_recoverydevice_slip39_advanced.py::test_group_threshold_reached": "3b075a276c4e0d53fbc51ce1f29594bbd474d25f47c0f6a32caac41ba0ba2138",
|
||||||
@ -214,7 +214,7 @@
|
|||||||
"test_msg_recoverydevice_slip39_basic.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
"test_msg_recoverydevice_slip39_basic.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_ask_word_number": "8e9d9fd75e17f6b44829ae2d7b0eb9e60b48577f975abc6d75116f8365241082",
|
"test_msg_recoverydevice_slip39_basic.py::test_ask_word_number": "8e9d9fd75e17f6b44829ae2d7b0eb9e60b48577f975abc6d75116f8365241082",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_noabort": "d374a9b85c03a0cc1bbb59130e454406513fc35f4f43b968db4920414de1bb72",
|
"test_msg_recoverydevice_slip39_basic.py::test_noabort": "d374a9b85c03a0cc1bbb59130e454406513fc35f4f43b968db4920414de1bb72",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "3b49ab7cd70cf4e2576c842948029046bc6a686b3763983985e3d3965e8652d8",
|
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "ee0edd912b913d31b8308b64ceed7fa3e58f5b63c8a4d4d85f3651d4b121a202",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_same_share": "e6a54429fdbedea9efca9cbed736aada07f95f3b20f895f9c1c5ec056a2be014",
|
"test_msg_recoverydevice_slip39_basic.py::test_same_share": "e6a54429fdbedea9efca9cbed736aada07f95f3b20f895f9c1c5ec056a2be014",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "54581a91b55ab531b215cba61052fd77c505232c510f170080760605eb9b8c46",
|
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "54581a91b55ab531b215cba61052fd77c505232c510f170080760605eb9b8c46",
|
||||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39bdbf2463be0878": "f73bef254762d761db27df46bff62641a9d2ac0602c34fc4e465262bf26ed08f",
|
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39bdbf2463be0878": "f73bef254762d761db27df46bff62641a9d2ac0602c34fc4e465262bf26ed08f",
|
||||||
@ -224,11 +224,11 @@
|
|||||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_dryrun": "d84427489f691ecc222b62f83af3e97fa09097404dcba07772a43b5eb0c689e8",
|
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_dryrun": "d84427489f691ecc222b62f83af3e97fa09097404dcba07772a43b5eb0c689e8",
|
||||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "55f2dd6b4958659f071c3f57e06286f872ac38af4828f446a0f4e91c657dfccc",
|
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "55f2dd6b4958659f071c3f57e06286f872ac38af4828f446a0f4e91c657dfccc",
|
||||||
"test_msg_resetdevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_resetdevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_resetdevice_bip39_t2.py-test_failed_pin": "a6ba803a7572dc8e29c821433a067c3a990185a8eb7b95d0106cd8f4b82e9da2",
|
"test_msg_resetdevice_bip39_t2.py-test_failed_pin": "a52f3479f7d8e14c7f89af9b305a50f3bd244fee28f17ebc0abedba701c32811",
|
||||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device": "cbe1e439c2e9810ff6ee1031268810188b3cd07e029a7aa1d4b227e33707c727",
|
"test_msg_resetdevice_bip39_t2.py-test_reset_device": "13d739a387a80aa96f533bb6f3b0f2ff12c7ba84608d0797ac603208d9aed796",
|
||||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device_pin": "af378bff7e8fdf16324715f640e8b17bebd69f779758ab33cd7025c557a1d5c0",
|
"test_msg_resetdevice_bip39_t2.py-test_reset_device_pin": "ed64165674816dfb408ead89abb8d1a5743f50f63933aceba6701149522c3866",
|
||||||
"test_msg_resetdevice_slip39_advanced.py-test_reset_device_slip39_advanced": "7daabeb21a17a8c8e8adb800a8b6b0504e5d2e7cf0bbfcb14770fb33df1426a2",
|
"test_msg_resetdevice_slip39_advanced.py-test_reset_device_slip39_advanced": "005622513c517610a4a1af529ef94760b4d0406b971f0b2f2557c93ce0dac8c9",
|
||||||
"test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic": "1b56e3335876c01ec684839158bc0aa3a31aac03d2beef8d3ee63a0a441e3f1f",
|
"test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic": "ebc62d2c49136c6ce75c8e026e51157d96a6f2440881dca0cdd8bc357e2c9354",
|
||||||
"test_msg_ripple_get_address.py-test_ripple_get_address": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
"test_msg_ripple_get_address.py-test_ripple_get_address": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
||||||
"test_msg_ripple_get_address.py-test_ripple_get_address_other": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
"test_msg_ripple_get_address.py-test_ripple_get_address_other": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
||||||
"test_msg_ripple_sign_tx.py-test_ripple_sign_invalid_fee": "1c0ca08b857da6121f43cfb1632c7f7e1d189ef1fdb665db7ba2cdfa7a59ea7c",
|
"test_msg_ripple_sign_tx.py-test_ripple_sign_invalid_fee": "1c0ca08b857da6121f43cfb1632c7f7e1d189ef1fdb665db7ba2cdfa7a59ea7c",
|
||||||
@ -380,12 +380,12 @@
|
|||||||
"test_passphrase_slip39_advanced.py::test_256bit_passphrase": "69b6b8b22c819e1282d7d2c14b31bf8d015c81ac05fe034540dbb11c8a20dbdb",
|
"test_passphrase_slip39_advanced.py::test_256bit_passphrase": "69b6b8b22c819e1282d7d2c14b31bf8d015c81ac05fe034540dbb11c8a20dbdb",
|
||||||
"test_passphrase_slip39_basic.py::test_2of5_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
"test_passphrase_slip39_basic.py::test_2of5_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
||||||
"test_passphrase_slip39_basic.py::test_3of6_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
"test_passphrase_slip39_basic.py::test_3of6_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
||||||
"test_reset_backup.py::test_skip_backup_manual[0-backup_flow_bip39]": "84ee487acfff37417e4d6862ab1b166be1165ae3eb4e19f828ae3363866edb39",
|
"test_reset_backup.py::test_skip_backup_manual[0-backup_flow_bip39]": "a73fcd9af54d3b55cc7c21e68b1637a6e29829bfd57d47b0503e67fa22e4106a",
|
||||||
"test_reset_backup.py::test_skip_backup_manual[1-backup_flow_slip39_basic]": "55224486083a1268c76ffe1d2b47ca7fba29ca6955e3601dae9e2abd44fb9d62",
|
"test_reset_backup.py::test_skip_backup_manual[1-backup_flow_slip39_basic]": "4b117541a58e7c209786a728ebc1406720cbd7d5a234f20a550b1ad3970b4e76",
|
||||||
"test_reset_backup.py::test_skip_backup_manual[2-backup_flow_slip39_advanced]": "c40d7894875393bc093e23d02dab8ad668929767a542a27ff89c6dd5b7b963de",
|
"test_reset_backup.py::test_skip_backup_manual[2-backup_flow_slip39_advanced]": "2d1d55f25b21c2be2e7aeaa17eeb3a4b9ee4d7763c4a65ae2fd59da2d9dbbcc0",
|
||||||
"test_reset_backup.py::test_skip_backup_msg[0-backup_flow_bip39]": "68eb02ad320eee49d3a6aac891e5dc4c35ee09a1e98a2480c2cb1b672cbb0696",
|
"test_reset_backup.py::test_skip_backup_msg[0-backup_flow_bip39]": "f448153452b154c0be153e18340f22c853430267316229548ad8f4f47cf946f7",
|
||||||
"test_reset_backup.py::test_skip_backup_msg[1-backup_flow_slip39_basic]": "34674c8b9f4b6a836940fa347bacfd6d31dd681d41bf8e1ec129e9deef590588",
|
"test_reset_backup.py::test_skip_backup_msg[1-backup_flow_slip39_basic]": "9fbcd0ab293de21ab3a24e0c29315d152c1bc5da4902ec054cc8800c0d855aa6",
|
||||||
"test_reset_backup.py::test_skip_backup_msg[2-backup_flow_slip39_advanced]": "24c641cde46665219de99978c3e27088fd4ade55f2665535a95e7dce1f2b3148",
|
"test_reset_backup.py::test_skip_backup_msg[2-backup_flow_slip39_advanced]": "0bafb944169d0ab608513deadba15411d1756ef2658253740c5d8792f4527e9d",
|
||||||
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe",
|
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe",
|
||||||
"test_zerosig.py-test_one_zero_signature": "401aeaf7b2f565e2064a3c1a57a8ee3afe1e9bf251fba0874390685e7e0f178f",
|
"test_zerosig.py-test_one_zero_signature": "401aeaf7b2f565e2064a3c1a57a8ee3afe1e9bf251fba0874390685e7e0f178f",
|
||||||
"test_zerosig.py-test_two_zero_signature": "7a01a057fb5dd3e6e38e7986875c5d07f0700bd80b519660e0b42973a9afd664"
|
"test_zerosig.py-test_two_zero_signature": "7a01a057fb5dd3e6e38e7986875c5d07f0700bd80b519660e0b42973a9afd664"
|
||||||
|
Loading…
Reference in New Issue
Block a user