1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-07 07:12:34 +00:00

WIP - seed backup design

This commit is contained in:
grdddj 2022-11-14 08:37:37 +01:00
parent 2376891b83
commit 70a2eb5ffb
4 changed files with 108 additions and 58 deletions

View File

@ -980,8 +980,12 @@ pub fn text_multiline(
let mut line_text: String<50> = String::new();
'characters: loop {
if let Some(character) = text.chars().nth(characters_drawn) {
unwrap!(line_text.push(character));
characters_drawn += 1;
if character == '\n' {
// The line is forced to end.
break 'characters;
}
unwrap!(line_text.push(character));
} else {
// No more characters to draw.
break 'characters;
@ -994,11 +998,11 @@ pub fn text_multiline(
}
}
text_left(baseline, &line_text, font, fg_color, bg_color);
taken_from_top += line_height;
if characters_drawn == characters_overall {
// No more lines to draw.
break 'lines;
}
taken_from_top += line_height;
}
// Some of the area was unused and is free to draw some further text.
Some(area.split_top(taken_from_top).1)

View File

@ -2,31 +2,28 @@ use crate::{
micropython::buffer::StrBuffer,
ui::{
component::{Component, Event, EventCtx, Never, Paginate},
display::Font,
display::{text_multiline, Font},
geometry::{Offset, Rect},
model_tr::theme,
},
};
use heapless::{String, Vec};
use super::common::display_inverse;
use super::common::display;
const WORDS_PER_PAGE: usize = 3;
const EXTRA_LINE_HEIGHT: i16 = 3;
const EXTRA_LINE_HEIGHT: i16 = 2;
const NUMBER_X_OFFSET: i16 = 5;
const NUMBER_WORD_OFFSET: i16 = 20;
const NUMBER_FONT: Font = Font::DEMIBOLD;
const WORD_FONT: Font = Font::NORMAL;
/// Showing the given share words.
///
/// Displays them in inverse colors - black text on white background.
/// It is that because of the OLED side attack - lot of white noise makes
/// the attack much harder.
pub struct ShareWords<const N: usize> {
area: Rect,
share_words: Vec<StrBuffer, N>,
word_index: usize,
page_index: usize,
}
impl<const N: usize> ShareWords<N> {
@ -34,7 +31,79 @@ impl<const N: usize> ShareWords<N> {
Self {
area: Rect::zero(),
share_words,
word_index: 0,
page_index: 0,
}
}
fn word_index(&self) -> usize {
(self.page_index - 1) * WORDS_PER_PAGE
}
fn is_entry_page(&self) -> bool {
self.page_index == 0
}
fn is_final_page(&self) -> bool {
self.page_index == self.total_page_count() - 1
}
fn total_page_count(&self) -> usize {
let word_screens = if self.share_words.len() % WORDS_PER_PAGE == 0 {
self.share_words.len() / WORDS_PER_PAGE
} else {
self.share_words.len() / WORDS_PER_PAGE + 1
};
// One page before the words, one after it
1 + word_screens + 1
}
/// Display the first page with user information.
fn render_entry_page(&self) {
// TODO: will it be always 12, or do we need to check the length?
// It would need creating a String out of it, which is not ideal.
let free_area = text_multiline(
self.area,
"Write all 12\nwords in order on\nrecovery seed card.",
Font::BOLD,
theme::FG,
theme::BG,
);
if let Some(free_area) = free_area {
// Creating a small vertical distance
text_multiline(
free_area.split_top(3).1,
"Do NOT make\ndigital copies!",
Font::MONO,
theme::FG,
theme::BG,
);
}
}
/// Display the final page with user confirmation.
fn render_final_page(&self) {
// Moving vertically down to avoid collision with the scrollbar
// and to look better.
text_multiline(
self.area.split_top(12).1,
"I wrote down all\n12 words in order.",
Font::MONO,
theme::FG,
theme::BG,
);
}
/// Display current set of recovery words.
fn render_words(&self) {
let mut y_offset = 0;
// Showing the word index and the words itself
for i in 0..WORDS_PER_PAGE {
y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT;
let index = self.word_index() + i;
let word = self.share_words[index].clone();
let baseline = self.area.top_left() + Offset::new(NUMBER_X_OFFSET, y_offset);
display(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
}
}
}
@ -52,30 +121,24 @@ impl<const N: usize> Component for ShareWords<N> {
}
fn paint(&mut self) {
let mut y_offset = 0;
// Showing the word index and the words itself
for i in 0..WORDS_PER_PAGE {
y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT;
let index = self.word_index + i;
let word = self.share_words[index].clone();
let baseline = self.area.top_left() + Offset::new(NUMBER_X_OFFSET, y_offset);
display_inverse(baseline, &inttostr!(index as u8 + 1), NUMBER_FONT);
display_inverse(baseline + Offset::x(NUMBER_WORD_OFFSET), &word, WORD_FONT);
if self.is_entry_page() {
self.render_entry_page();
} else if self.is_final_page() {
self.render_final_page();
} else {
self.render_words();
}
}
}
impl<const N: usize> Paginate for ShareWords<N> {
fn page_count(&mut self) -> usize {
if self.share_words.len() % WORDS_PER_PAGE == 0 {
self.share_words.len() / WORDS_PER_PAGE
} else {
self.share_words.len() / WORDS_PER_PAGE + 1
}
// Not defining the logic here, as we do not want it to be `&mut`.
self.total_page_count()
}
fn change_page(&mut self, active_page: usize) {
self.word_index = active_page * WORDS_PER_PAGE;
self.page_index = active_page;
}
}
@ -84,12 +147,18 @@ impl<const N: usize> crate::trace::Trace for ShareWords<N> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ShareWords");
t.content_flag();
if self.is_entry_page() {
t.string("entry page");
} else if self.is_final_page() {
t.string("final page");
} else {
for i in 0..WORDS_PER_PAGE {
let index = self.word_index + i;
let index = self.word_index() + i;
let word = self.share_words[index].clone();
let content = build_string!(20, inttostr!(index as u8 + 1), " ", &word, "\n");
t.string(&content);
}
}
t.content_flag();
t.close();
}

