diff --git a/mmgen-walletconv b/mmgen-walletconv new file mode 100755 index 00000000..b18b4adf --- /dev/null +++ b/mmgen-walletconv @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2015 Philemon +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +mmgen-walletconv: Convert an MMGen deterministic wallet from one format + to another +""" + +from mmgen.main import launch +launch("walletconv") diff --git a/mmgen/addr.py b/mmgen/addr.py index 086be6ad..78ae1acd 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -224,6 +224,13 @@ class AddrInfoList(MMGenObject): if sid in self.data: return self.data[sid] + def mmaddr2btcaddr(self,mmaddr): + btcaddr = "" + sid,idx = mmaddr.split(":") + if sid in self.seed_ids(): + btcaddr = self.addrinfo(sid).btcaddr(int(idx)) + return btcaddr + def add_wallet_data(self,c): vmsg_r("Getting account data from wallet...") data,accts,i = {},c.listaccounts(minconf=0,includeWatchonly=True),0 @@ -291,10 +298,12 @@ class AddrInfo(MMGenObject): self.seed_id = seed_id self.addrdata = addrdata self.num_addrs = len(addrdata) - if self.source in ("wallet","txsign") or \ - (self.source == "addrgen" and opt.gen_what == "k"): + if self.source in ("wallet","txsign"): self.checksum = None self.idxs_fmt = None + elif self.source == "addrgen" and opt.gen_what == "k": + self.checksum = None + self.fmt_addr_idxs() else: # self.source in addrfile, addrgen self.make_addrdata_chksum() self.fmt_addr_idxs() @@ -383,10 +392,11 @@ class AddrInfo(MMGenObject): out = [] from mmgen.addr import addrmsgs out.append(addrmsgs['addrfile_header'] + "\n") - w = "Key-address" if status[1] else "Address" - out.append("# {} data checksum for {}[{}]: {}".format( - w, self.seed_id, self.idxs_fmt, self.checksum)) - out.append("# Record this value to a secure location\n") + if self.checksum: + w = "Key-address" if status[1] else "Address" + out.append("# {} data checksum for {}[{}]: {}".format( + w, self.seed_id, self.idxs_fmt, self.checksum)) + out.append("# Record this value to a secure location\n") out.append("%s {" % self.seed_id) # Body @@ -409,7 +419,7 @@ class AddrInfo(MMGenObject): out.append("}") - return "\n".join([l.rstrip() for l in out]) + return "\n".join([l.rstrip() for l in out]) + "\n" def fmt_addr_idxs(self): diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 4f9e3c2d..4fdedbf0 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -17,7 +17,7 @@ # along with this program. If not, see . """ -crypto.py: Cryptographic and related routines for the 'mmgen-tool' utility +crypto.py: Cryptographic and related routines for the MMGen suite """ import sys @@ -58,7 +58,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. } def encrypt_seed(seed, key): - return encrypt_data(seed, key, iv=1, what="seed") + return encrypt_data(seed, key, iv=1, desc="seed") def decrypt_seed(enc_seed, key, seed_id, key_id): @@ -66,21 +66,21 @@ def decrypt_seed(enc_seed, key, seed_id, key_id): vmsg_r("Checking key...") chk1 = make_chksum_8(key) if key_id: - if not compare_chksums(key_id,"key id",chk1,"computed",die=False): + if not compare_chksums(key_id,"key ID",chk1,"computed"): msg("Incorrect passphrase") return False - dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed") + dec_seed = decrypt_data(enc_seed, key, iv=1, desc="seed") chk2 = make_chksum_8(dec_seed) if seed_id: - if compare_chksums(seed_id,"seed id",chk2,"decrypted seed",die=False): + if compare_chksums(seed_id,"seed ID",chk2,"decrypted seed"): qmsg("Passphrase is OK") else: if not opt.debug: msg_r("Checking key ID...") - if compare_chksums(key_id,"key id",chk1,"computed",die=False): + if compare_chksums(key_id,"key ID",chk1,"computed"): msg("Key ID is correct but decryption of seed failed") else: msg("Incorrect passphrase") @@ -90,29 +90,25 @@ def decrypt_seed(enc_seed, key, seed_id, key_id): # else: # qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1)) - if opt.debug: Msg("Decrypted seed: %s" % hexlify(dec_seed)) + dmsg("Decrypted seed: %s" % hexlify(dec_seed)) - vmsg("OK") return dec_seed -def encrypt_data(data, key, iv=1, what="data", verify=True): - """ - Encrypt arbitrary data using AES256 in counter mode - """ +def encrypt_data(data, key, iv=1, desc="data", verify=True): # 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR from Crypto.Cipher import AES from Crypto.Util import Counter - vmsg("Encrypting %s" % what) + vmsg("Encrypting %s" % desc) c = AES.new(key, AES.MODE_CTR, counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv)) enc_data = c.encrypt(data) if verify: - vmsg_r("Performing a test decryption of the %s..." % what) + vmsg_r("Performing a test decryption of the %s..." % desc) c = AES.new(key, AES.MODE_CTR, counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv)) @@ -120,15 +116,15 @@ def encrypt_data(data, key, iv=1, what="data", verify=True): if dec_data == data: vmsg("done") else: - msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what)) + msg("ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc)) sys.exit(2) return enc_data -def decrypt_data(enc_data, key, iv=1, what="data"): +def decrypt_data(enc_data, key, iv=1, desc="data"): - vmsg_r("Decrypting %s with key..." % what) + vmsg_r("Decrypting %s with key..." % desc) from Crypto.Cipher import AES from Crypto.Util import Counter @@ -151,18 +147,18 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32): def make_key(passwd,salt,hash_preset, - what="encryption key",from_what="passphrase",verbose=False): + desc="encryption key",from_what="passphrase",verbose=False): - if from_what: what += " from " + if from_what: desc += " from " if opt.verbose or verbose: - msg_r("Generating %s%s..." % (what,from_what)) + msg_r("Generating %s%s..." % (desc,from_what)) key = scrypt_hash_passphrase(passwd, salt, hash_preset) if opt.verbose or verbose: msg("done") - if opt.debug: Msg("Key: %s" % hexlify(key)) + dmsg("Key: %s" % hexlify(key)) return key -def get_random_data_from_user(uchars): +def _get_random_data_from_user(uchars): if opt.quiet: msg("Enter %s random symbols" % uchars) else: msg(crmsg['usr_rand_notice'] % uchars) @@ -189,8 +185,7 @@ def get_random_data_from_user(uchars): fmt_time_data = ["{:.22f}".format(i) for i in time_data] - if opt.debug: - msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" % + dmsg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" % (key_data,"\n".join(fmt_time_data))) prompt = "User random data successfully acquired. Press ENTER to continue" @@ -202,23 +197,23 @@ def get_random_data_from_user(uchars): def get_random(length): from Crypto import Random os_rand = Random.new().read(length) - if g.use_urandchars: + if g.use_urandchars and opt.usr_randchars: from_what = "OS random data" if not g.user_entropy: g.user_entropy = \ - sha256(get_random_data_from_user(opt.usr_randchars)).digest() + sha256(_get_random_data_from_user(opt.usr_randchars)).digest() from_what += " plus user-supplied entropy" else: from_what += " plus saved user-supplied entropy" key = make_key(g.user_entropy, "", '2', from_what=from_what, verbose=True) - return encrypt_data(os_rand,key,what="random data",verify=False) + return encrypt_data(os_rand,key,desc="random data",verify=False) else: return os_rand def get_seed_from_wallet( infile, - prompt_info="{pnm} wallet".format(pnm=g.proj_name), + desc="{pnm} wallet".format(pnm=g.proj_name), silent=False ): @@ -228,7 +223,7 @@ def get_seed_from_wallet( if opt.debug: display_control_data(*wdata) padd = " "+infile if opt.quiet else "" - passwd = get_mmgen_passphrase(prompt_info+padd) + passwd = get_mmgen_passphrase(desc+padd) key = make_key(passwd, salt, hash_preset) @@ -270,17 +265,17 @@ def confirm_old_format(): def get_seed_from_incog_wallet( infile, - prompt_info="{pnm} incognito wallet".format(pnm=g.proj_name), + desc="{pnm} incognito wallet".format(pnm=g.proj_name), silent=False, hex_input=False ): - what = "incognito wallet data" + desc = "incognito wallet data" if opt.from_incog_hidden: d = get_hidden_incog_data() else: - d = get_data_from_file(infile,what) + d = get_data_from_file(infile,desc) if hex_input: try: d = unhexlify("".join(d.split()).strip()) @@ -310,10 +305,13 @@ def get_seed_from_incog_wallet( else 'incog_iv_id']) while True: - passwd = get_mmgen_passphrase(prompt_info+" "+incog_id) + passwd = get_mmgen_passphrase(desc+" "+incog_id) qmsg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets))) - hp = get_hash_preset_from_user(what="incog wallet") + if 'hash_preset' in opt.set_by_user: + hp = opt.hash_preset + else: + hp = get_hash_preset_from_user(desc="incog wallet") # IV is used BOTH to initialize counter and to salt password! key = make_key(passwd, iv, hp, "wrapper key") @@ -399,7 +397,7 @@ def _get_seed(infile,silent=False,seed_id=""): msg("Invalid %s file '%s'" % (source,infile)) sys.exit(2) - if opt.debug: Msg("Seed: %s" % hexlify(seed)) + dmsg("Seed: %s" % hexlify(seed)) return seed @@ -415,9 +413,9 @@ def get_seed_retry(infile,seed_id=""): def _get_seed_from_brain_passphrase(words,silent=False): bp = " ".join(words) - if opt.debug: Msg("Sanitized brain passphrase: %s" % bp) + dmsg("Sanitized brain passphrase: %s" % bp) seed_len,hash_preset = get_from_brain_opt_params() - if opt.debug: Msg("Brainwallet l = %s, p = %s" % (seed_len,hash_preset)) + dmsg("Brainwallet l = %s, p = %s" % (seed_len,hash_preset)) vmsg_r("Hashing brainwallet data. Please wait...") # Use buflen arg of scrypt.hash() to get seed of desired length seed = scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8) @@ -432,31 +430,31 @@ def _get_seed_from_brain_passphrase(words,silent=False): # Vars for mmgen_*crypt functions only salt_len,sha256_len,nonce_len = 32,32,32 -def mmgen_encrypt(data,what="data",hash_preset=''): +def mmgen_encrypt(data,desc="data",hash_preset=''): salt,iv,nonce = get_random(salt_len),\ get_random(g.aesctr_iv_len), \ get_random(nonce_len) - hp = hash_preset or get_hash_preset_from_user('3',what) + hp = hash_preset or get_hash_preset_from_user('3',desc) m = "default" if hp == '3' else "user-requested" - vmsg("Encrypting %s" % what) + vmsg("Encrypting %s" % desc) qmsg("Using %s hash preset of '%s'" % (m,hp)) - passwd = get_new_passphrase(what, {}) + passwd = get_new_passphrase(desc, {}) key = make_key(passwd, salt, hp) enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key, - int(hexlify(iv),16), what=what) + int(hexlify(iv),16), desc=desc) return salt+iv+enc_d -def mmgen_decrypt(data,what="data",hash_preset=""): +def mmgen_decrypt(data,desc="data",hash_preset=""): dstart = salt_len + g.aesctr_iv_len salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:] - vmsg("Preparing to decrypt %s" % what) - hp = hash_preset or get_hash_preset_from_user('3',what) + vmsg("Preparing to decrypt %s" % desc) + hp = hash_preset or get_hash_preset_from_user('3',desc) m = "default" if hp == '3' else "user-requested" qmsg("Using %s hash preset of '%s'" % (m,hp)) - passwd = get_mmgen_passphrase(what) + passwd = get_mmgen_passphrase(desc) key = make_key(passwd, salt, hp) - dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what) + dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), desc) if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): vmsg("OK") return dec_d[sha256_len+nonce_len:] @@ -464,8 +462,8 @@ def mmgen_decrypt(data,what="data",hash_preset=""): msg("Incorrect passphrase or hash preset") return False -def mmgen_decrypt_retry(d,what="data"): +def mmgen_decrypt_retry(d,desc="data"): while True: - d_dec = mmgen_decrypt(d,what) + d_dec = mmgen_decrypt(d,desc) if d_dec: return d_dec msg("Trying again...") diff --git a/mmgen/filename.py b/mmgen/filename.py index 336e5e13..f5a74dcc 100755 --- a/mmgen/filename.py +++ b/mmgen/filename.py @@ -20,66 +20,25 @@ filename.py: Filename class and methods for the MMGen suite """ import sys,os -import mmgen.opt as opt from mmgen.obj import * -import mmgen.globalvars as g -from mmgen.util import msg,fmt_code_to_sstype +from mmgen.util import die,get_extension,check_infile class Filename(MMGenObject): - exts = { - 'seed': { - "mmdat": "Wallet", - "mmseed": "SeedFile", - "mmwords": "Mnemonic", - "mmbrain": "Brainwallet", - "mmincog": "IncogWallet", - "mmincox": "IncogWalletHex", - }, - 'tx': { - "raw": "RawTX", - "sig": "SigTX", - }, - 'addr': { - "addrs": "AddrInfo", - "keys": "KeyInfo", - "akeys": "KeyAddrInfo", - "akeys.mmenc": "KeyAddrInfoEnc", - }, - 'other': { - "chk": "AddrInfoChecksum", - "mmenc": "MMEncInfo", - }, - } - - ftypes = { - 'seed': { - "hincog": "IncogWalletHidden", - }, - } - - def __init__(self,fn,ftype=""): + def __init__(self,fn,ftype=None): self.name = fn self.dirname = os.path.dirname(fn) self.basename = os.path.basename(fn) self.ext = None + self.ftype = ftype - def mf1(k): return k == ftype - def mf2(k): return '.'+k == fn[-len('.'+k):] +# This should be done before license msg instead +# check_infile(fn) - # find file info for ftype or extension - e,attr,have_match = (self.ftypes,"ftype",mf1) if ftype else \ - (self.exts,"ext",mf2) - - for k in e: - for j in e[k]: - if have_match(j): - setattr(self,attr,j) - self.fclass = k - self.sstype = e[k][j] - - if not hasattr(self,attr): - die(2,"Unrecognized %s for file '%s'" % (attr,fn)) + if not ftype: + self.ext = get_extension(fn) + if not (self.ext): + die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn)) # TODO: Check for Windows import stat diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index a72bacbd..bbe5f5fa 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -53,7 +53,8 @@ version = '0.8.0' required_opts = [ "quiet","verbose","debug","outdir","echo_passphrase","passwd_file", - "usr_randchars","stdout","show_hash_presets" + "usr_randchars","stdout","show_hash_presets","label", + "keep_passphrase","keep_hash_preset" ] min_screen_width = 80 @@ -119,16 +120,3 @@ addr_label_symbols = wallet_label_symbols = printable_nonl max_addr_label_len = 32 max_wallet_label_len = 48 max_tx_comment_len = 72 # Comment is b58 encoded, so can permit all UTF-8 - -wallet_fmt_codes = ( - ( "Wallet", "wallet", "w" ), - ( "SeedFile", "mmseed","seed", "s" ), - ( "Mnemonic", "mmwords","words","mnemonic","mnem","m" ), - ( "Brainwallet", "mmbrain","brainwallet","brain","bw","b" ), - ( "IncogWallet", "mmincog","incog","i" ), - ( "IncogWalletHex", "mmincox","incog_hex","ix" ), - ( "IncogWalletHidden", "incog_hidden","ih" ), -) -#addr_label_punc = ".","_",",","-"," ","(",")" -#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc -#wallet_label_punc = addr_label_punc diff --git a/mmgen/license.py b/mmgen/license.py index 92713e20..c0070047 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -19,18 +19,16 @@ """ license.py: Text of GPLv3 """ + import mmgen.globalvars as g -gpl = { - 'warning': """ +warning = """ {pnm} Copyright (C) {g.Cdates} by {g.author} {g.email}. This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. -""".format(g=g,pnm=g.proj_name), - 'prompt': """ -Press 'w' for conditions and warranty info, or 'c' to continue: -""", - 'conditions': """ +""".format(g=g,pnm=g.proj_name) + +conditions = """ TERMS AND CONDITIONS 0. Definitions. @@ -581,4 +579,3 @@ an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. """ -} diff --git a/mmgen/main.py b/mmgen/main.py index a9d0bee3..0313fc5e 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -31,6 +31,7 @@ def launch(what): def launch_txsend(): import mmgen.main_txsend def launch_txsign(): import mmgen.main_txsign def launch_walletchk(): import mmgen.main_walletchk + def launch_walletconv(): import mmgen.main_walletconv def launch_walletgen(): import mmgen.main_walletgen try: import termios diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index b941a7f6..fc02b11b 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -161,8 +161,8 @@ if opt.stdout or not sys.stdout.isatty(): if enc_ext and sys.stdout.isatty(): msg("Cannot write encrypted data to screen. Exiting") sys.exit(2) - write_to_stdout(addrdata_str,what, - (what=="keys"and not opt.quiet and sys.stdout.isatty())) + write_to_stdout(addrdata_str,what,ask_terminal=(what=="keys" + and not opt.quiet and sys.stdout.isatty())) else: outfile = "%s.%s%s" % (outfile_base, ( g.keyaddrfile_ext if "ka" in opt.gen_what else ( diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 69a08e57..739dc247 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -64,7 +64,7 @@ if len(cmd_args) == 1: ai = AddrInfo(infile,has_keys=opt.keyaddr_file) else: msg(""" -"You must specify an mmgen address file (or a list of non-{pnm} addresses +You must specify an {pnm} address file (or a list of non-{pnm} addresses with the '--addrlist' option) """.strip().format(pnm=g.proj_name)) sys.exit(1) diff --git a/mmgen/main_passchg.py b/mmgen/main_passchg.py index b9cb6ac9..eb69fd0a 100755 --- a/mmgen/main_passchg.py +++ b/mmgen/main_passchg.py @@ -67,8 +67,8 @@ seed_id,key_id = metadata[:2] # Repeat on incorrect pw entry while True: - p = "{pnm} wallet".format(pnm=g.proj_name) - passwd = get_mmgen_passphrase(p,not opt.keep_old_passphrase) + desc = "{pnm} wallet".format(pnm=g.proj_name) + passwd = get_mmgen_passphrase(desc,not opt.keep_old_passphrase) key = make_key(passwd, salt, hash_preset) seed = decrypt_seed(enc_seed, key, seed_id, key_id) if seed: break diff --git a/mmgen/main_pywallet.py b/mmgen/main_pywallet.py index 67288c60..5f8b0480 100755 --- a/mmgen/main_pywallet.py +++ b/mmgen/main_pywallet.py @@ -180,8 +180,8 @@ class AES(object): def rotate(self, word): """ Rijndael's key schedule rotate operation. - Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d - Word is an char list of size 4 (32 bits overall). + Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d + Word is an char list of size 4 (32 bits overall). """ return word[1:] + word[:1] @@ -230,10 +230,10 @@ class AES(object): def expandKey(self, key, size, expandedKeySize): """Rijndael's key expansion. - Expands an 128,192,256 key into an 176,208,240 bytes key + Expands an 128,192,256 key into an 176,208,240 bytes key - expandedKey is a char list of large enough size, - key is the non-expanded key. + expandedKey is a char list of large enough size, + key is the non-expanded key. """ # current expanded keySize, in bytes currentSize = 0 @@ -276,8 +276,8 @@ class AES(object): def createRoundKey(self, expandedKey, roundKeyPointer): """Create a round key. - Creates a round key from the given expanded key and the - position within the expanded key. + Creates a round key from the given expanded key and the + position within the expanded key. """ roundKey = [0] * 16 for i in range(4): @@ -1183,8 +1183,8 @@ def parse_BlockLocator(vds): return d def deserialize_BlockLocator(d): - result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') - return result + result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') + return result def parse_setting(setting, vds): if setting[0] == "f": # flag (boolean) settings @@ -1404,11 +1404,11 @@ def parse_wallet(db, item_callback): def update_wallet(db, type, data): """Write a single item to the wallet. - db must be open with writable=True. - type and data are the type code and data dictionary as parse_wallet would - give to item_callback. - data's __key__, __value__ and __type__ are ignored; only the primary data - fields are used. + db must be open with writable=True. + type and data are the type code and data dictionary as parse_wallet would + give to item_callback. + data's __key__, __value__ and __type__ are ignored; only the primary data + fields are used. """ d = data kds = BCDataStream() @@ -1656,8 +1656,8 @@ elif opt.keysforaddrs: msg("Warning: not all requested keys found") len_arg = "%s" % len(wallet_addrs) \ - if len(data) == len(wallet_addrs) or ext == "json" \ - else "%s:%s" % (len(data),len(wallet_addrs)) + if len(data) == len(wallet_addrs) or ext == "json" \ + else "%s:%s" % (len(data),len(wallet_addrs)) from mmgen.util import make_chksum_8,write_to_file,write_to_stdout wallet_id = make_chksum_8(str(sorted(wallet_addrs))) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 9d096476..09764bf1 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -26,6 +26,7 @@ from decimal import Decimal import mmgen.globalvars as g import mmgen.opt as opt +from mmgen.util import dmsg from mmgen.tx import * pnm = g.proj_name @@ -196,9 +197,9 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen i.addr = "%s%s %s" % ( i.address[:btaddr_w-len(dots)], dots, ( - ("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "") - + i.comment)[:acct_w] - ) + ("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "") + + i.comment)[:acct_w] + ) else: i.addr = i.address @@ -294,16 +295,13 @@ def mmaddr2btcaddr_unspent(unspent,mmaddr): def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f): - # assume mmaddr has already been checked - sid,idx = mmaddr.split(":") - btcaddr = "" - if sid in ail_w.seed_ids(): - btcaddr = ail_w.addrinfo(sid).btcaddr(int(idx)) + # assume mmaddr has already been checked + btcaddr = ail_w.mmaddr2btcaddr(mmaddr) if not btcaddr: - if ail_f and sid in ail_f.seed_ids(): - btcaddr = ail_f.addrinfo(sid).btcaddr(int(idx)) + if ail_f: + btcaddr = ail_f.mmaddr2btcaddr(mmaddr) if btcaddr: msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr)) if not keypress_confirm("Continue anyway?"): @@ -449,9 +447,7 @@ if change > 0: tx_out[change_addr] = float(change) tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent] -if opt.debug: - Msg("tx_in: " + repr(tx_in)) - Msg("tx_out: " + repr(tx_out)) +dmsg("tx_in: %s\ntx_out: %s" % (repr(tx_in),repr(tx_out))) if opt.comment_file: if keypress_confirm("Edit comment?",False): diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 4249a473..1bf53dbe 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -68,12 +68,12 @@ if keypress_confirm("Edit transaction comment?"): write_to_file(outfile,data,w,False,True,True) warn = "Once this transaction is sent, there's no taking it back!" -what = "broadcast this transaction to the network" +action = "broadcast this transaction to the network" expect = "YES, I REALLY WANT TO DO THIS" if opt.quiet: warn,expect = "","YES" -confirm_or_exit(warn, what, expect) +confirm_or_exit(warn, action, expect) msg("Sending transaction") diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 1cfdb2ee..2d25635c 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -25,7 +25,7 @@ import sys import mmgen.globalvars as g import mmgen.opt as opt from mmgen.tx import * -from mmgen.util import do_license_msg +from mmgen.util import do_license_msg,dmsg pnm = g.proj_name pnl = pnm.lower() @@ -150,7 +150,7 @@ def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None): if keys: qmsg("Passing %s key%s to bitcoind" % (len(keys),suf(keys,"k"))) - if opt.debug: Msg("Keys:\n %s" % "\n ".join(keys)) + dmsg("Keys:\n %s" % "\n ".join(keys)) msg_r("Signing transaction{}...".format(tx_num_str)) from mmgen.rpc import exceptions @@ -192,11 +192,11 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys): return sig_tx -def check_maps_from_seeds(maplist,what,infiles,saved_seeds,return_keys=False): +def check_maps_from_seeds(maplist,desc,infiles,saved_seeds,return_keys=False): if not maplist: return [] qmsg("Checking {pnm} -> BTC address mappings for {w}s (from seed(s))".format( - pnm=pnm,w=what)) + pnm=pnm,w=desc)) d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds) # 0=mmaddr 1=addr 2=wif m = dict([(e[0],e[1]) for e in d]) @@ -253,9 +253,9 @@ def parse_keylist(from_file): # Check inputs and outputs maps against key-address file, deleting entries: -def check_maps_from_kafile(imap,what,kadata,return_keys=False): +def check_maps_from_kafile(imap,desc,kadata,return_keys=False): if not kadata: return [] - qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=what)) + qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=desc)) ret = [] for k in imap.keys(): if k in kadata.keys(): @@ -266,9 +266,9 @@ def check_maps_from_kafile(imap,what,kadata,return_keys=False): kl,il = "key-address file:","tx file:" msg(wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k])) sys.exit(2) - if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),what)) + if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),desc)) if return_keys: - vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),what)) + vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),desc)) return ret @@ -288,7 +288,7 @@ infiles = opt.opts.init(opts_data,add_opts=["b16"]) for l in ( ('tx_id', 'info'), ('tx_id', 'terse_info'), -): opt.opts.warn_incompatible_opts(l) +): opt.opts.die_on_incompatible_opts(l) if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True diff --git a/mmgen/main_walletchk.py b/mmgen/main_walletchk.py index c9632733..8800f025 100755 --- a/mmgen/main_walletchk.py +++ b/mmgen/main_walletchk.py @@ -95,8 +95,7 @@ def export_to_hidden_incog(incog_enc): outfile,offset = opt.export_incog_hidden.split(",") #Already sanity-checked if opt.outdir: outfile = make_full_path(opt.outdir,outfile) - if opt.debug: - Msg("Incog data len %s, offset %s" % (len(incog_enc),offset)) + dmsg("Incog data len %s, offset %s" % (len(incog_enc),offset)) check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write") if not opt.quiet: confirm_or_exit("","alter file '%s'" % outfile) @@ -162,7 +161,7 @@ if opt.export_mnemonic: elif opt.export_seed: from mmgen.bitcoin import b58encode_pad - data = split_into_columns(4,b58encode_pad(seed)) + data = split_into_cols(4,b58encode_pad(seed)) chk = make_chksum_6(b58encode_pad(seed)) fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext) write_to_file_or_stdout(fn, "%s %s\n" % (chk,data), "seed data") diff --git a/mmgen/main_walletconv.py b/mmgen/main_walletconv.py new file mode 100755 index 00000000..72222ef2 --- /dev/null +++ b/mmgen/main_walletconv.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2015 Philemon +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +mmgen-walletconv: Convert an MMGen deterministic wallet from one format + to another +""" + +import sys +import mmgen.globalvars as g +import mmgen.opt as opt +from mmgen.util import die,msg,green,do_license_msg,check_infile +from mmgen.seed import SeedSource + +opts_data = { + 'sets_disabled': ( + ('hidden_incog_input_params', bool, 'in_fmt', 'hi'), + ('hidden_incog_output_params', bool, 'out_fmt', 'hi') + ), + 'desc': "Convert an {pnm} wallet from one format to another".format( + pnm=g.proj_name), + 'usage':"[opts] [infile]", + 'options': """ +-h, --help Print this help message. +-d, --outdir= d Output files to directory 'd' instead of working dir. +-i, --in-fmt= f Convert from wallet format 'f' (see FMT CODES below). +-o, --out-fmt= f Convert to wallet format 'f' (see FMT CODES below). +-H, --hidden-incog-input-params=f,o Use filename 'f' and offset 'o' (comma + separated) for hidden incognito input. +-J, --hidden-incog-output-params=f,o Same above, but for output. If file + 'f' doesn't exist, it will be created and filled with + random data. +-O, --old-incog-fmt Specify old-format incognito input. +-k, --keep-passphrase Reuse input wallet passphrase for output wallet. +-K, --keep-hash-preset Reuse input wallet hash preset for output wallet. +-l, --seed-len= l Specify wallet seed length of 'l' bits. This option + is required only for brainwallet and incognito inputs + with non-standard (< {g.seed_len}-bit) seed lengths. +-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p' + for password hashing (default: '{g.hash_preset}'). +-q, --quiet Produce quieter output; suppress some warnings. +-r, --usr-randchars=n Get 'n' characters of additional randomness from user + (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}). +-S, --stdout Write wallet data to stdout instead of file. +-v, --verbose Produce more verbose output. + +FMT CODES: + {f} +""".format(g=g,f="\n ".join(SeedSource.format_fmt_codes().split("\n"))) +} + +cmd_args = opt.opts.init(opts_data) + +if len(cmd_args) == 0 \ + and not opt.hidden_incog_input_params \ + and not opt.in_fmt: + die(1,"An input file or input format must be specified") + +if len(cmd_args) > 1 or (len(cmd_args) == 1 and opt.hidden_incog_input_params): + die(1,"Only one input file may be specified") + +if len(cmd_args) == 1: + check_infile(cmd_args[0]) + +do_license_msg() + +msg(green("Processing input wallet")) + +ss_in = SeedSource(*cmd_args) + +msg(green("Processing output wallet")) + +ss_out = SeedSource(ss=ss_in) +ss_out.write_to_file() diff --git a/mmgen/obj.py b/mmgen/obj.py index 4b670cc3..8263f738 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -20,7 +20,7 @@ obj.py: The MMGenObject class and methods """ import mmgen.globalvars as g -from mmgen.util import msgrepr_exit,msgrepr +from mmgen.util import mdie,mmsg lvl = 0 @@ -72,18 +72,18 @@ class MMGenObject(object): col_w = max(len(k) for k in keys) fs = "{}%-{}s: %s".format(indent,col_w) - methods = [k for k in keys if repr(getattr(self,k))[:14] == ' 1: - msg("Mutually exclusive options: %s" % " ".join( - ["--"+b.replace("_","-") for b in bad])) - sys.exit(1) + die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad])) def _typeconvert_from_dfl(key): @@ -51,7 +49,6 @@ def _typeconvert_from_dfl(key): gval = g.__dict__[key] uval = opt.__dict__[key] gtype = type(gval) - utype = type(uval) try: opt.__dict__[key] = gtype(opt.__dict__[key]) @@ -62,16 +59,18 @@ def _typeconvert_from_dfl(key): 'float': 'a float', 'bool': 'a boolean value', } - m = [d[k] for k in d if __builtins__[k] == gtype] - fs = "'%s': invalid parameter for '--%s' option (not %s)" - msg(fs % (opt.__dict__[key],opt.replace("_","-"),m)) - sys.exit(1) + die(1, "'%s': invalid parameter for '--%s' option (not %s)" % ( + opt.__dict__[key], + key.replace("_","-"), + d[gtype.__name__] + )) if g.debug: Msg("Opt overriden by user:\n %-18s: %s" % ( key, ("%s -> %s" % (gval,uval)) )) +def fmt_opt(o): return "--" + o.replace("_","-") def _show_hash_presets(): fs = " {:<7} {:<6} {:<3} {}" @@ -116,21 +115,25 @@ def init(opts_data,add_opts=[]): for o in [s.rstrip("=") for s in long_opts] + g.required_opts + add_opts: opt.__dict__[o] = uopts[o] if o in uopts else None - # check user-set opts without modifying them - if not check_opts(uopts): sys.exit(1) - # A special case - do this here, before opt gets set from g.dfl_vars if opt.usr_randchars: g.use_urandchars = True # If user opt is set, convert its type based on value in mmgen.globalvars # If unset, set it to default value in mmgen.globalvars (g): + opt.__dict__['set_by_user'] = [] for k in g.dfl_vars: if k in opt.__dict__ and opt.__dict__[k] != None: _typeconvert_from_dfl(k) - else: opt.__dict__[k] = g.__dict__[k] + opt.set_by_user.append(k) + else: + opt.__dict__[k] = g.__dict__[k] + + # Check user-set opts without modifying them + if not check_opts(uopts): sys.exit(1) if opt.show_hash_presets: _show_hash_presets(); sys.exit(0) + if opt.debug: opt.verbose = True if g.debug: @@ -138,7 +141,7 @@ def init(opts_data,add_opts=[]): for k in opt.__dict__: v = opt.__dict__[k] if v != None and k != "opts": - Msg(" %-18s: %-6s [%s]" % (k,v,fmt_type(v))) + Msg(" %-18s: %-6s [%s]" % (k,v,type(v).__name__)) Msg("### END OPTS.PY ###\n") for l in ( @@ -146,7 +149,7 @@ def init(opts_data,add_opts=[]): ('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic', 'export_seed'), ('quiet','verbose') - ): warn_incompatible_opts(l) + ): die_on_incompatible_opts(l) return args @@ -160,50 +163,61 @@ def show_all_opts(): def check_opts(usr_opts): # Returns false if any check fails - def opt_splits(val,sep,n,what): + def opt_splits(val,sep,n,desc): sepword = "comma" if sep == "," else ( "colon" if sep == ":" else ("'"+sep+"'")) try: l = val.split(sep) except: - msg("'%s': invalid %s (not %s-separated list)" % (val,what,sepword)) + msg("'%s': invalid %s (not %s-separated list)" % (val,desc,sepword)) return False if len(l) == n: return True else: msg("'%s': invalid %s (%s %s-separated items required)" % - (val,what,n,sepword)) + (val,desc,n,sepword)) return False - def opt_compares(val,op,target,what): + def opt_compares(val,op,target,desc): if not eval("%s %s %s" % (val, op, target)): - msg("%s: invalid %s (not %s %s)" % (val,what,op,target)) + msg("%s: invalid %s (not %s %s)" % (val,desc,op,target)) return False return True - def opt_is_int(val,what): + def opt_is_int(val,desc): try: int(val) except: - msg("'%s': invalid %s (not an integer)" % (val,what)) + msg("'%s': invalid %s (not an integer)" % (val,desc)) return False return True - def opt_is_in_list(val,lst,what): + def opt_is_in_list(val,lst,desc): if val not in lst: q,sep = ("'","','") if type(lst[0]) == str else ("",",") - msg("{q}{}{q}: invalid {}\nValid options: {q}{}{q}".format( - val,what,sep.join([str(i) for i in sorted(lst)]),q=q)) + msg("{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}".format( + v=val,w=desc,q=q, + o=sep.join([str(i) for i in sorted(lst)]) + )) return False return True + def opt_unrecognized(key,val,desc): + msg("'%s': unrecognized %s for option '%s'" + % (val,desc,fmt_opt(key))) + return False + + def opt_display(key,val='',beg="For selected",end=":\n"): + s = "%s=%s" % (fmt_opt(key),val) if val else fmt_opt(key) + msg_r("%s option '%s'%s" % (beg,s,end)) + global opt for key,val in [(k,getattr(opt,k)) for k in usr_opts]: - what = "parameter for '--%s' option" % key.replace("_","-") + desc = "parameter for '%s' option" % fmt_opt(key) # Check for file existence and readability + from mmgen.util import check_infile if key in ('keys_from_file','mmgen_keys_from_file', 'passwd_file','keysforaddrs','comment_file'): - from mmgen.util import check_infile check_infile(val) # exits on error continue @@ -220,40 +234,83 @@ def check_opts(usr_opts): # Returns false if any check fails w = "character in label" for ch in list(val): if not opt_is_in_list(ch,g.wallet_label_symbols,w): return False + # NEW + elif key in ('in_fmt','out_fmt'): + from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden + sstype = SeedSource.fmt_code_to_sstype(val) + if not sstype: + return opt_unrecognized(key,val,"format code") + if key == 'out_fmt': + p = 'hidden_incog_output_params' + if sstype == IncogWalletHidden and not getattr(opt,p): + die(1,"Hidden incog format output requested. You must supply" + + " a file and offset with the '%s' option" % fmt_opt(p)) + if issubclass(sstype,IncogWallet) and opt.old_incog_fmt: + opt_display(key,val,beg="Selected",end=" ") + opt_display('old_incog_fmt',beg="conflicts with",end=":\n") + die(1,"Export to old incog wallet format unsupported") + elif issubclass(sstype,Brainwallet): + die(1,"Output to brainwallet format unsupported") + elif key in ('hidden_incog_input_params','hidden_incog_output_params'): + if key == 'hidden_incog_input_params': + check_infile(val.split(",")[0]) + key2 = 'in_fmt' + else: + key2 = 'out_fmt' + if hasattr(opt,key2): + val2 = getattr(opt,key2) + from mmgen.seed import IncogWalletHidden + if val2 and val2 not in IncogWalletHidden.fmt_codes: + die(1, + "Option conflict:\n %s, with\n %s=%s" % ( + fmt_opt(key),fmt_opt(key2),val2 + )) + + # begin OLD, deprecated + elif key == 'hidden_incog_params': + from mmgen.util import check_outfile + if not opt_splits(val,",",2,desc): return False + outfile,offset = val.split(",") + check_outfile(outfile) + w = "offset " + desc + if not opt_is_int(offset,w): return False + if not opt_compares(offset,">=",0,desc): return False elif key == 'export_incog_hidden' or key == 'from_incog_hidden': if key == 'from_incog_hidden': - if not opt_splits(val,",",3,what): return False + if not opt_splits(val,",",3,desc): return False infile,offset,seed_len = val.split(",") from mmgen.util import check_infile check_infile(infile) - w = "seed length " + what + w = "seed length " + desc if not opt_is_int(seed_len,w): return False if not opt_is_in_list(int(seed_len),g.seed_lens,w): return False else: from mmgen.util import check_outfile - if not opt_splits(val,",",2,what): return False + if not opt_splits(val,",",2,desc): return False outfile,offset = val.split(",") check_outfile(outfile) - w = "offset " + what + w = "offset " + desc if not opt_is_int(offset,w): return False - if not opt_compares(offset,">=",0,what): return False + if not opt_compares(offset,">=",0,desc): return False elif key == 'from_brain': - if not opt_splits(val,",",2,what): return False + if not opt_splits(val,",",2,desc): return False l,p = val.split(",") - w = "seed length " + what + w = "seed length " + desc if not opt_is_int(l,w): return False if not opt_is_in_list(int(l),g.seed_lens,w): return False - w = "hash preset " + what + w = "hash preset " + desc if not opt_is_in_list(p,g.hash_presets.keys(),w): return False + # end OLD elif key == 'seed_len': - if not opt_is_int(val,what): return False - if not opt_is_in_list(int(val),g.seed_lens,what): return False + if not opt_is_int(val,desc): return False + if not opt_is_in_list(int(val),g.seed_lens,desc): return False elif key == 'hash_preset': - if not opt_is_in_list(val,g.hash_presets.keys(),what): return False + if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False elif key == 'usr_randchars': - if not opt_is_int(val,what): return False - if not opt_compares(val,">=",g.min_urandchars,what): return False - if not opt_compares(val,"<=",g.max_urandchars,what): return False + if val == 0: continue + if not opt_is_int(val,desc): return False + if not opt_compares(val,">=",g.min_urandchars,desc): return False + if not opt_compares(val,"<=",g.max_urandchars,desc): return False else: if g.debug: Msg("check_opts(): No test for opt '%s'" % key) diff --git a/mmgen/rpc/__init__.py b/mmgen/rpc/__init__.py index 3dc3a5c9..e1dc4dd2 100755 --- a/mmgen/rpc/__init__.py +++ b/mmgen/rpc/__init__.py @@ -23,7 +23,7 @@ bitcoin-python - Easy-to-use Bitcoin API client def connect_to_local(filename=None): - """ + """ Connect to default bitcoin instance owned by this user, on this machine. Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. @@ -31,24 +31,23 @@ def connect_to_local(filename=None): Arguments: - `filename`: Path to a configuration file in a non-standard location (optional) - """ - from mmgen.rpc.connection import BitcoinConnection - from mmgen.rpc.config import read_default_config + """ + from mmgen.rpc.connection import BitcoinConnection + from mmgen.rpc.config import read_default_config - cfg = read_default_config(filename) - port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332')) - rcpuser = cfg.get('rpcuser', '') + cfg = read_default_config(filename) + port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332')) + rcpuser = cfg.get('rpcuser', '') - return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port) + return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port) -def connect_to_remote(user, password, host='localhost', port=8332, - use_https=False): - """ +def connect_to_remote(user,password,host='localhost',port=8332,use_https=False): + """ Connect to remote or alternative local bitcoin client instance. Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. - """ - from mmgen.rpc.connection import BitcoinConnection + """ + from mmgen.rpc.connection import BitcoinConnection - return BitcoinConnection(user, password, host, port, use_https) + return BitcoinConnection(user, password, host, port, use_https) diff --git a/mmgen/rpc/config.py b/mmgen/rpc/config.py index 6110c153..86f75c20 100755 --- a/mmgen/rpc/config.py +++ b/mmgen/rpc/config.py @@ -23,53 +23,53 @@ Utilities for reading bitcoin configuration files. def read_config_file(filename): - """ + """ Read a simple ``'='``-delimited config file. Raises :const:`IOError` if unable to open file, or :const:`ValueError` if an parse error occurs. - """ - f = open(filename) - try: - cfg = {} - for line in f: - line = line.strip() - if line and not line.startswith("#"): - try: - (key, value) = line.split('=', 1) - cfg[key] = value - except ValueError: - pass # Happens when line has no '=', ignore - finally: - f.close() - return cfg + """ + f = open(filename) + try: + cfg = {} + for line in f: + line = line.strip() + if line and not line.startswith("#"): + try: + (key, value) = line.split('=', 1) + cfg[key] = value + except ValueError: + pass # Happens when line has no '=', ignore + finally: + f.close() + return cfg def read_default_config(filename=None): - """ + """ Read bitcoin default configuration from the current user's home directory. Arguments: - `filename`: Path to a configuration file in a non-standard location (optional) - """ - if filename is None: - import os - import platform - home = os.getenv("HOME") - if not home: - raise IOError("Home directory not defined, don't know where to look for config file") + """ + if filename is None: + import os + import platform + home = os.getenv("HOME") + if not home: + raise IOError("Home directory not defined, don't know where to look for config file") - if platform.system() == "Darwin": - location = 'Library/Application Support/Bitcoin/bitcoin.conf' - else: - location = '.bitcoin/bitcoin.conf' - filename = os.path.join(home, location) + if platform.system() == "Darwin": + location = 'Library/Application Support/Bitcoin/bitcoin.conf' + else: + location = '.bitcoin/bitcoin.conf' + filename = os.path.join(home, location) - elif filename.startswith("~"): - import os - filename = os.path.expanduser(filename) + elif filename.startswith("~"): + import os + filename = os.path.expanduser(filename) - try: - return read_config_file(filename) - except (IOError, ValueError): - pass # Cannot read config file, ignore + try: + return read_config_file(filename) + except (IOError, ValueError): + pass # Cannot read config file, ignore diff --git a/mmgen/rpc/connection.py b/mmgen/rpc/connection.py index 27cc7e3e..1a0c8584 100755 --- a/mmgen/rpc/connection.py +++ b/mmgen/rpc/connection.py @@ -32,21 +32,21 @@ from mmgen.rpc.data import (ServerInfo, AccountInfo, AddressInfo, TransactionInf class BitcoinConnection(object): """ - A BitcoinConnection object defines a connection to a bitcoin server. - It is a thin wrapper around a JSON-RPC API connection. + A BitcoinConnection object defines a connection to a bitcoin server. + It is a thin wrapper around a JSON-RPC API connection. - Up-to-date for SVN revision 198. + Up-to-date for SVN revision 198. - Arguments to constructor: + Arguments to constructor: - - *user* -- Authenticate as user. - - *password* -- Authentication password. - - *host* -- Bitcoin JSON-RPC host. - - *port* -- Bitcoin JSON-RPC port. + - *user* -- Authenticate as user. + - *password* -- Authentication password. + - *host* -- Bitcoin JSON-RPC host. + - *port* -- Bitcoin JSON-RPC port. """ def __init__(self, user, password, host='localhost', port=8332, use_https=False): """ - Create a new bitcoin server connection. + Create a new bitcoin server connection. """ url = 'http{s}://{user}:{password}@{host}:{port}/'.format( s='s' if use_https else '', @@ -59,8 +59,6 @@ class BitcoinConnection(object): # importaddress
[label] [rescan=true] def importaddress(self,address,label=None,rescan=True): - """ - """ try: # return self.proxy.badmethod(address,label) # DEBUG return self.proxy.importaddress(address,label,rescan) @@ -78,8 +76,6 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? # sendrawtransaction [allowhighfees=false] def sendrawtransaction(self,tx): - """ - """ try: return self.proxy.sendrawtransaction(tx) except JSONRPCException as e: @@ -87,7 +83,7 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def stop(self): """ - Stop bitcoin server. + Stop bitcoin server. """ try: self.proxy.stop() @@ -96,7 +92,7 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getblock(self, hash): """ - Returns information about the given block hash. + Returns information about the given block hash. """ try: return self.proxy.getblock(hash) @@ -105,7 +101,7 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getblockcount(self): """ - Returns the number of blocks in the longest block chain. + Returns the number of blocks in the longest block chain. """ try: return self.proxy.getblockcount() @@ -114,9 +110,9 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getblockhash(self, index): """ - Returns hash of block in best-block-chain at index. + Returns hash of block in best-block-chain at index. - :param index: index ob the block + :param index: index ob the block """ try: @@ -126,14 +122,14 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getblocknumber(self): """ - Returns the block number of the latest block in the longest block chain. - Deprecated. Use getblockcount instead. + Returns the block number of the latest block in the longest block chain. + Deprecated. Use getblockcount instead. """ return self.getblockcount() def getconnectioncount(self): """ - Returns the number of connections to other nodes. + Returns the number of connections to other nodes. """ try: return self.proxy.getconnectioncount() @@ -142,7 +138,7 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getdifficulty(self): """ - Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + Returns the proof-of-work difficulty as a multiple of the minimum difficulty. """ try: return self.proxy.getdifficulty() @@ -151,8 +147,8 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getgenerate(self): """ - Returns :const:`True` or :const:`False`, depending on whether - generation is enabled. + Returns :const:`True` or :const:`False`, depending on whether + generation is enabled. """ try: return self.proxy.getgenerate() @@ -161,14 +157,14 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def setgenerate(self, generate, genproclimit=None): """ - Enable or disable generation (mining) of coins. + Enable or disable generation (mining) of coins. - Arguments: + Arguments: - - *generate* -- is :const:`True` or :const:`False` to turn generation - on or off. - - *genproclimit* -- Number of processors that are used for generation, - -1 is unlimited. + - *generate* -- is :const:`True` or :const:`False` to turn generation + on or off. + - *genproclimit* -- Number of processors that are used for generation, + -1 is unlimited. """ try: @@ -181,7 +177,7 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def gethashespersec(self): """ - Returns a recent hashes per second performance measurement while generating. + Returns a recent hashes per second performance measurement while generating. """ try: return self.proxy.gethashespersec() @@ -190,8 +186,8 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getinfo(self): """ - Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing - various state info. + Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing + various state info. """ try: return ServerInfo(**self.proxy.getinfo()) @@ -200,8 +196,8 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getmininginfo(self): """ - Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various - mining state info. + Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various + mining state info. """ try: return MiningInfo(**self.proxy.getmininginfo()) @@ -210,13 +206,13 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getnewaddress(self, account=None): """ - Returns a new bitcoin address for receiving payments. + Returns a new bitcoin address for receiving payments. - Arguments: + Arguments: - - *account* -- If account is specified (recommended), it is added to the - address book so that payments received with the address will be - credited to it. + - *account* -- If account is specified (recommended), it is added to the + address book so that payments received with the address will be + credited to it. """ try: @@ -229,11 +225,11 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getaccountaddress(self, account): """ - Returns the current bitcoin address for receiving payments to an account. + Returns the current bitcoin address for receiving payments to an account. - Arguments: + Arguments: - - *account* -- Account for which the address should be returned. + - *account* -- Account for which the address should be returned. """ try: @@ -243,12 +239,12 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def setaccount(self, bitcoinaddress, account): """ - Sets the account associated with the given address. + Sets the account associated with the given address. - Arguments: + Arguments: - - *bitcoinaddress* -- Bitcoin address to associate. - - *account* -- Account to associate the address to. + - *bitcoinaddress* -- Bitcoin address to associate. + - *account* -- Account to associate the address to. """ try: @@ -258,11 +254,11 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getaccount(self, bitcoinaddress): """ - Returns the account associated with the given address. + Returns the account associated with the given address. - Arguments: + Arguments: - - *bitcoinaddress* -- Bitcoin address to get account for. + - *bitcoinaddress* -- Bitcoin address to get account for. """ try: return self.proxy.getaccount(bitcoinaddress) @@ -271,11 +267,11 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getaddressesbyaccount(self, account): """ - Returns the list of addresses for the given account. + Returns the list of addresses for the given account. - Arguments: + Arguments: - - *account* -- Account to get list of addresses for. + - *account* -- Account to get list of addresses for. """ try: return self.proxy.getaddressesbyaccount(account) @@ -284,16 +280,16 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None): """ - Sends *amount* from the server's available balance to *bitcoinaddress*. + Sends *amount* from the server's available balance to *bitcoinaddress*. - Arguments: + Arguments: - - *bitcoinaddress* -- Bitcoin address to send to. - - *amount* -- Amount to send (float, rounded to the nearest 0.01). - - *minconf* -- Minimum number of confirmations required for transferred - balance. - - *comment* -- Comment for transaction. - - *comment_to* -- Comment for to-address. + - *bitcoinaddress* -- Bitcoin address to send to. + - *amount* -- Amount to send (float, rounded to the nearest 0.01). + - *minconf* -- Minimum number of confirmations required for transferred + balance. + - *comment* -- Comment for transaction. + - *comment_to* -- Comment for to-address. """ try: @@ -308,14 +304,14 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getreceivedbyaddress(self, bitcoinaddress, minconf=1): """ - Returns the total amount received by a bitcoin address in transactions - with at least a certain number of confirmations. + Returns the total amount received by a bitcoin address in transactions + with at least a certain number of confirmations. - Arguments: + Arguments: - - *bitcoinaddress* -- Address to query for total amount. + - *bitcoinaddress* -- Address to query for total amount. - - *minconf* -- Number of confirmations to require, defaults to 1. + - *minconf* -- Number of confirmations to require, defaults to 1. """ try: return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf) @@ -324,13 +320,13 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getreceivedbyaccount(self, account, minconf=1): """ - Returns the total amount received by addresses with an account in - transactions with at least a certain number of confirmations. + Returns the total amount received by addresses with an account in + transactions with at least a certain number of confirmations. - Arguments: + Arguments: - - *account* -- Account to query for total amount. - - *minconf* -- Number of confirmations to require, defaults to 1. + - *account* -- Account to query for total amount. + - *minconf* -- Number of confirmations to require, defaults to 1. """ try: @@ -340,11 +336,11 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def gettransaction(self, txid): """ - Get detailed information about transaction + Get detailed information about transaction - Arguments: + Arguments: - - *txid* -- Transactiond id for which the info should be returned + - *txid* -- Transactiond id for which the info should be returned """ try: @@ -354,12 +350,12 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def getrawtransaction(self, txid, verbose=True): """ - Get transaction raw info + Get transaction raw info - Arguments: + Arguments: - - *txid* -- Transactiond id for which the info should be returned. - - *verbose* -- If False, return only the "hex" of the transaction. + - *txid* -- Transactiond id for which the info should be returned. + - *verbose* -- If False, return only the "hex" of the transaction. """ try: @@ -371,24 +367,24 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def createrawtransaction(self, inputs, outputs): """ - Creates a raw transaction spending given inputs - (a list of dictionaries, each containing a transaction id and an output - number), sending to given address(es). + Creates a raw transaction spending given inputs + (a list of dictionaries, each containing a transaction id and an output + number), sending to given address(es). - Returns hex-encoded raw transaction. + Returns hex-encoded raw transaction. - Example usage: - >>> conn.createrawtransaction( - [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c", - "vout": 0}], - {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50}) + Example usage: + >>> conn.createrawtransaction( + [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c", + "vout": 0}], + {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50}) - Arguments: + Arguments: - - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries. - - *outputs* -- A dictionary mapping (public) addresses to the amount - they are to be paid. + - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries. + - *outputs* -- A dictionary mapping (public) addresses to the amount + they are to be paid. """ try: return self.proxy.createrawtransaction(inputs, outputs) @@ -397,22 +393,22 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None): """ - Sign inputs for raw transaction (serialized, hex-encoded). + Sign inputs for raw transaction (serialized, hex-encoded). - Returns a dictionary with the keys: - "hex": raw transaction with signature(s) (hex-encoded string) - "complete": 1 if transaction has a complete set of signature(s), 0 if not + Returns a dictionary with the keys: + "hex": raw transaction with signature(s) (hex-encoded string) + "complete": 1 if transaction has a complete set of signature(s), 0 if not - Arguments: + Arguments: - - *hexstring* -- A hex string of the transaction to sign. - - *previous_transactions* -- A (possibly empty) list of dictionaries of - the form: - {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex}, - representing previous transaction outputs that this transaction depends - on but may not yet be in the block chain. - - *private_keys* -- A (possibly empty) list of base58-encoded private - keys that, if given, will be the only keys used to sign the transaction. + - *hexstring* -- A hex string of the transaction to sign. + - *previous_transactions* -- A (possibly empty) list of dictionaries of + the form: + {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex}, + representing previous transaction outputs that this transaction depends + on but may not yet be in the block chain. + - *private_keys* -- A (possibly empty) list of base58-encoded private + keys that, if given, will be the only keys used to sign the transaction. """ try: return dict(self.proxy.signrawtransaction(hexstring, @@ -422,11 +418,11 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def decoderawtransaction(self, hexstring): """ - Produces a human-readable JSON object for a raw transaction. + Produces a human-readable JSON object for a raw transaction. - Arguments: + Arguments: - - *hexstring* -- A hex string of the transaction to be decoded. + - *hexstring* -- A hex string of the transaction to be decoded. """ try: return dict(self.proxy.decoderawtransaction(hexstring)) @@ -443,16 +439,16 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def listreceivedbyaddress(self, minconf=1, includeempty=False): """ - Returns a list of addresses. + Returns a list of addresses. - Each address is represented with a - :class:`~mmgen.rpc.data.AddressInfo` object. + Each address is represented with a + :class:`~mmgen.rpc.data.AddressInfo` object. - Arguments: + Arguments: - - *minconf* -- Minimum number of confirmations before payments are included. - - *includeempty* -- Whether to include addresses that haven't received - any payments. + - *minconf* -- Minimum number of confirmations before payments are included. + - *includeempty* -- Whether to include addresses that haven't received + any payments. """ try: @@ -463,12 +459,12 @@ ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? def listaccounts(self, minconf=1, includeWatchonly=False, as_dict=False): """ - Returns a list of account names. + Returns a list of account names. - Arguments: + Arguments: - - *minconf* -- Minimum number of confirmations before payments are included. - - *as_dict* -- Returns a dictionary of account names, with their balance as values. + - *minconf* -- Minimum number of confirmations before payments are included. + - *as_dict* -- Returns a dictionary of account names, with their balance as values. """ try: if as_dict: @@ -488,15 +484,15 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def listreceivedbyaccount(self, minconf=1, includeempty=False): """ - Returns a list of accounts. + Returns a list of accounts. - Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object. + Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object. - Arguments: + Arguments: - - *minconf* -- Minimum number of confirmations before payments are included. + - *minconf* -- Minimum number of confirmations before payments are included. - - *includeempty* -- Whether to include addresses that haven't received any payments. + - *includeempty* -- Whether to include addresses that haven't received any payments. """ try: return [AccountInfo(**x) for x in @@ -506,17 +502,17 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def listtransactions(self, account=None, count=10, from_=0, address=None): """ - Returns a list of the last transactions for an account. + Returns a list of the last transactions for an account. - Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object. + Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object. - Arguments: + Arguments: - - *account* -- Account to list transactions from. Return transactions from - all accounts if None. - - *count* -- Number of transactions to return. - - *from_* -- Skip the first transactions. - - *address* -- Receive address to consider + - *account* -- Account to list transactions from. Return transactions from + all accounts if None. + - *count* -- Number of transactions to return. + - *from_* -- Skip the first transactions. + - *address* -- Receive address to consider """ accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys() try: @@ -528,11 +524,11 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def backupwallet(self, destination): """ - Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path - with filename. + Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path + with filename. - Arguments: - - *destination* -- directory or path with filename to backup wallet to. + Arguments: + - *destination* -- directory or path with filename to backup wallet to. """ try: @@ -542,14 +538,14 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def validateaddress(self, validateaddress): """ - Validate a bitcoin address and return information for it. + Validate a bitcoin address and return information for it. - The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object. + The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object. - Arguments: -- Address to validate. + Arguments: -- Address to validate. - - *validateaddress* + - *validateaddress* """ try: return AddressValidation(**self.proxy.validateaddress(validateaddress)) @@ -558,11 +554,11 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def getbalance(self, account=None, minconf=None): """ - Get the current balance, either for an account or the total server balance. + Get the current balance, either for an account or the total server balance. - Arguments: - - *account* -- If this parameter is specified, returns the balance in the account. - - *minconf* -- Minimum number of confirmations required for transferred balance. + Arguments: + - *account* -- If this parameter is specified, returns the balance in the account. + - *minconf* -- Minimum number of confirmations required for transferred balance. """ args = [] @@ -577,15 +573,15 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def move(self, fromaccount, toaccount, amount, minconf=1, comment=None): """ - Move from one account in your wallet to another. + Move from one account in your wallet to another. - Arguments: + Arguments: - - *fromaccount* -- Source account name. - - *toaccount* -- Destination account name. - - *amount* -- Amount to transfer. - - *minconf* -- Minimum number of confirmations required for transferred balance. - - *comment* -- Comment to add to transaction log. + - *fromaccount* -- Source account name. + - *toaccount* -- Destination account name. + - *amount* -- Amount to transfer. + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment to add to transaction log. """ try: @@ -599,19 +595,19 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None, comment_to=None): """ - Sends amount from account's balance to bitcoinaddress. This method will fail - if there is less than amount bitcoins with minconf confirmations in the account's - balance (unless account is the empty-string-named default account; it - behaves like the sendtoaddress method). Returns transaction ID on success. + Sends amount from account's balance to bitcoinaddress. This method will fail + if there is less than amount bitcoins with minconf confirmations in the account's + balance (unless account is the empty-string-named default account; it + behaves like the sendtoaddress method). Returns transaction ID on success. - Arguments: + Arguments: - - *fromaccount* -- Account to send from. - - *tobitcoinaddress* -- Bitcoin address to send to. - - *amount* -- Amount to send (float, rounded to the nearest 0.01). - - *minconf* -- Minimum number of confirmations required for transferred balance. - - *comment* -- Comment for transaction. - - *comment_to* -- Comment for to-address. + - *fromaccount* -- Account to send from. + - *tobitcoinaddress* -- Bitcoin address to send to. + - *amount* -- Amount to send (float, rounded to the nearest 0.01). + - *minconf* -- Minimum number of confirmations required for transferred balance. + - *comment* -- Comment for transaction. + - *comment_to* -- Comment for to-address. """ try: @@ -626,20 +622,20 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def sendmany(self, fromaccount, todict, minconf=1, comment=None): """ - Sends specified amounts from account's balance to bitcoinaddresses. - This method will fail if there is less than total amount bitcoins with - minconf confirmations in the account's balance (unless account is the - empty-string-named default account; Returns transaction ID on - success. + Sends specified amounts from account's balance to bitcoinaddresses. + This method will fail if there is less than total amount bitcoins with + minconf confirmations in the account's balance (unless account is the + empty-string-named default account; Returns transaction ID on + success. - Arguments: + Arguments: - - *fromaccount* -- Account to send from. - - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as - values. - - *minconf* -- Minimum number of confirmations required for transferred - balance. - - *comment* -- Comment for transaction. + - *fromaccount* -- Account to send from. + - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as + values. + - *minconf* -- Minimum number of confirmations required for transferred + balance. + - *comment* -- Comment for transaction. """ try: @@ -652,15 +648,15 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def verifymessage(self, bitcoinaddress, signature, message): """ - Verifies a signature given the bitcoinaddress used to sign, - the signature itself, and the message that was signed. - Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid. + Verifies a signature given the bitcoinaddress used to sign, + the signature itself, and the message that was signed. + Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid. - Arguments: + Arguments: - - *bitcoinaddress* -- the bitcoinaddress used to sign the message - - *signature* -- the signature to be verified - - *message* -- the message that was originally signed + - *bitcoinaddress* -- the bitcoinaddress used to sign the message + - *signature* -- the signature to be verified + - *message* -- the message that was originally signed """ try: @@ -670,15 +666,15 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def getwork(self, data=None): """ - Get work for remote mining, or submit result. - If data is specified, the server tries to solve the block - using the provided data and returns :const:`True` if it was successful. - If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`) - to work on. + Get work for remote mining, or submit result. + If data is specified, the server tries to solve the block + using the provided data and returns :const:`True` if it was successful. + If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`) + to work on. - Arguments: + Arguments: - - *data* -- Result from remote mining. + - *data* -- Result from remote mining. """ try: @@ -692,13 +688,13 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def listunspent(self, minconf=1, maxconf=999999): """ - Returns a list of unspent transaction inputs in the wallet. + Returns a list of unspent transaction inputs in the wallet. - Arguments: + Arguments: - - *minconf* -- Minimum number of confirmations required to be listed. + - *minconf* -- Minimum number of confirmations required to be listed. - - *maxconf* -- Maximal number of confirmations allowed to be listed. + - *maxconf* -- Maximal number of confirmations allowed to be listed. """ @@ -717,15 +713,15 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def walletpassphrase(self, passphrase, timeout, dont_raise=False): """ - Stores the wallet decryption key in memory for seconds. + Stores the wallet decryption key in memory for seconds. - - *passphrase* -- The wallet passphrase. + - *passphrase* -- The wallet passphrase. - - *timeout* -- Time in seconds to keep the wallet unlocked - (by keeping the passphrase in memory). + - *timeout* -- Time in seconds to keep the wallet unlocked + (by keeping the passphrase in memory). - - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` - return False. + - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` + return False. """ try: self.proxy.walletpassphrase(passphrase, timeout) @@ -741,10 +737,10 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def walletlock(self): """ - Removes the wallet encryption key from memory, locking the wallet. - After calling this method, you will need to call walletpassphrase - again before being able to call any methods which require the wallet - to be unlocked. + Removes the wallet encryption key from memory, locking the wallet. + After calling this method, you will need to call walletpassphrase + again before being able to call any methods which require the wallet + to be unlocked. """ try: return self.proxy.walletlock() @@ -753,12 +749,12 @@ ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False): """ - Changes the wallet passphrase from to . + Changes the wallet passphrase from to . - Arguments: + Arguments: - - *dont_raise* -- instead of raising - `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False. + - *dont_raise* -- instead of raising + `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False. """ try: self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase) diff --git a/mmgen/rpc/exceptions.py b/mmgen/rpc/exceptions.py index 59189541..873fe4cc 100755 --- a/mmgen/rpc/exceptions.py +++ b/mmgen/rpc/exceptions.py @@ -23,181 +23,181 @@ Exception definitions. class BitcoinException(Exception): - """ + """ Base class for exceptions received from Bitcoin server. - *code* -- Error code from ``bitcoind``. - """ - # Standard JSON-RPC 2.0 errors - INVALID_REQUEST = -32600, - METHOD_NOT_FOUND = -32601, - INVALID_PARAMS = -32602, - INTERNAL_ERROR = -32603, - PARSE_ERROR = -32700, + """ + # Standard JSON-RPC 2.0 errors + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + PARSE_ERROR = -32700, - # General application defined errors - MISC_ERROR = -1 # std::exception thrown in command handling - FORBIDDEN_BY_SAFE_MODE = -2 # Server is in safe mode, and command is not allowed in safe mode - TYPE_ERROR = -3 # Unexpected type was passed as parameter - INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key - OUT_OF_MEMORY = -7 # Ran out of memory during operation - INVALID_PARAMETER = -8 # Invalid, missing or duplicate parameter - DATABASE_ERROR = -20 # Database error - DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format + # General application defined errors + MISC_ERROR = -1 # std::exception thrown in command handling + FORBIDDEN_BY_SAFE_MODE = -2 # Server is in safe mode, and command is not allowed in safe mode + TYPE_ERROR = -3 # Unexpected type was passed as parameter + INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key + OUT_OF_MEMORY = -7 # Ran out of memory during operation + INVALID_PARAMETER = -8 # Invalid, missing or duplicate parameter + DATABASE_ERROR = -20 # Database error + DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format - # P2P client errors - CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected - CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks + # P2P client errors + CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected + CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks - # Wallet errors - WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.) - WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account - WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name - WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out, call keypoolrefill first - WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first - WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect - WALLET_WRONG_ENC_STATE = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet - WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked + # Wallet errors + WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.) + WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account + WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name + WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out, call keypoolrefill first + WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first + WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect + WALLET_WRONG_ENC_STATE = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet + WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked - def __init__(self, error): - Exception.__init__(self, error['message']) - self.code = error['code'] + def __init__(self, error): + Exception.__init__(self, error['message']) + self.code = error['code'] ##### General application defined errors class SafeMode(BitcoinException): - """ + """ Operation denied in safe mode (run ``bitcoind`` with ``-disablesafemode``). - """ + """ class JSONTypeError(BitcoinException): - """ + """ Unexpected type was passed as parameter - """ + """ InvalidAmount = JSONTypeError # Backwards compatibility class InvalidAddressOrKey(BitcoinException): - """ + """ Invalid address or key. - """ + """ InvalidTransactionID = InvalidAddressOrKey # Backwards compatibility class OutOfMemory(BitcoinException): - """ + """ Out of memory during operation. - """ + """ class InvalidParameter(BitcoinException): - """ + """ Invalid parameter provided to RPC call. - """ + """ ##### Client errors class ClientException(BitcoinException): - """ + """ P2P network error. This exception is never raised but functions as a superclass for other P2P client exceptions. - """ + """ class NotConnected(ClientException): - """ + """ Not connected to any peers. - """ + """ class DownloadingBlocks(ClientException): - """ + """ Client is still downloading blocks. - """ + """ ##### Wallet errors class WalletError(BitcoinException): - """ + """ Unspecified problem with wallet (key not found etc.) - """ + """ SendError = WalletError # Backwards compatibility class InsufficientFunds(WalletError): - """ + """ Insufficient funds to complete transaction in wallet or account - """ + """ class InvalidAccountName(WalletError): - """ + """ Invalid account name - """ + """ class KeypoolRanOut(WalletError): - """ + """ Keypool ran out, call keypoolrefill first - """ + """ class WalletUnlockNeeded(WalletError): - """ + """ Enter the wallet passphrase with walletpassphrase first - """ + """ class WalletPassphraseIncorrect(WalletError): - """ + """ The wallet passphrase entered was incorrect - """ + """ class WalletWrongEncState(WalletError): - """ + """ Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - """ + """ class WalletEncryptionFailed(WalletError): - """ + """ Failed to encrypt the wallet - """ + """ class WalletAlreadyUnlocked(WalletError): - """ + """ Wallet is already unlocked - """ + """ # For convenience, we define more specific exception classes # for the more common errors. _exception_map = { - BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode, - BitcoinException.TYPE_ERROR: JSONTypeError, - BitcoinException.WALLET_ERROR: WalletError, - BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey, - BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, - BitcoinException.OUT_OF_MEMORY: OutOfMemory, - BitcoinException.INVALID_PARAMETER: InvalidParameter, - BitcoinException.CLIENT_NOT_CONNECTED: NotConnected, - BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks, - BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, - BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName, - BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut, - BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded, - BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect, - BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState, - BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed, - BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked, + BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode, + BitcoinException.TYPE_ERROR: JSONTypeError, + BitcoinException.WALLET_ERROR: WalletError, + BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey, + BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, + BitcoinException.OUT_OF_MEMORY: OutOfMemory, + BitcoinException.INVALID_PARAMETER: InvalidParameter, + BitcoinException.CLIENT_NOT_CONNECTED: NotConnected, + BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks, + BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, + BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName, + BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut, + BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded, + BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect, + BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState, + BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed, + BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked, } def _wrap_exception(error): - """ + """ Convert a JSON error object to a more specific Bitcoin exception. - """ - return _exception_map.get(error['code'], BitcoinException)(error) + """ + return _exception_map.get(error['code'], BitcoinException)(error) diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py index 7cfe6f43..925106f7 100755 --- a/mmgen/rpc/proxy.py +++ b/mmgen/rpc/proxy.py @@ -10,7 +10,7 @@ ServiceProxy class: - HTTP connections persist for the life of the AuthServiceProxy object - (if server supports HTTP/1.1) + (if server supports HTTP/1.1) - sends protocol 'version', per JSON-RPC 1.1 - sends proper, incrementing 'id' - sends Basic HTTP authentication headers diff --git a/mmgen/rpc/util.py b/mmgen/rpc/util.py index 7165bc58..35f71ce9 100755 --- a/mmgen/rpc/util.py +++ b/mmgen/rpc/util.py @@ -22,28 +22,28 @@ from copy import copy class DStruct(object): - """ + """ Simple dynamic structure, like :const:`collections.namedtuple` but more flexible (and less memory-efficient) - """ - # Default arguments. Defaults are *shallow copied*, to allow defaults such as []. - _fields = [] - _defaults = {} + """ + # Default arguments. Defaults are *shallow copied*, to allow defaults such as []. + _fields = [] + _defaults = {} - def __init__(self, *args_t, **args_d): - # order - if len(args_t) > len(self._fields): - raise TypeError("Number of arguments is larger than of predefined fields") - # Copy default values - for (k, v) in self._defaults.iteritems(): - self.__dict__[k] = copy(v) - # Set pass by value arguments - self.__dict__.update(zip(self._fields, args_t)) - # dict - self.__dict__.update(args_d) + def __init__(self, *args_t, **args_d): + # order + if len(args_t) > len(self._fields): + raise TypeError("Number of arguments is larger than of predefined fields") + # Copy default values + for (k, v) in self._defaults.iteritems(): + self.__dict__[k] = copy(v) + # Set pass by value arguments + self.__dict__.update(zip(self._fields, args_t)) + # dict + self.__dict__.update(args_d) - def __repr__(self): - return '{module}.{classname}({slots})'.format( - module=self.__class__.__module__, classname=self.__class__.__name__, - slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in - self.__dict__.iteritems())) + def __repr__(self): + return '{module}.{classname}({slots})'.format( + module=self.__class__.__module__, classname=self.__class__.__name__, + slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in + self.__dict__.iteritems())) diff --git a/mmgen/seed.py b/mmgen/seed.py index e8c52b50..4000c706 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -23,18 +23,25 @@ import sys,os from binascii import hexlify,unhexlify import mmgen.globalvars as g +import mmgen.opt as opt +from mmgen.bitcoin import b58encode_pad,b58decode_pad,b58_lens from mmgen.obj import * from mmgen.filename import * from mmgen.util import * -from mmgen.bitcoin import b58encode_pad,b58decode_pad from mmgen.crypto import * pnm = g.proj_name +def check_usr_seed_len(seed_len): + if opt.seed_len != seed_len and 'seed_len' in opt.set_by_user: + m = "ERROR: requested seed length (%s) " + \ + "doesn't match seed length of source (%s)" + die(1, m % (opt.seed_len,seed_len)) + + class Seed(MMGenObject): def __init__(self,seed_bin=None): if not seed_bin: - from mmgen.crypto import get_random # Truncate random data for smaller seed lengths seed_bin = sha256(get_random(1033)).digest()[:opt.seed_len/8] elif len(seed_bin)*8 not in g.seed_lens: @@ -43,101 +50,221 @@ class Seed(MMGenObject): self.data = seed_bin self.hexdata = hexlify(seed_bin) self.sid = make_chksum_8(seed_bin) - self.len_bytes = len(seed_bin) - self.len_bits = len(seed_bin) * 8 + self.length = len(seed_bin) * 8 + class SeedSource(MMGenObject): + desc = g.proj_name + " seed source" + stdin_ok = False + ask_tty = True + no_tty = False + _msg = {} + class SeedSourceData(MMGenObject): pass - desc = "seed source" + def __new__(cls,fn=None,ss=None,ignore_in_fmt_opt=False): - def __init__(self,fn=None,seed=None,passwd=None): + def die_on_opt_mismatch(opt,sstype): + opt_sstype = cls.fmt_code_to_sstype(opt) + compare_or_die( + opt_sstype.__name__, "input format specified on command line", + sstype.__name__, "input file format" + ) + + if ss: + sstype = cls.fmt_code_to_sstype(opt.out_fmt) + me = super(cls,cls).__new__(sstype or Wallet) # output default: Wallet + me.seed = ss.seed + me.ss_in = ss + elif fn or opt.hidden_incog_input_params: + if fn: + f = Filename(fn) + sstype = cls.ext_to_sstype(f.ext) + else: + fn = opt.hidden_incog_input_params.split(",")[0] + f = Filename(fn,ftype="hincog") + sstype = cls.fmt_code_to_sstype("hincog") + + if opt.in_fmt and not ignore_in_fmt_opt: + die_on_opt_mismatch(opt.in_fmt,sstype) + + me = super(cls,cls).__new__(sstype) + me.infile = f + elif opt.in_fmt: # Input format + sstype = cls.fmt_code_to_sstype(opt.in_fmt) + me = super(cls,cls).__new__(sstype) + else: # Called with no inputs - initialize with random seed + sstype = cls.fmt_code_to_sstype(opt.out_fmt) + me = super(cls,cls).__new__(sstype or Wallet) # output default: Wallet + me.seed = Seed() + + return me + + def __init__(self,fn=None,ss=None,ignore_in_fmt_opt=False): self.ssdata = self.SeedSourceData() - self.ssdata.passwd = passwd + self.msg = {} - if seed: - self.desc = "new " + self.desc - self.seed = seed - self._pre_encode() - self._encode() - else: - self._get_formatted_data(fn) - self._deformat() - self._decode() + for c in reversed(self.__class__.__mro__): + if hasattr(c,'_msg'): + self.msg.update(c._msg) - def _get_formatted_data(self,fn): - if fn: - self.infile = fn - self.fmt_data = get_data_from_file(fn.name,self.desc) + if hasattr(self,'seed'): + g.use_urandchars = True + self._encrypt() + elif hasattr(self,'infile'): + self._deformat_once() + self._decrypt_retry() + else: + if not self.stdin_ok: + die(1,"Reading from standard input not supported for %s format" + % self.desc) + self._deformat_retry() + self._decrypt_retry() + + def _get_data(self): + if hasattr(self,'infile'): + self.fmt_data = get_data_from_file(self.infile.name,self.desc) else: - self.infile = None self.fmt_data = get_data_from_user(self.desc) - def _pre_encode(self): pass + def _deformat_once(self): + self._get_data() + if not self._deformat(): + die(2,"Invalid format for input data") - def init(cls,infile=None,seed=None,passwd=None): + def _deformat_retry(self): + while True: + self._get_data() + if self._deformat(): break + msg("Trying again...") - sstype = None + def _decrypt_retry(self): + while True: + if self._decrypt(): break + msg("Trying again...") - if seed: - if opt.out_fmt: - sstype = fmt_code_to_sstype(opt.out_fmt) - # Output format defaults to "Wallet" - return globals()[sstype or "Wallet"](seed=seed,passwd=passwd) - else: - if infile: - fn = Filename(infile) - return globals()[fn.sstype](fn=fn,passwd=passwd) - elif opt.in_fmt: # Input format - sstype = fmt_code_to_sstype(opt.in_fmt) - return globals()[sstype](passwd=passwd) - else: - die(2,"Either an input file or input format must be specified") + subclasses = [] - init = classmethod(init) + @classmethod + def _get_subclasses(cls): + + if cls.subclasses: return cls.subclasses + + ret,gl = [],globals() + for c in [gl[k] for k in gl]: + try: + if issubclass(c,cls): + ret.append(c) + except: + pass + + cls.subclasses = ret + return ret + + @classmethod + def fmt_code_to_sstype(cls,fmt_code): + if not fmt_code: return None + for c in cls._get_subclasses(): + if hasattr(c,"fmt_codes") and fmt_code in c.fmt_codes: + return c + return None + + @classmethod + def ext_to_sstype(cls,ext): + if not ext: return None + for c in cls._get_subclasses(): + if hasattr(c,"ext") and ext == c.ext: + return c + return None + + @classmethod + def format_fmt_codes(cls): + d = [(c.__name__,",".join(c.fmt_codes)) for c in cls._get_subclasses() + if hasattr(c,"fmt_codes")] + w = max([len(a) for a,b in d]) + ret = ["{:<{w}} {}".format(a,b,w=w) for a,b in [ + ("Format","Valid codes"), + ("------","-----------") + ] + sorted(d)] + return "\n".join(ret) + "\n" def write_to_file(self): self._format() - write_to_file_or_stdout(self._filename(),self.fmt_data, self.desc) + kwargs = { + 'desc': self.desc, + 'ask_tty': self.ask_tty, + 'no_tty': self.no_tty + } + write_data_to_file(self._filename(),self.fmt_data,**kwargs) + + +class SeedSourceUnenc(SeedSource): + + def _decrypt_retry(self): pass + def _encrypt(self): pass -class SeedSourceUnenc(SeedSource): pass class SeedSourceEnc(SeedSource): - _ss_enc_msg = { + _msg = { 'choose_passphrase': """ You must choose a passphrase to encrypt your new %s with. A key will be generated from your passphrase using a hash preset of '%s'. -Please note that no strength checking of passphrases is performed. For an -empty passphrase, just hit ENTER twice. +Please note that no strength checking of passphrases is performed. For +an empty passphrase, just hit ENTER twice. """.strip() } - def _pre_encode(self): - if self.ssdata.passwd == None: self._get_first_passwd() - self._get_hash_preset() - self._encrypt_seed() + def _get_pw(self,desc=None): + self.ssdata.passwd = get_mmgen_passphrase(desc) - def _get_first_passwd(self): - qmsg(self._ss_enc_msg['choose_passphrase'] % (self.desc,opt.hash_preset)) - self.ssdata.passwd = get_new_passphrase(what=self.desc) + def _get_hash_preset(self,desc=None): + # Converting: + desc = desc or self.desc + if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'): + if opt.keep_hash_preset: + a = self.ss_in.ssdata.hash_preset + qmsg("Reusing hash preset '%s' as per user request" % a) + elif 'hash_preset' in opt.set_by_user: + # Prompt, but use user-requested value as default + a = get_hash_preset_from_user(hp=opt.hash_preset,desc=desc) + else: + a = get_hash_preset_from_user(desc=desc) + elif 'hash_preset' in opt.set_by_user: + a = opt.hash_preset + qmsg("Using user-requested hash preset of '%s'" % a) + else: + a = get_hash_preset_from_user(desc=self.desc) + self.ssdata.hash_preset = a - def _get_hash_preset(self): - self.ssdata.hash_preset = \ - opt.hash_preset or get_hash_preset_from_user(what=self.desc) - - def _encrypt_seed(self): + def _get_first_pw_and_hp_and_encrypt_seed(self): d = self.ssdata + + if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd') \ + and opt.keep_passphrase: + d.passwd = self.ss_in.ssdata.passwd + qmsg("Reusing passphrase as per user request") + + self._get_hash_preset(desc="new " + self.desc) + + if not hasattr(d,'passwd'): + qmsg(self.msg['choose_passphrase'] % (self.desc,self.ssdata.hash_preset)) + d.passwd = get_new_passphrase(desc="new " + self.desc) + d.salt = sha256(get_random(128)).digest()[:g.salt_len] key = make_key(d.passwd, d.salt, d.hash_preset) d.key_id = make_chksum_8(key) d.enc_seed = encrypt_seed(self.seed.data,key) + class Mnemonic (SeedSourceUnenc): + stdin_ok = True + fmt_codes = "mmwords","words","mnemonic","mnem","mn","m" desc = "mnemonic data" + ext = "mmwords" wl_checksums = { "electrum": '5ca31424', "tirosh": '1a5faeff' @@ -176,23 +303,18 @@ class Mnemonic (SeedSourceUnenc): return words.strip().split("\n") - def _encode(self): - + def _format(self): wl = self._get_wordlist() - seed_hex = hexlify(self.seed.data) + seed_hex = self.seed.hexdata mn = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) - rev = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) - if rev != seed_hex: - msg("ERROR: seed recomputed from wordlist doesn't match original seed!") - msg("Original seed: %s" % seed_hex) - msg("Recomputed seed: %s" % rev) - sys.exit(3) + ret = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) + # Internal error, so just die on fail + compare_or_die(ret,"recomputed seed", + seed_hex,"original",e="Internal error") self.ssdata.mnemonic = mn - - def _format(self): - self.fmt_data = " ".join(self.ssdata.mnemonic) + "\n" + self.fmt_data = " ".join(mn) + "\n" def _deformat(self): @@ -200,92 +322,103 @@ class Mnemonic (SeedSourceUnenc): wl = self._get_wordlist() if len(mn) not in g.mn_lens: - die(3,"Invalid mnemonic (%i words). Allowed numbers of words: %s" % + msg("Invalid mnemonic (%i words). Allowed numbers of words: %s" % (len(mn),", ".join([str(i) for i in g.mn_lens]))) + return False for n,w in enumerate(mn,1): if w not in wl: - die(3,"Invalid mnemonic: word #%s is not in the wordlist" % n) - - self.ssdata.mnemonic = mn - - def _decode(self): - - mn = self.ssdata.mnemonic - wl = self._get_wordlist() + msg("Invalid mnemonic: word #%s is not in the wordlist" % n) + return False seed_hex = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) - rev = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) - if rev != mn: - msg("ERROR: mnemonic recomputed from seed not the same as original") - die(3,"Recomputed mnemonic:\n%s" % " ".join(rev)) + ret = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) - qmsg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(seed_hex))) + # Internal error, so just die + compare_or_die(" ".join(ret),"recomputed mnemonic", + " ".join(mn),"original",e="Internal error") self.seed = Seed(unhexlify(seed_hex)) + self.ssdata.mnemonic = mn + + check_usr_seed_len(self.seed.length) + + qmsg("Valid mnemonic for seed ID %s" % make_chksum_8(self.seed.data)) + return True def _filename(self): - return "%s.%s" % (self.seed.sid, g.mn_ext) + return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext) + class SeedFile (SeedSourceUnenc): + stdin_ok = True + fmt_codes = "mmseed","seed","s" desc = "seed data" + ext = "mmseed" - def _encode(self): + def _format(self): b58seed = b58encode_pad(self.seed.data) self.ssdata.chksum = make_chksum_6(b58seed) self.ssdata.b58seed = b58seed - - def _decode(self): - - seed = b58decode_pad(self.ssdata.b58seed) - if seed == False: - msg("Invalid base 58 string: %s" % val) - return False - - msg("Valid seed data for seed ID %s" % make_chksum_8(seed)) - self.seed = Seed(seed) - - def _format(self): self.fmt_data = "%s %s\n" % ( self.ssdata.chksum, - split_into_columns(4,self.ssdata.b58seed) + split_into_cols(4,b58seed) ) def _deformat(self): - what = self.desc + desc = self.desc ld = self.fmt_data.split() if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11 - msg("Invalid data length (%s) in %s" % (len(ld),what)) + msg("Invalid data length (%s) in %s" % (len(ld),desc)) return False a,b = ld[0],"".join(ld[1:]) if not is_chksum_6(a): - msg("'%s': invalid checksum format, in %s" % (a, what)) + msg("'%s': invalid checksum format in %s" % (a, desc)) return False if not is_b58string(b): - msg("'%s': not a base 58 string, in %s" % (b, what)) + msg("'%s': not a base 58 string, in %s" % (b, desc)) return False - vmsg_r("Validating %s checksum..." % what) + vmsg_r("Validating %s checksum..." % desc) - compare_chksums(a,"checksum",make_chksum_6(b),"base 58 data") + if not compare_chksums( + a,"checksum",make_chksum_6(b),"base 58 data"): + return False + ret = b58decode_pad(b) + + if ret == False: + msg("Invalid base-58 encoded seed: %s" % val) + return False + + self.seed = Seed(ret) self.ssdata.chksum = a self.ssdata.b58seed = b + check_usr_seed_len(self.seed.length) + + qmsg("Valid seed data for seed ID %s" % make_chksum_8(self.seed.data)) + + return True + def _filename(self): - return "%s.%s" % (self.seed.sid, g.seed_ext) + return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext) + class Wallet (SeedSourceEnc): - desc = "{pnm} wallet".format(pnm=pnm) + fmt_codes = "wallet","w" + desc = g.proj_name + " wallet" + ext = "mmdat" - def _encode(self): + def _encrypt(self): + self._get_first_pw_and_hp_and_encrypt_seed() d = self.ssdata d.label = opt.label or "No Label" d.pw_status = "NE" if len(d.passwd) else "E" @@ -294,50 +427,42 @@ class Wallet (SeedSourceEnc): def _format(self): d = self.ssdata s = self.seed - s_fmt = b58encode_pad(d.salt) + slt_fmt = b58encode_pad(d.salt) es_fmt = b58encode_pad(d.enc_seed) lines = ( d.label, "{} {} {} {} {}".format(s.sid.lower(), d.key_id.lower(), - s.len_bits, d.pw_status, d.timestamp), + s.length, d.pw_status, d.timestamp), "{}: {} {} {}".format(d.hash_preset,*get_hash_params(d.hash_preset)), - "{} {}".format(make_chksum_6(s_fmt), split_into_columns(4,s_fmt)), - "{} {}".format(make_chksum_6(es_fmt), split_into_columns(4,es_fmt)) + "{} {}".format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)), + "{} {}".format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt)) ) chksum = make_chksum_6(" ".join(lines)) self.fmt_data = "%s\n" % "\n".join((chksum,)+lines) - def _decode(self): - d = self.ssdata - # Needed for multiple transactions with {}-txsign - prompt_add = " "+self.infile.name if opt.quiet else "" - passwd = get_mmgen_passphrase(self.desc+prompt_add) - key = make_key(passwd, d.salt, d.hash_preset) - self.seed = Seed(decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id)) - self.ssdata.passwd = passwd - - def _check_master_chksum(self,lines): - - if len(lines) != 6: - vmsg("Invalid number of lines (%s) in %s data" % (len(lines),self.desc)) - elif not is_chksum_6(lines[0]): - vmsg("Incorrect Master checksum (%s) in %s data" % (lines[0],self.desc)) - else: - chk = make_chksum_6(" ".join(lines[1:])) - if compare_chksums(lines[0],"master wallet",chk,"computed"): - return True - - msg("Invalid %s data" % self.desc) - sys.exit(2) - def _deformat(self): - qmsg("Getting {pnm} wallet data from file '{f}'".format( - pnm=pnm,f=self.infile.name)) + def check_master_chksum(lines,desc): + + if len(lines) != 6: + msg("Invalid number of lines (%s) in %s data" % + (len(lines),desc)) + return False + + if not is_chksum_6(lines[0]): + msg("Incorrect master checksum (%s) in %s data" % + (lines[0],desc)) + return False + + chk = make_chksum_6(" ".join(lines[1:])) + if not compare_chksums(lines[0],"master",chk,"computed", + hdr="For wallet master checksum"): + return False + + return True lines = self.fmt_data.rstrip().split("\n") - - self._check_master_chksum(lines) + if not check_master_chksum(lines,self.desc): return False d = self.ssdata d.label = lines[1] @@ -345,39 +470,67 @@ class Wallet (SeedSourceEnc): d1,d2,d3,d4,d5 = lines[2].split() d.seed_id = d1.upper() d.key_id = d2.upper() - d.seed_len = int(d3) + check_usr_seed_len(int(d3)) d.pw_status,d.timestamp = d4,d5 hpdata = lines[3].split() - d.hash_preset = hpdata[0][:-1] # a string! + + d.hash_preset = hp = hpdata[0][:-1] # a string! + qmsg("Hash preset of wallet: '%s'" % hp) + uhp = opt.hash_preset + if uhp and 'hash_preset' in opt.set_by_user and uhp != hp: + msg("Warning: ignoring user-requested hash preset '%s'" % uhp) + hash_params = [int(i) for i in hpdata[1:]] if hash_params != get_hash_params(d.hash_preset): msg("Hash parameters '%s' don't match hash preset '%s'" % (" ".join(hash_params), d.hash_preset)) - sys.exit(3) + return False + lmin,lmax = b58_lens[0],b58_lens[-1] # 22,33,44 for i,key in (4,"salt"),(5,"enc_seed"): - l = lines[i].split(" ",1) - if len(l) != 2: - msg("Invalid format for %s in %s: %s" % (key,self.desc,val)) - sys.exit(3) - chk,val = l[0],l[1].replace(" ","") - compare_chksums(chk,"wallet "+key, - make_chksum_6(val),"computed checksum") - val_bin = b58decode_pad(val) - if val_bin == False: - msg("Invalid base 58 number: %s" % val) - sys.exit(3) - setattr(d,key,val_bin) + l = lines[i].split(" ") + chk = l.pop(0) + b58_val = "".join(l) + + if len(b58_val) < lmin or len(b58_val) > lmax: + msg("Invalid format for %s in %s: %s" % (key,self.desc,l)) + return False + + if not compare_chksums(chk,key, + make_chksum_6(b58_val),"computed checksum"): + return False + + val = b58decode_pad(b58_val) + if val == False: + msg("Invalid base 58 number: %s" % b58_val) + return False + + setattr(d,key,val) + + return True + + def _decrypt(self): + d = self.ssdata + # Needed for multiple transactions with {}-txsign + add = " "+self.infile.name if opt.quiet else "" + self._get_pw(self.desc+add) + key = make_key(d.passwd, d.salt, d.hash_preset) + ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id) + if ret: + self.seed = Seed(ret) + return True + else: + return False def _filename(self): return "{}-{}[{},{}].{}".format( self.seed.sid, self.ssdata.key_id, - self.seed.len_bits, + self.seed.length, self.ssdata.hash_preset, - g.wallet_ext + self.ext ) # def __str__(self): @@ -391,7 +544,7 @@ class Wallet (SeedSourceEnc): # ("Label:", d.label), # ("Seed ID:", s.sid), # ("Key ID:", d.key_id), -# ("Seed length:", "%s bits (%s bytes)" % (s.len_bits,s.len_bytes)), +# ("Seed length:", "%s bits (%s bytes)" % (s.length,s.length/8)), # ("Scrypt params:", "Preset '%s' (%s)" % (opt.hash_preset, # " ".join([str(i) for i in get_hash_params(opt.hash_preset)]) # ) @@ -412,14 +565,19 @@ class Wallet (SeedSourceEnc): # # return "\n".join(out) + class Brainwallet (SeedSourceEnc): + stdin_ok = True + fmt_codes = "mmbrain","brainwallet","brain","bw","b" desc = "brainwallet" + ext = "mmbrain" def _deformat(self): self.brainpasswd = " ".join(self.fmt_data.split()) + return True - def _decode(self): + def _decrypt(self): self._get_hash_preset() vmsg_r("Hashing brainwallet data. Please wait...") # Use buflen arg of scrypt.hash() to get seed of desired length @@ -427,43 +585,75 @@ class Brainwallet (SeedSourceEnc): self.ssdata.hash_preset, buflen=opt.seed_len/8) vmsg("Done") self.seed = Seed(seed) + msg("Seed ID: %s" % self.seed.sid) + qmsg("Check this value against your records") + return True class IncogWallet (SeedSourceEnc): - desc = "incognito wallet" + fmt_codes = "mmincog","incog","icg","i" + desc = "incognito data" + ext = "mmincog" + no_tty = True - _icg_msg = { - 'incog_iv_id': """ -Check that the generated Incog ID above is correct. If it's not, then your -incognito data is incorrect or corrupted. - """.strip(), - 'incog_iv_id_hidden': """ -Check that the generated Incog ID above is correct. If it's not, then your -incognito data is incorrect or corrupted, or you've supplied an incorrect -offset. - """.strip(), - 'incorrect_incog_passphrase_try_again': """ + _msg = { + 'check_incog_id': """ + Check the generated Incog ID above against your records. If it doesn't + match, then your incognito data is incorrect or corrupted. + """, + 'record_incog_id': """ + Make a record of the Incog ID but keep it secret. You will use it to + identify your incog wallet data in the future. + """, + 'incorrect_incog_passphrase_try_again': """ Incorrect passphrase, hash preset, or maybe old-format incog wallet. Try again? (Y)es, (n)o, (m)ore information: """.strip(), - 'confirm_seed_id': """ + 'confirm_seed_id': """ If the seed ID above is correct but you're seeing this message, then you need to exit and re-run the program with the '--old-incog-fmt' option. """.strip(), + 'dec_chk': " %s hash preset" } def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper() def _get_incog_data_len(self,seed_len): - return g.aesctr_iv_len + g.salt_len + g.hincog_chk_len + seed_len/8 + e = 0 if opt.old_incog_fmt else g.hincog_chk_len + return g.aesctr_iv_len + g.salt_len + e + seed_len/8 - def _encode (self): + def _incog_data_size_chk(self): + # valid sizes: 56, 64, 72 + dlen = len(self.fmt_data) + valid_dlen = self._get_incog_data_len(opt.seed_len) + if dlen == valid_dlen: + return True + else: + if opt.old_incog_fmt: + msg("WARNING: old-style incognito format requested. " + + "Are you sure this is correct?") + msg(("Invalid incognito data size (%s bytes) for this " + + "seed length (%s bits)") % (dlen,opt.seed_len)) + msg("Valid data size for this seed length: %s bytes" % valid_dlen) + for sl in g.seed_lens: + if dlen == self._get_incog_data_len(sl): + die(1,"Valid seed length for this data size: %s bits" % sl) + msg(("This data size (%s bytes) is invalid for all available " + + "seed lengths") % dlen) + return False + + def _encrypt (self): + self._get_first_pw_and_hp_and_encrypt_seed() + if opt.old_incog_fmt: + die(1,"Writing old-format incog wallets is unsupported") d = self.ssdata # IV is used BOTH to initialize counter and to salt password! d.iv = get_random(g.aesctr_iv_len) d.iv_id = self._make_iv_chksum(d.iv) - msg("Incog ID: %s" % d.iv_id) + msg("New Incog Wallet ID: %s" % d.iv_id) + qmsg("Make a record of this value") + vmsg(self.msg['record_incog_id']) d.salt = get_random(g.salt_len) key = make_key(d.passwd, d.salt, d.hash_preset, "incog wallet key") @@ -472,7 +662,8 @@ to exit and re-run the program with the '--old-incog-fmt' option. d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, "incog wrapper key") d.key_id = make_chksum_8(d.wrapper_key) - d.target_data_len = self._get_incog_data_len(opt.seed_len) + vmsg("Key ID: %s" % d.key_id) + d.target_data_len = self._get_incog_data_len(self.seed.length) def _format(self): d = self.ssdata @@ -480,121 +671,144 @@ to exit and re-run the program with the '--old-incog-fmt' option. d.salt + d.enc_seed, d.wrapper_key, int(hexlify(d.iv),16), - "incog data" + self.desc ) def _filename(self): + s = self.seed + d = self.ssdata return "{}-{}-{}[{},{}].{}".format( - self.seed.sid, - self.ssdata.key_id, - self.ssdata.iv_id, - self.seed.len_bits, - self.ssdata.hash_preset, - g.incog_ext + s.sid, + d.key_id, + d.iv_id, + s.length, + d.hash_preset, + self.ext ) def _deformat(self): - # Data could be of invalid length, so check: [56, 64, 72] - valid_dlen = self._get_incog_data_len(opt.seed_len) - raw_d = self.fmt_data - if len(raw_d) != valid_dlen: - msg("Invalid incognito data size: %s" % len(raw_d)) - msg("Valid incognito data size for this seed length: %s bytes" % - valid_dlen) - for sl in g.seed_lens: - if len(raw_d) == self._get_incog_data_len(sl): - die(1,"Maybe you need to specify a seed length of %s?" % sl) - die(1,"The data size is invalid for all available seed lengths") + if not self._incog_data_size_chk(): return False d = self.ssdata - d.iv = raw_d[0:g.aesctr_iv_len] + d.iv = self.fmt_data[0:g.aesctr_iv_len] d.incog_id = self._make_iv_chksum(d.iv) - d.enc_incog_data = raw_d[g.aesctr_iv_len:] - msg("Incog ID: %s" % d.incog_id) - qmsg("Check the applicable value against your records") - ksuf = '_hidden' if self.__class__ == "IncogWalletHidden" else "" - vmsg("\n%s\n" % self._icg_msg['incog_iv_id' + ksuf]) + d.enc_incog_data = self.fmt_data[g.aesctr_iv_len:] + msg("Incog Wallet ID: %s" % d.incog_id) + qmsg("Check this value against your records") + vmsg(self.msg['check_incog_id']) - def _decode(self): + return True + + def _verify_seed_newfmt(self,data): + chk,seed = data[:8],data[8:] + if sha256(seed).digest()[:8] == chk: + qmsg("Passphrase%s are correct" % (self.msg['dec_chk'] % "and")) + return seed + else: + msg("Incorrect passphrase%s" % (self.msg['dec_chk'] % "or")) + return False + + def _verify_seed_oldfmt(self,seed): + m = "Seed ID: %s. Is the seed ID correct?" % make_chksum_8(seed) + if keypress_confirm(m, True): + return seed + else: + return False + + def _decrypt(self): d = self.ssdata - prompt_info="{pnm} incognito wallet".format(pnm=pnm) + desc = self.desc+" "+d.incog_id + self._get_hash_preset(desc) + self._get_pw(desc) - while True: - passwd = get_mmgen_passphrase(prompt_info+" "+d.incog_id) + # IV is used BOTH to initialize counter and to salt password! + key = make_key(d.passwd, d.iv, d.hash_preset, "wrapper key") + dd = decrypt_data(d.enc_incog_data, key, + int(hexlify(d.iv),16), "incog data") - qmsg("Configured hash presets: %s" % - " ".join(sorted(g.hash_presets))) - d.hash_preset = get_hash_preset_from_user(what="incog wallet") + d.salt = dd[0:g.salt_len] + d.enc_seed = dd[g.salt_len:] - # IV is used BOTH to initialize counter and to salt password! - key = make_key(passwd, d.iv, d.hash_preset, "wrapper key") - dd = decrypt_data(d.enc_incog_data, key, - int(hexlify(d.iv),16), "incog data") + key = make_key(d.passwd, d.salt, d.hash_preset, "main key") + msg("Key ID: %s" % make_chksum_8(key)) - d.salt = dd[0:g.salt_len] - d.enc_seed = dd[g.salt_len:] + verify_seed = self._verify_seed_oldfmt if opt.old_incog_fmt else \ + self._verify_seed_newfmt - key = make_key(passwd, d.salt, d.hash_preset, "main key") - vmsg("Key ID: %s" % make_chksum_8(key)) + seed = verify_seed(decrypt_seed(d.enc_seed, key, "", "")) - ret = decrypt_seed(d.enc_seed, key, "", "") - - chk,seed_maybe = ret[:8],ret[8:] - if sha256(seed_maybe).digest()[:8] == chk: - msg("Passphrase and hash preset are correct") - seed = seed_maybe - break - else: - msg("Incorrect passphrase or hash preset") - - self.seed = Seed(seed) + if seed: + self.seed = Seed(seed) + msg("Seed ID: %s" % self.seed.sid) + return True + else: + return False class IncogWalletHex (IncogWallet): + desc = "hex incognito data" + fmt_codes = "mmincox","incog_hex","xincog","ix","xi" + ext = "mmincox" + no_tty = False + def _deformat(self): - self.fmt_data = decode_pretty_hexdump(self.fmt_data) - IncogWallet._deformat(self) + ret = decode_pretty_hexdump(self.fmt_data) + if ret: + self.fmt_data = ret + return IncogWallet._deformat(self) + else: + return False def _format(self): IncogWallet._format(self) self.fmt_data = pretty_hexdump(self.fmt_data) - def _filename(self): - return IncogWallet._filename(self)[:-len(g.incog_ext)] + g.incog_hex_ext - class IncogWalletHidden (IncogWallet): - _hicg_msg = { + desc = "hidden incognito data" + fmt_codes = "incog_hidden","hincog","ih","hi" + ext = None + + _msg = { 'choose_file_size': """ You must choose a size for your new hidden incog data. The minimum size is {} bytes, which puts the incog data right at the end of the file. Since you -probably want to hide your data somewhere in the middle of the file where -it's harder to find, you're advised to choose a much larger file size. +probably want to hide your data somewhere in the middle of the file where it's +harder to find, you're advised to choose a much larger file size than this. """.strip(), + 'check_incog_id': """ + Check generated Incog ID above against your records. If it doesn't + match, then your incognito data is incorrect or corrupted, or you + may have specified an incorrect offset. + """, + 'record_incog_id': """ + Make a record of the Incog ID but keep it secret. You will used it to + identify the incog wallet data in the future and to locate the offset + where the data is hidden in the event you forget it. + """, + 'dec_chk': ", hash preset, offset %s seed length" } - def _get_hincog_params(self): - a,b = opt.hidden_incog_params.split(",") + + def _get_hincog_params(self,wtype): + p = getattr(opt,'hidden_incog_'+ wtype +'_params') + a,b = p.split(",") return a,int(b) def _check_valid_offset(self,fn,action): d = self.ssdata + m = "Destination" if action == "write" else "Input" if fn.size < d.hincog_offset + d.target_data_len: die(1, -"Destination file has length %s, too short to %s %s bytes of data at offset %s" - % (fn.size,action,d.target_data_len,d.hincog_offset)) + "%s file has length %s, too short to %s %s bytes of data at offset %s" + % (m,fn.size,action,d.target_data_len,d.hincog_offset)) - - # overrides method in SeedSource - def _get_formatted_data(self,fn): - if fn: die(1, -"Specify the filename as a parameter of the '--from-hidden-incog' option") + def _get_data(self): d = self.ssdata - f,d.hincog_offset = self._get_hincog_params() - self.infile = Filename(f,ftype="hincog") + d.hincog_offset = self._get_hincog_params("input")[1] qmsg("Getting hidden incog data from file '%s'" % self.infile.name) @@ -616,7 +830,7 @@ it's harder to find, you're advised to choose a much larger file size. self._format() compare_or_die(d.target_data_len, "target data length", len(self.fmt_data),"length of formatted " + self.desc) - fn,d.hincog_offset = self._get_hincog_params() + fn,d.hincog_offset = self._get_hincog_params("output") self.hincog_data_is_new = False try: @@ -625,13 +839,13 @@ it's harder to find, you're advised to choose a much larger file size. if keypress_confirm("Requested file '%s' does not exist. Create?" % fn, default_yes=True): min_fsize = d.target_data_len + d.hincog_offset - msg(self._hicg_msg['choose_file_size'].format(min_fsize)) + msg(self.msg['choose_file_size'].format(min_fsize)) while True: fsize = my_raw_input("Enter file size: ") if is_int(fsize) and int(fsize) >= min_fsize: break - msg("File size must be an integer no less than %s" % min_fsize) + msg("File size must be an integer no less than %s" % + min_fsize) - g.use_urandchars = True from mmgen.tool import rand2file rand2file(fn, str(fsize)) self.hincog_data_is_new = True @@ -640,8 +854,7 @@ it's harder to find, you're advised to choose a much larger file size. self.outfile = f = Filename(fn,ftype="hincog") - if opt.debug: - Msg("Incog data len %s, offset %s" % (d.target_data_len,d.hincog_offset)) + dmsg("Incog data len %s, offset %s" % (d.target_data_len,d.hincog_offset)) if not self.hincog_data_is_new: self._check_valid_offset(f,"write") @@ -651,4 +864,6 @@ it's harder to find, you're advised to choose a much larger file size. os.lseek(fh, int(d.hincog_offset), os.SEEK_SET) os.write(fh, self.fmt_data) os.close(fh) - msg("Incog data written to file '%s' at offset %s" % (f.name,d.hincog_offset)) + msg("%s written to file '%s' at offset %s" % ( + self.desc[0].upper()+self.desc[1:], + f.name,d.hincog_offset)) diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index 232b53f8..b2de0d4d 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -62,6 +62,21 @@ def process_opts(argv,opts_data,short_opts,long_opts): opt[1:]+":")][:-1].replace("-","_")] = arg else: assert False, "Invalid option" + if 'sets' in opts_data: + for o_in,v_in,o_out,v_out in opts_data['sets']: + if o_in in opts: + v = opts[o_in] + if (v and v_in == bool) or v == v_in: + if o_out in opts and opts[o_out] != v_out: + sys.stderr.write( + "Option conflict:\n --%s=%s, with\n --%s=%s\n" % ( + o_out.replace("_","-"),opts[o_out], + o_in.replace("_","-"),opts[o_in] + )) + sys.exit(1) + else: + opts[o_out] = v_out + return opts,args diff --git a/mmgen/test.py b/mmgen/test.py index 47ece172..60b302d0 100755 --- a/mmgen/test.py +++ b/mmgen/test.py @@ -77,14 +77,14 @@ def ok_or_die(val,chk_func,s,skip_ok=False): try: ret = chk_func(val) except: ret = False if ret: - if not skip_ok: ok() + if not skip_ok: ok() else: msg(red("Returned value '%s' is not a %s" % (val,s))) sys.exit(3) def cmp_or_die(s,t,skip_ok=False): if s == t: - if not skip_ok: ok() + if not skip_ok: ok() else: sys.stderr.write(red( "ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n" % diff --git a/mmgen/tool.py b/mmgen/tool.py index b057574e..a3b557cf 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -439,19 +439,17 @@ def add_label(mmaddr,label,remove=False): check_addr_label(label) # Exits on failure c = connect_to_bitcoind() - from mmgen.addr import AddrInfoList - ail = AddrInfoList(bitcoind_connection=c) - btcaddr = "" - sid,idx = mmaddr.split(":") - if sid in ail.seed_ids(): - btcaddr = ail.addrinfo(sid).btcaddr(int(idx)) + from mmgen.addr import AddrInfoList + btcaddr = AddrInfoList(bitcoind_connection=c).mmaddr2btcaddr(mmaddr) + if not btcaddr: die(1,"{pnm} address {a} not found in tracking wallet".format( pnm=pnm,a=mmaddr)) try: - c.importaddress(btcaddr," ".join((mmaddr,label)),rescan=False) + l = " " + label if label else "" + c.importaddress(btcaddr,mmaddr+l,rescan=False) except: die(1,"Unable to add label") @@ -508,7 +506,7 @@ def encrypt(infile,outfile="",hash_preset=""): data = get_data_from_file(infile,"data for encryption") enc_d = mmgen_encrypt(data,"user data",hash_preset) if outfile == '-': - write_to_stdout(enc_d,"encrypted data",confirm=True) + write_to_stdout(enc_d,"encrypted data") else: if not outfile: outfile = os.path.basename(infile) + "." + g.mmenc_ext @@ -522,7 +520,7 @@ def decrypt(infile,outfile="",hash_preset=""): if dec_d: break msg("Trying again...") if outfile == '-': - write_to_stdout(dec_d,"decrypted data",confirm=not opt.quiet) + write_to_stdout(dec_d,"decrypted data",ask_terminal=not opt.quiet) else: if not outfile: outfile = os.path.basename(infile) diff --git a/mmgen/tx.py b/mmgen/tx.py index db3c0bc9..5df58cc3 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -46,8 +46,7 @@ def normalize_btc_amt(amt): msg("%s: Invalid amount" % amt) return False - if opt.debug: - Msg("Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))) + dmsg("Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))) if ret.as_tuple()[-1] < -8: msg("%s: Too many decimal places in amount" % amt) diff --git a/mmgen/util.py b/mmgen/util.py index c004e73f..f0443abe 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -42,10 +42,10 @@ def msg_r(s): sys.stderr.write(s) def Msg(s): sys.stdout.write(s + "\n") def Msg_r(s): sys.stdout.write(s) def msgred(s): sys.stderr.write(red(s+"\n")) -def msgrepr(*args): +def mmsg(*args): for d in args: sys.stdout.write(repr(d)+"\n") -def msgrepr_exit(*args): +def mdie(*args): for d in args: sys.stdout.write(repr(d)+"\n") sys.exit() @@ -55,20 +55,8 @@ def die(ev,s): def Die(ev,s): sys.stdout.write(s+"\n"); sys.exit(ev) -def fmt_type(x): return "%s" % str(type(x)).split("'")[1] - import opt -def fmt_code_to_sstype(fmt_code): - for e in g.wallet_fmt_codes: - if fmt_code in e: return e[0] - die(2,"'%s': unrecognized format code" % fmt_code) - -def format_fmt_codes(): - return "".join( - ["%-20s " % (e[0]+":") + ",".join(e[1:]) + "\n" - for e in g.wallet_fmt_codes]) - def qmsg(s,alt=False): if opt.quiet: if alt != False: sys.stderr.write(alt + "\n") @@ -87,7 +75,10 @@ def Vmsg(s): def Vmsg_r(s): if opt.verbose: sys.stdout.write(s) -def suf(arg,what): +def dmsg(s): + if opt.debug: sys.stdout.write(s + "\n") + +def suf(arg,suf_type): t = type(arg) if t == int: n = arg @@ -97,9 +88,9 @@ def suf(arg,what): msg("%s: invalid parameter" % arg) return "" - if what in "a": + if suf_type in ("a","es"): return "" if n == 1 else "es" - if what in "k": + if suf_type in ("k","s"): return "" if n == 1 else "s" def get_extension(f): @@ -125,10 +116,14 @@ def splitN(s,n,sep=None): # always return an n-element list def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list -def split_into_columns(col_wid,s): +def split_into_cols(col_wid,s): return " ".join([s[col_wid*i:col_wid*(i+1)] for i in range(len(s)/col_wid+1)]).rstrip() +def capfirst(s): + if len(s) == 0: return s + return s[0].upper() + (s[1:] if len(s) > 1 else "") + def make_timestamp(): tv = time.gmtime(time.time())[:6] return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv) @@ -166,6 +161,13 @@ def is_utf8(s): def match_ext(addr,ext): return addr.split(".")[-1] == ext +def file_exists(f): + try: + os.stat(f) + return True + except: + return False + import opt as opt def get_from_brain_opt_params(): @@ -186,7 +188,11 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False): def decode_pretty_hexdump(data): from string import hexdigits lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")] - return unhexlify("".join(("".join(lines).split()))) + try: + return unhexlify("".join(("".join(lines).split()))) + except: + msg("Data not in hexdump format") + return False def get_hash_params(hash_preset): if hash_preset in g.hash_presets: @@ -195,22 +201,25 @@ def get_hash_params(hash_preset): msg("%s: invalid 'hash_preset' value" % hash_preset) sys.exit(3) -def compare_chksums(chk1, desc1, chk2, desc2, die=True): +def compare_chksums(chk1, desc1, chk2, desc2, hdr="", die_on_fail=False): if not chk1 == chk2: - if die: - die(3,"Checksum error: %s checksum (%s) doesn't match %s checksum (%s)" - % (desc2,chk2,desc1,chk1)) - else: return False + m = "%s ERROR: %s checksum (%s) doesn't match %s checksum (%s)"\ + % ((hdr+":\n " if hdr else "CHECKSUM"),desc2,chk2,desc1,chk1) + if die_on_fail: + die(3,m) + else: + msg(m) + return False - vmsg("%s checksum OK (%s)" % (desc1.capitalize(),chk1)) + vmsg("%s checksum OK (%s)" % (capfirst(desc1),chk1)) return True -def compare_or_die(val1, desc1, val2, desc2): +def compare_or_die(val1, desc1, val2, desc2, e="Error"): if cmp(val1,val2): - die(3,"Error: %s (%s) doesn't match %s (%s)" - % (desc2,val2,desc1,val1)) - vmsg("%s OK (%s)" % (desc2.capitalize(),val2)) + die(3,"%s: %s (%s) doesn't match %s (%s)" + % (e,desc2,val2,desc1,val1)) + dmsg("%s OK (%s)" % (capfirst(desc2),val2)) return True def get_default_wordlist(): @@ -224,8 +233,8 @@ def open_file_or_exit(filename,mode): try: f = open(filename, mode) except: - what = "reading" if 'r' in mode else "writing" - msg("Unable to open file '%s' for %s" % (filename,what)) + op = "reading" if 'r' in mode else "writing" + msg("Unable to open file '%s' for %s" % (filename,op)) sys.exit(2) return f @@ -307,9 +316,9 @@ def parse_addr_idxs(arg,sep=","): return sorted(set(ret)) -def get_new_passphrase(what,passchg=False): +def get_new_passphrase(desc,passchg=False): - w = "{}passphrase for {}".format("new " if passchg else "", what) + w = "{}passphrase for {}".format("new " if passchg else "", desc) if opt.passwd_file: pw = " ".join(_get_words_from_file(opt.passwd_file,w)) elif opt.echo_passphrase: @@ -318,7 +327,7 @@ def get_new_passphrase(what,passchg=False): for i in range(g.passwd_max_tries): pw = " ".join(_get_words_from_user("Enter {}: ".format(w))) pw2 = " ".join(_get_words_from_user("Repeat passphrase: ")) - if opt.debug: Msg("Passphrases: [%s] [%s]" % (pw,pw2)) + dmsg("Passphrases: [%s] [%s]" % (pw,pw2)) if pw == pw2: vmsg("Passphrases match"); break else: msg("Passphrases do not match. Try again.") @@ -332,27 +341,21 @@ def get_new_passphrase(what,passchg=False): def confirm_or_exit(message, question, expect="YES"): - if not confirm_or_false(message, question, expect): - msg("Exiting at user request") - sys.exit(2) - -def confirm_or_false(message, question, expect="YES"): m = message.strip() if m: msg(m) - conf_msg = "Type uppercase '%s' to confirm: " % expect + a = question+" " if question[0].isupper() else \ + "Are you sure you want to %s?\n" % question + b = "Type uppercase '%s' to confirm: " % expect - p = question+" "+conf_msg if question[0].isupper() else \ - "Are you sure you want to %s?\n%s" % (question,conf_msg) - - vmsg("") - return my_raw_input(p).strip() == expect + if my_raw_input(a+b).strip() != expect: + die(2,"Exiting at user request") -def write_to_stdout(data, what, confirm=True): - if sys.stdout.isatty() and confirm: - confirm_or_exit("",'output {} to screen'.format(what)) +def write_to_stdout(data, desc, ask_terminal=True): + if sys.stdout.isatty() and ask_terminal: + confirm_or_exit("",'output {} to screen'.format(desc)) elif not sys.stdout.isatty(): try: of = os.readlink("/proc/%d/fd/1" % os.getpid()) @@ -363,8 +366,83 @@ def write_to_stdout(data, what, confirm=True): msg("Redirecting output to file") sys.stdout.write(data) +# New function +def write_data_to_file( + outfile, + data, + desc="data", + ask_write=False, + ask_write_prompt="", + ask_write_default_yes=False, + ask_overwrite=True, + ask_tty=True, + no_tty=False, + silent=False + ): + if opt.stdout or not sys.stdout.isatty(): + qmsg("Output to STDOUT requested") + write_ok = False + if sys.stdout.isatty(): + if no_tty: + die(2,"Printing %s to screen is not allowed" % desc) + if ask_tty: + confirm_or_exit("",'output %s to screen' % desc) + else: + try: of = os.readlink("/proc/%d/fd/1" % os.getpid()) # Linux + except: of = None # Windows -def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True,silent=False): + if of: + if of[:5] == "pipe:": + if no_tty: + die(2,"Writing %s to pipe is not allowed" % desc) + if ask_tty: + confirm_or_exit("",'output %s to pipe' % desc) + msg("") + of2,pd = os.path.relpath(of),os.path.pardir + msg("Redirecting output to file '%s'" % + (of if of2[:len(pd)] == pd else of2)) + else: + msg("Redirecting output to file") + + sys.stdout.write(data) + else: + if opt.outdir: outfile = make_full_path(opt.outdir,outfile) + + if ask_write: + if not keypress_confirm(ask_write_prompt, + default_yes=ask_write_default_yes): + die(1,"Exiting at user request") + + hush = False + if file_exists(outfile): + if ask_overwrite: + q = "File '%s' already exists\nOverwrite?" % outfile + confirm_or_exit("",q) + if not silent: msg("Overwriting file '%s'" % outfile) + hush = True + + f = open_file_or_exit(outfile,'wb') + try: + f.write(data) + except: + if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile)) + sys.exit(2) + f.close + + if not hush: + msg("%s written to file '%s'" % (capfirst(desc),outfile)) + + return True + + +def write_to_file( + outfile, + data, + desc="data", + confirm_overwrite=False, + verbose=False, + silent=False + ): if opt.outdir: outfile = make_full_path(opt.outdir,outfile) @@ -373,12 +451,7 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False else: if confirm_overwrite: q = "File '%s' already exists\nOverwrite?" % outfile - if exit_on_error: - confirm_or_exit("",q) - else: - if not confirm_or_false("",q): - msg("Not overwriting file at user request") - return False + confirm_or_exit("",q) else: if not silent: msg("Overwriting file '%s'" % outfile) @@ -386,20 +459,20 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False try: f.write(data) except: - if not silent: msg("Failed to write %s to file '%s'" % (what,outfile)) + if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile)) sys.exit(2) f.close - if verbose: msg("%s written to file '%s'" % (what.capitalize(),outfile)) + if verbose: msg("%s written to file '%s'" % (capfirst(desc),outfile)) return True -def write_to_file_or_stdout(outfile, data, what="data"): +def write_to_file_or_stdout(outfile, data, desc="data"): if opt.stdout or not sys.stdout.isatty(): - write_to_stdout(data, what, confirm=True) + write_to_stdout(data, desc) else: - write_to_file(outfile,data,what,not opt.quiet,True) + write_to_file(outfile,data,desc,not opt.quiet,True) from mmgen.bitcoin import b58decode_pad,b58encode_pad @@ -437,7 +510,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed): seed_len = str(len(seed)*8) pw_status = "NE" if len(passwd) else "E" hash_preset = opt.hash_preset - label = opt.label if opt.label else "No Label" + label = opt.label or "No Label" metadata = seed_id.lower(),key_id.lower(),seed_len,\ pw_status,make_timestamp() sf = b58encode_pad(salt) @@ -447,8 +520,8 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed): label, "{} {} {} {} {}".format(*metadata), "{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)), - "{} {}".format(make_chksum_6(sf), split_into_columns(4,sf)), - "{} {}".format(make_chksum_6(esf), split_into_columns(4,esf)) + "{} {}".format(make_chksum_6(sf), split_into_cols(4,sf)), + "{} {}".format(make_chksum_6(esf), split_into_cols(4,esf)) ) chk = make_chksum_6(" ".join(lines)) @@ -465,18 +538,18 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed): def _check_mmseed_format(words): valid = False - what = "%s data" % g.seed_ext + desc = "%s data" % g.seed_ext try: chklen = len(words[0]) except: return False if len(words) < 3 or len(words) > 12: - msg("Invalid data length (%s) in %s" % (len(words),what)) + msg("Invalid data length (%s) in %s" % (len(words),desc)) elif not is_hexstring(words[0]): - msg("Invalid format of checksum '%s' in %s"%(words[0], what)) + msg("Invalid format of checksum '%s' in %s"%(words[0], desc)) elif chklen != 6: - msg("Incorrect length of checksum (%s) in %s" % (chklen,what)) + msg("Incorrect length of checksum (%s) in %s" % (chklen,desc)) else: valid = True return valid @@ -484,19 +557,19 @@ def _check_mmseed_format(words): def _check_wallet_format(infile, lines): - what = "wallet file '%s'" % infile + desc = "wallet file '%s'" % infile valid = False chklen = len(lines[0]) if len(lines) != 6: - vmsg("Invalid number of lines (%s) in %s" % (len(lines),what)) + vmsg("Invalid number of lines (%s) in %s" % (len(lines),desc)) elif chklen != 6: - vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,what)) + vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,desc)) elif not is_hexstring(lines[0]): - vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], what)) + vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], desc)) else: valid = True if valid == False: - msg("Invalid %s" % what) + msg("Invalid %s" % desc) sys.exit(2) @@ -506,8 +579,7 @@ def _check_chksum_6(chk,val,desc,infile): msg("%s checksum incorrect in file '%s'!" % (desc,infile)) msg("Checksum: %s. Computed value: %s" % (chk,comp_chk)) sys.exit(2) - elif opt.debug: - Msg("%s checksum passed: %s" % (desc.capitalize(),chk)) + dmsg("%s checksum passed: %s" % (capfirst(desc),chk)) def get_data_from_wallet(infile,silent=False): @@ -557,23 +629,23 @@ def get_data_from_wallet(infile,silent=False): def _get_words_from_user(prompt): # split() also strips words = my_raw_input(prompt, echo=opt.echo_passphrase).split() - if opt.debug: Msg("Sanitized input: [%s]" % " ".join(words)) + dmsg("Sanitized input: [%s]" % " ".join(words)) return words -def _get_words_from_file(infile,what): - qmsg("Getting %s from file '%s'" % (what,infile)) +def _get_words_from_file(infile,desc): + qmsg("Getting %s from file '%s'" % (desc,infile)) f = open_file_or_exit(infile, 'r') # split() also strips words = f.read().split() f.close() - if opt.debug: Msg("Sanitized input: [%s]" % " ".join(words)) + dmsg("Sanitized input: [%s]" % " ".join(words)) return words -def get_words(infile,what,prompt): +def get_words(infile,desc,prompt): if infile: - return _get_words_from_file(infile,what) + return _get_words_from_file(infile,desc) else: return _get_words_from_user(prompt) @@ -586,24 +658,24 @@ def remove_comments(lines): if i: ret.append(i) return ret -def get_lines_from_file(infile,what="",trim_comments=False): - if what != "": - qmsg("Getting %s from file '%s'" % (what,infile)) +def get_lines_from_file(infile,desc="",trim_comments=False): + if desc != "": + qmsg("Getting %s from file '%s'" % (desc,infile)) f = open_file_or_exit(infile,'r') lines = f.read().splitlines() f.close() return remove_comments(lines) if trim_comments else lines -def get_data_from_user(what="data",silent=False): - data = my_raw_input("Enter %s: " % what, echo=opt.echo_passphrase) - if opt.debug: Msg("User input: [%s]" % data) +def get_data_from_user(desc="data",silent=False): + data = my_raw_input("Enter %s: " % desc, echo=opt.echo_passphrase) + dmsg("User input: [%s]" % data) return data -def get_data_from_file(infile,what="data",dash=False,silent=False): +def get_data_from_file(infile,desc="data",dash=False,silent=False): if dash and infile == "-": return sys.stdin.read() if not silent: - qmsg("Getting %s from file '%s'" % (what,infile)) + qmsg("Getting %s from file '%s'" % (desc,infile)) f = open_file_or_exit(infile,'rb') data = f.read() f.close() @@ -622,7 +694,7 @@ def get_seed_from_seed_data(words): chk = make_chksum_6(seed_b58) vmsg_r("Validating %s checksum..." % g.seed_ext) - if compare_chksums(chk, "seed", stored_chk, "input",die=False): + if compare_chksums(chk, "seed", stored_chk, "input"): seed = b58decode_pad(seed_b58) if seed == False: msg("Invalid b58 number: %s" % val) @@ -645,9 +717,8 @@ def mark_passwd_file_as_used(): passwd_file_used = True -def get_mmgen_passphrase(prompt_info,passchg=False): - prompt = "Enter {}passphrase for {}: ".format( - "old " if passchg else "",prompt_info) +def get_mmgen_passphrase(desc,passchg=False): + prompt ="Enter {}passphrase for {}: ".format("old " if passchg else "",desc) if opt.passwd_file: mark_passwd_file_as_used() return " ".join(_get_words_from_file(opt.passwd_file,"passphrase")) @@ -674,17 +745,18 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action): fsize = os.stat(fname).st_size if fsize < offset + dlen: + m = "Destination" if action == "write" else "Input" msg( -"Destination file has length %s, too short to %s %s bytes of data at offset %s" - % (fsize,action,dlen,offset)) + "%s file has length %s, too short to %s %s bytes of data at offset %s" + % (m,fsize,action,dlen,offset)) sys.exit(1) from mmgen.term import kb_hold_protect,get_char -def get_hash_preset_from_user(hp=g.hash_preset,what="data"): +def get_hash_preset_from_user(hp=g.hash_preset,desc="data"): p = """Enter hash preset for %s, -or hit ENTER to accept the default value ('%s'): """ % (what,hp) + or hit ENTER to accept the default value ('%s'): """ % (desc,hp) while True: ret = my_raw_input(p) if ret: @@ -751,17 +823,18 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False): def do_license_msg(immed=False): - from mmgen.license import gpl + import mmgen.license as gpl if opt.quiet or g.no_license: return - msg(gpl['warning']) - prompt = "%s " % gpl['prompt'].strip() + p = "Press 'w' for conditions and warranty info, or 'c' to continue:" + msg(gpl.warning) + prompt = "%s " % p.strip() while True: reply = get_char(prompt, immed_chars="wc" if immed else "") if reply == 'w': from mmgen.term import do_pager - do_pager(gpl['conditions']) + do_pager(gpl.conditions) elif reply == 'c': msg(""); break else: diff --git a/setup.py b/setup.py index c4552da5..5401f403 100755 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ setup( 'mmgen.main_txsend', 'mmgen.main_txsign', 'mmgen.main_walletchk', + 'mmgen.main_walletconv', 'mmgen.main_walletgen', 'mmgen.share.__init__', @@ -83,6 +84,7 @@ setup( 'mmgen-addrimport', 'mmgen-passchg', 'mmgen-walletchk', + 'mmgen-walletconv', 'mmgen-walletgen', 'mmgen-txcreate', 'mmgen-txsign', diff --git a/test/gentest.py b/test/gentest.py index 8b5b1940..5a9c4552 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -11,8 +11,8 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir)) from binascii import hexlify import mmgen.opt as opt -import mmgen.config as g -from mmgen.util import msg,msg_r,msgrepr,msgrepr_exit,red,green +import mmgen.globalvars as g +from mmgen.util import msg,msg_r,mmsg,mdie,red,green,vmsg from mmgen.bitcoin import hextowif,privnum2addr rounds = 100 @@ -23,6 +23,7 @@ opts_data = { -h, --help Print this help message -s, --system Test scripts and modules installed on system rather than those in the repo root +-v, --verbose Produce more verbose output """, 'notes': """ @@ -63,6 +64,7 @@ for i in range(1,rounds+1): sec = hexlify(os.urandom(32)) wif = hextowif(sec) a = privnum2addr(int(sec,16)) + vmsg("\nkey: %s\naddr: %s\n" % (wif,a)) b = check_output(["keyconv", wif]).split()[1] if a != b: msg_r(red("\nERROR: Addresses do not match!")) @@ -74,4 +76,4 @@ for i in range(1,rounds+1): """.format(sec,wif,a,b,pnm=g.proj_name).rstrip()) sys.exit(3) -msg(green("\nOK")) +msg(green("%sOK" % ("" if opt.verbose else "\n"))) diff --git a/test/ref/1378FC64-2907DE97-F980D21F[192,1].mmincog b/test/ref/1378FC64-2907DE97-F980D21F[192,1].mmincog new file mode 100644 index 00000000..6f428364 --- /dev/null +++ b/test/ref/1378FC64-2907DE97-F980D21F[192,1].mmincog @@ -0,0 +1,2 @@ +H@&ÏRLrT.®Õ%¬Å7¿š ÙÞeÞøÁ¹:x%£5ƒîoù*À!×é Ä +í£0¾sÿè£R%‡PØÇ \ No newline at end of file diff --git a/test/ref/1378FC64-4DCB5174-872806A7[192,1].mmincox b/test/ref/1378FC64-4DCB5174-872806A7[192,1].mmincox new file mode 100644 index 00000000..f8b86bc4 --- /dev/null +++ b/test/ref/1378FC64-4DCB5174-872806A7[192,1].mmincox @@ -0,0 +1,4 @@ +8e7a aa61 cf9d acba 10ec cda2 a54c a64e +ee54 970a ebdc f44d 53d2 c005 46ea 1ab3 +d940 0d78 6050 16a8 4ef6 e228 661a 803a +38ea 6874 189f b022 db94 3e11 051b 0302 diff --git a/test/ref/98831F3A-1630A9F2-870376A9[256,1].mmincox b/test/ref/98831F3A-1630A9F2-870376A9[256,1].mmincox new file mode 100644 index 00000000..c0472b4e --- /dev/null +++ b/test/ref/98831F3A-1630A9F2-870376A9[256,1].mmincox @@ -0,0 +1,5 @@ +078b 15e6 42c1 8447 d5a4 f03c b3ae aab9 +7d79 2640 c307 bf2a 78a6 847b 44f2 6d96 +7d9b 44ac 193c a47a 9a12 6e60 9f8f fe96 +720b 0682 01d8 7da4 9eea be67 60c6 cbcf +d522 5a97 8bc2 5554 diff --git a/test/ref/98831F3A-5482381C-18460FB1[256,1].mmincog b/test/ref/98831F3A-5482381C-18460FB1[256,1].mmincog new file mode 100644 index 00000000..944ee04b --- /dev/null +++ b/test/ref/98831F3A-5482381C-18460FB1[256,1].mmincog @@ -0,0 +1 @@ +‘¹É7Í8»—vàìPšš0,ÿ%¡F§¡äâNé`ÚtÓì½$°@™pk%…ÒL}ÎñQçጙp/º=ú©-rï~Gïó9 \ No newline at end of file diff --git a/test/ref/FE3C6545-BC4BE3F2-32586837[128,1].mmincox b/test/ref/FE3C6545-BC4BE3F2-32586837[128,1].mmincox new file mode 100644 index 00000000..02c0a440 --- /dev/null +++ b/test/ref/FE3C6545-BC4BE3F2-32586837[128,1].mmincox @@ -0,0 +1,4 @@ +80f5 2531 04ab 4de4 11fd ccb8 aae4 26ed +2520 8218 e5e1 8744 d367 9e9d 3491 ff8e +f83e 523b 36f7 dbe5 3c92 aeca c935 15fa +1bc5 2c54 0e4c 0825 diff --git a/test/ref/FE3C6545-E29303EA-5E229E30[128,1].mmincog b/test/ref/FE3C6545-E29303EA-5E229E30[128,1].mmincog new file mode 100644 index 00000000..b8cedef4 Binary files /dev/null and b/test/ref/FE3C6545-E29303EA-5E229E30[128,1].mmincog differ diff --git a/test/ref/sample-text.mmenc b/test/ref/sample-text.mmenc new file mode 100644 index 00000000..dd38f9f0 Binary files /dev/null and b/test/ref/sample-text.mmenc differ diff --git a/test/test.py b/test/test.py index 4f926cbe..a3867b58 100755 --- a/test/test.py +++ b/test/test.py @@ -10,7 +10,7 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir)) import mmgen.globalvars as g import mmgen.opt as opt -from mmgen.util import msgrepr,msgrepr_exit,Msg,die +from mmgen.util import mmsg,mdie,Msg,die from mmgen.test import * hincog_fn = "rand_data" @@ -35,6 +35,9 @@ ref_kafile_pass = "kafile password" ref_kafile_hash_preset = "1" ref_enc_fn = "sample-text.mmenc" +tool_enc_passwd = "Scrypt it, don't hash it!" +sample_text = \ + "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n" cfgs = { '6': { @@ -46,8 +49,11 @@ cfgs = { 'keyaddrfile_chk': "CF83 32FB 8A8B 08E2 0F00 D601", 'wpasswd': "reference password", 'ref_wallet': "FE3C6545-D782B529[128,1].mmdat", - 'ic_wallet': "FE3C6545-161E495F-BEB7548E[128:1].incog-offset123", - 'ic_wallet_old': "FE3C6545-161E495F-9860A85B[128:1].incog-old.offset123", + 'ic_wallet': "FE3C6545-E29303EA-5E229E30[128,1].mmincog", + 'ic_wallet_hex': "FE3C6545-BC4BE3F2-32586837[128,1].mmincox", + + 'hic_wallet': "FE3C6545-161E495F-BEB7548E[128:1].incog-offset123", + 'hic_wallet_old': "FE3C6545-161E495F-9860A85B[128:1].incog-old.offset123", 'tmpdir': os.path.join("test","tmp6"), 'kapasswd': "", @@ -68,8 +74,11 @@ cfgs = { 'keyaddrfile_chk': "9648 5132 B98E 3AD9 6FC3 C5AD", 'wpasswd': "reference password", 'ref_wallet': "1378FC64-6F0F9BB4[192,1].mmdat", - 'ic_wallet': "1378FC64-B55E9958-77256FC1[192:1].incog.offset123", - 'ic_wallet_old': "1378FC64-B55E9958-D85FF20C[192:1].incog-old.offset123", + 'ic_wallet': "1378FC64-2907DE97-F980D21F[192,1].mmincog", + 'ic_wallet_hex': "1378FC64-4DCB5174-872806A7[192,1].mmincox", + + 'hic_wallet': "1378FC64-B55E9958-77256FC1[192:1].incog.offset123", + 'hic_wallet_old': "1378FC64-B55E9958-D85FF20C[192:1].incog-old.offset123", 'tmpdir': os.path.join("test","tmp7"), 'kapasswd': "", @@ -97,8 +106,11 @@ cfgs = { # 'ref_fake_unspent_data':"98831F3A_unspent.json", 'ref_tx_file': "tx_FFB367[1.234].raw", - 'ic_wallet': "98831F3A-F59B07A0-559CEF19[256:1].incog.offset123", - 'ic_wallet_old': "98831F3A-F59B07A0-848535F3[256:1].incog-old.offset123", + 'ic_wallet': "98831F3A-5482381C-18460FB1[256,1].mmincog", + 'ic_wallet_hex': "98831F3A-1630A9F2-870376A9[256,1].mmincox", + + 'hic_wallet': "98831F3A-F59B07A0-559CEF19[256:1].incog.offset123", + 'hic_wallet_old': "98831F3A-F59B07A0-848535F3[256:1].incog-old.offset123", 'tmpdir': os.path.join("test","tmp8"), 'kapasswd': "", @@ -108,7 +120,6 @@ cfgs = { 'addrs': "refaddrgen3", 'akeys.mmenc': "refkeyaddrgen3" }, - }, '1': { 'tmpdir': os.path.join("test","tmp1"), @@ -133,7 +144,7 @@ cfgs = { 'tmpdir': os.path.join("test","tmp2"), 'wpasswd': "Hodling away", 'addr_idx_list': "37,45,3-6,22-23", # 8 addresses - 'seed_len': 128, + 'seed_len': 128, 'dep_generators': { 'mmdat': "walletgen2", 'addrs': "addrgen2", @@ -157,7 +168,7 @@ cfgs = { 'tmpdir': os.path.join("test","tmp4"), 'wpasswd': "Hashrate rising", 'addr_idx_list': "63,1004,542-544,7-9", # 8 addresses - 'seed_len': 192, + 'seed_len': 192, 'dep_generators': { 'mmdat': "walletgen4", 'mmbrain': "walletgen4", @@ -177,9 +188,6 @@ cfgs = { }, '9': { 'tmpdir': os.path.join("test","tmp9"), - 'tool_enc_passwd': "Scrypt it, don't hash it!", - 'sample_text': - "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n", 'tool_enc_infn': "tool_encrypt.in", # 'tool_enc_ref_infn': "tool_encrypt_ref.in", 'dep_generators': { @@ -191,46 +199,14 @@ cfgs = { }, } +from copy import deepcopy +for a,b in ('6','11'),('7','12'),('8','13'): + cfgs[b] = deepcopy(cfgs[a]) + cfgs[b]['tmpdir'] = os.path.join("test","tmp"+b) + from collections import OrderedDict cmd_data = OrderedDict([ # test description depends - # Check saved reference files: - ['ref_wallet_chk1', (6,'saved reference wallet (128-bit)', [[[],6]])], - ['ref_wallet_chk2', (7,'saved reference wallet (192-bit)', [[[],7]])], - ['ref_wallet_chk3', (8,'saved reference wallet (256-bit)', [[[],8]])], - ['ref_seed_chk1', (6,'saved seed file (128-bit)', [[[],6]])], - ['ref_seed_chk2', (7,'saved seed file (192-bit)', [[[],7]])], - ['ref_seed_chk3', (8,'saved seed file (256-bit)', [[[],8]])], - ['ref_mn_chk1', (6,'saved mnemonic file (128-bit)', [[[],6]])], - ['ref_mn_chk2', (7,'saved mnemonic file (192-bit)', [[[],7]])], - ['ref_mn_chk3', (8,'saved mnemonic file (256-bit)', [[[],8]])], - ['ref_incog_chk1', (6,'saved incog reference wallet (128-bit)', [[[],6]])], - ['ref_incog_chk2', (7,'saved incog reference wallet (192-bit)', [[[],7]])], - ['ref_incog_chk3', (8,'saved incog reference wallet (256-bit)', [[[],8]])], - ['ref_brain_chk1', (6,'saved brainwallet (128-bit)', [[[],6]])], - ['ref_brain_chk2', (7,'saved brainwallet (192-bit)', [[[],7]])], - ['ref_brain_chk3', (8,'saved brainwallet (256-bit)', [[[],8]])], - ['ref_brain_chk3_spc', (8,'saved brainwallet (256-bit, non-standard spacing)', [[[],8]])], - - ['ref_addrfile_chk', (8,'saved reference address file', [[[],8]])], - ['ref_keyaddrfile_chk', (8,'saved reference key-address file', [[[],8]])], -# Create the fake inputs: -# ['txcreate8', (8,'transaction creation (8)', [[["addrs"],8]])], - ['ref_tx_chk', (8,'saved reference tx file', [[[],8]])], - - ['ref_tool_decrypt', (9,'decryption of saved MMGen-encrypted file', [[[],9]])], - - # Generate new reference ('abc' brainwallet) files: - ['refwalletgen1', (6,'gen new refwallet (128-bit)', [[[],6]])], - ['refwalletgen2', (7,'gen new refwallet (192-bit)', [[[],7]])], - ['refwalletgen3', (8,'gen new refwallet (256-bit)', [[[],8]])], - ['refaddrgen1', (6,'new refwallet addr chksum (128-bit)', [[["mmdat"],6]])], - ['refaddrgen2', (7,'new refwallet addr chksum (192-bit)', [[["mmdat"],7]])], - ['refaddrgen3', (8,'new refwallet addr chksum (256-bit)', [[["mmdat"],8]])], - ['refkeyaddrgen1', (6,'new refwallet key-addr chksum (128-bit)', [[["mmdat"],6]])], - ['refkeyaddrgen2', (7,'new refwallet key-addr chksum (192-bit)', [[["mmdat"],7]])], - ['refkeyaddrgen3', (8,'new refwallet key-addr chksum (256-bit)', [[["mmdat"],8]])], - ['walletgen', (1,'wallet generation', [[[],1]])], # ['walletchk', (1,'wallet check', [[["mmdat"],1]])], ['passchg', (5,'password, label and hash preset change',[[["mmdat"],1]])], @@ -256,13 +232,11 @@ cmd_data = OrderedDict([ ['keyaddrgen', (1,'key-address file generation', [[["mmdat"],1]])], ['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])], -# ['walletgen2',(2,'wallet generation (2)', [])], ['walletgen2',(2,'wallet generation (2), 128-bit seed', [])], ['addrgen2', (2,'address generation (2)', [[["mmdat"],2]])], ['txcreate2', (2,'transaction creation (2)', [[["addrs"],2]])], ['txsign2', (2,'transaction signing, two transactions',[[["mmdat","raw"],1],[["mmdat","raw"],2]])], ['export_mnemonic2', (2,'seed export to mmwords format (2)',[[["mmdat"],2]])], -# ['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])], ['walletgen3',(3,'wallet generation (3)', [])], ['addrgen3', (3,'address generation (3)', [[["mmdat"],3]])], @@ -270,18 +244,83 @@ cmd_data = OrderedDict([ ['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[["mmdat"],1],[["mmdat","raw"],3]])], ['walletgen4',(4,'wallet generation (4) (brainwallet)', [])], -# ['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])], ['addrgen4', (4,'address generation (4)', [[["mmdat"],4]])], ['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[["addrs"],1],[["addrs"],2],[["addrs"],3],[["addrs"],4]])], ['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[["mmincog"],1],[["mmwords"],2],[["mmdat"],3],[["mmbrain","raw"],4]])], ['tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])], - ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", - [[[cfgs['9']['tool_enc_infn'], - cfgs['9']['tool_enc_infn']+".mmenc"],9]])], + ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[cfgs['9']['tool_enc_infn'],cfgs['9']['tool_enc_infn']+".mmenc"],9]])], # ['tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])], ['tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])], ]) +# saved reference data +cmd_data_ref = ( + # reading + ('ref_wallet_chk', ([],'saved reference wallet')), + ('ref_seed_chk', ([],'saved seed file')), + ('ref_mn_chk', ([],'saved mnemonic file')), + ('ref_hincog_chk', ([],'saved hidden incog reference wallet')), + ('ref_brain_chk', ([],'saved brainwallet')), + # generating new reference ('abc' brainwallet) files: + ('refwalletgen', ([],'gen new refwallet')), + ('refaddrgen', (["mmdat"],'new refwallet addr chksum')), + ('refkeyaddrgen', (["mmdat"],'new refwallet key-addr chksum')) +) + +# misc. saved reference data +cmd_data_ref_other = ( + ('ref_addrfile_chk', 'saved reference address file'), + ('ref_keyaddrfile_chk','saved reference key-address file'), +# Create the fake inputs: +# ('txcreate8', 'transaction creation (8)'), + ('ref_tx_chk', 'saved reference tx file'), + ('ref_brain_chk_spc3', 'saved brainwallet (non-standard spacing)'), + ('ref_tool_decrypt', 'decryption of saved MMGen-encrypted file'), +) + +# mmgen-walletconv: +cmd_data_conv_in = ( # reading + ('ref_wallet_conv', 'conversion of saved reference wallet'), + ('ref_mn_conv', 'conversion of saved mnemonic'), + ('ref_seed_conv', 'conversion of saved seed file'), + ('ref_brain_conv', 'conversion of ref brainwallet'), + ('ref_incog_conv', 'conversion of saved incog wallet'), + ('ref_incox_conv', 'conversion of saved hex incog wallet'), + ('ref_hincog_conv', 'conversion of saved hidden incog wallet'), + ('ref_hincog_conv_old','conversion of saved hidden incog wallet (old format)') +) +cmd_data_conv_out = ( # writing + ('ref_wallet_conv_out', 'ref seed conversion to wallet'), + ('ref_mn_conv_out', 'ref seed conversion to mnemonic'), + ('ref_seed_conv_out', 'ref seed conversion to seed'), + ('ref_incog_conv_out', 'ref seed conversion to incog data'), + ('ref_incox_conv_out', 'ref seed conversion to hex incog data'), + ('ref_hincog_conv_out', 'ref seed conversion to hidden incog data') +) + +cmd_groups = OrderedDict([ + ('main', cmd_data.keys()), + ('ref', [c[0]+str(i) for c in cmd_data_ref for i in (1,2,3)]), + ('ref_other', [c[0] for c in cmd_data_ref_other]), + ('conv_in', [c[0]+str(i) for c in cmd_data_conv_in for i in (1,2,3)]), + ('conv_out', [c[0]+str(i) for c in cmd_data_conv_out for i in (1,2,3)]), +]) + +for a,b in cmd_data_ref: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (5+i,"%s (%s-bit)" % (b[1],j),[[b[0],5+i]]) + +for a,b in cmd_data_ref_other: + cmd_data[a] = (8,b,[[[],8]]) + +for a,b in cmd_data_conv_in: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]]) + +for a,b in cmd_data_conv_out: + for i,j in (1,128),(2,192),(3,256): + cmd_data[a+str(i)] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]]) + utils = { 'check_deps': 'check dependencies for specified command', 'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)', @@ -296,23 +335,34 @@ for k in cfgs.keys(): cfgs[k]['amts'][idx] = "%s.%s" % ((getrandnum(2) % mod), str(getrandnum(4))[:5]) meta_cmds = OrderedDict([ - ['saved_ref1', (6,("ref_wallet_chk1","ref_seed_chk1","ref_mn_chk1","ref_brain_chk1","ref_incog_chk1"))], - ['saved_ref2', (7,("ref_wallet_chk2","ref_seed_chk2","ref_mn_chk2","ref_brain_chk2","ref_incog_chk2"))], - ['saved_ref3', (8,("ref_wallet_chk3","ref_seed_chk3","ref_mn_chk3","ref_brain_chk3","ref_incog_chk3","ref_brain_chk3_spc"))], - ['saved_ref_other', (8,("ref_addrfile_chk","ref_tx_chk","ref_tool_decrypt"))], - ['ref1', (6,("refwalletgen1","refaddrgen1","refkeyaddrgen1"))], - ['ref2', (7,("refwalletgen2","refaddrgen2","refkeyaddrgen2"))], - ['ref3', (8,("refwalletgen3","refaddrgen3","refkeyaddrgen3"))], - ['gen', (1,("walletgen","addrgen"))], - ['pass', (5,("passchg","walletchk_newpass"))], - ['tx', (1,("txcreate","txsign","txsend"))], - ['export', (1,[k for k in cmd_data if k[:7] == "export_" and cmd_data[k][0] == 1])], - ['gen_sp', (1,[k for k in cmd_data if k[:8] == "addrgen_" and cmd_data[k][0] == 1])], - ['online', (1,("keyaddrgen","txsign_keyaddr"))], - ['2', (2,[k for k in cmd_data if cmd_data[k][0] == 2])], - ['3', (3,[k for k in cmd_data if cmd_data[k][0] == 3])], - ['4', (4,[k for k in cmd_data if cmd_data[k][0] == 4])], - ['tool', (9,("tool_encrypt","tool_decrypt","tool_find_incog_data"))], + ['ref1', ("refwalletgen1","refaddrgen1","refkeyaddrgen1")], + ['ref2', ("refwalletgen2","refaddrgen2","refkeyaddrgen2")], + ['ref3', ("refwalletgen3","refaddrgen3","refkeyaddrgen3")], + ['gen', ("walletgen","addrgen")], + ['pass', ("passchg","walletchk_newpass")], + ['tx', ("addrimport","txcreate","txsign","txsend")], + ['export', [k for k in cmd_data if k[:7] == "export_" and cmd_data[k][0] == 1]], + ['gen_sp', [k for k in cmd_data if k[:8] == "addrgen_" and cmd_data[k][0] == 1]], + ['online', ("keyaddrgen","txsign_keyaddr")], + ['2', [k for k in cmd_data if cmd_data[k][0] == 2]], + ['3', [k for k in cmd_data if cmd_data[k][0] == 3]], + ['4', [k for k in cmd_data if cmd_data[k][0] == 4]], + + ['tool', ("tool_encrypt","tool_decrypt","tool_find_incog_data")], + + ['saved_ref1', [c[0]+"1" for c in cmd_data_ref]], + ['saved_ref2', [c[0]+"2" for c in cmd_data_ref]], + ['saved_ref3', [c[0]+"3" for c in cmd_data_ref]], + + ['saved_ref_other', [c[0] for c in cmd_data_ref_other]], + + ['saved_ref_conv_in1', [c[0]+"1" for c in cmd_data_conv_in]], + ['saved_ref_conv_in2', [c[0]+"2" for c in cmd_data_conv_in]], + ['saved_ref_conv_in3', [c[0]+"3" for c in cmd_data_conv_in]], + + ['saved_ref_conv_out1', [c[0]+"1" for c in cmd_data_conv_out]], + ['saved_ref_conv_out2', [c[0]+"2" for c in cmd_data_conv_out]], + ['saved_ref_conv_out3', [c[0]+"3" for c in cmd_data_conv_out]], ]) opts_data = { @@ -350,8 +400,6 @@ else: send_delay = 0 os.environ["MMGEN_DISABLE_HOLD_PROTECT"] = "1" -if opt.debug: opt.verbose = True - if opt.exact_output: def msg(s): pass vmsg = vmsg_r = msg_r = msg @@ -378,15 +426,22 @@ def errmsg_r(s): stderr_save.write(s) if opt.list_cmds: fs = " {:<{w}} - {}" - Msg("Available commands:") + Msg("AVAILABLE COMMANDS:") w = max([len(i) for i in cmd_data]) for cmd in cmd_data: Msg(fs.format(cmd,cmd_data[cmd][1],w=w)) - Msg("\nAvailable metacommands:") + w = max([len(i) for i in meta_cmds]) + Msg("\nAVAILABLE METACOMMANDS:") for cmd in meta_cmds: - Msg(fs.format(cmd," + ".join(meta_cmds[cmd][1]),w=w)) - Msg("\nAvailable utilities:") + Msg(fs.format(cmd," ".join(meta_cmds[cmd]),w=w)) + + w = max([len(i) for i in cmd_groups.keys()]) + Msg("\nAVAILABLE COMMAND GROUPS:") + for g in cmd_groups.keys(): + Msg(fs.format(g," ".join(cmd_groups[g]),w=w)) + + Msg("\nAVAILABLE UTILITIES:") w = max([len(i) for i in utils]) for cmd in sorted(utils): Msg(fs.format(cmd,utils[cmd],w=w)) @@ -428,7 +483,8 @@ def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False): else: if t == '': if not nonl: vmsg("") - else: ret = my_send(p,t,delay,s) + else: + my_send(p,t,delay,s) return ret def get_file_with_ext(ext,mydir,delete=True): @@ -502,28 +558,32 @@ class MMGenExpect(object): vmsg("EOT") my_expect(self.p,"ENTER to continue: ",'\n') - def passphrase_new(self,what,passphrase): - my_expect(self.p,("Enter passphrase for %s: " % what), passphrase+"\n") + def passphrase_new(self,desc,passphrase): + my_expect(self.p,("Enter passphrase for %s: " % desc), passphrase+"\n") my_expect(self.p,"Repeat passphrase: ", passphrase+"\n") - def passphrase(self,what,passphrase,pwtype=""): + def passphrase(self,desc,passphrase,pwtype=""): if pwtype: pwtype += " " - my_expect(self.p,("Enter %spassphrase for %s.*?: " % (pwtype,what)), + my_expect(self.p,("Enter %spassphrase for %s.*?: " % (pwtype,desc)), passphrase+"\n",regex=True) - def hash_preset(self,what,preset=''): - my_expect(self.p,("Enter hash preset for %s," % what)) + def hash_preset(self,desc,preset=''): + my_expect(self.p,("Enter hash preset for %s," % desc)) my_expect(self.p,("or hit ENTER .*?:"), str(preset)+"\n",regex=True) - def written_to_file(self,what,overwrite_unlikely=False,query="Overwrite? "): - s1 = "%s written to file " % what + def written_to_file(self,desc,overwrite_unlikely=False,query="Overwrite? ",oo=False): + s1 = "%s written to file " % desc s2 = query + "Type uppercase 'YES' to confirm: " ret = my_expect(self.p,s1 if overwrite_unlikely else [s1,s2]) if ret == 1: my_send(self.p,"YES\n") - ret = my_expect(self.p,s1) + if oo: + outfile = self.expect_getend("Overwriting file '").rstrip("'") + return outfile + else: + ret = my_expect(self.p,s1) outfile = self.p.readline().strip().strip("'") - vmsg("%s file: %s" % (what,cyan(outfile.replace("'","")))) + vmsg("%s file: %s" % (desc,cyan(outfile.replace("'","")))) return outfile def no_overwrite(self): @@ -679,15 +739,15 @@ def check_needs_rerun(ts,cmd,build=False,root=True,force_delete=False,dpy=False) return rerun -def refcheck(what,chk,refchk): - vmsg("Comparing %s '%s' to stored reference" % (what,chk)) +def refcheck(desc,chk,refchk): + vmsg("Comparing %s '%s' to stored reference" % (desc,chk)) if chk == refchk: ok() else: if not opt.verbose: errmsg("") errmsg(red(""" Fatal error - %s '%s' does not match reference value '%s'. Aborting test -""".strip() % (what,chk,refchk))) +""".strip() % (desc,chk,refchk))) sys.exit(3) def check_deps(cmds): @@ -711,7 +771,7 @@ def check_deps(cmds): c = rebuild_list[cmd] m = "Rebuild" if (c[0] and c[1]) else "Build" if c[0] else "OK" msg("cmd {:<{w}} {}".format(cmd+":", m, w=w)) -# msgrepr(cmd,c) +# mmsg(cmd,c) def clean(dirs=[]): @@ -738,7 +798,7 @@ class MMGenTestSuite(object): def get_num_exts_for_cmd(self,cmd,dpy=False): # dpy ignored here num = str(cmd_data[cmd][0]) dgl = cfgs[num]['dep_generators'] -# msgrepr(num,cmd,dgl) +# mmsg(num,cmd,dgl) if cmd in dgl.values(): exts = [k for k in dgl if dgl[k] == cmd] return (num,exts) @@ -801,9 +861,9 @@ class MMGenTestSuite(object): t.usr_rand(10) t.passphrase_new("new MMGen wallet",cfg['wpasswd']) seed_id = t.written_to_file("Wallet").split("-")[0].split("/")[-1] - refcheck("seed id",seed_id,cfg['seed_id']) + refcheck("seed ID",seed_id,cfg['seed_id']) - refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen + refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen def passchg(self,name,walletfile): @@ -850,7 +910,7 @@ class MMGenTestSuite(object): d = " (%s-bit seed)" % cfg['seed_len'] self.addrgen(name,walletfile,check_ref=True) - refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen + refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen def addrimport(self,name,addrfile): outfile = os.path.join(cfg['tmpdir'],"addrfile_w_comments") @@ -1014,11 +1074,11 @@ class MMGenTestSuite(object): t.written_to_file("Data",query="") ok() - def addrgen_seed(self,name,walletfile,foo,what="seed data",arg="-s"): + def addrgen_seed(self,name,walletfile,foo,desc="seed data",arg="-s"): t = MMGenExpect(name,"mmgen-addrgen", [arg,"-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) t.license() - t.expect_getend("Valid %s for seed ID " % what) + t.expect_getend("Valid %s for seed ID " % desc) vmsg("Comparing generated checksum with checksum from previous address file") chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) verify_checksum_or_exit(get_addrfile_checksum(),chk) @@ -1026,14 +1086,14 @@ class MMGenTestSuite(object): ok() def addrgen_mnemonic(self,name,walletfile,foo): - self.addrgen_seed(name,walletfile,foo,what="mnemonic",arg="-m") + self.addrgen_seed(name,walletfile,foo,desc="mnemonic",arg="-m") def addrgen_incog(self,name,walletfile,foo,args=["-g"]): t = MMGenExpect(name,"mmgen-addrgen",args+["-d", cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) t.license() t.expect_getend("Incog ID: ") - t.passphrase("MMGen incognito wallet \w{8}", cfg['wpasswd']) + t.passphrase("incognito wallet data \w{8}", cfg['wpasswd']) t.hash_preset("incog wallet",'1') vmsg("Comparing generated checksum with checksum from address file") chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) @@ -1068,7 +1128,7 @@ class MMGenTestSuite(object): def refkeyaddrgen(self,name,walletfile): self.keyaddrgen(name,walletfile,check_ref=True) - refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen + refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen def txsign_keyaddr(self,name,keyaddr_file,txfile): t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],"-M",keyaddr_file,txfile]) @@ -1136,9 +1196,9 @@ class MMGenTestSuite(object): t.license() t.tx_view() - for cnum,what,app in ('1',"incognito"," incognito"),('3',"MMGen",""): - t.expect_getend("Getting %s wallet data from file " % what) - t.passphrase("MMGen%s wallet"%app,cfgs[cnum]['wpasswd']) + for cnum,desc,app in ('1',"incognito","incognito"),('3',"MMGen","MMGen"): + t.expect_getend("Getting %s wallet data from file " % desc) + t.passphrase("%s wallet"%app,cfgs[cnum]['wpasswd']) if cnum == '1': t.hash_preset("incog wallet",'1') @@ -1155,7 +1215,7 @@ class MMGenTestSuite(object): infn = get_tmpfile_fn(cfg,tmp_fn) t = MMGenExpect(name,"mmgen-tool",["-d",cfg['tmpdir'],"encrypt",infn]) t.hash_preset("user data",'1') - t.passphrase_new("user data",cfg['tool_enc_passwd']) + t.passphrase_new("user data",tool_enc_passwd) t.written_to_file("Encrypted data") ok() # Generate the reference mmenc file @@ -1168,7 +1228,7 @@ class MMGenTestSuite(object): of = name + ".out" t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir'],"decrypt",f2,"outfile="+of,"hash_preset=1"]) - t.passphrase("user data",cfg['tool_enc_passwd']) + t.passphrase("user data",tool_enc_passwd) t.written_to_file("Decrypted data") d1 = read_from_file(f1) d2 = read_from_file(get_tmpfile_fn(cfg,of)) @@ -1182,7 +1242,127 @@ class MMGenTestSuite(object): o = t.expect_getend("Incog data for ID \w{8} found at offset ",regex=True) cmp_or_die(hincog_offset,int(o)) + def walletconv_out(self,name,desc,out_fmt="w",uopts=[],uopts_chk=[],pw=False): + opts = ["-d",cfg['tmpdir'],"-r10","-p1","-o",out_fmt] + uopts + infile = os.path.join(ref_dir,cfg['seed_id']+".mmwords") + d = "(convert)" + t = MMGenExpect(name,"mmgen-walletconv",opts+[infile],extra_desc=d) + t.license() + if pw: + t.passphrase_new("new "+desc,cfg['wpasswd']) + t.usr_rand(10) + if desc == "hidden incognito data": + ret = t.expect(["Create? (Y/n): ","'YES' to confirm: "],"YES\n") + if ret == 0: + t.expect("Enter file size: ","1234\n") + wf = t.written_to_file(desc[0].upper()+desc[1:],oo=True) + ok() + + d = "(check)" + if desc == "hidden incognito data": + self.keygen_chksum_chk_hincog(name,cfg['seed_id'],uopts_chk) +# elif pw: +# self.walletchk_chksum_chk(name,wf,cfg['seed_id'],uopts=uopts_chk) + else: + self.keygen_chksum_chk(name,wf,cfg['seed_id'],pw=pw) + + def walletconv_in(self,name,infile,desc,uopts=[],pw=False,oo=False): + opts = ["-d",cfg['tmpdir'],"-o","words","-r10"] + if_arg = [infile] if infile else [] + d = "(convert)" + t = MMGenExpect(name,"mmgen-walletconv",opts+uopts+if_arg,extra_desc=d) + t.license() + if desc == "brainwallet": + t.expect("Enter brainwallet: ",ref_wallet_brainpass+"\n") + if pw: + t.passphrase(desc,cfg['wpasswd']) + if name[:19] == "ref_hincog_conv_old": + t.expect("Is the seed ID correct? (Y/n): ","\n") + else: + t.expect(["Passphrase is OK"," are correct"]) + # Output + wf = t.written_to_file("Mnemonic data",oo=oo) + t.close() + ok() + # back check of result + d = "(check)" + self.keygen_chksum_chk(name,wf,cfg['seed_id']) + # Saved reference file tests + def ref_wallet_conv(self,name): + wf = os.path.join(ref_dir,cfg['ref_wallet']) + self.walletconv_in(name,wf,"MMGen wallet",pw=True,oo=True) + + def ref_mn_conv(self,name,ext="mmwords",desc="Mnemonic data"): + wf = os.path.join(ref_dir,cfg['seed_id']+"."+ext) + self.walletconv_in(name,wf,desc,oo=True) + + def ref_seed_conv(self,name): + self.ref_mn_conv(name,ext="mmseed",desc="Seed data") + + def ref_brain_conv(self,name): + uopts = ["-i","b","-p","1","-l",str(cfg['seed_len'])] + self.walletconv_in(name,None,"brainwallet",uopts,oo=True) + + def ref_incog_conv(self,name,wfk="ic_wallet",in_fmt="i",desc="incognito data"): + uopts = ["-i",in_fmt,"-p","1","-l",str(cfg['seed_len'])] + wf = os.path.join(ref_dir,cfg[wfk]) + self.walletconv_in(name,wf,desc,uopts,oo=True,pw=True) + + def ref_incox_conv(self,name): + self.ref_incog_conv(name,in_fmt="xi",wfk="ic_wallet_hex",desc="hex incognito data") + + def ref_hincog_conv(self,name,wfk='hic_wallet',add_uopts=[]): + ic_f = os.path.join(ref_dir,cfg[wfk]) + uopts = ["-i","hi","-p","1","-l",str(cfg['seed_len'])] + add_uopts + hi_opt = ["-H","%s,%s" % (ic_f,ref_wallet_incog_offset)] + self.walletconv_in(name,None,"hidden incognito data",uopts+hi_opt,oo=True,pw=True) + + def ref_hincog_conv_old(self,name): + self.ref_hincog_conv(name,wfk='hic_wallet_old',add_uopts=["-O"]) + + def ref_wallet_conv_out(self,name): + self.walletconv_out(name,"MMGen wallet","w",pw=True) + + def ref_mn_conv_out(self,name): + self.walletconv_out(name,"mnemonic data","mn") + + def ref_seed_conv_out(self,name): + self.walletconv_out(name,"seed data","seed") + + def ref_incog_conv_out(self,name): + self.walletconv_out(name,"incognito data",out_fmt="i",pw=True) + + def ref_incox_conv_out(self,name): + self.walletconv_out(name,"hex incognito data",out_fmt="xi",pw=True) + + def ref_hincog_conv_out(self,name,extra_uopts=[]): + ic_f = os.path.join(cfg['tmpdir'],"rand.data") + hi_parms = "%s,%s" % (ic_f,ref_wallet_incog_offset) + hi_parms_legacy = "%s,%s,%s"%(ic_f,ref_wallet_incog_offset,cfg['seed_len']) + self.walletconv_out(name, + "hidden incognito data", "hi", + uopts=["-J",hi_parms] + extra_uopts, + uopts_chk=["-G",hi_parms_legacy], + pw=True + ) + + ref_wallet_conv1 = ref_wallet_conv2 = ref_wallet_conv3 = ref_wallet_conv + ref_mn_conv1 = ref_mn_conv2 = ref_mn_conv3 = ref_mn_conv + ref_seed_conv1 = ref_seed_conv2 = ref_seed_conv3 = ref_seed_conv + ref_brain_conv1 = ref_brain_conv2 = ref_brain_conv3 = ref_brain_conv + ref_incog_conv1 = ref_incog_conv2 = ref_incog_conv3 = ref_incog_conv + ref_incox_conv1 = ref_incox_conv2 = ref_incox_conv3 = ref_incox_conv + ref_hincog_conv1 = ref_hincog_conv2 = ref_hincog_conv3 = ref_hincog_conv + ref_hincog_conv_old1 = ref_hincog_conv_old2 = ref_hincog_conv_old3 = ref_hincog_conv_old + + ref_wallet_conv_out1 = ref_wallet_conv_out2 = ref_wallet_conv_out3 = ref_wallet_conv_out + ref_mn_conv_out1 = ref_mn_conv_out2 = ref_mn_conv_out3 = ref_mn_conv_out + ref_seed_conv_out1 = ref_seed_conv_out2 = ref_seed_conv_out3 = ref_seed_conv_out + ref_incog_conv_out1 = ref_incog_conv_out2 = ref_incog_conv_out3 = ref_incog_conv_out + ref_incox_conv_out1 = ref_incox_conv_out2 = ref_incox_conv_out3 = ref_incox_conv_out + ref_hincog_conv_out1 = ref_hincog_conv_out2 = ref_hincog_conv_out3 = ref_hincog_conv_out + def ref_wallet_chk(self,name): wf = os.path.join(ref_dir,cfg['ref_wallet']) self.walletchk(name,wf) @@ -1191,8 +1371,8 @@ class MMGenTestSuite(object): def ref_seed_chk(self,name,ext=g.seed_ext): wf = os.path.join(ref_dir,"%s.%s" % (cfg['seed_id'],ext)) - what = "seed data" if ext == g.seed_ext else "mnemonic" - self.keygen_chksum_chk(name,wf,cfg['seed_id'],what) + desc = "seed data" if ext == g.seed_ext else "mnemonic" + self.keygen_chksum_chk(name,wf,cfg['seed_id']) ref_seed_chk1 = ref_seed_chk2 = ref_seed_chk3 = ref_seed_chk @@ -1203,37 +1383,60 @@ class MMGenTestSuite(object): def ref_brain_chk(self,name,bw_file=ref_bw_file): wf = os.path.join(ref_dir,bw_file) arg = "-b%s,%s" % (cfg['seed_len'],ref_bw_hash_preset) - self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],"brainwallet",[arg]) + self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],[arg]) - def keygen_chksum_chk(self,name,wf,seed_id,what,args=[]): - t = MMGenExpect(name,"mmgen-keygen", ["-q","-A"]+args+[wf,"1"]) - chk = t.expect_getend("Valid %s for seed ID " % what) + def keygen_chksum_chk_hincog(self,name,seed_id,hincog_parm): + t = MMGenExpect(name,"mmgen-keygen", ["-p1","-q","-S","-A"]+hincog_parm+["1"],extra_desc="(check)") + t.passphrase("",cfg['wpasswd']) + t.expect("Encrypt key list? (y/N): ","\n") + t.expect("any printable ASCII symbol.\r\n") + chk = t.readline()[:8] + vmsg("Seed ID: %s" % cyan(chk)) + cmp_or_die(seed_id,chk) + + def keygen_chksum_chk(self,name,wf,seed_id,args=[],pw=False): + hp_arg = ["-p1"] if pw else [] + t = MMGenExpect(name,"mmgen-keygen", ["-q","-S","-A"]+args+hp_arg+[wf,"1"],extra_desc="(check)") + if pw: + t.passphrase("",cfg['wpasswd']) + t.expect("Encrypt key list? (y/N): ","\n") + t.expect("any printable ASCII symbol.\r\n") + chk = t.readline()[:8] + vmsg("Seed ID: %s" % cyan(chk)) + cmp_or_die(seed_id,chk) + + # Use this for encrypted wallets instead of keygen_chksum_chk() + def walletchk_chksum_chk(self,name,wf,seed_id,uopts=[]): + t = MMGenExpect(name,"mmgen-walletchk",["-v", wf]+uopts, + extra_desc="(check)") + t.passphrase("",cfg['wpasswd']) + chk = t.expect_getend("Seed ID checksum OK (")[:8] t.close() cmp_or_die(seed_id,chk) ref_brain_chk1 = ref_brain_chk2 = ref_brain_chk3 = ref_brain_chk - def ref_brain_chk3_spc(self,name): + def ref_brain_chk_spc3(self,name): self.ref_brain_chk(name,bw_file=ref_bw_file_spc) - def ref_incog_chk(self,name): - for wtype,desc,earg in ('ic_wallet','',[]), \ - ('ic_wallet_old','(old format)',["-o"]): + def ref_hincog_chk(self,name): + for wtype,desc,earg in ('hic_wallet','',[]), \ + ('hic_wallet_old','(old format)',["-o"]): ic_arg = "%s,%s,%s" % ( os.path.join(ref_dir,cfg[wtype]), ref_wallet_incog_offset,cfg['seed_len'] ) t = MMGenExpect(name,"mmgen-keygen", ["-q","-A"]+earg+["-G"]+[ic_arg]+['1'],extra_desc=desc) - t.passphrase("MMGen incognito wallet",cfg['wpasswd']) + t.passphrase("incognito wallet",cfg['wpasswd']) t.hash_preset("incog wallet","1") - if wtype == 'ic_wallet_old': + if wtype == 'hic_wallet_old': t.expect("Is the seed ID correct? (Y/n): ","\n") chk = t.expect_getend("Valid incog data for seed ID ") t.close() cmp_or_die(cfg['seed_id'],chk) - ref_incog_chk1 = ref_incog_chk2 = ref_incog_chk3 = ref_incog_chk + ref_hincog_chk1 = ref_hincog_chk2 = ref_hincog_chk3 = ref_hincog_chk def ref_addrfile_chk(self,name,ftype="addr"): wf = os.path.join(ref_dir,cfg['ref_'+ftype+'file']) @@ -1261,11 +1464,11 @@ class MMGenTestSuite(object): f = os.path.join(ref_dir,ref_enc_fn) t = MMGenExpect(name,"mmgen-tool", ["-q","decrypt",f,"outfile=-","hash_preset=1"]) - t.passphrase("user data",cfg['tool_enc_passwd']) + t.passphrase("user data",tool_enc_passwd) t.readline() import re o = re.sub('\r\n','\n',t.read()) - cmp_or_die(cfg['sample_text'],o) + cmp_or_die(sample_text,o) # main() if opt.pause: @@ -1288,8 +1491,11 @@ try: globals()[arg](cmd_args[cmd_args.index(arg)+1:]) sys.exit() elif arg in meta_cmds: - for cmd in meta_cmds[arg][1]: - check_needs_rerun(ts,cmd,build=True,force_delete=True) + for cmd in meta_cmds[arg]: + check_needs_rerun(ts,cmd,build=True) + elif arg in cmd_groups.keys(): + for cmd in cmd_groups[arg]: + check_needs_rerun(ts,cmd,build=True) elif arg in cmd_data: check_needs_rerun(ts,arg,build=True) else: diff --git a/test/tooltest.py b/test/tooltest.py index 1a0abe66..ff1dbae2 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -9,7 +9,7 @@ os.chdir(os.path.join(pn,os.pardir)) sys.path.__setitem__(0,os.path.abspath(os.curdir)) import mmgen.opt as opt -from mmgen.util import msg,msg_r,vmsg,vmsg_r,Msg,msgrepr, msgrepr_exit +from mmgen.util import msg,msg_r,vmsg,vmsg_r,Msg,mmsg,mdie from collections import OrderedDict cmd_data = OrderedDict([ @@ -94,8 +94,6 @@ cmd_args = opt.opts.init(opts_data,add_opts=["exact_output"]) if opt.system: sys.path.pop(0) -if opt.debug: opt.verbose = True - if opt.list_cmds: fs = " {:<{w}} - {}" Msg("Available commands:")