diff --git a/INSTALL b/INSTALL index 756d492f..02aab569 100644 --- a/INSTALL +++ b/INSTALL @@ -1,4 +1,3 @@ - MMGen is written in Pure Python and runs on MS Windows and Linux. Instructions for installation and use reside on MMGen's Github wiki: diff --git a/mmgen/addr.py b/mmgen/addr.py index ff8911cf..2f3a03ee 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -176,7 +176,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False): def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): - if buf: lines = remove_comments(buf.split("\n")) + if buf: lines = remove_comments(buf.splitlines()) # DOS-safe else: lines = get_lines_from_file(fn,"address data",trim_comments=True) try: @@ -190,7 +190,7 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): elif cbrace != '}': errmsg = "'%s': invalid last line" % cbrace elif not is_mmgen_seed_id(sid): - errmsg = "'%s': invalid seed ID" % sid + errmsg = "'%s': invalid Seed ID" % sid else: ret = _parse_addrfile_body(lines[1:-1],has_keys) if type(ret) == list: return sid,ret @@ -203,14 +203,9 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): return False -def _parse_keyaddr_file(infile): - d = get_data_from_file(infile,"{pnm} key-address file data".format(pnm=pnm)) - enc_ext = get_extension(infile) == g.mmenc_ext - if enc_ext or not is_utf8(d): - m = "Decrypting" if enc_ext else "Attempting to decrypt" - msg("%s key-address file %s" % (m,infile)) - from crypto import mmgen_decrypt_retry - d = mmgen_decrypt_retry(d,"key-address file") +def _parse_keyaddr_file(fn): + from mmgen.crypto import mmgen_decrypt_file_maybe + d = mmgen_decrypt_file_maybe(fn,"key-address file") return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False) diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 53ce6e18..b6bae59d 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -52,7 +52,7 @@ keystrokes will also be used as a source of randomness. # Try again? (Y)es, (n)o, (m)ore information: # """.strip(), # 'confirm_seed_id': """ -# If the seed ID above is correct but you're seeing this message, then you need +# 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(), } @@ -75,7 +75,7 @@ def decrypt_seed(enc_seed, key, seed_id, key_id): chk2 = make_chksum_8(dec_seed) if seed_id: - if compare_chksums(seed_id,"seed ID",chk2,"decrypted seed"): + if compare_chksums(seed_id,"Seed ID",chk2,"decrypted seed"): qmsg("Passphrase is OK") else: if not opt.debug: @@ -246,6 +246,15 @@ def mmgen_decrypt(data,desc="data",hash_preset=""): msg("Incorrect passphrase or hash preset") return False +def mmgen_decrypt_file_maybe(fn,desc): + d = get_data_from_file(fn,"{} data".format(desc),binary=True) + have_enc_ext = get_extension(fn) == g.mmenc_ext + if have_enc_ext or not is_ascii(d): + m = ("Attempting to decrypt","Decrypting")[int(have_enc_ext)] + msg("%s %s %s" % (m,desc,fn)) + d = mmgen_decrypt_retry(d,desc) + return d + def mmgen_decrypt_retry(d,desc="data"): while True: d_dec = mmgen_decrypt(d,desc) diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 0188ae03..fa9481de 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -92,7 +92,7 @@ FMT CODES: {f} """.format( n=note1, - f="\n ".join(SeedSource.format_fmt_codes().split("\n")), + f="\n ".join(SeedSource.format_fmt_codes().splitlines()), o=opt.opts ) } diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index bdd69b04..562bbcc6 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -76,7 +76,7 @@ for e in ai.addrdata: msg("%s: invalid address" % e.addr) sys.exit(2) -m = (" from seed ID %s" % ai.seed_id) if ai.seed_id else "" +m = (" from Seed ID %s" % ai.seed_id) if ai.seed_id else "" qmsg("OK. %s addresses%s" % (ai.num_addrs,m)) g.http_timeout = 3600 diff --git a/mmgen/main_pywallet.py b/mmgen/main_pywallet.py index e7ce9f59..fc853975 100755 --- a/mmgen/main_pywallet.py +++ b/mmgen/main_pywallet.py @@ -61,7 +61,7 @@ import math import mmgen.globalvars as g import mmgen.opt as opt -from mmgen.util import msg,mdie,mmsg,write_data_to_file +from mmgen.util import msg max_version = 60000 addrtype = 0 @@ -1664,7 +1664,7 @@ len_arg = "%s" % 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 +from mmgen.util import make_chksum_8,write_data_to_file wallet_id = make_chksum_8(str(sorted(wallet_addrs))) data = "\n".join(data) + "\n" diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index b6ebcd23..a8f2e681 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -32,6 +32,7 @@ opts_data = { 'options': """ -d, --outdir= d Specify an alternate directory 'd' for output -h, --help Print this help message +-P, --passwd-file= f Get passphrase from file 'f'. -q, --quiet Produce quieter output -r, --usr-randchars=n Get 'n' characters of additional randomness from user (min={g.min_urandchars}, max={g.max_urandchars}) @@ -45,7 +46,12 @@ command """.format(tool.cmd_help,g.prog_name) } -cmd_args = opt.opts.init(opts_data,add_opts=["no_keyconv"]) +cmd_args = opt.opts.init(opts_data, + add_opts=[ + "no_keyconv", + "hidden_incog_input_params", + "in_fmt" + ]) if len(cmd_args) < 1: opt.opts.usage() diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 09764bf1..5c847606 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -155,7 +155,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen from copy import deepcopy from mmgen.term import get_terminal_size - write_to_file_msg = "" + written_to_file_msg = "" msg("") while True: @@ -215,8 +215,8 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age) for n,i in enumerate(unsp)] - msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg) - write_to_file_msg = "" + msg("\n".join(out) +"\n\n" + written_to_file_msg + options_msg) + written_to_file_msg = "" skip_prompt = False @@ -241,8 +241,8 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen elif reply == 'p': d = format_unspent_outputs_for_printing(unsp,sort_info,total) of = "listunspent[%s].out" % ",".join(sort_info) - write_to_file(of, d, "",False,False) - write_to_file_msg = "Data written to '%s'\n\n" % of + write_data_to_file(of,d,"unspent outputs listing") + written_to_file_msg = "Data written to '%s'\n\n" % of elif reply == 'v': do_pager("\n".join(out)) continue @@ -390,7 +390,8 @@ if g.bogus_wallet_data: # for debugging purposes only us = eval(get_data_from_file(g.bogus_wallet_data)) else: us = c.listunspent() -# write_to_file("bogus_unspent.json", repr(us)); sys.exit() +# write_data_to_file("bogus_unspent.json", repr(us), "bogus unspent data") +# sys.exit() if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2) for o in us: @@ -470,10 +471,7 @@ b2m_map = make_b2m_map(sel_unspent,tx_out,ail_w,ail_f) prompt_and_view_tx_data(c,"View decoded transaction?", sel_unspent,tx_hex,b2m_map,comment,metadata) -if keypress_confirm("Save transaction?",default_yes=False): - outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) - data = make_tx_data("{} {} {}".format(*metadata), - tx_hex,sel_unspent,b2m_map,comment) - write_to_file(outfile,data,"transaction",False,True) -else: - msg("Transaction not saved") +outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) +data = make_tx_data("{} {} {}".format(*metadata), + tx_hex,sel_unspent,b2m_map,comment) +write_data_to_file(outfile,data,"transaction",ask_write_default_yes=False) diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 1bf53dbe..3623f247 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -63,9 +63,7 @@ if keypress_confirm("Edit transaction comment?"): comment = get_tx_comment_from_user(comment) data = make_tx_data("{} {} {}".format(*metadata), tx_hex, inputs_data, b2m_map, comment) - w = "signed transaction with edited comment" - outfile = infile - write_to_file(outfile,data,w,False,True,True) + write_data_to_file(infile,data,"signed transaction with edited comment") warn = "Once this transaction is sent, there's no taking it back!" action = "broadcast this transaction to the network" @@ -86,4 +84,4 @@ except: msg("Transaction sent: %s" % tx_id) of = "tx_{}[{}].txid".format(*metadata[:2]) -write_to_file(of, tx_id+"\n","transaction ID",True,True) +write_data_to_file(of, tx_id+"\n","transaction ID",ask_overwrite=True) diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 25130fc3..2c4d9faf 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -101,7 +101,7 @@ Seed data supplied in files must have the following extensions: FMT CODES: {f} """.format( - f="\n ".join(SeedSource.format_fmt_codes().split("\n")), + f="\n ".join(SeedSource.format_fmt_codes().splitlines()), g=g,pnm=pnm,pnl=pnl ) } @@ -126,11 +126,11 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds): if infiles: ss = SeedSource(infiles.pop(0),ignore_in_fmt=True) elif opt.in_fmt: - qmsg("Need seed data for seed ID %s" % seed_id) + qmsg("Need seed data for Seed ID %s" % seed_id) ss = SeedSource() - msg("User input produced seed ID %s" % make_chksum_8(seed)) + msg("User input produced Seed ID %s" % make_chksum_8(seed)) else: - msg("ERROR: No seed source found for seed ID: %s" % seed_id) + msg("ERROR: No seed source found for Seed ID: %s" % seed_id) sys.exit(2) saved_seeds[ss.seed.sid] = ss.seed.data @@ -229,7 +229,7 @@ for the following non-{pnm} address{suf}:\n {l}""".format( def parse_mmgen_keyaddr_file(): from mmgen.addr import AddrInfo ai = AddrInfo(opt.mmgen_keys_from_file,has_keys=True) - vmsg("Found %s wif key%s for seed ID %s" % + vmsg("Found %s wif key%s for Seed ID %s" % (ai.num_addrs, suf(ai.num_addrs,"k"), ai.seed_id)) # idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif) return dict( @@ -238,14 +238,10 @@ def parse_mmgen_keyaddr_file(): def parse_keylist(from_file): fn = opt.keys_from_file - d = get_data_from_file(fn,"non-{pnm} keylist".format(pnm=pnm)) - enc_ext = get_extension(fn) == g.mmenc_ext - if enc_ext or not is_utf8(d): - if not enc_ext: qmsg("Keylist file appears to be encrypted") - from crypto import mmgen_decrypt_retry - d = mmgen_decrypt_retry(d,"encrypted keylist") - # Check for duplication with key-address file - keys_all = set(remove_comments(d.split("\n"))) + from mmgen.crypto import mmgen_decrypt_file_maybe + dec = mmgen_decrypt_file_maybe(fn,"non-{} keylist file".format(pnm)) + # Remove possible dups from key-address file + keys_all = set(remove_comments(dec.splitlines())) # DOS-safe d = from_file['mmdata'] kawifs = [d[k][1] for k in d.keys()] keys = [k for k in keys_all if k not in kawifs] @@ -359,7 +355,7 @@ for tx_num,tx_file in enumerate(tx_files,1): extra_sids = set(saved_seeds.keys()) - sids if extra_sids: - msg("Unused seed ID%s: %s" % + msg("Unused Seed ID%s: %s" % (suf(extra_sids,"k")," ".join(extra_sids))) # Begin signing @@ -378,13 +374,16 @@ for tx_num,tx_file in enumerate(tx_files,1): if keypress_confirm("Edit transaction comment?"): comment = get_tx_comment_from_user(comment) outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext) - data = make_tx_data("{} {} {t}".format(*metadata[:2], t=make_timestamp()), - sig_tx['hex'], inputs_data, b2m_map, comment) - w = "signed transaction{}".format(tx_num_str) - if keypress_confirm("Save signed transaction?",default_yes=False): - write_to_file(outfile,data,w,(not opt.quiet),True,False) - else: - msg("Signed transaction not saved") + data = make_tx_data( + "{} {} {t}".format(*metadata[:2], + t=make_timestamp()), + sig_tx['hex'], inputs_data, b2m_map, comment + ) + write_data_to_file( + outfile,data, + "signed transaction{}".format(tx_num_str), + ask_write_prompt="Save signed transaction?" + ) else: msg_r("failed\nSome keys were missing. ") msg("Transaction %scould not be signed." % tx_num_str) diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 026286cb..4dabefea 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -103,7 +103,7 @@ opts_data = { FMT CODES: {f} """.format( - f="\n ".join(SeedSource.format_fmt_codes().split("\n")), + f="\n ".join(SeedSource.format_fmt_codes().splitlines()), pw_note=pw_note, bw_note=("","\n\n" + bw_note)[int(bool(bw_note))] ) diff --git a/mmgen/mnemonic.py b/mmgen/mnemonic.py deleted file mode 100755 index af6230c4..00000000 --- a/mmgen/mnemonic.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/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 . - -""" -mnemonic.py: Mnemonic routines for the MMGen suite -""" - -import sys -from binascii import hexlify -from mmgen.util import msg,msg_r,make_chksum_8,Vmsg,Msg -from mmgen.crypto import get_random -import mmgen.globalvars as g -import mmgen.opt as opt - -wl_checksums = { - "electrum": '5ca31424', - "tirosh": '1a5faeff' -} - -# These are the only base-1626 specific configs: -mn_base = 1626 -def mn2hex_pad(mn): return len(mn) * 8 / 3 -def hex2mn_pad(hexnum): return len(hexnum) * 3 / 8 - -# Universal base-conversion routines: -def baseNtohex(base,words,wordlist,pad=0): - deconv = \ - [wordlist.index(words[::-1][i])*(base**i) for i in range(len(words))] - ret = ("{:0%sx}" % pad).format(sum(deconv)) - return "%s%s" % (('0' if len(ret) % 2 else ''), ret) - -def hextobaseN(base,hexnum,wordlist,pad=0): - num,ret = int(hexnum,16),[] - while num: - ret.append(num % base) - num /= base - return [wordlist[n] for n in [0] * (pad-len(ret)) + ret[::-1]] - -def get_seed_from_mnemonic(mn,wl,silent=False,label=""): - - if len(mn) not in g.mn_lens: - msg("Invalid mnemonic (%i words). Allowed numbers of words: %s" % - (len(mn),", ".join([str(i) for i in g.mn_lens]))) - sys.exit(3) - - for n,w in enumerate(mn,1): - if w not in wl: - msg("Invalid mnemonic: word #%s is not in the wordlist" % n) - sys.exit(3) - - from binascii import unhexlify - hseed = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn)) - - rev = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed)) - if rev != mn: - msg("ERROR: mnemonic recomputed from seed not the same as original") - msg("Recomputed mnemonic:\n%s" % " ".join(rev)) - sys.exit(3) - - if not silent: - msg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(hseed))) - - return unhexlify(hseed) - - -def get_mnemonic_from_seed(seed, wl, label="", verbose=False): - - if len(seed)*8 not in g.seed_lens: - msg("%s: invalid seed length" % len(seed)) - sys.exit(3) - - from binascii import hexlify - - hseed = hexlify(seed) - mn = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed)) - - if verbose: - msg("Wordlist: %s" % label.capitalize()) - msg("Seed length: %s bits" % (len(seed) * 8)) - msg("Seed: %s" % hseed) - msg("mnemonic (%s words):\n%s" % (len(mn), " ".join(mn))) - - rev = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn)) - if rev != hseed: - msg("ERROR: seed recomputed from wordlist not the same as original seed!") - msg("Original seed: %s" % hseed) - msg("Recomputed seed: %s" % rev) - sys.exit(3) - - return mn - - -def check_wordlist(wl,label): - - Msg("Wordlist: %s" % label.capitalize()) - - from hashlib import sha256 - - Msg("Length: %i words" % len(wl)) - new_chksum = sha256(" ".join(wl)).hexdigest()[:8] - - if new_chksum != wl_checksums[label]: - Msg("ERROR: Checksum mismatch. Computed: %s, Saved: %s" % \ - (new_chksum,wl_checksums[label])) - sys.exit(3) - - Msg("Checksum: %s (matches)" % new_chksum) - - if (sorted(wl) == wl): - Msg("List is sorted") - else: - Msg("ERROR: List is not sorted!") - sys.exit(3) - -from mmgen.mn_electrum import words as el -from mmgen.mn_tirosh import words as tl -wordlists = sorted(wl_checksums) - -def get_wordlist(wordlist): - wordlist = wordlist.lower() - if wordlist not in wordlists: - Msg('"%s": invalid wordlist. Valid choices: %s' % - (wordlist,'"'+'" "'.join(wordlists)+'"')) - sys.exit(1) - return (el if wordlist == "electrum" else tl).strip().split("\n") - -def do_random_mn(nbytes,wordlist): - r = get_random(nbytes) - Vmsg("Seed: %s" % hexlify(r)) - for wlname in (wordlists if wordlist == "all" else [wordlist]): - wl = get_wordlist(wlname) - mn = get_mnemonic_from_seed(r,wl,wordlist) - Vmsg("%s wordlist mnemonic:" % (wlname.capitalize())) - Msg(" ".join(mn)) diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py index 925106f7..342f6adb 100755 --- a/mmgen/rpc/proxy.py +++ b/mmgen/rpc/proxy.py @@ -104,10 +104,8 @@ class AuthServiceProxy(object): 'Authorization' : self.__authhdr, 'Content-type' : 'application/json' }) except: - from mmgen.util import msg - import sys - msg("Unable to connect to bitcoind") - sys.exit(2) + from mmgen.util import die,red + die(1,red("Unable to connect to bitcoind")) httpresp = self.__conn.getresponse() if httpresp is None: diff --git a/mmgen/seed.py b/mmgen/seed.py index f15d500d..d6041138 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -56,6 +56,7 @@ class Seed(MMGenObject): class SeedSource(MMGenObject): desc = g.proj_name + " seed source" + file_mode = "text" stdin_ok = False ask_tty = True no_tty = False @@ -64,7 +65,8 @@ class SeedSource(MMGenObject): class SeedSourceData(MMGenObject): pass - def __new__(cls,fn=None,ss=None,ignore_in_fmt=False,passchg=False): + def __new__(cls,fn=None,ss=None,seed=None, + ignore_in_fmt=False,passchg=False): def die_on_opt_mismatch(opt,sstype): opt_sstype = cls.fmt_code_to_sstype(opt) @@ -104,12 +106,13 @@ class SeedSource(MMGenObject): 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) # default: Wallet - me.seed = Seed() + me.seed = Seed(seed_bin=seed or None) me.op = "new" return me - def __init__(self,fn=None,ss=None,ignore_in_fmt=False,passchg=False): + def __init__(self,fn=None,ss=None,seed=None, + ignore_in_fmt=False,passchg=False): self.ssdata = self.SeedSourceData() self.msg = {} @@ -132,12 +135,13 @@ class SeedSource(MMGenObject): self._deformat_retry() self._decrypt_retry() - m = (""," length %s" % self.seed.length)[int(self.seed.length != 256)] - qmsg("Valid %s for seed ID %s%s" % (self.desc,self.seed.sid,m)) + m = ("",", seed length %s" % self.seed.length)[int(self.seed.length != 256)] + qmsg("Valid %s for Seed ID %s%s" % (self.desc,self.seed.sid,m)) def _get_data(self): if hasattr(self,'infile'): - self.fmt_data = get_data_from_file(self.infile.name,self.desc) + self.fmt_data = get_data_from_file(self.infile.name,self.desc, + binary=self.file_mode=="binary") else: self.fmt_data = get_data_from_user(self.desc) @@ -204,12 +208,17 @@ class SeedSource(MMGenObject): ] + sorted(d)] return "\n".join(ret) + "\n" + def get_fmt_data(self): + self._format() + return self.fmt_data + def write_to_file(self): self._format() kwargs = { 'desc': self.desc, 'ask_tty': self.ask_tty, - 'no_tty': self.no_tty + 'no_tty': self.no_tty, + 'binary': self.file_mode == "binary" } write_data_to_file(self._filename(),self.fmt_data,**kwargs) @@ -358,43 +367,70 @@ class Mnemonic (SeedSourceUnenc): mn_base = 1626 wordlists = sorted(wl_checksums) - def _mn2hex_pad(self,mn): return len(mn) * 8 / 3 - def _hex2mn_pad(self,hexnum): return len(hexnum) * 3 / 8 + @staticmethod + def _mn2hex_pad(mn): return len(mn) * 8 / 3 - def _baseNtohex(self,base,words,wl,pad=0): + @staticmethod + def _hex2mn_pad(hexnum): return len(hexnum) * 3 / 8 + + @staticmethod + def baseNtohex(base,words,wl,pad=0): deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))] ret = ("{:0%sx}" % pad).format(sum(deconv)) return ('','0')[len(ret) % 2] + ret - def _hextobaseN(self,base,hexnum,wl,pad=0): + @staticmethod + def hextobaseN(base,hexnum,wl,pad=0): num,ret = int(hexnum,16),[] while num: ret.append(num % base) num /= base return [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]] - def _get_wordlist(self,wordlist=g.default_wordlist): - wordlist = wordlist.lower() - if wordlist not in self.wordlists: + @classmethod + def hex2mn(cls,hexnum,wordlist): + wl = cls.get_wordlist(wordlist) + return cls.hextobaseN(cls.mn_base,hexnum,wl,cls._hex2mn_pad(hexnum)) + + @classmethod + def mn2hex(cls,mn,wordlist): + wl = cls.get_wordlist(wordlist) + return cls.baseNtohex(cls.mn_base,mn,wl,cls._mn2hex_pad(mn)) + + @classmethod + def get_wordlist(cls,wordlist=None): + wordlist = wordlist or g.default_wordlist + if wordlist not in cls.wordlists: die(1,'"%s": invalid wordlist. Valid choices: %s' % - (wordlist,'"'+'" "'.join(self.wordlists)+'"')) + (wordlist,'"'+'" "'.join(cls.wordlists)+'"')) - if wordlist == "electrum": - from mmgen.mn_electrum import words - elif wordlist == "tirosh": - from mmgen.mn_tirosh import words + return __import__("mmgen.mn_"+wordlist,fromlist=["words"]).words.split() + + @classmethod + def check_wordlist(cls,wlname): + + wl = cls.get_wordlist(wlname) + Msg("Wordlist: %s\nLength: %i words" % (capfirst(wlname),len(wl))) + new_chksum = sha256(" ".join(wl)).hexdigest()[:8] + + if (sorted(wl) == wl): + Msg("List is sorted") else: - die(3,"Internal error: unknown wordlist") + die(3,"ERROR: List is not sorted!") - return words.strip().split("\n") + compare_chksums( + new_chksum,"generated checksum", + cls.wl_checksums[wlname],"saved checksum", + die_on_fail=True) + Msg("Checksum %s matches" % new_chksum) def _format(self): - wl = self._get_wordlist() + wl = self.get_wordlist() seed_hex = self.seed.hexdata - mn = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) + mn = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) - ret = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) + 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") @@ -405,7 +441,7 @@ class Mnemonic (SeedSourceUnenc): def _deformat(self): mn = self.fmt_data.split() - wl = self._get_wordlist() + wl = self.get_wordlist() if len(mn) not in g.mn_lens: msg("Invalid mnemonic (%i words). Allowed numbers of words: %s" % @@ -417,9 +453,9 @@ class Mnemonic (SeedSourceUnenc): 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)) + seed_hex = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) - ret = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) + ret = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) # Internal error, so just die compare_or_die(" ".join(ret),"recomputed mnemonic", @@ -580,7 +616,7 @@ class Wallet (SeedSourceEnc): return True - lines = self.fmt_data.rstrip().split("\n") + lines = self.fmt_data.splitlines() if not check_master_chksum(lines,self.desc): return False d = self.ssdata @@ -723,6 +759,7 @@ class Brainwallet (SeedSourceEnc): class IncogWallet (SeedSourceEnc): + file_mode = "binary" fmt_codes = "mmincog","incog","icg","i" desc = "incognito data" ext = "mmincog" @@ -742,7 +779,7 @@ Incorrect passphrase, hash preset, or maybe old-format incog wallet. Try again? (Y)es, (n)o, (m)ore information: """.strip(), 'confirm_seed_id': """ -If the seed ID above is correct but you're seeing this message, then you need +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" @@ -841,7 +878,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. return False def _verify_seed_oldfmt(self,seed): - m = "Seed ID: %s. Is the seed ID correct?" % make_chksum_8(seed) + m = "Seed ID: %s. Is the Seed ID correct?" % make_chksum_8(seed) if keypress_confirm(m, True): return seed else: @@ -878,6 +915,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. class IncogWalletHex (IncogWallet): + file_mode = "text" desc = "hex incognito data" fmt_codes = "mmincox","incox","incog_hex","xincog","ix","xi" ext = "mmincox" diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index 5142b663..6654853a 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -28,9 +28,9 @@ def print_help(opts_data): print (" %-"+pn_len+"s %s") % (pn.upper()+":", opts_data['desc'].strip()) print (" %-"+pn_len+"s %s %s")%("USAGE:", pn, opts_data['usage'].strip()) sep = "\n " - print " OPTIONS:"+sep+"%s" % sep.join(opts_data['options'].strip().split("\n")) + print " OPTIONS:"+sep+"%s" % sep.join(opts_data['options'].strip().splitlines()) if "notes" in opts_data: - print " %s" % "\n ".join(opts_data['notes'][1:-1].split("\n")) + print " %s" % "\n ".join(opts_data['notes'][1:-1].splitlines()) def process_opts(argv,opts_data,short_opts,long_opts): @@ -86,7 +86,7 @@ def parse_opts(argv,opts_data,opt_filter=None): pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)" od,skip = [],True - for l in opts_data['options'].strip().split("\n"): + for l in opts_data['options'].strip().splitlines(): m = re.match(pat,l) if m: skip = True if (opt_filter and m.group(1) not in opt_filter) else False diff --git a/mmgen/test.py b/mmgen/test.py index c960b65c..df225923 100755 --- a/mmgen/test.py +++ b/mmgen/test.py @@ -22,7 +22,7 @@ test.py: Shared routines for the test suites import sys,os from binascii import hexlify -from mmgen.util import msg,write_to_file,red,green +from mmgen.util import msg,write_data_to_file,red,green import mmgen.opt as opt def cleandir(d): @@ -49,16 +49,20 @@ def mk_tmpdir(cfg): def get_tmpfile_fn(cfg,fn): return os.path.join(cfg['tmpdir'],fn) -def write_to_tmpfile(cfg,fn,data,mode='wb'): - write_to_file(os.path.join(cfg['tmpdir'],fn),data,silent=True,mode=mode) +def write_to_tmpfile(cfg,fn,data,binary=False): + write_data_to_file( + os.path.join(cfg['tmpdir'],fn), + data, + silent=True, + binary=binary + ) -def read_from_tmpfile(cfg,fn): +def read_from_file(fn,binary=False): from mmgen.util import get_data_from_file - return get_data_from_file(os.path.join(cfg['tmpdir'],fn),silent=True) + return get_data_from_file(fn,silent=True,binary=binary) -def read_from_file(fn): - from mmgen.util import get_data_from_file - return get_data_from_file(fn,silent=True) +def read_from_tmpfile(cfg,fn,binary=False): + return read_from_file(os.path.join(cfg['tmpdir'],fn),binary=binary) def ok(): if opt.verbose or opt.exact_output: diff --git a/mmgen/tool.py b/mmgen/tool.py index 7a16a703..d72ee0ec 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -252,8 +252,9 @@ def usage(cmd): help = usage def hexdump(infile, cols=8, line_nums=True): - Msg(pretty_hexdump(get_data_from_file(infile,dash=True,silent=True), - cols=cols, line_nums=line_nums)) + Msg(pretty_hexdump( + get_data_from_file(infile,dash=True,silent=True,binary=True), + cols=cols,line_nums=line_nums)) def unhexdump(infile): if sys.platform[:3] == "win": @@ -316,42 +317,54 @@ def wif2addr(wif,compressed=False): addr = bitcoin.privnum2addr(int(s_enc,16),compressed) Vmsg_r("Addr: "); Msg(addr) -from mmgen.mnemonic import * +wordlists = "electrum","tirosh" +dfl_wordlist = "electrum" -def mn_rand128(wordlist="electrum"): do_random_mn(16,wordlist) -def mn_rand192(wordlist="electrum"): do_random_mn(24,wordlist) -def mn_rand256(wordlist="electrum"): do_random_mn(32,wordlist) +from mmgen.seed import Mnemonic +def do_random_mn(nbytes,wordlist): + hexrand = ba.hexlify(get_random(nbytes)) + Vmsg("Seed: %s" % hexrand) + for wlname in (wordlists if wordlist == "all" else [wordlist]): + if wordlist == "all": + Msg("%s mnemonic:" % (wlname.capitalize())) + mn = Mnemonic.hex2mn(hexrand,wordlist=wlname) + Msg(" ".join(mn)) -def hex2mn(s,wordlist="electrum"): - import mmgen.mnemonic - wl = get_wordlist(wordlist) - Msg(" ".join(get_mnemonic_from_seed(ba.unhexlify(s), wl, wordlist))) +def mn_rand128(wordlist=dfl_wordlist): do_random_mn(16,wordlist) +def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist) +def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist) -def mn2hex(s,wordlist="electrum"): - import mmgen.mnemonic - wl = get_wordlist(wordlist) - Msg(ba.hexlify(get_seed_from_mnemonic(s.split(),wl,True))) +def hex2mn(s,wordlist=dfl_wordlist): + mn = Mnemonic.hex2mn(s,wordlist) + Msg(" ".join(mn)) + +def mn2hex(s,wordlist=dfl_wordlist): + hexnum = Mnemonic.mn2hex(s.split(),wordlist) + Msg(hexnum) def b32tohex(s): b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" - import mmgen.mnemonic - Msg(baseNtohex(32,s,b32a)) + Msg(Mnemonic.baseNtohex(32,s,b32a)) def hextob32(s): b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" - import mmgen.mnemonic - Msg("".join(hextobaseN(32,s,b32a))) + Msg("".join(Mnemonic.hextobaseN(32,s,b32a))) -def mn_stats(wordlist="electrum"): - l = get_wordlist(wordlist) - check_wordlist(l,wordlist) +def mn_stats(wordlist=dfl_wordlist): + Mnemonic.check_wordlist(wordlist) -def mn_printlist(wordlist="electrum"): - wl = get_wordlist(wordlist) +def mn_printlist(wordlist=dfl_wordlist): + wl = Mnemonic.get_wordlist(wordlist) Msg("\n".join(wl)) -def id8(infile): Msg(make_chksum_8(get_data_from_file(infile,dash=True,silent=True))) -def id6(infile): Msg(make_chksum_6(get_data_from_file(infile,dash=True,silent=True))) +def id8(infile): + Msg(make_chksum_8( + get_data_from_file(infile,dash=True,silent=True,binary=True) + )) +def id6(infile): + Msg(make_chksum_6( + get_data_from_file(infile,dash=True,silent=True,binary=True) + )) def str2id6(s): Msg(make_chksum_6("".join(s.split()))) # List MMGen addresses and their balances: @@ -478,7 +491,7 @@ def hexlify(s): def sha256x2(s, file_input=False, hex_input=False): from hashlib import sha256 - if file_input: b = get_data_from_file(s) + if file_input: b = get_data_from_file(s,binary=True) elif hex_input: b = decode_pretty_hexdump(s) else: b = s Msg(sha256(sha256(b).digest()).hexdigest()) @@ -506,32 +519,27 @@ def hex2wif(hexpriv,compressed=False): def encrypt(infile,outfile="",hash_preset=""): - data = get_data_from_file(infile,"data for encryption") + data = get_data_from_file(infile,"data for encryption",binary=True) enc_d = mmgen_encrypt(data,"user data",hash_preset) - if outfile == '-': - write_to_stdout(enc_d,"encrypted data") - else: - if not outfile: - outfile = os.path.basename(infile) + "." + g.mmenc_ext - write_to_file(outfile,enc_d,"encrypted data",True,True) + if not outfile: + outfile = "%s.%s" % (os.path.basename(infile),g.mmenc_ext) + + write_data_to_file(outfile,enc_d,"encrypted data",binary=True) def decrypt(infile,outfile="",hash_preset=""): - enc_d = get_data_from_file(infile,"encrypted data") + enc_d = get_data_from_file(infile,"encrypted data",binary=True) while True: dec_d = mmgen_decrypt(enc_d,"user data",hash_preset) if dec_d: break msg("Trying again...") - if outfile == '-': - write_to_stdout(dec_d,"decrypted data",ask_terminal=not opt.quiet) - else: - if not outfile: - outfile = os.path.basename(infile) - if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext: - outfile = outfile[:-len(g.mmenc_ext)-1] - else: - outfile = outfile + ".dec" - write_to_file(outfile, dec_d, "decrypted data",True,True) + + if not outfile: + o = os.path.basename(infile) + outfile = remove_extension(o,g.mmenc_ext) + if outfile == o: outfile += ".dec" + + write_data_to_file(outfile,dec_d,"decrypted data",binary=True) def find_incog_data(filename,iv_id,keep_searching=False): @@ -618,4 +626,4 @@ def rand2file(outfile, nbytes, threads=4, silent=False): q2.join() f.close() -def bytespec(s): Msg(parse_nbytes(s)) +def bytespec(s): Msg(str(parse_nbytes(s))) diff --git a/mmgen/util.py b/mmgen/util.py index 6ef081c1..5cf44a7a 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -29,11 +29,12 @@ import mmgen.globalvars as g pnm = g.proj_name -_red,_grn,_yel,_cya,_reset = \ - ["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0"] +_red,_grn,_yel,_cya,_reset,_grnbg = \ + ["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0","30;102"] def red(s): return _red+s+_reset def green(s): return _grn+s+_reset +def grnbg(s): return _grnbg+s+_reset def yellow(s): return _yel+s+_reset def cyan(s): return _cya+s+_reset def nocolor(s): return s @@ -43,13 +44,13 @@ def start_mscolor(): global red,green,yellow,cyan,nocolor import os if "MMGEN_NOMSCOLOR" in os.environ: - red = green = yellow = cyan = nocolor + red = green = yellow = cyan = grnbg = nocolor else: try: import colorama colorama.init(strip=True,convert=True) except: - red = green = yellow = cyan = nocolor + red = green = yellow = cyan = grnbg = nocolor def msg(s): sys.stderr.write(s+"\n") def msg_r(s): sys.stderr.write(s) @@ -144,7 +145,12 @@ def suf(arg,suf_type): return "" if n == 1 else "s" def get_extension(f): - return os.path.splitext(f)[1][1:] + a,b = os.path.splitext(f) + return ('',b[1:])[int(len(b) > 1)] + +def remove_extension(f,e): + a,b = os.path.splitext(f) + return (f,a)[int(len(b) > 1 and b[1:] == e)] def make_chksum_N(s,nchars,sep=False): if nchars%4 or not (4 <= nchars <= 64): return False @@ -208,6 +214,11 @@ def is_utf8(s): except: return False else: return True +def is_ascii(s): + try: s.decode("ascii") + except: return False + else: return True + def match_ext(addr,ext): return addr.split(".")[-1] == ext @@ -235,7 +246,8 @@ 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")] + pat = r'^[%s]+:\s+' % hexdigits + lines = [re.sub(pat,'',l) for l in data.splitlines()] try: return unhexlify("".join(("".join(lines).split()))) except: @@ -270,20 +282,12 @@ def compare_or_die(val1, desc1, val2, desc2, e="Error"): dmsg("%s OK (%s)" % (capfirst(desc2),val2)) return True -def get_default_wordlist(): - - wl_id = g.default_wordlist - if wl_id == "electrum": from mmgen.mn_electrum import words as wl - elif wl_id == "tirosh": from mmgen.mn_tirosh import words as wl - return wl.strip().split("\n") - def open_file_or_exit(filename,mode): try: f = open(filename, mode) except: - op = "reading" if 'r' in mode else "writing" - msg("Unable to open file '%s' for %s" % (filename,op)) - sys.exit(2) + op = ("writing","reading")[int('r' in mode)] + die(2,"Unable to open file '%s' for %s" % (filename,op)) return f @@ -408,19 +412,6 @@ def confirm_or_exit(message, question, expect="YES"): die(2,"Exiting at user request") -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()) - of_maybe = os.path.relpath(of) - of = of if of_maybe.find(os.path.pardir) == 0 else of_maybe - msg("Redirecting output to file '%s'" % of) - except: - msg("Redirecting output to file") - sys.stdout.write(data) - # New function def write_data_to_file( outfile, @@ -428,19 +419,26 @@ def write_data_to_file( desc="data", ask_write=False, ask_write_prompt="", - ask_write_default_yes=False, + ask_write_default_yes=True, ask_overwrite=True, ask_tty=True, no_tty=False, - silent=False + silent=False, + binary=False ): - if opt.stdout or not sys.stdout.isatty(): + + if silent: ask_tty = ask_overwrite = False + if opt.quiet: ask_overwrite = False + + if ask_write_default_yes == False or ask_write_prompt: + ask_write = True + + if opt.stdout or not sys.stdout.isatty() or outfile in ('','-'): 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: + if ask_tty and not opt.quiet: confirm_or_exit("",'output %s to screen' % desc) else: try: of = os.readlink("/proc/%d/fd/1" % os.getpid()) # Linux @@ -450,7 +448,7 @@ def write_data_to_file( if of[:5] == "pipe:": if no_tty: die(2,"Writing %s to pipe is not allowed" % desc) - if ask_tty: + if ask_tty and not opt.quiet: confirm_or_exit("",'output %s to pipe' % desc) msg("") of2,pd = os.path.relpath(of),os.path.pardir @@ -459,138 +457,41 @@ def write_data_to_file( else: msg("Redirecting output to file") + if binary and sys.platform[:3] == "win": + import msvcrt + msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY) + sys.stdout.write(data) else: if opt.outdir: outfile = make_full_path(opt.outdir,outfile) if ask_write: + if not ask_write_prompt: ask_write_prompt = "Save %s?" % desc if not keypress_confirm(ask_write_prompt, default_yes=ask_write_default_yes): - die(1,"Exiting at user request") + die(1,"%s not saved" % capfirst(desc)) hush = False - if file_exists(outfile): - if ask_overwrite and not silent: - q = "File '%s' already exists\nOverwrite?" % outfile - confirm_or_exit("",q) - msg("Overwriting file '%s'" % outfile) + if file_exists(outfile) and ask_overwrite: + q = "File '%s' already exists\nOverwrite?" % outfile + confirm_or_exit("",q) + msg("Overwriting file '%s'" % outfile) hush = True - f = open_file_or_exit(outfile,'wb') + f = open_file_or_exit(outfile,'w'+('','b')[int(binary)]) try: f.write(data) except: - if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile)) - sys.exit(2) + die(2,"Failed to write %s to file '%s'" % (desc,outfile)) f.close - if not hush: + if not (hush or silent): 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, - mode='wb' - ): - - if opt.outdir: outfile = make_full_path(opt.outdir,outfile) - - try: os.stat(outfile) - except: pass - else: - if confirm_overwrite: - q = "File '%s' already exists\nOverwrite?" % outfile - confirm_or_exit("",q) - else: - if not silent: msg("Overwriting file '%s'" % outfile) - - f = open_file_or_exit(outfile,mode) - try: - f.write(data) - except: - 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'" % (capfirst(desc),outfile)) - return True - - -def write_to_file_or_stdout(outfile, data, desc="data"): - - if opt.stdout or not sys.stdout.isatty(): - write_to_stdout(data, desc) - else: - write_to_file(outfile,data,desc,not opt.quiet,True) - - from mmgen.bitcoin import b58decode_pad,b58encode_pad -def display_control_data(label,metadata,hash_preset,salt,enc_seed): - Msg("WALLET DATA") - fs = " {:18} {}" - pw_empty = "yes" if metadata[3] == "E" else "no" - for i in ( - ("Label:", label), - ("Seed ID:", metadata[0].upper()), - ("Key ID:", metadata[1].upper()), - ("Seed length:", "%s bits (%s bytes)" % - (metadata[2],int(metadata[2])/8)), - ("Scrypt params:", "Preset '%s' (%s)" % (hash_preset, - " ".join([str(i) for i in get_hash_params(hash_preset)]))), - ("Passphrase empty?", pw_empty.capitalize()), - ("Timestamp:", "%s UTC" % metadata[4]), - ): Msg(fs.format(*i)) - - fs = " {:6} {}" - for i in ( - ("Salt:", ""), - (" b58:", b58encode_pad(salt)), - (" hex:", hexlify(salt)), - ("Encrypted seed:", ""), - (" b58:", b58encode_pad(enc_seed)), - (" hex:", hexlify(enc_seed)) - ): Msg(fs.format(*i)) - - -def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed): - - seed_id = make_chksum_8(seed) - seed_len = str(len(seed)*8) - pw_status = "NE" if len(passwd) else "E" - hash_preset = opt.hash_preset - label = opt.label or "No Label" - metadata = seed_id.lower(),key_id.lower(),seed_len,\ - pw_status,make_timestamp() - sf = b58encode_pad(salt) - esf = b58encode_pad(enc_seed) - - lines = ( - label, - "{} {} {} {} {}".format(*metadata), - "{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)), - "{} {}".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)) - outfile="{}-{}[{},{}].{}".format( - seed_id,key_id,seed_len,hash_preset,g.wallet_ext) - - d = "\n".join((chk,)+lines)+"\n" - write_to_file(outfile,d,"wallet",not opt.quiet,True) - - if opt.debug: - display_control_data(label,metadata,hash_preset,salt,enc_seed) - - def _check_mmseed_format(words): valid = False @@ -638,50 +539,6 @@ def _check_chksum_6(chk,val,desc,infile): dmsg("%s checksum passed: %s" % (capfirst(desc),chk)) -def get_data_from_wallet(infile,silent=False): - - # Don't make this a qmsg: User will be prompted for passphrase and must see - # the filename. - if not silent and not opt.quiet: - msg("Getting {pnm} wallet data from file '{f}'".format(pnm=pnm,f=infile)) - - f = open_file_or_exit(infile, 'r') - - lines = [i.strip() for i in f.readlines()] - f.close() - - _check_wallet_format(infile, lines) - - label = lines[1] - - metadata = lines[2].split() - - for i in 0,1: metadata[i] = metadata[i].upper() - - hd = lines[3].split() - hash_preset = hd[0][:-1] - hash_params = [int(i) for i in hd[1:]] - - if hash_params != get_hash_params(hash_preset): - msg("Hash parameters '%s' don't match hash preset '%s'" % - (" ".join(hash_params), hash_preset)) - sys.exit(9) - - res = {} - for i,key in (4,"salt"),(5,"enc_seed"): - l = lines[i].split() - val = "".join(l[1:]) - _check_chksum_6(l[0], val, key, infile) - res[key] = b58decode_pad(val) - if res[key] == False: - msg("Invalid b58 number: %s" % val) - sys.exit(9) - - _check_chksum_6(lines[0], " ".join(lines[1:]), "Master", infile) - - return label,metadata,hash_preset,res['salt'],res['enc_seed'] - - def get_words_from_user(prompt): # split() also strips words = my_raw_input(prompt, echo=opt.echo_passphrase).split() @@ -719,7 +576,7 @@ 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() + lines = f.read().splitlines() # DOS-safe f.close() return remove_comments(lines) if trim_comments else lines @@ -729,11 +586,11 @@ def get_data_from_user(desc="data",silent=False): dmsg("User input: [%s]" % data) return data -def get_data_from_file(infile,desc="data",dash=False,silent=False): +def get_data_from_file(infile,desc="data",dash=False,silent=False,binary=False): if dash and infile == "-": return sys.stdin.read() if not silent: qmsg("Getting %s from file '%s'" % (desc,infile)) - f = open_file_or_exit(infile,'rb') + f = open_file_or_exit(infile,'r'+('','b')[int(binary)]) data = f.read() f.close() return data @@ -757,7 +614,7 @@ def get_seed_from_seed_data(words): msg("Invalid b58 number: %s" % val) return False - msg("Valid seed data for seed ID %s" % make_chksum_8(seed)) + msg("Valid seed data for Seed ID %s" % make_chksum_8(seed)) return seed else: msg("Invalid checksum for {pnm} seed".format(pnm=pnm)) @@ -787,8 +644,7 @@ def get_mmgen_passphrase(desc,passchg=False): def get_bitcoind_passphrase(prompt): if opt.passwd_file: pwfile_reuse_warning() - return get_data_from_file(opt.passwd_file, - "passphrase").strip("\r\n") + return get_data_from_file(opt.passwd_file,"passphrase").strip("\r\n") else: return my_raw_input(prompt, echo=opt.echo_passphrase) diff --git a/setup.py b/setup.py index 6787b401..46872d1b 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ setup( 'mmgen.filename', 'mmgen.license', 'mmgen.mn_electrum', - 'mmgen.mnemonic', 'mmgen.mn_tirosh', 'mmgen.obj', 'mmgen.opts', diff --git a/test/test.py b/test/test.py index 573921c7..030d3f95 100755 --- a/test/test.py +++ b/test/test.py @@ -140,6 +140,7 @@ cfgs = { 'addr_idx_list': "1010,500-501,31-33,1,33,500,1011", # 8 addresses 'dep_generators': { 'mmdat': "refwalletgen1", + pwfile: "refwalletgen1", 'addrs': "refaddrgen1", 'akeys.mmenc': "refkeyaddrgen1" }, @@ -165,6 +166,7 @@ cfgs = { 'addr_idx_list': "1010,500-501,31-33,1,33,500,1011", # 8 addresses 'dep_generators': { 'mmdat': "refwalletgen2", + pwfile: "refwalletgen2", 'addrs': "refaddrgen2", 'akeys.mmenc': "refkeyaddrgen2" }, @@ -197,6 +199,7 @@ cfgs = { 'addr_idx_list': "1010,500-501,31-33,1,33,500,1011", # 8 addresses 'dep_generators': { 'mmdat': "refwalletgen3", + pwfile: "refwalletgen3", 'addrs': "refaddrgen3", 'akeys.mmenc': "refkeyaddrgen3" }, @@ -221,9 +224,15 @@ for a,b in ('6','11'),('7','12'),('8','13'): cfgs[b]['tmpdir'] = os.path.join("test","tmp"+b) from collections import OrderedDict -cmd_data = OrderedDict([ + +cmd_group = OrderedDict() + +cmd_group['help'] = OrderedDict([ # test description depends ['helpscreens', (1,'help screens', [],1)], +]) + +cmd_group['main'] = OrderedDict([ ['walletgen', (1,'wallet generation', [[[],1]],1)], # ['walletchk', (1,'wallet check', [[["mmdat"],1]])], ['passchg', (5,'password, label and hash preset change',[[["mmdat",pwfile],1]],1)], @@ -246,7 +255,7 @@ cmd_data = OrderedDict([ ['addrgen_incog_hex',(1,'address generation from mmincog hex file',[[["mmincox","addrs"],1]])], ['addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,"addrs"],1]])], - ['keyaddrgen', (1,'key-address file generation', [[["mmdat"],1]])], + ['keyaddrgen', (1,'key-address file generation', [[["mmdat",pwfile],1]])], ['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])], ['walletgen2',(2,'wallet generation (2), 128-bit seed', [])], @@ -264,15 +273,18 @@ cmd_data = OrderedDict([ ['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]])], +]) + +cmd_group['tool'] = OrderedDict([ + ['tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [],1)], + ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[cfgs['9']['tool_enc_infn'],cfgs['9']['tool_enc_infn']+".mmenc"],9]],1)], # ['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]])], ['pywallet', (9,"'mmgen-pywallet'", [],1)], ]) # saved reference data -cmd_data_ref = ( +cmd_group['ref'] = ( # reading ('ref_wallet_chk', ([],'saved reference wallet')), ('ref_seed_chk', ([],'saved seed file')), @@ -280,13 +292,13 @@ cmd_data_ref = ( ('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')) + ('refwalletgen', ([],'gen new refwallet')), + ('refaddrgen', (["mmdat",pwfile],'new refwallet addr chksum')), + ('refkeyaddrgen', (["mmdat",pwfile],'new refwallet key-addr chksum')) ) # misc. saved reference data -cmd_data_ref_other = ( +cmd_group['ref_other'] = ( ('ref_addrfile_chk', 'saved reference address file'), ('ref_keyaddrfile_chk','saved reference key-address file'), # Create the fake inputs: @@ -297,7 +309,7 @@ cmd_data_ref_other = ( ) # mmgen-walletconv: -cmd_data_conv_in = ( # reading +cmd_group['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'), @@ -307,7 +319,8 @@ cmd_data_conv_in = ( # reading ('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 + +cmd_group['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'), @@ -316,28 +329,45 @@ cmd_data_conv_out = ( # writing ('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)]), -]) +cmd_list = OrderedDict() +for k in cmd_group: cmd_list[k] = [] -for a,b in cmd_data_ref: +cmd_data = OrderedDict() +for k,v in ( + ('help', ("help screens",[])), + ('main', ("basic operations",[1,2,3,4,5])), + ('tool', ("tools",[9])) + ): + cmd_data['info_'+k] = v + for i in cmd_group[k]: + cmd_list[k].append(i) + cmd_data[i] = cmd_group[k][i] + +cmd_data['info_ref'] = "reference data",[6,7,8] +for a,b in cmd_group['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]]) + k = a+str(i) + cmd_list['ref'].append(k) + cmd_data[k] = (5+i,"%s (%s-bit)" % (b[1],j),[[b[0],5+i]],1) -for a,b in cmd_data_ref_other: - cmd_data[a] = (8,b,[[[],8]]) +cmd_data['info_ref_other'] = "other reference data",[8] +for a,b in cmd_group['ref_other']: + cmd_list['ref_other'].append(a) + cmd_data[a] = (8,b,[[[],8]],1) -for a,b in cmd_data_conv_in: +cmd_data['info_conv_in'] = "wallet conversion from reference data",[11,12,13] +for a,b in cmd_group['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]]) + k = a+str(i) + cmd_list['conv_in'].append(k) + cmd_data[k] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]],1) -for a,b in cmd_data_conv_out: +cmd_data['info_conv_out'] = "wallet conversion to reference data",[11,12,13] +for a,b in cmd_group['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]]) + k = a+str(i) + cmd_list['conv_out'].append(k) + cmd_data[k] = (10+i,"%s (%s-bit)" % (b,j),[[[],10+i]],1) utils = { 'check_deps': 'check dependencies for specified command', @@ -347,7 +377,7 @@ utils = { addrs_per_wallet = 8 # total of two outputs must be < 10 BTC -for k in cfgs.keys(): +for k in cfgs: cfgs[k]['amts'] = [0,0] for idx,mod in (0,6),(1,4): cfgs[k]['amts'][idx] = "%s.%s" % ((getrandnum(2) % mod), str(getrandnum(4))[:5]) @@ -366,23 +396,23 @@ meta_cmds = OrderedDict([ ['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","pywallet")], + ['saved_ref1', [c[0]+"1" for c in cmd_group['ref']]], + ['saved_ref2', [c[0]+"2" for c in cmd_group['ref']]], + ['saved_ref3', [c[0]+"3" for c in cmd_group['ref']]], - ['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_group['ref_other']]], - ['saved_ref_other', [c[0] for c in cmd_data_ref_other]], + ['saved_ref_conv_in1', [c[0]+"1" for c in cmd_group['conv_in']]], + ['saved_ref_conv_in2', [c[0]+"2" for c in cmd_group['conv_in']]], + ['saved_ref_conv_in3', [c[0]+"3" for c in cmd_group['conv_in']]], - ['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]], + ['saved_ref_conv_out1', [c[0]+"1" for c in cmd_group['conv_out']]], + ['saved_ref_conv_out2', [c[0]+"2" for c in cmd_group['conv_out']]], + ['saved_ref_conv_out3', [c[0]+"3" for c in cmd_group['conv_out']]], ]) +del cmd_group + opts_data = { # 'sets': [('non_interactive',bool,'verbose',None)], 'desc': "Test suite for the MMGen suite", @@ -417,6 +447,7 @@ ni = bool(opt.non_interactive) # Disable MS color in spawned scripts due to bad interactions os.environ["MMGEN_NOMSCOLOR"] = "1" +os.environ["MMGEN_NOLICENSE"] = "1" if opt.debug_scripts: os.environ["MMGEN_DEBUG"] = "1" @@ -453,22 +484,26 @@ def errmsg_r(s): stderr_save.write(s) if opt.list_cmds: fs = " {:<{w}} - {}" - Msg("AVAILABLE COMMANDS:") + Msg(green("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)) + if cmd[:5] == "info_": + m = capfirst(cmd_data[cmd][0]) + msg(green(" %s:"% m)) + continue + Msg(" "+fs.format(cmd,cmd_data[cmd][1],w=w)) w = max([len(i) for i in meta_cmds]) - Msg("\nAVAILABLE METACOMMANDS:") + Msg(green("\nAVAILABLE METACOMMANDS:")) for cmd in meta_cmds: 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)) + w = max([len(i) for i in cmd_list]) + Msg(green("\nAVAILABLE COMMAND GROUPS:")) + for g in cmd_list: + Msg(fs.format(g," ".join(cmd_list[g]),w=w)) - Msg("\nAVAILABLE UTILITIES:") + Msg(green("\nAVAILABLE UTILITIES:")) w = max([len(i) for i in utils]) for cmd in sorted(utils): Msg(fs.format(cmd,utils[cmd],w=w)) @@ -478,10 +513,17 @@ import time,re try: import pexpect except: # Windows - msg(red("MS Windows detected (or missing pexpect module). Running in non-interactive mode")) + m = green("MS Windows detected (or missing pexpect module). Skipping some tests.\n") + n = green("Interactive mode. User prompts will be ") + p = grnbg("HIGHLIGHTED IN GREEN") + q = green(".\nContinue?") ni = True + from mmgen.util import keypress_confirm + if not keypress_confirm(m+n+p+q,default_yes=True): + errmsg("Exiting at user request") + sys.exit() -from mmgen.util import get_data_from_file,write_to_file,get_lines_from_file +from mmgen.util import get_data_from_file,write_data_to_file,get_lines_from_file def my_send(p,t,delay=send_delay,s=False): if delay: time.sleep(delay) @@ -520,10 +562,11 @@ def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False): my_send(p,t,delay,s) return ret -def get_file_with_ext(ext,mydir,delete=True): +def get_file_with_ext(ext,mydir,delete=True,no_dot=False): + dot = ('.','')[int(no_dot)] flist = [os.path.join(mydir,f) for f in os.listdir(mydir) - if f == ext or f[-(len(ext)+1):] == "."+ext] + if f == ext or f[-len(dot+ext):] == dot+ext] if not flist: return False @@ -565,13 +608,17 @@ class MMGenExpect(object): cyan("'%s %s'\n" % (mmgen_cmd," ".join(cmd_args))) ) else: - msg_r("Testing %s: " % desc) + m = "Testing %s: " % desc + msg_r(yellow(m) if ni else m) if opt.direct_exec or ni: msg("") - from subprocess import check_call,check_output - f = (check_call,check_output)[int(no_output)] - f(["python", mmgen_cmd] + cmd_args) + from subprocess import call,check_output + f = (call,check_output)[int(no_output)] + ret = f(["python", mmgen_cmd] + cmd_args) + if f == call and ret != 0: + m = "Warning: process returned a non-zero exit status (%s)" + msg(red(m % ret)) else: if opt.traceback: cmd_args = [mmgen_cmd] + cmd_args @@ -580,6 +627,7 @@ class MMGenExpect(object): if opt.exact_output: self.p.logfile = sys.stdout def license(self): + if "MMGEN_NOLICENSE" in os.environ: return p = "'w' for conditions and warranty info, or 'c' to continue: " my_expect(self.p,p,'c') @@ -623,11 +671,11 @@ class MMGenExpect(object): ret = my_expect(self.p,s1 if overwrite_unlikely else [s1,s2]) if ret == 1: my_send(self.p,"YES\n") - if oo: - outfile = self.expect_getend("Overwriting file '").rstrip("'") - return outfile - else: - 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" % (desc,cyan(outfile.replace("'","")))) return outfile @@ -688,7 +736,7 @@ def add_fake_unspent_entry(out,address,comment): def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input=''): out = [] - for s in tx_data.keys(): + for s in tx_data: sid = tx_data[s]['sid'] a = adata.addrinfo(sid) for n,(idx,btcaddr) in enumerate(a.addrpairs(),1): @@ -700,28 +748,29 @@ def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input='') privnum = getrandnum(32) btcaddr = privnum2addr(privnum,compressed=True) of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn) - write_to_file(of, hextowif("{:064x}".format(privnum), - compressed=True)+"\n","compressed bitcoin key") + write_data_to_file(of, hextowif("{:064x}".format(privnum), + compressed=True)+"\n","compressed bitcoin key",silent=True) add_fake_unspent_entry(out,btcaddr,"Non-MMGen address") # msg("\n".join([repr(o) for o in out])); sys.exit() - write_to_file(unspent_data_file,repr(out),"Unspent outputs",verbose=True) + write_data_to_file(unspent_data_file,repr(out),"Unspent outputs",silent=True) -def add_comments_to_addr_file(addrfile,tfile): +def add_comments_to_addr_file(addrfile,outfile): silence() msg(green("Adding comments to address file '%s'" % addrfile)) from mmgen.addr import AddrInfo a = AddrInfo(addrfile) - for i in a.idxs(): a.set_comment(idx,"Test address %s" % idx) - write_to_file(tfile,a.fmt_data(),{}) + for n,idx in enumerate(a.idxs(),1): + if n % 2: a.set_comment(idx,"Test address %s" % n) + write_data_to_file(outfile,a.fmt_data(enable_comments=True),silent=True) end_silence() def make_brainwallet_file(fn): # Print random words with random whitespace in between from mmgen.mn_tirosh import words - wl = words.split("\n") + wl = words.split() nwords,ws_list,max_spaces = 10," \n",5 def rand_ws_seq(): nchars = getrandnum(1) % max_spaces + 1 @@ -729,7 +778,7 @@ def make_brainwallet_file(fn): rand_pairs = [wl[getrandnum(4) % len(wl)] + rand_ws_seq() for i in range(nwords)] d = "".join(rand_pairs).rstrip() + "\n" if opt.verbose: msg_r("Brainwallet password:\n%s" % cyan(d)) - write_to_file(fn,d,"brainwallet password") + write_data_to_file(fn,d,"brainwallet password",silent=True) def do_between(): if opt.pause: @@ -745,7 +794,14 @@ def do_between(): rebuild_list = OrderedDict() -def check_needs_rerun(ts,cmd,build=False,root=True,force_delete=False,dpy=False): +def check_needs_rerun( + ts, + cmd, + build=False, + root=True, + force_delete=False, + dpy=False + ): rerun = True if root else False # force_delete is not passed to recursive call @@ -821,16 +877,14 @@ def check_deps(cmds): # mmsg(cmd,c) -def clean(dirs=[]): - ts = MMGenTestSuite() - dirlist = ts.list_tmp_dirs() - if not dirs: dirs = dirlist.keys() +def clean(usr_dirs=[]): + all_dirs = MMGenTestSuite().list_tmp_dirs() + dirs = (usr_dirs or all_dirs) for d in sorted(dirs): - if d in dirlist: - cleandir(dirlist[d]) + if str(d) in all_dirs: + cleandir(all_dirs[str(d)]) else: - msg("%s: invalid directory number" % d) - sys.exit(1) + die(1,"%s: invalid directory number" % d) class MMGenTestSuite(object): @@ -897,19 +951,26 @@ class MMGenTestSuite(object): hp_arg = "-p%s" % ref_wallet_hash_preset label = "test.py ref. wallet (pw '%s', seed len %s)" \ % (ref_wallet_brainpass,cfg['seed_len']) - args = ["-d",cfg['tmpdir'],hp_arg,"-r10",sl_arg,"-ib","-L",label] - t = MMGenExpect(name,"mmgen-walletconv", args) + bf = "ref.mmbrain" + args = ["-d",cfg['tmpdir'],hp_arg,sl_arg,"-ib","-L",label] + write_to_tmpfile(cfg,bf,ref_wallet_brainpass) + write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) + if ni: + add_args = ["-r0", "-q", "-P%s" % get_tmpfile_fn(cfg,pwfile), + get_tmpfile_fn(cfg,bf)] + else: + add_args = ["-r10"] + t = MMGenExpect(name,"mmgen-walletconv", args + add_args) + if ni: return t.license() t.expect("Enter brainwallet: ", ref_wallet_brainpass+"\n") t.passphrase_new("new MMGen wallet",cfg['wpasswd']) t.usr_rand(10) sid = t.written_to_file("MMGen wallet").split("-")[0].split("/")[-1] - refcheck("seed ID",sid,cfg['seed_id']) + refcheck("Seed ID",sid,cfg['seed_id']) def refwalletgen(self,name): self.brainwalletgen_ref(name) - refwalletgen1 = refwalletgen2 = refwalletgen3 = refwalletgen - def passchg(self,name,wf,pf): # ni: reuse password, since there's no way to change it non-interactively silence() @@ -930,18 +991,34 @@ class MMGenTestSuite(object): t.written_to_file("MMGen wallet") ok() - def walletchk(self,name,wf,pf): + def walletchk(self,name,wf,pf,desc="MMGen wallet", + add_args=[],sid=None,pw=False,extra_desc=""): args = ["-P",pf,"-q"] if ni and pf else [] hp = cfg['hash_preset'] if 'hash_preset' in cfg else '1' - t = MMGenExpect(name,"mmgen-walletchk", args + ["-p",hp] + [wf]) - if ni: return - t.expect("Getting MMGen wallet from file '%s'" % wf) - t.passphrase("MMGen wallet",cfg['wpasswd']) - t.expect("Passphrase is OK") - t.expect_getend("Valid MMGen wallet for seed ID ") - ok() + wf_arg = [wf] if wf else [] + t = MMGenExpect(name,"mmgen-walletchk", + add_args+args+["-p",hp]+wf_arg, + extra_desc=extra_desc) + if ni: + if sid: + n = "" if desc == "MMGen wallet" else " should be" + m = grnbg("Seed ID%s:" % n) + msg(grnbg("%s %s" % (m,cyan(sid)))) + return + if desc != "hidden incognito data": + t.expect("Getting %s from file '%s'" % (desc,wf)) + if pw: + t.passphrase(desc,cfg['wpasswd']) + t.expect( + ["Passphrase is OK", "Passphrase.* are correct"], + regex=True + ) + chk = t.expect_getend("Valid %s for Seed ID " % desc)[:8] + if sid: cmp_or_die(chk,sid) + else: ok() - walletchk_newpass = walletchk + def walletchk_newpass (self,name,wf,pf): + return self.walletchk(name,wf,pf,pw=True) def addrgen(self,name,wf,pf,check_ref=False): add_args = ["-P",pf,"-q"] if ni else [] @@ -958,11 +1035,9 @@ class MMGenTestSuite(object): t.written_to_file("Addresses",oo=True) ok() - def refaddrgen(self,name,wf): + def refaddrgen(self,name,wf,pf): d = " (%s-bit seed)" % cfg['seed_len'] - self.addrgen(name,wf,pf="",check_ref=True) - - refaddrgen1 = refaddrgen2 = refaddrgen3 = refaddrgen + self.addrgen(name,wf,pf=pf,check_ref=True) def addrimport(self,name,addrfile): add_args = ["-q","-t"] if ni else [] @@ -1010,7 +1085,7 @@ class MMGenTestSuite(object): btcaddr = privnum2addr(getrandnum(32),compressed=True) cmd_args = ["-d",cfg['tmpdir']] - for num in tx_data.keys(): + for num in tx_data: s = tx_data[num] cmd_args += [ "%s:%s,%s" % (s['sid'],s['addr_idxs'][0],cfgs[num]['amts'][0]), @@ -1029,13 +1104,13 @@ class MMGenTestSuite(object): add_args = ["-q"] if ni else [] if ni: - m = "Answer the interactive prompts as follows:\n" + \ - " 'y', 'y', 'q', '1-8', ENTER, ENTER, ENTER, 'y'" - msg(green(m)) + m = "\nAnswer the interactive prompts as follows:\n" + \ + " 'y', 'y', 'q', '1-8', ENTER, ENTER, ENTER, 'y'" + msg(grnbg(m)) t = MMGenExpect(name,"mmgen-txcreate",add_args + cmd_args) if ni: return t.license() - for num in tx_data.keys(): + for num in tx_data: t.expect_getend("Getting address data from file ") chk=t.expect_getend(r"Checksum for address data .*?: ",regex=True) verify_checksum_or_exit(tx_data[num]['chk'],chk) @@ -1048,7 +1123,7 @@ class MMGenTestSuite(object): errmsg(red("Error: unable to connect to bitcoind. Exiting")) sys.exit(1) - for num in tx_data.keys(): + for num in tx_data: t.expect("Continue anyway? (y/N): ","y") t.expect(r"'q' = quit sorting, .*?: ","M", regex=True) t.expect(r"'q' = quit sorting, .*?: ","q", regex=True) @@ -1067,15 +1142,15 @@ class MMGenTestSuite(object): def txsign_end(self,t,tnum=None): t.expect("Signing transaction") t.expect("Edit transaction comment? (y/N): ","\n") - t.expect("Save signed transaction? (y/N): ","y") + t.expect("Save signed transaction? (Y/n): ","y") add = " #" + tnum if tnum else "" - t.written_to_file("Signed transaction" + add) + t.written_to_file("Signed transaction" + add, oo=True) def txsign(self,name,txfile,wf,pf="",save=True): add_args = ["-q","-P",pf] if ni else [] if ni: - m = "Answer the interactive prompts as follows:\n ENTER, ENTER, 'y'" - msg(green(m)) + m = "\nAnswer the interactive prompts as follows:\n ENTER, ENTER, ENTER" + msg(grnbg(m)) t = MMGenExpect(name,"mmgen-txsign", add_args + ["-d",cfg['tmpdir'],txfile,wf]) if ni: return @@ -1086,8 +1161,7 @@ class MMGenTestSuite(object): self.txsign_end(t) else: t.expect("Edit transaction comment? (y/N): ","\n") - t.expect("Save signed transaction? (y/N): ","\n") - t.expect("Signed transaction not saved") + t.close() ok() def txsend(self,name,sigfile): @@ -1156,7 +1230,7 @@ class MMGenTestSuite(object): t = MMGenExpect(name,"mmgen-addrgen", add_arg + ["-i"+in_fmt,"-d",cfg['tmpdir'],wf,cfg['addr_idx_list']]) t.license() - t.expect_getend("Valid %s for seed ID " % desc) + 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) if stdout: t.read() @@ -1192,9 +1266,14 @@ class MMGenTestSuite(object): self.addrgen_incog(name,[],"",in_fmt="hi",desc="hidden incognito data", args=["-H","%s,%s"%(rf,hincog_offset),"-l",str(hincog_seedlen)]) - def keyaddrgen(self,name,wf,check_ref=False): - t = MMGenExpect(name,"mmgen-keygen", - ["-d",cfg['tmpdir'],wf,cfg['addr_idx_list']]) + def keyaddrgen(self,name,wf,pf,check_ref=False): + args = ["-d",cfg['tmpdir'],wf,cfg['addr_idx_list']] + if ni: + m = "\nAnswer 'n' at the interactive prompt" + msg(grnbg(m)) + args = ["-q","-P",pf] + args + t = MMGenExpect(name,"mmgen-keygen", args) + if ni: return t.license() t.passphrase("MMGen wallet",cfg['wpasswd']) chk = t.expect_getend(r"Checksum for key-address data .*?: ",regex=True) @@ -1207,10 +1286,8 @@ class MMGenTestSuite(object): t.written_to_file("Secret keys",oo=True) ok() - def refkeyaddrgen(self,name,wf): - self.keyaddrgen(name,wf,check_ref=True) - - refkeyaddrgen1 = refkeyaddrgen2 = refkeyaddrgen3 = refkeyaddrgen + def refkeyaddrgen(self,name,wf,pf): + self.keyaddrgen(name,wf,pf,check_ref=True) def txsign_keyaddr(self,name,keyaddr_file,txfile): t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],"-M",keyaddr_file,txfile]) @@ -1262,7 +1339,7 @@ class MMGenTestSuite(object): self.txsign_end(t) ok() - def brainwalletgen_pwfile(self,name): + def walletgen4(self,name): bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename']) make_brainwallet_file(bwf) seed_len = str(cfg['seed_len']) @@ -1275,8 +1352,6 @@ class MMGenTestSuite(object): t.written_to_file("MMGen wallet") ok() - def walletgen4(self,name): self.brainwalletgen_pwfile(name) - def addrgen4(self,name,wf): self.addrgen(name,wf,pf="") @@ -1302,34 +1377,50 @@ class MMGenTestSuite(object): else: d = os.urandom(1033) tmp_fn = cfg['tool_enc_infn'] - write_to_tmpfile(cfg,tmp_fn,d) + write_to_tmpfile(cfg,tmp_fn,d,binary=True) infn = get_tmpfile_fn(cfg,tmp_fn) - t = MMGenExpect(name,"mmgen-tool",["-d",cfg['tmpdir'],"encrypt",infn]) + if ni: + pwfn = 'ni_pw' + write_to_tmpfile(cfg,pwfn,tool_enc_passwd+"\n") + pre = ["-P", get_tmpfile_fn(cfg,pwfn)] + app = ["hash_preset=1"] + else: + pre,app = [],[] + t = MMGenExpect(name,"mmgen-tool",pre+["-d",cfg['tmpdir'],"encrypt",infn]+app) + if ni: return t.hash_preset("user data",'1') t.passphrase_new("user data",tool_enc_passwd) t.written_to_file("Encrypted data") ok() + # Generate the reference mmenc file # def tool_encrypt_ref(self,name): # infn = get_tmpfile_fn(cfg,cfg['tool_enc_ref_infn']) -# write_to_file(infn,cfg['tool_enc_reftext'],silent=True) +# write_data_to_file(infn,cfg['tool_enc_reftext'],silent=True) # self.tool_encrypt(name,infn) def tool_decrypt(self,name,f1,f2): of = name + ".out" + if ni: + pwfn = 'ni_pw' + pre = ["-P", get_tmpfile_fn(cfg,pwfn)] + else: + pre = [] t = MMGenExpect(name,"mmgen-tool", - ["-d",cfg['tmpdir'],"decrypt",f2,"outfile="+of,"hash_preset=1"]) - 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)) - cmp_or_die(d1,d2) + pre+["-d",cfg['tmpdir'],"decrypt",f2,"outfile="+of,"hash_preset=1"]) + if not ni: + t.passphrase("user data",tool_enc_passwd) + t.written_to_file("Decrypted data") + d1 = read_from_file(f1,binary=True) + d2 = read_from_file(get_tmpfile_fn(cfg,of),binary=True) + cmp_or_die(d1,d2,skip_ok=ni) def tool_find_incog_data(self,name,f1,f2): i_id = read_from_file(f2).rstrip() vmsg("Incog ID: %s" % cyan(i_id)) t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir'],"find_incog_data",f1,i_id]) + if ni: return o = t.expect_getend("Incog data for ID %s found at offset " % i_id) os.unlink(f1) cmp_or_die(hincog_offset,int(o)) @@ -1355,54 +1446,6 @@ class MMGenTestSuite(object): t.written_to_file(capfirst(w),oo=True) if not ni: ok() - 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") - t = MMGenExpect(name,"mmgen-walletconv",opts+[infile],extra_desc="(convert)") - t.license() - if pw: - t.passphrase_new("new "+desc,cfg['wpasswd']) - t.usr_rand(10) - if " ".join(desc.split()[-2:]) == "incognito data": - for i in (1,2,3): - t.expect("Generating encryption key from OS random data ") - if desc == "hidden incognito data": - ret = t.expect(["Create? (Y/n): ","'YES' to confirm: "]) - if ret == 0: - t.send("\n") - t.expect("Enter file size: ",str(hincog_bytes)+"\n") - else: - t.send("YES\n") - if out_fmt == "w": t.label() - wf = t.written_to_file(capfirst(desc),oo=True) - ok() - if desc == "hidden incognito data": - self.keygen_chksum_chk_hincog(name,cfg['seed_id'],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']) @@ -1452,7 +1495,7 @@ class MMGenTestSuite(object): 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") + ic_f = os.path.join(cfg['tmpdir'],hincog_fn) hi_parms = "%s,%s" % (ic_f,ref_wallet_incog_offset) sl_parm = "-l" + str(cfg['seed_len']) self.walletconv_out(name, @@ -1462,103 +1505,77 @@ class MMGenTestSuite(object): 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,pf="") - - ref_wallet_chk1 = ref_wallet_chk2 = ref_wallet_chk3 = ref_wallet_chk + if ni: + write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) + pf = get_tmpfile_fn(cfg,pwfile) + else: + pf = None + self.walletchk(name,wf,pf=pf,pw=True,sid=cfg['seed_id']) def ref_seed_chk(self,name,ext=g.seed_ext): wf = os.path.join(ref_dir,"%s.%s" % (cfg['seed_id'],ext)) - 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 + desc = "seed data" if ext == g.seed_ext else "mnemonic data" + self.walletchk(name,wf,pf=None,desc=desc,sid=cfg['seed_id']) def ref_mn_chk(self,name): self.ref_seed_chk(name,ext=g.mn_ext) - ref_mn_chk1 = ref_mn_chk2 = ref_mn_chk3 = ref_mn_chk - def ref_brain_chk(self,name,bw_file=ref_bw_file): wf = os.path.join(ref_dir,bw_file) - args = ["-l%s" % cfg['seed_len'], "-p"+ref_bw_hash_preset] - self.keygen_chksum_chk(name,wf,cfg['ref_bw_seed_id'],args) - - 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("ignored by MMGen.\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", - ["-l",str(cfg['seed_len']),"-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("ignored by MMGen.\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 + add_args = ["-l%s" % cfg['seed_len'], "-p"+ref_bw_hash_preset] + self.walletchk(name,wf,pf=None,add_args=add_args, + desc="brainwallet",sid=cfg['ref_bw_seed_id']) def ref_brain_chk_spc3(self,name): self.ref_brain_chk(name,bw_file=ref_bw_file_spc) def ref_hincog_chk(self,name,desc="hidden incognito data"): - for wtype,edesc,earg in ('hic_wallet','',[]), \ + for wtype,edesc,of_arg in ('hic_wallet','',[]), \ ('hic_wallet_old','(old format)',["-O"]): - ic_arg = "%s,%s" % ( + ic_arg = ["-H%s,%s" % ( os.path.join(ref_dir,cfg[wtype]), ref_wallet_incog_offset - ) - t = MMGenExpect(name,"mmgen-keygen",["-l",str(cfg['seed_len']), - "-q","-A"]+earg+["-H"]+[ic_arg]+['1'],extra_desc=edesc) - t.hash_preset(desc,"1") + )] + slarg = ["-l%s " % cfg['seed_len']] + hparg = ["-p1"] + if ni: + write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) + add_args = ["-q","-P%s" % get_tmpfile_fn(cfg,pwfile)] + else: + add_args = [] + if ni and wtype == 'hic_wallet_old': + m = grnbg("Answer 'y' at the interactive prompt if Seed ID is") + n = cyan(cfg['seed_id']) + msg("\n%s %s" % (m,n)) + t = MMGenExpect(name,"mmgen-walletchk", + add_args + slarg + hparg + of_arg + ic_arg, + extra_desc=edesc) + if ni: continue t.passphrase(desc,cfg['wpasswd']) if wtype == 'hic_wallet_old': - t.expect("Is the seed ID correct? (Y/n): ","\n") + t.expect("Is the Seed ID correct? (Y/n): ","\n") chk = t.expect_getend("Seed ID: ") - t.expect("Encrypt key list? (y/N): ","\n") t.close() cmp_or_die(cfg['seed_id'],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']) - t = MMGenExpect(name,"mmgen-tool",[ftype+"file_chksum",wf]) + if ni: + m = "\nAnswer the interactive prompts as follows: '1', ENTER" + msg(grnbg(m)) + pfn = "ref_kafile_passwd" + write_to_tmpfile(cfg,pfn,ref_kafile_pass) + aa = ["-P",get_tmpfile_fn(cfg,pfn)] + else: + aa = [] + t = MMGenExpect(name,"mmgen-tool",aa+[ftype+"file_chksum",wf]) + if ni: + k = 'ref_%saddrfile_chksum' % ('key' if ftype == "keyaddr" else '') + m = grnbg("Checksum should be:") + n = cyan(cfg[k]) + msg(grnbg("%s %s" % (m,n))) + return if ftype == "keyaddr": w = "key-address file" t.hash_preset(w,ref_kafile_hash_preset) @@ -1576,18 +1593,153 @@ class MMGenTestSuite(object): def ref_tx_chk(self,name): tf = os.path.join(ref_dir,cfg['ref_tx_file']) wf = os.path.join(ref_dir,cfg['ref_wallet']) - self.txsign(name,tf,wf,save=False) + write_to_tmpfile(cfg,pwfile,cfg['wpasswd']) + pf = get_tmpfile_fn(cfg,pwfile) + self.txsign(name,tf,wf,pf,save=False) def ref_tool_decrypt(self,name): f = os.path.join(ref_dir,ref_enc_fn) + aa = [] + if ni: + pfn = "tool_enc_passwd" + write_to_tmpfile(cfg,pfn,tool_enc_passwd) + aa = ["-P",get_tmpfile_fn(cfg,pfn)] t = MMGenExpect(name,"mmgen-tool", - ["-q","decrypt",f,"outfile=-","hash_preset=1"]) + aa + ["-q","decrypt",f,"outfile=-","hash_preset=1"]) + if ni: return t.passphrase("user data",tool_enc_passwd) t.readline() import re o = re.sub('\r\n','\n',t.read()) cmp_or_die(sample_text,o) + # wallet conversion tests + 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)" + if ni: + opts += ["-q"] + msg("") + if pw: + pfn = "ni_passwd" + write_to_tmpfile(cfg,pfn,cfg['wpasswd']) + opts += ["-P",get_tmpfile_fn(cfg,pfn)] + if desc == "brainwallet": + m = "\nAnswer the interactive prompt as follows: '%s'" + msg(grnbg(m % ref_wallet_brainpass)) + if "-O" in uopts: + m = grnbg("Answer 'y' at the interactive prompt if Seed ID is") + n = cyan(cfg['seed_id']) + msg("\n%s %s" % (m,n)) + t = MMGenExpect(name,"mmgen-walletconv",opts+uopts+if_arg,extra_desc=d) + if ni: + m = grnbg("Seed ID should be:") + n = cyan(cfg['seed_id']) + msg(grnbg("%s %s" % (m,n))) + return + 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 + self.walletchk(name,wf,pf=None, + desc="mnemonic data", + sid=cfg['seed_id'], + extra_desc="(check)" + ) + + def walletconv_out(self,name,desc,out_fmt="w",uopts=[],uopts_chk=[],pw=False): + opts = ["-d",cfg['tmpdir'],"-p1","-o",out_fmt] + uopts + if ni: + pfn = "ni_passwd" + write_to_tmpfile(cfg,pfn,cfg['wpasswd']) + l = "Non-Interactive Test Wallet" + aa = ["-q","-L",l,"-r0","-P",get_tmpfile_fn(cfg,pfn)] + if desc == "hidden incognito data": + rd = os.urandom(ref_wallet_incog_offset+128) + write_to_tmpfile(cfg,hincog_fn,rd) + else: + aa = ["-r10"] + infile = os.path.join(ref_dir,cfg['seed_id']+".mmwords") + t = MMGenExpect(name,"mmgen-walletconv",aa+opts+[infile],extra_desc="(convert)") + + add_args = ["-l%s" % cfg['seed_len']] + if ni: + pfn = "ni_passwd" + write_to_tmpfile(cfg,pfn,cfg['wpasswd']) + pf = get_tmpfile_fn(cfg,pfn) + if desc != "hidden incognito data": + from mmgen.seed import SeedSource + ext = SeedSource.fmt_code_to_sstype(out_fmt).ext + hps = ("",",1")[int(pw)] # TODO real hp + pre_ext = "[%s%s]." % (cfg['seed_len'],hps) + wf = get_file_with_ext(pre_ext+ext,cfg['tmpdir'],no_dot=True) + else: + t.license() + if pw: + t.passphrase_new("new "+desc,cfg['wpasswd']) + t.usr_rand(10) + if " ".join(desc.split()[-2:]) == "incognito data": + for i in (1,2,3): + t.expect("Generating encryption key from OS random data ") + if desc == "hidden incognito data": + ret = t.expect(["Create? (Y/n): ","'YES' to confirm: "]) + if ret == 0: + t.send("\n") + t.expect("Enter file size: ",str(hincog_bytes)+"\n") + else: + t.send("YES\n") + if out_fmt == "w": t.label() + wf = t.written_to_file(capfirst(desc),oo=True) + pf = None + ok() + + if desc == "hidden incognito data": + add_args += uopts_chk + wf = None + self.walletchk(name,wf,pf=pf, + desc=desc,sid=cfg['seed_id'],pw=pw, + add_args=add_args, + extra_desc="(check)") + + for k in ( + "ref_wallet_conv", + "ref_mn_conv", + "ref_seed_conv", + "ref_brain_conv", + "ref_incog_conv", + "ref_incox_conv", + "ref_hincog_conv", + "ref_hincog_conv_old", + "ref_wallet_conv_out", + "ref_mn_conv_out", + "ref_seed_conv_out", + "ref_incog_conv_out", + "ref_incox_conv_out", + "ref_hincog_conv_out", + "ref_wallet_chk", + "refwalletgen", + "refaddrgen", + "ref_seed_chk", + "ref_mn_chk", + "ref_brain_chk", + "ref_hincog_chk", + "refkeyaddrgen", + ): + for i in ('1','2','3'): + locals()[k+i] = locals()[k] + + # main() if opt.pause: import termios,atexit @@ -1608,12 +1760,14 @@ try: if arg in utils: globals()[arg](cmd_args[cmd_args.index(arg)+1:]) sys.exit() + elif "info_"+arg in cmd_data: + dirs = cmd_data["info_"+arg][1] + if dirs: clean(dirs) + for cmd in cmd_list[arg]: + check_needs_rerun(ts,cmd,build=True) elif arg in meta_cmds: 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: @@ -1621,6 +1775,9 @@ try: else: clean() for cmd in cmd_data: + if cmd[:5] == "info_": + msg(green("\nTesting " + cmd_data[cmd][0])) + continue ts.do_cmd(cmd) if cmd is not cmd_data.keys()[-1]: do_between() except: diff --git a/test/tooltest.py b/test/tooltest.py index c6aa6f55..1dbea10e 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,mmsg,mdie,start_mscolor +from mmgen.util import * from collections import OrderedDict start_mscolor() @@ -108,9 +108,7 @@ if opt.list_cmds: import binascii from mmgen.test import * -from mmgen.util import get_data_from_file,write_to_file,get_lines_from_file from mmgen.tx import is_wif,is_btc_addr,is_b58_str -from mmgen.mnemonic import get_seed_from_mnemonic class MMGenToolTestSuite(object): @@ -192,12 +190,12 @@ class MMGenToolTestSuite(object): return ret def run_cmd_out(self,name,carg=None,Return=False,kwargs="",fn_idx="",extra_msg=""): - if carg: write_to_tmpfile(cfg,"%s%s.in" % (name,fn_idx),carg+"\n",mode='w') + if carg: write_to_tmpfile(cfg,"%s%s.in" % (name,fn_idx),carg+"\n") ret = self.run_cmd(name,[carg] if carg else [],kwargs=kwargs,extra_msg=extra_msg) if carg: vmsg("In: " + repr(carg)) vmsg("Out: " + repr(ret)) if ret: - write_to_tmpfile(cfg,"%s%s.out" % (name,fn_idx),ret+"\n",mode='w') + write_to_tmpfile(cfg,"%s%s.out" % (name,fn_idx),ret+"\n") if Return: return ret else: ok() else: @@ -207,10 +205,10 @@ class MMGenToolTestSuite(object): def run_cmd_randinput(self,name,strip=True): s = os.urandom(128) fn = name+".in" - write_to_tmpfile(cfg,fn,s) + write_to_tmpfile(cfg,fn,s,binary=True) ret = self.run_cmd(name,[get_tmpfile_fn(cfg,fn)],strip=strip) fn = name+".out" - write_to_tmpfile(cfg,fn,ret+"\n",mode='w') + write_to_tmpfile(cfg,fn,ret+"\n") ok() vmsg("Returned: %s" % ret) @@ -244,14 +242,14 @@ class MMGenToolTestSuite(object): def unhexdump(self,name,fn1,fn2): ret = self.run_cmd(name,[fn2],strip=False) - orig = read_from_file(fn1) + orig = read_from_file(fn1,binary=True) cmp_or_die(orig,ret) def rand2file(self,name): of = name + ".out" dlen = 1024 self.run_cmd(name,[of,str(1024),"threads=4","silent=1"],strip=False) - d = read_from_tmpfile(cfg,of) + d = read_from_tmpfile(cfg,of,binary=True) cmp_or_die(dlen,len(d)) def strtob58(self,name): self.run_cmd_out(name,getrandstr(16)) @@ -297,8 +295,8 @@ class MMGenToolTestSuite(object): for n,fi,fo,m in (1,f1,f2,""),(2,f3,f4,"from compressed"): self.run_cmd_chk(name,fi,fo,extra_msg=m) def privhex2addr(self,name,f1,f2): - key1 = read_from_file(f1) - key2 = read_from_file(f2) + key1 = read_from_file(f1).rstrip() + key2 = read_from_file(f2).rstrip() for n,args in enumerate([[key1],[key2,"compressed=1"]]): ret = self.run_cmd(name,args).rstrip() iaddr = read_from_tmpfile(cfg,"randpair%s.out" % (n+1)).split()[-1]