View File

@ -524,13 +524,11 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?;
let confirm_btn =
Some(ButtonDetails::text("CONFIRM").with_duration(Duration::from_secs(1)));
Some(ButtonDetails::text("HOLD TO CONFIRM").with_duration(Duration::from_secs(1)));
let obj = LayoutObj::new(
ButtonPage::new_str(ShareWords::new(share_words), theme::FG)
.with_cancel_btn(None)
.with_confirm_btn(confirm_btn)
.with_scrollbar(false),
ButtonPage::new_str(ShareWords::new(share_words), theme::BG)
.with_confirm_btn(confirm_btn),
)?;
Ok(obj.into())
};

View File

@ -5,7 +5,7 @@ from trezor.enums import ButtonRequestType
import trezorui2
from ..common import interact
from . import RustLayout, _placeholder_confirm, confirm_action, get_bool
from . import RustLayout, confirm_action, get_bool
if TYPE_CHECKING:
from trezor import wire
@ -19,15 +19,6 @@ async def show_share_words(
share_index: int | None = None,
group_index: int | None = None,
) -> None:
await _placeholder_confirm(
ctx=ctx,
br_type="backup_words",
title="SHARE WORDS",
description="Write all words in order on recovery seed card.",
data="Do NOT make digital copies.",
br_code=ButtonRequestType.ResetDevice,
)
# Showing words, asking for write down confirmation and preparing for check
# until user accepts everything.
while True:
@ -42,18 +33,6 @@ async def show_share_words(
br_code=ButtonRequestType.ResetDevice,
)
wrote_down = await get_bool(
ctx=ctx,
title="SHARE WORDS",
data=f"I wrote down all {len(share_words)} words in order.",
verb="I WROTE DOWN",
hold=True,
br_type="backup_words",
br_code=ButtonRequestType.ResetDevice,
)
if not wrote_down:
continue
ready_to_check = await get_bool(
ctx=ctx,
title="CHECK PHRASE",