camera: Compact SeedQR support

This commit is contained in:
ardocrat 2024-05-25 23:40:40 +03:00
parent 03eb8a7af7
commit 53f524325c
3 changed files with 98 additions and 15 deletions

View file

@ -30,6 +30,7 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{QrScanResult, QrScanState}; use crate::gui::views::types::{QrScanResult, QrScanState};
use crate::gui::views::View; use crate::gui::views::View;
use crate::wallet::types::PhraseSize; use crate::wallet::types::PhraseSize;
use crate::wallet::WalletUtils;
/// Camera QR code scanner. /// Camera QR code scanner.
pub struct CameraContent { pub struct CameraContent {
@ -148,11 +149,6 @@ impl CameraContent {
let mut w_scan = self.qr_scan_state.write(); let mut w_scan = self.qr_scan_state.write();
w_scan.image_processing = true; 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. // Launch scanner at separate thread.
let data = data.clone(); let data = data.clone();
let qr_scan_state = self.qr_scan_state.clone(); let qr_scan_state = self.qr_scan_state.clone();
@ -168,9 +164,9 @@ impl CameraContent {
= rqrr::PreparedImage::prepare(img); = rqrr::PreparedImage::prepare(img);
// Scan and save results. // Scan and save results.
let grids = img.detect_grids(); let grids = img.detect_grids();
for g in grids { if let Some(g) = grids.get(0) {
if let Ok((_, text)) = g.decode() { let mut data = vec![];
let text = text.trim(); if let Ok(_) = g.decode_to(&mut data) {
let cur_text = { let cur_text = {
let r_scan = qr_scan_state.read(); let r_scan = qr_scan_state.read();
let text = if let Some(res) = r_scan.qr_scan_result.clone() { let text = if let Some(res) = r_scan.qr_scan_result.clone() {
@ -180,14 +176,16 @@ impl CameraContent {
}; };
text text
}; };
if !text.is_empty() && text != cur_text { let text = String::from_utf8(data.clone()).unwrap_or("".to_string());
let result = Self::parse_scan_result(text); if !data.is_empty() && (cur_text.is_empty() || text != cur_text) {
let result = Self::parse_scan_result(&data);
let mut w_scan = qr_scan_state.write(); let mut w_scan = qr_scan_state.write();
w_scan.qr_scan_result = Some(result); w_scan.qr_scan_result = Some(result);
return;
} }
} }
} }
// Setup scanning flag. // Reset scanning flag to process again.
{ {
let mut w_scan = qr_scan_state.write(); let mut w_scan = qr_scan_state.write();
w_scan.image_processing = false; w_scan.image_processing = false;
@ -196,8 +194,10 @@ impl CameraContent {
}); });
} }
fn parse_scan_result(text: &str) -> QrScanResult { fn parse_scan_result(data: &Vec<u8>) -> QrScanResult {
// Check if string starts with Grin address prefix. // Check if string starts with Grin address prefix.
let text_string = String::from_utf8(data.clone()).unwrap_or("".to_string());
let text = text_string.as_str();
if text.starts_with("tgrin") || text.starts_with("grin") { if text.starts_with("tgrin") || text.starts_with("grin") {
if SlatepackAddress::try_from(text).is_ok() { if SlatepackAddress::try_from(text).is_ok() {
return QrScanResult::Address(ZeroingString::from(text)); return QrScanResult::Address(ZeroingString::from(text));
@ -209,7 +209,57 @@ impl CameraContent {
return QrScanResult::Slatepack(ZeroingString::from(text)); return QrScanResult::Slatepack(ZeroingString::from(text));
} }
// Check SeedQR format. // Check Compact SeedQR format (https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#compactseedqr-specification).
if data.len() <= 32 && 16 <= data.len() && data.len() % 4 == 0 {
// Setup words amount.
let total_bits = data.len() * 8;
let checksum_bits = total_bits / 32;
let total_words = (total_bits + checksum_bits) / 11;
// Setup entropy.
let mut entropy = data.clone();
WalletUtils::setup_checksum(&mut entropy);
// Setup bits.
let mut bits = vec![false; entropy.len() * 8];
for i in 0..entropy.len() {
for j in 0..8 {
bits[(i * 8) + j] = (entropy[i] & (1 << (7 - j))) != 0;
}
}
// Extract word index.
let extract_index = |i: usize| -> usize {
let mut index = 0;
for j in 0..11 {
index = index << 1;
if bits[(i * 11) + j] {
index += 1;
}
}
return index;
};
// Setup words.
let mut words = "".to_string();
for n in 0..total_words {
// Setup word index.
let index = extract_index(n);
// Setup word.
let empty_word = "".to_string();
let word = WORDS.get(index).clone().unwrap_or(&empty_word).clone();
if word.is_empty() {
words = empty_word;
break;
}
words = if words.is_empty() {
format!("{}", word)
} else {
format!("{} {}", words, word)
};
}
if !words.is_empty() {
return QrScanResult::SeedQR(ZeroingString::from(words));
}
}
// Check Standard SeedQR format (https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#standard-seedqr-specification).
let only_numbers = || { let only_numbers = || {
for c in text.chars() { for c in text.chars() {
if !c.is_numeric() { if !c.is_numeric() {
@ -218,7 +268,7 @@ impl CameraContent {
} }
true true
}; };
if only_numbers() { if !text.is_empty() && data.len() <= 96 && data.len() % 4 == 0 && only_numbers() {
if let Some(_) = PhraseSize::type_for_value(text.len() / 4) { if let Some(_) = PhraseSize::type_for_value(text.len() / 4) {
let chars: Vec<char> = text.trim().chars().collect(); let chars: Vec<char> = text.trim().chars().collect();
let split = &chars.chunks(4) let split = &chars.chunks(4)

View file

@ -28,3 +28,6 @@ pub use config::*;
mod list; mod list;
pub use list::*; pub use list::*;
mod utils;
pub use utils::WalletUtils;

30
src/wallet/utils.rs Normal file
View file

@ -0,0 +1,30 @@
// Copyright 2024 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use sha2::{Sha256, Digest};
/// Wallet utilities functions.
pub struct WalletUtils {}
impl WalletUtils {
/// Setup entropy data checksum.
pub fn setup_checksum(data: &mut Vec<u8>) {
let mut hasher = Sha256::new();
hasher.update(data.clone());
let checksum = hasher.finalize();
println!("BEFORE data: {}, checksum: {}", data.len(), checksum.len());
data.extend(checksum);
println!("AFTER data: {}", data.len());
}
}