Skip to content

Commit 5207d30

Browse files
committed
Merge branch 'test-show-mnemonic'
2 parents 906968b + dc22ee5 commit 5207d30

File tree

6 files changed

+182
-13
lines changed

6 files changed

+182
-13
lines changed

src/rust/bitbox02-rust/src/hww/api/bip85.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub async fn process(
4444
/// https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39.
4545
async fn process_bip39(hal: &mut impl crate::hal::Hal) -> Result<(), Error> {
4646
use crate::workflow::trinary_choice::TrinaryChoice;
47-
use crate::workflow::{mnemonic, trinary_input_string};
47+
use crate::workflow::trinary_input_string;
4848

4949
hal.ui()
5050
.confirm(&confirm::Params {
@@ -123,7 +123,7 @@ async fn process_bip39(hal: &mut impl crate::hal::Hal) -> Result<(), Error> {
123123

124124
let mnemonic = keystore::bip85_bip39(num_words, index)?;
125125
let words: Vec<&str> = mnemonic.split(' ').collect();
126-
mnemonic::show_and_confirm_mnemonic(hal, &words).await?;
126+
hal.ui().show_and_confirm_mnemonic(&words).await?;
127127

128128
hal.ui().status("Finished", true).await;
129129

src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::pb;
2020
use pb::response::Response;
2121

2222
use crate::hal::Ui;
23-
use crate::workflow::{confirm, mnemonic, unlock};
23+
use crate::workflow::{confirm, unlock};
2424

2525
use crate::keystore;
2626

@@ -55,10 +55,156 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
5555

5656
let words: Vec<&str> = mnemonic_sentence.split(' ').collect();
5757

58-
mnemonic::show_and_confirm_mnemonic(hal, &words).await?;
58+
hal.ui().show_and_confirm_mnemonic(&words).await?;
5959

6060
bitbox02::memory::set_initialized().or(Err(Error::Memory))?;
6161

6262
hal.ui().status("Backup created", true).await;
6363
Ok(Response::Success(pb::Success {}))
6464
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use super::*;
69+
70+
use alloc::boxed::Box;
71+
72+
use crate::hal::testing::TestingHal;
73+
use crate::workflow::testing::Screen;
74+
use bitbox02::testing::mock_memory;
75+
use util::bb02_async::block_on;
76+
77+
/// When not yet initialized, we show the mnemonic without a password check. This happens during
78+
/// wallet setup.
79+
#[test]
80+
fn test_process_uninitialized() {
81+
mock_memory();
82+
bitbox02::keystore::encrypt_and_store_seed(
83+
hex::decode("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c")
84+
.unwrap()
85+
.as_slice(),
86+
"password",
87+
)
88+
.unwrap();
89+
90+
assert!(!bitbox02::memory::is_initialized());
91+
92+
let mut mock_hal = TestingHal::new();
93+
mock_hal.ui.set_enter_string(Box::new(|_params| {
94+
panic!("unexpected call to enter password")
95+
}));
96+
97+
bitbox02::securechip::fake_event_counter_reset();
98+
assert_eq!(
99+
block_on(process(&mut mock_hal)),
100+
Ok(Response::Success(pb::Success {}))
101+
);
102+
// 1 operation for one copy_seed() to get the seed to display it.
103+
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
104+
105+
assert_eq!(
106+
mock_hal.ui.screens,
107+
vec![
108+
Screen::Confirm {
109+
title: "Warning".into(),
110+
body: "DO NOT share your\nrecovery words with\nanyone!".into(),
111+
longtouch: false
112+
},
113+
Screen::Confirm {
114+
title: "Recovery\nwords".into(),
115+
body: "Please write down\nthe following words".into(),
116+
longtouch: false
117+
},
118+
Screen::ShowAndConfirmMnemonic {
119+
mnemonic: "shy parrot age monkey rhythm snake mystery burden topic hello mouse script gesture tattoo demand float verify shoe recycle cool network better aspect list".into(),
120+
},
121+
Screen::Status {
122+
title: "Backup created".into(),
123+
success: true
124+
},
125+
]
126+
);
127+
}
128+
/// When initialized, a password check is prompted before displaying the mnemonic.
129+
#[test]
130+
fn test_process_initialized() {
131+
mock_memory();
132+
bitbox02::keystore::encrypt_and_store_seed(
133+
hex::decode("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c")
134+
.unwrap()
135+
.as_slice(),
136+
"password",
137+
)
138+
.unwrap();
139+
140+
bitbox02::memory::set_initialized().unwrap();
141+
142+
let mut mock_hal = TestingHal::new();
143+
mock_hal
144+
.ui
145+
.set_enter_string(Box::new(|_params| Ok("password".into())));
146+
147+
bitbox02::securechip::fake_event_counter_reset();
148+
assert_eq!(
149+
block_on(process(&mut mock_hal)),
150+
Ok(Response::Success(pb::Success {}))
151+
);
152+
assert_eq!(bitbox02::securechip::fake_event_counter(), 7);
153+
154+
assert_eq!(
155+
mock_hal.ui.screens,
156+
vec![
157+
Screen::Confirm {
158+
title: "Warning".into(),
159+
body: "DO NOT share your\nrecovery words with\nanyone!".into(),
160+
longtouch: false
161+
},
162+
Screen::Confirm {
163+
title: "Recovery\nwords".into(),
164+
body: "Please write down\nthe following words".into(),
165+
longtouch: false
166+
},
167+
Screen::ShowAndConfirmMnemonic {
168+
mnemonic: "shy parrot age monkey rhythm snake mystery burden topic hello mouse script gesture tattoo demand float verify shoe recycle cool network better aspect list".into(),
169+
},
170+
Screen::Status {
171+
title: "Backup created".into(),
172+
success: true
173+
},
174+
]
175+
);
176+
}
177+
178+
/// When initialized, a password check is prompted before displaying the mnemonic.
179+
/// This tests that we fail early if the wrong password is entered.
180+
#[test]
181+
fn test_process_initialized_wrong_password() {
182+
mock_memory();
183+
bitbox02::keystore::encrypt_and_store_seed(
184+
hex::decode("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c")
185+
.unwrap()
186+
.as_slice(),
187+
"password",
188+
)
189+
.unwrap();
190+
191+
bitbox02::memory::set_initialized().unwrap();
192+
193+
let mut mock_hal = TestingHal::new();
194+
mock_hal
195+
.ui
196+
.set_enter_string(Box::new(|_params| Ok("wrong password".into())));
197+
198+
bitbox02::securechip::fake_event_counter_reset();
199+
assert_eq!(block_on(process(&mut mock_hal)), Err(Error::Generic));
200+
assert_eq!(bitbox02::securechip::fake_event_counter(), 5);
201+
202+
assert_eq!(
203+
mock_hal.ui.screens,
204+
vec![Screen::Status {
205+
title: "Wrong password\n9 tries remain".into(),
206+
success: false,
207+
}]
208+
);
209+
}
210+
}

src/rust/bitbox02-rust/src/workflow.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ pub trait Workflows {
7979
choices: &[&str],
8080
title: &str,
8181
) -> Result<u8, cancel::Error>;
82+
83+
/// Display the mnemonic words and have the user confirm them in a multiple-choice quiz.
84+
///
85+
/// The default implementation is implemented in terms of `self.show_mnemonic()`,
86+
/// `self.quiz_mnemonic_word()`, etc.
87+
///
88+
/// This function is defined in the HAL so unit tests can easily mock it. Real implementations
89+
/// should leave the default implementation.
90+
async fn show_and_confirm_mnemonic(&mut self, words: &[&str]) -> Result<(), cancel::Error>
91+
where
92+
Self: Sized,
93+
{
94+
mnemonic::show_and_confirm_mnemonic(self, words).await
95+
}
8296
}
8397

8498
pub struct RealWorkflows;

src/rust/bitbox02-rust/src/workflow/mnemonic.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ pub async fn confirm_word(choices: &[&str], title: &str) -> Result<u8, CancelErr
111111
}
112112

113113
pub async fn show_and_confirm_mnemonic(
114-
hal: &mut impl crate::hal::Hal,
114+
hal_ui: &mut impl crate::hal::Ui,
115115
words: &[&str],
116116
) -> Result<(), CancelError> {
117-
hal.ui()
117+
hal_ui
118118
.confirm(&confirm::Params {
119119
title: "",
120120
body: &format!("{} words follow", words.len()),
@@ -125,11 +125,10 @@ pub async fn show_and_confirm_mnemonic(
125125
.map_err(|_| CancelError::Cancelled)?;
126126

127127
// Part 1) Scroll through words
128-
hal.ui().show_mnemonic(words).await?;
128+
hal_ui.show_mnemonic(words).await?;
129129

130130
// Can only succeed due to `accept_only`.
131-
let _ = hal
132-
.ui()
131+
let _ = hal_ui
133132
.confirm(&confirm::Params {
134133
title: "",
135134
body: "Please confirm\neach word",
@@ -147,10 +146,10 @@ pub async fn show_and_confirm_mnemonic(
147146
choices.push("Back to\nrecovery words");
148147
let back_idx = (choices.len() - 1) as u8;
149148
loop {
150-
match hal.ui().quiz_mnemonic_word(&choices, &title).await? {
149+
match hal_ui.quiz_mnemonic_word(&choices, &title).await? {
151150
selected_idx if selected_idx == correct_idx => break,
152-
selected_idx if selected_idx == back_idx => hal.ui().show_mnemonic(words).await?,
153-
_ => hal.ui().status("Incorrect word\nTry again", false).await,
151+
selected_idx if selected_idx == back_idx => hal_ui.show_mnemonic(words).await?,
152+
_ => hal_ui.status("Incorrect word\nTry again", false).await,
154153
}
155154
}
156155
}

src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub async fn confirm_word(_choices: &[&str], _title: &str) -> Result<u8, CancelE
2626
}
2727

2828
pub async fn show_and_confirm_mnemonic(
29-
_hal: &mut impl crate::hal::Hal,
29+
_ui: &mut impl crate::hal::Ui,
3030
words: &[&str],
3131
) -> Result<(), CancelError> {
3232
for word in words.iter() {

src/rust/bitbox02-rust/src/workflow/testing.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub enum Screen {
4040
title: String,
4141
success: bool,
4242
},
43+
ShowAndConfirmMnemonic {
44+
mnemonic: String,
45+
},
4346
More,
4447
}
4548

@@ -169,6 +172,13 @@ impl Workflows for TestingWorkflows<'_> {
169172
) -> Result<u8, cancel::Error> {
170173
todo!("not used in unit tests yet");
171174
}
175+
176+
async fn show_and_confirm_mnemonic(&mut self, words: &[&str]) -> Result<(), cancel::Error> {
177+
self.screens.push(Screen::ShowAndConfirmMnemonic {
178+
mnemonic: words.join(" "),
179+
});
180+
Ok(())
181+
}
172182
}
173183

174184
impl<'a> TestingWorkflows<'a> {

0 commit comments

Comments
 (0)