wallet: import recovery phrase from qr code
This commit is contained in:
parent
f29527a891
commit
f01d6cc863
8 changed files with 134 additions and 35 deletions
|
@ -117,6 +117,7 @@ wallets:
|
|||
change_server_confirmation: To apply change of connection settings, you need to re-open your wallet. Reopen it now?
|
||||
tx_send_cancel_conf: 'Are you sure you want to cancel sending of %{amount} ツ?'
|
||||
tx_receive_cancel_conf: 'Are you sure you want to cancel receiving of %{amount} ツ?'
|
||||
rec_phrase_not_found: Recovery phrase not found.
|
||||
transport:
|
||||
desc: 'Use transport to receive or send messages synchronously:'
|
||||
tor_network: Tor network
|
||||
|
|
|
@ -117,6 +117,7 @@ wallets:
|
|||
change_server_confirmation: Для применения изменения настроек соединения необходимо переоткрыть кошелёк. Переоткрыть его сейчас?
|
||||
tx_send_cancel_conf: 'Вы действительно хотите отменить отправку %{amount} ツ?'
|
||||
tx_receive_cancel_conf: 'Вы действительно хотите отменить получение %{amount} ツ?'
|
||||
rec_phrase_not_found: Фраза восстановления не найдена.
|
||||
transport:
|
||||
desc: 'Используйте транспорт для синхронных получения или отправки сообщений:'
|
||||
tor_network: Сеть Tor
|
||||
|
|
|
@ -17,6 +17,7 @@ use std::thread;
|
|||
use eframe::emath::Align;
|
||||
use egui::load::SizedTexture;
|
||||
use egui::{Layout, Pos2, Rect, TextureOptions, Widget};
|
||||
use grin_util::ZeroingString;
|
||||
use grin_wallet_libwallet::SlatepackAddress;
|
||||
use image::{DynamicImage, EncodableLayout, ImageFormat};
|
||||
use crate::gui::Colors;
|
||||
|
@ -181,16 +182,16 @@ impl CameraContent {
|
|||
// Check if string starts with Grin address prefix.
|
||||
if text.starts_with("tgrin") || text.starts_with("grin") {
|
||||
if SlatepackAddress::try_from(text).is_ok() {
|
||||
return QrScanResult::Address(text.to_string())
|
||||
return QrScanResult::Address(ZeroingString::from(text))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if string contains Slatepack message prefix and postfix.
|
||||
if text.starts_with("BEGINSLATEPACK.") && text.ends_with("ENDSLATEPACK.") {
|
||||
return QrScanResult::Slatepack(text.to_string())
|
||||
return QrScanResult::Slatepack(ZeroingString::from(text))
|
||||
}
|
||||
|
||||
QrScanResult::Text(text.to_string())
|
||||
QrScanResult::Text(ZeroingString::from(text))
|
||||
}
|
||||
|
||||
/// Get QR code scan result.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use grin_util::ZeroingString;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::Modal;
|
||||
|
||||
|
@ -144,11 +145,11 @@ impl TextEditOptions {
|
|||
#[derive(Clone)]
|
||||
pub enum QrScanResult {
|
||||
/// Slatepack message.
|
||||
Slatepack(String),
|
||||
Slatepack(ZeroingString),
|
||||
/// Slatepack address.
|
||||
Address(String),
|
||||
Address(ZeroingString),
|
||||
/// Parsed text.
|
||||
Text(String)
|
||||
Text(ZeroingString)
|
||||
}
|
||||
|
||||
/// QR code scan state.
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
|
||||
use egui::{Id, Margin, RichText, ScrollArea, TextStyle, vec2, Widget};
|
||||
use egui_extras::{RetainedImage, Size, StripBuilder};
|
||||
use grin_util::ZeroingString;
|
||||
|
||||
use crate::built_info;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, FOLDER_PLUS, SHARE_FAT};
|
||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, FOLDER_PLUS, SCAN, SHARE_FAT};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
|
@ -178,28 +179,30 @@ impl WalletCreation {
|
|||
}
|
||||
if step == Step::EnterMnemonic {
|
||||
ui.add_space(4.0);
|
||||
if !step_available {
|
||||
self.copy_or_paste_button_ui(ui, cb);
|
||||
ui.add_space(4.0);
|
||||
} else {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
// Show copy or paste button for mnemonic phrase step.
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
self.copy_or_paste_button_ui(ui, cb);
|
||||
});
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Show next step button if there are no empty words.
|
||||
ui.columns(2, |columns| {
|
||||
// Show copy or paste button for mnemonic phrase step.
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
self.copy_or_paste_button_ui(ui, cb);
|
||||
});
|
||||
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
if step_available {
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
self.next_step_button_ui(ui, step, on_create);
|
||||
// Show next step button if there are no empty words.
|
||||
self.next_step_button_ui(ui, step, on_create);
|
||||
} else {
|
||||
// Show QR code scan button.
|
||||
let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase());
|
||||
View::button(ui, scan_text, Colors::WHITE, || {
|
||||
self.mnemonic_setup.show_qr_scan_modal(cb);
|
||||
});
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
} else {
|
||||
if step_available {
|
||||
ui.add_space(4.0);
|
||||
|
@ -225,7 +228,8 @@ impl WalletCreation {
|
|||
// Show paste button.
|
||||
let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
|
||||
View::button(ui, p_t, Colors::WHITE, || {
|
||||
self.mnemonic_setup.mnemonic.import_text(cb.get_string_from_buffer());
|
||||
let data = ZeroingString::from(cb.get_string_from_buffer());
|
||||
self.mnemonic_setup.mnemonic.import_text(&data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ use egui::{Id, RichText};
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::PENCIL;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions};
|
||||
use crate::gui::views::{CameraContent, Modal, Root, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions};
|
||||
use crate::wallet::Mnemonic;
|
||||
use crate::wallet::types::{PhraseMode, PhraseSize};
|
||||
|
||||
|
@ -37,6 +37,11 @@ pub struct MnemonicSetup {
|
|||
/// Flag to check if entered word is valid.
|
||||
valid_word_edit: bool,
|
||||
|
||||
/// Camera content for QR scan [`Modal`].
|
||||
camera_content: CameraContent,
|
||||
/// Flag to check if recovery phrase was found at QR code scanning [`Modal`].
|
||||
scan_phrase_not_found: Option<bool>,
|
||||
|
||||
/// [`Modal`] identifiers allowed at this ui container.
|
||||
modal_ids: Vec<&'static str>
|
||||
}
|
||||
|
@ -44,6 +49,9 @@ pub struct MnemonicSetup {
|
|||
/// Identifier for word input [`Modal`].
|
||||
pub const WORD_INPUT_MODAL: &'static str = "word_input_modal";
|
||||
|
||||
/// Identifier for QR code recovery phrase scan [`Modal`].
|
||||
const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal";
|
||||
|
||||
impl Default for MnemonicSetup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -52,8 +60,11 @@ impl Default for MnemonicSetup {
|
|||
word_num_edit: 0,
|
||||
word_edit: String::from(""),
|
||||
valid_word_edit: true,
|
||||
camera_content: CameraContent::default(),
|
||||
scan_phrase_not_found: None,
|
||||
modal_ids: vec![
|
||||
WORD_INPUT_MODAL
|
||||
WORD_INPUT_MODAL,
|
||||
QR_CODE_PHRASE_SCAN_MODAL
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +82,7 @@ impl ModalContainer for MnemonicSetup {
|
|||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
WORD_INPUT_MODAL => self.word_modal_ui(ui, modal, cb),
|
||||
QR_CODE_PHRASE_SCAN_MODAL => self.scan_qr_modal_ui(ui, modal, cb),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -338,6 +350,85 @@ impl MnemonicSetup {
|
|||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show QR code recovery phrase scanner [`Modal`].
|
||||
pub fn show_qr_scan_modal(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
self.scan_phrase_not_found = None;
|
||||
// Show QR code scan modal.
|
||||
Modal::new(QR_CODE_PHRASE_SCAN_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("scan_qr"))
|
||||
.closeable(false)
|
||||
.show();
|
||||
cb.start_camera();
|
||||
}
|
||||
|
||||
/// Draw QR code scan [`Modal`] content.
|
||||
fn scan_qr_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Show scan result if exists or show camera content while scanning.
|
||||
if let Some(_) = &self.scan_phrase_not_found {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.rec_phrase_not_found"))
|
||||
.size(17.0)
|
||||
.color(Colors::RED));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
} else if let Some(result) = self.camera_content.qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
self.camera_content.clear_state();
|
||||
match &result {
|
||||
QrScanResult::Text(text) => {
|
||||
self.mnemonic.import_text(text);
|
||||
if self.mnemonic.is_valid_phrase() {
|
||||
modal.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Set an error when found phrase was not valid.
|
||||
self.scan_phrase_not_found = Some(true);
|
||||
Modal::set_title(t!("scan_result"));
|
||||
} else {
|
||||
ui.add_space(6.0);
|
||||
self.camera_content.ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
if self.scan_phrase_not_found.is_some() {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::WHITE, || {
|
||||
self.scan_phrase_not_found = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::WHITE, || {
|
||||
Modal::set_title(t!("scan_qr"));
|
||||
self.scan_phrase_not_found = None;
|
||||
cb.start_camera();
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
cb.stop_camera();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate word list columns count based on available ui width.
|
||||
|
|
|
@ -192,9 +192,8 @@ impl WalletContent {
|
|||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to scan QR code.
|
||||
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
|
||||
// Load accounts.
|
||||
self.qr_scan_result = None;
|
||||
// Show account list modal.
|
||||
// Show QR code scan modal.
|
||||
Modal::new(QR_CODE_SCAN_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("scan_qr"))
|
||||
|
@ -359,7 +358,7 @@ impl WalletContent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw QR scanner [`Modal`] content.
|
||||
/// Draw QR code scan [`Modal`] content.
|
||||
fn scan_qr_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
|
@ -371,7 +370,7 @@ impl WalletContent {
|
|||
QrScanResult::Slatepack(t) => t,
|
||||
QrScanResult::Address(t) => t,
|
||||
QrScanResult::Text(t) => t
|
||||
}.clone();
|
||||
}.to_string();
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(3.0);
|
||||
ScrollArea::vertical()
|
||||
|
@ -396,7 +395,7 @@ impl WalletContent {
|
|||
ui.vertical_centered(|ui| {
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::BUTTON, || {
|
||||
cb.copy_string_to_buffer(result_text);
|
||||
cb.copy_string_to_buffer(result_text.to_string());
|
||||
self.qr_scan_result = None;
|
||||
modal.close();
|
||||
});
|
||||
|
@ -409,7 +408,7 @@ impl WalletContent {
|
|||
QrScanResult::Slatepack(message) => {
|
||||
// Redirect to messages to handle parsed message.
|
||||
let mut messages =
|
||||
WalletMessages::new(wallet.can_use_dandelion(), Some(message.clone()));
|
||||
WalletMessages::new(wallet.can_use_dandelion(), Some(message.to_string()));
|
||||
messages.parse_message(wallet);
|
||||
modal.close();
|
||||
self.current_tab = Box::new(messages);
|
||||
|
@ -420,7 +419,7 @@ impl WalletContent {
|
|||
// Redirect to send amount with Tor.
|
||||
let addr = wallet.slatepack_address().unwrap();
|
||||
let mut transport = WalletTransport::new(addr.clone());
|
||||
transport.show_send_tor_modal(cb, Some(receiver.clone()));
|
||||
transport.show_send_tor_modal(cb, Some(receiver.to_string()));
|
||||
modal.close();
|
||||
self.current_tab = Box::new(transport);
|
||||
return;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
use grin_keychain::mnemonic::{from_entropy, search, to_entropy};
|
||||
use grin_util::ZeroingString;
|
||||
use rand::{Rng, thread_rng};
|
||||
|
||||
use crate::wallet::types::{PhraseMode, PhraseSize};
|
||||
|
@ -105,7 +106,7 @@ impl Mnemonic {
|
|||
}
|
||||
|
||||
/// Set words from provided text if possible.
|
||||
pub fn import_text(&mut self, text: String) {
|
||||
pub fn import_text(&mut self, text: &ZeroingString) {
|
||||
if self.mode != PhraseMode::Import {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue