1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-20 00:59:02 +00:00

feat(eckhart): add FIDO2 support

FidoCredential component, confirm_fido flow and icons
This commit is contained in:
Lukas Bielesch 2025-04-12 23:56:54 +02:00
parent fda73e8314
commit 2ade8f2217
50 changed files with 477 additions and 17 deletions

View File

@ -782,6 +782,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__amount;
MP_QSTR_words__are_you_sure;
MP_QSTR_words__array_of;
MP_QSTR_words__authenticate;
MP_QSTR_words__blockhash;
MP_QSTR_words__buying;
MP_QSTR_words__cancel_and_exit;

View File

@ -499,7 +499,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")]
fido__device_not_registered = 301, // "This device is not registered with this application."
#[cfg(feature = "universal_fw")]
fido__does_not_belong = 302, // "The credential you are trying to import does\nnot belong to this authenticator."
fido__does_not_belong = 302, // {"Bolt": "The credential you are trying to import does\nnot belong to this authenticator.", "Caesar": "The credential you are trying to import does\nnot belong to this authenticator.", "Delizia": "The credential you are trying to import does\nnot belong to this authenticator.", "Eckhart": "The credential you are trying to import does not belong to this authenticator."}
#[cfg(feature = "universal_fw")]
fido__erase_credentials = 303, // "erase all credentials?"
#[cfg(feature = "universal_fw")]
@ -1419,6 +1419,7 @@ pub enum TranslatedString {
send__sign_cancelled = 1005, // "Sign cancelled."
words__send = 1006, // "Send"
words__wallet = 1007, // "Wallet"
words__authenticate = 1008, // "Authenticate"
}
impl TranslatedString {
@ -1921,8 +1922,18 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")]
Self::fido__device_not_registered => "This device is not registered with this application.",
#[cfg(feature = "universal_fw")]
#[cfg(feature = "layout_bolt")]
Self::fido__does_not_belong => "The credential you are trying to import does\nnot belong to this authenticator.",
#[cfg(feature = "universal_fw")]
#[cfg(feature = "layout_caesar")]
Self::fido__does_not_belong => "The credential you are trying to import does\nnot belong to this authenticator.",
#[cfg(feature = "universal_fw")]
#[cfg(feature = "layout_delizia")]
Self::fido__does_not_belong => "The credential you are trying to import does\nnot belong to this authenticator.",
#[cfg(feature = "universal_fw")]
#[cfg(feature = "layout_eckhart")]
Self::fido__does_not_belong => "The credential you are trying to import does not belong to this authenticator.",
#[cfg(feature = "universal_fw")]
Self::fido__erase_credentials => "erase all credentials?",
#[cfg(feature = "universal_fw")]
Self::fido__export_credentials => "Export information about the credentials stored on this device?",
@ -2939,6 +2950,7 @@ impl TranslatedString {
Self::send__sign_cancelled => "Sign cancelled.",
Self::words__send => "Send",
Self::words__wallet => "Wallet",
Self::words__authenticate => "Authenticate",
}
}
@ -4353,6 +4365,7 @@ impl TranslatedString {
Qstr::MP_QSTR_send__sign_cancelled => Some(Self::send__sign_cancelled),
Qstr::MP_QSTR_words__send => Some(Self::words__send),
Qstr::MP_QSTR_words__wallet => Some(Self::words__wallet),
Qstr::MP_QSTR_words__authenticate => Some(Self::words__authenticate),
_ => None,
}
}

View File

