1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-08 05:32:39 +00:00

feat(core): increase maximum passphrase length to 128 bytes

Legacy is still limited to 50 bytes.

Also, deduplicate common passphrase test vectors definitions.
This commit is contained in:
Roman Zeyde 2025-01-16 21:20:17 +02:00
parent e9aca68612
commit a0282add7f
15 changed files with 107 additions and 150 deletions

View File

@ -0,0 +1 @@
Increase maximum passphrase length to 128 bytes.

View File

@ -1,7 +1,7 @@
use heapless::Vec; use heapless::{String, Vec};
use crate::{ use crate::{
strutil::{ShortString, TString}, strutil::TString,
time::Duration, time::Duration,
ui::{ ui::{
button_request::{ButtonRequest, ButtonRequestCode}, button_request::{ButtonRequest, ButtonRequestCode},
@ -592,6 +592,8 @@ impl EventCtx {
} }
} }
pub type FlowMsgText = String<128>;
/// Component::Msg for component parts of a swipe flow. Converting results of /// Component::Msg for component parts of a swipe flow. Converting results of
/// different screens to a shared type makes things easier to work with. /// different screens to a shared type makes things easier to work with.
/// ///
@ -603,7 +605,7 @@ pub enum FlowMsg {
Cancelled, Cancelled,
Info, Info,
Choice(usize), Choice(usize),
Text(ShortString), Text(FlowMsgText),
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]

View File

@ -1,6 +1,6 @@
use crate::{ use crate::ui::{
strutil::ShortString, component::{base::FlowMsgText, EventCtx},
ui::{component::EventCtx, util::ResultExt}, util::ResultExt,
}; };
/// Reified editing operations of `TextBox`. /// Reified editing operations of `TextBox`.
@ -16,13 +16,13 @@ pub enum TextEdit {
/// operations over it. Text ops usually take a `EventCtx` to request a paint /// operations over it. Text ops usually take a `EventCtx` to request a paint
/// pass in case of any state modification. /// pass in case of any state modification.
pub struct TextBox { pub struct TextBox {
text: ShortString, text: FlowMsgText,
} }
impl TextBox { impl TextBox {
/// Create a new `TextBox` with content `text`. /// Create a new `TextBox` with content `text`.
pub fn new(text: &str, max_len: usize) -> Self { pub fn new(text: &str, max_len: usize) -> Self {
let text = unwrap!(ShortString::try_from(text)); let text = unwrap!(FlowMsgText::try_from(text));
debug_assert!(text.capacity() >= max_len); debug_assert!(text.capacity() >= max_len);
Self { text } Self { text }
} }

View File

@ -48,7 +48,7 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="], ["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="],
]; ];
const MAX_LENGTH: usize = 50; const MAX_LENGTH: usize = 128;
const INPUT_AREA_HEIGHT: i16 = ScrollBar::DOT_SIZE + 9; const INPUT_AREA_HEIGHT: i16 = ScrollBar::DOT_SIZE + 9;
impl PassphraseKeyboard { impl PassphraseKeyboard {

View File

@ -1,12 +1,9 @@
use crate::{ use crate::ui::{
strutil::ShortString, component::{base::FlowMsgText, Component, Event, EventCtx, Never, Pad},
ui::{ display::Font,
component::{Component, Event, EventCtx, Never, Pad}, geometry::{Alignment, Point, Rect},
display::Font, shape::{self, Renderer},
geometry::{Alignment, Point, Rect}, util::long_line_content_with_ellipsis,
shape::{self, Renderer},
util::long_line_content_with_ellipsis,
},
}; };
use super::theme; use super::theme;
@ -16,7 +13,7 @@ use super::theme;
/// and without being affected by other components. /// and without being affected by other components.
pub struct ChangingTextLine { pub struct ChangingTextLine {
pad: Pad, pad: Pad,
text: ShortString, text: FlowMsgText,
font: Font, font: Font,
/// Whether to show the text. Can be disabled. /// Whether to show the text. Can be disabled.
show_content: bool, show_content: bool,
@ -29,7 +26,7 @@ pub struct ChangingTextLine {
impl ChangingTextLine { impl ChangingTextLine {
pub fn new(text: &str, font: Font, alignment: Alignment, max_len: usize) -> Self { pub fn new(text: &str, font: Font, alignment: Alignment, max_len: usize) -> Self {
let text = unwrap!(ShortString::try_from(text)); let text = unwrap!(FlowMsgText::try_from(text));
debug_assert!(text.capacity() >= max_len); debug_assert!(text.capacity() >= max_len);
Self { Self {
pad: Pad::with_background(theme::BG), pad: Pad::with_background(theme::BG),

View File

@ -1,9 +1,12 @@
use crate::{ use crate::{
strutil::{ShortString, TString}, strutil::TString,
translations::TR, translations::TR,
trezorhal::random, trezorhal::random,
ui::{ ui::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, component::{
base::FlowMsgText, text::common::TextBox, Child, Component, ComponentExt, Event,
EventCtx,
},
display::Icon, display::Icon,
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
@ -26,7 +29,7 @@ enum ChoiceCategory {
SpecialSymbol, SpecialSymbol,
} }
const MAX_PASSPHRASE_LENGTH: usize = 50; const MAX_PASSPHRASE_LENGTH: usize = 128;
const DIGITS: &str = "0123456789"; const DIGITS: &str = "0123456789";
const LOWERCASE_LETTERS: &str = "abcdefghijklmnopqrstuvwxyz"; const LOWERCASE_LETTERS: &str = "abcdefghijklmnopqrstuvwxyz";
@ -291,17 +294,17 @@ impl PassphraseEntry {
fn update_passphrase_dots(&mut self, ctx: &mut EventCtx) { fn update_passphrase_dots(&mut self, ctx: &mut EventCtx) {
debug_assert!({ debug_assert!({
let s = ShortString::new(); let s = FlowMsgText::new();
s.capacity() >= MAX_PASSPHRASE_LENGTH s.capacity() >= MAX_PASSPHRASE_LENGTH
}); });
let text_to_show = if self.show_plain_passphrase { let text_to_show = if self.show_plain_passphrase {
unwrap!(ShortString::try_from(self.passphrase())) unwrap!(FlowMsgText::try_from(self.passphrase()))
} else if self.is_empty() { } else if self.is_empty() {
unwrap!(ShortString::try_from("")) unwrap!(FlowMsgText::try_from(""))
} else { } else {
// Showing asterisks and possibly the last digit. // Showing asterisks and possibly the last digit.
let mut dots = ShortString::new(); let mut dots = FlowMsgText::new();
for _ in 0..self.textbox.len() - 1 { for _ in 0..self.textbox.len() - 1 {
unwrap!(dots.push('*')); unwrap!(dots.push('*'));
} }

View File

@ -1,10 +1,12 @@
use crate::{ use crate::{
strutil::{ShortString, TString}, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
base::ComponentExt, swipe_detect::SwipeConfig, text::common::TextBox, Component, Event, base::{ComponentExt, FlowMsgText},
EventCtx, Label, Maybe, Never, Swipe, swipe_detect::SwipeConfig,
text::common::TextBox,
Component, Event, EventCtx, Label, Maybe, Never, Swipe,
}, },
display, display,
geometry::{Alignment, Direction, Grid, Insets, Offset, Rect}, geometry::{Alignment, Direction, Grid, Insets, Offset, Rect},
@ -27,7 +29,7 @@ use core::cell::Cell;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
pub enum PassphraseKeyboardMsg { pub enum PassphraseKeyboardMsg {
Confirmed(ShortString), Confirmed(FlowMsgText),
Cancelled, Cancelled,
} }
@ -101,7 +103,7 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="], ["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="],
]; ];
const MAX_LENGTH: usize = 50; const MAX_LENGTH: usize = 128;
const CONFIRM_BTN_INSETS: Insets = Insets::new(5, 0, 5, 0); const CONFIRM_BTN_INSETS: Insets = Insets::new(5, 0, 5, 0);
const CONFIRM_EMPTY_BTN_MARGIN_RIGHT: i16 = 7; const CONFIRM_EMPTY_BTN_MARGIN_RIGHT: i16 = 7;
@ -338,12 +340,12 @@ impl Component for PassphraseKeyboard {
// Confirm button was clicked, we're done. // Confirm button was clicked, we're done.
if let Some(ButtonMsg::Clicked) = self.confirm_empty_btn.event(ctx, event) { if let Some(ButtonMsg::Clicked) = self.confirm_empty_btn.event(ctx, event) {
return Some(PassphraseKeyboardMsg::Confirmed(unwrap!( return Some(PassphraseKeyboardMsg::Confirmed(unwrap!(
ShortString::try_from(self.passphrase()) FlowMsgText::try_from(self.passphrase())
))); )));
} }
if let Some(ButtonMsg::Clicked) = self.confirm_btn.event(ctx, event) { if let Some(ButtonMsg::Clicked) = self.confirm_btn.event(ctx, event) {
return Some(PassphraseKeyboardMsg::Confirmed(unwrap!( return Some(PassphraseKeyboardMsg::Confirmed(unwrap!(
ShortString::try_from(self.passphrase()) FlowMsgText::try_from(self.passphrase())
))); )));
} }
if let Some(ButtonMsg::Clicked) = self.cancel_btn.event(ctx, event) { if let Some(ButtonMsg::Clicked) = self.cancel_btn.event(ctx, event) {

View File

@ -1,9 +1,8 @@
use crate::{ use crate::{
error, error,
strutil::ShortString,
translations::TR, translations::TR,
ui::{ ui::{
component::ComponentExt, component::{base::FlowMsgText, ComponentExt},
flow::{ flow::{
base::{Decision, DecisionBuilder as _}, base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow, FlowController, FlowMsg, SwipeFlow,
@ -44,7 +43,7 @@ impl FlowController for RequestPassphrase {
(Self::Keypad, FlowMsg::Cancelled) => self.return_msg(FlowMsg::Cancelled), (Self::Keypad, FlowMsg::Cancelled) => self.return_msg(FlowMsg::Cancelled),
(Self::ConfirmEmpty, FlowMsg::Cancelled) => Self::Keypad.goto(), (Self::ConfirmEmpty, FlowMsg::Cancelled) => Self::Keypad.goto(),
(Self::ConfirmEmpty, FlowMsg::Confirmed) => { (Self::ConfirmEmpty, FlowMsg::Confirmed) => {
self.return_msg(FlowMsg::Text(ShortString::new())) self.return_msg(FlowMsg::Text(FlowMsgText::new()))
} }
_ => self.do_nothing(), _ => self.do_nothing(),
} }

View File

@ -3,7 +3,7 @@ from micropython import const
import storage.device as storage_device import storage.device as storage_device
from trezor.wire import DataError from trezor.wire import DataError
_MAX_PASSPHRASE_LEN = const(50) _MAX_PASSPHRASE_LEN = const(128)
def is_enabled() -> bool: def is_enabled() -> bool:

View File

@ -38,7 +38,7 @@ MT = TypeVar("MT", bound=MessageType)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MAX_PASSPHRASE_LENGTH = 50 MAX_PASSPHRASE_LENGTH = 128
MAX_PIN_LENGTH = 50 MAX_PIN_LENGTH = 50
PASSPHRASE_ON_DEVICE = object() PASSPHRASE_ON_DEVICE = object()

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import itertools
import typing as t import typing as t
from enum import Enum from enum import Enum
@ -27,6 +28,32 @@ class CommonPass:
EMPTY_ADDRESS = "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q" EMPTY_ADDRESS = "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q"
LONGEST = "".join(itertools.islice(itertools.cycle("1234567890"), 128))
LONGEST_ADDRESS = "msHs5d865WetSd7Uai8roAu2eCaZgx3oqg"
ALMOST_LONGEST = LONGEST[:-1]
ALMOST_LONGEST_ADDRESS = "mkRvtfidQoPhNQx1eesDNMXCRiZeUK5kLB"
TOO_LONG = LONGEST + "x"
TOO_LONG_ADDRESS = LONGEST_ADDRESS
VECTORS = ( # passphrase, address
(SHORT, SHORT_ADDRESS),
(WITH_SPACE, WITH_SPACE_ADDRESS),
(RANDOM_25, RANDOM_25_ADDRESS),
(ALMOST_LONGEST, ALMOST_LONGEST_ADDRESS),
(LONGEST, LONGEST_ADDRESS),
)
assert len(CommonPass.LONGEST) == 128
assert len(CommonPass.ALMOST_LONGEST) == 127
assert len(CommonPass.TOO_LONG) == 129
assert CommonPass.LONGEST_ADDRESS != CommonPass.ALMOST_LONGEST_ADDRESS
assert CommonPass.LONGEST_ADDRESS == CommonPass.TOO_LONG_ADDRESS
# TODO: show some UI message when length reaches the limit?
# (it currently disabled typing and greys out the buttons)
class PassphraseCategory(Enum): class PassphraseCategory(Enum):
MENU = "MENU" MENU = "MENU"

View File

@ -45,24 +45,6 @@ TT_CATEGORIES = [
TT_CATEGORY = PassphraseCategory.LOWERCASE TT_CATEGORY = PassphraseCategory.LOWERCASE
TT_COORDS_PREV: buttons.Coords = (0, 0) TT_COORDS_PREV: buttons.Coords = (0, 0)
# Testing the maximum length is really 50
# TODO: show some UI message when length reaches 50?
# (it currently disabled typing and greys out the buttons)
DA_50 = 25 * "da"
DA_50_ADDRESS = "mg5L2i8HZKUvceK1sfmGHhE4gichFSsdvm"
assert len(DA_50) == 50
DA_49 = DA_50[:-1]
DA_49_ADDRESS = "mxrB75ydMS3ZzqmYKK28fj4bNMEx7dDw6e"
assert len(DA_49) == 49
assert DA_49_ADDRESS != DA_50_ADDRESS
DA_51 = DA_50 + "d"
DA_51_ADDRESS = DA_50_ADDRESS
assert len(DA_51) == 51
assert DA_51_ADDRESS == DA_50_ADDRESS
@contextmanager @contextmanager
def prepare_passphrase_dialogue( def prepare_passphrase_dialogue(
@ -151,16 +133,7 @@ def delete_char(debug: "DebugLink") -> None:
debug.click(coords) debug.click(coords)
VECTORS = ( # passphrase, address @pytest.mark.parametrize("passphrase, address", CommonPass.VECTORS)
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
(CommonPass.RANDOM_25, CommonPass.RANDOM_25_ADDRESS),
(DA_49, DA_49_ADDRESS),
(DA_50, DA_50_ADDRESS),
)
@pytest.mark.parametrize("passphrase, address", VECTORS)
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input( def test_passphrase_input(
device_handler: "BackgroundDeviceHandler", passphrase: str, address: str device_handler: "BackgroundDeviceHandler", passphrase: str, address: str
@ -171,10 +144,10 @@ def test_passphrase_input(
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input_over_50_chars(device_handler: "BackgroundDeviceHandler"): def test_passphrase_input_too_long(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, DA_51_ADDRESS) as debug: # type: ignore with prepare_passphrase_dialogue(device_handler, CommonPass.TOO_LONG_ADDRESS) as debug: # type: ignore
input_passphrase(debug, DA_51, check=False) input_passphrase(debug, CommonPass.TOO_LONG, check=False)
assert debug.read_layout().passphrase() == DA_50 assert debug.read_layout().passphrase() == CommonPass.LONGEST
enter_passphrase(debug) enter_passphrase(debug)
@ -290,9 +263,10 @@ def test_passphrase_dollar_sign_deletion(
def test_cycle_through_last_character( def test_cycle_through_last_character(
device_handler: "BackgroundDeviceHandler", device_handler: "BackgroundDeviceHandler",
): ):
# Checks that we can cycle through the last (50th) passphrase character # Checks that we can cycle through the last (128th) passphrase character
# (was a bug previously) # (was a bug previously)
with prepare_passphrase_dialogue(device_handler) as debug: with prepare_passphrase_dialogue(device_handler) as debug:
passphrase = DA_49 + "i" # for i we need to cycle through "ghi" three times # for "i" we need to cycle through "ghi" three times
passphrase = CommonPass.ALMOST_LONGEST + "i"
input_passphrase(debug, passphrase) input_passphrase(debug, passphrase)
enter_passphrase(debug) enter_passphrase(debug)

View File

@ -37,24 +37,6 @@ if TYPE_CHECKING:
pytestmark = pytest.mark.models("safe3") pytestmark = pytest.mark.models("safe3")
# Testing the maximum length is really 50
# TODO: show some UI message when length reaches 50?
AAA_50 = 50 * "a"
AAA_50_ADDRESS = "miPeCUxf1Ufh5DtV3AuBopNM8YEDvnQZMh"
assert len(AAA_50) == 50
AAA_49 = AAA_50[:-1]
AAA_49_ADDRESS = "n2MPUjAB86MuVmyYe8HCgdznJS1FXk3qvg"
assert len(AAA_49) == 49
assert AAA_49_ADDRESS != AAA_50_ADDRESS
AAA_51 = AAA_50 + "a"
AAA_51_ADDRESS = "miPeCUxf1Ufh5DtV3AuBopNM8YEDvnQZMh"
assert len(AAA_51) == 51
assert AAA_51_ADDRESS == AAA_50_ADDRESS
BACK = "inputs__back" BACK = "inputs__back"
SHOW = "inputs__show" SHOW = "inputs__show"
ENTER = "inputs__enter" ENTER = "inputs__enter"
@ -186,16 +168,7 @@ def cancel(debug: "DebugLink") -> None:
delete_char(debug) delete_char(debug)
VECTORS = ( # passphrase, address @pytest.mark.parametrize("passphrase, address", CommonPass.VECTORS)
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
(CommonPass.RANDOM_25, CommonPass.RANDOM_25_ADDRESS),
(AAA_49, AAA_49_ADDRESS),
(AAA_50, AAA_50_ADDRESS),
)
@pytest.mark.parametrize("passphrase, address", VECTORS)
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input( def test_passphrase_input(
device_handler: "BackgroundDeviceHandler", passphrase: str, address: str device_handler: "BackgroundDeviceHandler", passphrase: str, address: str
@ -207,22 +180,22 @@ def test_passphrase_input(
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input_over_50_chars(device_handler: "BackgroundDeviceHandler"): def test_passphrase_input_too_long(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, AAA_51_ADDRESS) as debug: # type: ignore with prepare_passphrase_dialogue(device_handler, CommonPass.TOO_LONG_ADDRESS) as debug: # type: ignore
# First 50 chars # First 128 chars
input_passphrase(debug, AAA_51[:-1]) input_passphrase(debug, CommonPass.TOO_LONG[:-1])
layout = debug.read_layout() layout = debug.read_layout()
assert AAA_51[:-1] in layout.passphrase() assert CommonPass.TOO_LONG[:-1] in layout.passphrase()
show_passphrase(debug) show_passphrase(debug)
# Over-limit character # Over-limit character
press_char(debug, AAA_51[-1]) press_char(debug, CommonPass.TOO_LONG[-1])
# No change # No change
layout = debug.read_layout() layout = debug.read_layout()
assert AAA_51[:-1] in layout.passphrase() assert CommonPass.TOO_LONG[:-1] in layout.passphrase()
assert AAA_51 not in layout.passphrase() assert CommonPass.TOO_LONG not in layout.passphrase()
show_passphrase(debug) show_passphrase(debug)
enter_passphrase(debug) enter_passphrase(debug)

View File

@ -54,22 +54,6 @@ PASSPHRASE_SPECIAL = ("_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", "
KEYBOARD_CATEGORY = PassphraseCategory.LOWERCASE KEYBOARD_CATEGORY = PassphraseCategory.LOWERCASE
COORDS_PREV: buttons.Coords = (0, 0) COORDS_PREV: buttons.Coords = (0, 0)
# Testing the maximum length is really 50
DA_50 = 25 * "da"
DA_50_ADDRESS = "mg5L2i8HZKUvceK1sfmGHhE4gichFSsdvm"
assert len(DA_50) == 50
DA_49 = DA_50[:-1]
DA_49_ADDRESS = "mxrB75ydMS3ZzqmYKK28fj4bNMEx7dDw6e"
assert len(DA_49) == 49
assert DA_49_ADDRESS != DA_50_ADDRESS
DA_51 = DA_50 + "d"
DA_51_ADDRESS = DA_50_ADDRESS
assert len(DA_51) == 51
assert DA_51_ADDRESS == DA_50_ADDRESS
def get_passphrase_choices(char: str) -> tuple[str, ...]: def get_passphrase_choices(char: str) -> tuple[str, ...]:
if char in " *#": if char in " *#":
@ -182,16 +166,7 @@ def delete_char(debug: "DebugLink") -> None:
debug.click(coords) debug.click(coords)
VECTORS = ( # passphrase, address @pytest.mark.parametrize("passphrase, address", CommonPass.VECTORS)
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
(CommonPass.RANDOM_25, CommonPass.RANDOM_25_ADDRESS),
(DA_49, DA_49_ADDRESS),
(DA_50, DA_50_ADDRESS),
)
@pytest.mark.parametrize("passphrase, address", VECTORS)
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input( def test_passphrase_input(
device_handler: "BackgroundDeviceHandler", passphrase: str, address: str device_handler: "BackgroundDeviceHandler", passphrase: str, address: str
@ -202,10 +177,10 @@ def test_passphrase_input(
@pytest.mark.setup_client(passphrase=True) @pytest.mark.setup_client(passphrase=True)
def test_passphrase_input_over_50_chars(device_handler: "BackgroundDeviceHandler"): def test_passphrase_input_over_max_length(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, DA_51_ADDRESS) as debug: # type: ignore with prepare_passphrase_dialogue(device_handler, CommonPass.TOO_LONG_ADDRESS) as debug: # type: ignore
input_passphrase(debug, DA_51, check=False) input_passphrase(debug, CommonPass.TOO_LONG, check=False)
assert debug.read_layout().passphrase() == DA_50 assert debug.read_layout().passphrase() == CommonPass.LONGEST
enter_passphrase(debug) enter_passphrase(debug)
@ -323,9 +298,10 @@ def test_passphrase_dollar_sign_deletion(
def test_cycle_through_last_character( def test_cycle_through_last_character(
device_handler: "BackgroundDeviceHandler", device_handler: "BackgroundDeviceHandler",
): ):
# Checks that we can cycle through the last (50th) passphrase character # Checks that we can cycle through the last (128th) passphrase character
# (was a bug previously) # (was a bug previously)
with prepare_passphrase_dialogue(device_handler) as debug: with prepare_passphrase_dialogue(device_handler) as debug:
passphrase = DA_49 + "i" # for i we need to cycle through "ghi" three times # for "i" we need to cycle through "ghi" three times
passphrase = CommonPass.ALMOST_LONGEST + "i"
input_passphrase(debug, passphrase) input_passphrase(debug, passphrase)
enter_passphrase(debug) enter_passphrase(debug)

View File

@ -18,7 +18,7 @@ import random
import pytest import pytest
from trezorlib import device, exceptions, messages from trezorlib import device, exceptions, messages, models
from trezorlib.debuglink import LayoutType from trezorlib.debuglink import LayoutType
from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure from trezorlib.exceptions import TrezorFailure
@ -369,14 +369,17 @@ def test_passphrase_length(client: Client):
assert expected_result is False, "Call should have succeeded" assert expected_result is False, "Call should have succeeded"
assert e.code == FailureType.DataError assert e.code == FailureType.DataError
# 50 is ok max_len = 50 if client.model is models.T1B1 else 128
call(passphrase="A" * 50, expected_result=True)
# 51 is not # N is ok
call(passphrase="A" * 51, expected_result=False) call(passphrase="A" * max_len, expected_result=True)
# "š" has two bytes - 48x A and "š" should be fine (50 bytes) # N+1 is not
call(passphrase="A" * 48 + "š", expected_result=True) call(passphrase="A" * (max_len + 1), expected_result=False)
# "š" has two bytes - 49x A and "š" should not (51 bytes)
call(passphrase="A" * 49 + "š", expected_result=False) # "š" has two bytes - (N-2)x A and "š" should be fine (N bytes)
call(passphrase="A" * (max_len - 2) + "š", expected_result=True)
# "š" has two bytes - (N-1)x A and "š" should not (N+1 bytes)
call(passphrase="A" * (max_len - 1) + "š", expected_result=False)
@pytest.mark.models("core") @pytest.mark.models("core")