diff --git a/iapyx/src/cli/args/qr/address.rs b/iapyx/src/cli/args/qr/address.rs new file mode 100644 index 00000000..cf0d6b6d --- /dev/null +++ b/iapyx/src/cli/args/qr/address.rs @@ -0,0 +1,100 @@ +use crate::PinReadMode; +use crate::QrReader; + +use crate::cli::args::qr::IapyxQrCommandError; +use chain_addr::AddressReadable; +use chain_addr::{Discrimination, Kind}; +use chain_core::mempack::ReadBuf; +use chain_core::mempack::Readable; +use chain_core::property::Deserialize; +use chain_crypto::Ed25519Extended; +use chain_crypto::SecretKey; +use chain_impl_mockchain::block::Block; +use jormungandr_lib::interfaces::{Block0Configuration, Initial}; +use std::convert::TryInto; +use std::io::BufReader; +use std::path::Path; +use std::path::PathBuf; +use structopt::StructOpt; +use url::Url; +#[derive(StructOpt, Debug)] +pub struct GetAddressFromQRCommand { + #[structopt(long = "qr")] + pub qr: PathBuf, + + #[structopt(short = "p", long = "pin", default_value = "1234")] + pub pin: String, + + #[structopt(short = "f", long = "read-from-filename")] + pub read_pin_from_filename: bool, + + // if true then testing discrimination would be used + #[structopt(long = "testing")] + pub testing: bool, + + // if true then testing discrimination would be used + #[structopt(long = "prefix", default_value = "ca")] + pub prefix: String, + + #[structopt(long = "block0")] + pub block0: Option, +} + +impl GetAddressFromQRCommand { + pub fn exec(&self) -> Result<(), IapyxQrCommandError> { + println!("Decoding qr from file: {:?}...", self.qr); + let pin_read_mode = PinReadMode::new(self.read_pin_from_filename, &self.pin); + let pin_reader = QrReader::new(pin_read_mode); + let secret = pin_reader.read_qr(&self.qr)?; + let bin: [u8; 64] = secret.leak_secret().as_ref().try_into().unwrap(); + let secret_key: SecretKey = SecretKey::from_binary(&bin).unwrap(); + let kind = Kind::Single(secret_key.to_public()); + let address = chain_addr::Address(Discrimination::Production, kind); + + if let Some(block0_path) = &self.block0 { + println!("Reading block0 from location {:?}...", block0_path); + + let block = { + if Path::new(block0_path).exists() { + let reader = std::fs::OpenOptions::new() + .create(false) + .write(false) + .read(true) + .append(false) + .open(block0_path)?; + let reader = BufReader::new(reader); + Block::deserialize(reader)? + } else if Url::parse(block0_path).is_ok() { + let response = reqwest::blocking::get(block0_path)?; + let block0_bytes = response.bytes()?.to_vec(); + Block::read(&mut ReadBuf::from(&block0_bytes))? + } else { + panic!(" block0 should be either path to filesystem or url "); + } + }; + let genesis = Block0Configuration::from_block(&block)?; + + for initial in genesis.initial.iter() { + if let Initial::Fund(initial_utxos) = initial { + if let Some(entry) = initial_utxos.iter().find(|x| { + let entry_address: chain_addr::Address = x.address.clone().into(); + entry_address == address + }) { + println!( + "Address corresponding to input qr found in block0: '{}' with value: '{}'", + AddressReadable::from_address(&self.prefix, &entry.address.clone().into()).to_string(),entry.value + ); + return Ok(()); + } + } + } + println!("Address corresponding to input qr not found in block0"); + } else { + println!( + "Address: {}", + AddressReadable::from_address(&self.prefix, &address).to_string() + ); + } + Ok(()) + } +} diff --git a/iapyx/src/cli/args/qr/mod.rs b/iapyx/src/cli/args/qr/mod.rs index 9909fc16..543b28c8 100644 --- a/iapyx/src/cli/args/qr/mod.rs +++ b/iapyx/src/cli/args/qr/mod.rs @@ -1,59 +1,44 @@ -use crate::PinReadMode; -use crate::QrReader; -use crate::Wallet; -use std::convert::TryInto; -use std::path::PathBuf; +mod address; +mod secret; +mod verify; +use crate::cli::args::qr::secret::GetSecretFromQRCommand; +use address::GetAddressFromQRCommand; +use jormungandr_lib::interfaces::Block0ConfigurationError; use structopt::StructOpt; use thiserror::Error; -#[derive(Error, Debug)] -pub enum IapyxQrCommandError { - #[error("proxy error")] - ProxyError(#[from] crate::backend::ProxyServerError), -} +use verify::VerifyQrCommand; #[derive(StructOpt, Debug)] -pub struct IapyxQrCommand { - #[structopt(short = "q", long = "qr-codes-folder")] - pub qr_codes_folder: PathBuf, - - #[structopt(short = "g", long = "global-pin", default_value = "1234")] - pub global_pin: String, - - #[structopt(short = "f", long = "read-from-filename")] - pub read_pin_from_filename: bool, - - #[structopt(short = "s", long = "stop-at-fail")] - pub stop_at_fail: bool, +pub enum IapyxQrCommand { + Verify(VerifyQrCommand), + CheckAddress(GetAddressFromQRCommand), + Secret(GetSecretFromQRCommand), } impl IapyxQrCommand { pub fn exec(&self) -> Result<(), IapyxQrCommandError> { - println!("Decoding..."); - - let pin_read_mode = PinReadMode::new(self.read_pin_from_filename, &self.global_pin); - - let qr_codes: Vec = std::fs::read_dir(&self.qr_codes_folder) - .unwrap() - .into_iter() - .map(|x| x.unwrap().path()) - .collect(); - - let pin_reader = QrReader::new(pin_read_mode); - let secrets = pin_reader.read_qrs(&qr_codes, self.stop_at_fail); - let wallets: Vec = secrets - .into_iter() - .map(|secret| { - Wallet::recover_from_utxo(secret.leak_secret().as_ref().try_into().unwrap()) - .unwrap() - }) - .collect(); - - println!( - "{} QR read. {} succesfull, {} failed", - qr_codes.len(), - wallets.len(), - qr_codes.len() - wallets.len() - ); - Ok(()) + match self { + Self::Verify(verify) => verify.exec(), + Self::CheckAddress(check_address) => check_address.exec(), + Self::Secret(secret) => secret.exec(), + } } } + +#[derive(Error, Debug)] +pub enum IapyxQrCommandError { + #[error("proxy error")] + ProxyError(#[from] crate::backend::ProxyServerError), + #[error("pin error")] + PinError(#[from] crate::qr::PinReadError), + #[error("reqwest error")] + IapyxQrCommandError(#[from] reqwest::Error), + #[error("block0 parse error")] + Block0ParseError(#[from] Block0ConfigurationError), + #[error("io error")] + IoError(#[from] std::io::Error), + #[error("read error")] + ReadError(#[from] chain_core::mempack::ReadError), + #[error("bech32 error")] + Bech32Error(#[from] bech32::Error), +} diff --git a/iapyx/src/cli/args/qr/secret.rs b/iapyx/src/cli/args/qr/secret.rs new file mode 100644 index 00000000..6214a070 --- /dev/null +++ b/iapyx/src/cli/args/qr/secret.rs @@ -0,0 +1,40 @@ +use crate::PinReadMode; +use crate::QrReader; + +use crate::cli::args::qr::IapyxQrCommandError; +use bech32::ToBase32; +use chain_crypto::AsymmetricKey; +use chain_crypto::Ed25519Extended; +use chain_crypto::SecretKey; +use std::convert::TryInto; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +pub struct GetSecretFromQRCommand { + #[structopt(long = "qr")] + pub qr: PathBuf, + + #[structopt(short = "p", long = "pin", default_value = "1234")] + pub pin: String, + + #[structopt(short = "f", long = "read-from-filename")] + pub read_pin_from_filename: bool, +} + +impl GetSecretFromQRCommand { + pub fn exec(&self) -> Result<(), IapyxQrCommandError> { + println!("Decoding qr from file: {:?}...", self.qr); + let pin_read_mode = PinReadMode::new(self.read_pin_from_filename, &self.pin); + let pin_reader = QrReader::new(pin_read_mode); + let secret = pin_reader.read_qr(&self.qr)?; + let bin: [u8; 64] = secret.leak_secret().as_ref().try_into().unwrap(); + let secret_key: SecretKey = SecretKey::from_binary(&bin).unwrap(); + let hrp = Ed25519Extended::SECRET_BECH32_HRP; + println!( + "{}", + bech32::encode(hrp, secret_key.leak_secret().to_base32())? + ); + Ok(()) + } +} diff --git a/iapyx/src/cli/args/qr/verify.rs b/iapyx/src/cli/args/qr/verify.rs new file mode 100644 index 00000000..cefe215d --- /dev/null +++ b/iapyx/src/cli/args/qr/verify.rs @@ -0,0 +1,67 @@ +use crate::cli::args::qr::IapyxQrCommandError; +use crate::PinReadMode; +use crate::QrReader; +use crate::Wallet; +use chain_addr::AddressReadable; +use chain_addr::{Discrimination, Kind}; +use chain_crypto::Ed25519Extended; +use chain_crypto::SecretKey; +use std::convert::TryInto; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +pub struct VerifyQrCommand { + #[structopt(short = "q", long = "qr-codes-folder")] + pub qr_codes_folder: PathBuf, + + #[structopt(short = "g", long = "global-pin", default_value = "1234")] + pub global_pin: String, + + #[structopt(short = "f", long = "read-from-filename")] + pub read_pin_from_filename: bool, + + #[structopt(short = "s", long = "stop-at-fail")] + pub stop_at_fail: bool, +} + +impl VerifyQrCommand { + pub fn exec(&self) -> Result<(), IapyxQrCommandError> { + println!("Decoding..."); + + let pin_read_mode = PinReadMode::new(self.read_pin_from_filename, &self.global_pin); + + let qr_codes: Vec = std::fs::read_dir(&self.qr_codes_folder) + .unwrap() + .into_iter() + .map(|x| x.unwrap().path()) + .collect(); + + let pin_reader = QrReader::new(pin_read_mode); + let secrets = pin_reader.read_qrs(&qr_codes, self.stop_at_fail); + let wallets: Vec = secrets + .into_iter() + .map(|secret| { + let bin: [u8; 64] = secret.leak_secret().as_ref().try_into().unwrap(); + + let secret_key: SecretKey = SecretKey::from_binary(&bin).unwrap(); + let kind = Kind::Single(secret_key.to_public()); + let address = chain_addr::Address(Discrimination::Production, kind); + println!( + "{}", + AddressReadable::from_address("ca", &address).to_string() + ); + + Wallet::recover_from_utxo(&bin).unwrap() + }) + .collect(); + + println!( + "{} QR read. {} succesfull, {} failed", + qr_codes.len(), + wallets.len(), + qr_codes.len() - wallets.len() + ); + Ok(()) + } +} diff --git a/iapyx/src/qr/mod.rs b/iapyx/src/qr/mod.rs index a70ace6a..a26f765f 100644 --- a/iapyx/src/qr/mod.rs +++ b/iapyx/src/qr/mod.rs @@ -1,4 +1,5 @@ use jormungandr_testing_utils::qr_code::KeyQrCode; +use jormungandr_testing_utils::qr_code::KeyQrCodeError; use std::path::Path; use std::path::PathBuf; use thiserror::Error; @@ -26,8 +27,14 @@ pub enum PinReadError { UnableToDetectFileName(PathBuf), #[error("cannot read file name from path {0:?}")] UnableToReadFileName(PathBuf), - #[error("cannot divie file name from path {0:?}")] + #[error("cannot split file name from path {0:?}")] UnableToSplitFileName(PathBuf), + #[error("Cannot read qr from file")] + UnableToReadQR(#[from] std::io::Error), + #[error("Cannot decode qr from file")] + UnableToDecodeQR(#[from] KeyQrCodeError), + #[error("cannot open image")] + UnableToOpenImage(#[from] image::ImageError), } #[derive(Debug)] @@ -40,6 +47,17 @@ impl QrReader { Self { pin_read_mode } } + pub fn read_qr>( + &self, + qr: P, + ) -> Result, PinReadError> { + let pin = get_pin(&self.pin_read_mode, &qr)?; + let pin = pin_to_bytes(&pin); + let img = image::open(qr.as_ref())?; + let secret = KeyQrCode::decode(img, &pin)?; + Ok(secret.first().unwrap().clone()) + } + pub fn read_qrs>( &self, qrs: &[P],