@ -0,0 +1,95 @@
use crate::{
strutil::TString,
ui::{
component::{
image::Image,
paginated::SinglePage,
text::{
paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
TextStyle,
},
Component, Event, EventCtx, LineBreaking,
},
geometry::{Insets, LinearPlacement, Offset, Rect},
shape::Renderer,
},
};
use super::super::{firmware::fido_icons::get_fido_icon_data, theme};
pub struct FidoCredential<F: Fn() -> TString<'static>> {
app_icon: Option<Image>,
text: Paragraphs<ParagraphVecShort<'static>>,
get_account: F,
}
impl<F: Fn() -> TString<'static>> FidoCredential<F> {
const ICON_SIZE: i16 = 32;
const SPACING: i16 = 24;
pub fn new(
icon_name: Option<TString<'static>>,
app_name: TString<'static>,
get_account: F,
) -> Self {
const STYLE: TextStyle =
theme::TEXT_REGULAR.with_line_breaking(LineBreaking::BreakWordsNoHyphen);
let app_icon = get_fido_icon_data(icon_name).map(Image::new);
let text = ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_MEDIUM_GREY, app_name),
Paragraph::new(&STYLE, (get_account)()),
])
.into_paragraphs()
.with_placement(LinearPlacement::vertical())
.with_spacing(Self::SPACING);
Self {
app_icon,
text,
get_account,
}
}
}
impl<F: Fn() -> TString<'static>> Component for FidoCredential<F> {
type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect {
let icon_size = self.app_icon.map_or(Offset::zero(), |i| i.toif.size());
let (icon_area, text_area) = bounds.split_top(icon_size.y);
let text_area = text_area.inset(Insets::top(Self::SPACING));
self.text.place(text_area);
// let text_height = self.text.area().height();
// let vertical_space = bounds.height() - icon_size.y - Self::SPACING -
// text_height; let off = Offset::y(vertical_space / 2);
// let icon_area = icon_area.with_width(icon_size.x).translate(off);
// let text_area = text_area.with_height(text_height).translate(off);
self.app_icon.place(icon_area);
self.text.place(text_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Attach(_) = event {
self.text.mutate(|p| p[1].update((self.get_account)()));
ctx.request_paint();
}
self.app_icon.event(ctx, event);
self.text.event(ctx, event);
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.app_icon.render(target);
self.text.render(target);
}
}
impl<F: Fn() -> TString<'static>> SinglePage for FidoCredential<F> {}
#[cfg(feature = "ui_debug")]
impl<F: Fn() -> TString<'static>> crate::trace::Trace for FidoCredential<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("FidoCredential");
}
}

View File

