Browse Source

Add vanity address generator

duke
Aditya Kulkarni 5 years ago
parent
commit
a96b825078
  1. 72
      cli/src/main.rs
  2. 172
      lib/src/paper.rs
  3. 20
      lib/src/pdf.rs
  4. 8
      ui/qtlib/src/lib.rs
  5. 6
      ui/qtlib/src/zecpaperrust.h
  6. 2
      ui/src/mainwindow.cpp

72
cli/src/main.rs

@ -38,6 +38,10 @@ fn main() {
.long("entropy") .long("entropy")
.takes_value(true) .takes_value(true)
.help("Provide additional entropy to the random number generator. Any random string, containing 32-64 characters")) .help("Provide additional entropy to the random number generator. Any random string, containing 32-64 characters"))
.arg(Arg::with_name("vanity")
.long("vanity")
.help("Generate a vanity address with the given prefix")
.takes_value(true))
.arg(Arg::with_name("t_addresses") .arg(Arg::with_name("t_addresses")
.short("t") .short("t")
.long("taddrs") .long("taddrs")
@ -60,7 +64,7 @@ fn main() {
})) }))
.get_matches(); .get_matches();
let testnet: bool = matches.is_present("testnet"); let is_testnet: bool = matches.is_present("testnet");
let nohd: bool = matches.is_present("nohd"); let nohd: bool = matches.is_present("nohd");
@ -74,22 +78,6 @@ fn main() {
return; return;
} }
// Get user entropy.
let mut entropy: Vec<u8> = Vec::new();
// If the user hasn't specified any, read from the stdin
if matches.value_of("entropy").is_none() {
// Read from stdin
println!("Provide additional entropy for generating random numbers. Type in a string of random characters, press [ENTER] when done");
let mut buffer = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut buffer).unwrap();
entropy.extend_from_slice(buffer.as_bytes());
} else {
// Use provided entropy.
entropy.extend(matches.value_of("entropy").unwrap().as_bytes());
}
// Get the filename and output format // Get the filename and output format
let filename = matches.value_of("output"); let filename = matches.value_of("output");
let format = matches.value_of("format").unwrap(); let format = matches.value_of("format").unwrap();
@ -106,11 +94,49 @@ fn main() {
// Number of z addresses to generate // Number of z addresses to generate
let z_addresses = matches.value_of("z_addresses").unwrap().parse::<u32>().unwrap(); let z_addresses = matches.value_of("z_addresses").unwrap().parse::<u32>().unwrap();
print!("Generating {} Sapling addresses and {} Transparent addresses...", z_addresses, t_addresses);
io::stdout().flush().ok(); let addresses = if !matches.value_of("vanity").is_none() {
let addresses = generate_wallet(testnet, nohd, z_addresses, t_addresses, &entropy); if z_addresses != 1 {
println!("[OK]"); eprintln!("Can only generate 1 zaddress in vanity mode. You specified {}", z_addresses);
return;
}
if t_addresses != 0 {
eprintln!("Can't generate vanity t-addressses yet");
return;
}
let prefix = matches.value_of("vanity").unwrap().to_string();
println!("Generating address starting with \"{}\"", prefix);
let addresses = generate_vanity_wallet(is_testnet, prefix);
// return
addresses
} else {
// Get user entropy.
let mut entropy: Vec<u8> = Vec::new();
// If the user hasn't specified any, read from the stdin
if matches.value_of("entropy").is_none() {
// Read from stdin
println!("Provide additional entropy for generating random numbers. Type in a string of random characters, press [ENTER] when done");
let mut buffer = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut buffer).unwrap();
entropy.extend_from_slice(buffer.as_bytes());
} else {
// Use provided entropy.
entropy.extend(matches.value_of("entropy").unwrap().as_bytes());
}
print!("Generating {} Sapling addresses and {} Transparent addresses...", z_addresses, t_addresses);
io::stdout().flush().ok();
let addresses = generate_wallet(is_testnet, nohd, z_addresses, t_addresses, &entropy);
println!("[OK]");
addresses
};
// If the default format is present, write to the console if the filename is absent // If the default format is present, write to the console if the filename is absent
if format == "json" { if format == "json" {
if filename.is_none() { if filename.is_none() {
@ -123,7 +149,7 @@ fn main() {
// We already know the output file name was specified // We already know the output file name was specified
print!("Writing {:?} as a PDF file...", filename.unwrap()); print!("Writing {:?} as a PDF file...", filename.unwrap());
io::stdout().flush().ok(); io::stdout().flush().ok();
match pdf::save_to_pdf(&addresses, filename.unwrap()) { match pdf::save_to_pdf(is_testnet, &addresses, filename.unwrap()) {
Ok(_) => { println!("[OK]");}, Ok(_) => { println!("[OK]");},
Err(e) => { Err(e) => {
eprintln!("[ERROR]"); eprintln!("[ERROR]");

172
lib/src/paper.rs

@ -1,14 +1,19 @@
use std::thread;
use hex; use hex;
use secp256k1; use secp256k1;
use ripemd160::{Ripemd160, Digest}; use ripemd160::{Ripemd160, Digest};
use base58::{ToBase58}; use base58::{ToBase58};
use zip32::{ChildIndex, ExtendedSpendingKey}; use zip32::{ChildIndex, ExtendedSpendingKey, ExtendedFullViewingKey};
use bech32::{Bech32, u5, ToBase32}; use bech32::{Bech32, u5, ToBase32};
use rand::{Rng, ChaChaRng, FromEntropy, SeedableRng}; use rand::{Rng, ChaChaRng, FromEntropy, SeedableRng};
use json::{array, object}; use json::{array, object};
use sha2; use sha2;
use std::io;
use std::io::Write;
use std::sync::mpsc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{SystemTime};
/// A trait for converting a [u8] to base58 encoded string. /// A trait for converting a [u8] to base58 encoded string.
pub trait ToBase58Check { pub trait ToBase58Check {
@ -42,21 +47,23 @@ fn double_sha256(payload: &[u8]) -> Vec<u8> {
/// Parameters used to generate addresses and private keys. Look in chainparams.cpp (in zcashd/src) /// Parameters used to generate addresses and private keys. Look in chainparams.cpp (in zcashd/src)
/// to get these values. /// to get these values.
/// Usually these will be different for testnet and for mainnet. /// Usually these will be different for testnet and for mainnet.
struct CoinParams { pub struct CoinParams {
taddress_version: [u8; 2], pub taddress_version: [u8; 2],
tsecret_prefix : [u8; 1], pub tsecret_prefix : [u8; 1],
zaddress_prefix : String, pub zaddress_prefix : String,
zsecret_prefix : String, pub zsecret_prefix : String,
cointype : u32, pub zviewkey_prefix : String,
pub cointype : u32,
} }
fn params(testnet: bool) -> CoinParams { pub fn params(is_testnet: bool) -> CoinParams {
if testnet { if is_testnet {
CoinParams { CoinParams {
taddress_version : [0x1D, 0x25], taddress_version : [0x1D, 0x25],
tsecret_prefix : [0xEF], tsecret_prefix : [0xEF],
zaddress_prefix : "ztestsapling".to_string(), zaddress_prefix : "ztestsapling".to_string(),
zsecret_prefix : "secret-extended-key-test".to_string(), zsecret_prefix : "secret-extended-key-test".to_string(),
zviewkey_prefix : "zviews".to_string(),
cointype : 1 cointype : 1
} }
} else { } else {
@ -65,13 +72,116 @@ fn params(testnet: bool) -> CoinParams {
tsecret_prefix : [0x80], tsecret_prefix : [0x80],
zaddress_prefix : "zs".to_string(), zaddress_prefix : "zs".to_string(),
zsecret_prefix : "secret-extended-key-main".to_string(), zsecret_prefix : "secret-extended-key-main".to_string(),
zviewkey_prefix : "zviewtestsapling".to_string(),
cointype : 133 cointype : 133
} }
} }
} }
pub fn vanity_thread(is_testnet: bool, entropy: &[u8], prefix: String, tx: mpsc::Sender<String>, please_stop: Arc<AtomicBool>) {
let prefix_str : String = params(is_testnet).zaddress_prefix + "1" + &prefix;
let msk = ExtendedSpendingKey::master(&entropy);
let mut i: u32 = 0;
let mut v = vec![0; 43];
loop {
let spk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(&msk, &[ ChildIndex::NonHardened(i) ]);
let (_d, addr) = spk.default_address().expect("Cannot get result");
// Address is encoded as a bech32 string
v.get_mut(..11).unwrap().copy_from_slice(&addr.diversifier.0);
addr.pk_d.write(v.get_mut(11..).unwrap()).expect("Cannot write!");
let checked_data: Vec<u5> = v.to_base32();
let encoded : String = Bech32::new(params(is_testnet).zaddress_prefix.into(), checked_data).expect("bech32 failed").to_string();
if encoded.starts_with(&prefix_str) {
// Private Key is encoded as bech32 string
let mut vp = Vec::new();
spk.write(&mut vp).expect("Can't write private key");
let c_d: Vec<u5> = vp.to_base32();
let encoded_pk = Bech32::new(params(is_testnet).zsecret_prefix.into(), c_d).expect("bech32 failed").to_string();
let wallet = array!{object!{
"num" => 0,
"address" => encoded,
"private_key" => encoded_pk,
"type" => "zaddr"}};
tx.send(json::stringify_pretty(wallet, 2)).unwrap();
return;
}
i = i + 1;
if i%1000 == 0 {
if please_stop.load(Ordering::Relaxed) {
return;
}
tx.send("Processed:1000".to_string()).unwrap();
}
if i == 0 { return; }
}
}
/// Generate a vanity address with the given prefix.
pub fn generate_vanity_wallet(is_testnet: bool, prefix: String) -> String {
// Get 32 bytes of system entropy
let mut system_rng = ChaChaRng::from_entropy();
let (tx, rx) = mpsc::channel();
let please_stop = Arc::new(AtomicBool::new(false));
let mut handles = Vec::new();
for _i in 0..8 {
let testnet_local = is_testnet.clone();
let prefix_local = prefix.clone();
let tx_local = mpsc::Sender::clone(&tx);
let ps_local = please_stop.clone();
let mut entropy: [u8; 32] = [0; 32];
system_rng.fill(&mut entropy);
let handle = thread::spawn(move || {
vanity_thread(testnet_local, &entropy, prefix_local, tx_local, ps_local);
});
handles.push(handle);
}
let mut processed: u64 = 0;
let now = SystemTime::now();
let mut wallet: String;
loop {
let recv = rx.recv().unwrap();
if recv.starts_with(&"Processed") {
processed = processed + 1000;
let timeelapsed = now.elapsed().unwrap().as_secs();
print!("Checking addresses at {}/sec \r", (processed / timeelapsed));
io::stdout().flush().ok().unwrap();
} else {
// Found a solution
println!(""); // To clear the previous inline output to stdout;
wallet = recv;
please_stop.store(true, Ordering::Relaxed);
break;
}
}
for handle in handles {
handle.join().unwrap();
}
return wallet;
}
/// Generate a series of `count` addresses and private keys. /// Generate a series of `count` addresses and private keys.
pub fn generate_wallet(testnet: bool, nohd: bool, zcount: u32, tcount: u32, user_entropy: &[u8]) -> String { pub fn generate_wallet(is_testnet: bool, nohd: bool, zcount: u32, tcount: u32, user_entropy: &[u8]) -> String {
// Get 32 bytes of system entropy // Get 32 bytes of system entropy
let mut system_entropy:[u8; 32] = [0; 32]; let mut system_entropy:[u8; 32] = [0; 32];
{ {
@ -95,10 +205,10 @@ pub fn generate_wallet(testnet: bool, nohd: bool, zcount: u32, tcount: u32, user
let mut seed: [u8; 32] = [0; 32]; let mut seed: [u8; 32] = [0; 32];
rng.fill(&mut seed); rng.fill(&mut seed);
return gen_addresses_with_seed_as_json(testnet, zcount, tcount, |i| (seed.to_vec(), i)); return gen_addresses_with_seed_as_json(is_testnet, zcount, tcount, |i| (seed.to_vec(), i));
} else { } else {
// Not using HD addresses, so derive a new seed every time // Not using HD addresses, so derive a new seed every time
return gen_addresses_with_seed_as_json(testnet, zcount, tcount, |_| { return gen_addresses_with_seed_as_json(is_testnet, zcount, tcount, |_| {
let mut seed:[u8; 32] = [0; 32]; let mut seed:[u8; 32] = [0; 32];
rng.fill(&mut seed); rng.fill(&mut seed);
@ -115,7 +225,7 @@ pub fn generate_wallet(testnet: bool, nohd: bool, zcount: u32, tcount: u32, user
/// get_seed is a closure that will take the address number being derived, and return a tuple cointaining the /// get_seed is a closure that will take the address number being derived, and return a tuple cointaining the
/// seed and child number to use to derive this wallet. /// seed and child number to use to derive this wallet.
/// It is useful if we want to reuse (or not) the seed across multiple wallets. /// It is useful if we want to reuse (or not) the seed across multiple wallets.
fn gen_addresses_with_seed_as_json<F>(testnet: bool, zcount: u32, tcount: u32, mut get_seed: F) -> String fn gen_addresses_with_seed_as_json<F>(is_testnet: bool, zcount: u32, tcount: u32, mut get_seed: F) -> String
where F: FnMut(u32) -> (Vec<u8>, u32) where F: FnMut(u32) -> (Vec<u8>, u32)
{ {
let mut ans = array![]; let mut ans = array![];
@ -131,7 +241,7 @@ fn gen_addresses_with_seed_as_json<F>(testnet: bool, zcount: u32, tcount: u32, m
// First generate the Z addresses // First generate the Z addresses
for i in 0..zcount { for i in 0..zcount {
let (seed, child) = get_seed(i); let (seed, child) = get_seed(i);
let (addr, pk, path) = get_zaddress(testnet, &seed, child); let (addr, pk, _vk, path) = get_zaddress(is_testnet, &seed, child);
ans.push(object!{ ans.push(object!{
"num" => i, "num" => i,
"address" => addr, "address" => addr,
@ -143,7 +253,7 @@ fn gen_addresses_with_seed_as_json<F>(testnet: bool, zcount: u32, tcount: u32, m
// Next generate the T addresses // Next generate the T addresses
for i in 0..tcount { for i in 0..tcount {
let (addr, pk_wif) = get_taddress(testnet, &mut rng); let (addr, pk_wif) = get_taddress(is_testnet, &mut rng);
ans.push(object!{ ans.push(object!{
"num" => i, "num" => i,
@ -157,7 +267,7 @@ fn gen_addresses_with_seed_as_json<F>(testnet: bool, zcount: u32, tcount: u32, m
} }
/// Generate a t address /// Generate a t address
fn get_taddress(testnet: bool, mut rng: &mut ChaChaRng) -> (String, String) { fn get_taddress(is_testnet: bool, mut rng: &mut ChaChaRng) -> (String, String) {
// SECP256k1 context // SECP256k1 context
let ctx = secp256k1::Secp256k1::default(); let ctx = secp256k1::Secp256k1::default();
@ -166,28 +276,28 @@ fn get_taddress(testnet: bool, mut rng: &mut ChaChaRng) -> (String, String) {
// Address // Address
let mut hash160 = Ripemd160::new(); let mut hash160 = Ripemd160::new();
hash160.input(sha2::Sha256::digest(&pubkey.serialize().to_vec())); hash160.input(sha2::Sha256::digest(&pubkey.serialize().to_vec()));
let addr = hash160.result().to_base58check(&params(testnet).taddress_version, &[]); let addr = hash160.result().to_base58check(&params(is_testnet).taddress_version, &[]);
// Private Key // Private Key
let sk_bytes: &[u8] = &sk[..]; let sk_bytes: &[u8] = &sk[..];
let pk_wif = sk_bytes.to_base58check(&params(testnet).tsecret_prefix, &[0x01]); let pk_wif = sk_bytes.to_base58check(&params(is_testnet).tsecret_prefix, &[0x01]);
return (addr, pk_wif); return (addr, pk_wif);
} }
/// Generate a standard ZIP-32 address from the given seed at 32'/44'/0'/index /// Generate a standard ZIP-32 address from the given seed at 32'/44'/0'/index
fn get_zaddress(testnet: bool, seed: &[u8], index: u32) -> (String, String, json::JsonValue) { fn get_zaddress(is_testnet: bool, seed: &[u8], index: u32) -> (String, String, String, json::JsonValue) {
let spk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( let spk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(seed), &ExtendedSpendingKey::master(seed),
&[ &[
ChildIndex::Hardened(32), ChildIndex::Hardened(32),
ChildIndex::Hardened(params(testnet).cointype), ChildIndex::Hardened(params(is_testnet).cointype),
ChildIndex::Hardened(index) ChildIndex::Hardened(index)
], ],
); );
let path = object!{ let path = object!{
"HDSeed" => hex::encode(seed), "HDSeed" => hex::encode(seed),
"path" => format!("m/32'/{}'/{}'", params(testnet).cointype, index) "path" => format!("m/32'/{}'/{}'", params(is_testnet).cointype, index)
}; };
let (_d, addr) = spk.default_address().expect("Cannot get result"); let (_d, addr) = spk.default_address().expect("Cannot get result");
@ -197,15 +307,21 @@ fn get_zaddress(testnet: bool, seed: &[u8], index: u32) -> (String, String, json
v.get_mut(..11).unwrap().copy_from_slice(&addr.diversifier.0); v.get_mut(..11).unwrap().copy_from_slice(&addr.diversifier.0);
addr.pk_d.write(v.get_mut(11..).unwrap()).expect("Cannot write!"); addr.pk_d.write(v.get_mut(11..).unwrap()).expect("Cannot write!");
let checked_data: Vec<u5> = v.to_base32(); let checked_data: Vec<u5> = v.to_base32();
let encoded = Bech32::new(params(testnet).zaddress_prefix.into(), checked_data).expect("bech32 failed").to_string(); let encoded = Bech32::new(params(is_testnet).zaddress_prefix.into(), checked_data).expect("bech32 failed").to_string();
// Private Key is encoded as bech32 string // Private Key is encoded as bech32 string
let mut vp = Vec::new(); let mut vp = Vec::new();
spk.write(&mut vp).expect("Can't write private key"); spk.write(&mut vp).expect("Can't write private key");
let c_d: Vec<u5> = vp.to_base32(); let c_d: Vec<u5> = vp.to_base32();
let encoded_pk = Bech32::new(params(testnet).zsecret_prefix.into(), c_d).expect("bech32 failed").to_string(); let encoded_pk = Bech32::new(params(is_testnet).zsecret_prefix.into(), c_d).expect("bech32 failed").to_string();
// Viewing Key is encoded as bech32 string
let mut vv = Vec::new();
ExtendedFullViewingKey::from(&spk).write(&mut vv).expect("Can't write viewing key");
let c_v: Vec<u5> = vv.to_base32();
let encoded_vk = Bech32::new(params(is_testnet).zviewkey_prefix.into(), c_v).expect("bech32 failed").to_string();
return (encoded.to_string(), encoded_pk.to_string(), path); return (encoded, encoded_pk, encoded_vk, path);
} }
@ -340,7 +456,7 @@ mod tests {
} }
/// Test the address derivation against the test data (see below) /// Test the address derivation against the test data (see below)
fn test_address_derivation(testdata: &str, testnet: bool) { fn test_address_derivation(testdata: &str, is_testnet: bool) {
use crate::paper::gen_addresses_with_seed_as_json; use crate::paper::gen_addresses_with_seed_as_json;
let td = json::parse(&testdata.replace("'", "\"")).unwrap(); let td = json::parse(&testdata.replace("'", "\"")).unwrap();
@ -348,7 +464,7 @@ mod tests {
let seed = hex::decode(i["seed"].as_str().unwrap()).unwrap(); let seed = hex::decode(i["seed"].as_str().unwrap()).unwrap();
let num = i["num"].as_u32().unwrap(); let num = i["num"].as_u32().unwrap();
let addresses = gen_addresses_with_seed_as_json(testnet, num+1, 0, |child| (seed.clone(), child)); let addresses = gen_addresses_with_seed_as_json(is_testnet, num+1, 0, |child| (seed.clone(), child));
let j = json::parse(&addresses).unwrap(); let j = json::parse(&addresses).unwrap();
assert_eq!(j[num as usize]["address"], i["addr"]); assert_eq!(j[num as usize]["address"], i["addr"]);

20
lib/src/pdf.rs

@ -1,5 +1,7 @@
extern crate printpdf; extern crate printpdf;
use crate::paper::params;
use qrcode::QrCode; use qrcode::QrCode;
use qrcode::types::Color; use qrcode::types::Color;
@ -9,10 +11,11 @@ use std::f64;
use std::fs::File; use std::fs::File;
use printpdf::*; use printpdf::*;
/** /**
* Save the list of wallets (address + private keys) to the given PDF file name. * Save the list of wallets (address + private keys) to the given PDF file name.
*/ */
pub fn save_to_pdf(addresses: &str, filename: &str) -> Result<(), String> { pub fn save_to_pdf(is_testnet: bool, addresses: &str, filename: &str) -> Result<(), String> {
let (doc, page1, layer1) = PdfDocument::new("Zec Sapling Paper Wallet", Mm(210.0), Mm(297.0), "Layer 1"); let (doc, page1, layer1) = PdfDocument::new("Zec Sapling Paper Wallet", Mm(210.0), Mm(297.0), "Layer 1");
let font = doc.add_builtin_font(BuiltinFont::Courier).unwrap(); let font = doc.add_builtin_font(BuiltinFont::Courier).unwrap();
@ -39,15 +42,16 @@ pub fn save_to_pdf(addresses: &str, filename: &str) -> Result<(), String> {
current_layer = doc.get_page(page2).add_layer("Layer 3"); current_layer = doc.get_page(page2).add_layer("Layer 3");
} }
let address = kv["address"].as_str().unwrap(); let address = kv["address"].as_str().unwrap();
let pk = kv["private_key"].as_str().unwrap(); let pk = kv["private_key"].as_str().unwrap();
let is_taddr = !address.starts_with(&params(is_testnet).zaddress_prefix);
let (seed, hdpath, is_taddr) = if kv["type"].as_str().unwrap() == "zaddr" { let (seed, hdpath) = if kv["type"].as_str().unwrap() == "zaddr" && kv.contains("seed") {
(kv["seed"]["HDSeed"].as_str().unwrap(), kv["seed"]["path"].as_str().unwrap(), false) (kv["seed"]["HDSeed"].as_str().unwrap(), kv["seed"]["path"].as_str().unwrap())
} else { } else {
("", "", true) ("", "")
}; };
// Add address + private key // Add address + private key
add_address_to_page(&current_layer, &font, &font_bold, address, is_taddr, pos); add_address_to_page(&current_layer, &font, &font_bold, address, is_taddr, pos);
add_pk_to_page(&current_layer, &font, &font_bold, pk, address, is_taddr, seed, hdpath, pos); add_pk_to_page(&current_layer, &font, &font_bold, pk, address, is_taddr, seed, hdpath, pos);
@ -202,7 +206,7 @@ fn add_pk_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, fon
} }
// And add the seed too. // And add the seed too.
if !is_taddr { if !seed.is_empty() {
current_layer.use_text(format!("HDSeed: {}, Path: {}", seed, path).as_str(), 8, Mm(10.0), Mm(ypos-35.0), &font); current_layer.use_text(format!("HDSeed: {}, Path: {}", seed, path).as_str(), 8, Mm(10.0), Mm(ypos-35.0), &font);
} }
} }

8
ui/qtlib/src/lib.rs

@ -8,19 +8,19 @@ use zecpaperlib::{pdf, paper};
* after using it to free it properly * after using it to free it properly
*/ */
#[no_mangle] #[no_mangle]
pub extern fn rust_generate_wallet(testnet: bool, zcount: u32, tcount: u32, entropy: *const c_char) -> *mut c_char { pub extern fn rust_generate_wallet(is_testnet: bool, zcount: u32, tcount: u32, entropy: *const c_char) -> *mut c_char {
let entropy_str = unsafe { let entropy_str = unsafe {
assert!(!entropy.is_null()); assert!(!entropy.is_null());
CStr::from_ptr(entropy) CStr::from_ptr(entropy)
}; };
let c_str = CString::new(paper::generate_wallet(testnet, false, zcount, tcount, entropy_str.to_bytes())).unwrap(); let c_str = CString::new(paper::generate_wallet(is_testnet, false, zcount, tcount, entropy_str.to_bytes())).unwrap();
return c_str.into_raw(); return c_str.into_raw();
} }
#[no_mangle] #[no_mangle]
pub extern fn rust_save_as_pdf(json: *const c_char, file: *const c_char)-> bool { pub extern fn rust_save_as_pdf(is_testnet: bool, json: *const c_char, file: *const c_char)-> bool {
let json_str = unsafe { let json_str = unsafe {
assert!(!json.is_null()); assert!(!json.is_null());
@ -33,7 +33,7 @@ pub extern fn rust_save_as_pdf(json: *const c_char, file: *const c_char)-> bool
CStr::from_ptr(file) CStr::from_ptr(file)
}; };
match pdf::save_to_pdf(json_str.to_str().unwrap(), file_str.to_str().unwrap()) { match pdf::save_to_pdf(is_testnet, json_str.to_str().unwrap(), file_str.to_str().unwrap()) {
Ok(_) => return true, Ok(_) => return true,
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);

6
ui/qtlib/src/zecpaperrust.h

@ -5,9 +5,9 @@
extern "C"{ extern "C"{
#endif #endif
extern char * rust_generate_wallet(bool testnet, unsigned int zcount, unsigned int tcount, const char* entropy); extern char * rust_generate_wallet(bool is_testnet, unsigned int zcount, unsigned int tcount, const char* entropy);
extern void rust_free_string(char* s); extern void rust_free_string (char* s);
extern bool rust_save_as_pdf(const char* json, const char* filename); extern bool rust_save_as_pdf (bool is_testnet, const char* json, const char* filename);
#ifdef __cplusplus #ifdef __cplusplus
} }

2
ui/src/mainwindow.cpp

@ -105,7 +105,7 @@ void MainWindow::SaveAsPDFButton() {
if (!filename.endsWith(".pdf")) if (!filename.endsWith(".pdf"))
filename = filename + ".pdf"; filename = filename + ".pdf";
bool success = rust_save_as_pdf(this->currentWallets.toStdString().c_str(), filename.toStdString().c_str()); bool success = rust_save_as_pdf(false, this->currentWallets.toStdString().c_str(), filename.toStdString().c_str());
if (success) { if (success) {
QMessageBox::information(this, tr("Saved!"), tr("The wallets were saved to ") + filename); QMessageBox::information(this, tr("Saved!"), tr("The wallets were saved to ") + filename);
} else { } else {

Loading…
Cancel
Save