mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-07-30 20:01:26 +00:00
Add export command
This commit is contained in:
@@ -168,6 +168,37 @@ impl Command for AddressCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ExportCommand {}
|
||||||
|
impl Command for ExportCommand {
|
||||||
|
fn help(&self) -> String {
|
||||||
|
let mut h = vec![];
|
||||||
|
h.push("Export private key for wallet addresses.");
|
||||||
|
h.push("Usage:");
|
||||||
|
h.push("export [t-address or z-address]");
|
||||||
|
h.push("");
|
||||||
|
h.push("If no address is passed, private key for all addresses in the wallet are exported/");
|
||||||
|
h.push("");
|
||||||
|
h.push("Example:");
|
||||||
|
h.push("export ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d");
|
||||||
|
|
||||||
|
h.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_help(&self) -> String {
|
||||||
|
"Export private key for wallet addresses".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
|
||||||
|
if args.len() > 1 {
|
||||||
|
return self.help();
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = if args.is_empty() { None } else { Some(args[0].to_string()) };
|
||||||
|
|
||||||
|
format!("{}", lightclient.do_export(address).pretty(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SendCommand {}
|
struct SendCommand {}
|
||||||
impl Command for SendCommand {
|
impl Command for SendCommand {
|
||||||
@@ -352,6 +383,7 @@ pub fn get_commands() -> Box<HashMap<String, Box<dyn Command>>> {
|
|||||||
map.insert("help".to_string(), Box::new(HelpCommand{}));
|
map.insert("help".to_string(), Box::new(HelpCommand{}));
|
||||||
map.insert("balance".to_string(), Box::new(BalanceCommand{}));
|
map.insert("balance".to_string(), Box::new(BalanceCommand{}));
|
||||||
map.insert("address".to_string(), Box::new(AddressCommand{}));
|
map.insert("address".to_string(), Box::new(AddressCommand{}));
|
||||||
|
map.insert("export".to_string(), Box::new(ExportCommand{}));
|
||||||
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
map.insert("info".to_string(), Box::new(InfoCommand{}));
|
||||||
map.insert("send".to_string(), Box::new(SendCommand{}));
|
map.insert("send".to_string(), Box::new(SendCommand{}));
|
||||||
map.insert("save".to_string(), Box::new(SaveCommand{}));
|
map.insert("save".to_string(), Box::new(SaveCommand{}));
|
||||||
|
@@ -63,7 +63,6 @@ impl LightClientConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_zcash_data_path(&self) -> Box<Path> {
|
pub fn get_zcash_data_path(&self) -> Box<Path> {
|
||||||
|
|
||||||
let mut zcash_data_location;
|
let mut zcash_data_location;
|
||||||
if cfg!(target_os="macos") || cfg!(target_os="windows") {
|
if cfg!(target_os="macos") || cfg!(target_os="windows") {
|
||||||
zcash_data_location = dirs::data_dir().expect("Couldn't determine app data directory!");
|
zcash_data_location = dirs::data_dir().expect("Couldn't determine app data directory!");
|
||||||
@@ -123,6 +122,15 @@ impl LightClientConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hrp_sapling_private_key(&self) -> &str {
|
||||||
|
match &self.chain_name[..] {
|
||||||
|
"main" => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
|
||||||
|
"test" => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
|
||||||
|
"regtest" => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY,
|
||||||
|
c => panic!("Unknown chain {}", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base58_pubkey_address(&self) -> [u8; 2] {
|
pub fn base58_pubkey_address(&self) -> [u8; 2] {
|
||||||
match &self.chain_name[..] {
|
match &self.chain_name[..] {
|
||||||
"main" => mainnet::B58_PUBKEY_ADDRESS_PREFIX,
|
"main" => mainnet::B58_PUBKEY_ADDRESS_PREFIX,
|
||||||
@@ -141,6 +149,15 @@ impl LightClientConfig {
|
|||||||
c => panic!("Unknown chain {}", c)
|
c => panic!("Unknown chain {}", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn base58_secretkey_prefix(&self) -> [u8; 1] {
|
||||||
|
match &self.chain_name[..] {
|
||||||
|
"main" => [0x80],
|
||||||
|
"test" => [0xEF],
|
||||||
|
"regtest" => [0xEF],
|
||||||
|
c => panic!("Unknown chain {}", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LightClient {
|
pub struct LightClient {
|
||||||
@@ -222,6 +239,41 @@ impl LightClient {
|
|||||||
self.wallet.last_scanned_height() as u64
|
self.wallet.last_scanned_height() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export private keys
|
||||||
|
pub fn do_export(&self, addr: Option<String>) -> json::JsonValue {
|
||||||
|
// Clone address so it can be moved into the closure
|
||||||
|
let address = addr.clone();
|
||||||
|
|
||||||
|
// Go over all z addresses
|
||||||
|
let z_keys = self.wallet.get_z_private_keys().iter()
|
||||||
|
.filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr))
|
||||||
|
.map( |(addr, pk)|
|
||||||
|
object!{
|
||||||
|
"address" => addr.clone(),
|
||||||
|
"private_key" => pk.clone()
|
||||||
|
}
|
||||||
|
).collect::<Vec<JsonValue>>();
|
||||||
|
|
||||||
|
// Clone address so it can be moved into the closure
|
||||||
|
let address = addr.clone();
|
||||||
|
|
||||||
|
// Go over all t addresses
|
||||||
|
let t_keys = self.wallet.get_t_secret_keys().iter()
|
||||||
|
.filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr))
|
||||||
|
.map( |(addr, sk)|
|
||||||
|
object!{
|
||||||
|
"address" => addr.clone(),
|
||||||
|
"private_key" => sk.clone(),
|
||||||
|
}
|
||||||
|
).collect::<Vec<JsonValue>>();
|
||||||
|
|
||||||
|
let mut all_keys = vec![];
|
||||||
|
all_keys.extend_from_slice(&z_keys);
|
||||||
|
all_keys.extend_from_slice(&t_keys);
|
||||||
|
|
||||||
|
all_keys.into()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn do_address(&self) -> json::JsonValue {
|
pub fn do_address(&self) -> json::JsonValue {
|
||||||
// Collect z addresses
|
// Collect z addresses
|
||||||
let z_addresses = self.wallet.address.iter().map( |ad| {
|
let z_addresses = self.wallet.address.iter().map( |ad| {
|
||||||
|
@@ -3,7 +3,6 @@ use std::io::{self, Read, Write};
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
use log::{info, warn, error};
|
use log::{info, warn, error};
|
||||||
@@ -16,7 +15,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
|||||||
use pairing::bls12_381::{Bls12};
|
use pairing::bls12_381::{Bls12};
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
encoding::encode_payment_address,
|
encoding::{encode_payment_address, encode_extended_spending_key},
|
||||||
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,27 +130,6 @@ impl LightWallet {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sapling_params(config: &LightClientConfig) -> Result<(Vec<u8>, Vec<u8>), Error> {
|
|
||||||
// Read Sapling Params
|
|
||||||
let mut sapling_output = vec![];
|
|
||||||
let mut f = match File::open(config.get_params_path("sapling-output.params")) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => return Err(Error::new(ErrorKind::NotFound,
|
|
||||||
format!("Couldn't read {}", config.get_params_path("sapling-output.params").display())))
|
|
||||||
};
|
|
||||||
f.read_to_end(&mut sapling_output)?;
|
|
||||||
|
|
||||||
let mut sapling_spend = vec![];
|
|
||||||
let mut f = match File::open(config.get_params_path("sapling-spend.params")) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => return Err(Error::new(ErrorKind::NotFound,
|
|
||||||
format!("Couldn't read {}", config.get_params_path("sapling-spend.params").display())))
|
|
||||||
};
|
|
||||||
f.read_to_end(&mut sapling_spend)?;
|
|
||||||
|
|
||||||
Ok((sapling_spend, sapling_output))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pk_from_bip39seed(bip39seed: &[u8]) ->
|
fn get_pk_from_bip39seed(bip39seed: &[u8]) ->
|
||||||
(ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) {
|
(ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) {
|
||||||
let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
|
let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
|
||||||
@@ -306,6 +284,23 @@ impl LightWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all z-address private keys. Returns a Vector of (address, privatekey)
|
||||||
|
pub fn get_z_private_keys(&self) -> Vec<(String, String)> {
|
||||||
|
self.extsks.iter().map(|sk| {
|
||||||
|
(encode_payment_address(self.config.hrp_sapling_address(),
|
||||||
|
&ExtendedFullViewingKey::from(sk).default_address().unwrap().1),
|
||||||
|
encode_extended_spending_key(self.config.hrp_sapling_private_key(), &sk)
|
||||||
|
)
|
||||||
|
}).collect::<Vec<(String, String)>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all t-address private keys. Returns a Vector of (address, secretkey)
|
||||||
|
pub fn get_t_secret_keys(&self) -> Vec<(String, String)> {
|
||||||
|
self.tkeys.iter().map(|sk| {
|
||||||
|
(self.address_from_sk(sk), sk[..].to_base58check(&self.config.base58_secretkey_prefix(), &[0x01]))
|
||||||
|
}).collect::<Vec<(String, String)>>()
|
||||||
|
}
|
||||||
|
|
||||||
// Clears all the downloaded blocks and resets the state back to the inital block.
|
// Clears all the downloaded blocks and resets the state back to the inital block.
|
||||||
// After this, the wallet's initial state will need to be set
|
// After this, the wallet's initial state will need to be set
|
||||||
// and the wallet will need to be rescanned
|
// and the wallet will need to be rescanned
|
||||||
@@ -1116,6 +1111,8 @@ impl LightWallet {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Error, ErrorKind};
|
||||||
use ff::{Field, PrimeField, PrimeFieldRepr};
|
use ff::{Field, PrimeField, PrimeFieldRepr};
|
||||||
use pairing::bls12_381::Bls12;
|
use pairing::bls12_381::Bls12;
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
@@ -1146,6 +1143,27 @@ pub mod tests {
|
|||||||
use crate::LightClientConfig;
|
use crate::LightClientConfig;
|
||||||
use secp256k1::{Secp256k1, key::PublicKey, key::SecretKey};
|
use secp256k1::{Secp256k1, key::PublicKey, key::SecretKey};
|
||||||
|
|
||||||
|
fn get_sapling_params(config: &LightClientConfig) -> Result<(Vec<u8>, Vec<u8>), Error> {
|
||||||
|
// Read Sapling Params
|
||||||
|
let mut sapling_output = vec![];
|
||||||
|
let mut f = match File::open(config.get_params_path("sapling-output.params")) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => return Err(Error::new(ErrorKind::NotFound,
|
||||||
|
format!("Couldn't read {}", config.get_params_path("sapling-output.params").display())))
|
||||||
|
};
|
||||||
|
f.read_to_end(&mut sapling_output)?;
|
||||||
|
|
||||||
|
let mut sapling_spend = vec![];
|
||||||
|
let mut f = match File::open(config.get_params_path("sapling-spend.params")) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => return Err(Error::new(ErrorKind::NotFound,
|
||||||
|
format!("Couldn't read {}", config.get_params_path("sapling-spend.params").display())))
|
||||||
|
};
|
||||||
|
f.read_to_end(&mut sapling_spend)?;
|
||||||
|
|
||||||
|
Ok((sapling_spend, sapling_output))
|
||||||
|
}
|
||||||
|
|
||||||
struct FakeCompactBlock {
|
struct FakeCompactBlock {
|
||||||
block: CompactBlock,
|
block: CompactBlock,
|
||||||
}
|
}
|
||||||
@@ -1787,7 +1805,7 @@ pub mod tests {
|
|||||||
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||||
|
|
||||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||||
let (ss, so) = LightWallet::get_sapling_params(&config).unwrap();
|
let (ss, so) = get_sapling_params(&config).unwrap();
|
||||||
|
|
||||||
// Create a tx and send to address
|
// Create a tx and send to address
|
||||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||||
@@ -1848,7 +1866,7 @@ pub mod tests {
|
|||||||
let (wallet, config, txid1, block_hash) = get_test_wallet(AMOUNT1);
|
let (wallet, config, txid1, block_hash) = get_test_wallet(AMOUNT1);
|
||||||
|
|
||||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||||
let (ss, so) = LightWallet::get_sapling_params(&config).unwrap();
|
let (ss, so) = get_sapling_params(&config).unwrap();
|
||||||
|
|
||||||
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
|
let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap());
|
||||||
const AMOUNT_SENT: u64 = 30;
|
const AMOUNT_SENT: u64 = 30;
|
||||||
@@ -1941,7 +1959,7 @@ pub mod tests {
|
|||||||
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||||
|
|
||||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||||
let (ss, so) = LightWallet::get_sapling_params(&config).unwrap();
|
let (ss, so) = get_sapling_params(&config).unwrap();
|
||||||
|
|
||||||
// Create a tx and send to address. This should consume both the UTXO and the note
|
// Create a tx and send to address. This should consume both the UTXO and the note
|
||||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||||
@@ -2015,7 +2033,7 @@ pub mod tests {
|
|||||||
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
let fee: u64 = DEFAULT_FEE.try_into().unwrap();
|
||||||
|
|
||||||
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap();
|
||||||
let (ss, so) = LightWallet::get_sapling_params(&config).unwrap();
|
let (ss, so) = get_sapling_params(&config).unwrap();
|
||||||
|
|
||||||
// Create a tx and send to address
|
// Create a tx and send to address
|
||||||
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
let raw_tx = wallet.send_to_address(branch_id, &ss, &so,
|
||||||
|
Reference in New Issue
Block a user