@ -0,0 +1,80 @@
//! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`)
//! do not edit manually!
use crate::strutil::TString;
use crate::ui::util::include_res;
const ICON_APPLE: &[u8] = include_res!("layout_eckhart/res/fido/icon_apple.toif");
const ICON_AWS: &[u8] = include_res!("layout_eckhart/res/fido/icon_aws.toif");
const ICON_BINANCE: &[u8] = include_res!("layout_eckhart/res/fido/icon_binance.toif");
const ICON_BITBUCKET: &[u8] = include_res!("layout_eckhart/res/fido/icon_bitbucket.toif");
const ICON_BITFINEX: &[u8] = include_res!("layout_eckhart/res/fido/icon_bitfinex.toif");
const ICON_BITWARDEN: &[u8] = include_res!("layout_eckhart/res/fido/icon_bitwarden.toif");
const ICON_CLOUDFLARE: &[u8] = include_res!("layout_eckhart/res/fido/icon_cloudflare.toif");
const ICON_COINBASE: &[u8] = include_res!("layout_eckhart/res/fido/icon_coinbase.toif");
const ICON_DASHLANE: &[u8] = include_res!("layout_eckhart/res/fido/icon_dashlane.toif");
const ICON_DROPBOX: &[u8] = include_res!("layout_eckhart/res/fido/icon_dropbox.toif");
const ICON_DUO: &[u8] = include_res!("layout_eckhart/res/fido/icon_duo.toif");
const ICON_FACEBOOK: &[u8] = include_res!("layout_eckhart/res/fido/icon_facebook.toif");
const ICON_FASTMAIL: &[u8] = include_res!("layout_eckhart/res/fido/icon_fastmail.toif");
const ICON_FEDORA: &[u8] = include_res!("layout_eckhart/res/fido/icon_fedora.toif");
const ICON_GANDI: &[u8] = include_res!("layout_eckhart/res/fido/icon_gandi.toif");
const ICON_GEMINI: &[u8] = include_res!("layout_eckhart/res/fido/icon_gemini.toif");
const ICON_GITHUB: &[u8] = include_res!("layout_eckhart/res/fido/icon_github.toif");
const ICON_GITLAB: &[u8] = include_res!("layout_eckhart/res/fido/icon_gitlab.toif");
const ICON_GOOGLE: &[u8] = include_res!("layout_eckhart/res/fido/icon_google.toif");
const ICON_INVITY: &[u8] = include_res!("layout_eckhart/res/fido/icon_invity.toif");
const ICON_KEEPER: &[u8] = include_res!("layout_eckhart/res/fido/icon_keeper.toif");
const ICON_KRAKEN: &[u8] = include_res!("layout_eckhart/res/fido/icon_kraken.toif");
const ICON_LOGIN_GOV: &[u8] = include_res!("layout_eckhart/res/fido/icon_login.gov.toif");
const ICON_MICROSOFT: &[u8] = include_res!("layout_eckhart/res/fido/icon_microsoft.toif");
const ICON_MOJEID: &[u8] = include_res!("layout_eckhart/res/fido/icon_mojeid.toif");
const ICON_NAMECHEAP: &[u8] = include_res!("layout_eckhart/res/fido/icon_namecheap.toif");
const ICON_PROTON: &[u8] = include_res!("layout_eckhart/res/fido/icon_proton.toif");
const ICON_SLUSHPOOL: &[u8] = include_res!("layout_eckhart/res/fido/icon_slushpool.toif");
const ICON_STRIPE: &[u8] = include_res!("layout_eckhart/res/fido/icon_stripe.toif");
const ICON_TUTANOTA: &[u8] = include_res!("layout_eckhart/res/fido/icon_tutanota.toif");
/// Translates icon name into its data.
pub fn get_fido_icon_data(icon_name: Option<TString<'static>>) -> Option< &'static [u8]> {
if let Some(icon_name) = icon_name {
icon_name.map(|c| match c {
"apple" => Some(ICON_APPLE),
"aws" => Some(ICON_AWS),
"binance" => Some(ICON_BINANCE),
"bitbucket" => Some(ICON_BITBUCKET),
"bitfinex" => Some(ICON_BITFINEX),
"bitwarden" => Some(ICON_BITWARDEN),
"cloudflare" => Some(ICON_CLOUDFLARE),
"coinbase" => Some(ICON_COINBASE),
"dashlane" => Some(ICON_DASHLANE),
"dropbox" => Some(ICON_DROPBOX),
"duo" => Some(ICON_DUO),
"facebook" => Some(ICON_FACEBOOK),
"fastmail" => Some(ICON_FASTMAIL),
"fedora" => Some(ICON_FEDORA),
"gandi" => Some(ICON_GANDI),
"gemini" => Some(ICON_GEMINI),
"github" => Some(ICON_GITHUB),
"gitlab" => Some(ICON_GITLAB),
"google" => Some(ICON_GOOGLE),
"invity" => Some(ICON_INVITY),
"keeper" => Some(ICON_KEEPER),
"kraken" => Some(ICON_KRAKEN),
"login.gov" => Some(ICON_LOGIN_GOV),
"microsoft" => Some(ICON_MICROSOFT),
"mojeid" => Some(ICON_MOJEID),
"namecheap" => Some(ICON_NAMECHEAP),
"proton" => Some(ICON_PROTON),
"slushpool" => Some(ICON_SLUSHPOOL),
"stripe" => Some(ICON_STRIPE),
"tutanota" => Some(ICON_TUTANOTA),
_ => None,
})
} else {
None
}
}

View File

@ -0,0 +1,35 @@
//! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`)
//! do not edit manually!
use crate::strutil::TString;
use crate::ui::util::include_res;
<%
icons: list[tuple[str, str]] = []
for app in fido:
if app.icon is not None:
# Variable names cannot have a dot in themselves
icon_name = app.key
var_name = icon_name.replace(".", "_").upper()
icons.append((icon_name, var_name))
%>\
% for icon_name, var_name in icons:
const ICON_${var_name}: &[u8] = include_res!("layout_eckhart/res/fido/icon_${icon_name}.toif");
% endfor
/// Translates icon name into its data.
pub fn get_fido_icon_data(icon_name: Option<TString<'static>>) -> Option< &'static [u8]> {
if let Some(icon_name) = icon_name {
icon_name.map(|c| match c {
% for icon_name, var_name in icons:
"${icon_name}" => Some(ICON_${var_name}),
% endfor
_ => None,
})
} else {
None
}
}

View File

@ -2,6 +2,9 @@ mod action_bar;
mod brightness_screen;
mod confirm_homescreen;
mod device_menu_screen;
mod fido;
#[rustfmt::skip]
mod fido_icons;
mod header;
mod hint;
mod hold_to_confirm;
@ -19,6 +22,7 @@ pub use action_bar::{ActionBar, ActionBarMsg};
pub use brightness_screen::SetBrightnessScreen;
pub use confirm_homescreen::{ConfirmHomescreen, ConfirmHomescreenMsg};
pub use device_menu_screen::{DeviceMenuMsg, DeviceMenuScreen};
pub use fido::FidoCredential;
pub use header::{Header, HeaderMsg};
pub use hint::Hint;
pub use hold_to_confirm::HoldToConfirmAnim;

View File

@ -0,0 +1,168 @@
use crate::{
error,
micropython::{gc::Gc, list::List},
strutil::TString,
translations::TR,
ui::{
component::{
base::ComponentExt,
text::paragraphs::{Paragraph, ParagraphSource},
},
flow::{
base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow,
},
geometry::{Direction, LinearPlacement},
},
};
use super::super::{
component::Button,
firmware::{
ActionBar, FidoCredential, Header, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme,
};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmFido {
Intro,
ChooseCredential,
Authenticate,
Menu,
}
static CRED_SELECTED: AtomicUsize = AtomicUsize::new(0);
static SINGLE_CRED: AtomicBool = AtomicBool::new(false);
const EXTRA_PADDING: i16 = 6;
impl FlowController for ConfirmFido {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, _direction: Direction) -> Decision {
self.do_nothing()
}
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) {
(Self::Intro, FlowMsg::Confirmed) => Self::ChooseCredential.goto(),
(Self::ChooseCredential, FlowMsg::Choice(i)) => {
CRED_SELECTED.store(i, Ordering::Relaxed);
Self::Authenticate.goto()
}
(_, FlowMsg::Info) => Self::Menu.goto(),
(Self::Authenticate, FlowMsg::Cancelled) => Self::ChooseCredential.goto(),
(Self::Authenticate, FlowMsg::Confirmed) => {
self.return_msg(FlowMsg::Choice(CRED_SELECTED.load(Ordering::Relaxed)))
}
(Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Cancelled) => {
if single_cred() {
Self::Authenticate.goto()
} else {
Self::Intro.goto()
}
}
_ => self.do_nothing(),
}
}
}
fn single_cred() -> bool {
SINGLE_CRED.load(Ordering::Relaxed)
}
pub fn new_confirm_fido(
title: TString<'static>,
app_name: TString<'static>,
icon_name: Option<TString<'static>>,
accounts: Gc<List>,
) -> Result<SwipeFlow, error::Error> {
let num_accounts = accounts.len();
SINGLE_CRED.store(num_accounts <= 1, Ordering::Relaxed);
CRED_SELECTED.store(0, Ordering::Relaxed);
let content_intro = TextScreen::new(
Paragraph::new::<TString>(&theme::TEXT_REGULAR, TR::fido__select_intro.into())
.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(Header::new(title).with_menu_button())
.with_action_bar(ActionBar::new_single(Button::with_text(
TR::buttons__continue.into(),
)))
.map(|msg| match msg {
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
TextScreenMsg::Menu => Some(FlowMsg::Info),
_ => None,
});
let mut credentials = VerticalMenu::empty();
for i in 0..num_accounts {
let account = unwrap!(accounts.get(i));
let label = account
.try_into()
.unwrap_or_else(|_| TString::from_str("-"));
credentials = credentials.item(Button::with_text(label));
}
let content_choose_credential = VerticalMenuScreen::new(credentials)
.with_header(Header::new(TR::fido__title_select_credential.into()))
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
_ => None,
});
let get_account = move || {
let current = CRED_SELECTED.load(Ordering::Relaxed);
let account = unwrap!(accounts.get(current));
account.try_into().unwrap_or_else(|_| TString::from_str(""))
};
let mut auth_header = Header::new(TR::fido__title_credential_details.into());
auth_header = if single_cred() {
auth_header.with_menu_button()
} else {
auth_header.with_close_button()
};
let content_authenticate =
TextScreen::new(FidoCredential::new(icon_name, app_name, get_account))
.with_header(auth_header)
.with_action_bar(ActionBar::new_single(
Button::with_text(TR::words__authenticate.into()).styled(theme::button_confirm()),
))
.map(|msg| match msg {
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
TextScreenMsg::Menu => Some(FlowMsg::Info),
_ => None,
});
let content_menu = VerticalMenuScreen::new(VerticalMenu::empty().item(
Button::with_text(TR::buttons__cancel.into()).styled(theme::menu_item_title_orange()),
))
.with_header(Header::new(TR::fido__title_authenticate.into()).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(0) => Some(FlowMsg::Choice(0)),
VerticalMenuScreenMsg::Menu => Some(FlowMsg::Info),
_ => None,
});
let initial_page = if single_cred() {
&ConfirmFido::Authenticate
} else {
&ConfirmFido::Intro
};
let mut flow = SwipeFlow::new(initial_page)?;
flow.add_page(&ConfirmFido::Intro, content_intro)?
.add_page(&ConfirmFido::ChooseCredential, content_choose_credential)?
.add_page(&ConfirmFido::Authenticate, content_authenticate)?
.add_page(&ConfirmFido::Menu, content_menu)?;
Ok(flow)
}

