From 479ad82d9baef250e327ecd535ba2255693eaac7 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Sat, 25 May 2024 11:59:39 +0300 Subject: [PATCH] scan: seed_qr format support --- src/gui/views/camera.rs | 65 +++++++++++++++++++++++-- src/gui/views/types.rs | 7 ++- src/gui/views/wallets/wallet/content.rs | 8 +-- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/gui/views/camera.rs b/src/gui/views/camera.rs index 7f782ac..7870468 100644 --- a/src/gui/views/camera.rs +++ b/src/gui/views/camera.rs @@ -22,12 +22,14 @@ use image::{DynamicImage, EncodableLayout, ImageFormat}; use grin_util::ZeroingString; use grin_wallet_libwallet::SlatepackAddress; +use grin_keychain::mnemonic::WORDS; use crate::gui::Colors; use crate::gui::icons::CAMERA_ROTATE; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::{QrScanResult, QrScanState}; use crate::gui::views::View; +use crate::wallet::types::PhraseSize; /// Camera QR code scanner. pub struct CameraContent { @@ -146,6 +148,11 @@ impl CameraContent { let mut w_scan = self.qr_scan_state.write(); w_scan.image_processing = true; } + // Clear previous scanning result. + { + let mut w_scan = self.qr_scan_state.write(); + w_scan.qr_scan_result = None; + } // Launch scanner at separate thread. let data = data.clone(); let qr_scan_state = self.qr_scan_state.clone(); @@ -164,7 +171,16 @@ impl CameraContent { for g in grids { if let Ok((_, text)) = g.decode() { let text = text.trim(); - if !text.is_empty() { + let cur_text = { + let r_scan = qr_scan_state.read(); + let text = if let Some(res) = r_scan.qr_scan_result.clone() { + res.value() + } else { + "".to_string() + }; + text + }; + if !text.is_empty() && text != cur_text { let result = Self::parse_scan_result(text); let mut w_scan = qr_scan_state.write(); w_scan.qr_scan_result = Some(result); @@ -184,15 +200,58 @@ 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(ZeroingString::from(text)) + 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(ZeroingString::from(text)) + return QrScanResult::Slatepack(ZeroingString::from(text)); } + // Check SeedQR format. + let only_numbers = || { + for c in text.chars() { + if !c.is_numeric() { + return false; + } + } + true + }; + if only_numbers() { + if let Some(_) = PhraseSize::type_for_value(text.len() / 4) { + let chars: Vec = text.trim().chars().collect(); + let split = &chars.chunks(4) + .map(|chunk| chunk.iter().collect::() + .trim() + .trim_start_matches("0") + .to_string() + ) + .collect::>(); + let mut words = "".to_string(); + for i in split { + let index = if i.is_empty() { + 0usize + } else { + i.parse::().unwrap_or(WORDS.len()) + }; + let empty_word = "".to_string(); + let word = WORDS.get(index).clone().unwrap_or(&empty_word).clone(); + // Return text result when BIP39 word was not found. + if word.is_empty() { + return QrScanResult::Text(ZeroingString::from(text)); + } + words = if words.is_empty() { + format!("{}", word) + } else { + format!("{} {}", words, word) + }; + } + return QrScanResult::SeedQR(ZeroingString::from(words)); + } + } + + // Return default text result. QrScanResult::Text(ZeroingString::from(text)) } diff --git a/src/gui/views/types.rs b/src/gui/views/types.rs index ccbbe99..c1581eb 100644 --- a/src/gui/views/types.rs +++ b/src/gui/views/types.rs @@ -153,7 +153,9 @@ pub enum QrScanResult { /// Slatepack address. Address(ZeroingString), /// Parsed text. - Text(ZeroingString) + Text(ZeroingString), + /// Parsed SeedQR https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md. + SeedQR(ZeroingString) } impl QrScanResult { @@ -162,7 +164,8 @@ impl QrScanResult { match self { QrScanResult::Slatepack(text) => text, QrScanResult::Address(text) => text, - QrScanResult::Text(text) => text + QrScanResult::Text(text) => text, + QrScanResult::SeedQR(text) => text }.to_string() } } diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 10b0b18..14cf73b 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -371,11 +371,7 @@ impl WalletContent { cb: &dyn PlatformCallbacks) { // Show scan result if exists or show camera content while scanning. if let Some(result) = &self.qr_scan_result { - let mut result_text = match result { - QrScanResult::Slatepack(t) => t, - QrScanResult::Address(t) => t, - QrScanResult::Text(t) => t - }.to_string(); + let mut result_text = result.value(); View::horizontal_line(ui, Colors::ITEM_STROKE); ui.add_space(3.0); ScrollArea::vertical() @@ -431,7 +427,7 @@ impl WalletContent { return; } } - QrScanResult::Text(_) => {} + _ => {} } // Set result and rename modal title.