mnemonic: words import and errors check refactoring

This commit is contained in:
ardocrat 2024-08-10 02:35:42 +03:00
parent 86fbf2e14f
commit cb9e86750c
5 changed files with 229 additions and 141 deletions

View file

@ -143,26 +143,19 @@ impl WalletCreation {
// Setup step description text and availability. // Setup step description text and availability.
let (step_text, mut step_available) = match step { let (step_text, mut step_available) = match step {
Step::EnterMnemonic => { Step::EnterMnemonic => {
let mode = &self.mnemonic_setup.mnemonic.mode; let mode = &self.mnemonic_setup.mnemonic.mode();
let text = if mode == &PhraseMode::Generate { let (text, available) = match mode {
t!("wallets.create_phrase_desc") PhraseMode::Generate => (t!("wallets.create_phrase_desc"), true),
} else { PhraseMode::Import => {
t!("wallets.restore_phrase_desc") let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid();
(t!("wallets.restore_phrase_desc"), available)
}
}; };
let available = !self
.mnemonic_setup
.mnemonic
.words
.contains(&String::from(""));
(text, available) (text, available)
} }
Step::ConfirmMnemonic => { Step::ConfirmMnemonic => {
let text = t!("wallets.restore_phrase_desc"); let text = t!("wallets.restore_phrase_desc");
let available = !self let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid();
.mnemonic_setup
.mnemonic
.confirm_words
.contains(&String::from(""));
(text, available) (text, available)
} }
Step::SetupConnection => { Step::SetupConnection => {
@ -170,8 +163,11 @@ impl WalletCreation {
} }
}; };
// Show step description or error if entered phrase is not valid. // Show step description or error.
if self.mnemonic_setup.valid_phrase && self.creation_error.is_none() { let generate_step = step == Step::EnterMnemonic &&
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
generate_step {
ui.add_space(2.0); ui.add_space(2.0);
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray())); ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
ui.add_space(2.0); ui.add_space(2.0);
@ -185,6 +181,7 @@ impl WalletCreation {
.color(Colors::red())); .color(Colors::red()));
ui.add_space(10.0); ui.add_space(10.0);
} else { } else {
ui.add_space(2.0);
ui.label(RichText::new(&t!("wallets.not_valid_phrase")) ui.label(RichText::new(&t!("wallets.not_valid_phrase"))
.size(16.0) .size(16.0)
.color(Colors::red())); .color(Colors::red()));
@ -225,8 +222,8 @@ impl WalletCreation {
} else { } else {
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase()); let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
View::button(ui, paste_text, Colors::white_or_black(false), || { View::button(ui, paste_text, Colors::white_or_black(false), || {
let data = ZeroingString::from(cb.get_string_from_buffer().trim()); let data = ZeroingString::from(cb.get_string_from_buffer());
self.mnemonic_setup.mnemonic.import_text(&data, true); self.mnemonic_setup.mnemonic.import(&data);
}); });
} }
ui.add_space(4.0); ui.add_space(4.0);
@ -241,7 +238,7 @@ impl WalletCreation {
/// Draw copy or paste button at [`Step::EnterMnemonic`]. /// Draw copy or paste button at [`Step::EnterMnemonic`].
fn copy_or_paste_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { fn copy_or_paste_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
match self.mnemonic_setup.mnemonic.mode { match self.mnemonic_setup.mnemonic.mode() {
PhraseMode::Generate => { PhraseMode::Generate => {
// Show copy button. // Show copy button.
let c_t = format!("{} {}", COPY, t!("copy").to_uppercase()); let c_t = format!("{} {}", COPY, t!("copy").to_uppercase());
@ -253,8 +250,8 @@ impl WalletCreation {
// Show paste button. // Show paste button.
let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase()); let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
View::button(ui, p_t, Colors::white_or_black(false), || { View::button(ui, p_t, Colors::white_or_black(false), || {
let data = ZeroingString::from(cb.get_string_from_buffer().trim()); let data = ZeroingString::from(cb.get_string_from_buffer());
self.mnemonic_setup.mnemonic.import_text(&data, false); self.mnemonic_setup.mnemonic.import(&data);
}); });
} }
} }
@ -278,15 +275,10 @@ impl WalletCreation {
self.step = if let Some(step) = &self.step { self.step = if let Some(step) = &self.step {
match step { match step {
Step::EnterMnemonic => { Step::EnterMnemonic => {
if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate { if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate {
Some(Step::ConfirmMnemonic) Some(Step::ConfirmMnemonic)
} else { } else {
// Check if entered phrase was valid. Some(Step::SetupConnection)
if self.mnemonic_setup.valid_phrase {
Some(Step::SetupConnection)
} else {
Some(Step::EnterMnemonic)
}
} }
} }
Step::ConfirmMnemonic => { Step::ConfirmMnemonic => {
@ -388,7 +380,10 @@ impl WalletCreation {
self.creation_error = None; self.creation_error = None;
}, },
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
Step::SetupConnection => self.step = Some(Step::EnterMnemonic) Step::SetupConnection => {
self.creation_error = None;
self.step = Some(Step::EnterMnemonic)
}
} }
} }
} }

View file

@ -20,21 +20,18 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, Modal, Content, View}; use crate::gui::views::{CameraContent, Modal, Content, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions}; use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions};
use crate::wallet::Mnemonic; use crate::wallet::Mnemonic;
use crate::wallet::types::{PhraseMode, PhraseSize}; use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord};
/// Mnemonic phrase setup content. /// Mnemonic phrase setup content.
pub struct MnemonicSetup { pub struct MnemonicSetup {
/// Current mnemonic phrase. /// Current mnemonic phrase.
pub(crate) mnemonic: Mnemonic, pub mnemonic: Mnemonic,
/// Flag to check if entered phrase was valid.
pub(crate) valid_phrase: bool,
/// Current word number to edit at [`Modal`]. /// Current word number to edit at [`Modal`].
word_num_edit: usize, word_index_edit: usize,
/// Entered word value for [`Modal`]. /// Entered word value for [`Modal`].
word_edit: String, word_edit: String,
/// Flag to check if entered word is valid. /// Flag to check if entered word is valid at [`Modal`].
valid_word_edit: bool, valid_word_edit: bool,
/// Camera content for QR scan [`Modal`]. /// Camera content for QR scan [`Modal`].
@ -56,8 +53,7 @@ impl Default for MnemonicSetup {
fn default() -> Self { fn default() -> Self {
Self { Self {
mnemonic: Mnemonic::default(), mnemonic: Mnemonic::default(),
valid_phrase: true, word_index_edit: 0,
word_num_edit: 0,
word_edit: String::from(""), word_edit: String::from(""),
valid_word_edit: true, valid_word_edit: true,
camera_content: CameraContent::default(), camera_content: CameraContent::default(),
@ -103,7 +99,7 @@ impl MnemonicSetup {
ui.add_space(6.0); ui.add_space(6.0);
// Show words setup. // Show words setup.
self.word_list_ui(ui, self.mnemonic.mode == PhraseMode::Import, cb); self.word_list_ui(ui, self.mnemonic.mode() == PhraseMode::Import, cb);
} }
/// Draw content for phrase confirmation step. /// Draw content for phrase confirmation step.
@ -123,7 +119,7 @@ impl MnemonicSetup {
/// Draw mode and size setup. /// Draw mode and size setup.
fn mode_type_ui(&mut self, ui: &mut egui::Ui) { fn mode_type_ui(&mut self, ui: &mut egui::Ui) {
// Show mode setup. // Show mode setup.
let mut mode = self.mnemonic.mode.clone(); let mut mode = self.mnemonic.mode();
ui.columns(2, |columns| { ui.columns(2, |columns| {
columns[0].vertical_centered(|ui| { columns[0].vertical_centered(|ui| {
let create_mode = PhraseMode::Generate; let create_mode = PhraseMode::Generate;
@ -136,8 +132,8 @@ impl MnemonicSetup {
View::radio_value(ui, &mut mode, import_mode, import_text); View::radio_value(ui, &mut mode, import_mode, import_text);
}); });
}); });
if mode != self.mnemonic.mode { if mode != self.mnemonic.mode() {
self.mnemonic.set_mode(mode) self.mnemonic.set_mode(mode);
} }
ui.add_space(10.0); ui.add_space(10.0);
@ -150,7 +146,7 @@ impl MnemonicSetup {
ui.add_space(6.0); ui.add_space(6.0);
// Show mnemonic phrase size setup. // Show mnemonic phrase size setup.
let mut size = self.mnemonic.size.clone(); let mut size = self.mnemonic.size();
ui.columns(5, |columns| { ui.columns(5, |columns| {
for (index, word) in PhraseSize::VALUES.iter().enumerate() { for (index, word) in PhraseSize::VALUES.iter().enumerate() {
columns[index].vertical_centered(|ui| { columns[index].vertical_centered(|ui| {
@ -159,29 +155,20 @@ impl MnemonicSetup {
}); });
} }
}); });
if size != self.mnemonic.size { if size != self.mnemonic.size() {
self.mnemonic.set_size(size); self.mnemonic.set_size(size);
} }
} }
/// Draw list of words for mnemonic phrase. /// Draw grid of words for mnemonic phrase.
fn word_list_ui(&mut self, ui: &mut egui::Ui, edit_words: bool, cb: &dyn PlatformCallbacks) { fn word_list_ui(&mut self, ui: &mut egui::Ui, edit: bool, cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
ui.scope(|ui| { ui.scope(|ui| {
// Setup spacing between columns. // Setup spacing between columns.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 6.0); ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 6.0);
// Select list of words based on current mode and edit flag. // Select list of words based on current mode and edit flag.
let words = match self.mnemonic.mode { let words = self.mnemonic.words(edit);
PhraseMode::Generate => {
if edit_words {
&self.mnemonic.confirm_words
} else {
&self.mnemonic.words
}
}
PhraseMode::Import => &self.mnemonic.words
}.clone();
let mut word_number = 0; let mut word_number = 0;
let cols = list_columns_count(ui); let cols = list_columns_count(ui);
@ -192,25 +179,25 @@ impl MnemonicSetup {
ui.columns(cols, |columns| { ui.columns(cols, |columns| {
columns[0].horizontal(|ui| { columns[0].horizontal(|ui| {
let word = chunk.get(0).unwrap(); let word = chunk.get(0).unwrap();
self.word_item_ui(ui, word_number, word, edit_words, cb); self.word_item_ui(ui, word_number, word, edit, cb);
}); });
columns[1].horizontal(|ui| { columns[1].horizontal(|ui| {
word_number += 1; word_number += 1;
let word = chunk.get(1).unwrap(); let word = chunk.get(1).unwrap();
self.word_item_ui(ui, word_number, word, edit_words, cb); self.word_item_ui(ui, word_number, word, edit, cb);
}); });
if size > 2 { if size > 2 {
columns[2].horizontal(|ui| { columns[2].horizontal(|ui| {
word_number += 1; word_number += 1;
let word = chunk.get(2).unwrap(); let word = chunk.get(2).unwrap();
self.word_item_ui(ui, word_number, word, edit_words, cb); self.word_item_ui(ui, word_number, word, edit, cb);
}); });
} }
if size > 3 { if size > 3 {
columns[3].horizontal(|ui| { columns[3].horizontal(|ui| {
word_number += 1; word_number += 1;
let word = chunk.get(3).unwrap(); let word = chunk.get(3).unwrap();
self.word_item_ui(ui, word_number, word, edit_words, cb); self.word_item_ui(ui, word_number, word, edit, cb);
}); });
} }
}); });
@ -218,7 +205,7 @@ impl MnemonicSetup {
ui.columns(cols, |columns| { ui.columns(cols, |columns| {
columns[0].horizontal(|ui| { columns[0].horizontal(|ui| {
let word = chunk.get(0).unwrap(); let word = chunk.get(0).unwrap();
self.word_item_ui(ui, word_number, word, edit_words, cb); self.word_item_ui(ui, word_number, word, edit, cb);
}); });
}); });
} }
@ -227,20 +214,24 @@ impl MnemonicSetup {
ui.add_space(6.0); ui.add_space(6.0);
} }
/// Draw word list item for current mode. /// Draw word grid item.
fn word_item_ui(&mut self, fn word_item_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
num: usize, num: usize,
word: &String, word: &PhraseWord,
edit: bool, edit: bool,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let color = if !word.valid || (word.text.is_empty() && !self.mnemonic.valid()) {
Colors::red()
} else {
Colors::white_or_black(true)
};
if edit { if edit {
ui.add_space(6.0); ui.add_space(6.0);
View::button(ui, PENCIL.to_string(), Colors::button(), || { View::button(ui, PENCIL.to_string(), Colors::button(), || {
// Setup modal values. self.word_index_edit = num - 1;
self.word_num_edit = num; self.word_edit = word.text.clone();
self.word_edit = word.clone(); self.valid_word_edit = word.valid;
self.valid_word_edit = true;
// Show word edit modal. // Show word edit modal.
Modal::new(WORD_INPUT_MODAL) Modal::new(WORD_INPUT_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
@ -248,34 +239,33 @@ impl MnemonicSetup {
.show(); .show();
cb.show_keyboard(); cb.show_keyboard();
}); });
ui.label(RichText::new(format!("#{} {}", num, word)) ui.label(RichText::new(format!("#{} {}", num, word.text))
.size(17.0) .size(17.0)
.color(Colors::white_or_black(true))); .color(color));
} else { } else {
ui.add_space(12.0); ui.add_space(12.0);
let text = format!("#{} {}", num, word); let text = format!("#{} {}", num, word.text);
ui.label(RichText::new(text).size(17.0).color(Colors::white_or_black(true))); ui.label(RichText::new(text).size(17.0).color(color));
} }
} }
/// Reset mnemonic phrase to default values. /// Reset mnemonic phrase state to default values.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.mnemonic = Mnemonic::default(); self.mnemonic = Mnemonic::default();
self.valid_phrase = true;
} }
/// Draw word input [`Modal`] content. /// Draw word input [`Modal`] content.
fn word_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { fn word_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.enter_word", "number" => self.word_num_edit)) ui.label(RichText::new(t!("wallets.enter_word", "number" => self.word_index_edit + 1))
.size(17.0) .size(17.0)
.color(Colors::gray())); .color(Colors::gray()));
ui.add_space(8.0); ui.add_space(8.0);
// Draw word value text edit. // Draw word value text edit.
let mut text_edit_opts = TextEditOptions::new( let mut text_edit_opts = TextEditOptions::new(
Id::from(modal.id).with(self.word_num_edit) Id::from(modal.id).with(self.word_index_edit)
); );
View::text_edit(ui, cb, &mut self.word_edit, &mut text_edit_opts); View::text_edit(ui, cb, &mut self.word_edit, &mut text_edit_opts);
@ -305,38 +295,22 @@ impl MnemonicSetup {
columns[1].vertical_centered_justified(|ui| { columns[1].vertical_centered_justified(|ui| {
// Callback to save the word. // Callback to save the word.
let mut save = || { let mut save = || {
self.word_edit = self.word_edit.trim().to_string(); // Insert word checking validity.
let word = &self.word_edit.trim().to_string();
// Check if word is valid. self.valid_word_edit = self.mnemonic.insert(self.word_index_edit, word);
if !self.mnemonic.is_valid_word(&self.word_edit) { if !self.valid_word_edit {
self.valid_word_edit = false;
return; return;
} }
self.valid_word_edit = true;
// Select list where to save word.
let words = match self.mnemonic.mode {
PhraseMode::Generate => &mut self.mnemonic.confirm_words,
PhraseMode::Import => &mut self.mnemonic.words
};
// Save word at list.
let word_index = self.word_num_edit - 1;
words.remove(word_index);
words.insert(word_index, self.word_edit.clone());
// Close modal or go to next word to edit. // Close modal or go to next word to edit.
let close_modal = words.len() == self.word_num_edit let next_word = self.mnemonic.get(self.word_index_edit + 1);
|| !words.get(self.word_num_edit).unwrap().is_empty(); let close_modal = next_word.is_none() ||
(!next_word.as_ref().unwrap().text.is_empty() &&
next_word.unwrap().valid);
if close_modal { if close_modal {
// Check if entered phrase was valid when all words were entered.
if !self.mnemonic.words.contains(&String::from("")) {
self.valid_phrase = self.mnemonic.is_valid_phrase();
}
cb.hide_keyboard(); cb.hide_keyboard();
modal.close(); modal.close();
} else { } else {
self.word_num_edit += 1; self.word_index_edit += 1;
self.word_edit = String::from(""); self.word_edit = String::from("");
} }
}; };
@ -383,8 +357,8 @@ impl MnemonicSetup {
self.camera_content.clear_state(); self.camera_content.clear_state();
match &result { match &result {
QrScanResult::Text(text) => { QrScanResult::Text(text) => {
self.mnemonic.import_text(text, false); self.mnemonic.import(text);
if self.mnemonic.is_valid_phrase() { if self.mnemonic.valid() {
modal.close(); modal.close();
return; return;
} }

View file

@ -16,18 +16,20 @@ use grin_keychain::mnemonic::{from_entropy, search, to_entropy};
use grin_util::ZeroingString; use grin_util::ZeroingString;
use rand::{Rng, thread_rng}; use rand::{Rng, thread_rng};
use crate::wallet::types::{PhraseMode, PhraseSize}; use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord};
/// Mnemonic phrase container. /// Mnemonic phrase container.
pub struct Mnemonic { pub struct Mnemonic {
/// Phrase setup mode. /// Phrase setup mode.
pub(crate) mode: PhraseMode, mode: PhraseMode,
/// Size of phrase based on words count. /// Size of phrase based on words count.
pub(crate) size: PhraseSize, size: PhraseSize,
/// Generated words. /// Generated words.
pub(crate) words: Vec<String>, words: Vec<PhraseWord>,
/// Words to confirm the phrase. /// Words to confirm the phrase.
pub(crate) confirm_words: Vec<String> confirmation: Vec<PhraseWord>,
/// Flag to check if entered phrase if valid.
valid: bool,
} }
impl Default for Mnemonic { impl Default for Mnemonic {
@ -35,43 +37,67 @@ impl Default for Mnemonic {
let size = PhraseSize::Words24; let size = PhraseSize::Words24;
let mode = PhraseMode::Generate; let mode = PhraseMode::Generate;
let words = Self::generate_words(&mode, &size); let words = Self::generate_words(&mode, &size);
let confirm_words = Self::empty_words(&size); let confirmation = Self::empty_words(&size);
Self { mode, size, words, confirm_words } Self { mode, size, words, confirmation, valid: true }
} }
} }
impl Mnemonic { impl Mnemonic {
/// Change mnemonic phrase setup [`PhraseMode`]. /// Generate words based on provided [`PhraseMode`].
pub fn set_mode(&mut self, mode: PhraseMode) { pub fn set_mode(&mut self, mode: PhraseMode) {
self.mode = mode; self.mode = mode;
self.words = Self::generate_words(&self.mode, &self.size); self.words = Self::generate_words(&self.mode, &self.size);
self.confirm_words = Self::empty_words(&self.size); self.confirmation = Self::empty_words(&self.size);
self.valid = true;
} }
/// Change mnemonic phrase words [`PhraseSize`]. /// Get current phrase mode.
pub fn mode(&self) -> PhraseMode {
self.mode.clone()
}
/// Generate words based on provided [`PhraseSize`].
pub fn set_size(&mut self, size: PhraseSize) { pub fn set_size(&mut self, size: PhraseSize) {
self.size = size; self.size = size;
self.words = Self::generate_words(&self.mode, &self.size); self.words = Self::generate_words(&self.mode, &self.size);
self.confirm_words = Self::empty_words(&self.size); self.confirmation = Self::empty_words(&self.size);
self.valid = true;
} }
/// Check if provided word is in BIP39 format. /// Get current phrase size.
pub fn is_valid_word(&self, word: &String) -> bool { pub fn size(&self) -> PhraseSize {
search(word).is_ok() self.size.clone()
}
/// Get words based on current [`PhraseMode`].
pub fn words(&self, edit: bool) -> Vec<PhraseWord> {
match self.mode {
PhraseMode::Generate => {
if edit {
&self.confirmation
} else {
&self.words
}
}
PhraseMode::Import => &self.words
}.clone()
} }
/// Check if current phrase is valid. /// Check if current phrase is valid.
pub fn is_valid_phrase(&self) -> bool { pub fn valid(&self) -> bool {
to_entropy(self.get_phrase().as_str()).is_ok() self.valid
} }
/// Get phrase from words. /// Get phrase from words.
pub fn get_phrase(&self) -> String { pub fn get_phrase(&self) -> String {
self.words.iter().map(|x| x.to_string() + " ").collect::<String>() self.words.iter()
.enumerate()
.map(|(i, x)| if i == 0 { "" } else { " " }.to_owned() + &x.text)
.collect::<String>()
} }
/// Generate list of words based on provided [`PhraseMode`] and [`PhraseSize`]. /// Generate [`PhraseWord`] list based on provided [`PhraseMode`] and [`PhraseSize`].
fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<String> { fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<PhraseWord> {
match mode { match mode {
PhraseMode::Generate => { PhraseMode::Generate => {
let mut rng = thread_rng(); let mut rng = thread_rng();
@ -81,8 +107,14 @@ impl Mnemonic {
} }
from_entropy(&entropy).unwrap() from_entropy(&entropy).unwrap()
.split(" ") .split(" ")
.map(|s| String::from(s)) .map(|s| {
.collect::<Vec<String>>() let text = s.to_string();
PhraseWord {
text,
valid: true,
}
})
.collect::<Vec<PhraseWord>>()
}, },
PhraseMode::Import => { PhraseMode::Import => {
Self::empty_words(size) Self::empty_words(size)
@ -91,39 +123,117 @@ impl Mnemonic {
} }
/// Generate empty list of words based on provided [`PhraseSize`]. /// Generate empty list of words based on provided [`PhraseSize`].
fn empty_words(size: &PhraseSize) -> Vec<String> { fn empty_words(size: &PhraseSize) -> Vec<PhraseWord> {
let mut words = Vec::with_capacity(size.value()); let mut words = Vec::with_capacity(size.value());
for _ in 0..size.value() { for _ in 0..size.value() {
words.push(String::from("")) words.push(PhraseWord {
text: "".to_string(),
valid: true,
});
} }
words words
} }
/// Set words from provided text if possible. /// Insert word into provided index and return validation result.
pub fn import_text(&mut self, text: &ZeroingString, confirmation: bool) { pub fn insert(&mut self, index: usize, word: &String) -> bool {
// Check if word is valid.
let found = search(word).is_ok();
if !found {
return false;
}
let is_confirmation = self.mode == PhraseMode::Generate;
if is_confirmation {
let w = self.words.get(index).unwrap();
if word != &w.text {
return false;
}
}
// Save valid word at list.
let words = if is_confirmation {
&mut self.confirmation
} else {
&mut self.words
};
words.remove(index);
words.insert(index, PhraseWord { text: word.to_owned(), valid: true });
// Validate phrase when all words are entered.
let mut has_empty = false;
let _: Vec<_> = words.iter().map(|w| {
if w.text.is_empty() {
has_empty = true;
}
}).collect();
if !has_empty {
self.valid = to_entropy(self.get_phrase().as_str()).is_ok();
}
true
}
/// Get word from provided index.
pub fn get(&self, index: usize) -> Option<PhraseWord> {
let words = match self.mode {
PhraseMode::Generate => &self.confirmation,
PhraseMode::Import => &self.words
};
let word = words.get(index);
if let Some(w) = word {
return Some(PhraseWord {
text: w.text.clone(),
valid: w.valid
});
}
None
}
/// Setup phrase from provided text if possible.
pub fn import(&mut self, text: &ZeroingString) {
let words_split = text.trim().split(" "); let words_split = text.trim().split(" ");
let count = words_split.clone().count(); let count = words_split.clone().count();
if let Some(size) = PhraseSize::type_for_value(count) { if let Some(size) = PhraseSize::type_for_value(count) {
if !confirmation { // Setup phrase size.
let confirm = self.mode == PhraseMode::Generate;
if !confirm {
self.size = size; self.size = size;
} else if self.size != size { } else if self.size != size {
return; return;
} }
// Setup word list.
let mut words = vec![]; let mut words = vec![];
words_split.for_each(|word| { words_split.for_each(|w| {
if confirmation && !self.is_valid_word(&word.to_string()) { let mut text = w.to_string();
words = vec![]; text.retain(|c| c.is_alphabetic());
return; let valid = search(&text).is_ok();
} words.push(PhraseWord { text, valid });
words.push(word.to_string())
}); });
if confirmation { let mut has_invalid = false;
if !words.is_empty() { for (i, w) in words.iter().enumerate() {
self.confirm_words = words; if !self.insert(i, &w.text) {
has_invalid = true;
} }
} else {
self.words = words;
} }
self.valid = !has_invalid;
} }
} }
/// Check if phrase has invalid or empty words.
pub fn has_empty_or_invalid(&self) -> bool {
let words = match self.mode {
PhraseMode::Generate => &self.confirmation,
PhraseMode::Import => &self.words
};
let mut has_empty = false;
let mut has_invalid = false;
let _: Vec<_> = words.iter().map(|w| {
if w.text.is_empty() {
has_empty = true;
}
if !w.valid {
has_invalid = true;
}
}).collect();
has_empty || has_invalid
}
} }

View file

@ -19,6 +19,15 @@ use grin_util::Mutex;
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst}; use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
/// Mnemonic phrase word.
#[derive(Clone)]
pub struct PhraseWord {
/// Word text.
pub text: String,
/// Flag to check if word is valid.
pub valid: bool,
}
/// Mnemonic phrase setup mode. /// Mnemonic phrase setup mode.
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum PhraseMode { pub enum PhraseMode {

View file

@ -140,7 +140,7 @@ impl Wallet {
let p = w_lock.lc_provider()?; let p = w_lock.lc_provider()?;
p.create_wallet(None, p.create_wallet(None,
Some(ZeroingString::from(mnemonic.get_phrase())), Some(ZeroingString::from(mnemonic.get_phrase())),
mnemonic.size.entropy_size(), mnemonic.size().entropy_size(),
ZeroingString::from(password.clone()), ZeroingString::from(password.clone()),
false, false,
)?; )?;