View File

@ -1,3 +1,4 @@
pub mod confirm_fido;
pub mod confirm_output;
pub mod confirm_reset;
pub mod confirm_set_new_pin;
@ -9,6 +10,7 @@ pub mod request_passphrase;
pub mod show_danger;
pub mod show_share_words;
pub use confirm_fido::new_confirm_fido;
pub use confirm_output::new_confirm_output;
pub use confirm_reset::new_confirm_reset;
pub use confirm_set_new_pin::new_set_new_pin;

View File

@ -53,6 +53,10 @@ pub const TEXT_MEDIUM: TextStyle = TextStyle::new(
GREY_LIGHT,
GREY_LIGHT,
);
pub const TEXT_MEDIUM_GREY: TextStyle =
TextStyle::new(fonts::FONT_SATOSHI_MEDIUM_26, GREY, BG, GREY, GREY);
/// TT Satoshi Regular - 22 (Screen title, Hint, PageCounter, Secondary info)
/// with negative line spacing to make it more compact
pub const TEXT_SMALL: TextStyle =

View File

@ -158,13 +158,13 @@ impl FirmwareUI for UIEckhart {
}
fn confirm_fido(
_title: TString<'static>,
_app_name: TString<'static>,
_icon: Option<TString<'static>>,
_accounts: Gc<List>,
title: TString<'static>,
app_name: TString<'static>,
icon: Option<TString<'static>>,
accounts: Gc<List>,
) -> Result<impl LayoutMaybeTrace, Error> {
#[cfg(feature = "universal_fw")]
return Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"));
return flow::confirm_fido::new_confirm_fido(title, app_name, icon, accounts);
#[cfg(not(feature = "universal_fw"))]
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(
c"confirm_fido not used in bitcoin-only firmware",
@ -727,8 +727,6 @@ impl FirmwareUI for UIEckhart {
.with_placement(LinearPlacement::vertical());
let action_bar = if allow_cancel {
ActionBar::new_single(Button::with_text(button))
} else {
ActionBar::new_double(
Button::with_icon(theme::ICON_CLOSE),
Button::with_text(button),

View File

@ -950,6 +950,7 @@ class TR:
words__amount: str = "Amount"
words__are_you_sure: str = "Are you sure?"
words__array_of: str = "Array of"
words__authenticate: str = "Authenticate"
words__blockhash: str = "Blockhash"
words__buying: str = "Buying"
words__cancel_and_exit: str = "Cancel and exit"

View File

@ -8,8 +8,7 @@ async def add_resident_credential(msg: WebAuthnAddResidentCredential) -> Success
import storage.device as storage_device
from trezor import TR, wire
from trezor.messages import Success
from trezor.ui.layouts import show_error_and_raise
from trezor.ui.layouts.fido import confirm_fido
from trezor.ui.layouts.fido import confirm_fido, credential_warning
from .credential import Fido2Credential
from .resident_credentials import store_resident_credential
@ -22,7 +21,7 @@ async def add_resident_credential(msg: WebAuthnAddResidentCredential) -> Success
try:
cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None)
except Exception:
await show_error_and_raise(
await credential_warning(
"warning_credential",
TR.fido__does_not_belong,
)

View File

@ -1,9 +1,15 @@
from typing import TYPE_CHECKING
import trezorui_api
from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import show_error_and_raise
from ..common import interact
if TYPE_CHECKING:
from typing import NoReturn
async def confirm_fido(
header: str,
@ -60,3 +66,10 @@ async def confirm_fido_reset() -> bool:
)
)
return (await confirm.get_result()) is trezorui_api.CONFIRMED
async def credential_warning(br_name: str, content: str) -> NoReturn:
await show_error_and_raise(
br_name=br_name,
content=content,
)

View File

@ -1,9 +1,15 @@
from typing import TYPE_CHECKING
import trezorui_api
from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import show_error_and_raise
from ..common import interact
if TYPE_CHECKING:
from typing import NoReturn
async def confirm_fido(
header: str,
@ -42,3 +48,10 @@ async def confirm_fido_reset() -> bool:
verb=TR.buttons__confirm,
)
return (await ui.Layout(confirm).get_result()) is trezorui_api.CONFIRMED
async def credential_warning(br_name: str, content: str) -> NoReturn:
await show_error_and_raise(
br_name=br_name,
content=content,
)

View File

@ -1,9 +1,15 @@
from typing import TYPE_CHECKING
import trezorui_api
from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import show_error_and_raise
from ..common import interact
if TYPE_CHECKING:
from typing import NoReturn
async def confirm_fido(
header: str,
@ -52,3 +58,10 @@ async def confirm_fido_reset() -> bool:
)
)
return (await confirm.get_result()) is trezorui_api.CONFIRMED
async def credential_warning(br_name: str, content: str) -> NoReturn:
await show_error_and_raise(
br_name=br_name,
content=content,
)

View File

@ -1,9 +1,15 @@
from typing import TYPE_CHECKING
import trezorui_api
from trezor import ui
from trezor import ui, TR
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import show_error_and_raise
from ..common import interact
if TYPE_CHECKING:
from typing import NoReturn
async def confirm_fido(
header: str,
@ -52,3 +58,11 @@ async def confirm_fido_reset() -> bool:
)
)
return (await confirm.get_result()) is trezorui_api.CONFIRMED
async def credential_warning(br_name: str, content: str) -> NoReturn:
await show_error_and_raise(
br_name=br_name,
content=content,
subheader=TR.words__pay_attention,
)

View File

@ -364,7 +364,12 @@
"fido__device_already_registered": "This device is already registered with this application.",
"fido__device_already_registered_with_template": "This device is already registered with {0}.",
"fido__device_not_registered": "This device is not registered with this application.",
"fido__does_not_belong": "The credential you are trying to import does\nnot belong to this authenticator.",
"fido__does_not_belong": {
"Bolt": "The credential you are trying to import does\nnot belong to this authenticator.",
"Caesar": "The credential you are trying to import does\nnot belong to this authenticator.",
"Delizia": "The credential you are trying to import does\nnot belong to this authenticator.",
"Eckhart": "The credential you are trying to import does not belong to this authenticator."
},
"fido__erase_credentials": "erase all credentials?",
"fido__export_credentials": "Export information about the credentials stored on this device?",
"fido__more_credentials": "More credentials",
@ -1022,6 +1027,7 @@
"words__amount": "Amount",
"words__are_you_sure": "Are you sure?",
"words__array_of": "Array of",
"words__authenticate": "Authenticate",
"words__blockhash": "Blockhash",
"words__buying": "Buying",
"words__cancel_and_exit": "Cancel and exit",

View File

@ -1006,5 +1006,6 @@
"1004": "send__send_in_the_app",
"1005": "send__sign_cancelled",
"1006": "words__send",
"1007": "words__wallet"
"1007": "words__wallet",
"1008": "words__authenticate"
}

View File

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "32a4bda1a1ee4fdcef587ba5a1ab26ce37902bc1714e87e77cda9c98bb913c76",
"datetime": "2025-04-08T10:31:11.342473",
"commit": "b6db7571e9e73304b7229b03970662c117b3a1df"
"merkle_root": "51fbfb5399c7ad544c547c2b3b435d854c4dc0de8b8c8cf474f727964ae482e7",
"datetime": "2025-04-11T10:27:48.914997",
"commit": "4f65836acfde34c5898396b22d15298554d79de5"
},
"history": [
{