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")
.takes_value(true)
.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")
.short("t")
.long("taddrs")
@ -60,7 +64,7 @@ fn main() {
}))
.get_matches();
let testnet: bool = matches.is_present("testnet");
let is_testnet: bool = matches.is_present("testnet");
let nohd: bool = matches.is_present("nohd");
@ -74,22 +78,6 @@ fn main() {
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
let filename = matches.value_of("output");
let format = matches.value_of("format").unwrap();
@ -106,11 +94,49 @@ fn main() {
// Number of z addresses to generate
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 = generate_wallet(testnet, nohd, z_addresses, t_addresses, &entropy);
println!("[OK]");
let addresses = if !matches.value_of("vanity").is_none() {
if z_addresses != 1 {
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 format == "json" {
if filename.is_none() {
@ -123,7 +149,7 @@ fn main() {
// We already know the output file name was specified
print!("Writing {:?} as a PDF file...", filename.unwrap());
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]");},
Err(e) => {
eprintln!("[ERROR]");

172
lib/src/paper.rs

@ -1,14 +1,19 @@
use std::thread;
use hex;
use secp256k1;
use ripemd160::{Ripemd160, Digest};
use base58::{ToBase58};
use zip32::{ChildIndex, ExtendedSpendingKey};
use zip32::{ChildIndex, ExtendedSpendingKey, ExtendedFullViewingKey};
use bech32::{Bech32, u5, ToBase32};
use rand::{Rng, ChaChaRng, FromEntropy, SeedableRng};
use json::{array, object};
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.
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)
/// to get these values.
/// Usually these will be different for testnet and for mainnet.
struct CoinParams {
taddress_version: [u8; 2],
tsecret_prefix : [u8; 1],
zaddress_prefix : String,
zsecret_prefix : String,
cointype : u32,
pub struct CoinParams {
pub taddress_version: [u8; 2],
pub tsecret_prefix : [u8; 1],
pub zaddress_prefix : String,
pub zsecret_prefix : String,
pub zviewkey_prefix : String,
pub cointype : u32,
}
fn params(testnet: bool) -> CoinParams {
if testnet {
pub fn params(is_testnet: bool) -> CoinParams {
if is_testnet {
CoinParams {
taddress_version : [0x1D, 0x25],
tsecret_prefix : [0xEF],
zaddress_prefix : "ztestsapling".to_string(),
zsecret_prefix : "secret-extended-key-test".to_string(),
zviewkey_prefix : "zviews".to_string(),
cointype : 1
}
} else {
@ -65,13 +72,116 @@ fn params(testnet: bool) -> CoinParams {
tsecret_prefix : [0x80],
zaddress_prefix : "zs".to_string(),
zsecret_prefix : "secret-extended-key-main".to_string(),
zviewkey_prefix : "zviewtestsapling".to_string(),
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.
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
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];
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 {
// 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];
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
/// 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.
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)
{
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
for i in 0..zcount {
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!{
"num" => i,
"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
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!{
"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
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
let ctx = secp256k1::Secp256k1::default();
@ -166,28 +276,28 @@ fn get_taddress(testnet: bool, mut rng: &mut ChaChaRng) -> (String, String) {
// Address
let mut hash160 = Ripemd160::new();
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
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);
}
/// 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(
&ExtendedSpendingKey::master(seed),
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(params(testnet).cointype),
ChildIndex::Hardened(params(is_testnet).cointype),
ChildIndex::Hardened(index)
],
);
let path = object!{
"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");
@ -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);
addr.pk_d.write(v.get_mut(11..).unwrap()).expect("Cannot write!");
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
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(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)
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;
let td = json::parse(&testdata.replace("'", "\"")).unwrap();
@ -348,7 +464,7 @@ mod tests {
let seed = hex::decode(i["seed"].as_str().unwrap()).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();
assert_eq!(j[num as usize]["address"], i["addr"]);

20
lib/src/pdf.rs

@ -1,5 +1,7 @@
extern crate printpdf;
use crate::paper::params;
use qrcode::QrCode;
use qrcode::types::Color;
@ -9,10 +11,11 @@ use std::f64;
use std::fs::File;
use printpdf::*;
/**
* 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 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");
}
let address = kv["address"].as_str().unwrap();
let pk = kv["private_key"].as_str().unwrap();
let address = kv["address"].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" {
(kv["seed"]["HDSeed"].as_str().unwrap(), kv["seed"]["path"].as_str().unwrap(), false)
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())
} else {
("", "", true)
("", "")
};
// Add address + private key
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);
@ -202,7 +206,7 @@ fn add_pk_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, fon
}
// 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);
}
}

8
ui/qtlib/src/lib.rs

@ -8,19 +8,19 @@ use zecpaperlib::{pdf, paper};
* after using it to free it properly
*/
#[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 {
assert!(!entropy.is_null());
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();
}
#[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 {
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)
};
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,
Err(e) => {
eprintln!("{}", e);

6
ui/qtlib/src/zecpaperrust.h

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

2
ui/src/mainwindow.cpp

@ -105,7 +105,7 @@ void MainWindow::SaveAsPDFButton() {
if (!filename.endsWith(".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) {
QMessageBox::information(this, tr("Saved!"), tr("The wallets were saved to ") + filename);
} else {

Loading…
Cancel
Save