camera: Compact SeedQR support
This commit is contained in:
parent
03eb8a7af7
commit
53f524325c
3 changed files with 98 additions and 15 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
30
src/wallet/utils.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue